ILIAS  trunk Revision v11.0_alpha-1749-g1a06bdef097
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
Renderer.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
30 use stdClass;
31 use LogicException;
32 
34 {
35  public function render(Component\Component $component, RendererInterface $default_renderer): string
36  {
37  if ($component instanceof Bar\Horizontal) {
38  return $this->renderHorizontal($component, $default_renderer);
39  } elseif ($component instanceof Bar\Vertical) {
40  return $this->renderVertical($component, $default_renderer);
41  }
42 
43  $this->cannotHandleComponent($component);
44  }
45 
46  protected function renderHorizontal(
47  Bar\Horizontal $component,
48  RendererInterface $default_renderer
49  ): string {
50  $tpl = $this->getTemplate("tpl.bar_horizontal.html", true, true);
51 
52  $this->renderBasics($component, $tpl);
53 
54  $a11y_list = $this->getAccessibilityList($component);
55  $tpl->setVariable("LIST", $default_renderer->render($a11y_list));
56 
57  $options = json_encode($this->getParsedOptions($component));
58  $data = json_encode($this->getParsedData($component));
59  $dimensions = $component->getDataset()->getDimensions();
60  $x_labels = json_encode($this->reformatValueLabels($dimensions[key($dimensions)]->getLabels()));
61  $tooltips = json_encode($component->getDataset()->getAlternativeInformation());
62 
63  $component = $component->withAdditionalOnLoadCode(
64  function ($id) use ($options, $data, $x_labels, $tooltips) {
65  return "il.UI.chart.bar.horizontal.init(
66  $id,
67  $options,
68  $data,
69  $x_labels,
70  $tooltips
71  );";
72  }
73  );
74  $id = $this->bindJavaScript($component);
75  $tpl->setVariable("ID", $id);
76 
77  return $tpl->get();
78  }
79 
80  protected function renderVertical(
81  Bar\Vertical $component,
82  RendererInterface $default_renderer
83  ): string {
84  $tpl = $this->getTemplate("tpl.bar_vertical.html", true, true);
85 
86  $this->renderBasics($component, $tpl);
87 
88  $options = json_encode($this->getParsedOptions($component));
89  $data = json_encode($this->getParsedData($component));
90  $dimensions = $component->getDataset()->getDimensions();
91  $y_labels = json_encode($this->reformatValueLabels($dimensions[key($dimensions)]->getLabels()));
92  $tooltips = json_encode($component->getDataset()->getAlternativeInformation());
93 
94  $a11y_list = $this->getAccessibilityList($component);
95  $tpl->setVariable("LIST", $default_renderer->render($a11y_list));
96 
97  $component = $component->withAdditionalOnLoadCode(
98  function ($id) use ($options, $data, $y_labels, $tooltips) {
99  return "il.UI.chart.bar.vertical.init(
100  $id,
101  $options,
102  $data,
103  $y_labels,
104  $tooltips
105  );";
106  }
107  );
108  $id = $this->bindJavaScript($component);
109  $tpl->setVariable("ID", $id);
110 
111  return $tpl->get();
112  }
113 
114  protected function renderBasics(Bar\Bar $component, Template $tpl): void
115  {
116  $tpl->setVariable("TITLE", $component->getTitle());
117  $height = "";
118  if ($component instanceof Bar\Horizontal) {
119  $height = $this->determineHeightForHorizontal($component);
120  } elseif ($component instanceof Bar\Vertical) {
121  $height = $this->determineHeightForVertical($component);
122  }
123  $tpl->setVariable("HEIGHT", $height);
124  }
125 
126  protected function determineHeightForHorizontal(Bar\Bar $component): string
127  {
128  $min_height = 150;
129  $max_height = 900;
130  $item_count = count($component->getDataset()->getPoints());
131  $height = $min_height + ($item_count - 2) * 50;
132  if ($height < $min_height) {
133  $height = $min_height;
134  }
135  if ($height > $max_height) {
136  $height = $max_height;
137  }
138 
139  return $height . "px";
140  }
141 
142  protected function determineHeightForVertical(Bar\Bar $component): string
143  {
144  $min_height = 150;
145  $max_height = 900;
146  $data_max = $this->getHighestValueOfChart($component) - $this->getLowestValueOfChart($component);
147  $height = $min_height + ($data_max / 10) * 50;
148  if ($height > $max_height) {
149  $height = $max_height;
150  }
151 
152  return $height . "px";
153  }
154 
155  protected function getAccessibilityList(
156  Bar\Bar $component
157  ): Component\Listing\Descriptive {
158  $ui_fac = $this->getUIFactory();
159 
160  $points_per_dimension = $component->getDataset()->getPointsPerDimension();
161  $tooltips_per_dimension = $component->getDataset()->getAlternativeInformationPerDimension();
162  $dimensions = $component->getDataset()->getDimensions();
163  $value_labels = $dimensions[key($dimensions)]->getLabels();
164  $lowest = $this->getLowestValueOfChart($component);
165  $list_items = [];
166 
167  foreach ($points_per_dimension as $dimension_name => $item_points) {
168  $entries = [];
169  foreach ($item_points as $messeaurement_item_label => $point) {
170  if (isset($tooltips_per_dimension[$dimension_name][$messeaurement_item_label])) {
171  // use custom tooltips if defined
172  $entries[] = $messeaurement_item_label . ": " . $tooltips_per_dimension[$dimension_name][$messeaurement_item_label];
173  } elseif (is_array($point)) {
174  // handle range values
175  $range = "";
176  foreach ($point as $p) {
177  $range .= $p . " - ";
178  }
179  $range = rtrim($range, " -");
180  $entries[] = $messeaurement_item_label . ": " . $range;
181  } elseif (is_null($point)) {
182  // handle null values
183  $entries[] = $messeaurement_item_label . ": -";
184  } elseif (!empty($value_labels) && is_int($point) && !empty($value_labels[$point - $lowest])) {
185  // use custom value labels if defined
186  $entries[] = $messeaurement_item_label . ": " . $value_labels[$point - $lowest];
187  } else {
188  // use numeric value for all other cases
189  $entries[] = $messeaurement_item_label . ": " . $point;
190  }
191  }
192  $list_items[$dimension_name] = $ui_fac->listing()->unordered($entries);
193  }
194 
195  $list = $ui_fac->listing()->descriptive($list_items);
196 
197  return $list;
198  }
199 
200  public function getLowestValueOfChart(Bar\Bar $component): int
201  {
202  $min = null;
203  $new_min = 0;
204  foreach ($component->getDataset()->getDimensions() as $dimension_name => $dimension) {
205  $new_min = floor($component->getDataset()->getMinValueForDimension($dimension_name));
206  if (is_null($min) || $new_min < $min) {
207  $min = $new_min;
208  }
209  }
210 
211  if ($component instanceof Bar\Horizontal) {
212  $min = $component->getXAxis()->getMinValue() ?? $min;
213  } elseif ($component instanceof Bar\Vertical) {
214  $min = $component->getYAxis()->getMinValue() ?? $min;
215  }
216 
217  return (int) $min;
218  }
219 
220  public function getHighestValueOfChart(Bar\Bar $component): int
221  {
222  $max = null;
223  $new_max = 0;
224  foreach ($component->getDataset()->getDimensions() as $dimension_name => $dimension) {
225  $new_max = ceil($component->getDataset()->getMaxValueForDimension($dimension_name));
226  if (is_null($max) || $new_max > $max) {
227  $max = $new_max;
228  }
229  }
230 
231  if ($component instanceof Bar\Horizontal) {
232  $max = $component->getXAxis()->getMaxValue() ?? $max;
233  } elseif ($component instanceof Bar\Vertical) {
234  $max = $component->getYAxis()->getMaxValue() ?? $max;
235  }
236 
237  return (int) $max;
238  }
239 
240  protected function reformatValueLabels(array $labels): array
241  {
242  $index = 0;
243  $new_labels = [];
244  foreach ($labels as $label) {
245  $new_labels[$index] = $label;
246  $index++;
247  }
248 
249  return $new_labels;
250  }
251 
252  protected function getParsedOptions(Bar\Bar $component): stdClass
253  {
254  $options = new stdClass();
255  $options->indexAxis = $component->getIndexAxis();
256  $options->responsive = true;
257  $options->maintainAspectRatio = false;
258  $options->plugins = new stdClass();
259  $options->plugins->legend = new stdClass();
260  $options->plugins->legend->display = $component->isLegendVisible();
261  $options->plugins->legend->position = $component->getLegendPosition();
262  $options->plugins->tooltip = new stdClass();
263  $options->plugins->tooltip->enabled = $component->isTooltipsVisible();
264  $options->plugins->tooltip->callbacks = new stdClass();
265  $options->plugins->title = new stdClass();
266  $options->plugins->title->display = $component->isTitleVisible();
267  $options->plugins->title->text = $component->getTitle();
268 
269  if ($component instanceof Bar\Horizontal) {
270  $options->scales = $this->getParsedOptionsForHorizontal($component);
271  } elseif ($component instanceof Bar\Vertical) {
272  $options->scales = $this->getParsedOptionsForVertical($component);
273  }
274 
275  return $options;
276  }
277 
278  protected function getParsedOptionsForHorizontal(Bar\Bar $component): stdClass
279  {
280  $scales = new stdClass();
281  $scales->y = new stdClass();
282  $x_axis = $component->getXAxis();
283  $scales->x = new stdClass();
284  $scales->x->axis = $x_axis->getAbbreviation();
285  $scales->x->type = $x_axis->getType();
286  $scales->x->display = $x_axis->isDisplayed();
287  $scales->x->position = $x_axis->getPosition();
288  $scales->x->beginAtZero = $x_axis->isBeginAtZero();
289  $scales->x->ticks = new stdClass();
290  $scales->x->ticks->callback = null;
291  $scales->x->ticks->stepSize = $x_axis->getStepSize();
292  if ($x_axis->getMinValue()) {
293  $scales->x->min = $x_axis->getMinValue();
294  }
295  if ($x_axis->getMaxValue()) {
296  $scales->x->max = $x_axis->getMaxValue();
297  }
298  $dimension_groups = $component->getDataset()->getDimensionGroups();
299  foreach ($component->getGroupConfigs() as $group_name => $config) {
300  if (!isset($dimension_groups[$group_name]) || !$config->isStacked()) {
301  continue;
302  }
303  $scales->y->stacked = true;
304  $scales->x->stacked = true;
305  break;
306  }
307 
308  // hide pseudo y axes
309  $dimension_scales = $component->getDataset()->getDimensions();
310  foreach ($dimension_scales as $scale_id) {
311  $scales->{get_class($scale_id)} = new stdClass();
312  $scales->{get_class($scale_id)}->axis = "y";
313  $scales->{get_class($scale_id)}->display = false;
314  }
315 
316  return $scales;
317  }
318 
319  protected function getParsedOptionsForVertical(Bar\Bar $component): stdClass
320  {
321  $scales = new stdClass();
322  $scales->x = new stdClass();
323  $y_axis = $component->getYAxis();
324  $scales->y = new stdClass();
325  $scales->y->axis = $y_axis->getAbbreviation();
326  $scales->y->type = $y_axis->getType();
327  $scales->y->display = $y_axis->isDisplayed();
328  $scales->y->position = $y_axis->getPosition();
329  $scales->y->beginAtZero = $y_axis->isBeginAtZero();
330  $scales->y->ticks = new stdClass();
331  $scales->y->ticks->callback = null;
332  $scales->y->ticks->stepSize = $y_axis->getStepSize();
333  if ($y_axis->getMinValue()) {
334  $scales->y->min = $y_axis->getMinValue();
335  }
336  if ($y_axis->getMaxValue()) {
337  $scales->y->max = $y_axis->getMaxValue();
338  }
339  $dimension_groups = $component->getDataset()->getDimensionGroups();
340  foreach ($component->getGroupConfigs() as $group_name => $config) {
341  if (!isset($dimension_groups[$group_name]) || !$config->isStacked()) {
342  continue;
343  }
344  $scales->x->stacked = true;
345  $scales->y->stacked = true;
346  break;
347  }
348 
349  // hide pseudo x axes
350  $dimension_scales = $component->getDataset()->getDimensions();
351  foreach ($dimension_scales as $scale_id) {
352  $scales->{get_class($scale_id)} = new stdClass();
353  $scales->{get_class($scale_id)}->axis = "x";
354  $scales->{get_class($scale_id)}->display = false;
355  }
356 
357  return $scales;
358  }
359 
360  protected function getParsedData(Bar\Bar $component): stdClass
361  {
362  $data = new stdClass();
363  $data->datasets = new stdClass();
364 
365  $user_data = $this->getUserData($component);
366  $datasets = [];
367  foreach ($user_data as $set) {
368  $dataset = (object) $set;
369  $datasets[] = $dataset;
370  }
371  $data->datasets = $datasets;
372  $data->labels = array_keys($component->getDataset()->getPoints());
373 
374  return $data;
375  }
376 
377  protected function getUserData(Bar\Bar $component): array
378  {
379  $points_per_dimension = $component->getDataset()->getPointsPerDimension();
380  $dimensions = $component->getDataset()->getDimensions();
381  $dimension_groups = $component->getDataset()->getDimensionGroups();
382  $bar_configs = $component->getBarConfigs();
383  $group_configs = $component->getGroupConfigs();
384  $data = [];
385  $stacking_groups = [];
386 
387  foreach ($group_configs as $group_name => $config) {
388  if (!isset($dimension_groups[$group_name]) || !$config->isStacked()) {
389  continue;
390  }
391  foreach ($dimension_groups[$group_name]->getDimensionKeys() as $dimension_name) {
392  $stacking_groups[$dimension_name] = $group_name;
393  }
394  }
395 
396  foreach ($points_per_dimension as $dimension_name => $item_points) {
397  $data[$dimension_name]["label"] = $dimension_name;
398  if (isset($bar_configs[$dimension_name]) && $bar_configs[$dimension_name]->getColor()) {
399  $data[$dimension_name]["backgroundColor"] = $bar_configs[$dimension_name]->getColor()->asHex();
400  }
401  if (isset($bar_configs[$dimension_name]) && $bar_configs[$dimension_name]->getRelativeWidth()) {
402  $data[$dimension_name]["barPercentage"] = $bar_configs[$dimension_name]->getRelativeWidth();
403  }
404  if (isset($stacking_groups[$dimension_name])) {
405  $data[$dimension_name]["stack"] = $stacking_groups[$dimension_name];
406  }
407 
408  $points_as_objects = [];
409  if ($component instanceof Bar\Horizontal) {
410  foreach ($item_points as $y_point => $x_point) {
411  $datasets = new stdClass();
412  $datasets->data = new stdClass();
413  $datasets->data->y = $y_point;
414  $datasets->data->x = $x_point;
415  $points_as_objects[] = $datasets->data;
416  }
417  $data[$dimension_name]["data"] = $points_as_objects;
418  $data[$dimension_name]["yAxisID"] = get_class($dimensions[$dimension_name]);
419  } elseif ($component instanceof Bar\Vertical) {
420  foreach ($item_points as $x_point => $y_point) {
421  $datasets = new stdClass();
422  $datasets->data = new stdClass();
423  $datasets->data->x = $x_point;
424  $datasets->data->y = $y_point;
425  $points_as_objects[] = $datasets->data;
426  }
427  $data[$dimension_name]["data"] = $points_as_objects;
428  $data[$dimension_name]["xAxisID"] = get_class($dimensions[$dimension_name]);
429  }
430  }
431 
432  return $data;
433  }
434 
438  public function registerResources(ResourceRegistry $registry): void
439  {
440  parent::registerResources($registry);
441  $registry->register('assets/js/chart.umd.js');
442  $registry->register('assets/js/bar.js');
443  }
444 }
Registry for resources required by rendered output like Javascript or CSS.
renderHorizontal(Bar\Horizontal $component, RendererInterface $default_renderer)
Definition: Renderer.php:46
renderVertical(Bar\Vertical $component, RendererInterface $default_renderer)
Definition: Renderer.php:80
setVariable(string $name, $value)
Set a variable in the current block.
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
renderBasics(Bar\Bar $component, Template $tpl)
Definition: Renderer.php:114
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:438
cannotHandleComponent(Component $component)
This method MUST be called by derived component renderers, if.
register(string $name)
Add a dependency.
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
render(Component\Component $component, RendererInterface $default_renderer)
Definition: Renderer.php:35
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.