mirror of
https://github.com/seigler/neat-charts
synced 2025-07-27 01:16:09 +00:00
improved demo, added "honest charts" zero-axis option
This commit is contained in:
parent
a582c2f97d
commit
bbe218d7fe
3 changed files with 70 additions and 48 deletions
17
demo.php
17
demo.php
|
@ -3,9 +3,9 @@ require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
function randomData($count = 96) {
|
function randomData($count = 96) {
|
||||||
$randomData = [];
|
$randomData = [];
|
||||||
$offset = 100 * (rand()/getRandMax())**4;
|
$offset = 100 * (rand()/getRandMax())**2;
|
||||||
$scale = 100 * (rand()/getRandMax())**2;
|
$scale = max(0.1 * $offset, 100 * rand() / getRandMax());
|
||||||
$volatility = 0.5 * (rand()/getRandMax())**3;
|
$volatility = 0.25 * (rand()/getRandMax())**3 + 0.25;
|
||||||
for ($n = 0, $current = $offset + 0.5 * $scale; $n < $count; $n++) {
|
for ($n = 0, $current = $offset + 0.5 * $scale; $n < $count; $n++) {
|
||||||
$current -= $offset;
|
$current -= $offset;
|
||||||
$current *= 1 + $volatility * (rand()/getRandMax() - 0.5);
|
$current *= 1 + $volatility * (rand()/getRandMax() - 0.5);
|
||||||
|
@ -32,14 +32,14 @@ function randomData($count = 96) {
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
<h2>SVG chart in <code>img</code> tag</h2>
|
<h2>Chart in <code>img</code> tag</h2>
|
||||||
<figure>
|
<figure>
|
||||||
<img src="./demo-as-image.php">
|
<img src="./demo-as-image.php">
|
||||||
<figcaption>Random generated data, loaded as an image</figcaption>
|
<figcaption>Random generated data, loaded as an image</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>SVG chart in <code>svg</code> tag</h2>
|
<h2>Chart in <code>svg</code> tag, zero axis shown</h2>
|
||||||
<figure>
|
<figure>
|
||||||
<?php
|
<?php
|
||||||
$chart = new NeatCharts\LineChart(randomData(), [
|
$chart = new NeatCharts\LineChart(randomData(), [
|
||||||
|
@ -48,7 +48,8 @@ $chart = new NeatCharts\LineChart(randomData(), [
|
||||||
'lineColor'=>'#F00',
|
'lineColor'=>'#F00',
|
||||||
'labelColor'=>'#222',
|
'labelColor'=>'#222',
|
||||||
'smoothed'=>false,
|
'smoothed'=>false,
|
||||||
'fontSize'=>14
|
'fontSize'=>14,
|
||||||
|
'yAxisZero'=>true
|
||||||
]);
|
]);
|
||||||
echo $chart->render();
|
echo $chart->render();
|
||||||
?>
|
?>
|
||||||
|
@ -56,7 +57,7 @@ echo $chart->render();
|
||||||
</figure>
|
</figure>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>Smoothed SVG chart in <code>svg</code> tag</h2>
|
<h2>Smoothed chart in <code>svg</code> tag</h2>
|
||||||
<figure>
|
<figure>
|
||||||
<?php
|
<?php
|
||||||
$chart = new NeatCharts\LineChart(randomData(12), [
|
$chart = new NeatCharts\LineChart(randomData(12), [
|
||||||
|
@ -73,7 +74,7 @@ echo $chart->render();
|
||||||
</figure>
|
</figure>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>SVG sparkline in <code>svg</code> tag</h2>
|
<h2>Sparkline in <code>svg</code> tag</h2>
|
||||||
<figure>
|
<figure>
|
||||||
<?php
|
<?php
|
||||||
$chart = new NeatCharts\LineChart(randomData(48), [
|
$chart = new NeatCharts\LineChart(randomData(48), [
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
<?php
|
<?php
|
||||||
namespace NeatCharts {
|
namespace NeatCharts {
|
||||||
class LineChart extends NeatChart {
|
class LineChart extends NeatChart {
|
||||||
|
public function setOptions($options) {
|
||||||
|
$this->options = [ // LineChart defaults
|
||||||
|
'width' => 800,
|
||||||
|
'height' => 250,
|
||||||
|
'lineColor' => '#000',
|
||||||
|
'markerColor' => '#000',
|
||||||
|
'labelColor' => '#000',
|
||||||
|
'smoothed' => false,
|
||||||
|
'fontSize' => 15,
|
||||||
|
'yAxisEnabled'=>true,
|
||||||
|
'xAxisEnabled'=>false,
|
||||||
|
'yAxisZero'=>false
|
||||||
|
];
|
||||||
|
parent::setOptions($options);
|
||||||
|
}
|
||||||
|
|
||||||
public function setData($chartData) {
|
public function setData($chartData) {
|
||||||
|
$this->setWindow($chartData, $this->options); // sets min, max, range, etc
|
||||||
// we assume $chartData is sorted by key and keys and values are all numeric
|
// we assume $chartData is sorted by key and keys and values are all numeric
|
||||||
$previousX = $previousY = null;
|
$previousX = $previousY = null;
|
||||||
end($chartData);
|
|
||||||
$this->xMax = key($chartData);
|
|
||||||
reset($chartData);
|
|
||||||
$this->xMin = key($chartData);
|
|
||||||
$this->xRange = $this->xMax - $this->xMin;
|
|
||||||
$count = count($chartData);
|
$count = count($chartData);
|
||||||
$deltaX = $this->xRange / $count;
|
$deltaX = $this->xRange / $count;
|
||||||
$this->yMin = INF; // so the first comparison sets this to an actual value
|
|
||||||
$this->yMax = -INF;
|
|
||||||
$averageAbsSlope = 0; // we will add all of them then divide to get an average
|
$averageAbsSlope = 0; // we will add all of them then divide to get an average
|
||||||
$secants = []; // slope between this point and the previous one
|
$secants = []; // slope between this point and the previous one
|
||||||
$tangents = []; // slope across the point
|
$tangents = []; // slope across the point
|
||||||
|
|
||||||
foreach ($chartData as $x => $y) {
|
foreach ($chartData as $x => $y) {
|
||||||
if ($y < $this->yMin) {
|
|
||||||
$this->yMin = $y;
|
|
||||||
$yMinX = $x;
|
|
||||||
}
|
|
||||||
if ($y > $this->yMax) {
|
|
||||||
$this->yMax = $y;
|
|
||||||
$yMaxX = $x;
|
|
||||||
}
|
|
||||||
if (!is_null($previousY)) {
|
if (!is_null($previousY)) {
|
||||||
$averageAbsSlope += abs($y - $previousY); // just add up all the Y differences
|
$averageAbsSlope += abs($y - $previousY); // just add up all the Y differences
|
||||||
$secants[$previousX] = ($y - $previousY) / $deltaX;
|
$secants[$previousX] = ($y - $previousY) / $deltaX;
|
||||||
|
@ -36,7 +38,6 @@ namespace NeatCharts {
|
||||||
$previousY = $y;
|
$previousY = $y;
|
||||||
$previousX = $x;
|
$previousX = $x;
|
||||||
}
|
}
|
||||||
$this->yRange = $this->yMax - $this->yMin;
|
|
||||||
$averageAbsSlope /= $this->yRange * $deltaX; // turn this absolute-deltas total into a slope
|
$averageAbsSlope /= $this->yRange * $deltaX; // turn this absolute-deltas total into a slope
|
||||||
|
|
||||||
if ($this->options['smoothed']) {
|
if ($this->options['smoothed']) {
|
||||||
|
@ -140,15 +141,13 @@ namespace NeatCharts {
|
||||||
}
|
}
|
||||||
|
|
||||||
$chartPoints = 'M';
|
$chartPoints = 'M';
|
||||||
$chartSplines = 'M'.
|
|
||||||
$this->transformX($this->xMin).','.
|
|
||||||
$this->transformY($chartData[$this->xMin]);
|
|
||||||
if ($this->options['smoothed']) {
|
if ($this->options['smoothed']) {
|
||||||
|
$chartPoints .= $this->transformX($this->xMin).','.$this->transformY($chartData[$this->xMin]);
|
||||||
foreach ($chartData as $x => $y) {
|
foreach ($chartData as $x => $y) {
|
||||||
$controlX = $deltaX / 3 / sqrt(1 + $tangents[$x]**2);
|
$controlX = $deltaX / 3 / sqrt(1 + $tangents[$x]**2);
|
||||||
$controlY = $tangents[$x] * $controlX;
|
$controlY = $tangents[$x] * $controlX;
|
||||||
if ($x != $this->xMin) {
|
if ($x != $this->xMin) {
|
||||||
$chartSplines .= ' S'.
|
$chartPoints .= ' S'.
|
||||||
$this->transformX($x - $controlX).','.
|
$this->transformX($x - $controlX).','.
|
||||||
$this->transformY($y - $controlY).' '.
|
$this->transformY($y - $controlY).' '.
|
||||||
$this->transformX($x).','.
|
$this->transformX($x).','.
|
||||||
|
@ -166,31 +165,29 @@ namespace NeatCharts {
|
||||||
$chartID = rand();
|
$chartID = rand();
|
||||||
$this->output = '<svg viewBox="-'.( $this->padding['left'] ).' -'.( $this->padding['top'] ).' '.( $this->options['width'] ).' '.( $this->options['height'] ).'" width="'.( $this->options['width'] ).'" height="'.( $this->options['height'] ).'" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
$this->output = '<svg viewBox="-'.( $this->padding['left'] ).' -'.( $this->padding['top'] ).' '.( $this->options['width'] ).' '.( $this->options['height'] ).'" width="'.( $this->options['width'] ).'" height="'.( $this->options['height'] ).'" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<defs>
|
<defs>
|
||||||
<marker id="SVGChart-markerCircle-'.( $chartID ).'" markerWidth="2" markerHeight="2" refX="1" refY="1" markerUnits="strokeWidth">
|
<marker id="neatchart-markerCircle-'.( $chartID ).'" markerWidth="2" markerHeight="2" refX="1" refY="1" markerUnits="strokeWidth">
|
||||||
<circle cx="1" cy="1" r="1" style="stroke: none; fill:'.( $this->options['markerColor'] ).';" />
|
<circle class="neatchart-marker" cx="1" cy="1" r="1" stroke="none" fill="'.( $this->options['markerColor'] ).'" />
|
||||||
</marker>
|
</marker>
|
||||||
<linearGradient id="SVGChart-fadeFromNothing-'.( $chartID ).'" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="neatchart-fadeFromNothing-'.( $chartID ).'" x1="0%" y1="0%" x2="100%" y2="0%" gradientUnits="userSpaceOnUse">
|
||||||
<stop offset="0.5%" stop-color="'.( $this->options['lineColor'] ).'" stop-opacity="0"></stop>
|
<stop offset="0.5%" stop-color="'.( $this->options['lineColor'] ).'" stop-opacity="0"></stop>
|
||||||
<stop offset="5%" stop-color="'.( $this->options['lineColor'] ).'" stop-opacity="1"></stop>
|
<stop offset="2%" stop-color="'.( $this->options['lineColor'] ).'" stop-opacity="1"></stop>
|
||||||
<stop offset="100%" stop-color="'.( $this->options['lineColor'] ).'" stop-opacity="1"></stop>
|
<stop offset="100%" stop-color="'.( $this->options['lineColor'] ).'" stop-opacity="1"></stop>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<g class="SVGChart">'.( $this->options['yAxisEnabled'] || $this->options['xAxisEnabled'] ? '
|
<g class="neatchart">'.( $this->options['yAxisEnabled'] || $this->options['xAxisEnabled'] ? '
|
||||||
<g class="chart__gridLines"
|
<g class="chart__gridLines"
|
||||||
shape-rendering="crispEdges"
|
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="'.( $this->options['labelColor'] ).'"
|
stroke="'.( $this->options['labelColor'] ).'"
|
||||||
stroke-opacity="0.75"
|
|
||||||
stroke-width="1"
|
stroke-width="1"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
stroke-dasharray="2, 2"
|
stroke-dasharray="2, 2"
|
||||||
>
|
shape-rendering="crispEdges">
|
||||||
<path class="chart__gridLinePaths" d="'.( $gridLines ).'" />
|
<path class="chart__gridLinePaths" d="'.( $gridLines ).'" />
|
||||||
</g>
|
</g>
|
||||||
<g class="chart__gridLabels"
|
<g class="chart__gridLabels"
|
||||||
fill="'.( $this->options['labelColor'] ).'"
|
fill="'.( $this->options['labelColor'] ).'"
|
||||||
font-family="monospace"
|
font-family="monospace"
|
||||||
font-size="'.( $this->options['fontSize'] ).'px"
|
font-size="'.( $this->options['fontSize'] ).'px">
|
||||||
>
|
|
||||||
'.( $gridText ).'
|
'.( $gridText ).'
|
||||||
</g>' : '').'
|
</g>' : '').'
|
||||||
<g class="chart__plotLine"
|
<g class="chart__plotLine"
|
||||||
|
@ -198,10 +195,16 @@ namespace NeatCharts {
|
||||||
stroke-width="'.( $this->options['fontSize'] / 3 ).'"
|
stroke-width="'.( $this->options['fontSize'] / 3 ).'"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke="url(#SVGChart-fadeFromNothing-'.( $chartID ).')"
|
stroke="url(#neatchart-fadeFromNothing-'.( $chartID ).')"
|
||||||
marker-end="url(#SVGChart-markerCircle-'.( $chartID ).')"
|
marker-end="url(#neatchart-markerCircle-'.( $chartID ).')"
|
||||||
>
|
>
|
||||||
<path d="'.( $this->options['smoothed'] ? $chartSplines : $chartPoints ).'" />
|
<path d="'.( $chartPoints ).'" />'.($this->options['yAxisZero'] ? '
|
||||||
|
<path
|
||||||
|
stroke="none"
|
||||||
|
fill="url(#neatchart-fadeFromNothing-'.( $chartID ).')"
|
||||||
|
fill-opacity="0.25"
|
||||||
|
marker-end="none"
|
||||||
|
d="'.$chartPoints.' L'.$this->width.','.$this->height.' 0,'.$this->height.' Z'.'" />' : '').'
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>';
|
</svg>';
|
||||||
|
|
|
@ -10,7 +10,8 @@ namespace NeatCharts {
|
||||||
'smoothed' => false,
|
'smoothed' => false,
|
||||||
'fontSize' => 15,
|
'fontSize' => 15,
|
||||||
'yAxisEnabled'=>true,
|
'yAxisEnabled'=>true,
|
||||||
'xAxisEnabled'=>false
|
'xAxisEnabled'=>false,
|
||||||
|
'yAxisZero'=>false
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $width;
|
protected $width;
|
||||||
|
@ -24,11 +25,6 @@ namespace NeatCharts {
|
||||||
protected $yRange;
|
protected $yRange;
|
||||||
protected $padding = ['top'=>10, 'right'=>10, 'bottom'=>10, 'left'=>10];
|
protected $padding = ['top'=>10, 'right'=>10, 'bottom'=>10, 'left'=>10];
|
||||||
|
|
||||||
protected function arrayGet($array, $key, $default = NULL)
|
|
||||||
{
|
|
||||||
return isset($array[$key]) ? $array[$key] : $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function labelFormat($float, $places, $minPlaces = 0) {
|
protected function labelFormat($float, $places, $minPlaces = 0) {
|
||||||
$value = number_format($float, max($minPlaces, $places));
|
$value = number_format($float, max($minPlaces, $places));
|
||||||
// add a trailing space if there's no decimal
|
// add a trailing space if there's no decimal
|
||||||
|
@ -57,7 +53,29 @@ namespace NeatCharts {
|
||||||
return $precision;
|
return $precision;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct($chartData, $options = []) {
|
protected function setWindow($chartData) {
|
||||||
|
end($chartData);
|
||||||
|
$this->xMax = key($chartData);
|
||||||
|
reset($chartData);
|
||||||
|
$this->xMin = key($chartData);
|
||||||
|
$this->xRange = $this->xMax - $this->xMin;
|
||||||
|
$this->yMin = ($this->options['yAxisZero'] ? 0 : INF);
|
||||||
|
$this->yMax = -INF;
|
||||||
|
|
||||||
|
foreach ($chartData as $x => $y) {
|
||||||
|
if ($y < $this->yMin) {
|
||||||
|
$this->yMin = $y;
|
||||||
|
$yMinX = $x;
|
||||||
|
}
|
||||||
|
if ($y > $this->yMax) {
|
||||||
|
$this->yMax = $y;
|
||||||
|
$yMaxX = $x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->yRange = $this->yMax - $this->yMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
final public function __construct($chartData, $options = []) {
|
||||||
$this->setOptions($options);
|
$this->setOptions($options);
|
||||||
$this->setData($chartData);
|
$this->setData($chartData);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue