From 481dd6747b4ec8278b474190b71985aab3c72749 Mon Sep 17 00:00:00 2001 From: Joshua Seigler Date: Tue, 28 Jun 2016 21:23:56 -0400 Subject: [PATCH] horrible blob checkin I changed to an options object, and found a cleaner way to merge defaults and passed options. I renamed and reorganized the demo files. --- SVGChartBuilder.php | 305 +++++++++++++++++++++++------------------- Util.php | 21 --- demo.php | 93 +++++++++++++ index.php | 34 ----- poloniex-dash-btc.php | 53 ++++++++ test-stockMarket.php | 39 ------ 6 files changed, 317 insertions(+), 228 deletions(-) delete mode 100644 Util.php create mode 100644 demo.php delete mode 100644 index.php create mode 100644 poloniex-dash-btc.php delete mode 100644 test-stockMarket.php diff --git a/SVGChartBuilder.php b/SVGChartBuilder.php index d2bf19c..a9a3205 100644 --- a/SVGChartBuilder.php +++ b/SVGChartBuilder.php @@ -1,56 +1,109 @@ 800, + 'height' => 250, + 'lineColor' => '#000', + 'labelColor' => '#000', + 'smoothed' => false, + 'fontSize' => 15 + ]; - public static function renderStockChart($chartData, $options) { - function arrayGet($array, $key, $default = NULL) - { - return isset($array[$key]) ? $array[$key] : $default; - } + private $width; + private $height; + private $output; + private $xMin; + private $xMax; + private $xRange; + private $yMin; + private $yMax; + private $yRange; + private $padding = ['top'=>10, 'right'=>10, 'bottom'=>10, 'left'=>10]; - $width = arrayGet($options, "width", 800) - 100; - $height = arrayGet($options, "height"); - $lineColor = arrayGet($options, "lineColor", "#FF2F00"); - $labelColor = arrayGet($options, "labelColor", "#999"); - $smoothed = arrayGet($options, "smoothed", false); + private function arrayGet($array, $key, $default = NULL) + { + return isset($array[$key]) ? $array[$key] : $default; + } + + private function labelFormat($float, $places, $minPlaces = 0) { + $value = number_format($float, max($minPlaces, $places)); + // add a trailing space if there's no decimal + return (strpos($value, '.') === false ? $value . '.' : $value); + } + + /* Transform data coords to chart coords */ + /* Transform data coords to chart coords */ + private function transformX($x) { + return round( + ($x - $this->xMin) / $this->xRange * $this->width + , 2); + } + private function transformY($y) { + return round( + // SVG has y axis reversed, 0 is at the top + ($this->yMax - $y) / $this->yRange * $this->height + , 2); + } + + private 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; + } + + public function __construct($chartData, $options) { + $this->setOptions($options); + $this->setData($chartData); + } + + public function setOptions($options) { + $this->options = array_replace($this->options, $options); + $this->padding['left'] = $this->options['fontSize'] * 5; + $this->padding['top'] = $this->padding['bottom'] = $this->options['fontSize']; + } + + public function setData($chartData) { // we assume $chartData is sorted by key and keys and values are all numeric - $previousY = $previousY = null; + $previousX = $previousY = null; end($chartData); - $xMax = key($chartData); + $this->xMax = key($chartData); reset($chartData); - $xMin = key($chartData); - $xRange = $xMax - $xMin; + $this->xMin = key($chartData); + $this->xRange = $this->xMax - $this->xMin; $count = count($chartData); - $deltaX = $xRange / $count; - $yMin = INF; // so the first comparison sets this to an actual value - $yMax = -INF; + $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 < $yMin) { - $yMin = $y; + if ($y < $this->yMin) { + $this->yMin = $y; $yMinX = $x; } - if ($y > $yMax) { - $yMax = $y; + 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; } - if ($x == $xMax) { + if ($x == $this->xMax) { $secants[$x] = ($y - $previousY) / $deltaX; } $previousY = $y; $previousX = $x; } - $yRange = $yMax - $yMin; - $averageAbsSlope /= $yRange * $deltaX; // turn this absolute-deltas total into a slope + $this->yRange = $this->yMax - $this->yMin; + $averageAbsSlope /= $this->yRange * $deltaX; // turn this absolute-deltas total into a slope - if ($smoothed) { + if ($this->options['smoothed']) { // take all these slopes and average them with their neighbors // unless they change direction, then make them zero // also restrict them a bit when they are very different @@ -69,10 +122,10 @@ class SVGChartBuilder { } } } - if ($x == $xMax) { + if ($x == $this->xMax) { $tangents[$x] = $secant; } - if ($x == $xMin) { + if ($x == $this->xMin) { $tangents[$x] = $secant; } @@ -88,60 +141,41 @@ class SVGChartBuilder { http://vis4.net/blog/posts/doing-the-line-charts-right/ */ $aspectRatio = max(0.25, min(0.75, 1 / $averageAbsSlope)); - $height = (is_null($height) ? floor($aspectRatio * $width) : $height); - - function labelFormat($float, $places, $minPlaces = 0) { - $value = number_format($float, max($minPlaces, $places)); - // add a trailing space if there's no decimal - return (strpos($value, ".") === false ? $value . "." : $value); + $this->width = $this->options['width'] - $this->padding['left'] - $this->padding['right']; + if (isset($this->options['height'])) { + $this->height = $this->options['height'] - $this->padding['top'] - $this->padding['bottom']; + } else { + $this->height = floor($aspectRatio * $this->width); + $this->options['height'] = $this->height + $this->padding['top'] + $this->padding['bottom']; } - /* 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"; - $chartSplines = "M". - transformX($xMin, $xMin, $xRange, $width).",". - transformY($chartData[$xMin], $yMax, $yRange, $height); - foreach ($chartData as $x => $y) { - $chartPoints .= - transformX($x, $xMin, $xRange, $width).",". - transformY($y, $yMax, $yRange, $height) . "\n"; - - if ($smoothed) { + $chartPoints = 'M'; + $chartSplines = 'M'. + $this->transformX($this->xMin).','. + $this->transformY($chartData[$this->xMin]); + if ($this->options['smoothed']) { + foreach ($chartData as $x => $y) { $controlX = $deltaX / 3 / sqrt(1 + $tangents[$x]**2); $controlY = $tangents[$x] * $controlX; - if ($x != $xMin) { - $chartSplines .= " S". - transformX($x - $controlX, $xMin, $xRange, $width).",". - transformY($y - $controlY, $yMax, $yRange, $height)." ". - transformX($x, $xMin, $xRange, $width).",". - transformY($y, $yMax, $yRange, $height); + if ($x != $this->xMin) { + $chartSplines .= ' S'. + $this->transformX($x - $controlX).','. + $this->transformY($y - $controlY).' '. + $this->transformX($x).','. + $this->transformY($y); } } + } else { + foreach ($chartData as $x => $y) { + $chartPoints .= + $this->transformX($x).','. + $this->transformY($y) . ' '; + } } - $numLabels = 2 + ceil($height / 60); - $labelInterval = $yRange / $numLabels; - $labelModulation = 10 ** (1 + floor(-log($yRange / $numLabels, 10))); + $numLabels = 2 + ceil($this->height / $this->options['fontSize'] / 6); + $labelInterval = $this->yRange / $numLabels; + $labelModulation = 10 ** (1 + floor(-log($this->yRange / $numLabels, 10))); // 0.1 here is a fudge factor so we get multiples of 2.5 a little more often if (fmod($labelInterval * $labelModulation, 2.5) < fmod($labelInterval * $labelModulation, 2) + 0.1) { @@ -150,90 +184,93 @@ class SVGChartBuilder { $labelModulation /= 2; } $labelInterval = ceil($labelInterval * $labelModulation) / $labelModulation; - $labelPrecision = getPrecision($labelInterval); + $labelPrecision = $this->getPrecision($labelInterval); // Top and bottom grid lines $gridLines = - "M10,0 ".$width.",0\n". - "M10,".$height.",".$width.",".$height."\n"; + 'M10,0 '.$this->width.',0 '. + ' M10,'.$this->height.','.$this->width.','.$this->height; // Top and bottom grid labels $gridText = - ''.labelFormat($yMax, $labelPrecision + 1).'' . - ''.labelFormat($yMin, $labelPrecision + 1).''; + ''.($this->labelFormat($this->yMax, $labelPrecision + 1)).'' . + ''.($this->labelFormat($this->yMin, $labelPrecision + 1)).''; // Main labels and grid lines for ( - $labelY = $yMin - fmod($yMin, $labelInterval) + $labelInterval; // Start at the first "nice" Y value > min - $labelY < $yMax; // Keep going until max + $labelY = $this->yMin - fmod($this->yMin, $labelInterval) + $labelInterval; // Start at the first "nice" Y value > min + $labelY < $this->yMax; // Keep going until max $labelY += $labelInterval // Add Interval each iteration ) { - $labelHeight = transformY($labelY, $yMax, $yRange, $height); + $labelHeight = $this->transformY($labelY); if ( // label is not too close to the min or max - $labelHeight < $height - 25 && - $labelHeight > 25 + $labelHeight < $this->height - 2.5 * $this->options['fontSize'] && + $labelHeight > $this->options['fontSize'] * 2.5 ) { - $gridText .= ''.labelFormat($labelY, $labelPrecision).''; - $gridLines .= " M0,".$labelHeight." ".$width.",".$labelHeight; + $gridText .= ''.$this->labelFormat($labelY, $labelPrecision).''; + $gridLines .= ' M0,'.$labelHeight.' '.$this->width.','.$labelHeight; } else if ( // label is too close - $labelHeight < $height - 4 && - $labelHeight > 4 + $labelHeight < $this->height - $this->options['fontSize'] / 2 && + $labelHeight > $this->options['fontSize'] / 2 ) { - $gridLines .= " M".( // move grid line over when it's very close to the min or max label - $labelHeight < $height - 10 && $labelHeight > 10 ? 0 : 10 - ).",".$labelHeight." ".$width.",".$labelHeight; + $gridLines .= ' M'.( // move grid line over when it's very close to the min or max label + $labelHeight < $this->height - $this->options['fontSize'] / 2 && $labelHeight > $this->options['fontSize'] / 2 ? 0 : $this->options['fontSize'] / 2 + ).','.$labelHeight.' '.$this->width.','.$labelHeight; } } - print ' - + $chartID = rand(); + $this->output = ' + - - + + - - - - + + + + - + - - - '.( $gridText ).' - - - + + + '.( $gridText ).' + + + + '; } + public function render() { + return $this->output; + } } diff --git a/Util.php b/Util.php deleted file mode 100644 index eb55c1b..0000000 --- a/Util.php +++ /dev/null @@ -1,21 +0,0 @@ - + + + + + SVGChartBuilder demo + + + + +
+
+

Poloniex Dash/BTC Price

+ Poloniex Dash/BTC price +
+ +
+

Fake Stock Market Data

+500, + "height"=>150, + "fontSize"=>10 +]); +print $stockChart->render(); +?> +
+ +
+

Monotonically Smoothed Chart

+700, + "height"=>400, + "lineColor"=>"#D00", + "labelColor"=>"#777", + "smoothed"=>true +]); +print $tempChart->render(); +?> +
+
+ + + diff --git a/index.php b/index.php deleted file mode 100644 index 296606f..0000000 --- a/index.php +++ /dev/null @@ -1,34 +0,0 @@ -date] = $item->weightedAverage; -} - -print SVGChartBuilder::renderStockChart($chartData, [ - 'width'=>800, - 'height'=>250, - 'lineColor'=>"#1C75BC", - 'labelColor'=>"#777", - 'smoothed'=>false -]); diff --git a/poloniex-dash-btc.php b/poloniex-dash-btc.php new file mode 100644 index 0000000..6ba1e88 --- /dev/null +++ b/poloniex-dash-btc.php @@ -0,0 +1,53 @@ +date] = $item->weightedAverage; +} + +$poloniexChart = new SVGChartBuilder($chartData, [ + 'width'=>800, + 'height'=>250, + 'lineColor'=>"#1C75BC", // Dash blue + 'labelColor'=>"#777", + 'smoothed'=>false +]); +print $poloniexChart->render(); diff --git a/test-stockMarket.php b/test-stockMarket.php deleted file mode 100644 index 2a44f81..0000000 --- a/test-stockMarket.php +++ /dev/null @@ -1,39 +0,0 @@ -1000, - 'height'=>250, - 'lineColor'=>"#708", - 'labelColor'=>"#777", - 'smoothed'=>false -]);