ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
Renderer.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
28use ILIAS\UI\Renderer as RendererInterface;
30use stdClass;
31use 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}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
render(Component\Component $component, RendererInterface $default_renderer)
Definition: Renderer.php:35
registerResources(ResourceRegistry $registry)
Announce resources this renderer requires.
Definition: Renderer.php:438
renderBasics(Bar\Bar $component, Template $tpl)
Definition: Renderer.php:114
renderVertical(Bar\Vertical $component, RendererInterface $default_renderer)
Definition: Renderer.php:80
renderHorizontal(Bar\Horizontal $component, RendererInterface $default_renderer)
Definition: Renderer.php:46
cannotHandleComponent(Component $component)
This method MUST be called by derived component renderers, if.
bindJavaScript(JavaScriptBindable $component)
Bind the component to JavaScript.
getTemplate(string $name, bool $purge_unfilled_vars, bool $purge_unused_blocks)
Get template of component this renderer is made for.
return true
Registry for resources required by rendered output like Javascript or CSS.
register(string $name)
Add a dependency.
Interface to templating as it is used in the UI framework.
Definition: Template.php:29
setVariable(string $name, $value)
Set a variable in the current block.
An entity that renders components to a string output.
Definition: Renderer.php:31