summaryrefslogtreecommitdiffstats
path: root/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icinga/Web/Widget/Chart/HistoryColorGrid.php')
-rw-r--r--library/Icinga/Web/Widget/Chart/HistoryColorGrid.php400
1 files changed, 400 insertions, 0 deletions
diff --git a/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php
new file mode 100644
index 0000000..b7b50d0
--- /dev/null
+++ b/library/Icinga/Web/Widget/Chart/HistoryColorGrid.php
@@ -0,0 +1,400 @@
+<?php
+/* Icinga Web 2 | (c) 2014 Icinga Development Team | GPLv2+ */
+
+namespace Icinga\Web\Widget\Chart;
+
+use DateInterval;
+use DateTime;
+use Icinga\Util\Color;
+use Icinga\Util\Csp;
+use Icinga\Web\Widget\AbstractWidget;
+use ipl\Web\Style;
+
+/**
+ * Display a colored grid that visualizes a set of values for each day
+ * on a given time-frame.
+ */
+class HistoryColorGrid extends AbstractWidget
+{
+ const CAL_GROW_INTO_PAST = 'past';
+ const CAL_GROW_INTO_PRESENT = 'present';
+
+ const ORIENTATION_VERTICAL = 'vertical';
+ const ORIENTATION_HORIZONTAL = 'horizontal';
+
+ public $weekFlow = self::CAL_GROW_INTO_PAST;
+ public $orientation = self::ORIENTATION_VERTICAL;
+ public $weekStartMonday = true;
+
+ private $maxValue = 1;
+
+ private $start = null;
+ private $end = null;
+ private $data = array();
+ private $color;
+ public $opacity = 1.0;
+
+ /** @var array<string, array<string, string>> History grid css rulesets */
+ protected $rulesets = [];
+
+ public function __construct($color = '#51e551', $start = null, $end = null)
+ {
+ $this->setColor($color);
+ if (isset($start)) {
+ $this->start = $this->tsToDateStr($start);
+ }
+ if (isset($end)) {
+ $this->end = $this->tsToDateStr($end);
+ }
+ }
+
+ /**
+ * Set the displayed data-set
+ *
+ * @param $events array The history events to display as an array of arrays:
+ * value: The value to display
+ * caption: The caption on mouse-over
+ * url: The url to open on click.
+ */
+ public function setData(array $events)
+ {
+ $this->data = $events;
+ $start = time();
+ $end = time();
+ foreach ($this->data as $entry) {
+ $entry['value'] = intval($entry['value']);
+ }
+ foreach ($this->data as $date => $entry) {
+ $time = strtotime($date);
+ if ($entry['value'] > $this->maxValue) {
+ $this->maxValue = $entry['value'];
+ }
+ if ($time > $end) {
+ $end = $time;
+ }
+ if ($time < $start) {
+ $start = $time;
+ }
+ }
+ if (!isset($this->start)) {
+ $this->start = $this->tsToDateStr($start);
+ }
+ if (!isset($this->end)) {
+ $this->end = $this->tsToDateStr($end);
+ }
+ }
+
+ /**
+ * Set the used color.
+ *
+ * @param $color
+ */
+ public function setColor($color)
+ {
+ $this->color = $color;
+ }
+
+ /**
+ * Set the used opacity
+ *
+ * @param $opacity
+ */
+ public function setOpacity($opacity)
+ {
+ $this->opacity = $opacity;
+ }
+
+ /**
+ * Calculate the color to display for the given value.
+ *
+ * @param $value integer
+ *
+ * @return string The color-string to use for this entry.
+ */
+ private function calculateColor($value)
+ {
+ $saturation = $value / $this->maxValue;
+ return Color::changeSaturation($this->color, $saturation);
+ }
+
+ /**
+ * Render the html to display the given $day
+ *
+ * @param $day string The day to display YYYY-MM-DD
+ *
+ * @return string The rendered html
+ */
+ private function renderDay($day)
+ {
+ if (array_key_exists($day, $this->data) && $this->data[$day]['value'] > 0) {
+ $entry = $this->data[$day];
+ $this->rulesets['.grid-day-with-entry-' . $entry['value']] = [
+ 'background-color' => $this->calculateColor($entry['value']),
+ 'opacity' => $this->opacity
+ ];
+
+ return '<a class="grid-day-with-entry-'
+ . $entry['value']
+ . '" '
+ . 'aria-label="' . $entry['caption']
+ . '" '
+ . 'title="' . $entry['caption']
+ . '" '
+ . 'href="' . $entry['url']
+ . '" '
+ . '"></a>';
+ } else {
+ if (! isset($this->rulesets['.grid-day-no-entry'])) {
+ $this->rulesets['.grid-day-no-entry'] = [
+ 'background-color' => $this->calculateColor(0),
+ 'opacity' => $this->opacity
+ ];
+ }
+
+ return '<span class="grid-day-no-entry"' . ' title="No entries for ' . $day . '"></span>';
+ }
+ }
+
+ /**
+ * Render the grid with an horizontal alignment.
+ *
+ * @param array $grid The values returned from the createGrid function
+ *
+ * @return string The rendered html
+ */
+ private function renderHorizontal($grid)
+ {
+ $weeks = $grid['weeks'];
+ $months = $grid['months'];
+ $years = $grid['years'];
+ $html = '<table class="historycolorgrid">';
+ $html .= '<tr><th></th>';
+ $old = -1;
+ foreach ($months as $week => $month) {
+ if ($old !== $month) {
+ $old = $month;
+ $txt = $this->monthName($month, $years[$week]);
+ } else {
+ $txt = '';
+ }
+ $html .= '<th>' . $txt . '</th>';
+ }
+ $html .= '</tr>';
+ for ($i = 0; $i < 7; $i++) {
+ $html .= $this->renderWeekdayHorizontal($i, $weeks);
+ }
+ $html .= '</table>';
+ return $html;
+ }
+
+ /**
+ * @param $grid
+ *
+ * @return string
+ */
+ private function renderVertical($grid)
+ {
+ $years = $grid['years'];
+ $weeks = $grid['weeks'];
+ $months = $grid['months'];
+ $html = '<table class="historycolorgrid">';
+ $html .= '<tr>';
+ for ($i = 0; $i < 7; $i++) {
+ $html .= '<th>' . $this->weekdayName($this->weekStartMonday ? $i + 1 : $i) . "</th>";
+ }
+ $html .= '</tr>';
+ $old = -1;
+ foreach ($weeks as $index => $week) {
+ for ($i = 0; $i < 7; $i++) {
+ if (array_key_exists($i, $week)) {
+ $html .= '<td>' . $this->renderDay($week[$i]) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ }
+ if ($old !== $months[$index]) {
+ $old = $months[$index];
+ $txt = $this->monthName($old, $years[$index]);
+ } else {
+ $txt = '';
+ }
+ $html .= '<td class="weekday">' . $txt . '</td></tr>';
+ }
+ $html .= '</table>';
+ return $html;
+ }
+
+ /**
+ * Render the row for the given weekday.
+ *
+ * @param integer $weekday The day to render (0-6)
+ * @param array $weeks The weeks
+ *
+ * @return string The formatted table-row
+ */
+ private function renderWeekdayHorizontal($weekday, &$weeks)
+ {
+ $html = '<tr><td class="weekday">'
+ . $this->weekdayName($this->weekStartMonday ? $weekday + 1 : $weekday)
+ . '</td>';
+ foreach ($weeks as $week) {
+ if (array_key_exists($weekday, $week)) {
+ $html .= '<td>' . $this->renderDay($week[$weekday]) . '</td>';
+ } else {
+ $html .= '<td></td>';
+ }
+ }
+ $html .= '</tr>';
+ return $html;
+ }
+
+
+
+ /**
+ * @return array
+ */
+ private function createGrid()
+ {
+ $weeks = array(array());
+ $week = 0;
+ $months = array();
+ $years = array();
+ $start = strtotime($this->start);
+ $year = intval(date('Y', $start));
+ $month = intval(date('n', $start));
+ $day = intval(date('j', $start));
+ $weekday = intval(date('w', $start));
+ if ($this->weekStartMonday) {
+ // 0 => monday, 6 => sunday
+ $weekday = $weekday === 0 ? 6 : $weekday - 1;
+ }
+
+ $date = $this->toDateStr($day, $month, $year);
+ $weeks[0][$weekday] = $date;
+ $years[0] = $year;
+ $months[0] = $month;
+ while ($date !== $this->end) {
+ $day++;
+ $weekday++;
+ if ($weekday > 6) {
+ $weekday = 0;
+ $weeks[] = array();
+ // PRESENT => The last day of week determines the month
+ if ($this->weekFlow === self::CAL_GROW_INTO_PRESENT) {
+ $months[$week] = $month;
+ $years[$week] = $year;
+ }
+ $week++;
+ }
+ if ($day > date('t', mktime(0, 0, 0, $month, 1, $year))) {
+ $month++;
+ if ($month > 12) {
+ $year++;
+ $month = 1;
+ }
+ $day = 1;
+ }
+ if ($weekday === 0) {
+ // PAST => The first day of each week determines the month
+ if ($this->weekFlow === self::CAL_GROW_INTO_PAST) {
+ $months[$week] = $month;
+ $years[$week] = $year;
+ }
+ }
+ $date = $this->toDateStr($day, $month, $year);
+ $weeks[$week][$weekday] = $date;
+ };
+ $years[$week] = $year;
+ $months[$week] = $month;
+ if ($this->weekFlow == self::CAL_GROW_INTO_PAST) {
+ return array(
+ 'weeks' => array_reverse($weeks),
+ 'months' => array_reverse($months),
+ 'years' => array_reverse($years)
+ );
+ }
+ return array(
+ 'weeks' => $weeks,
+ 'months' => $months,
+ 'years' => $years
+ );
+ }
+
+ /**
+ * Get the localized month-name for the given month
+ *
+ * @param integer $month The month-number
+ *
+ * @return string The
+ */
+ private function monthName($month, $year)
+ {
+ // TODO: find a way to render years without messing up the layout
+ $dt = new DateTime($year . '-' . $month . '-01');
+ return $dt->format('M');
+ }
+
+ /**
+ * @param $weekday
+ *
+ * @return string
+ */
+ private function weekdayName($weekday)
+ {
+ $sun = new DateTime('last Sunday');
+ $interval = new DateInterval('P' . $weekday . 'D');
+ $sun->add($interval);
+ return substr($sun->format('D'), 0, 2);
+ }
+
+ /**
+ *
+ *
+ * @param $timestamp
+ *
+ * @return bool|string
+ */
+ private function tsToDateStr($timestamp)
+ {
+ return date('Y-m-d', $timestamp);
+ }
+
+ /**
+ * @param $day
+ * @param $mon
+ * @param $year
+ *
+ * @return string
+ */
+ private function toDateStr($day, $mon, $year)
+ {
+ $day = $day > 9 ? (string)$day : '0' . (string)$day;
+ $mon = $mon > 9 ? (string)$mon : '0' . (string)$mon;
+ return $year . '-' . $mon . '-' . $day;
+ }
+
+ /**
+ * @return string
+ */
+ public function render()
+ {
+ if (empty($this->data)) {
+ return '<div>No entries</div>';
+ }
+ $grid = $this->createGrid();
+ if ($this->orientation === self::ORIENTATION_HORIZONTAL) {
+ $html = $this->renderHorizontal($grid);
+ } else {
+ $html = $this->renderVertical($grid);
+ }
+
+ $historyGridStyle = new Style();
+ $historyGridStyle->setNonce(Csp::getStyleNonce());
+
+ foreach ($this->rulesets as $selector => $properties) {
+ $historyGridStyle->add($selector, $properties);
+ }
+
+ return $html . $historyGridStyle;
+ }
+}