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) {
|
||||
$randomData = [];
|
||||
$offset = 100 * (rand()/getRandMax())**4;
|
||||
$scale = 100 * (rand()/getRandMax())**2;
|
||||
$volatility = 0.5 * (rand()/getRandMax())**3;
|
||||
$offset = 100 * (rand()/getRandMax())**2;
|
||||
$scale = max(0.1 * $offset, 100 * rand() / getRandMax());
|
||||
$volatility = 0.25 * (rand()/getRandMax())**3 + 0.25;
|
||||
for ($n = 0, $current = $offset + 0.5 * $scale; $n < $count; $n++) {
|
||||
$current -= $offset;
|
||||
$current *= 1 + $volatility * (rand()/getRandMax() - 0.5);
|
||||
|
@ -32,14 +32,14 @@ function randomData($count = 96) {
|
|||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<h2>SVG chart in <code>img</code> tag</h2>
|
||||
<h2>Chart in <code>img</code> tag</h2>
|
||||
<figure>
|
||||
<img src="./demo-as-image.php">
|
||||
<figcaption>Random generated data, loaded as an image</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
<section>
|
||||
<h2>SVG chart in <code>svg</code> tag</h2>
|
||||
<h2>Chart in <code>svg</code> tag, zero axis shown</h2>
|
||||
<figure>
|
||||
<?php
|
||||
$chart = new NeatCharts\LineChart(randomData(), [
|
||||
|
@ -48,7 +48,8 @@ $chart = new NeatCharts\LineChart(randomData(), [
|
|||
'lineColor'=>'#F00',
|
||||
'labelColor'=>'#222',
|
||||
'smoothed'=>false,
|
||||
'fontSize'=>14
|
||||
'fontSize'=>14,
|
||||
'yAxisZero'=>true
|
||||
]);
|
||||
echo $chart->render();
|
||||
?>
|
||||
|
@ -56,7 +57,7 @@ echo $chart->render();
|
|||
</figure>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Smoothed SVG chart in <code>svg</code> tag</h2>
|
||||
<h2>Smoothed chart in <code>svg</code> tag</h2>
|
||||
<figure>
|
||||
<?php
|
||||
$chart = new NeatCharts\LineChart(randomData(12), [
|
||||
|
@ -73,7 +74,7 @@ echo $chart->render();
|
|||
</figure>
|
||||
</section>
|
||||
<section>
|
||||
<h2>SVG sparkline in <code>svg</code> tag</h2>
|
||||
<h2>Sparkline in <code>svg</code> tag</h2>
|
||||
<figure>
|
||||
<?php
|
||||
$chart = new NeatCharts\LineChart(randomData(48), [
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
<?php
|
||||
namespace NeatCharts {
|
||||
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) {
|
||||
$this->setWindow($chartData, $this->options); // sets min, max, range, etc
|
||||
// we assume $chartData is sorted by key and keys and values are all numeric
|
||||
$previousX = $previousY = null;
|
||||
end($chartData);
|
||||
$this->xMax = key($chartData);
|
||||
reset($chartData);
|
||||
$this->xMin = key($chartData);
|
||||
$this->xRange = $this->xMax - $this->xMin;
|
||||
$count = count($chartData);
|
||||
$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
|
||||
$secants = []; // slope between this point and the previous one
|
||||
$tangents = []; // slope across the point
|
||||
|
||||
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)) {
|
||||
$averageAbsSlope += abs($y - $previousY); // just add up all the Y differences
|
||||
$secants[$previousX] = ($y - $previousY) / $deltaX;
|
||||
|
@ -36,7 +38,6 @@ namespace NeatCharts {
|
|||
$previousY = $y;
|
||||
$previousX = $x;
|
||||
}
|
||||
$this->yRange = $this->yMax - $this->yMin;
|
||||
$averageAbsSlope /= $this->yRange * $deltaX; // turn this absolute-deltas total into a slope
|
||||
|
||||
if ($this->options['smoothed']) {
|
||||
|
@ -140,15 +141,13 @@ namespace NeatCharts {
|
|||
}
|
||||
|
||||
$chartPoints = 'M';
|
||||
$chartSplines = 'M'.
|
||||
$this->transformX($this->xMin).','.
|
||||
$this->transformY($chartData[$this->xMin]);
|
||||
if ($this->options['smoothed']) {
|
||||
$chartPoints .= $this->transformX($this->xMin).','.$this->transformY($chartData[$this->xMin]);
|
||||
foreach ($chartData as $x => $y) {
|
||||
$controlX = $deltaX / 3 / sqrt(1 + $tangents[$x]**2);
|
||||
$controlY = $tangents[$x] * $controlX;
|
||||
if ($x != $this->xMin) {
|
||||
$chartSplines .= ' S'.
|
||||
$chartPoints .= ' S'.
|
||||
$this->transformX($x - $controlX).','.
|
||||
$this->transformY($y - $controlY).' '.
|
||||
$this->transformX($x).','.
|
||||
|
@ -166,31 +165,29 @@ namespace NeatCharts {
|
|||
$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">
|
||||
<defs>
|
||||
<marker id="SVGChart-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'] ).';" />
|
||||
<marker id="neatchart-markerCircle-'.( $chartID ).'" markerWidth="2" markerHeight="2" refX="1" refY="1" markerUnits="strokeWidth">
|
||||
<circle class="neatchart-marker" cx="1" cy="1" r="1" stroke="none" fill="'.( $this->options['markerColor'] ).'" />
|
||||
</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="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>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g class="SVGChart">'.( $this->options['yAxisEnabled'] || $this->options['xAxisEnabled'] ? '
|
||||
<g class="neatchart">'.( $this->options['yAxisEnabled'] || $this->options['xAxisEnabled'] ? '
|
||||
<g class="chart__gridLines"
|
||||
shape-rendering="crispEdges"
|
||||
fill="none"
|
||||
stroke="'.( $this->options['labelColor'] ).'"
|
||||
stroke-opacity="0.75"
|
||||
stroke-width="1"
|
||||
vector-effect="non-scaling-stroke"
|
||||
stroke-dasharray="2, 2"
|
||||
>
|
||||
shape-rendering="crispEdges">
|
||||
<path class="chart__gridLinePaths" d="'.( $gridLines ).'" />
|
||||
</g>
|
||||
<g class="chart__gridLabels"
|
||||
fill="'.( $this->options['labelColor'] ).'"
|
||||
font-family="monospace"
|
||||
font-size="'.( $this->options['fontSize'] ).'px"
|
||||
>
|
||||
font-size="'.( $this->options['fontSize'] ).'px">
|
||||
'.( $gridText ).'
|
||||
</g>' : '').'
|
||||
<g class="chart__plotLine"
|
||||
|
@ -198,10 +195,16 @@ namespace NeatCharts {
|
|||
stroke-width="'.( $this->options['fontSize'] / 3 ).'"
|
||||
stroke-linejoin="round"
|
||||
stroke-linecap="round"
|
||||
stroke="url(#SVGChart-fadeFromNothing-'.( $chartID ).')"
|
||||
marker-end="url(#SVGChart-markerCircle-'.( $chartID ).')"
|
||||
stroke="url(#neatchart-fadeFromNothing-'.( $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>
|
||||
</svg>';
|
||||
|
|
|
@ -10,7 +10,8 @@ namespace NeatCharts {
|
|||
'smoothed' => false,
|
||||
'fontSize' => 15,
|
||||
'yAxisEnabled'=>true,
|
||||
'xAxisEnabled'=>false
|
||||
'xAxisEnabled'=>false,
|
||||
'yAxisZero'=>false
|
||||
];
|
||||
|
||||
protected $width;
|
||||
|
@ -24,11 +25,6 @@ namespace NeatCharts {
|
|||
protected $yRange;
|
||||
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) {
|
||||
$value = number_format($float, max($minPlaces, $places));
|
||||
// add a trailing space if there's no decimal
|
||||
|
@ -57,7 +53,29 @@ namespace NeatCharts {
|
|||
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->setData($chartData);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue