Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generating a dynamic table from a data set #11

Open
zikwall opened this issue Dec 1, 2017 · 22 comments
Open

Generating a dynamic table from a data set #11

zikwall opened this issue Dec 1, 2017 · 22 comments

Comments

@zikwall
Copy link

zikwall commented Dec 1, 2017

Hello! I would like to know if it is possible to generate hierarchical tables from a data set in a database?

Is it possible to make one of the following patterns?

  • Adjacency List
  • Materialized Path
  • Nested Sets
  • Closure Table
id parent_id name sort_order
1 0 F1 1
2 0 F2 3
3 0 F3 2
4 1 F4 1
5 2 F5 2
6 1 F6 2
7 10 F7 1
8 2 F8 1
9 10 F9 2
10 3 F10 2
11 3 F11 2
12 9 F12 1
13 9 F13 2

I have a table with a description of the hierarchical structure. The result is to output the following table: https://ibb.co/jg9Wjb

Tell me please, or advise how to implement it with the help of your library?

I can get an array of ancestors and children, like this:

    [parents] => Array
        (
            [F1] => 
            [F1.f4] => F1
            [F2] => 
            [F2.f5] => F2
            [F1.f6] => F1
            [F3] => 
            [F3.F10] => F3
            [F3.F10.f7] => F3.F10
            [F2.f8] => F2
            [F3.F10.f9] => F3.F10
            [F3.f10] => F3
            [F3.f11] => F3
            [F3.F10.F9] => F3.F10
            [F3.F10.F9.f12] => F3.F10.F9
            [F3.F10.F9.f13] => F3.F10.F9
        )

I do not understand how to manipulate such data, namely, to use methods addColName(), addRowName() and th().

@donquixote
Copy link
Owner

Hi,
it has been a while since I wrote and used this library, so I will play around and see how far I get.

@donquixote
Copy link
Owner

Do the machine names reflect the hierarchy always, in your use case?

@donquixote
Copy link
Owner

Ok, here is my solution.

/**
 * @var bool[] $colNames
 *   Format: $[$colName] = $isLeaf
 */
$colNames = [
  'F1' => false,
  'F1.F4' => true,
  'F1.F5' => true,
  'F2' => false,
  'F2.F6' => true,
  'F2.F8' => true,
  'F3' => false,
  'F3.F10' => false,
  'F3.F10.F7' => true,
  'F3.F10.F9' => false,
  'F3.F10.F9.F12' => true,
  'F3.F10.F9.F13' => true,
  'F3.F11' => true,
];

$maxdepth = 0;
foreach ($colNames as $colName => $isLeaf) {
  $depth = substr_count($colName, '.');
  $maxdepth = max($maxdepth, $depth);
}

$table = Table::create();
$thead = $table->thead();

$rowGroupName = 'g';
$rowGroupNames = [$rowGroupName];
for ($i = 0; $i < $maxdepth; ++$i) {
  $thead->addRow($rowGroupName . '.caption');
  $rowGroupName .= '.g';
  $rowGroupNames[] = $rowGroupName;
}

$thead->addRow($rowGroupName);

foreach ($colNames as $colName => $isLeaf) {

  if (FALSE !== $pos = strrpos($colName, '.')) {
    $caption = substr($colName, $pos + 1);
  }
  else {
    $caption = $colName;
  }

  $table->addColName($colName);

  $depth = substr_count($colName, '.');
  $rowGroupName = $rowGroupNames[$depth];
  $rowName = $isLeaf
    ? $rowGroupName
    : $rowGroupName . '.caption';

  $thead->th($rowName, $colName, $caption);
}

$dataset = [
  'F1.F4' => 'F4 data',
  'F1.F5' => 'F5 data',
  'F2.F6' => 'F6 data',
  'F2.F8' => 'F8 data',
  'F3.F10.F7' => 'F7 data',
  'F3.F10.F9.F12' => 'F12 data',
  'F3.F10.F9.F13' => 'F13 data',
  'F3.F11' => 'F11 data',
];

$table->addRow(0);
foreach ($dataset as $colName => $cellContent) {
  $table->td(0, $colName, $cellContent);
}

return $table->render();

Some things to note:

  • The machine names of the columns with "." character need to reflect the hierarchy. E.g. "F1.F4" is automatically a child of "F1".
  • In addition to the hierarchy of columns, we also need to establish a hierarchy of rows in the header.
  • $table->thead()->td(..) adds stuff to the <thead> element.
  • $table->td(..) adds stuff to the main <tbody> element.
  • Column names apply across all table sections.
  • Row names apply only to one table section, e.g. either thead or tbody.

@donquixote
Copy link
Owner

For examples like this, maybe it would be nice if one didn't need the long hierarchic machine names.
Also it would be nice if the row hierarchy in the header would be built automatically.
But in the way that the library works in the current version, I think it has to be done the way I described.

@zikwall
Copy link
Author

zikwall commented Dec 1, 2017

Do the machine names reflect the hierarchy always, in your use case?

I did not immediately notice the error in building the hierarchy, I did a little wrong recursion... In the end, I got a set of data like yours

$table->addColNames(['F1.F4', 'F1.F6', 'F2.F5', 'F2.F8', 'F3.F10', 'F3.F11', 'F3.F10.F7', 'F3.F10.F9', 'F3.F10.F9.F12', 'F3.F10.F9.F13']);

For examples like this, maybe it would be nice if one didn't need the long hierarchic machine names.

Alas, I have not found another way as adjacency tables or material path, if there are proposals I will gladly accept and test

Also it would be nice if the row hierarchy in the header would be built automatically.

This is what I strive for, your library is a huge work, thank you for this, I would not have managed without you...


$dataset = [
  'F1.F4' => 'F4 data',
  'F1.F5' => 'F5 data',
  'F2.F6' => 'F6 data',
  'F2.F8' => 'F8 data',
  'F3.F10.F7' => 'F7 data',
  'F3.F10.F9.F12' => 'F12 data',
  'F3.F10.F9.F13' => 'F13 data',
  'F3.F11' => 'F11 data',
];

$table->addRow(0);
foreach ($dataset as $colName => $cellContent) {
  $table->td(0, $colName, $cellContent);
}

I want to do some refinement on the data set, for example, not one line but 10, how would it look? How is the enclosed array?


'F1.F4' => [
        'F4 data',
        'F4 data 2',
        'F4 data 3',
        etc..

    ],

Once again, thank you very much!

@donquixote
Copy link
Owner

donquixote commented Dec 1, 2017

I am not sure I understand all of the questions, if something is missing please ask again.

I want to do some refinement on the data set, for example, not one line but 10, how would it look? How is the enclosed array?

The things you need to know for adding multiple data rows:

  • Every row needs a machine name or numeric id. In the example code I simply used a numeric id 0. Maybe if the data is coming from the database, you want to use the primary key of the entry.
  • The row machine name needs to be explicitly added like so: $table->addRow($rowMachineName);.
  • Each row cell can be added like this: $table->td($rowName, $colName, $cellContents);. Note that $cellContents needs to be safe html. Cellbrush is not responsible for any escaping.

If you want, you can use the shortcut syntax with row handles, but I want to keep it simple for now.

If you want to split up a cell within a data set:
Please show me the desired output, then we can find a solution.

@donquixote
Copy link
Owner

your library is a huge work

Sorry I am a bit confused, do you mean it is a lot of work to use it or it was a lot of work to write it?

@zikwall
Copy link
Author

zikwall commented Dec 1, 2017

Sorry I am a bit confused, do you mean it is a lot of work to use it or it was a lot of work to write it?

It's all my bad English ... No, I wanted to say that you did a great job, the result came out very worthy!

@donquixote
Copy link
Owner

ok :) thank you!

@zikwall
Copy link
Author

zikwall commented Dec 8, 2017

Hello! It's me again 😃

Last time there were a lot of problems because of my poor spoken English, I'm studying it, but so far everything has become very bad :(, so I will adjust my thoughts from Google Translate 😋

You really help me, but I still do not understand how to add extra rows of data...

How it all looks at the moment:

  1. I get all the columns in the form of a tree
The UFO came and published this code here

and initialize all

$this->_tree = $this->getArray()->createTree();

and get the sorted array (fragment) print_r($this->_tree)

Array
(
    [1] => Array
        (
            [id] => 1
            [name] => F1
            [field_name] => f1
            [internal_key] => 0
            [sort_order] => 1
            [path] => F1
            [childs] => Array
                (
                    [6] => Array
                        (
                            [id] => 6
                            [name] => F6
                            [field_name] => f6
                            [internal_key] => 1
                            [sort_order] => 1
                            [path] => F1.F6
                        )

                    [4] => Array
                        (
                            [id] => 4
                            [name] => F4
                            [field_name] => f4
                            [internal_key] => 1
                            [sort_order] => 3
                            [path] => F1.F4
                        )

                )

        )
  1. Recursively read this tree and form an array in which there are long paths, in the rest of the function your implementation is executed
public function create($data)
    {
        if(!is_array($data)){
            return;
        }

        foreach ($data as $el => $els) {
            $this->_array[$els['path']] = is_array($els['childs']) ? false : true;

            if (isset($els['childs'])) {
                $this->create($els['childs']);
            }
        }

        $this->_table = Table::create();
        $colNames = ($this->_array); 

        $maxdepth = 0;

        foreach ($colNames as $colName => $isLeaf) {
            $depth = substr_count($colName, '.');
            $maxdepth = max($maxdepth, $depth);
        }

        $thead = $this->_table->thead();

        $rowGroupName = 'g';
        $rowGroupNames = [$rowGroupName];

        //if(!array_key_exists($this->_table->thead->rows->parents[$rowGroupName], $rowGroupNames)) {
        for ($i = 0; $i < $maxdepth; ++$i) {
            $thead->addRow($rowGroupName . '.caption');
            $rowGroupName .= '.g';
            $rowGroupNames[] = $rowGroupName;
        }
        $thead->addRow($rowGroupName);
        //}

        //$this->p($this->_table->thead->rows->parents);
        foreach ($colNames as $colName => $isLeaf) {
            //if (!array_key_exists($this->_table->columns->parents[$colName], $colNames)) {
            if (false !== $pos = strrpos($colName, '.')) {
                $caption = substr($colName, $pos + 1);
            } else {
                $caption = $colName;
            }
            $this->_table->addColName($colName);
            $depth = substr_count($colName, '.');
            $rowGroupName = $rowGroupNames[$depth];
            $rowName = $isLeaf
                ? $rowGroupName
                : $rowGroupName . '.caption';
            $thead->th($rowName, $colName, $caption);
        }
        /*
         * ToDo create automatic data output from the database
         */

        $dataset = [];
        foreach ($this->_lastLevelColumns as $key => $val){
            $dataset[$val['path']] = $val['values'][0]['value'];
        }

        //$this->p($dataset);

        /*
        $dataset = [
            'F1.F4'         => 'F4 Data',
            'F1.F6'         => 'F5 Data',
            'F2.F5'         => 'F6 Data',
            'F2.F8'         => 'F8 Data',
            'F3.F10.F7'     => 'F7 Data',
            'F3.F10.F9.F12' => 'F12 Data',
            'F3.F10.F9.F13' => 'F13 Data',
            'F3.F11'        => 'F11 Data',
        ];
        */

        $this->_table->addRow(1);

        foreach ($dataset as $colName => $colValue) {
            $this->_table->td(1, $colName, $colValue);
        }

        return $this->_table;
    }

call

<?= $mapper->create($mapper->_tree)->render(); ?>
  1. At the end, where do you add data, how to add multiple rows, how many in the database itself in the value table?

For example, there is a method for obtaining the last branches of a tree together with the corresponding values

      The UFO came and published this code here

get such an array (fragment) print_r($this->_lastLevelColumns)

[4] => Array
        (
            [id] => 4
            [internal_key] => 1
            [name] => F4
            [values] => Array
                (
                    [0] => Array
                        (
                            [id] => 3
                            [value] => this field 4 value
                        )

                    [1] => Array
                        (
                            [id] => 13
                            [value] => value 4
                        )

                )

            [path] => F1.F4
        )

The problem is

/*
         * ToDo create automatic data output from the database
         */

        $dataset = [];
        foreach ($this->_lastLevelColumns as $key => $val){
            $dataset[$val['path']] = $val['values'][0]['value'];
        }

        //$this->p($dataset);

        /*
        $dataset = [
            'F1.F4'         => 'F4 Data',
            'F1.F6'         => 'F5 Data',
            'F2.F5'         => 'F6 Data',
            'F2.F8'         => 'F8 Data',
            'F3.F10.F7'     => 'F7 Data',
            'F3.F10.F9.F12' => 'F12 Data',
            'F3.F10.F9.F13' => 'F13 Data',
            'F3.F11'        => 'F11 Data',
        ];
        */

        $this->_table->addRow(1);

        foreach ($dataset as $colName => $colValue) {
            $this->_table->td(1, $colName, $colValue);
        }

Full code listing and dump of the database, you can look at the links

If you have free time, help me, please, I can not do this myself... :(
The quality of the code is probably terrible 👎 , I'm just a beginner programmer

@zikwall
Copy link
Author

zikwall commented Dec 8, 2017

Visual representation

Visual representation

it may be necessary to know the number of rows

@donquixote
Copy link
Owner

Wow this is a lot of detail, which is not directly related to Cellbrush library.

Some general feedback about your code

I see in your code that you are writing a lot on object properties of $this. E.g. in getArray() you write on $this->_data. and then return $this.

I would recommend to instead treat the object as immutable, and make $data a local variable which can be the return value of the method. Then in createTree() you can receive the $data as a parameter.

Also $this->_table should instead be a local variable.

I think none of the object properties that you write on need to be object properties. Everything can be local variables, parameters and return values.

This way, some of your functions / methods can become "pure", see https://en.wikipedia.org/wiki/Pure_function

Then, add doc comments everywhere, and document the type of parameters and return values.
Especially for nested arrays, they need better documentation.

E.g.

  /**
   * @param int $colId
   *
   * @return array[]
   *   Format: $[] = ['id' => .., 'value' => ..]
   */
  public function getAllValuesForColumn($colId) {..

Also rename your variables to be more meaningful.
For each variable, ask yourself: What would someone looking at the variable name imagine that this variable does?
E.g. if a variable is named $value, what does this suggest?

The entire example seems quite artificial. E.g. why are fields labeled "F1", "F2", "F3"?
Maybe more realistic data would make it easier to imagine the purpose of everything.

Feedback about the uploaded picture

In the picture it looks like for each data row, you display the values for F6, F4, F5, F8, F11, F7, F12, F13.
However, I don't see you displaying the values for F1, F2, F3, F10, F9.
But the "values" table clearly does have data for those columns. So do you want to display this data or not? If so, where and how?

Feedback about Cellbrush usage

I admit I do not fully understand the rest of your code, and I don't have time to study all of it.

But from only looking at the parts that deal with Cellbrush:

        $this->_table->addRow(1);

        foreach ($dataset as $colName => $colValue) {
            $this->_table->td(1, $colName, $colValue);
        }

Here you add the row named "1".
This means you always write on the same table row.
Instead, the row should have a dynamic key.
E.g. if you want to make one row per "id" from the "values" table, you can use the id as the row key.

@donquixote
Copy link
Owner

Here is something that might help.
In this code we iterate over the entire "values" table, and insert the value into the table.

Before this code can run, the table already needs to have all the columns.

Notes:

  • We insert into the main <tbody> section.
  • We use $row['id'] as the cellbrush row id.
  • We do NOT use $row['field_id'] as the Cellbrush column id. Instead, we determine the correct column id via the lookup map $columnIdsByFieldId.
  • Every column that is not in $columnIdsByFieldId will not be displayed.
  • Values need to be escaped before using them in html. In this case I use htmlspecialchars() for this. Otherwise you risk to output broken and insecure html.
  • I noticed that there is a method ->addRowIfNotExists($rowName) on $tbody / tsection element, but not on $table itself. We need to use ->addRowIfNotExists(), because we are likely to add the same row more than once.
  • We need to sort by id so that rows are added in the correct order.
// Visible (leaf) columns
// @todo This should be filled from the description table.
$columnIdsByFieldId = [
  6 => 'F1.F6',
  4 => 'F1.F4',
  5 => 'F2.F5',
  8 => 'F2.F8',
  11 => 'F3.F11',
  7 => 'F3.F10.F7',
  12 => 'F3.F10.F9.F12',
  13 => 'F3.F10.F9.F13',
];

$q = $this->db->query('SELECT * FROM `values` ORDER BY `id`');

$tbody = $table->tbody();

while ($row = $q->fetch_assoc()) {
  $fieldId = $row['field_id'];
  if (!isset($columnIdsByFieldId[$fieldId])) {
    // Ignore this entry.
    continue;
  }
  $rowId = $row['id'];
  $columnId = $columnIdsByFieldId[$fieldId];
  $valueUnsafe = $row['value'];
  $valueSafe = htmlspecialchars($valueUnsafe, ENT_QUOTES, 'UTF-8');
  $tbody->addRowIfNotExists($rowId);
  $tbody->td($rowId, $columnId, $valueSafe);
}

@donquixote
Copy link
Owner

You can use a shortcut for this:

  $tbody->addRowIfNotExists($rowId);
  $tbody->td($rowId, $columnId, $valueSafe);

Shortcut:

  $tbody->addRowIfNotExists($rowId)->td($columnId, $valueSafe);

I am only writing this for other people who might be interested. You have other problems than using shortcuts at this stage :)

@zikwall
Copy link
Author

zikwall commented Dec 22, 2017

@donquixote Many thanks! :) But there is one more question, I think that this will even facilitate the work. You add data in this way:

while ($row = $q->fetch_assoc()) {
  $fieldId = $row['field_id'];
  if (!isset($columnIdsByFieldId[$fieldId])) {
    // Ignore this entry.
    continue;
  }
  $rowId = $row['id'];
  $columnId = $columnIdsByFieldId[$fieldId];
  $valueUnsafe = $row['value'];
  $valueSafe = htmlspecialchars($valueUnsafe, ENT_QUOTES, 'UTF-8');
  $tbody->addRowIfNotExists($rowId);
  $tbody->td($rowId, $columnId, $valueSafe);
}

namely a row and columns

$tbody->td($rowId, $columnId, $valueSafe);

and it works fine, but I think it will be better if all this is stored in a matrix form

$dataset = [
            ['F16.F18.F20', 'F1.F6',             'x - 20; y - 6'],
            ['F16.F18.F21', 'F1.F6',             'x - 21; y - 6'],
            ['F16.F19',     'F1.F6',             'x - 19; y - 6'],

            ['F16.F18.F20', 'F1.F4',             'x - 20; y - 4'],
            ['F16.F18.F21', 'F1.F4',             'x - 21; y - 4'],
            ['F16.F19',     'F1.F4',             'x - 19; y - 4'],

            ['F16.F18.F20', 'F2.F5',             'x - 20; y - 5'],
            ['F16.F18.F21', 'F2.F5',             'x - 21; y - 5'],
            ['F16.F19',     'F2.F5',             'x - 19; y - 5'],

            ['F16.F18.F20', 'F2.F8',             'x - 20; y - 8'],
            ['F16.F18.F21', 'F2.F8',             'x - 21; y - 8'],
            ['F16.F19',     'F2.F8',             'x - 19; y - 8'],

            ['F16.F18.F20', 'F3.F10.F9.F12.F14', 'x - 20; y - 14'],
            ['F16.F18.F21', 'F3.F10.F9.F12.F14', 'x - 21; y - 14'],
            ['F16.F19',     'F3.F10.F9.F12.F14', 'x - 19; y - 14'],

            ['F16.F18.F20', 'F3.F10.F9.F13',     'x - 20; y - 13'],
            ['F16.F18.F21', 'F3.F10.F9.F13',     'x - 21; y - 13'],
            ['F16.F19',     'F3.F10.F9.F13',     'x - 19; y - 13'],
        ];

In the database, we also store the matrix

id row column value
1 21 4 x - 21; y - 4
2 20 4 x - 20; y - 4
3 19 4 x - 19; y - 4
4 21 6 x - 21; y - 6

matrix

But I do not quite understand how to properly organize this using your library... So far the simplest and not quite the right way:

       /*
         * [PATH, LEVEL(column name, should be deleted)]
         */
        $rowsVerticalHierarhy = [
            ['F16', 0],
            ['F16.F18', 1],
                ['F16.F18.F20', 2],
                ['F16.F18.F21', 2],
            ['F16.F19', 1],
        ];

        $table->addColNames([0, 1, 2]); // example columns as levels, how to do without levels?

        foreach ($rowsVerticalHierarhy as $row){
            $table->addRow($row[0]);
            $table->th($row[0], $row[1], $row[0]);
        }
// ...
// and 
        foreach ($dataset as $data){
            $tbody->td($data[0], $data[1], $data[2]);
        }

how to do it and how do I delete these additional rows in the left corner of the table, those. rowspan and colspan? :)

@zikwall
Copy link
Author

zikwall commented Dec 22, 2017

it would be awesome...

@donquixote
Copy link
Owner

Hello,
I am happy to respond to questions about the library. But I don't have the time to understand your use case and organize your code.
Please extract more specific questions.

@zikwall
Copy link
Author

zikwall commented Dec 22, 2017

Well, I'll try to put the essence, in general, there are two arrays, one of which contains a description of the horizontal hierarchy (from above), and the other - the vertical one (on the left).

And also the third array with data, where the first two elements are the keys of the last columns of the table (ie, full paths, ends of the hierarchy), and the third one - values for intersection of cells, vertical and hierarchical hierarchy, ie, a matrix.

This is the question of how to implement this decision qualitatively and beautifully? I have it was poorly implemented, there is no grouping, many extra lines, and you have it elegantly and practical... In the picture you can see by the elements of F16 (left) a lot of lines that should not be...

@zikwall
Copy link
Author

zikwall commented Dec 22, 2017

Those. the following scheme is obtained:

  1. Add columns with a hierarchy
  2. Add rows with a hierarchy
  3. Add data using known coordinates [x, y] (paths)

The first moment is perfect, the second one was made by me, it was very bad. Well, the third seems more normal, it remains only to bind to the database (but somehow I'll do it myself)

@donquixote
Copy link
Owner

Do you have more intuitively meaningful example data? Something more appealing to my imagination? Something with animals and flowers and food perhaps? Or rivers and mountains?
I don't understand what the F11 etc and the 'x - 20; y - 14' mean. If I understand correctly they are coordinates. But how do you explain the hierarchy?

@zikwall
Copy link
Author

zikwall commented Dec 24, 2017

@donquixote Well, I'll try to put the essence. I made a more intuitive hierarchy, for this I slightly reduced the depth of the hierarchy. Look at the picture.

matrix

I mentioned some cells in red - these are extra cells, and they should not exist in theory, that is, Weight row have attr rowspan = 2

Common information


The top and left hierarchies are stored in the same table with the orientation attribute:

id parent_id name field_name sort_order orientation
1 0 F1 Mazda 1 1
2 0 F2 BMW 1 1
3 0 F3 Honda 1 1
4 1 F4 Mazda6 1 1
3 2 F5 BMW M5 xDrive 1 1
... ... ... ... ... ...
16 0 F16 Characteristics 1 2
18 16 F18 Motor 1 2
20 18 F20 Speed 1 2

and the data itself is stored in the form of records with coordinates (that is, with long paths) in the form:

id row column value
1 21 4 200 km/h
2 20 4 260 h.p
3 19 4 1650
4 21 6 192 km/h
n ... ... ...

Now about the very implementation


1. Left Hierarchy:

        /* Array for LEFT TH
         * [PATH, LEVEL(COLUMN, should be deleted)]
         * LEVEL is columns from left to right. How do you do without specifying these columns?
         */
        $rowsVerticalHierarhy = [
            ['F16', 0, 'Characteristics'],
            ['F16.F18', 1, 'Motor'],
                ['F16.F18.F20', 2, 'Speed'],
                ['F16.F18.F21', 2, 'HorsePower'],
            ['F16.F19', 1, 'Weight'],
        ];

        $table->addColNames([0, 1, 2]); //artificial columns

        foreach ($rowsVerticalHierarhy as $row){
            $table->addRow($row[0]);
            $table->th($row[0], $row[1], $row[2]);
        }

Data(array) for Left Hierarchy + JSON: https://codeshare.io/a31Jv4

2. Up Hierarchy:

Data(array) for Up Hierarchy + JSON: https://codeshare.io/ay79rl

//generate array(https://codeshare.io/ay79rl)
foreach ($data as $els => $el) {
   $colNames[$el['path']] = is_array($el['childs']) ? ['leaf' => false, 'name' => $el['field_name']] : ['leaf' => true, 'name' => $el['field_name']];

    if (isset($el['childs'])) {
       $this->create($el['childs']);
    }
}
// out function create gereate  array
$colNames = [
  'F1' => ['leaf' => false, 'name' => 'Mazda']
  'F1.F6' => ['leaf' => true, 'name' => 'Mazda CX-9'],
  'F1.F4' => ['leaf' => true, 'name' => 'Mazda'],
  'F2' => ['leaf' => false, 'name' => 'BMW'],
  'F2.F8' => ['leaf' => true, 'name' => 'BMW 750Li xDrive'],
  'F2.F5' => ['leaf' => true, 'name' => 'BMW M5 xDrive'],
  'F3' => ['leaf' => false, 'name' => 'Honda'],
  'F3.F11' => ['leaf' => true, 'name' => 'CR-V 2.4 PRESTIGE CVT'],
  'F3.F10' => ['leaf' => false, 'name' => 'Motorcycle'],
  'F3.F10.F7' => ['leaf' => true, 'name' => 'CBR1000SP1 FIREBLADE'],
  'F3.F10.F9' => ['leaf' => false, 'name' => 'Africa Twin Adventure Sports 2018'],
];

//and add
         foreach ($colNames as $colName => $isLeaf) {
            /*
            if (false !== $pos = strrpos($colName, '.')) {
                $caption = substr($colName, $pos + 1);
            } else {
                $caption = $isLeaf['name'];
            }
            */
            $this->_table->addColName($colName);
            $depth = substr_count($colName, '.');
            $rowGroupName = $rowGroupNames[$depth];
            $rowName = $isLeaf['leaf']
                ? $rowGroupName
                : $rowGroupName . '.caption';
            $thead->th($rowName, $colName, $isLeaf['name']);
        }

3. Matrix Values:

Data(array) for Matrix Values + JSON: https://codeshare.io/GLgY6K

// Here the data set is an artificial imitation of data from the database
        $dataset = [
            ['F16.F18.F20', 'F1.F6',             '192 km/h'],
            ['F16.F18.F21', 'F1.F6',             '267 h.p'],
            ['F16.F19',     'F1.F6',             '2100'],

            ['F16.F18.F20', 'F1.F4',             '200 km/h'],
            ['F16.F18.F21', 'F1.F4',             '260 h.p'],
            ['F16.F19',     'F1.F4',             '1650'],

            ['F16.F18.F20', 'F2.F5',             '305 km/h'],
            ['F16.F18.F21', 'F2.F5',             '600 h.p'],
            ['F16.F19',     'F2.F5',             '1700'],

            ['F16.F18.F20', 'F2.F8',             '240 km/h'],
            ['F16.F18.F21', 'F2.F8',             '444 h.p'],
            ['F16.F19',     'F2.F8',             '2200'],
            
            ['F16.F18.F20', 'F3.F11',            '... km/h'],
            ['F16.F18.F21', 'F3.F11',            '... h.p'],
            ['F16.F19',     'F3.F11',            '....'],
            
            ['F16.F18.F20', 'F3.F10.F7',         '... km/h'],
            ['F16.F18.F21', 'F3.F10.F7',         '... h.p'],
            ['F16.F19',     'F3.F10.F7',         '...'],
            
            ['F16.F18.F20', 'F3.F10.F9',         '... km/h'],
            ['F16.F18.F21', 'F3.F10.F9',         '... h.p'],
            ['F16.F19',     'F3.F10.F9',         '...'],
            
            
        ];

        foreach ($dataset as $data){
            $tbody->td($data[0], $data[1], $data[2]);
        }

@donquixote
Copy link
Owner

Ok, this example is more intuitive.
It seems the only problem is the red cells.

In this case, our job is a bit easier, we can use ->tdOpenEnd() and ->thOpenEnd() to let the cells continue to the right.

3x3 red cells in top left corner:
These are automatically created to fill out the blanks.
If you want instead one connected area, you can execute this once:

$thead->thOpenEnd('g', 0, '');

Explanation:

  • 'g' addresses the row group that covers the entire height of the header.
  • 0 addresses the first column in the left side (the legend). Because we use ->thOpenEnd, it continues until it hits something else, so it should span all the columns 0, 1, 2.

1x1 red cell next to "Weight":
You can let the "Weight" cell continue to the right by using ->thOpenEnd() instead of ->th() for all the cells in the left side legend.

DIsclaimer
I don't remember how exactly the ->tdOpenEnd() works. I hope these cells will not grow into the data area! I think they stop at row groups, but I'm not sure.

If this does not help, you need to define a hierarchy for the columns on the left.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants