<?php
/**
* Copyright (C) 2019-2023 Graham Breach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* For more information, please contact <[email protected]>
*/
namespace Goat1000\SVGGraph;
class CrossHairs {
private $graph;
private $show_h = false;
private $show_v = false;
private $left, $top, $width, $height;
private $x_axis, $y_axis;
private $assoc;
private $flip_axes;
private $encoding;
public function __construct(&$graph, $left, $top, $w, $h, $x_axis, $y_axis,
$assoc, $flip_axes, $encoding)
{
if($graph->getOption('crosshairs')) {
$this->show_h = $graph->getOption('crosshairs_show_h');
$this->show_v = $graph->getOption('crosshairs_show_v');
}
if(!$this->show_h && !$this->show_v)
return;
$this->left = $left;
$this->top = $top;
$this->width = $w;
$this->height = $h;
$this->x_axis = $x_axis;
$this->y_axis = $y_axis;
$this->assoc = $assoc;
$this->flip_axes = $flip_axes;
$this->encoding = $encoding;
$this->graph =& $graph;
}
/**
* Returns TRUE if the crosshairs are enabled
*/
public function enabled()
{
return $this->show_h || $this->show_v;
}
/**
* Returns a horizontal or vertical hair
*/
private function getHair($orientation, $ch)
{
// line is always added, stays hidden if not enabled
if($orientation == 'h')
$hch = ['class' => 'chX', 'x2' => $ch['x1'] + $this->width];
else
$hch = ['class' => 'chY', 'y2' => $ch['y1'] + $this->height];
$show = 'show_' . $orientation;
if($this->$show) {
$stroke = $this->graph->getOption('crosshairs_colour_' . $orientation,
'crosshairs_colour');
$hch['stroke'] = new Colour($this->graph, $stroke, false, false);
$hch['stroke-width'] = $this->graph->getOption(
'crosshairs_stroke_width_' . $orientation, 'crosshairs_stroke_width');
$opacity = $this->graph->getOption(
'crosshairs_opacity_' . $orientation, 'crosshairs_opacity');
if($opacity > 0 && $opacity < 1)
$hch['opacity'] = $opacity;
$dash = $this->graph->getOption('crosshairs_dash_' . $orientation,
'crosshairs_dash');
if(!empty($dash))
$hch['stroke-dasharray'] = $dash;
}
return $this->graph->element('line', array_merge($ch, $hch));
}
/**
* Returns the crosshair code and also adds the JS and defs
*/
public function getCrossHairs()
{
if(!($this->show_v || $this->show_h))
return '';
// make the crosshair lines
$crosshairs = '';
$ch = [
'x1' => $this->left, 'y1' => $this->top,
'x2' => $this->left, 'y2' => $this->top,
'visibility' => 'hidden', // don't show them to start with!
];
$crosshairs .= $this->getHair('h', $ch);
$crosshairs .= $this->getHair('v', $ch);
$text_options = [
'back_colour', 'round', 'stroke_width', 'colour', 'font_size', 'font',
'font_weight', 'padding', 'space',
];
$t_opt = [];
foreach($text_options as $opt)
$t_opt[$opt] = $this->graph->getOption('crosshairs_text_' . $opt);
// text group for grid details
$text_group = ['id' => $this->graph->newId(), 'visibility' => 'hidden'];
$text_rect = [
'x' => '0', 'y' => '0', 'width' => '10', 'height' => '10',
'fill' => new Colour($this->graph, $t_opt['back_colour']),
];
if($t_opt['round'])
$text_rect['rx'] = $text_rect['ry'] = $t_opt['round'];
if($t_opt['stroke_width']) {
$text_rect['stroke-width'] = $t_opt['stroke_width'];
$text_rect['stroke'] = $t_opt['colour'];
}
$font_size = max(3, Number::units($t_opt['font_size']));
$text_element = [
'x' => 0, 'y' => $font_size,
'font-family' => $t_opt['font'],
'font-size' => $font_size,
'fill' => new Colour($this->graph, $t_opt['colour']),
];
$weight = $t_opt['font_weight'];
if($weight && $weight != 'normal')
$text_element['font-weight'] = $weight;
$svg_text = new Text($this->graph);
$text = $this->graph->element('g', $text_group, null,
$this->graph->element('rect', $text_rect) .
$svg_text->text('', $font_size, $text_element));
$this->graph->addBackMatter($text);
// add in the details of the grid scales
$zero_x = $this->x_axis->zero();
$scale_x = $this->x_axis->unit();
$zero_y = $this->y_axis->zero();
$scale_y = $this->y_axis->unit();
$prec_x = $this->graph->getOption('crosshairs_text_precision_h',
max(0, ceil(log10($scale_x))));
$prec_y = $this->graph->getOption('crosshairs_text_precision_v',
max(0, ceil(log10($scale_y))));
$scale_x = new Number($scale_x);
$scale_x->precision = 7;
$scale_y = new Number($scale_y);
$scale_y->precision = 7;
$gridx_attrs = [
'function' => 'strValueX',
'zero' => $zero_x,
'scale' => $scale_x,
'precision' => $prec_x,
];
$gridy_attrs = [
'function' => 'strValueY',
'zero' => $zero_y,
'scale' => $scale_y,
'precision' => $prec_y,
];
$chtextitem_attrs = [
'type' => 'xy',
'groupid' => $text_group['id'],
];
$u = $this->x_axis->afterUnits();
if(!empty($u))
$chtextitem_attrs['unitsx'] = $u;
$u = $this->y_axis->afterUnits();
if(!empty($u))
$chtextitem_attrs['unitsy'] = $u;
$u = $this->x_axis->beforeUnits();
if(!empty($u))
$chtextitem_attrs['unitsbx'] = $u;
$u = $this->y_axis->beforeUnits();
if(!empty($u))
$chtextitem_attrs['unitsby'] = $u;
$log_x = $this->graph->getOption(['log_axis_x', 0]);
$log_y = $this->graph->getOption(['log_axis_y', 0]);
if($log_x || $log_y) {
$base_y = $this->graph->getOption('log_axis_y_base');
$base_x = $this->graph->getOption('log_axis_x_base');
$log_h = $this->flip_axes ? $log_y : $log_x;
$log_v = $this->flip_axes ? $log_x : $log_y;
if($log_h) {
$gridx_attrs['base'] = $this->flip_axes ? $base_y : $base_x;
$gridx_attrs['zero'] = $this->x_axis->value(0);
$gridx_attrs['scale'] = $this->x_axis->value($this->width);
$this->graph->getJavascript()->addFunction('logStrValueX');
$gridx_attrs['function'] = 'logStrValueX';
}
if($log_v) {
$gridy_attrs['base'] = $this->flip_axes ? $base_x : $base_y;
$gridy_attrs['zero'] = $this->y_axis->value(0);
$gridy_attrs['scale'] = $this->y_axis->value($this->height);
$this->graph->getJavascript()->addFunction('logStrValueY');
$gridy_attrs['function'] = 'logStrValueY';
}
}
if($this->graph->getOption('datetime_keys') &&
(method_exists($this->x_axis, 'GetFormat') ||
method_exists($this->y_axis, 'GetFormat'))) {
$dtf = new DateTimeFormatter;
if($this->flip_axes) {
$this->graph->getJavascript()->addFunction('dateStrValueY');
$zy = (int)$this->y_axis->value(0);
$ey = (int)$this->y_axis->value($this->width);
$dt = new \DateTime('@' . $zy);
$gridy_attrs['scale'] = ($ey - $zy) / $this->height;
$gridy_attrs['zero'] = $dtf->format($dt, 'c', true);
$gridy_attrs['function'] = 'dateStrValueY';
$gridy_attrs['format'] = $this->y_axis->getFormat();
} else {
$this->graph->getJavascript()->addFunction('dateStrValueX');
$zx = (int)$this->x_axis->value(0);
$ex = (int)$this->x_axis->value($this->width);
$dt = new \DateTime('@' . $zx);
$gridx_attrs['scale'] = ($ex - $zx) / $this->width;
$gridx_attrs['zero'] = $dtf->format($dt, 'c', true);
$gridx_attrs['function'] = 'dateStrValueX';
$gridx_attrs['format'] = $this->x_axis->getFormat();
}
$long_days = $dtf->getLongDays();
$short_days = $dtf->getShortDays();
$long_months = $dtf->getLongMonths();
$short_months = $dtf->getShortMonths();
foreach($long_days as $day)
$this->graph->getJavascript()->insertVariable('daysLong', null, $day);
foreach($short_days as $day)
$this->graph->getJavascript()->insertVariable('daysShort', null, $day);
foreach($long_months as $month)
$this->graph->getJavascript()->insertVariable('monthsLong', null, $month);
foreach($short_months as $month)
$this->graph->getJavascript()->insertVariable('monthsShort', null, $month);
}
// build associative data keys XML
$keys_xml = '';
if($this->assoc) {
$k_max = $this->graph->getMaxKey();
for($i = 0; $i <= $k_max; ++$i) {
$k = $this->graph->getKey($i);
$keys_xml .= $this->graph->element('svggraph:key', ['value' => $k]);
}
$keys_xml = $this->graph->element('svggraph:keys', null, null, $keys_xml);
// choose a rounding function
$round_function = 'kround';
if($this->graph->getOption('label_centre'))
$round_function = 'kroundDown';
$this->graph->getJavascript()->addFunction($round_function);
// set the string function
if($this->flip_axes) {
$this->graph->getJavascript()->addFunction('keyStrValueY');
$gridy_attrs['function'] = 'keyStrValueY';
$gridy_attrs['round'] = $round_function;
} else {
$this->graph->getJavascript()->addFunction('keyStrValueX');
$gridx_attrs['function'] = 'keyStrValueX';
$gridx_attrs['round'] = $round_function;
}
}
$gridx = $this->graph->element('svggraph:gridx', $gridx_attrs);
$gridy = $this->graph->element('svggraph:gridy', $gridy_attrs);
$chtext = $this->graph->element('svggraph:chtext', null, null,
$this->graph->element('svggraph:chtextitem', $chtextitem_attrs));
$xml = $gridx . $gridy . $chtext . $keys_xml;
$defs = $this->graph->element('svggraph:data',
['xmlns:svggraph' => 'http://www.goat1000.com/svggraph'], null, $xml);
$this->graph->defs->add($defs);
// add the main function at the end - it can fill in any defaults
$this->graph->getJavascript()->addFunction('crosshairs');
return $crosshairs;
}
}
|