You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
7.3 KiB
206 lines
7.3 KiB
define(["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "dojo/has", "./CartesianBase", "./_PlotEvents", "./common", |
|
"dojox/lang/functional", "dojox/lang/utils", "dojox/gfx/fx"], |
|
function(lang, arr, declare, has, CartesianBase, _PlotEvents, dc, df, du, fx){ |
|
|
|
// Candlesticks are based on the Bars plot type; we expect the following passed |
|
// as values in a series: |
|
// { x?, open, close, high, low } |
|
// if x is not provided, the array index is used. |
|
// failing to provide the OHLC values will throw an error. |
|
return declare("dojox.charting.plot2d.OHLC", [CartesianBase, _PlotEvents], { |
|
// summary: |
|
// A plot that represents typical open/high/low/close (financial reporting, primarily). |
|
// Unlike most charts, the Candlestick expects data points to be represented by |
|
// an object of the form { x?, open, close, high, low, mid? }, where both |
|
// x and mid are optional parameters. If x is not provided, the index of the |
|
// data array is used. |
|
defaultParams: { |
|
gap: 2, // gap between columns in pixels |
|
animate: null // animate chart to place |
|
}, |
|
optionalParams: { |
|
minBarSize: 1, // minimal bar size in pixels |
|
maxBarSize: 1, // maximal bar size in pixels |
|
// theme component |
|
stroke: {}, |
|
outline: {}, |
|
shadow: {}, |
|
fill: {}, |
|
font: "", |
|
fontColor: "" |
|
}, |
|
|
|
constructor: function(chart, kwArgs){ |
|
// summary: |
|
// The constructor for a candlestick chart. |
|
// chart: dojox/charting/Chart |
|
// The chart this plot belongs to. |
|
// kwArgs: dojox.charting.plot2d.__BarCtorArgs? |
|
// An optional keyword arguments object to help define the plot. |
|
this.opt = lang.clone(this.defaultParams); |
|
du.updateWithObject(this.opt, kwArgs); |
|
du.updateWithPattern(this.opt, kwArgs, this.optionalParams); |
|
this.animate = this.opt.animate; |
|
}, |
|
|
|
collectStats: function(series){ |
|
// summary: |
|
// Collect all statistics for drawing this chart. Since the common |
|
// functionality only assumes x and y, OHLC must create it's own |
|
// stats (since data has no y value, but open/close/high/low instead). |
|
// series: dojox/charting/Series[] |
|
// The data series array to be drawn on this plot. |
|
// returns: Object |
|
// Returns an object in the form of { hmin, hmax, vmin, vmax }. |
|
|
|
// we have to roll our own, since we need to use all four passed |
|
// values to figure out our stats, and common only assumes x and y. |
|
var stats = lang.delegate(dc.defaultStats); |
|
for(var i=0; i<series.length; i++){ |
|
var run = series[i]; |
|
if(!run.data.length){ continue; } |
|
var old_vmin = stats.vmin, old_vmax = stats.vmax; |
|
if(!("ymin" in run) || !("ymax" in run)){ |
|
arr.forEach(run.data, function(val, idx){ |
|
if(!this.isNullValue(val)){ |
|
var x = val.x || idx + 1; |
|
stats.hmin = Math.min(stats.hmin, x); |
|
stats.hmax = Math.max(stats.hmax, x); |
|
stats.vmin = Math.min(stats.vmin, val.open, val.close, val.high, val.low); |
|
stats.vmax = Math.max(stats.vmax, val.open, val.close, val.high, val.low); |
|
} |
|
}, this); |
|
} |
|
if("ymin" in run){ stats.vmin = Math.min(old_vmin, run.ymin); } |
|
if("ymax" in run){ stats.vmax = Math.max(old_vmax, run.ymax); } |
|
} |
|
return stats; // Object |
|
}, |
|
|
|
getSeriesStats: function(){ |
|
// summary: |
|
// Calculate the min/max on all attached series in both directions. |
|
// returns: Object |
|
// {hmin, hmax, vmin, vmax} min/max in both directions. |
|
var stats = this.collectStats(this.series); |
|
stats.hmin -= 0.5; |
|
stats.hmax += 0.5; |
|
return stats; // Object |
|
}, |
|
|
|
render: function(dim, offsets){ |
|
// summary: |
|
// Run the calculations for any axes for this plot. |
|
// dim: Object |
|
// An object in the form of { width, height } |
|
// offsets: Object |
|
// An object of the form { l, r, t, b}. |
|
// returns: dojox/charting/plot2d/OHLC |
|
// A reference to this plot for functional chaining. |
|
if(this.zoom && !this.isDataDirty()){ |
|
return this.performZoom(dim, offsets); |
|
} |
|
this.resetEvents(); |
|
this.dirty = this.isDirty(); |
|
if(this.dirty){ |
|
arr.forEach(this.series, dc.purgeGroup); |
|
this._eventSeries = {}; |
|
this.cleanGroup(); |
|
var s = this.getGroup(); |
|
df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); |
|
} |
|
var t = this.chart.theme, f, gap, width, |
|
ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), |
|
vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), |
|
events = this.events(); |
|
f = dc.calculateBarSize(this._hScaler.bounds.scale, this.opt); |
|
gap = f.gap; |
|
width = f.size; |
|
for(var i = 0; i < this.series.length; i++){ |
|
var run = this.series[i]; |
|
if(!this.dirty && !run.dirty){ |
|
t.skip(); |
|
this._reconnectEvents(run.name); |
|
continue; |
|
} |
|
run.cleanGroup(); |
|
var theme = t.next("candlestick", [this.opt, run]), s = run.group, |
|
eventSeries = new Array(run.data.length); |
|
for(var j = 0; j < run.data.length; ++j){ |
|
var v = run.data[j]; |
|
if(!this.isNullValue(v)){ |
|
var finalTheme = t.addMixin(theme, "candlestick", v, true); |
|
|
|
// calculate the points we need for OHLC |
|
var x = ht(v.x || (j+0.5)) + offsets.l + gap, |
|
y = dim.height - offsets.b, |
|
open = vt(v.open), |
|
close = vt(v.close), |
|
high = vt(v.high), |
|
low = vt(v.low); |
|
if(low > high){ |
|
var tmp = high; |
|
high = low; |
|
low = tmp; |
|
} |
|
|
|
if(width >= 1){ |
|
var hl = {x1: width/2, x2: width/2, y1: y - high, y2: y - low}, |
|
op = {x1: 0, x2: ((width/2) + ((finalTheme.series.stroke ? finalTheme.series.stroke.width || 1 : 1)/2)), y1: y-open, y2: y-open}, |
|
cl = {x1: ((width/2) - ((finalTheme.series.stroke ? finalTheme.series.stroke.width || 1 : 1)/2)), x2: width, y1: y-close, y2: y-close}; |
|
var shape = s.createGroup(); |
|
shape.setTransform({dx: x, dy: 0}); |
|
var inner = shape.createGroup(); |
|
inner.createLine(hl).setStroke(finalTheme.series.stroke); |
|
inner.createLine(op).setStroke(finalTheme.series.stroke); |
|
inner.createLine(cl).setStroke(finalTheme.series.stroke); |
|
|
|
// TODO: double check this. |
|
run.dyn.stroke = finalTheme.series.stroke; |
|
if(events){ |
|
var o = { |
|
element: "candlestick", |
|
index: j, |
|
run: run, |
|
shape: inner, |
|
x: x, |
|
y: y-Math.max(open, close), |
|
cx: width/2, |
|
cy: (y-Math.max(open, close)) + (Math.max(open > close ? open-close : close-open, 1)/2), |
|
width: width, |
|
height: Math.max(open > close ? open-close : close-open, 1), |
|
data: v |
|
}; |
|
this._connectEvents(o); |
|
eventSeries[j] = o; |
|
} |
|
} |
|
if(this.animate){ |
|
this._animateOHLC(shape, y - low, high - low); |
|
} |
|
} |
|
} |
|
this._eventSeries[run.name] = eventSeries; |
|
run.dirty = false; |
|
} |
|
this.dirty = false; |
|
// chart mirroring starts |
|
if(has("dojo-bidi")){ |
|
this._checkOrientation(this.group, dim, offsets); |
|
} |
|
// chart mirroring ends |
|
return this; // dojox/charting/plot2d/OHLC |
|
}, |
|
_animateOHLC: function(shape, voffset, vsize){ |
|
fx.animateTransform(lang.delegate({ |
|
shape: shape, |
|
duration: 1200, |
|
transform: [ |
|
{name: "translate", start: [0, voffset - (voffset/vsize)], end: [0, 0]}, |
|
{name: "scale", start: [1, 1/vsize], end: [1, 1]}, |
|
{name: "original"} |
|
] |
|
}, this.animate)).play(); |
|
} |
|
}); |
|
});
|
|
|