1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace MySociety\TheyWorkForYou\DataClass; |
6
|
|
|
|
7
|
|
|
class Cell { |
8
|
|
|
public string $key; |
9
|
|
|
public $value; |
10
|
|
|
|
11
|
|
|
public function getValue() { |
12
|
|
|
return $this->value; |
13
|
|
|
} |
14
|
|
|
} |
15
|
|
|
|
16
|
|
|
class Row { |
17
|
|
|
/** @var Cell[] $cells */ |
18
|
|
|
public array $cells = []; |
19
|
|
|
|
20
|
|
|
public function __construct(array $data) { |
21
|
|
|
foreach ($data as $key => $value) { |
22
|
|
|
$cell = new Cell(); |
23
|
|
|
$cell->key = (string) $key; |
24
|
|
|
$cell->value = $value; |
25
|
|
|
$this->cells[$key] = $cell; |
26
|
|
|
} |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
public function get(string $column) { |
30
|
|
|
$cell = $this->cells[$column] ?? null; |
31
|
|
|
return $cell ? $cell->getValue() : null; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
public function toArray(): array { |
35
|
|
|
$array = []; |
36
|
|
|
foreach ($this->cells as $cell) { |
37
|
|
|
$array[$cell->key] = $cell->value; |
38
|
|
|
} |
39
|
|
|
return $array; |
40
|
|
|
} |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
class DataFrame { |
44
|
|
|
/** |
45
|
|
|
* @var Row[] $rows An array of Row objects |
46
|
|
|
* @var string[] $columns An array of strings representing column names |
47
|
|
|
*/ |
48
|
|
|
public array $rows = []; |
49
|
|
|
public array $columns = []; |
50
|
|
|
|
51
|
|
|
public function __construct(array $data) { |
52
|
|
|
/** |
53
|
|
|
* Accepts an array of associative arrays, where each associative array represents a row in the DataFrame. |
54
|
|
|
* From this extract the column names and create a Row object for each row. |
55
|
|
|
*/ |
56
|
|
|
foreach ($data as $rowData) { |
57
|
|
|
// update the columns with any new keys in each row |
58
|
|
|
$this->columns = array_unique(array_merge($this->columns, array_keys($rowData))); |
59
|
|
|
// make sure all columns are strings rather than ints |
60
|
|
|
$this->rows[] = new Row($rowData); |
61
|
|
|
} |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
public function toArray(): array { |
65
|
|
|
return array_map(fn($row) => $row->toArray(), $this->rows); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
public function toHTML(?string $url_column = null): string { |
69
|
|
|
// Generate a HTML table from the DataFrame |
70
|
|
|
$html = '<div style="overflow-x: auto; white-space: nowrap;">'; |
71
|
|
|
$html .= '<table class="df-table">'; |
72
|
|
|
// Add table headers |
73
|
|
|
$html .= '<tr class="df-row">'; |
74
|
|
|
foreach ($this->columns as $column) { |
75
|
|
|
if ($url_column === $column) { |
76
|
|
|
continue; |
77
|
|
|
} |
78
|
|
|
$html .= '<th class="df-header">' . htmlspecialchars($column) . '</th>'; |
79
|
|
|
} |
80
|
|
|
$html .= '</tr>'; |
81
|
|
|
// Add table rows |
82
|
|
|
foreach ($this->rows as $row) { |
83
|
|
|
$url_value = $url_column ? $row->get($url_column) : null; |
84
|
|
|
$html .= '<tr class="df-row">'; |
85
|
|
|
foreach ($this->columns as $index => $column) { |
86
|
|
|
if ($url_column === $column) { |
87
|
|
|
continue; |
88
|
|
|
} |
89
|
|
|
$raw_cell_value = $row->get($column); |
90
|
|
|
$cell_value = htmlspecialchars((string) $raw_cell_value); |
91
|
|
|
$data_classes = ['df-data']; |
92
|
|
|
if (is_numeric($raw_cell_value)) { |
93
|
|
|
$data_classes[] = 'df-data--number'; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
if ($url_value && $index === 0) { |
97
|
|
|
// wrap first value in url |
98
|
|
|
$cell_value = '<a href="' . $url_value . '">' . $cell_value . '</a>'; |
99
|
|
|
} |
100
|
|
|
$html .= '<td class="' . implode(' ', $data_classes) . '">' . $cell_value . '</td>'; |
101
|
|
|
} |
102
|
|
|
$html .= '</tr>'; |
103
|
|
|
} |
104
|
|
|
$html .= '</table>'; |
105
|
|
|
$html .= '</div>'; |
106
|
|
|
return $html; |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|