✏️ 正在编辑: CSvgGraph.php
路径:
/usr/share/zabbix/include/classes/graph/CSvgGraph.php
提示:
您可以编辑任何文件(包括二进制文件),但请注意不当修改可能导致文件损坏。
<?php /* ** Zabbix ** Copyright (C) 2001-2021 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ /** * General class for SVG Graph usage. */ class CSvgGraph extends CSvg { const SVG_GRAPH_X_AXIS_HEIGHT = 20; const SVG_GRAPH_DEFAULT_COLOR = '#b0af07'; const SVG_GRAPH_DEFAULT_TRANSPARENCY = 5; const SVG_GRAPH_DEFAULT_POINTSIZE = 1; const SVG_GRAPH_DEFAULT_LINE_WIDTH = 1; const SVG_GRAPH_X_AXIS_LABEL_MARGIN = 5; const SVG_GRAPH_Y_AXIS_LEFT_LABEL_MARGIN = 5; const SVG_GRAPH_Y_AXIS_RIGHT_LABEL_MARGIN = 12; protected $canvas_height; protected $canvas_width; protected $canvas_x; protected $canvas_y; /** * Problems annotation labels color. * * @var string */ protected $color_annotation = '#AA4455'; /** * Text color. * * @var string */ protected $text_color; /** * Grid color. * * @var string */ protected $grid_color; /** * Array of graph metrics data. * * @var array */ protected $metrics = []; /** * Array of graph points data. Calculated from metrics data. * * @var array */ protected $points = []; /** * Array of metric paths. Where key is metric index from $metrics array. * * @var array */ protected $paths = []; /** * Array of graph problems to display. * * @var array */ protected $problems = []; protected $max_value_left = null; protected $max_value_right = null; protected $min_value_left = null; protected $min_value_right = null; protected $left_y_show = false; protected $left_y_min = null; protected $left_y_min_calculated = null; protected $left_y_max = null; protected $left_y_max_calculated = null; protected $left_y_interval = null; protected $left_y_units = null; protected $left_y_is_binary = null; protected $left_y_power = null; protected $left_y_empty = true; protected $right_y_show = false; protected $right_y_min = null; protected $right_y_min_calculated = null; protected $right_y_max = null; protected $right_y_max_calculated = null; protected $right_y_interval = null; protected $right_y_units = null; protected $right_y_is_binary = null; protected $right_y_power = null; protected $right_y_empty = true; protected $right_y_zero = null; protected $left_y_zero = null; protected $x_show; protected $offset_bottom; /** * Value for graph left offset. Is used as width for left Y axis container. * * @var int */ protected $offset_left = 20; /** * Value for graph right offset. Is used as width for right Y axis container. * * @var int */ protected $offset_right = 20; /** * Maximum width of container for every Y axis. * * @var int */ protected $max_yaxis_width = 120; protected $cell_height_min = 30; protected $offset_top; protected $time_from; protected $time_till; /** * Height for X axis container. * * @var int */ protected $xaxis_height = 20; /** * SVG default size. */ protected $width = 1000; protected $height = 1000; public function __construct(array $options) { parent::__construct(); // Set colors. $theme = getUserGraphTheme(); $this->text_color = '#' . $theme['textcolor']; $this->grid_color = '#' . $theme['gridcolor']; $this ->addClass(ZBX_STYLE_SVG_GRAPH) ->setTimePeriod($options['time_period']['time_from'], $options['time_period']['time_to']) ->setXAxis($options['x_axis']) ->setYAxisLeft($options['left_y_axis']) ->setYAxisRight($options['right_y_axis']); } /** * Get graph canvas X offset. * * @return int */ public function getCanvasX() { return $this->canvas_x; } /** * Get graph canvas Y offset. * * @return int */ public function getCanvasY() { return $this->canvas_y; } /** * Get graph canvas width. * * @return int */ public function getCanvasWidth() { return $this->canvas_width; } /** * Get graph canvas height. * * @return int */ public function getCanvasHeight() { return $this->canvas_height; } /** * Set problems data for graph. * * @param array $problems Array of problems data. * * @return CSvgGraph */ public function addProblems(array $problems) { $this->problems = $problems; return $this; } /** * Set metrics data for graph. * * @param array $metrics Array of metrics data. * * @return CSvgGraph */ public function addMetrics(array $metrics = []) { $metrics_for_each_axes = [ GRAPH_YAXIS_SIDE_LEFT => 0, GRAPH_YAXIS_SIDE_RIGHT => 0 ]; foreach ($metrics as $i => $metric) { $min_value = null; $max_value = null; if (array_key_exists('points', $metric)) { $metrics_for_each_axes[$metric['options']['axisy']]++; foreach ($metric['points'] as $point) { if ($min_value === null || $min_value > $point['value']) { $min_value = $point['value']; } if ($max_value === null || $max_value < $point['value']) { $max_value = $point['value']; } $this->points[$i][$point['clock']] = $point['value']; } if ($metric['options']['axisy'] == GRAPH_YAXIS_SIDE_LEFT) { if ($this->min_value_left === null || $this->min_value_left > $min_value) { $this->min_value_left = $min_value; } if ($this->max_value_left === null || $this->max_value_left < $max_value) { $this->max_value_left = $max_value; } } else { if ($this->min_value_right === null || $this->min_value_right > $min_value) { $this->min_value_right = $min_value; } if ($this->max_value_right === null || $this->max_value_right < $max_value) { $this->max_value_right = $max_value; } } } $this->metrics[$i] = [ 'name' => $metric['name'], 'itemid' => $metric['itemid'], 'units' => $metric['units'], 'host' => $metric['hosts'][0], 'options' => ['order' => $i] + $metric['options'] ]; } $this->left_y_empty = ($metrics_for_each_axes[GRAPH_YAXIS_SIDE_LEFT] == 0); $this->right_y_empty = ($metrics_for_each_axes[GRAPH_YAXIS_SIDE_RIGHT] == 0); return $this; } /** * Set graph time period. * * @param int $time_from Timestamp. * @param int @time_till Timestamp. * * @return CSvgGraph */ public function setTimePeriod($time_from, $time_till) { $this->time_from = $time_from; $this->time_till = $time_till; return $this; } /** * Set left side Y axis display options. * * @param array $options * @param int $options['show'] * @param string $options['min'] * @param string $options['max'] * @param string $options['units'] * * @return CSvgGraph */ public function setYAxisLeft(array $options) { $this->left_y_show = ($options['show'] == SVG_GRAPH_AXIS_SHOW); if ($options['min'] !== '') { $this->left_y_min = $options['min']; } if ($options['max'] !== '') { $this->left_y_max = $options['max']; } if ($options['units'] !== null) { $units = trim(preg_replace('/\s+/', ' ', $options['units'])); $this->left_y_units = htmlspecialchars($units); } return $this; } /** * Set right side Y axis display options. * * @param array $options * @param int $options['show'] * @param string $options['min'] * @param string $options['max'] * @param string $options['units'] * * @return CSvgGraph */ public function setYAxisRight(array $options) { $this->right_y_show = ($options['show'] == SVG_GRAPH_AXIS_SHOW); if ($options['min'] !== '') { $this->right_y_min = $options['min']; } if ($options['max'] !== '') { $this->right_y_max = $options['max']; } if ($options['units'] !== null) { $units = trim(preg_replace('/\s+/', ' ', $options['units'])); $this->right_y_units = htmlspecialchars($units); } return $this; } /** * Show or hide X axis. * * @param array $options * * @return CSvgGraph */ public function setXAxis(array $options) { $this->x_show = ($options['show'] == SVG_GRAPH_AXIS_SHOW); return $this; } /** * Return array of horizontal labels with positions. Array key will be position, value will be label. * * @return array */ public function getTimeGridWithPosition() { $period = $this->time_till - $this->time_from; $step = round($period / $this->canvas_width * 100); // Grid cell (100px) in seconds. /* * In case if requested time period is so small that it is rounded to zero, we are displaying only two * milestones on X axis - the start and the end of period. */ if ($step == 0) { return [ 0 => date('H:i:s', $this->time_from), $this->canvas_width => date('H:i:s', $this->time_till) ]; } $start = $this->time_from + $step - $this->time_from % $step; $time_formats = ['Y-n-d', 'n-d', 'n-d H:i','H:i', 'H:i:s']; // Search for most appropriate time format. foreach ($time_formats as $fmt) { $grid_values = []; for ($clock = $start; $this->time_till >= $clock; $clock += $step) { $relative_pos = round($this->canvas_width - $this->canvas_width * ($this->time_till - $clock) / $period); $grid_values[$relative_pos] = date($fmt, $clock); } /** * If at least two calculated time-strings are equal, proceed with next format. Do that as long as each date * is different or there is no more time formats to test. */ if (count(array_flip($grid_values)) == count($grid_values) || $fmt === end($time_formats)) { break; } } return $grid_values; } /** * Add UI selection box element to graph. * * @return CSvgGraph */ public function addSBox() { $this->addItem([ (new CSvgRect(0, 0, 0, 0))->addClass('svg-graph-selection'), (new CSvgText(0, 0, ''))->addClass('svg-graph-selection-text') ]); return $this; } /** * Add UI helper line that follows mouse. * * @return CSvgGraph */ public function addHelper() { $this->addItem((new CSvgLine(0, 0, 0, 0))->addClass(CSvgTag::ZBX_STYLE_GRAPH_HELPER)); return $this; } /** * Render graph. * * @return CSvgGraph */ public function draw() { $this->applyMissingDataFunc(); $this->calculateDimensions(); $this->calculatePaths(); $this->drawGrid(); if ($this->left_y_show) { $this->drawCanvasLeftYAxis(); } if ($this->right_y_show) { $this->drawCanvasRightYAxis(); } if ($this->x_show) { $this->drawCanvasXAxis(); } $this->drawMetricsLine(); $this->drawMetricsPoint(); $this->drawMetricsBar(); $this->drawProblems(); $this->addClipArea(); return $this; } /** * Add dynamic clip path to hide metric lines and area outside graph canvas. */ protected function addClipArea() { $areaid = uniqid('metric_clip_'); // CSS styles. $this->styles['.'.CSvgTag::ZBX_STYLE_GRAPH_AREA]['clip-path'] = 'url(#'.$areaid.')'; $this->styles['[data-metric]']['clip-path'] = 'url(#'.$areaid.')'; $this->addItem( (new CsvgTag('clipPath')) ->addItem( (new CSvgPath(implode(' ', [ 'M'.$this->canvas_x.','.($this->canvas_y - 3), 'H'.($this->canvas_width + $this->canvas_x), 'V'.($this->canvas_height + $this->canvas_y), 'H'.($this->canvas_x) ]))) ) ->setAttribute('id', $areaid) ); } /** * Calculate canvas size, margins and offsets for graph canvas inside SVG element. */ protected function calculateDimensions() { // Canvas height must be specified before call self::getValuesGridWithPosition. $this->offset_top = 10; $this->offset_bottom = self::SVG_GRAPH_X_AXIS_HEIGHT; $this->canvas_height = $this->height - $this->offset_top - $this->offset_bottom; $this->canvas_y = $this->offset_top; // Determine units for left side. if ($this->left_y_units === null) { $this->left_y_units = ''; foreach ($this->metrics as $metric) { if ($metric['options']['axisy'] == GRAPH_YAXIS_SIDE_LEFT) { $this->left_y_units = $metric['units']; break; } } } // Determine units for right side. if ($this->right_y_units === null) { $this->right_y_units = ''; foreach ($this->metrics as $metric) { if ($metric['options']['axisy'] == GRAPH_YAXIS_SIDE_RIGHT) { $this->right_y_units = $metric['units']; break; } } } // Calculate vertical scale parameters for left side. $rows_min = (int) max(1, floor($this->canvas_height / $this->cell_height_min / 1.5)); $rows_max = (int) max(1, floor($this->canvas_height / $this->cell_height_min)); $this->left_y_min_calculated = $this->left_y_min === null; $this->left_y_max_calculated = $this->left_y_max === null; if ($this->left_y_min_calculated) { $this->left_y_min = $this->min_value_left ? : 0; } if ($this->left_y_max_calculated) { $this->left_y_max = $this->max_value_left ? : 1; } $this->left_y_is_binary = $this->left_y_units === 'B' || $this->left_y_units === 'Bps'; $result = calculateGraphScaleExtremes($this->left_y_min, $this->left_y_max, $this->left_y_is_binary, $this->left_y_min_calculated, $this->left_y_max_calculated, $rows_min, $rows_max ); [ 'min' => $this->left_y_min, 'max' => $this->left_y_max, 'interval' => $this->left_y_interval, 'power' => $this->left_y_power ] = $result; // Calculate vertical scale parameters for right side. if ($this->left_y_min_calculated && $this->left_y_max_calculated) { $rows_min = $rows_max = $result['rows']; } $this->right_y_min_calculated = $this->right_y_min === null; $this->right_y_max_calculated = $this->right_y_max === null; if ($this->right_y_min_calculated) { $this->right_y_min = $this->min_value_right ? : 0; } if ($this->right_y_max_calculated) { $this->right_y_max = $this->max_value_right ? : 1; } $this->right_y_is_binary = $this->right_y_units === 'B' || $this->right_y_units === 'Bps'; $result = calculateGraphScaleExtremes($this->right_y_min, $this->right_y_max, $this->right_y_is_binary, $this->right_y_min_calculated, $this->right_y_max_calculated, $rows_min, $rows_max ); [ 'min' => $this->right_y_min, 'max' => $this->right_y_max, 'interval' => $this->right_y_interval, 'power' => $this->right_y_power ] = $result; // Define canvas dimensions and offsets, except canvas height and bottom offset. $approx_width = 10; if ($this->left_y_show) { $values = $this->getValuesGridWithPosition(GRAPH_YAXIS_SIDE_LEFT, $this->left_y_empty); if ($values) { $offset_left = max($this->offset_left, max(array_map('strlen', $values)) * $approx_width); $this->offset_left = (int) min($offset_left, $this->max_yaxis_width); } } if ($this->right_y_show) { $values = $this->getValuesGridWithPosition(GRAPH_YAXIS_SIDE_RIGHT, $this->right_y_empty); if ($values) { $offset_right = max($this->offset_right, max(array_map('strlen', $values)) * $approx_width); $offset_right += self::SVG_GRAPH_Y_AXIS_RIGHT_LABEL_MARGIN; $this->offset_right = (int) min($offset_right, $this->max_yaxis_width); } } $this->canvas_width = $this->width - $this->offset_left - $this->offset_right; $this->canvas_x = $this->offset_left; // Calculate vertical zero position. if ($this->left_y_max - $this->left_y_min == INF) { $this->left_y_zero = $this->canvas_y + $this->canvas_height * max(0, min(1, $this->left_y_max / 10 / ($this->left_y_max / 10 - $this->left_y_min / 10))); } else { $this->left_y_zero = $this->canvas_y + $this->canvas_height * max(0, min(1, $this->left_y_max / ($this->left_y_max - $this->left_y_min))); } if ($this->right_y_max - $this->right_y_min == INF) { $this->right_y_zero = $this->canvas_y + $this->canvas_height * max(0, min(1, $this->right_y_max / 10 / ($this->right_y_max / 10 - $this->right_y_min / 10))); } else { $this->right_y_zero = $this->canvas_y + $this->canvas_height * max(0, min(1, $this->right_y_max / ($this->right_y_max - $this->right_y_min))); } } /** * Get array of X points with labels, for grid and X/Y axes. Array key is Y coordinate for SVG, value is label with * axis units. * * @param int $side Type of Y axis: GRAPH_YAXIS_SIDE_RIGHT, GRAPH_YAXIS_SIDE_LEFT * @param bool $empty_set Return defaults for empty side. * * @return array */ protected function getValuesGridWithPosition($side, $empty_set = false) { $min = 0; $max = 1; $min_calculated = true; $max_calculated = true; $interval = 1; $units = ''; $is_binary = false; $power = 0; if (!$empty_set) { if ($side === GRAPH_YAXIS_SIDE_LEFT) { $min = $this->left_y_min; $max = $this->left_y_max; $min_calculated = $this->left_y_min_calculated; $max_calculated = $this->left_y_max_calculated; $interval = $this->left_y_interval; $units = $this->left_y_units; $is_binary = $this->left_y_is_binary; $power = $this->left_y_power; } elseif ($side === GRAPH_YAXIS_SIDE_RIGHT) { $min = $this->right_y_min; $max = $this->right_y_max; $min_calculated = $this->right_y_min_calculated; $max_calculated = $this->right_y_max_calculated; $interval = $this->right_y_interval; $units = $this->right_y_units; $is_binary = $this->right_y_is_binary; $power = $this->right_y_power; } } $relative_values = calculateGraphScaleValues($min, $max, $min_calculated, $max_calculated, $interval, $units, $is_binary, $power, 14 ); $absolute_values = []; foreach ($relative_values as ['relative_pos' => $relative_pos, 'value' => $value]) { $absolute_values[(int) round($this->canvas_height * $relative_pos)] = $value; } return $absolute_values; } /** * Add Y axis with labels to left side of graph. */ protected function drawCanvasLeftYaxis() { $grid_values = $this->getValuesGridWithPosition(GRAPH_YAXIS_SIDE_LEFT, $this->left_y_empty); $this->addItem( (new CSvgGraphAxis($grid_values, GRAPH_YAXIS_SIDE_LEFT)) ->setLineColor($this->grid_color) ->setTextColor($this->text_color) ->setSize($this->offset_left, $this->canvas_height) ->setPosition($this->canvas_x - $this->offset_left, $this->canvas_y) ); } /** * Add Y axis with labels to right side of graph. */ protected function drawCanvasRightYAxis() { $grid_values = $this->getValuesGridWithPosition(GRAPH_YAXIS_SIDE_RIGHT, $this->right_y_empty); // Do not draw label at the bottom of right Y axis to avoid label averlapping with horizontal axis arrow. if (array_key_exists(0, $grid_values)) { unset($grid_values[0]); } $this->addItem( (new CSvgGraphAxis($grid_values, GRAPH_YAXIS_SIDE_RIGHT)) ->setLineColor($this->grid_color) ->setTextColor($this->text_color) ->setSize($this->offset_right, $this->canvas_height) ->setPosition($this->canvas_x + $this->canvas_width, $this->canvas_y) ); } /** * Add X axis with labels to graph. */ protected function drawCanvasXAxis() { $this->addItem( (new CSvgGraphAxis($this->getTimeGridWithPosition(), GRAPH_YAXIS_SIDE_BOTTOM)) ->setLineColor($this->grid_color) ->setTextColor($this->text_color) ->setSize($this->canvas_width, $this->xaxis_height) ->setPosition($this->canvas_x, $this->canvas_y + $this->canvas_height) ); } /** * Add grid to graph. */ protected function drawGrid() { $time_points = $this->x_show ? $this->getTimeGridWithPosition() : []; $value_points = []; if ($this->left_y_show) { $value_points = $this->getValuesGridWithPosition(GRAPH_YAXIS_SIDE_LEFT, $this->left_y_empty); unset($time_points[0]); } elseif ($this->right_y_show) { $value_points = $this->getValuesGridWithPosition(GRAPH_YAXIS_SIDE_RIGHT, $this->right_y_empty); unset($time_points[$this->canvas_width]); } if ($this->x_show) { unset($value_points[0]); } $this->addItem((new CSvgGraphGrid($value_points, $time_points)) ->setColor($this->grid_color) ->setPosition($this->canvas_x, $this->canvas_y) ->setSize($this->canvas_width, $this->canvas_height) ); } /** * Calculate paths for metric elements. */ protected function calculatePaths() { // Metric having very big values of y outside visible area will fail to render. $y_max = pow(2, 16); $y_min = -$y_max; foreach ($this->metrics as $index => $metric) { if (!array_key_exists($index, $this->points)) { continue; } if ($metric['options']['axisy'] == GRAPH_YAXIS_SIDE_RIGHT) { $min_value = $this->right_y_min; $max_value = $this->right_y_max; } else { $min_value = $this->left_y_min; $max_value = $this->left_y_max; } $time_range = ($this->time_till - $this->time_from) ? : 1; $timeshift = $metric['options']['timeshift']; $paths = []; $path_num = 0; foreach ($this->points[$index] as $clock => $point) { // If missing data function is SVG_GRAPH_MISSING_DATA_NONE, path should be split in multiple svg shapes. if ($point === null) { $path_num++; continue; } /** * Avoid invisible data point drawing. Data sets of type != SVG_GRAPH_TYPE_POINTS cannot be skipped to * keep shape unchanged. */ $in_range = ($max_value >= $point && $min_value <= $point); if ($in_range || $metric['options']['type'] != SVG_GRAPH_TYPE_POINTS) { $x = $this->canvas_x + $this->canvas_width - $this->canvas_width * ($this->time_till - $clock + $timeshift) / $time_range; if ($max_value - $min_value == INF) { $y = $this->canvas_y + CMathHelper::safeMul([$this->canvas_height, $max_value / 10 - $point / 10, 1 / ($max_value / 10 - $min_value / 10) ]); } else { $y = $this->canvas_y + CMathHelper::safeMul([$this->canvas_height, $max_value - $point, 1 / ($max_value - $min_value) ]); } if (!$in_range) { $y = ($point > $max_value) ? max($y_min, $y) : min($y_max, $y); } $paths[$path_num][] = [$x, ceil($y), convertUnits([ 'value' => $point, 'units' => $metric['units'] ])]; } } if ($paths) { $this->paths[$index] = $paths; } } } /** * Modifies metric data and Y value range according specified missing data function. */ protected function applyMissingDataFunc() { foreach ($this->metrics as $index => $metric) { /** * - Missing data points are calculated only between existing data points; * - Missing data points are not calculated for SVG_GRAPH_TYPE_POINTS && SVG_GRAPH_TYPE_BAR metrics; * - SVG_GRAPH_MISSING_DATA_CONNECTED is default behavior of SVG graphs, so no need to calculate anything * here. */ if (array_key_exists($index, $this->points) && !in_array($metric['options']['type'], [SVG_GRAPH_TYPE_POINTS, SVG_GRAPH_TYPE_BAR]) && $metric['options']['missingdatafunc'] != SVG_GRAPH_MISSING_DATA_CONNECTED) { $points = &$this->points[$index]; $missing_data_points = $this->getMissingData($points, $metric['options']['missingdatafunc']); // Sort according new clock times (array keys). $points += $missing_data_points; ksort($points); // Missing data function can change min value of Y axis. if ($missing_data_points && $metric['options']['missingdatafunc'] == SVG_GRAPH_MISSING_DATA_TREAT_AS_ZERO) { if ($this->min_value_left > 0 && $metric['options']['axisy'] == GRAPH_YAXIS_SIDE_LEFT) { $this->min_value_left = 0; } elseif ($this->min_value_right > 0 && $metric['options']['axisy'] == GRAPH_YAXIS_SIDE_RIGHT) { $this->min_value_right = 0; } } } } } /** * Calculate missing data for given set of $points according given $missingdatafunc. * * @param array $points Array of metric points to modify, where key is metric timestamp. * @param int $missingdatafunc Type of function, allowed value: * SVG_GRAPH_MISSING_DATA_TREAT_AS_ZERO, SVG_GRAPH_MISSING_DATA_NONE, * SVG_GRAPH_MISSING_DATA_CONNECTED * * @return array Array of calculated missing data points. */ protected function getMissingData(array $points, $missingdatafunc) { // Get average distance between points to detect gaps of missing data. $prev_clock = null; $points_distance = []; foreach ($points as $clock => $point) { if ($prev_clock !== null) { $points_distance[] = $clock - $prev_clock; } $prev_clock = $clock; } /** * $threshold is a minimal period of time at what we assume that data point is missed; * $average_distance is an average distance between existing data points; * $gap_interval is a time distance between missing points used to fulfill gaps of missing data. * It's unique for each gap. */ $average_distance = $points_distance ? array_sum($points_distance) / count($points_distance) : 0; $threshold = $points_distance ? $average_distance * 3 : 0; // Add missing values. $prev_clock = null; $missing_points = []; foreach ($points as $clock => $point) { if ($prev_clock !== null && ($clock - $prev_clock) > $threshold) { $gap_interval = floor(($clock - $prev_clock) / $threshold); if ($missingdatafunc == SVG_GRAPH_MISSING_DATA_NONE) { $missing_points[$prev_clock + $gap_interval] = null; } elseif ($missingdatafunc == SVG_GRAPH_MISSING_DATA_TREAT_AS_ZERO) { $missing_points[$prev_clock + $gap_interval] = 0; $missing_points[$clock - $gap_interval] = 0; } } $prev_clock = $clock; } return $missing_points; } /** * Add fill area to graph for metric of type SVG_GRAPH_TYPE_LINE or SVG_GRAPH_TYPE_STAIRCASE. */ protected function drawMetricArea(array $metric, array $paths) { $y_zero = ($metric['options']['axisy'] == GRAPH_YAXIS_SIDE_RIGHT) ? $this->right_y_zero : $this->left_y_zero; foreach ($paths as $path) { if (count($path) > 1) { $this->addItem(new CSvgGraphArea($path, $metric, $y_zero)); } } } /** * Add line paths to graph for metric of type SVG_GRAPH_TYPE_LINE or SVG_GRAPH_TYPE_STAIRCASE. */ protected function drawMetricsLine() { foreach ($this->metrics as $index => $metric) { if (array_key_exists($index, $this->paths) && ($metric['options']['type'] == SVG_GRAPH_TYPE_LINE || $metric['options']['type'] == SVG_GRAPH_TYPE_STAIRCASE)) { if ($metric['options']['fill'] > 0) { $this->drawMetricArea($metric, $this->paths[$index]); } $this->addItem(new CSvgGraphLineGroup($this->paths[$index], $metric)); } } } /** * Add metric of type points to graph. */ protected function drawMetricsPoint() { foreach ($this->metrics as $index => $metric) { if ($metric['options']['type'] == SVG_GRAPH_TYPE_POINTS && array_key_exists($index, $this->paths)) { $this->addItem(new CSvgGraphPoints(reset($this->paths[$index]), $metric)); } } } /** * Add metric of type bar to graph. */ protected function drawMetricsBar() { $bar_min_width = [ GRAPH_YAXIS_SIDE_LEFT => $this->canvas_width * .25, GRAPH_YAXIS_SIDE_RIGHT => $this->canvas_width * .25 ]; $bar_groups_indexes = []; $bar_groups_position = []; foreach ($this->paths as $index => $path) { if ($this->metrics[$index]['options']['type'] == SVG_GRAPH_TYPE_BAR) { // If one second in displayed over multiple pixels, this shows number of px in second. $sec_per_px = ceil(($this->time_till - $this->time_from) / $this->canvas_width); $px_per_sec = ceil($this->canvas_width / ($this->time_till - $this->time_from)); $y_axis_side = $this->metrics[$index]['options']['axisy']; $time_points = array_keys($this->points[$index]); $last_point = 0; $path = reset($path); foreach ($path as $point_index => $point) { $time_point = ($sec_per_px > $px_per_sec) ? floor($time_points[$point_index] / $sec_per_px) * $sec_per_px : $time_points[$point_index]; $bar_groups_indexes[$y_axis_side][$time_point][$index] = $point_index; $bar_groups_position[$y_axis_side][$time_point][$point_index] = $point[0]; if ($last_point > 0) { $bar_min_width[$y_axis_side] = min($point[0] - $last_point, $bar_min_width[$y_axis_side]); } $last_point = $point[0]; } } } foreach ($bar_groups_indexes as $y_axis => $points) { foreach ($points as $time_point => $paths) { $group_count = count($paths); $group_width = $bar_min_width[$y_axis]; $bar_width = ceil($group_width / $group_count * .75); $group_index = 0; foreach ($paths as $path_index => $point_index) { $group_x = $bar_groups_position[$y_axis][$time_point][$point_index]; if ($group_count > 1) { $this->paths[$path_index][0][$point_index][0] = $group_x // Calculate the leftmost X-coordinate including gap size. - $group_width * .375 // Calculate the X-offset for the each bar in the group. + ceil($bar_width * ($group_index + .5)); $group_index++; } $this->paths[$path_index][0][$point_index][3] = max(1, $bar_width); // X position for bars group. $this->paths[$path_index][0][$point_index][4] = $group_x - $group_width * .375; } } } foreach ($this->metrics as $index => $metric) { if ($metric['options']['type'] == SVG_GRAPH_TYPE_BAR && array_key_exists($index, $this->paths)) { $metric['options']['y_zero'] = ($metric['options']['axisy'] == GRAPH_YAXIS_SIDE_RIGHT) ? $this->right_y_zero : $this->left_y_zero; $metric['options']['bar_width'] = $bar_min_width[$metric['options']['axisy']]; $this->addItem(new CSvgGraphBar(reset($this->paths[$index]), $metric)); } } } /** * Add problems tooltip data to graph. */ protected function drawProblems() { $today = strtotime('today'); $container = (new CSvgGroup())->addClass(CSvgTag::ZBX_STYLE_GRAPH_PROBLEMS); foreach ($this->problems as $problem) { // If problem is never recovered, it will be drown till the end of graph or till current time. $time_to = ($problem['r_clock'] == 0) ? min($this->time_till, time()) : min($this->time_till, $problem['r_clock']); $time_range = $this->time_till - $this->time_from; $x1 = $this->canvas_x + $this->canvas_width - $this->canvas_width * ($this->time_till - $problem['clock']) / $time_range; $x2 = $this->canvas_x + $this->canvas_width - $this->canvas_width * ($this->time_till - $time_to) / $time_range; if ($this->canvas_x > $x1) { $x1 = $this->canvas_x; } // Make problem info. if ($problem['r_clock'] != 0) { $status_str = _('RESOLVED'); $status_color = ZBX_STYLE_OK_UNACK_FG; } else { $status_str = _('PROBLEM'); $status_color = ZBX_STYLE_PROBLEM_UNACK_FG; foreach ($problem['acknowledges'] as $acknowledge) { if ($acknowledge['action'] & ZBX_PROBLEM_UPDATE_CLOSE) { $status_str = _('CLOSING'); $status_color = ZBX_STYLE_OK_UNACK_FG; break; } } } $info = [ 'name' => $problem['name'], 'clock' => ($problem['clock'] >= $today) ? zbx_date2str(TIME_FORMAT_SECONDS, $problem['clock']) : zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['clock']), 'r_clock' => ($problem['r_clock'] != 0) ? ($problem['r_clock'] >= $today) ? zbx_date2str(TIME_FORMAT_SECONDS, $problem['r_clock']) : zbx_date2str(DATE_TIME_FORMAT_SECONDS, $problem['r_clock']) : '', 'url' => (new CUrl('tr_events.php')) ->setArgument('triggerid', $problem['objectid']) ->setArgument('eventid', $problem['eventid']) ->getUrl(), 'r_eventid' => $problem['r_eventid'], 'severity' => getSeverityStyle($problem['severity'], $problem['r_clock'] == 0), 'status' => $status_str, 'status_color' => $status_color ]; // At least 3 pixels expected to be occupied to show the range. Show simple anotation otherwise. $draw_type = ($x2 - $x1) > 2 ? CSvgGraphAnnotation::TYPE_RANGE : CSvgGraphAnnotation::TYPE_SIMPLE; // Draw border lines. Make them dashed if beginning or ending of highlighted zone is visible in graph. if ($problem['clock'] > $this->time_from) { $draw_type |= CSvgGraphAnnotation::DASH_LINE_START; } if ($this->time_till > $time_to) { $draw_type |= CSvgGraphAnnotation::DASH_LINE_END; } $container->addItem( (new CSvgGraphAnnotation($draw_type)) ->setInformation(json_encode($info)) ->setSize(min($x2 - $x1, $this->canvas_width), $this->canvas_height) ->setPosition(max($x1, $this->canvas_x), $this->canvas_y) ->setColor($this->color_annotation) ); } $this->addItem($container); } }
💾 保存文件
← 返回文件管理器