1 | define(["dojo/_base/lang", "dojo/_base/array", "dojo/_base/declare", "./Base", "./common", |
---|
2 | "dojox/lang/functional", "dojox/lang/functional/reversed", "dojox/lang/utils", "dojox/gfx/fx"], |
---|
3 | function(lang, arr, declare, Base, dc, df, dfr, du, fx){ |
---|
4 | /*===== |
---|
5 | var Base = dojox.charting.plot2d.Base; |
---|
6 | =====*/ |
---|
7 | |
---|
8 | var purgeGroup = dfr.lambda("item.purgeGroup()"); |
---|
9 | |
---|
10 | // Candlesticks are based on the Bars plot type; we expect the following passed |
---|
11 | // as values in a series: |
---|
12 | // { x?, open, close, high, low } |
---|
13 | // if x is not provided, the array index is used. |
---|
14 | // failing to provide the OHLC values will throw an error. |
---|
15 | return declare("dojox.charting.plot2d.OHLC", Base, { |
---|
16 | // summary: |
---|
17 | // A plot that represents typical open/high/low/close (financial reporting, primarily). |
---|
18 | // Unlike most charts, the Candlestick expects data points to be represented by |
---|
19 | // an object of the form { x?, open, close, high, low, mid? }, where both |
---|
20 | // x and mid are optional parameters. If x is not provided, the index of the |
---|
21 | // data array is used. |
---|
22 | defaultParams: { |
---|
23 | hAxis: "x", // use a horizontal axis named "x" |
---|
24 | vAxis: "y", // use a vertical axis named "y" |
---|
25 | gap: 2, // gap between columns in pixels |
---|
26 | animate: null // animate chart to place |
---|
27 | }, |
---|
28 | optionalParams: { |
---|
29 | minBarSize: 1, // minimal bar size in pixels |
---|
30 | maxBarSize: 1, // maximal bar size in pixels |
---|
31 | // theme component |
---|
32 | stroke: {}, |
---|
33 | outline: {}, |
---|
34 | shadow: {}, |
---|
35 | fill: {}, |
---|
36 | font: "", |
---|
37 | fontColor: "" |
---|
38 | }, |
---|
39 | |
---|
40 | constructor: function(chart, kwArgs){ |
---|
41 | // summary: |
---|
42 | // The constructor for a candlestick chart. |
---|
43 | // chart: dojox.charting.Chart |
---|
44 | // The chart this plot belongs to. |
---|
45 | // kwArgs: dojox.charting.plot2d.__BarCtorArgs? |
---|
46 | // An optional keyword arguments object to help define the plot. |
---|
47 | this.opt = lang.clone(this.defaultParams); |
---|
48 | du.updateWithObject(this.opt, kwArgs); |
---|
49 | du.updateWithPattern(this.opt, kwArgs, this.optionalParams); |
---|
50 | this.series = []; |
---|
51 | this.hAxis = this.opt.hAxis; |
---|
52 | this.vAxis = this.opt.vAxis; |
---|
53 | this.animate = this.opt.animate; |
---|
54 | }, |
---|
55 | |
---|
56 | collectStats: function(series){ |
---|
57 | // summary: |
---|
58 | // Collect all statistics for drawing this chart. Since the common |
---|
59 | // functionality only assumes x and y, OHLC must create it's own |
---|
60 | // stats (since data has no y value, but open/close/high/low instead). |
---|
61 | // series: dojox.charting.Series[] |
---|
62 | // The data series array to be drawn on this plot. |
---|
63 | // returns: Object |
---|
64 | // Returns an object in the form of { hmin, hmax, vmin, vmax }. |
---|
65 | |
---|
66 | // we have to roll our own, since we need to use all four passed |
---|
67 | // values to figure out our stats, and common only assumes x and y. |
---|
68 | var stats = lang.delegate(dc.defaultStats); |
---|
69 | for(var i=0; i<series.length; i++){ |
---|
70 | var run = series[i]; |
---|
71 | if(!run.data.length){ continue; } |
---|
72 | var old_vmin = stats.vmin, old_vmax = stats.vmax; |
---|
73 | if(!("ymin" in run) || !("ymax" in run)){ |
---|
74 | arr.forEach(run.data, function(val, idx){ |
---|
75 | if(val !== null){ |
---|
76 | var x = val.x || idx + 1; |
---|
77 | stats.hmin = Math.min(stats.hmin, x); |
---|
78 | stats.hmax = Math.max(stats.hmax, x); |
---|
79 | stats.vmin = Math.min(stats.vmin, val.open, val.close, val.high, val.low); |
---|
80 | stats.vmax = Math.max(stats.vmax, val.open, val.close, val.high, val.low); |
---|
81 | } |
---|
82 | }); |
---|
83 | } |
---|
84 | if("ymin" in run){ stats.vmin = Math.min(old_vmin, run.ymin); } |
---|
85 | if("ymax" in run){ stats.vmax = Math.max(old_vmax, run.ymax); } |
---|
86 | } |
---|
87 | return stats; |
---|
88 | }, |
---|
89 | |
---|
90 | getSeriesStats: function(){ |
---|
91 | // summary: |
---|
92 | // Calculate the min/max on all attached series in both directions. |
---|
93 | // returns: Object |
---|
94 | // {hmin, hmax, vmin, vmax} min/max in both directions. |
---|
95 | var stats = this.collectStats(this.series); |
---|
96 | stats.hmin -= 0.5; |
---|
97 | stats.hmax += 0.5; |
---|
98 | return stats; |
---|
99 | }, |
---|
100 | |
---|
101 | render: function(dim, offsets){ |
---|
102 | // summary: |
---|
103 | // Run the calculations for any axes for this plot. |
---|
104 | // dim: Object |
---|
105 | // An object in the form of { width, height } |
---|
106 | // offsets: Object |
---|
107 | // An object of the form { l, r, t, b}. |
---|
108 | // returns: dojox.charting.plot2d.OHLC |
---|
109 | // A reference to this plot for functional chaining. |
---|
110 | if(this.zoom && !this.isDataDirty()){ |
---|
111 | return this.performZoom(dim, offsets); |
---|
112 | } |
---|
113 | this.resetEvents(); |
---|
114 | this.dirty = this.isDirty(); |
---|
115 | if(this.dirty){ |
---|
116 | arr.forEach(this.series, purgeGroup); |
---|
117 | this._eventSeries = {}; |
---|
118 | this.cleanGroup(); |
---|
119 | var s = this.group; |
---|
120 | df.forEachRev(this.series, function(item){ item.cleanGroup(s); }); |
---|
121 | } |
---|
122 | var t = this.chart.theme, f, gap, width, |
---|
123 | ht = this._hScaler.scaler.getTransformerFromModel(this._hScaler), |
---|
124 | vt = this._vScaler.scaler.getTransformerFromModel(this._vScaler), |
---|
125 | baseline = Math.max(0, this._vScaler.bounds.lower), |
---|
126 | baselineHeight = vt(baseline), |
---|
127 | events = this.events(); |
---|
128 | f = dc.calculateBarSize(this._hScaler.bounds.scale, this.opt); |
---|
129 | gap = f.gap; |
---|
130 | width = f.size; |
---|
131 | for(var i = this.series.length - 1; i >= 0; --i){ |
---|
132 | var run = this.series[i]; |
---|
133 | if(!this.dirty && !run.dirty){ |
---|
134 | t.skip(); |
---|
135 | this._reconnectEvents(run.name); |
---|
136 | continue; |
---|
137 | } |
---|
138 | run.cleanGroup(); |
---|
139 | var theme = t.next("candlestick", [this.opt, run]), s = run.group, |
---|
140 | eventSeries = new Array(run.data.length); |
---|
141 | for(var j = 0; j < run.data.length; ++j){ |
---|
142 | var v = run.data[j]; |
---|
143 | if(v !== null){ |
---|
144 | var finalTheme = t.addMixin(theme, "candlestick", v, true); |
---|
145 | |
---|
146 | // calculate the points we need for OHLC |
---|
147 | var x = ht(v.x || (j+0.5)) + offsets.l + gap, |
---|
148 | y = dim.height - offsets.b, |
---|
149 | open = vt(v.open), |
---|
150 | close = vt(v.close), |
---|
151 | high = vt(v.high), |
---|
152 | low = vt(v.low); |
---|
153 | if(low > high){ |
---|
154 | var tmp = high; |
---|
155 | high = low; |
---|
156 | low = tmp; |
---|
157 | } |
---|
158 | |
---|
159 | if(width >= 1){ |
---|
160 | var hl = {x1: width/2, x2: width/2, y1: y - high, y2: y - low}, |
---|
161 | op = {x1: 0, x2: ((width/2) + ((finalTheme.series.stroke.width||1)/2)), y1: y-open, y2: y-open}, |
---|
162 | cl = {x1: ((width/2) - ((finalTheme.series.stroke.width||1)/2)), x2: width, y1: y-close, y2: y-close}; |
---|
163 | var shape = s.createGroup(); |
---|
164 | shape.setTransform({dx: x, dy: 0}); |
---|
165 | var inner = shape.createGroup(); |
---|
166 | inner.createLine(hl).setStroke(finalTheme.series.stroke); |
---|
167 | inner.createLine(op).setStroke(finalTheme.series.stroke); |
---|
168 | inner.createLine(cl).setStroke(finalTheme.series.stroke); |
---|
169 | |
---|
170 | // TODO: double check this. |
---|
171 | run.dyn.stroke = finalTheme.series.stroke; |
---|
172 | if(events){ |
---|
173 | var o = { |
---|
174 | element: "candlestick", |
---|
175 | index: j, |
---|
176 | run: run, |
---|
177 | shape: inner, |
---|
178 | x: x, |
---|
179 | y: y-Math.max(open, close), |
---|
180 | cx: width/2, |
---|
181 | cy: (y-Math.max(open, close)) + (Math.max(open > close ? open-close : close-open, 1)/2), |
---|
182 | width: width, |
---|
183 | height: Math.max(open > close ? open-close : close-open, 1), |
---|
184 | data: v |
---|
185 | }; |
---|
186 | this._connectEvents(o); |
---|
187 | eventSeries[j] = o; |
---|
188 | } |
---|
189 | } |
---|
190 | if(this.animate){ |
---|
191 | this._animateOHLC(shape, y - low, high - low); |
---|
192 | } |
---|
193 | } |
---|
194 | } |
---|
195 | this._eventSeries[run.name] = eventSeries; |
---|
196 | run.dirty = false; |
---|
197 | } |
---|
198 | this.dirty = false; |
---|
199 | return this; // dojox.charting.plot2d.OHLC |
---|
200 | }, |
---|
201 | _animateOHLC: function(shape, voffset, vsize){ |
---|
202 | fx.animateTransform(lang.delegate({ |
---|
203 | shape: shape, |
---|
204 | duration: 1200, |
---|
205 | transform: [ |
---|
206 | {name: "translate", start: [0, voffset - (voffset/vsize)], end: [0, 0]}, |
---|
207 | {name: "scale", start: [1, 1/vsize], end: [1, 1]}, |
---|
208 | {name: "original"} |
---|
209 | ] |
---|
210 | }, this.animate)).play(); |
---|
211 | } |
---|
212 | }); |
---|
213 | }); |
---|