Improve chart axis readability
Introduce different line weights to separate between the smallest visible separator (steps) and single chart values (ticks). Calculate the amount of ticks per step using the available chart space. fixes #7846
This commit is contained in:
parent
debc305789
commit
807666bf88
|
@ -77,6 +77,20 @@ class Axis implements Drawable
|
|||
*/
|
||||
private $yUnit = null;
|
||||
|
||||
/**
|
||||
* The minimum amount of units each step must take up
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $minUnitsPerStep = 80;
|
||||
|
||||
/**
|
||||
* The minimum amount of units each tick must take up
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $minUnitsPerTick = 15;
|
||||
|
||||
/**
|
||||
* If the displayed labels should be aligned horizontally or diagonally
|
||||
*/
|
||||
|
@ -160,6 +174,14 @@ class Axis implements Drawable
|
|||
*/
|
||||
private function renderHorizontalAxis(RenderContext $ctx, DOMElement $group)
|
||||
{
|
||||
$steps = $this->ticksPerX($this->xUnit->getTicks(), $ctx->getNrOfUnitsX(), $this->minUnitsPerStep);
|
||||
$ticks = $this->ticksPerX($this->xUnit->getTicks(), $ctx->getNrOfUnitsX(), $this->minUnitsPerTick);
|
||||
|
||||
// Steps should always be ticks
|
||||
if ($ticks !== $steps) {
|
||||
$steps = $ticks * 5;
|
||||
}
|
||||
|
||||
$line = new Line(0, 100, 100, 100);
|
||||
$line->setStrokeWidth(2);
|
||||
$group->appendChild($line->toSvg($ctx));
|
||||
|
@ -168,41 +190,48 @@ class Axis implements Drawable
|
|||
$lastLabelEnd = -1;
|
||||
$shift = 0;
|
||||
|
||||
$i = 0;
|
||||
foreach ($this->xUnit as $label => $pos) {
|
||||
if ($this->labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) {
|
||||
// If the last label would overlap this label we shift the y axis a bit
|
||||
if ($lastLabelEnd > $pos) {
|
||||
$shift = ($shift + 5) % 10;
|
||||
} else {
|
||||
$shift = 0;
|
||||
|
||||
if ($i % $ticks === 0) {
|
||||
$tick = new Line($pos, 100, $pos, 101);
|
||||
$group->appendChild($tick->toSvg($ctx));
|
||||
}
|
||||
|
||||
if ($i % $steps === 0) {
|
||||
if ($this->labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) {
|
||||
// If the last label would overlap this label we shift the y axis a bit
|
||||
if ($lastLabelEnd > $pos) {
|
||||
$shift = ($shift + 5) % 10;
|
||||
} else {
|
||||
$shift = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$labelField = new Text($pos + 0.5, ($this->xLabel ? 107 : 105) + $shift, $label);
|
||||
if ($this->labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) {
|
||||
$labelField->setAlignment(Text::ALIGN_MIDDLE)
|
||||
->setFontSize('1.8em');
|
||||
} else {
|
||||
$labelField->setFontSize('2.5em');
|
||||
}
|
||||
|
||||
if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) {
|
||||
$labelField = new Rotator($labelField, 45);
|
||||
}
|
||||
$labelField = $labelField->toSvg($ctx);
|
||||
|
||||
$group->appendChild($labelField);
|
||||
|
||||
if ($this->drawYGrid) {
|
||||
$bgLine = new Line($pos, 0, $pos, 100);
|
||||
$bgLine->setStrokeWidth(0.5)
|
||||
->setStrokeColor('#232');
|
||||
$group->appendChild($bgLine->toSvg($ctx));
|
||||
}
|
||||
$lastLabelEnd = $pos + strlen($label) * 1.2;
|
||||
}
|
||||
|
||||
$tick = new Line($pos, 100, $pos, 102);
|
||||
$group->appendChild($tick->toSvg($ctx));
|
||||
|
||||
$labelField = new Text($pos + 0.5, ($this->xLabel ? 107 : 105) + $shift, $label);
|
||||
if ($this->labelRotationStyle === self::LABEL_ROTATE_HORIZONTAL) {
|
||||
$labelField->setAlignment(Text::ALIGN_MIDDLE)
|
||||
->setFontSize('1.8em');
|
||||
} else {
|
||||
$labelField->setFontSize('2.5em');
|
||||
}
|
||||
|
||||
if ($this->labelRotationStyle === self::LABEL_ROTATE_DIAGONAL) {
|
||||
$labelField = new Rotator($labelField, 45);
|
||||
}
|
||||
$labelField = $labelField->toSvg($ctx);
|
||||
|
||||
$group->appendChild($labelField);
|
||||
|
||||
if ($this->drawYGrid) {
|
||||
$bgLine = new Line($pos, 0, $pos, 100);
|
||||
$bgLine->setStrokeWidth(0.5)
|
||||
->setStrokeColor('#232');
|
||||
$group->appendChild($bgLine->toSvg($ctx));
|
||||
}
|
||||
$lastLabelEnd = $pos + strlen($label) * 1.2;
|
||||
$i++;
|
||||
}
|
||||
|
||||
// render the label for this axis
|
||||
|
@ -223,26 +252,42 @@ class Axis implements Drawable
|
|||
*/
|
||||
private function renderVerticalAxis(RenderContext $ctx, DOMElement $group)
|
||||
{
|
||||
$steps = $this->ticksPerX($this->yUnit->getTicks(), $ctx->getNrOfUnitsY(), $this->minUnitsPerStep);
|
||||
$ticks = $this->ticksPerX($this->yUnit->getTicks(), $ctx->getNrOfUnitsY(), $this->minUnitsPerTick);
|
||||
|
||||
// Steps should always be ticks
|
||||
if ($ticks !== $steps) {
|
||||
$steps = $ticks * 5;
|
||||
}
|
||||
$line = new Line(0, 0, 0, 100);
|
||||
$line->setStrokeWidth(2);
|
||||
$group->appendChild($line->toSvg($ctx));
|
||||
|
||||
$i = 0;
|
||||
foreach ($this->yUnit as $label => $pos) {
|
||||
$pos = 100 - $pos;
|
||||
$tick = new Line(0, $pos, -1, $pos);
|
||||
$group->appendChild($tick->toSvg($ctx));
|
||||
|
||||
$labelField = new Text(-0.5, $pos+0.5, $label);
|
||||
$labelField->setFontSize('1.8em')
|
||||
->setAlignment(Text::ALIGN_END);
|
||||
|
||||
$group->appendChild($labelField->toSvg($ctx));
|
||||
if ($this->drawXGrid) {
|
||||
$bgLine = new Line(0, $pos, 100, $pos);
|
||||
$bgLine->setStrokeWidth(0.5)
|
||||
->setStrokeColor('#343');
|
||||
$group->appendChild($bgLine->toSvg($ctx));
|
||||
if ($i % $ticks === 0) {
|
||||
// draw a tick
|
||||
$tick = new Line(0, $pos, -1, $pos);
|
||||
$group->appendChild($tick->toSvg($ctx));
|
||||
}
|
||||
|
||||
if ($i % $steps === 0) {
|
||||
// draw a step
|
||||
$labelField = new Text(-0.5, $pos+0.5, $label);
|
||||
$labelField->setFontSize('1.8em')
|
||||
->setAlignment(Text::ALIGN_END);
|
||||
|
||||
$group->appendChild($labelField->toSvg($ctx));
|
||||
if ($this->drawXGrid) {
|
||||
$bgLine = new Line(0, $pos, 100, $pos);
|
||||
$bgLine->setStrokeWidth(0.5)
|
||||
->setStrokeColor('#343');
|
||||
$group->appendChild($bgLine->toSvg($ctx));
|
||||
}
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
if ($this->yLabel) {
|
||||
|
@ -416,4 +461,14 @@ class Axis implements Drawable
|
|||
$this->renderVerticalAxis($ctx, $group);
|
||||
return $group;
|
||||
}
|
||||
|
||||
protected function ticksPerX($ticks, $units, $min)
|
||||
{
|
||||
$per = 1;
|
||||
while ($per * $units / $ticks < $min) {
|
||||
$per++;
|
||||
}
|
||||
return $per;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class BarGraph extends Styleable implements Drawable
|
|||
*
|
||||
* @var int
|
||||
*/
|
||||
private $barWidth = 4;
|
||||
private $barWidth = 1;
|
||||
|
||||
/**
|
||||
* The dataset to use for this bar graph
|
||||
|
|
|
@ -9,13 +9,14 @@ use Iterator;
|
|||
/**
|
||||
* Base class for Axis Units
|
||||
*
|
||||
* An AxisUnit takes a set of values and places them on a given range
|
||||
*
|
||||
* Concrete subclasses must implement the iterator interface, with
|
||||
* getCurrent returning the axis relative position and getValue the label
|
||||
* that will be displayed
|
||||
*/
|
||||
interface AxisUnit extends Iterator
|
||||
{
|
||||
|
||||
/**
|
||||
* Add a dataset to this AxisUnit, required for dynamic min and max vlaues
|
||||
*
|
||||
|
@ -46,4 +47,11 @@ interface AxisUnit extends Iterator
|
|||
* @param int $max The new maximum value
|
||||
*/
|
||||
public function setMax($max);
|
||||
|
||||
/**
|
||||
* Get the amount of ticks of this axis
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTicks();
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ namespace Icinga\Chart\Unit;
|
|||
*/
|
||||
class LinearUnit implements AxisUnit
|
||||
{
|
||||
|
||||
/**
|
||||
* The minimum value to display
|
||||
*
|
||||
|
@ -43,7 +42,7 @@ class LinearUnit implements AxisUnit
|
|||
*
|
||||
* @var int
|
||||
*/
|
||||
private $nrOfTicks = 10;
|
||||
protected $nrOfTicks = 10;
|
||||
|
||||
/**
|
||||
* The currently displayed tick
|
||||
|
@ -95,45 +94,13 @@ class LinearUnit implements AxisUnit
|
|||
if (!$this->staticMin) {
|
||||
$this->min = min($this->min, $datapoints[0]);
|
||||
}
|
||||
if (!$this->staticMin || !$this->staticMax) {
|
||||
$this->updateMaxValue();
|
||||
}
|
||||
$this->currentTick = 0;
|
||||
$this->currentValue = $this->min;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the range depending on the current values of min, max and nrOfTicks
|
||||
*/
|
||||
private function updateMaxValue()
|
||||
{
|
||||
$this->max = $this->calculateTickRange($this->max - $this->min, $this->nrOfTicks) *
|
||||
$this->nrOfTicks + $this->min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the minimum tick range that is necessary to display the given value range
|
||||
* correctly
|
||||
*
|
||||
* @param int range The range to display
|
||||
* @param int ticks The amount of ticks to use
|
||||
*
|
||||
* @return int The value for each tick
|
||||
*/
|
||||
private function calculateTickRange($range, $ticks)
|
||||
{
|
||||
$factor = 1;
|
||||
$steps = array(1, 2, 5);
|
||||
$step = 0;
|
||||
while ($range / ($factor * $steps[$step]) > $ticks) {
|
||||
$step++;
|
||||
if ($step === count($steps)) {
|
||||
$step = 0;
|
||||
$factor *= 10;
|
||||
}
|
||||
if ($this->max === $this->min) {
|
||||
$this->max = $this->min + 10;
|
||||
}
|
||||
return $steps[$step] * $factor;
|
||||
$this->nrOfTicks = $this->max - $this->min;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,7 +116,7 @@ class LinearUnit implements AxisUnit
|
|||
} elseif ($value > $this->max) {
|
||||
return 100;
|
||||
} else {
|
||||
return 100 * ($value - $this->min) / $this->max - $this->min;
|
||||
return 100 * ($value - $this->min) / $this->nrOfTicks;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,7 +178,6 @@ class LinearUnit implements AxisUnit
|
|||
if ($max !== null) {
|
||||
$this->max = $max;
|
||||
$this->staticMax = true;
|
||||
$this->updateMaxValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +191,6 @@ class LinearUnit implements AxisUnit
|
|||
if ($min !== null) {
|
||||
$this->min = $min;
|
||||
$this->staticMin = true;
|
||||
$this->updateMaxValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,4 +213,14 @@ class LinearUnit implements AxisUnit
|
|||
{
|
||||
return $this->max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of ticks necessary to display this AxisUnit
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTicks()
|
||||
{
|
||||
return $this->nrOfTicks;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ class LogarithmicUnit implements AxisUnit
|
|||
{
|
||||
$currentBase = $this->currentTick + $this->minExp;
|
||||
if (abs($currentBase) > 4) {
|
||||
return '10E' . $currentBase;
|
||||
return $this->base . 'E' . $currentBase;
|
||||
}
|
||||
return (string) intval($this->pow($currentBase));
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ class LogarithmicUnit implements AxisUnit
|
|||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getTicks()
|
||||
public function getTicks()
|
||||
{
|
||||
return $this->maxExp - $this->minExp;
|
||||
}
|
||||
|
|
|
@ -118,4 +118,14 @@ class StaticAxis implements AxisUnit
|
|||
{
|
||||
return reset($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of ticks of this axis
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTicks()
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue