From c98dc22241892e3553769cf1308886d05d56b553 Mon Sep 17 00:00:00 2001 From: Zhenlei Cai Date: Mon, 26 Mar 2012 18:33:01 -0700 Subject: [PATCH 1/3] Add candle chart and update gallery --- dygraph-candlechart.js | 65 ++++++++++++++++++++++++++++++++++++ dygraph-canvas.js | 3 ++ dygraph-dev.js | 1 + dygraph-options-reference.js | 6 ++++ dygraph.js | 12 +++++-- gallery/candle-chart.js | 18 ++++++++++ gallery/data.js | 50 +++++++++++++++++++++++++++ gallery/index.html | 1 + 8 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 dygraph-candlechart.js create mode 100644 gallery/candle-chart.js diff --git a/dygraph-candlechart.js b/dygraph-candlechart.js new file mode 100644 index 000000000..802765810 --- /dev/null +++ b/dygraph-candlechart.js @@ -0,0 +1,65 @@ +var BAR_WIDTH = 8; +function DygraphCandleChartRenderer(dygraph, element, elementContext, layout) { + DygraphCanvasRenderer.call(this,dygraph, element, elementContext, layout); +} + +DygraphCandleChartRenderer.prototype = new DygraphCanvasRenderer(); + +DygraphCandleChartRenderer.prototype.constructor = DygraphCandleChartRenderer ; + +DygraphCandleChartRenderer.prototype._setColors = function(colors) { +}; + + +DygraphCandleChartRenderer.prototype._renderLineChart=function(){ + var ctx = this.elementContext; + var points = this.layout.points; + var pointsLength = points.length; + + var setCount = this.layout.setNames.length; + if (setCount != 4 || pointsLength % 4 !== 0) + throw "Exactly 4 prices each point must be provided for candle chart (open close high low)"; + + var prices = []; + var price; + var num_candles = pointsLength / 4; + for (var p = 0 ; p < num_candles; p++) { + price = { open : points[p].yval, close : points[p + num_candles].yval, + high : points[p + num_candles * 2].yval, low : points[p + num_candles * 3].yval, + openY : points[p].y, closeY : points[p + num_candles].y, + highY : points[p + num_candles * 2].y, lowY : points[p + num_candles * 3].y + }; + prices.push(price); + } + + ctx.save(); + ctx.strokeStyle = '#202020'; + ctx.lineWidth = 0.6; + + for (p = 0 ; p < num_candles; p++) { + ctx.beginPath(); + ctx.strokeStyle = '#202020'; + + price = prices[p]; + var topY = this.area.h * price.highY + this.area.y; + var bottomY = this.area.h * price.lowY + this.area.y; + var centerX = this.area.x + points[p].x * this.area.w; + ctx.moveTo(centerX, topY); + ctx.lineTo(centerX, bottomY); + ctx.closePath(); + ctx.stroke(); + var bodyY; + if (price.open > price.close) { + ctx.fillStyle ='rgba(244,44,44,1.0)'; + bodyY = this.area.h * price.openY + this.area.y; + } + else { + ctx.fillStyle ='rgba(44,244,44,1.0)'; + bodyY = this.area.h * price.closeY + this.area.y; + } + var bodyHeight = this.area.h * Math.abs(price.openY - price.closeY); + ctx.fillRect(centerX - BAR_WIDTH / 2, bodyY, BAR_WIDTH, bodyHeight); + } + ctx.restore(); +}; + diff --git a/dygraph-canvas.js b/dygraph-canvas.js index 3c1d40c0f..213a5a3e6 100644 --- a/dygraph-canvas.js +++ b/dygraph-canvas.js @@ -30,6 +30,9 @@ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) { + if (dygraph == undefined) + return; + this.dygraph_ = dygraph; this.layout = layout; diff --git a/dygraph-dev.js b/dygraph-dev.js index f17ee2b97..f666f8bad 100644 --- a/dygraph-dev.js +++ b/dygraph-dev.js @@ -21,6 +21,7 @@ "stacktrace.js", "dygraph-layout.js", "dygraph-canvas.js", + "dygraph-candlechart.js", "dygraph.js", "dygraph-utils.js", "dygraph-gviz.js", diff --git a/dygraph-options-reference.js b/dygraph-options-reference.js index 0e6b98e09..3a5537175 100644 --- a/dygraph-options-reference.js +++ b/dygraph-options-reference.js @@ -550,6 +550,12 @@ Dygraph.OPTIONS_REFERENCE = // "type": "boolean", "description": "When set, parse each CSV cell as \"low;middle;high\". Error bars will be drawn for each point between low and high, with the series itself going through middle." }, + "renderer": { + "default": "line", + "labels": ["Data Line display"], + "type": "string", + "description": "How to render the graph (as line or candle etc)." + }, "colorValue": { "default": "1.0", "labels": ["Data Series Colors"], diff --git a/dygraph.js b/dygraph.js index c07fe3594..dba6d8a73 100644 --- a/dygraph.js +++ b/dygraph.js @@ -223,6 +223,7 @@ Dygraph.DEFAULT_ATTRS = { fractions: false, wilsonInterval: true, // only relevant if fractions is true customBars: false, + renderer : "line", fillGraph: false, fillAlpha: 0.15, connectSeparatedPoints: false, @@ -2183,11 +2184,18 @@ Dygraph.prototype.predraw_ = function() { // Create a new plotter. if (this.plotter_) this.plotter_.clear(); - this.plotter_ = new DygraphCanvasRenderer(this, + var renderType = this.attr_("renderer"); + if (renderType == 'line') { + this.plotter_ = new DygraphCanvasRenderer(this, this.hidden_, this.hidden_ctx_, this.layout_); - + } else if (renderType == 'candle') { + this.plotter_ = new DygraphCandleChartRenderer(this, + this.hidden_, + this.hidden_ctx_, + this.layout_); + } // The roller sits in the bottom left corner of the chart. We don't know where // this will be until the options are available, so it's positioned here. this.createRollInterface_(); diff --git a/gallery/candle-chart.js b/gallery/candle-chart.js new file mode 100644 index 000000000..f12f0d860 --- /dev/null +++ b/gallery/candle-chart.js @@ -0,0 +1,18 @@ +Gallery.register( + 'candle', + { + name: 'Candle Chart Demo', + title: 'Candle Chart Demo', + setup: function(parent) { + parent.innerHTML = [ + "

", + "
", + "
"].join("\n"); + }, + run: function() { + var g = new Dygraph(document.getElementById("stock_div"), candleData, + { + renderer : "candle" + }); + } + }); \ No newline at end of file diff --git a/gallery/data.js b/gallery/data.js index 52105e401..d28c7f84d 100644 --- a/gallery/data.js +++ b/gallery/data.js @@ -2683,3 +2683,53 @@ var stockData = function() { "2009-09-15,9280.67;9712.28;9829.87,4297.2232125907;4497.07133894216;4551.51896800004\n" + "2009-10-15,9487.67;9712.73;10092.2,4388.84340147194;4492.9525342659;4668.48924723722\n"; } + +var candleData = "Date,Open,Close,High,Low\n" + + "2011-12-06,392.54,390.95,394.63,389.38\n" + + "2011-12-07,389.93,389.09,390.94,386.76\n" + + "2011-12-08,391.45,390.66,395.50,390.23\n" + + "2011-12-09,392.85,393.62,394.04,391.03\n" + + "2011-12-12,391.68,391.84,393.90,389.45\n" + + "2011-12-13,393.00,388.81,395.40,387.10\n" + + "2011-12-14,386.70,380.19,387.38,377.68\n" + + "2011-12-15,383.33,378.94,383.74,378.31\n" + + "2011-12-16,380.36,381.02,384.15,379.57\n" + + "2011-12-19,382.47,382.21,384.85,380.48\n" + + "2011-12-20,387.76,395.95,396.10,387.26\n" + + "2011-12-21,396.69,396.45,397.30,392.01\n" + + "2011-12-22,397.00,398.55,399.13,396.10\n" + + "2011-12-23,399.69,403.33,403.59,399.49\n" + + "2011-12-27,403.10,406.53,409.09,403.02\n" + + "2011-12-28,406.89,402.64,408.25,401.34\n" + + "2011-12-29,403.40,405.12,405.65,400.51\n" + + "2011-12-30,403.51,405.00,406.28,403.49\n" + + "2012-01-03,409.50,411.23,412.50,409.00\n" + + "2012-01-04,410.21,413.44,414.68,409.28\n" + + "2012-01-05,414.95,418.03,418.55,412.67\n" + + "2012-01-06,419.77,422.40,422.75,419.22\n" + + "2012-01-09,425.52,421.73,427.75,421.35\n" + + "2012-01-10,425.91,423.24,426.00,421.50\n" + + "2012-01-11,422.59,422.55,422.85,419.31\n" + + "2012-01-12,422.41,421.39,422.90,418.75\n" + + "2012-01-13,419.53,419.81,420.45,418.66\n" + + "2012-01-17,424.20,424.70,425.99,422.96\n" + + "2012-01-18,426.87,429.11,429.47,426.30\n" + + "2012-01-19,430.03,427.75,431.37,426.51\n" + + "2012-01-20,427.49,420.30,427.50,419.75\n" + + "2012-01-23,422.67,427.41,428.45,422.30\n" + + "2012-01-24,425.10,420.41,425.10,419.55\n" + + "2012-01-25,454.26,446.66,454.45,443.73\n" + + "2012-01-26,448.45,444.63,448.79,443.14\n" + + "2012-01-27,444.37,447.28,448.48,443.77\n" + + "2012-01-30,445.71,453.01,453.90,445.39\n" + + "2012-01-31,455.85,456.48,458.24,453.07\n" + + "2012-02-01,458.49,456.19,458.99,455.55\n" + + "2012-02-02,455.90,455.12,457.17,453.98\n" + + "2012-02-03,457.30,459.68,460.00,455.56\n" + + "2012-02-06,458.38,463.97,464.98,458.20\n" + + "2012-02-07,465.25,468.83,469.75,464.58\n" + + "2012-02-08,470.50,476.68,476.79,469.70\n" + + "2012-02-09,480.95,493.17,496.75,480.56\n" + + "2012-02-10,491.17,493.42,497.62,488.55\n" + + "2012-02-13,499.74,502.60,503.83,497.09\n" + + "2012-02-14,504.70,509.46,509.56,502.00\n" ; diff --git a/gallery/index.html b/gallery/index.html index ca10598bc..e8d1317c6 100644 --- a/gallery/index.html +++ b/gallery/index.html @@ -28,6 +28,7 @@ + From 6923af0a0a13b4d0e5f435d4d4e86bd0f9104e8c Mon Sep 17 00:00:00 2001 From: Zhenlei Cai Date: Wed, 28 Mar 2012 15:37:58 -0700 Subject: [PATCH 2/3] Add back to-be-refactored code so the highlight (mousemove) function still works --- dygraph-candlechart.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/dygraph-candlechart.js b/dygraph-candlechart.js index 802765810..498569e7f 100644 --- a/dygraph-candlechart.js +++ b/dygraph-candlechart.js @@ -4,22 +4,32 @@ function DygraphCandleChartRenderer(dygraph, element, elementContext, layout) { } DygraphCandleChartRenderer.prototype = new DygraphCanvasRenderer(); - DygraphCandleChartRenderer.prototype.constructor = DygraphCandleChartRenderer ; -DygraphCandleChartRenderer.prototype._setColors = function(colors) { -}; - - DygraphCandleChartRenderer.prototype._renderLineChart=function(){ var ctx = this.elementContext; var points = this.layout.points; var pointsLength = points.length; + var i, point; + + // Update Points + // TODO(danvk): here + for (i = pointsLength; i--;) { + point = points[i]; + point.canvasx = this.area.w * point.x + this.area.x; + point.canvasy = this.area.h * point.y + this.area.y; + } var setCount = this.layout.setNames.length; if (setCount != 4 || pointsLength % 4 !== 0) throw "Exactly 4 prices each point must be provided for candle chart (open close high low)"; + // TODO(danvk): Move this mapping into Dygraph and get it out of here. + this.colors = {}; + for (i = 0; i < setCount; i++) { + this.colors[this.layout.setNames[i]] = this.colorScheme_[i % this.colorScheme_.length]; + } + var prices = []; var price; var num_candles = pointsLength / 4; From 5d3e20d63365a47bfd5030418dfe4480901e1d32 Mon Sep 17 00:00:00 2001 From: Zhenlei Cai Date: Wed, 28 Mar 2012 20:37:45 -0700 Subject: [PATCH 3/3] add license text --- dygraph-candlechart.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/dygraph-candlechart.js b/dygraph-candlechart.js index 498569e7f..dd002fda4 100644 --- a/dygraph-candlechart.js +++ b/dygraph-candlechart.js @@ -1,3 +1,10 @@ +/** + * @license + * Copyright 2012 Zhenlei Cai (jpenguin@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + + var BAR_WIDTH = 8; function DygraphCandleChartRenderer(dygraph, element, elementContext, layout) { DygraphCanvasRenderer.call(this,dygraph, element, elementContext, layout); @@ -32,12 +39,12 @@ DygraphCandleChartRenderer.prototype._renderLineChart=function(){ var prices = []; var price; - var num_candles = pointsLength / 4; - for (var p = 0 ; p < num_candles; p++) { - price = { open : points[p].yval, close : points[p + num_candles].yval, - high : points[p + num_candles * 2].yval, low : points[p + num_candles * 3].yval, - openY : points[p].y, closeY : points[p + num_candles].y, - highY : points[p + num_candles * 2].y, lowY : points[p + num_candles * 3].y + var numCandles = pointsLength / 4; + for (var p = 0 ; p < numCandles; p++) { + price = { open : points[p].yval, close : points[p + numCandles].yval, + high : points[p + numCandles * 2].yval, low : points[p + numCandles * 3].yval, + openY : points[p].y, closeY : points[p + numCandles].y, + highY : points[p + numCandles * 2].y, lowY : points[p + numCandles * 3].y }; prices.push(price); } @@ -46,7 +53,7 @@ DygraphCandleChartRenderer.prototype._renderLineChart=function(){ ctx.strokeStyle = '#202020'; ctx.lineWidth = 0.6; - for (p = 0 ; p < num_candles; p++) { + for (p = 0 ; p < numCandles; p++) { ctx.beginPath(); ctx.strokeStyle = '#202020';