$thisY) {
if (!is_null($previousValue)) {
$absoluteDeltas[] = abs($thisY - $previousValue);
}
if ($thisY < $yMin) {
$yMin = $thisY;
$yMinX = $thisX;
}
if ($thisY > $yMax) {
$yMax = $thisY;
$yMaxX = $thisX;
}
$previousValue = $thisY;
}
$yRange = $yMax - $yMin;
end($chartData);
$xMax = key($chartData);
reset($chartData);
$xMin = key($chartData);
$xRange = $xMax - $xMin;
$count = count($chartData);
$averageDelta = abs(array_sum($absoluteDeltas)/$count);
/*
We want the height of the median y-delta to be the same as
the width of one x-delta, which puts the median slope at
45 degrees. This improves comprehension.
http://vis4.net/blog/posts/doing-the-line-charts-right/
*/
$aspectRatio = max(0.25, $yRange / $averageDelta / $count);
$height = floor($aspectRatio * $width);
function labelFormat($float, $sigFigs, $minPlaces = 0) {
return number_format($float, max($minPlaces, $sigFigs));
}
/* Transform data coords to chart coords */
function transformX($x, $xMin, $xRange, $width) {
return round(
($x - $xMin) / $xRange * $width
, 2);
}
function transformY($y, $yMax, $yRange, $height) {
return round(
// SVG has y axis reversed, 0 is at the top
($yMax - $y) / $yRange * $height
, 2);
}
function getPrecision($value) { // thanks http://stackoverflow.com/a/21788335/5402566
if (!is_numeric($value)) { return false; }
$decimal = $value - floor($value); //get the decimal portion of the number
if ($decimal == 0) { return 0; } //if it's a whole number
$precision = strlen(trim(number_format($decimal,10),"0")) - 1; //-2 to account for "0."
return $precision;
}
$chartPoints = "M";
foreach ($chartData as $x => $y) {
$chartPoints .= transformX($x, $xMin, $xRange, $width) . ',' . transformY($y, $yMax, $yRange, $height) . '
';
}
$numLabels = min(4, ceil($height / 40));
$labelInterval = $yRange / $numLabels;
$labelModulation = 10 ** (1 + floor(-log($yRange / $numLabels, 10)));
// if (fmod($labelInterval, $labelModulation / 5) < $labelInterval * 0.5) {
$labelModulation /= 2.5;
// } else if (fmod($labelInterval, $labelModulation / 2) < $labelInterval * 0.25) {
// $labelModulation /= 2;
// }
// var_dump($labelInterval, $labelModulation, $labelInterval * $labelModulation, ceil($labelInterval * $labelModulation) / $labelModulation);
$labelInterval = ceil($labelInterval * $labelModulation) / $labelModulation;
$labelPlaces = getPrecision($labelInterval);
// Top and bottom grid lines
$gridLines =
"M10,0 ".$width.",0
M10,".$height.",".$width.",".$height."
";
// Top and bottom grid labels
$gridText =
''.labelFormat($yMax, $labelPlaces + 1).'' .
''.labelFormat($yMin, $labelPlaces + 1).'';
// Start at the first "nice" Y value > min + 50% of the interval
// Keep going until max - 50% of the interval
// Add Interval each iteration
for (
$labelY = $yMin - fmod($yMin, $labelInterval) + $labelInterval;
$labelY < $yMax;
$labelY += $labelInterval
) {
$labelHeight = transformY($labelY, $yMax, $yRange, $height);
if (
$labelY < $yMax - 0.1 * $labelInterval &&
$labelY > $yMin + 0.1 * $labelInterval
) {
$gridLines .= " M0,".$labelHeight." ".$width.",".$labelHeight;
}
if (
$labelY < $yMax - 0.3 * $labelInterval &&
$labelY > $yMin + 0.3 * $labelInterval
) {
$gridText .= ''.labelFormat($labelY, $labelPlaces).'';
}
}
return '
';
}
}