ILIAS  trunk Revision v12.0_alpha-1227-g7ff6d300864
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", $this->convertSpecialCharacters($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 (string) (int) $height;
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 (string) (int) $height;
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 $measurement_item_label => $point) {
170 // use numeric value as default
171 if (isset($tooltips_per_dimension[$dimension_name][$measurement_item_label])) {
172 // use custom tooltips if defined
173 $entry = $measurement_item_label . ": " . $tooltips_per_dimension[$dimension_name][$measurement_item_label];
174 } elseif (is_array($point)) {
175 // handle range values
176 $range = "";
177 foreach ($point as $p) {
178 $range .= $p . " - ";
179 }
180 $range = rtrim($range, " -");
181 $entry = $measurement_item_label . ": " . $range;
182 } elseif (is_null($point)) {
183 // handle null values
184 $entry = $measurement_item_label . ": -";
185 } elseif (!empty($value_labels) && is_int($point) && !empty($value_labels[$point - $lowest])) {
186 // use custom value labels if defined
187 $entry = $measurement_item_label . ": " . $value_labels[$point - $lowest];
188 } else {
189 // use numeric value for all other cases
190 $entry = $measurement_item_label . ": " . $point;
191 }
192 $entries[] = $this->convertSpecialCharacters($entry);
193 }
194 $list_items[$this->convertSpecialCharacters($dimension_name)] = $ui_fac->listing()->unordered($entries);
195 }
196
197 $list = $ui_fac->listing()->descriptive($list_items);
198
199 return $list;
200 }
201
202 public function getLowestValueOfChart(Bar\Bar $component): int
203 {
204 $min = null;
205 $new_min = 0;
206 foreach ($component->getDataset()->getDimensions() as $dimension_name => $dimension) {
207 $new_min = floor($component->getDataset()->getMinValueForDimension($dimension_name));
208 if (is_null($min) || $new_min < $min) {
209 $min = $new_min;
210 }
211 }
212
213 if ($component instanceof Bar\Horizontal) {
214 $min = $component->getXAxis()->getMinValue() ?? $min;
215 } elseif ($component instanceof Bar\Vertical) {
216 $min = $component->getYAxis()->getMinValue() ?? $min;
217 }
218
219 return (int) $min;
220 }
221
222 public function getHighestValueOfChart(Bar\Bar $component): int
223 {
224 $max = null;
225 $new_max = 0;
226 foreach ($component->getDataset()->getDimensions() as $dimension_name => $dimension) {
227 $new_max = ceil($component->getDataset()->getMaxValueForDimension($dimension_name));
228 if (is_null($max) || $new_max > $max) {
229 $max = $new_max;
230 }
231 }
232
233 if ($component instanceof Bar\Horizontal) {
234 $max = $component->getXAxis()->getMaxValue() ?? $max;
235 } elseif ($component instanceof Bar\Vertical) {
236 $max = $component->getYAxis()->getMaxValue() ?? $max;
237 }
238
239 return (int) $max;
240 }
241
242 protected function reformatValueLabels(array $labels): array
243 {
244 $index = 0;
245 $new_labels = [];
246 foreach ($labels as $label) {
247 $new_labels[$index] = $label;
248 $index++;
249 }
250
251 return $new_labels;
252 }
253
254 protected function getParsedOptions(Bar\Bar $component): stdClass
255 {
256 $options = new stdClass();
257 $options->indexAxis = $component->getIndexAxis();
258 $options->responsive = true;
259 $options->maintainAspectRatio = false;
260 $options->plugins = new stdClass();
261 $options->plugins->legend = new stdClass();
262 $options->plugins->legend->display = $component->isLegendVisible();
263 $options->plugins->legend->position = $component->getLegendPosition();
264 $options->plugins->tooltip = new stdClass();
265 $options->plugins->tooltip->enabled = $component->isTooltipsVisible();
266 $options->plugins->tooltip->callbacks = new stdClass();
267 $options->plugins->title = new stdClass();
268 $options->plugins->title->display = $component->isTitleVisible();
269 $options->plugins->title->text = $component->getTitle();
270
271 if ($component instanceof Bar\Horizontal) {
272 $options->scales = $this->getParsedOptionsForHorizontal($component);
273 } elseif ($component instanceof Bar\Vertical) {
274 $options->scales = $this->getParsedOptionsForVertical($component);
275 }
276
277 return $options;
278 }
279
280 protected function getParsedOptionsForHorizontal(Bar\Bar $component): stdClass
281 {
282 $scales = new stdClass();
283 $scales->y = new stdClass();
284 $x_axis = $component->getXAxis();
285 $scales->x = new stdClass();
286 $scales->x->axis = $x_axis->getAbbreviation();
287 $scales->x->type = $x_axis->getType();
288 $scales->x->display = $x_axis->isDisplayed();
289 $scales->x->position = $x_axis->getPosition();
290 $scales->x->beginAtZero = $x_axis->isBeginAtZero();
291 $scales->x->ticks = new stdClass();
292 $scales->x->ticks->callback = null;
293 $scales->x->ticks->stepSize = $x_axis->getStepSize();
294 if ($x_axis->getMinValue()) {
295 $scales->x->min = $x_axis->getMinValue();
296 }
297 if ($x_axis->getMaxValue()) {
298 $scales->x->max = $x_axis->getMaxValue();
299 }
300 $dimension_groups = $component->getDataset()->getDimensionGroups();
301 foreach ($component->getGroupConfigs() as $group_name => $config) {
302 if (!isset($dimension_groups[$group_name]) || !$config->isStacked()) {
303 continue;
304 }
305 $scales->y->stacked = true;
306 $scales->x->stacked = true;
307 break;
308 }
309
310 // hide pseudo y axes
311 $dimension_scales = $component->getDataset()->getDimensions();
312 foreach ($dimension_scales as $scale_id) {
313 $scales->{get_class($scale_id)} = new stdClass();
314 $scales->{get_class($scale_id)}->axis = "y";
315 $scales->{get_class($scale_id)}->display = false;
316 }
317
318 return $scales;
319 }
320
321 protected function getParsedOptionsForVertical(Bar\Bar $component): stdClass
322 {
323 $scales = new stdClass();
324 $scales->x = new stdClass();
325 $y_axis = $component->getYAxis();
326 $scales->y = new stdClass();
327 $scales->y->axis = $y_axis->getAbbreviation();
328 $scales->y->type = $y_axis->getType();
329 $scales->y->display = $y_axis->isDisplayed();
330 $scales->y->position = $y_axis->getPosition();
331 $scales->y->beginAtZero = $y_axis->isBeginAtZero();
332 $scales->y->ticks = new stdClass();
333 $scales->y->ticks->callback = null;
334 $scales->y->ticks->stepSize = $y_axis->getStepSize();
335 if ($y_axis->getMinValue()) {
336 $scales->y->min = $y_axis->getMinValue();
337 }
338 if ($y_axis->getMaxValue()) {
339 $scales->y->max = $y_axis->getMaxValue();
340 }
341 $dimension_groups = $component->getDataset()->getDimensionGroups();
342 foreach ($component->getGroupConfigs() as $group_name => $config) {
343 if (!isset($dimension_groups[$group_name]) || !$config->isStacked()) {
344 continue;
345 }
346 $scales->x->stacked = true;
347 $scales->y->stacked = true;
348 break;
349 }
350
351 // hide pseudo x axes
352 $dimension_scales = $component->getDataset()->getDimensions();
353 foreach ($dimension_scales as $scale_id) {
354 $scales->{get_class($scale_id)} = new stdClass();
355 $scales->{get_class($scale_id)}->axis = "x";
356 $scales->{get_class($scale_id)}->display = false;
357 }
358
359 return $scales;
360 }
361
362 protected function getParsedData(Bar\Bar $component): stdClass
363 {
364 $data = new stdClass();
365 $data->datasets = new stdClass();
366
367 $user_data = $this->getUserData($component);
368 $datasets = [];
369 foreach ($user_data as $set) {
370 $dataset = (object) $set;
371 $datasets[] = $dataset;
372 }
373 $data->datasets = $datasets;
374 $data->labels = array_keys($component->getDataset()->getPoints());
375
376 return $data;
377 }
378
379 protected function getUserData(Bar\Bar $component): array
380 {
381 $points_per_dimension = $component->getDataset()->getPointsPerDimension();
382 $dimensions = $component->getDataset()->getDimensions();
383 $dimension_groups = $component->getDataset()->getDimensionGroups();
384 $bar_configs = $component->getBarConfigs();
385 $group_configs = $component->getGroupConfigs();
386 $data = [];
387 $stacking_groups = [];
388
389 foreach ($group_configs as $group_name => $config) {
390 if (!isset($dimension_groups[$group_name]) || !$config->isStacked()) {
391 continue;
392 }
393 foreach ($dimension_groups[$group_name]->getDimensionKeys() as $dimension_name) {
394 $stacking_groups[$dimension_name] = $group_name;
395 }
396 }
397
398 foreach ($points_per_dimension as $dimension_name => $item_points) {
399 $data[$dimension_name]["label"] = $dimension_name;
400 if (isset($bar_configs[$dimension_name]) && $bar_configs[$dimension_name]->getColor()) {
401 $data[$dimension_name]["backgroundColor"] = $bar_configs[$dimension_name]->getColor()->asHex();
402 }
403 if (isset($bar_configs[$dimension_name]) && $bar_configs[$dimension_name]->getRelativeWidth()) {
404 $data[$dimension_name]["barPercentage"] = $bar_configs[$dimension_name]->getRelativeWidth();
405 }
406 if (isset($stacking_groups[$dimension_name])) {
407 $data[$dimension_name]["stack"] = $stacking_groups[$dimension_name];
408 }
409
410 $points_as_objects = [];
411 if ($component instanceof Bar\Horizontal) {
412 foreach ($item_points as $y_point => $x_point) {
413 $datasets = new stdClass();
414 $datasets->data = new stdClass();
415 $datasets->data->y = $y_point;
416 $datasets->data->x = $x_point;
417 $points_as_objects[] = $datasets->data;
418 }
419 $data[$dimension_name]["data"] = $points_as_objects;
420 $data[$dimension_name]["yAxisID"] = get_class($dimensions[$dimension_name]);
421 } elseif ($component instanceof Bar\Vertical) {
422 foreach ($item_points as $x_point => $y_point) {
423 $datasets = new stdClass();
424 $datasets->data = new stdClass();
425 $datasets->data->x = $x_point;
426 $datasets->data->y = $y_point;
427 $points_as_objects[] = $datasets->data;
428 }
429 $data[$dimension_name]["data"] = $points_as_objects;
430 $data[$dimension_name]["xAxisID"] = get_class($dimensions[$dimension_name]);
431 }
432 }
433
434 return $data;
435 }
436
440 public function registerResources(ResourceRegistry $registry): void
441 {
442 parent::registerResources($registry);
443 $registry->register('assets/js/chart.umd.js');
444 $registry->register('assets/js/bar.js');
445 }
446}
$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:440
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