rev |
line source |
paulo@89
|
1 /* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */
|
paulo@89
|
2 (function(){
|
paulo@89
|
3
|
paulo@89
|
4 // set up main nv object
|
paulo@89
|
5 var nv = {};
|
paulo@89
|
6
|
paulo@89
|
7 // the major global objects under the nv namespace
|
paulo@89
|
8 nv.dev = false; //set false when in production
|
paulo@89
|
9 nv.tooltip = nv.tooltip || {}; // For the tooltip system
|
paulo@89
|
10 nv.utils = nv.utils || {}; // Utility subsystem
|
paulo@89
|
11 nv.models = nv.models || {}; //stores all the possible models/components
|
paulo@89
|
12 nv.charts = {}; //stores all the ready to use charts
|
paulo@89
|
13 nv.logs = {}; //stores some statistics and potential error messages
|
paulo@89
|
14 nv.dom = {}; //DOM manipulation functions
|
paulo@89
|
15
|
paulo@89
|
16 nv.dispatch = d3.dispatch('render_start', 'render_end');
|
paulo@89
|
17
|
paulo@89
|
18 // Function bind polyfill
|
paulo@89
|
19 // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
|
paulo@89
|
20 // https://github.com/ariya/phantomjs/issues/10522
|
paulo@89
|
21 // http://kangax.github.io/compat-table/es5/#Function.prototype.bind
|
paulo@89
|
22 // phantomJS is used for running the test suite
|
paulo@89
|
23 if (!Function.prototype.bind) {
|
paulo@89
|
24 Function.prototype.bind = function (oThis) {
|
paulo@89
|
25 if (typeof this !== "function") {
|
paulo@89
|
26 // closest thing possible to the ECMAScript 5 internal IsCallable function
|
paulo@89
|
27 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
|
paulo@89
|
28 }
|
paulo@89
|
29
|
paulo@89
|
30 var aArgs = Array.prototype.slice.call(arguments, 1),
|
paulo@89
|
31 fToBind = this,
|
paulo@89
|
32 fNOP = function () {},
|
paulo@89
|
33 fBound = function () {
|
paulo@89
|
34 return fToBind.apply(this instanceof fNOP && oThis
|
paulo@89
|
35 ? this
|
paulo@89
|
36 : oThis,
|
paulo@89
|
37 aArgs.concat(Array.prototype.slice.call(arguments)));
|
paulo@89
|
38 };
|
paulo@89
|
39
|
paulo@89
|
40 fNOP.prototype = this.prototype;
|
paulo@89
|
41 fBound.prototype = new fNOP();
|
paulo@89
|
42 return fBound;
|
paulo@89
|
43 };
|
paulo@89
|
44 }
|
paulo@89
|
45
|
paulo@89
|
46 // Development render timers - disabled if dev = false
|
paulo@89
|
47 if (nv.dev) {
|
paulo@89
|
48 nv.dispatch.on('render_start', function(e) {
|
paulo@89
|
49 nv.logs.startTime = +new Date();
|
paulo@89
|
50 });
|
paulo@89
|
51
|
paulo@89
|
52 nv.dispatch.on('render_end', function(e) {
|
paulo@89
|
53 nv.logs.endTime = +new Date();
|
paulo@89
|
54 nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
|
paulo@89
|
55 nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
|
paulo@89
|
56 });
|
paulo@89
|
57 }
|
paulo@89
|
58
|
paulo@89
|
59 // Logs all arguments, and returns the last so you can test things in place
|
paulo@89
|
60 // Note: in IE8 console.log is an object not a function, and if modernizr is used
|
paulo@89
|
61 // then calling Function.prototype.bind with with anything other than a function
|
paulo@89
|
62 // causes a TypeError to be thrown.
|
paulo@89
|
63 nv.log = function() {
|
paulo@89
|
64 if (nv.dev && window.console && console.log && console.log.apply)
|
paulo@89
|
65 console.log.apply(console, arguments);
|
paulo@89
|
66 else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
|
paulo@89
|
67 var log = Function.prototype.bind.call(console.log, console);
|
paulo@89
|
68 log.apply(console, arguments);
|
paulo@89
|
69 }
|
paulo@89
|
70 return arguments[arguments.length - 1];
|
paulo@89
|
71 };
|
paulo@89
|
72
|
paulo@89
|
73 // print console warning, should be used by deprecated functions
|
paulo@89
|
74 nv.deprecated = function(name, info) {
|
paulo@89
|
75 if (console && console.warn) {
|
paulo@89
|
76 console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || '');
|
paulo@89
|
77 }
|
paulo@89
|
78 };
|
paulo@89
|
79
|
paulo@89
|
80 // The nv.render function is used to queue up chart rendering
|
paulo@89
|
81 // in non-blocking async functions.
|
paulo@89
|
82 // When all queued charts are done rendering, nv.dispatch.render_end is invoked.
|
paulo@89
|
83 nv.render = function render(step) {
|
paulo@89
|
84 // number of graphs to generate in each timeout loop
|
paulo@89
|
85 step = step || 1;
|
paulo@89
|
86
|
paulo@89
|
87 nv.render.active = true;
|
paulo@89
|
88 nv.dispatch.render_start();
|
paulo@89
|
89
|
paulo@89
|
90 var renderLoop = function() {
|
paulo@89
|
91 var chart, graph;
|
paulo@89
|
92
|
paulo@89
|
93 for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
|
paulo@89
|
94 chart = graph.generate();
|
paulo@89
|
95 if (typeof graph.callback == typeof(Function)) graph.callback(chart);
|
paulo@89
|
96 }
|
paulo@89
|
97
|
paulo@89
|
98 nv.render.queue.splice(0, i);
|
paulo@89
|
99
|
paulo@89
|
100 if (nv.render.queue.length) {
|
paulo@89
|
101 setTimeout(renderLoop);
|
paulo@89
|
102 }
|
paulo@89
|
103 else {
|
paulo@89
|
104 nv.dispatch.render_end();
|
paulo@89
|
105 nv.render.active = false;
|
paulo@89
|
106 }
|
paulo@89
|
107 };
|
paulo@89
|
108
|
paulo@89
|
109 setTimeout(renderLoop);
|
paulo@89
|
110 };
|
paulo@89
|
111
|
paulo@89
|
112 nv.render.active = false;
|
paulo@89
|
113 nv.render.queue = [];
|
paulo@89
|
114
|
paulo@89
|
115 /*
|
paulo@89
|
116 Adds a chart to the async rendering queue. This method can take arguments in two forms:
|
paulo@89
|
117 nv.addGraph({
|
paulo@89
|
118 generate: <Function>
|
paulo@89
|
119 callback: <Function>
|
paulo@89
|
120 })
|
paulo@89
|
121
|
paulo@89
|
122 or
|
paulo@89
|
123
|
paulo@89
|
124 nv.addGraph(<generate Function>, <callback Function>)
|
paulo@89
|
125
|
paulo@89
|
126 The generate function should contain code that creates the NVD3 model, sets options
|
paulo@89
|
127 on it, adds data to an SVG element, and invokes the chart model. The generate function
|
paulo@89
|
128 should return the chart model. See examples/lineChart.html for a usage example.
|
paulo@89
|
129
|
paulo@89
|
130 The callback function is optional, and it is called when the generate function completes.
|
paulo@89
|
131 */
|
paulo@89
|
132 nv.addGraph = function(obj) {
|
paulo@89
|
133 if (typeof arguments[0] === typeof(Function)) {
|
paulo@89
|
134 obj = {generate: arguments[0], callback: arguments[1]};
|
paulo@89
|
135 }
|
paulo@89
|
136
|
paulo@89
|
137 nv.render.queue.push(obj);
|
paulo@89
|
138
|
paulo@89
|
139 if (!nv.render.active) {
|
paulo@89
|
140 nv.render();
|
paulo@89
|
141 }
|
paulo@89
|
142 };
|
paulo@89
|
143
|
paulo@89
|
144 // Node/CommonJS exports
|
paulo@89
|
145 if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
|
paulo@89
|
146 module.exports = nv;
|
paulo@89
|
147 }
|
paulo@89
|
148
|
paulo@89
|
149 if (typeof(window) !== 'undefined') {
|
paulo@89
|
150 window.nv = nv;
|
paulo@89
|
151 }
|
paulo@89
|
152 /* Facade for queueing DOM write operations
|
paulo@89
|
153 * with Fastdom (https://github.com/wilsonpage/fastdom)
|
paulo@89
|
154 * if available.
|
paulo@89
|
155 * This could easily be extended to support alternate
|
paulo@89
|
156 * implementations in the future.
|
paulo@89
|
157 */
|
paulo@89
|
158 nv.dom.write = function(callback) {
|
paulo@89
|
159 if (window.fastdom !== undefined) {
|
paulo@89
|
160 return fastdom.write(callback);
|
paulo@89
|
161 }
|
paulo@89
|
162 return callback();
|
paulo@89
|
163 };
|
paulo@89
|
164
|
paulo@89
|
165 /* Facade for queueing DOM read operations
|
paulo@89
|
166 * with Fastdom (https://github.com/wilsonpage/fastdom)
|
paulo@89
|
167 * if available.
|
paulo@89
|
168 * This could easily be extended to support alternate
|
paulo@89
|
169 * implementations in the future.
|
paulo@89
|
170 */
|
paulo@89
|
171 nv.dom.read = function(callback) {
|
paulo@89
|
172 if (window.fastdom !== undefined) {
|
paulo@89
|
173 return fastdom.read(callback);
|
paulo@89
|
174 }
|
paulo@89
|
175 return callback();
|
paulo@89
|
176 };/* Utility class to handle creation of an interactive layer.
|
paulo@89
|
177 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
|
paulo@89
|
178 containing the X-coordinate. It can also render a vertical line where the mouse is located.
|
paulo@89
|
179
|
paulo@89
|
180 dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
|
paulo@89
|
181 the rectangle. The dispatch is given one object which contains the mouseX/Y location.
|
paulo@89
|
182 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
|
paulo@89
|
183 */
|
paulo@89
|
184 nv.interactiveGuideline = function() {
|
paulo@89
|
185 "use strict";
|
paulo@89
|
186
|
paulo@89
|
187 var tooltip = nv.models.tooltip();
|
paulo@89
|
188 tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false);
|
paulo@89
|
189
|
paulo@89
|
190 //Public settings
|
paulo@89
|
191 var width = null;
|
paulo@89
|
192 var height = null;
|
paulo@89
|
193
|
paulo@89
|
194 //Please pass in the bounding chart's top and left margins
|
paulo@89
|
195 //This is important for calculating the correct mouseX/Y positions.
|
paulo@89
|
196 var margin = {left: 0, top: 0}
|
paulo@89
|
197 , xScale = d3.scale.linear()
|
paulo@89
|
198 , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick')
|
paulo@89
|
199 , showGuideLine = true;
|
paulo@89
|
200 //Must pass in the bounding chart's <svg> container.
|
paulo@89
|
201 //The mousemove event is attached to this container.
|
paulo@89
|
202 var svgContainer = null;
|
paulo@89
|
203
|
paulo@89
|
204 // check if IE by looking for activeX
|
paulo@89
|
205 var isMSIE = "ActiveXObject" in window;
|
paulo@89
|
206
|
paulo@89
|
207
|
paulo@89
|
208 function layer(selection) {
|
paulo@89
|
209 selection.each(function(data) {
|
paulo@89
|
210 var container = d3.select(this);
|
paulo@89
|
211 var availableWidth = (width || 960), availableHeight = (height || 400);
|
paulo@89
|
212 var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
|
paulo@89
|
213 .data([data]);
|
paulo@89
|
214 var wrapEnter = wrap.enter()
|
paulo@89
|
215 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
|
paulo@89
|
216 wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
|
paulo@89
|
217
|
paulo@89
|
218 if (!svgContainer) {
|
paulo@89
|
219 return;
|
paulo@89
|
220 }
|
paulo@89
|
221
|
paulo@89
|
222 function mouseHandler() {
|
paulo@89
|
223 var d3mouse = d3.mouse(this);
|
paulo@89
|
224 var mouseX = d3mouse[0];
|
paulo@89
|
225 var mouseY = d3mouse[1];
|
paulo@89
|
226 var subtractMargin = true;
|
paulo@89
|
227 var mouseOutAnyReason = false;
|
paulo@89
|
228 if (isMSIE) {
|
paulo@89
|
229 /*
|
paulo@89
|
230 D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
|
paulo@89
|
231 d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
|
paulo@89
|
232 over a rect in IE 10.
|
paulo@89
|
233 However, d3.event.offsetX/Y also returns the mouse coordinates
|
paulo@89
|
234 relative to the triggering <rect>. So we use offsetX/Y on IE.
|
paulo@89
|
235 */
|
paulo@89
|
236 mouseX = d3.event.offsetX;
|
paulo@89
|
237 mouseY = d3.event.offsetY;
|
paulo@89
|
238
|
paulo@89
|
239 /*
|
paulo@89
|
240 On IE, if you attach a mouse event listener to the <svg> container,
|
paulo@89
|
241 it will actually trigger it for all the child elements (like <path>, <circle>, etc).
|
paulo@89
|
242 When this happens on IE, the offsetX/Y is set to where ever the child element
|
paulo@89
|
243 is located.
|
paulo@89
|
244 As a result, we do NOT need to subtract margins to figure out the mouse X/Y
|
paulo@89
|
245 position under this scenario. Removing the line below *will* cause
|
paulo@89
|
246 the interactive layer to not work right on IE.
|
paulo@89
|
247 */
|
paulo@89
|
248 if(d3.event.target.tagName !== "svg") {
|
paulo@89
|
249 subtractMargin = false;
|
paulo@89
|
250 }
|
paulo@89
|
251
|
paulo@89
|
252 if (d3.event.target.className.baseVal.match("nv-legend")) {
|
paulo@89
|
253 mouseOutAnyReason = true;
|
paulo@89
|
254 }
|
paulo@89
|
255
|
paulo@89
|
256 }
|
paulo@89
|
257
|
paulo@89
|
258 if(subtractMargin) {
|
paulo@89
|
259 mouseX -= margin.left;
|
paulo@89
|
260 mouseY -= margin.top;
|
paulo@89
|
261 }
|
paulo@89
|
262
|
paulo@89
|
263 /* If mouseX/Y is outside of the chart's bounds,
|
paulo@89
|
264 trigger a mouseOut event.
|
paulo@89
|
265 */
|
paulo@89
|
266 if (mouseX < 0 || mouseY < 0
|
paulo@89
|
267 || mouseX > availableWidth || mouseY > availableHeight
|
paulo@89
|
268 || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
|
paulo@89
|
269 || mouseOutAnyReason
|
paulo@89
|
270 ) {
|
paulo@89
|
271
|
paulo@89
|
272 if (isMSIE) {
|
paulo@89
|
273 if (d3.event.relatedTarget
|
paulo@89
|
274 && d3.event.relatedTarget.ownerSVGElement === undefined
|
paulo@89
|
275 && (d3.event.relatedTarget.className === undefined
|
paulo@89
|
276 || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) {
|
paulo@89
|
277
|
paulo@89
|
278 return;
|
paulo@89
|
279 }
|
paulo@89
|
280 }
|
paulo@89
|
281 dispatch.elementMouseout({
|
paulo@89
|
282 mouseX: mouseX,
|
paulo@89
|
283 mouseY: mouseY
|
paulo@89
|
284 });
|
paulo@89
|
285 layer.renderGuideLine(null); //hide the guideline
|
paulo@89
|
286 tooltip.hidden(true);
|
paulo@89
|
287 return;
|
paulo@89
|
288 } else {
|
paulo@89
|
289 tooltip.hidden(false);
|
paulo@89
|
290 }
|
paulo@89
|
291
|
paulo@89
|
292 var pointXValue = xScale.invert(mouseX);
|
paulo@89
|
293 dispatch.elementMousemove({
|
paulo@89
|
294 mouseX: mouseX,
|
paulo@89
|
295 mouseY: mouseY,
|
paulo@89
|
296 pointXValue: pointXValue
|
paulo@89
|
297 });
|
paulo@89
|
298
|
paulo@89
|
299 //If user double clicks the layer, fire a elementDblclick
|
paulo@89
|
300 if (d3.event.type === "dblclick") {
|
paulo@89
|
301 dispatch.elementDblclick({
|
paulo@89
|
302 mouseX: mouseX,
|
paulo@89
|
303 mouseY: mouseY,
|
paulo@89
|
304 pointXValue: pointXValue
|
paulo@89
|
305 });
|
paulo@89
|
306 }
|
paulo@89
|
307
|
paulo@89
|
308 // if user single clicks the layer, fire elementClick
|
paulo@89
|
309 if (d3.event.type === 'click') {
|
paulo@89
|
310 dispatch.elementClick({
|
paulo@89
|
311 mouseX: mouseX,
|
paulo@89
|
312 mouseY: mouseY,
|
paulo@89
|
313 pointXValue: pointXValue
|
paulo@89
|
314 });
|
paulo@89
|
315 }
|
paulo@89
|
316 }
|
paulo@89
|
317
|
paulo@89
|
318 svgContainer
|
paulo@89
|
319 .on("touchmove",mouseHandler)
|
paulo@89
|
320 .on("mousemove",mouseHandler, true)
|
paulo@89
|
321 .on("mouseout" ,mouseHandler,true)
|
paulo@89
|
322 .on("dblclick" ,mouseHandler)
|
paulo@89
|
323 .on("click", mouseHandler)
|
paulo@89
|
324 ;
|
paulo@89
|
325
|
paulo@89
|
326 layer.guideLine = null;
|
paulo@89
|
327 //Draws a vertical guideline at the given X postion.
|
paulo@89
|
328 layer.renderGuideLine = function(x) {
|
paulo@89
|
329 if (!showGuideLine) return;
|
paulo@89
|
330 if (layer.guideLine && layer.guideLine.attr("x1") === x) return;
|
paulo@89
|
331 nv.dom.write(function() {
|
paulo@89
|
332 var line = wrap.select(".nv-interactiveGuideLine")
|
paulo@89
|
333 .selectAll("line")
|
paulo@89
|
334 .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
|
paulo@89
|
335 line.enter()
|
paulo@89
|
336 .append("line")
|
paulo@89
|
337 .attr("class", "nv-guideline")
|
paulo@89
|
338 .attr("x1", function(d) { return d;})
|
paulo@89
|
339 .attr("x2", function(d) { return d;})
|
paulo@89
|
340 .attr("y1", availableHeight)
|
paulo@89
|
341 .attr("y2",0);
|
paulo@89
|
342 line.exit().remove();
|
paulo@89
|
343 });
|
paulo@89
|
344 }
|
paulo@89
|
345 });
|
paulo@89
|
346 }
|
paulo@89
|
347
|
paulo@89
|
348 layer.dispatch = dispatch;
|
paulo@89
|
349 layer.tooltip = tooltip;
|
paulo@89
|
350
|
paulo@89
|
351 layer.margin = function(_) {
|
paulo@89
|
352 if (!arguments.length) return margin;
|
paulo@89
|
353 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
|
paulo@89
|
354 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
|
paulo@89
|
355 return layer;
|
paulo@89
|
356 };
|
paulo@89
|
357
|
paulo@89
|
358 layer.width = function(_) {
|
paulo@89
|
359 if (!arguments.length) return width;
|
paulo@89
|
360 width = _;
|
paulo@89
|
361 return layer;
|
paulo@89
|
362 };
|
paulo@89
|
363
|
paulo@89
|
364 layer.height = function(_) {
|
paulo@89
|
365 if (!arguments.length) return height;
|
paulo@89
|
366 height = _;
|
paulo@89
|
367 return layer;
|
paulo@89
|
368 };
|
paulo@89
|
369
|
paulo@89
|
370 layer.xScale = function(_) {
|
paulo@89
|
371 if (!arguments.length) return xScale;
|
paulo@89
|
372 xScale = _;
|
paulo@89
|
373 return layer;
|
paulo@89
|
374 };
|
paulo@89
|
375
|
paulo@89
|
376 layer.showGuideLine = function(_) {
|
paulo@89
|
377 if (!arguments.length) return showGuideLine;
|
paulo@89
|
378 showGuideLine = _;
|
paulo@89
|
379 return layer;
|
paulo@89
|
380 };
|
paulo@89
|
381
|
paulo@89
|
382 layer.svgContainer = function(_) {
|
paulo@89
|
383 if (!arguments.length) return svgContainer;
|
paulo@89
|
384 svgContainer = _;
|
paulo@89
|
385 return layer;
|
paulo@89
|
386 };
|
paulo@89
|
387
|
paulo@89
|
388 return layer;
|
paulo@89
|
389 };
|
paulo@89
|
390
|
paulo@89
|
391 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
|
paulo@89
|
392 This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
|
paulo@89
|
393
|
paulo@89
|
394 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
|
paulo@89
|
395 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
|
paulo@89
|
396 because 28 is closer to 30 than 10.
|
paulo@89
|
397
|
paulo@89
|
398 Unit tests can be found in: interactiveBisectTest.html
|
paulo@89
|
399
|
paulo@89
|
400 Has the following known issues:
|
paulo@89
|
401 * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
|
paulo@89
|
402 * Won't work if there are duplicate x coordinate values.
|
paulo@89
|
403 */
|
paulo@89
|
404 nv.interactiveBisect = function (values, searchVal, xAccessor) {
|
paulo@89
|
405 "use strict";
|
paulo@89
|
406 if (! (values instanceof Array)) {
|
paulo@89
|
407 return null;
|
paulo@89
|
408 }
|
paulo@89
|
409 var _xAccessor;
|
paulo@89
|
410 if (typeof xAccessor !== 'function') {
|
paulo@89
|
411 _xAccessor = function(d) {
|
paulo@89
|
412 return d.x;
|
paulo@89
|
413 }
|
paulo@89
|
414 } else {
|
paulo@89
|
415 _xAccessor = xAccessor;
|
paulo@89
|
416 }
|
paulo@89
|
417 var _cmp = function(d, v) {
|
paulo@89
|
418 // Accessors are no longer passed the index of the element along with
|
paulo@89
|
419 // the element itself when invoked by d3.bisector.
|
paulo@89
|
420 //
|
paulo@89
|
421 // Starting at D3 v3.4.4, d3.bisector() started inspecting the
|
paulo@89
|
422 // function passed to determine if it should consider it an accessor
|
paulo@89
|
423 // or a comparator. This meant that accessors that take two arguments
|
paulo@89
|
424 // (expecting an index as the second parameter) are treated as
|
paulo@89
|
425 // comparators where the second argument is the search value against
|
paulo@89
|
426 // which the first argument is compared.
|
paulo@89
|
427 return _xAccessor(d) - v;
|
paulo@89
|
428 };
|
paulo@89
|
429
|
paulo@89
|
430 var bisect = d3.bisector(_cmp).left;
|
paulo@89
|
431 var index = d3.max([0, bisect(values,searchVal) - 1]);
|
paulo@89
|
432 var currentValue = _xAccessor(values[index]);
|
paulo@89
|
433
|
paulo@89
|
434 if (typeof currentValue === 'undefined') {
|
paulo@89
|
435 currentValue = index;
|
paulo@89
|
436 }
|
paulo@89
|
437
|
paulo@89
|
438 if (currentValue === searchVal) {
|
paulo@89
|
439 return index; //found exact match
|
paulo@89
|
440 }
|
paulo@89
|
441
|
paulo@89
|
442 var nextIndex = d3.min([index+1, values.length - 1]);
|
paulo@89
|
443 var nextValue = _xAccessor(values[nextIndex]);
|
paulo@89
|
444
|
paulo@89
|
445 if (typeof nextValue === 'undefined') {
|
paulo@89
|
446 nextValue = nextIndex;
|
paulo@89
|
447 }
|
paulo@89
|
448
|
paulo@89
|
449 if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
|
paulo@89
|
450 return index;
|
paulo@89
|
451 } else {
|
paulo@89
|
452 return nextIndex
|
paulo@89
|
453 }
|
paulo@89
|
454 };
|
paulo@89
|
455
|
paulo@89
|
456 /*
|
paulo@89
|
457 Returns the index in the array "values" that is closest to searchVal.
|
paulo@89
|
458 Only returns an index if searchVal is within some "threshold".
|
paulo@89
|
459 Otherwise, returns null.
|
paulo@89
|
460 */
|
paulo@89
|
461 nv.nearestValueIndex = function (values, searchVal, threshold) {
|
paulo@89
|
462 "use strict";
|
paulo@89
|
463 var yDistMax = Infinity, indexToHighlight = null;
|
paulo@89
|
464 values.forEach(function(d,i) {
|
paulo@89
|
465 var delta = Math.abs(searchVal - d);
|
paulo@89
|
466 if ( d != null && delta <= yDistMax && delta < threshold) {
|
paulo@89
|
467 yDistMax = delta;
|
paulo@89
|
468 indexToHighlight = i;
|
paulo@89
|
469 }
|
paulo@89
|
470 });
|
paulo@89
|
471 return indexToHighlight;
|
paulo@89
|
472 };
|
paulo@89
|
473 /* Tooltip rendering model for nvd3 charts.
|
paulo@89
|
474 window.nv.models.tooltip is the updated,new way to render tooltips.
|
paulo@89
|
475
|
paulo@89
|
476 window.nv.tooltip.show is the old tooltip code.
|
paulo@89
|
477 window.nv.tooltip.* also has various helper methods.
|
paulo@89
|
478 */
|
paulo@89
|
479 (function() {
|
paulo@89
|
480 "use strict";
|
paulo@89
|
481
|
paulo@89
|
482 /* Model which can be instantiated to handle tooltip rendering.
|
paulo@89
|
483 Example usage:
|
paulo@89
|
484 var tip = nv.models.tooltip().gravity('w').distance(23)
|
paulo@89
|
485 .data(myDataObject);
|
paulo@89
|
486
|
paulo@89
|
487 tip(); //just invoke the returned function to render tooltip.
|
paulo@89
|
488 */
|
paulo@89
|
489 nv.models.tooltip = function() {
|
paulo@89
|
490
|
paulo@89
|
491 /*
|
paulo@89
|
492 Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
|
paulo@89
|
493 Example Format of data:
|
paulo@89
|
494 {
|
paulo@89
|
495 key: "Date",
|
paulo@89
|
496 value: "August 2009",
|
paulo@89
|
497 series: [
|
paulo@89
|
498 {key: "Series 1", value: "Value 1", color: "#000"},
|
paulo@89
|
499 {key: "Series 2", value: "Value 2", color: "#00f"}
|
paulo@89
|
500 ]
|
paulo@89
|
501 }
|
paulo@89
|
502 */
|
paulo@89
|
503 var data = null;
|
paulo@89
|
504 var gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned.
|
paulo@89
|
505 , distance = 25 //Distance to offset tooltip from the mouse location.
|
paulo@89
|
506 , snapDistance = 0 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
|
paulo@89
|
507 , fixedTop = null //If not null, this fixes the top position of the tooltip.
|
paulo@89
|
508 , classes = null //Attaches additional CSS classes to the tooltip DIV that is created.
|
paulo@89
|
509 , chartContainer = null //Parent dom element of the SVG that holds the chart.
|
paulo@89
|
510 , hidden = true // start off hidden, toggle with hide/show functions below
|
paulo@89
|
511 , hideDelay = 400 // delay before the tooltip hides after calling hide()
|
paulo@89
|
512 , tooltip = null // d3 select of tooltipElem below
|
paulo@89
|
513 , tooltipElem = null //actual DOM element representing the tooltip.
|
paulo@89
|
514 , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
|
paulo@89
|
515 , offset = {left: 0, top: 0} //Offset of tooltip against the pointer
|
paulo@89
|
516 , enabled = true //True -> tooltips are rendered. False -> don't render tooltips.
|
paulo@89
|
517 , duration = 100 // duration for tooltip movement
|
paulo@89
|
518 , headerEnabled = true
|
paulo@89
|
519 ;
|
paulo@89
|
520
|
paulo@89
|
521 // set to true by interactive layer to adjust tooltip positions
|
paulo@89
|
522 // eventually we should probably fix interactive layer to get the position better.
|
paulo@89
|
523 // for now this is needed if you want to set chartContainer for normal tooltips, else it "fixes" it to broken
|
paulo@89
|
524 var isInteractiveLayer = false;
|
paulo@89
|
525
|
paulo@89
|
526 //Generates a unique id when you create a new tooltip() object
|
paulo@89
|
527 var id = "nvtooltip-" + Math.floor(Math.random() * 100000);
|
paulo@89
|
528
|
paulo@89
|
529 //CSS class to specify whether element should not have mouse events.
|
paulo@89
|
530 var nvPointerEventsClass = "nv-pointer-events-none";
|
paulo@89
|
531
|
paulo@89
|
532 //Format function for the tooltip values column
|
paulo@89
|
533 var valueFormatter = function(d,i) {
|
paulo@89
|
534 return d;
|
paulo@89
|
535 };
|
paulo@89
|
536
|
paulo@89
|
537 //Format function for the tooltip header value.
|
paulo@89
|
538 var headerFormatter = function(d) {
|
paulo@89
|
539 return d;
|
paulo@89
|
540 };
|
paulo@89
|
541
|
paulo@89
|
542 var keyFormatter = function(d, i) {
|
paulo@89
|
543 return d;
|
paulo@89
|
544 };
|
paulo@89
|
545
|
paulo@89
|
546 //By default, the tooltip model renders a beautiful table inside a DIV.
|
paulo@89
|
547 //You can override this function if a custom tooltip is desired.
|
paulo@89
|
548 var contentGenerator = function(d) {
|
paulo@89
|
549 if (d === null) {
|
paulo@89
|
550 return '';
|
paulo@89
|
551 }
|
paulo@89
|
552
|
paulo@89
|
553 var table = d3.select(document.createElement("table"));
|
paulo@89
|
554 if (headerEnabled) {
|
paulo@89
|
555 var theadEnter = table.selectAll("thead")
|
paulo@89
|
556 .data([d])
|
paulo@89
|
557 .enter().append("thead");
|
paulo@89
|
558
|
paulo@89
|
559 theadEnter.append("tr")
|
paulo@89
|
560 .append("td")
|
paulo@89
|
561 .attr("colspan", 3)
|
paulo@89
|
562 .append("strong")
|
paulo@89
|
563 .classed("x-value", true)
|
paulo@89
|
564 .html(headerFormatter(d.value));
|
paulo@89
|
565 }
|
paulo@89
|
566
|
paulo@89
|
567 var tbodyEnter = table.selectAll("tbody")
|
paulo@89
|
568 .data([d])
|
paulo@89
|
569 .enter().append("tbody");
|
paulo@89
|
570
|
paulo@89
|
571 var trowEnter = tbodyEnter.selectAll("tr")
|
paulo@89
|
572 .data(function(p) { return p.series})
|
paulo@89
|
573 .enter()
|
paulo@89
|
574 .append("tr")
|
paulo@89
|
575 .classed("highlight", function(p) { return p.highlight});
|
paulo@89
|
576
|
paulo@89
|
577 trowEnter.append("td")
|
paulo@89
|
578 .classed("legend-color-guide",true)
|
paulo@89
|
579 .append("div")
|
paulo@89
|
580 .style("background-color", function(p) { return p.color});
|
paulo@89
|
581
|
paulo@89
|
582 trowEnter.append("td")
|
paulo@89
|
583 .classed("key",true)
|
paulo@89
|
584 .html(function(p, i) {return keyFormatter(p.key, i)});
|
paulo@89
|
585
|
paulo@89
|
586 trowEnter.append("td")
|
paulo@89
|
587 .classed("value",true)
|
paulo@89
|
588 .html(function(p, i) { return valueFormatter(p.value, i) });
|
paulo@89
|
589
|
paulo@89
|
590
|
paulo@89
|
591 trowEnter.selectAll("td").each(function(p) {
|
paulo@89
|
592 if (p.highlight) {
|
paulo@89
|
593 var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
|
paulo@89
|
594 var opacity = 0.6;
|
paulo@89
|
595 d3.select(this)
|
paulo@89
|
596 .style("border-bottom-color", opacityScale(opacity))
|
paulo@89
|
597 .style("border-top-color", opacityScale(opacity))
|
paulo@89
|
598 ;
|
paulo@89
|
599 }
|
paulo@89
|
600 });
|
paulo@89
|
601
|
paulo@89
|
602 var html = table.node().outerHTML;
|
paulo@89
|
603 if (d.footer !== undefined)
|
paulo@89
|
604 html += "<div class='footer'>" + d.footer + "</div>";
|
paulo@89
|
605 return html;
|
paulo@89
|
606
|
paulo@89
|
607 };
|
paulo@89
|
608
|
paulo@89
|
609 var dataSeriesExists = function(d) {
|
paulo@89
|
610 if (d && d.series) {
|
paulo@89
|
611 if (d.series instanceof Array) {
|
paulo@89
|
612 return !!d.series.length;
|
paulo@89
|
613 }
|
paulo@89
|
614 // if object, it's okay just convert to array of the object
|
paulo@89
|
615 if (d.series instanceof Object) {
|
paulo@89
|
616 d.series = [d.series];
|
paulo@89
|
617 return true;
|
paulo@89
|
618 }
|
paulo@89
|
619 }
|
paulo@89
|
620 return false;
|
paulo@89
|
621 };
|
paulo@89
|
622
|
paulo@89
|
623 var calcTooltipPosition = function(pos) {
|
paulo@89
|
624 if (!tooltipElem) return;
|
paulo@89
|
625
|
paulo@89
|
626 nv.dom.read(function() {
|
paulo@89
|
627 var height = parseInt(tooltipElem.offsetHeight, 10),
|
paulo@89
|
628 width = parseInt(tooltipElem.offsetWidth, 10),
|
paulo@89
|
629 windowWidth = nv.utils.windowSize().width,
|
paulo@89
|
630 windowHeight = nv.utils.windowSize().height,
|
paulo@89
|
631 scrollTop = window.pageYOffset,
|
paulo@89
|
632 scrollLeft = window.pageXOffset,
|
paulo@89
|
633 left, top;
|
paulo@89
|
634
|
paulo@89
|
635 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
|
paulo@89
|
636 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
|
paulo@89
|
637
|
paulo@89
|
638
|
paulo@89
|
639 //Helper functions to find the total offsets of a given DOM element.
|
paulo@89
|
640 //Looks up the entire ancestry of an element, up to the first relatively positioned element.
|
paulo@89
|
641 var tooltipTop = function ( Elem ) {
|
paulo@89
|
642 var offsetTop = top;
|
paulo@89
|
643 do {
|
paulo@89
|
644 if( !isNaN( Elem.offsetTop ) ) {
|
paulo@89
|
645 offsetTop += (Elem.offsetTop);
|
paulo@89
|
646 }
|
paulo@89
|
647 Elem = Elem.offsetParent;
|
paulo@89
|
648 } while( Elem );
|
paulo@89
|
649 return offsetTop;
|
paulo@89
|
650 };
|
paulo@89
|
651 var tooltipLeft = function ( Elem ) {
|
paulo@89
|
652 var offsetLeft = left;
|
paulo@89
|
653 do {
|
paulo@89
|
654 if( !isNaN( Elem.offsetLeft ) ) {
|
paulo@89
|
655 offsetLeft += (Elem.offsetLeft);
|
paulo@89
|
656 }
|
paulo@89
|
657 Elem = Elem.offsetParent;
|
paulo@89
|
658 } while( Elem );
|
paulo@89
|
659 return offsetLeft;
|
paulo@89
|
660 };
|
paulo@89
|
661
|
paulo@89
|
662 // calculate position based on gravity
|
paulo@89
|
663 var tLeft, tTop;
|
paulo@89
|
664 switch (gravity) {
|
paulo@89
|
665 case 'e':
|
paulo@89
|
666 left = pos[0] - width - distance;
|
paulo@89
|
667 top = pos[1] - (height / 2);
|
paulo@89
|
668 tLeft = tooltipLeft(tooltipElem);
|
paulo@89
|
669 tTop = tooltipTop(tooltipElem);
|
paulo@89
|
670 if (tLeft < scrollLeft) left = pos[0] + distance > scrollLeft ? pos[0] + distance : scrollLeft - tLeft + left;
|
paulo@89
|
671 if (tTop < scrollTop) top = scrollTop - tTop + top;
|
paulo@89
|
672 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
|
paulo@89
|
673 break;
|
paulo@89
|
674 case 'w':
|
paulo@89
|
675 left = pos[0] + distance;
|
paulo@89
|
676 top = pos[1] - (height / 2);
|
paulo@89
|
677 tLeft = tooltipLeft(tooltipElem);
|
paulo@89
|
678 tTop = tooltipTop(tooltipElem);
|
paulo@89
|
679 if (tLeft + width > windowWidth) left = pos[0] - width - distance;
|
paulo@89
|
680 if (tTop < scrollTop) top = scrollTop + 5;
|
paulo@89
|
681 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
|
paulo@89
|
682 break;
|
paulo@89
|
683 case 'n':
|
paulo@89
|
684 left = pos[0] - (width / 2) - 5;
|
paulo@89
|
685 top = pos[1] + distance;
|
paulo@89
|
686 tLeft = tooltipLeft(tooltipElem);
|
paulo@89
|
687 tTop = tooltipTop(tooltipElem);
|
paulo@89
|
688 if (tLeft < scrollLeft) left = scrollLeft + 5;
|
paulo@89
|
689 if (tLeft + width > windowWidth) left = left - width/2 + 5;
|
paulo@89
|
690 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
|
paulo@89
|
691 break;
|
paulo@89
|
692 case 's':
|
paulo@89
|
693 left = pos[0] - (width / 2);
|
paulo@89
|
694 top = pos[1] - height - distance;
|
paulo@89
|
695 tLeft = tooltipLeft(tooltipElem);
|
paulo@89
|
696 tTop = tooltipTop(tooltipElem);
|
paulo@89
|
697 if (tLeft < scrollLeft) left = scrollLeft + 5;
|
paulo@89
|
698 if (tLeft + width > windowWidth) left = left - width/2 + 5;
|
paulo@89
|
699 if (scrollTop > tTop) top = scrollTop;
|
paulo@89
|
700 break;
|
paulo@89
|
701 case 'none':
|
paulo@89
|
702 left = pos[0];
|
paulo@89
|
703 top = pos[1] - distance;
|
paulo@89
|
704 tLeft = tooltipLeft(tooltipElem);
|
paulo@89
|
705 tTop = tooltipTop(tooltipElem);
|
paulo@89
|
706 break;
|
paulo@89
|
707 }
|
paulo@89
|
708
|
paulo@89
|
709 // adjust tooltip offsets
|
paulo@89
|
710 left -= offset.left;
|
paulo@89
|
711 top -= offset.top;
|
paulo@89
|
712
|
paulo@89
|
713 // using tooltip.style('transform') returns values un-usable for tween
|
paulo@89
|
714 var box = tooltipElem.getBoundingClientRect();
|
paulo@89
|
715 var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
paulo@89
|
716 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
paulo@89
|
717 var old_translate = 'translate(' + (box.left + scrollLeft) + 'px, ' + (box.top + scrollTop) + 'px)';
|
paulo@89
|
718 var new_translate = 'translate(' + left + 'px, ' + top + 'px)';
|
paulo@89
|
719 var translateInterpolator = d3.interpolateString(old_translate, new_translate);
|
paulo@89
|
720
|
paulo@89
|
721 var is_hidden = tooltip.style('opacity') < 0.1;
|
paulo@89
|
722
|
paulo@89
|
723 // delay hiding a bit to avoid flickering
|
paulo@89
|
724 if (hidden) {
|
paulo@89
|
725 tooltip
|
paulo@89
|
726 .transition()
|
paulo@89
|
727 .delay(hideDelay)
|
paulo@89
|
728 .duration(0)
|
paulo@89
|
729 .style('opacity', 0);
|
paulo@89
|
730 } else {
|
paulo@89
|
731 tooltip
|
paulo@89
|
732 .interrupt() // cancel running transitions
|
paulo@89
|
733 .transition()
|
paulo@89
|
734 .duration(is_hidden ? 0 : duration)
|
paulo@89
|
735 // using tween since some versions of d3 can't auto-tween a translate on a div
|
paulo@89
|
736 .styleTween('transform', function (d) {
|
paulo@89
|
737 return translateInterpolator;
|
paulo@89
|
738 }, 'important')
|
paulo@89
|
739 // Safari has its own `-webkit-transform` and does not support `transform`
|
paulo@89
|
740 // transform tooltip without transition only in Safari
|
paulo@89
|
741 .style('-webkit-transform', new_translate)
|
paulo@89
|
742 .style('opacity', 1);
|
paulo@89
|
743 }
|
paulo@89
|
744
|
paulo@89
|
745
|
paulo@89
|
746
|
paulo@89
|
747 });
|
paulo@89
|
748 };
|
paulo@89
|
749
|
paulo@89
|
750 //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
|
paulo@89
|
751 function convertViewBoxRatio() {
|
paulo@89
|
752 if (chartContainer) {
|
paulo@89
|
753 var svg = d3.select(chartContainer);
|
paulo@89
|
754 if (svg.node().tagName !== "svg") {
|
paulo@89
|
755 svg = svg.select("svg");
|
paulo@89
|
756 }
|
paulo@89
|
757 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
|
paulo@89
|
758 if (viewBox) {
|
paulo@89
|
759 viewBox = viewBox.split(' ');
|
paulo@89
|
760 var ratio = parseInt(svg.style('width'), 10) / viewBox[2];
|
paulo@89
|
761
|
paulo@89
|
762 position.left = position.left * ratio;
|
paulo@89
|
763 position.top = position.top * ratio;
|
paulo@89
|
764 }
|
paulo@89
|
765 }
|
paulo@89
|
766 }
|
paulo@89
|
767
|
paulo@89
|
768 //Creates new tooltip container, or uses existing one on DOM.
|
paulo@89
|
769 function initTooltip() {
|
paulo@89
|
770 if (!tooltip) {
|
paulo@89
|
771 var body;
|
paulo@89
|
772 if (chartContainer) {
|
paulo@89
|
773 body = chartContainer;
|
paulo@89
|
774 } else {
|
paulo@89
|
775 body = document.body;
|
paulo@89
|
776 }
|
paulo@89
|
777 //Create new tooltip div if it doesn't exist on DOM.
|
paulo@89
|
778 tooltip = d3.select(body).append("div")
|
paulo@89
|
779 .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
|
paulo@89
|
780 .attr("id", id);
|
paulo@89
|
781 tooltip.style("top", 0).style("left", 0);
|
paulo@89
|
782 tooltip.style('opacity', 0);
|
paulo@89
|
783 tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true);
|
paulo@89
|
784 tooltip.classed(nvPointerEventsClass, true);
|
paulo@89
|
785 tooltipElem = tooltip.node();
|
paulo@89
|
786 }
|
paulo@89
|
787 }
|
paulo@89
|
788
|
paulo@89
|
789 //Draw the tooltip onto the DOM.
|
paulo@89
|
790 function nvtooltip() {
|
paulo@89
|
791 if (!enabled) return;
|
paulo@89
|
792 if (!dataSeriesExists(data)) return;
|
paulo@89
|
793
|
paulo@89
|
794 convertViewBoxRatio();
|
paulo@89
|
795
|
paulo@89
|
796 var left = position.left;
|
paulo@89
|
797 var top = (fixedTop !== null) ? fixedTop : position.top;
|
paulo@89
|
798
|
paulo@89
|
799 nv.dom.write(function () {
|
paulo@89
|
800 initTooltip();
|
paulo@89
|
801 // generate data and set it into tooltip
|
paulo@89
|
802 // Bonus - If you override contentGenerator and return falsey you can use something like
|
paulo@89
|
803 // React or Knockout to bind the data for your tooltip
|
paulo@89
|
804 var newContent = contentGenerator(data);
|
paulo@89
|
805 if (newContent) {
|
paulo@89
|
806 tooltipElem.innerHTML = newContent;
|
paulo@89
|
807 }
|
paulo@89
|
808
|
paulo@89
|
809 if (chartContainer && isInteractiveLayer) {
|
paulo@89
|
810 nv.dom.read(function() {
|
paulo@89
|
811 var svgComp = chartContainer.getElementsByTagName("svg")[0];
|
paulo@89
|
812 var svgOffset = {left:0,top:0};
|
paulo@89
|
813 if (svgComp) {
|
paulo@89
|
814 var svgBound = svgComp.getBoundingClientRect();
|
paulo@89
|
815 var chartBound = chartContainer.getBoundingClientRect();
|
paulo@89
|
816 var svgBoundTop = svgBound.top;
|
paulo@89
|
817
|
paulo@89
|
818 //Defensive code. Sometimes, svgBoundTop can be a really negative
|
paulo@89
|
819 // number, like -134254. That's a bug.
|
paulo@89
|
820 // If such a number is found, use zero instead. FireFox bug only
|
paulo@89
|
821 if (svgBoundTop < 0) {
|
paulo@89
|
822 var containerBound = chartContainer.getBoundingClientRect();
|
paulo@89
|
823 svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
|
paulo@89
|
824 }
|
paulo@89
|
825 svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
|
paulo@89
|
826 svgOffset.left = Math.abs(svgBound.left - chartBound.left);
|
paulo@89
|
827 }
|
paulo@89
|
828 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
|
paulo@89
|
829 //You need to also add any offset between the <svg> element and its containing <div>
|
paulo@89
|
830 //Finally, add any offset of the containing <div> on the whole page.
|
paulo@89
|
831 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
|
paulo@89
|
832 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
|
paulo@89
|
833
|
paulo@89
|
834 if (snapDistance && snapDistance > 0) {
|
paulo@89
|
835 top = Math.floor(top/snapDistance) * snapDistance;
|
paulo@89
|
836 }
|
paulo@89
|
837 calcTooltipPosition([left,top]);
|
paulo@89
|
838 });
|
paulo@89
|
839 } else {
|
paulo@89
|
840 calcTooltipPosition([left,top]);
|
paulo@89
|
841 }
|
paulo@89
|
842 });
|
paulo@89
|
843
|
paulo@89
|
844 return nvtooltip;
|
paulo@89
|
845 }
|
paulo@89
|
846
|
paulo@89
|
847 nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
|
paulo@89
|
848 nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip);
|
paulo@89
|
849
|
paulo@89
|
850 nvtooltip._options = Object.create({}, {
|
paulo@89
|
851 // simple read/write options
|
paulo@89
|
852 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
|
paulo@89
|
853 gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
|
paulo@89
|
854 distance: {get: function(){return distance;}, set: function(_){distance=_;}},
|
paulo@89
|
855 snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}},
|
paulo@89
|
856 classes: {get: function(){return classes;}, set: function(_){classes=_;}},
|
paulo@89
|
857 chartContainer: {get: function(){return chartContainer;}, set: function(_){chartContainer=_;}},
|
paulo@89
|
858 fixedTop: {get: function(){return fixedTop;}, set: function(_){fixedTop=_;}},
|
paulo@89
|
859 enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}},
|
paulo@89
|
860 hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}},
|
paulo@89
|
861 contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}},
|
paulo@89
|
862 valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}},
|
paulo@89
|
863 headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}},
|
paulo@89
|
864 keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
|
paulo@89
|
865 headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}},
|
paulo@89
|
866
|
paulo@89
|
867 // internal use only, set by interactive layer to adjust position.
|
paulo@89
|
868 _isInteractiveLayer: {get: function(){return isInteractiveLayer;}, set: function(_){isInteractiveLayer=!!_;}},
|
paulo@89
|
869
|
paulo@89
|
870 // options with extra logic
|
paulo@89
|
871 position: {get: function(){return position;}, set: function(_){
|
paulo@89
|
872 position.left = _.left !== undefined ? _.left : position.left;
|
paulo@89
|
873 position.top = _.top !== undefined ? _.top : position.top;
|
paulo@89
|
874 }},
|
paulo@89
|
875 offset: {get: function(){return offset;}, set: function(_){
|
paulo@89
|
876 offset.left = _.left !== undefined ? _.left : offset.left;
|
paulo@89
|
877 offset.top = _.top !== undefined ? _.top : offset.top;
|
paulo@89
|
878 }},
|
paulo@89
|
879 hidden: {get: function(){return hidden;}, set: function(_){
|
paulo@89
|
880 if (hidden != _) {
|
paulo@89
|
881 hidden = !!_;
|
paulo@89
|
882 nvtooltip();
|
paulo@89
|
883 }
|
paulo@89
|
884 }},
|
paulo@89
|
885 data: {get: function(){return data;}, set: function(_){
|
paulo@89
|
886 // if showing a single data point, adjust data format with that
|
paulo@89
|
887 if (_.point) {
|
paulo@89
|
888 _.value = _.point.x;
|
paulo@89
|
889 _.series = _.series || {};
|
paulo@89
|
890 _.series.value = _.point.y;
|
paulo@89
|
891 _.series.color = _.point.color || _.series.color;
|
paulo@89
|
892 }
|
paulo@89
|
893 data = _;
|
paulo@89
|
894 }},
|
paulo@89
|
895
|
paulo@89
|
896 // read only properties
|
paulo@89
|
897 tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}},
|
paulo@89
|
898 id: {get: function(){return id;}, set: function(_){}}
|
paulo@89
|
899 });
|
paulo@89
|
900
|
paulo@89
|
901 nv.utils.initOptions(nvtooltip);
|
paulo@89
|
902 return nvtooltip;
|
paulo@89
|
903 };
|
paulo@89
|
904
|
paulo@89
|
905 })();
|
paulo@89
|
906
|
paulo@89
|
907
|
paulo@89
|
908 /*
|
paulo@89
|
909 Gets the browser window size
|
paulo@89
|
910
|
paulo@89
|
911 Returns object with height and width properties
|
paulo@89
|
912 */
|
paulo@89
|
913 nv.utils.windowSize = function() {
|
paulo@89
|
914 // Sane defaults
|
paulo@89
|
915 var size = {width: 640, height: 480};
|
paulo@89
|
916
|
paulo@89
|
917 // Most recent browsers use
|
paulo@89
|
918 if (window.innerWidth && window.innerHeight) {
|
paulo@89
|
919 size.width = window.innerWidth;
|
paulo@89
|
920 size.height = window.innerHeight;
|
paulo@89
|
921 return (size);
|
paulo@89
|
922 }
|
paulo@89
|
923
|
paulo@89
|
924 // IE can use depending on mode it is in
|
paulo@89
|
925 if (document.compatMode=='CSS1Compat' &&
|
paulo@89
|
926 document.documentElement &&
|
paulo@89
|
927 document.documentElement.offsetWidth ) {
|
paulo@89
|
928
|
paulo@89
|
929 size.width = document.documentElement.offsetWidth;
|
paulo@89
|
930 size.height = document.documentElement.offsetHeight;
|
paulo@89
|
931 return (size);
|
paulo@89
|
932 }
|
paulo@89
|
933
|
paulo@89
|
934 // Earlier IE uses Doc.body
|
paulo@89
|
935 if (document.body && document.body.offsetWidth) {
|
paulo@89
|
936 size.width = document.body.offsetWidth;
|
paulo@89
|
937 size.height = document.body.offsetHeight;
|
paulo@89
|
938 return (size);
|
paulo@89
|
939 }
|
paulo@89
|
940
|
paulo@89
|
941 return (size);
|
paulo@89
|
942 };
|
paulo@89
|
943
|
paulo@89
|
944 /*
|
paulo@89
|
945 Binds callback function to run when window is resized
|
paulo@89
|
946 */
|
paulo@89
|
947 nv.utils.windowResize = function(handler) {
|
paulo@89
|
948 if (window.addEventListener) {
|
paulo@89
|
949 window.addEventListener('resize', handler);
|
paulo@89
|
950 } else {
|
paulo@89
|
951 nv.log("ERROR: Failed to bind to window.resize with: ", handler);
|
paulo@89
|
952 }
|
paulo@89
|
953 // return object with clear function to remove the single added callback.
|
paulo@89
|
954 return {
|
paulo@89
|
955 callback: handler,
|
paulo@89
|
956 clear: function() {
|
paulo@89
|
957 window.removeEventListener('resize', handler);
|
paulo@89
|
958 }
|
paulo@89
|
959 }
|
paulo@89
|
960 };
|
paulo@89
|
961
|
paulo@89
|
962
|
paulo@89
|
963 /*
|
paulo@89
|
964 Backwards compatible way to implement more d3-like coloring of graphs.
|
paulo@89
|
965 Can take in nothing, an array, or a function/scale
|
paulo@89
|
966 To use a normal scale, get the range and pass that because we must be able
|
paulo@89
|
967 to take two arguments and use the index to keep backward compatibility
|
paulo@89
|
968 */
|
paulo@89
|
969 nv.utils.getColor = function(color) {
|
paulo@89
|
970 //if you pass in nothing, get default colors back
|
paulo@89
|
971 if (color === undefined) {
|
paulo@89
|
972 return nv.utils.defaultColor();
|
paulo@89
|
973
|
paulo@89
|
974 //if passed an array, turn it into a color scale
|
paulo@89
|
975 // use isArray, instanceof fails if d3 range is created in an iframe
|
paulo@89
|
976 } else if(Array.isArray(color)) {
|
paulo@89
|
977 var color_scale = d3.scale.ordinal().range(color);
|
paulo@89
|
978 return function(d, i) {
|
paulo@89
|
979 var key = i === undefined ? d : i;
|
paulo@89
|
980 return d.color || color_scale(key);
|
paulo@89
|
981 };
|
paulo@89
|
982
|
paulo@89
|
983 //if passed a function or scale, return it, or whatever it may be
|
paulo@89
|
984 //external libs, such as angularjs-nvd3-directives use this
|
paulo@89
|
985 } else {
|
paulo@89
|
986 //can't really help it if someone passes rubbish as color
|
paulo@89
|
987 return color;
|
paulo@89
|
988 }
|
paulo@89
|
989 };
|
paulo@89
|
990
|
paulo@89
|
991
|
paulo@89
|
992 /*
|
paulo@89
|
993 Default color chooser uses a color scale of 20 colors from D3
|
paulo@89
|
994 https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
|
paulo@89
|
995 */
|
paulo@89
|
996 nv.utils.defaultColor = function() {
|
paulo@89
|
997 // get range of the scale so we'll turn it into our own function.
|
paulo@89
|
998 return nv.utils.getColor(d3.scale.category20().range());
|
paulo@89
|
999 };
|
paulo@89
|
1000
|
paulo@89
|
1001
|
paulo@89
|
1002 /*
|
paulo@89
|
1003 Returns a color function that takes the result of 'getKey' for each series and
|
paulo@89
|
1004 looks for a corresponding color from the dictionary
|
paulo@89
|
1005 */
|
paulo@89
|
1006 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
|
paulo@89
|
1007 // use default series.key if getKey is undefined
|
paulo@89
|
1008 getKey = getKey || function(series) { return series.key };
|
paulo@89
|
1009 defaultColors = defaultColors || d3.scale.category20().range();
|
paulo@89
|
1010
|
paulo@89
|
1011 // start at end of default color list and walk back to index 0
|
paulo@89
|
1012 var defIndex = defaultColors.length;
|
paulo@89
|
1013
|
paulo@89
|
1014 return function(series, index) {
|
paulo@89
|
1015 var key = getKey(series);
|
paulo@89
|
1016 if (typeof dictionary[key] === 'function') {
|
paulo@89
|
1017 return dictionary[key]();
|
paulo@89
|
1018 } else if (dictionary[key] !== undefined) {
|
paulo@89
|
1019 return dictionary[key];
|
paulo@89
|
1020 } else {
|
paulo@89
|
1021 // no match in dictionary, use a default color
|
paulo@89
|
1022 if (!defIndex) {
|
paulo@89
|
1023 // used all the default colors, start over
|
paulo@89
|
1024 defIndex = defaultColors.length;
|
paulo@89
|
1025 }
|
paulo@89
|
1026 defIndex = defIndex - 1;
|
paulo@89
|
1027 return defaultColors[defIndex];
|
paulo@89
|
1028 }
|
paulo@89
|
1029 };
|
paulo@89
|
1030 };
|
paulo@89
|
1031
|
paulo@89
|
1032
|
paulo@89
|
1033 /*
|
paulo@89
|
1034 From the PJAX example on d3js.org, while this is not really directly needed
|
paulo@89
|
1035 it's a very cool method for doing pjax, I may expand upon it a little bit,
|
paulo@89
|
1036 open to suggestions on anything that may be useful
|
paulo@89
|
1037 */
|
paulo@89
|
1038 nv.utils.pjax = function(links, content) {
|
paulo@89
|
1039
|
paulo@89
|
1040 var load = function(href) {
|
paulo@89
|
1041 d3.html(href, function(fragment) {
|
paulo@89
|
1042 var target = d3.select(content).node();
|
paulo@89
|
1043 target.parentNode.replaceChild(
|
paulo@89
|
1044 d3.select(fragment).select(content).node(),
|
paulo@89
|
1045 target);
|
paulo@89
|
1046 nv.utils.pjax(links, content);
|
paulo@89
|
1047 });
|
paulo@89
|
1048 };
|
paulo@89
|
1049
|
paulo@89
|
1050 d3.selectAll(links).on("click", function() {
|
paulo@89
|
1051 history.pushState(this.href, this.textContent, this.href);
|
paulo@89
|
1052 load(this.href);
|
paulo@89
|
1053 d3.event.preventDefault();
|
paulo@89
|
1054 });
|
paulo@89
|
1055
|
paulo@89
|
1056 d3.select(window).on("popstate", function() {
|
paulo@89
|
1057 if (d3.event.state) {
|
paulo@89
|
1058 load(d3.event.state);
|
paulo@89
|
1059 }
|
paulo@89
|
1060 });
|
paulo@89
|
1061 };
|
paulo@89
|
1062
|
paulo@89
|
1063
|
paulo@89
|
1064 /*
|
paulo@89
|
1065 For when we want to approximate the width in pixels for an SVG:text element.
|
paulo@89
|
1066 Most common instance is when the element is in a display:none; container.
|
paulo@89
|
1067 Forumla is : text.length * font-size * constant_factor
|
paulo@89
|
1068 */
|
paulo@89
|
1069 nv.utils.calcApproxTextWidth = function (svgTextElem) {
|
paulo@89
|
1070 if (typeof svgTextElem.style === 'function'
|
paulo@89
|
1071 && typeof svgTextElem.text === 'function') {
|
paulo@89
|
1072
|
paulo@89
|
1073 var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
|
paulo@89
|
1074 var textLength = svgTextElem.text().length;
|
paulo@89
|
1075 return textLength * fontSize * 0.5;
|
paulo@89
|
1076 }
|
paulo@89
|
1077 return 0;
|
paulo@89
|
1078 };
|
paulo@89
|
1079
|
paulo@89
|
1080
|
paulo@89
|
1081 /*
|
paulo@89
|
1082 Numbers that are undefined, null or NaN, convert them to zeros.
|
paulo@89
|
1083 */
|
paulo@89
|
1084 nv.utils.NaNtoZero = function(n) {
|
paulo@89
|
1085 if (typeof n !== 'number'
|
paulo@89
|
1086 || isNaN(n)
|
paulo@89
|
1087 || n === null
|
paulo@89
|
1088 || n === Infinity
|
paulo@89
|
1089 || n === -Infinity) {
|
paulo@89
|
1090
|
paulo@89
|
1091 return 0;
|
paulo@89
|
1092 }
|
paulo@89
|
1093 return n;
|
paulo@89
|
1094 };
|
paulo@89
|
1095
|
paulo@89
|
1096 /*
|
paulo@89
|
1097 Add a way to watch for d3 transition ends to d3
|
paulo@89
|
1098 */
|
paulo@89
|
1099 d3.selection.prototype.watchTransition = function(renderWatch){
|
paulo@89
|
1100 var args = [this].concat([].slice.call(arguments, 1));
|
paulo@89
|
1101 return renderWatch.transition.apply(renderWatch, args);
|
paulo@89
|
1102 };
|
paulo@89
|
1103
|
paulo@89
|
1104
|
paulo@89
|
1105 /*
|
paulo@89
|
1106 Helper object to watch when d3 has rendered something
|
paulo@89
|
1107 */
|
paulo@89
|
1108 nv.utils.renderWatch = function(dispatch, duration) {
|
paulo@89
|
1109 if (!(this instanceof nv.utils.renderWatch)) {
|
paulo@89
|
1110 return new nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
1111 }
|
paulo@89
|
1112
|
paulo@89
|
1113 var _duration = duration !== undefined ? duration : 250;
|
paulo@89
|
1114 var renderStack = [];
|
paulo@89
|
1115 var self = this;
|
paulo@89
|
1116
|
paulo@89
|
1117 this.models = function(models) {
|
paulo@89
|
1118 models = [].slice.call(arguments, 0);
|
paulo@89
|
1119 models.forEach(function(model){
|
paulo@89
|
1120 model.__rendered = false;
|
paulo@89
|
1121 (function(m){
|
paulo@89
|
1122 m.dispatch.on('renderEnd', function(arg){
|
paulo@89
|
1123 m.__rendered = true;
|
paulo@89
|
1124 self.renderEnd('model');
|
paulo@89
|
1125 });
|
paulo@89
|
1126 })(model);
|
paulo@89
|
1127
|
paulo@89
|
1128 if (renderStack.indexOf(model) < 0) {
|
paulo@89
|
1129 renderStack.push(model);
|
paulo@89
|
1130 }
|
paulo@89
|
1131 });
|
paulo@89
|
1132 return this;
|
paulo@89
|
1133 };
|
paulo@89
|
1134
|
paulo@89
|
1135 this.reset = function(duration) {
|
paulo@89
|
1136 if (duration !== undefined) {
|
paulo@89
|
1137 _duration = duration;
|
paulo@89
|
1138 }
|
paulo@89
|
1139 renderStack = [];
|
paulo@89
|
1140 };
|
paulo@89
|
1141
|
paulo@89
|
1142 this.transition = function(selection, args, duration) {
|
paulo@89
|
1143 args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
|
paulo@89
|
1144
|
paulo@89
|
1145 if (args.length > 1) {
|
paulo@89
|
1146 duration = args.pop();
|
paulo@89
|
1147 } else {
|
paulo@89
|
1148 duration = _duration !== undefined ? _duration : 250;
|
paulo@89
|
1149 }
|
paulo@89
|
1150 selection.__rendered = false;
|
paulo@89
|
1151
|
paulo@89
|
1152 if (renderStack.indexOf(selection) < 0) {
|
paulo@89
|
1153 renderStack.push(selection);
|
paulo@89
|
1154 }
|
paulo@89
|
1155
|
paulo@89
|
1156 if (duration === 0) {
|
paulo@89
|
1157 selection.__rendered = true;
|
paulo@89
|
1158 selection.delay = function() { return this; };
|
paulo@89
|
1159 selection.duration = function() { return this; };
|
paulo@89
|
1160 return selection;
|
paulo@89
|
1161 } else {
|
paulo@89
|
1162 if (selection.length === 0) {
|
paulo@89
|
1163 selection.__rendered = true;
|
paulo@89
|
1164 } else if (selection.every( function(d){ return !d.length; } )) {
|
paulo@89
|
1165 selection.__rendered = true;
|
paulo@89
|
1166 } else {
|
paulo@89
|
1167 selection.__rendered = false;
|
paulo@89
|
1168 }
|
paulo@89
|
1169
|
paulo@89
|
1170 var n = 0;
|
paulo@89
|
1171 return selection
|
paulo@89
|
1172 .transition()
|
paulo@89
|
1173 .duration(duration)
|
paulo@89
|
1174 .each(function(){ ++n; })
|
paulo@89
|
1175 .each('end', function(d, i) {
|
paulo@89
|
1176 if (--n === 0) {
|
paulo@89
|
1177 selection.__rendered = true;
|
paulo@89
|
1178 self.renderEnd.apply(this, args);
|
paulo@89
|
1179 }
|
paulo@89
|
1180 });
|
paulo@89
|
1181 }
|
paulo@89
|
1182 };
|
paulo@89
|
1183
|
paulo@89
|
1184 this.renderEnd = function() {
|
paulo@89
|
1185 if (renderStack.every( function(d){ return d.__rendered; } )) {
|
paulo@89
|
1186 renderStack.forEach( function(d){ d.__rendered = false; });
|
paulo@89
|
1187 dispatch.renderEnd.apply(this, arguments);
|
paulo@89
|
1188 }
|
paulo@89
|
1189 }
|
paulo@89
|
1190
|
paulo@89
|
1191 };
|
paulo@89
|
1192
|
paulo@89
|
1193
|
paulo@89
|
1194 /*
|
paulo@89
|
1195 Takes multiple objects and combines them into the first one (dst)
|
paulo@89
|
1196 example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
|
paulo@89
|
1197 gives: {a: 2, b: 3, c: 4}
|
paulo@89
|
1198 */
|
paulo@89
|
1199 nv.utils.deepExtend = function(dst){
|
paulo@89
|
1200 var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
|
paulo@89
|
1201 sources.forEach(function(source) {
|
paulo@89
|
1202 for (var key in source) {
|
paulo@89
|
1203 var isArray = dst[key] instanceof Array;
|
paulo@89
|
1204 var isObject = typeof dst[key] === 'object';
|
paulo@89
|
1205 var srcObj = typeof source[key] === 'object';
|
paulo@89
|
1206
|
paulo@89
|
1207 if (isObject && !isArray && srcObj) {
|
paulo@89
|
1208 nv.utils.deepExtend(dst[key], source[key]);
|
paulo@89
|
1209 } else {
|
paulo@89
|
1210 dst[key] = source[key];
|
paulo@89
|
1211 }
|
paulo@89
|
1212 }
|
paulo@89
|
1213 });
|
paulo@89
|
1214 };
|
paulo@89
|
1215
|
paulo@89
|
1216
|
paulo@89
|
1217 /*
|
paulo@89
|
1218 state utility object, used to track d3 states in the models
|
paulo@89
|
1219 */
|
paulo@89
|
1220 nv.utils.state = function(){
|
paulo@89
|
1221 if (!(this instanceof nv.utils.state)) {
|
paulo@89
|
1222 return new nv.utils.state();
|
paulo@89
|
1223 }
|
paulo@89
|
1224 var state = {};
|
paulo@89
|
1225 var _self = this;
|
paulo@89
|
1226 var _setState = function(){};
|
paulo@89
|
1227 var _getState = function(){ return {}; };
|
paulo@89
|
1228 var init = null;
|
paulo@89
|
1229 var changed = null;
|
paulo@89
|
1230
|
paulo@89
|
1231 this.dispatch = d3.dispatch('change', 'set');
|
paulo@89
|
1232
|
paulo@89
|
1233 this.dispatch.on('set', function(state){
|
paulo@89
|
1234 _setState(state, true);
|
paulo@89
|
1235 });
|
paulo@89
|
1236
|
paulo@89
|
1237 this.getter = function(fn){
|
paulo@89
|
1238 _getState = fn;
|
paulo@89
|
1239 return this;
|
paulo@89
|
1240 };
|
paulo@89
|
1241
|
paulo@89
|
1242 this.setter = function(fn, callback) {
|
paulo@89
|
1243 if (!callback) {
|
paulo@89
|
1244 callback = function(){};
|
paulo@89
|
1245 }
|
paulo@89
|
1246 _setState = function(state, update){
|
paulo@89
|
1247 fn(state);
|
paulo@89
|
1248 if (update) {
|
paulo@89
|
1249 callback();
|
paulo@89
|
1250 }
|
paulo@89
|
1251 };
|
paulo@89
|
1252 return this;
|
paulo@89
|
1253 };
|
paulo@89
|
1254
|
paulo@89
|
1255 this.init = function(state){
|
paulo@89
|
1256 init = init || {};
|
paulo@89
|
1257 nv.utils.deepExtend(init, state);
|
paulo@89
|
1258 };
|
paulo@89
|
1259
|
paulo@89
|
1260 var _set = function(){
|
paulo@89
|
1261 var settings = _getState();
|
paulo@89
|
1262
|
paulo@89
|
1263 if (JSON.stringify(settings) === JSON.stringify(state)) {
|
paulo@89
|
1264 return false;
|
paulo@89
|
1265 }
|
paulo@89
|
1266
|
paulo@89
|
1267 for (var key in settings) {
|
paulo@89
|
1268 if (state[key] === undefined) {
|
paulo@89
|
1269 state[key] = {};
|
paulo@89
|
1270 }
|
paulo@89
|
1271 state[key] = settings[key];
|
paulo@89
|
1272 changed = true;
|
paulo@89
|
1273 }
|
paulo@89
|
1274 return true;
|
paulo@89
|
1275 };
|
paulo@89
|
1276
|
paulo@89
|
1277 this.update = function(){
|
paulo@89
|
1278 if (init) {
|
paulo@89
|
1279 _setState(init, false);
|
paulo@89
|
1280 init = null;
|
paulo@89
|
1281 }
|
paulo@89
|
1282 if (_set.call(this)) {
|
paulo@89
|
1283 this.dispatch.change(state);
|
paulo@89
|
1284 }
|
paulo@89
|
1285 };
|
paulo@89
|
1286
|
paulo@89
|
1287 };
|
paulo@89
|
1288
|
paulo@89
|
1289
|
paulo@89
|
1290 /*
|
paulo@89
|
1291 Snippet of code you can insert into each nv.models.* to give you the ability to
|
paulo@89
|
1292 do things like:
|
paulo@89
|
1293 chart.options({
|
paulo@89
|
1294 showXAxis: true,
|
paulo@89
|
1295 tooltips: true
|
paulo@89
|
1296 });
|
paulo@89
|
1297
|
paulo@89
|
1298 To enable in the chart:
|
paulo@89
|
1299 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
1300 */
|
paulo@89
|
1301 nv.utils.optionsFunc = function(args) {
|
paulo@89
|
1302 if (args) {
|
paulo@89
|
1303 d3.map(args).forEach((function(key,value) {
|
paulo@89
|
1304 if (typeof this[key] === "function") {
|
paulo@89
|
1305 this[key](value);
|
paulo@89
|
1306 }
|
paulo@89
|
1307 }).bind(this));
|
paulo@89
|
1308 }
|
paulo@89
|
1309 return this;
|
paulo@89
|
1310 };
|
paulo@89
|
1311
|
paulo@89
|
1312
|
paulo@89
|
1313 /*
|
paulo@89
|
1314 numTicks: requested number of ticks
|
paulo@89
|
1315 data: the chart data
|
paulo@89
|
1316
|
paulo@89
|
1317 returns the number of ticks to actually use on X axis, based on chart data
|
paulo@89
|
1318 to avoid duplicate ticks with the same value
|
paulo@89
|
1319 */
|
paulo@89
|
1320 nv.utils.calcTicksX = function(numTicks, data) {
|
paulo@89
|
1321 // find max number of values from all data streams
|
paulo@89
|
1322 var numValues = 1;
|
paulo@89
|
1323 var i = 0;
|
paulo@89
|
1324 for (i; i < data.length; i += 1) {
|
paulo@89
|
1325 var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
|
paulo@89
|
1326 numValues = stream_len > numValues ? stream_len : numValues;
|
paulo@89
|
1327 }
|
paulo@89
|
1328 nv.log("Requested number of ticks: ", numTicks);
|
paulo@89
|
1329 nv.log("Calculated max values to be: ", numValues);
|
paulo@89
|
1330 // make sure we don't have more ticks than values to avoid duplicates
|
paulo@89
|
1331 numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
|
paulo@89
|
1332 // make sure we have at least one tick
|
paulo@89
|
1333 numTicks = numTicks < 1 ? 1 : numTicks;
|
paulo@89
|
1334 // make sure it's an integer
|
paulo@89
|
1335 numTicks = Math.floor(numTicks);
|
paulo@89
|
1336 nv.log("Calculating tick count as: ", numTicks);
|
paulo@89
|
1337 return numTicks;
|
paulo@89
|
1338 };
|
paulo@89
|
1339
|
paulo@89
|
1340
|
paulo@89
|
1341 /*
|
paulo@89
|
1342 returns number of ticks to actually use on Y axis, based on chart data
|
paulo@89
|
1343 */
|
paulo@89
|
1344 nv.utils.calcTicksY = function(numTicks, data) {
|
paulo@89
|
1345 // currently uses the same logic but we can adjust here if needed later
|
paulo@89
|
1346 return nv.utils.calcTicksX(numTicks, data);
|
paulo@89
|
1347 };
|
paulo@89
|
1348
|
paulo@89
|
1349
|
paulo@89
|
1350 /*
|
paulo@89
|
1351 Add a particular option from an options object onto chart
|
paulo@89
|
1352 Options exposed on a chart are a getter/setter function that returns chart
|
paulo@89
|
1353 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
|
paulo@89
|
1354
|
paulo@89
|
1355 option objects should be generated via Object.create() to provide
|
paulo@89
|
1356 the option of manipulating data via get/set functions.
|
paulo@89
|
1357 */
|
paulo@89
|
1358 nv.utils.initOption = function(chart, name) {
|
paulo@89
|
1359 // if it's a call option, just call it directly, otherwise do get/set
|
paulo@89
|
1360 if (chart._calls && chart._calls[name]) {
|
paulo@89
|
1361 chart[name] = chart._calls[name];
|
paulo@89
|
1362 } else {
|
paulo@89
|
1363 chart[name] = function (_) {
|
paulo@89
|
1364 if (!arguments.length) return chart._options[name];
|
paulo@89
|
1365 chart._overrides[name] = true;
|
paulo@89
|
1366 chart._options[name] = _;
|
paulo@89
|
1367 return chart;
|
paulo@89
|
1368 };
|
paulo@89
|
1369 // calling the option as _option will ignore if set by option already
|
paulo@89
|
1370 // so nvd3 can set options internally but the stop if set manually
|
paulo@89
|
1371 chart['_' + name] = function(_) {
|
paulo@89
|
1372 if (!arguments.length) return chart._options[name];
|
paulo@89
|
1373 if (!chart._overrides[name]) {
|
paulo@89
|
1374 chart._options[name] = _;
|
paulo@89
|
1375 }
|
paulo@89
|
1376 return chart;
|
paulo@89
|
1377 }
|
paulo@89
|
1378 }
|
paulo@89
|
1379 };
|
paulo@89
|
1380
|
paulo@89
|
1381
|
paulo@89
|
1382 /*
|
paulo@89
|
1383 Add all options in an options object to the chart
|
paulo@89
|
1384 */
|
paulo@89
|
1385 nv.utils.initOptions = function(chart) {
|
paulo@89
|
1386 chart._overrides = chart._overrides || {};
|
paulo@89
|
1387 var ops = Object.getOwnPropertyNames(chart._options || {});
|
paulo@89
|
1388 var calls = Object.getOwnPropertyNames(chart._calls || {});
|
paulo@89
|
1389 ops = ops.concat(calls);
|
paulo@89
|
1390 for (var i in ops) {
|
paulo@89
|
1391 nv.utils.initOption(chart, ops[i]);
|
paulo@89
|
1392 }
|
paulo@89
|
1393 };
|
paulo@89
|
1394
|
paulo@89
|
1395
|
paulo@89
|
1396 /*
|
paulo@89
|
1397 Inherit options from a D3 object
|
paulo@89
|
1398 d3.rebind makes calling the function on target actually call it on source
|
paulo@89
|
1399 Also use _d3options so we can track what we inherit for documentation and chained inheritance
|
paulo@89
|
1400 */
|
paulo@89
|
1401 nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
|
paulo@89
|
1402 target._d3options = oplist.concat(target._d3options || []);
|
paulo@89
|
1403 oplist.unshift(d3_source);
|
paulo@89
|
1404 oplist.unshift(target);
|
paulo@89
|
1405 d3.rebind.apply(this, oplist);
|
paulo@89
|
1406 };
|
paulo@89
|
1407
|
paulo@89
|
1408
|
paulo@89
|
1409 /*
|
paulo@89
|
1410 Remove duplicates from an array
|
paulo@89
|
1411 */
|
paulo@89
|
1412 nv.utils.arrayUnique = function(a) {
|
paulo@89
|
1413 return a.sort().filter(function(item, pos) {
|
paulo@89
|
1414 return !pos || item != a[pos - 1];
|
paulo@89
|
1415 });
|
paulo@89
|
1416 };
|
paulo@89
|
1417
|
paulo@89
|
1418
|
paulo@89
|
1419 /*
|
paulo@89
|
1420 Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
|
paulo@89
|
1421 Necessary since d3 doesn't let you extend its list -_-
|
paulo@89
|
1422 Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
|
paulo@89
|
1423 */
|
paulo@89
|
1424 nv.utils.symbolMap = d3.map();
|
paulo@89
|
1425
|
paulo@89
|
1426
|
paulo@89
|
1427 /*
|
paulo@89
|
1428 Replaces d3.svg.symbol so that we can look both there and our own map
|
paulo@89
|
1429 */
|
paulo@89
|
1430 nv.utils.symbol = function() {
|
paulo@89
|
1431 var type,
|
paulo@89
|
1432 size = 64;
|
paulo@89
|
1433 function symbol(d,i) {
|
paulo@89
|
1434 var t = type.call(this,d,i);
|
paulo@89
|
1435 var s = size.call(this,d,i);
|
paulo@89
|
1436 if (d3.svg.symbolTypes.indexOf(t) !== -1) {
|
paulo@89
|
1437 return d3.svg.symbol().type(t).size(s)();
|
paulo@89
|
1438 } else {
|
paulo@89
|
1439 return nv.utils.symbolMap.get(t)(s);
|
paulo@89
|
1440 }
|
paulo@89
|
1441 }
|
paulo@89
|
1442 symbol.type = function(_) {
|
paulo@89
|
1443 if (!arguments.length) return type;
|
paulo@89
|
1444 type = d3.functor(_);
|
paulo@89
|
1445 return symbol;
|
paulo@89
|
1446 };
|
paulo@89
|
1447 symbol.size = function(_) {
|
paulo@89
|
1448 if (!arguments.length) return size;
|
paulo@89
|
1449 size = d3.functor(_);
|
paulo@89
|
1450 return symbol;
|
paulo@89
|
1451 };
|
paulo@89
|
1452 return symbol;
|
paulo@89
|
1453 };
|
paulo@89
|
1454
|
paulo@89
|
1455
|
paulo@89
|
1456 /*
|
paulo@89
|
1457 Inherit option getter/setter functions from source to target
|
paulo@89
|
1458 d3.rebind makes calling the function on target actually call it on source
|
paulo@89
|
1459 Also track via _inherited and _d3options so we can track what we inherit
|
paulo@89
|
1460 for documentation generation purposes and chained inheritance
|
paulo@89
|
1461 */
|
paulo@89
|
1462 nv.utils.inheritOptions = function(target, source) {
|
paulo@89
|
1463 // inherit all the things
|
paulo@89
|
1464 var ops = Object.getOwnPropertyNames(source._options || {});
|
paulo@89
|
1465 var calls = Object.getOwnPropertyNames(source._calls || {});
|
paulo@89
|
1466 var inherited = source._inherited || [];
|
paulo@89
|
1467 var d3ops = source._d3options || [];
|
paulo@89
|
1468 var args = ops.concat(calls).concat(inherited).concat(d3ops);
|
paulo@89
|
1469 args.unshift(source);
|
paulo@89
|
1470 args.unshift(target);
|
paulo@89
|
1471 d3.rebind.apply(this, args);
|
paulo@89
|
1472 // pass along the lists to keep track of them, don't allow duplicates
|
paulo@89
|
1473 target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
|
paulo@89
|
1474 target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
|
paulo@89
|
1475 };
|
paulo@89
|
1476
|
paulo@89
|
1477
|
paulo@89
|
1478 /*
|
paulo@89
|
1479 Runs common initialize code on the svg before the chart builds
|
paulo@89
|
1480 */
|
paulo@89
|
1481 nv.utils.initSVG = function(svg) {
|
paulo@89
|
1482 svg.classed({'nvd3-svg':true});
|
paulo@89
|
1483 };
|
paulo@89
|
1484
|
paulo@89
|
1485
|
paulo@89
|
1486 /*
|
paulo@89
|
1487 Sanitize and provide default for the container height.
|
paulo@89
|
1488 */
|
paulo@89
|
1489 nv.utils.sanitizeHeight = function(height, container) {
|
paulo@89
|
1490 return (height || parseInt(container.style('height'), 10) || 400);
|
paulo@89
|
1491 };
|
paulo@89
|
1492
|
paulo@89
|
1493
|
paulo@89
|
1494 /*
|
paulo@89
|
1495 Sanitize and provide default for the container width.
|
paulo@89
|
1496 */
|
paulo@89
|
1497 nv.utils.sanitizeWidth = function(width, container) {
|
paulo@89
|
1498 return (width || parseInt(container.style('width'), 10) || 960);
|
paulo@89
|
1499 };
|
paulo@89
|
1500
|
paulo@89
|
1501
|
paulo@89
|
1502 /*
|
paulo@89
|
1503 Calculate the available height for a chart.
|
paulo@89
|
1504 */
|
paulo@89
|
1505 nv.utils.availableHeight = function(height, container, margin) {
|
paulo@89
|
1506 return nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom;
|
paulo@89
|
1507 };
|
paulo@89
|
1508
|
paulo@89
|
1509 /*
|
paulo@89
|
1510 Calculate the available width for a chart.
|
paulo@89
|
1511 */
|
paulo@89
|
1512 nv.utils.availableWidth = function(width, container, margin) {
|
paulo@89
|
1513 return nv.utils.sanitizeWidth(width, container) - margin.left - margin.right;
|
paulo@89
|
1514 };
|
paulo@89
|
1515
|
paulo@89
|
1516 /*
|
paulo@89
|
1517 Clear any rendered chart components and display a chart's 'noData' message
|
paulo@89
|
1518 */
|
paulo@89
|
1519 nv.utils.noData = function(chart, container) {
|
paulo@89
|
1520 var opt = chart.options(),
|
paulo@89
|
1521 margin = opt.margin(),
|
paulo@89
|
1522 noData = opt.noData(),
|
paulo@89
|
1523 data = (noData == null) ? ["No Data Available."] : [noData],
|
paulo@89
|
1524 height = nv.utils.availableHeight(opt.height(), container, margin),
|
paulo@89
|
1525 width = nv.utils.availableWidth(opt.width(), container, margin),
|
paulo@89
|
1526 x = margin.left + width/2,
|
paulo@89
|
1527 y = margin.top + height/2;
|
paulo@89
|
1528
|
paulo@89
|
1529 //Remove any previously created chart components
|
paulo@89
|
1530 container.selectAll('g').remove();
|
paulo@89
|
1531
|
paulo@89
|
1532 var noDataText = container.selectAll('.nv-noData').data(data);
|
paulo@89
|
1533
|
paulo@89
|
1534 noDataText.enter().append('text')
|
paulo@89
|
1535 .attr('class', 'nvd3 nv-noData')
|
paulo@89
|
1536 .attr('dy', '-.7em')
|
paulo@89
|
1537 .style('text-anchor', 'middle');
|
paulo@89
|
1538
|
paulo@89
|
1539 noDataText
|
paulo@89
|
1540 .attr('x', x)
|
paulo@89
|
1541 .attr('y', y)
|
paulo@89
|
1542 .text(function(t){ return t; });
|
paulo@89
|
1543 };
|
paulo@89
|
1544
|
paulo@89
|
1545 nv.models.axis = function() {
|
paulo@89
|
1546 "use strict";
|
paulo@89
|
1547
|
paulo@89
|
1548 //============================================================
|
paulo@89
|
1549 // Public Variables with Default Settings
|
paulo@89
|
1550 //------------------------------------------------------------
|
paulo@89
|
1551
|
paulo@89
|
1552 var axis = d3.svg.axis();
|
paulo@89
|
1553 var scale = d3.scale.linear();
|
paulo@89
|
1554
|
paulo@89
|
1555 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
1556 , width = 75 //only used for tickLabel currently
|
paulo@89
|
1557 , height = 60 //only used for tickLabel currently
|
paulo@89
|
1558 , axisLabelText = null
|
paulo@89
|
1559 , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
|
paulo@89
|
1560 , rotateLabels = 0
|
paulo@89
|
1561 , rotateYLabel = true
|
paulo@89
|
1562 , staggerLabels = false
|
paulo@89
|
1563 , isOrdinal = false
|
paulo@89
|
1564 , ticks = null
|
paulo@89
|
1565 , axisLabelDistance = 0
|
paulo@89
|
1566 , duration = 250
|
paulo@89
|
1567 , dispatch = d3.dispatch('renderEnd')
|
paulo@89
|
1568 ;
|
paulo@89
|
1569 axis
|
paulo@89
|
1570 .scale(scale)
|
paulo@89
|
1571 .orient('bottom')
|
paulo@89
|
1572 .tickFormat(function(d) { return d })
|
paulo@89
|
1573 ;
|
paulo@89
|
1574
|
paulo@89
|
1575 //============================================================
|
paulo@89
|
1576 // Private Variables
|
paulo@89
|
1577 //------------------------------------------------------------
|
paulo@89
|
1578
|
paulo@89
|
1579 var scale0;
|
paulo@89
|
1580 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
1581
|
paulo@89
|
1582 function chart(selection) {
|
paulo@89
|
1583 renderWatch.reset();
|
paulo@89
|
1584 selection.each(function(data) {
|
paulo@89
|
1585 var container = d3.select(this);
|
paulo@89
|
1586 nv.utils.initSVG(container);
|
paulo@89
|
1587
|
paulo@89
|
1588 // Setup containers and skeleton of chart
|
paulo@89
|
1589 var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
|
paulo@89
|
1590 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
|
paulo@89
|
1591 var gEnter = wrapEnter.append('g');
|
paulo@89
|
1592 var g = wrap.select('g');
|
paulo@89
|
1593
|
paulo@89
|
1594 if (ticks !== null)
|
paulo@89
|
1595 axis.ticks(ticks);
|
paulo@89
|
1596 else if (axis.orient() == 'top' || axis.orient() == 'bottom')
|
paulo@89
|
1597 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
|
paulo@89
|
1598
|
paulo@89
|
1599 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
|
paulo@89
|
1600 g.watchTransition(renderWatch, 'axis').call(axis);
|
paulo@89
|
1601
|
paulo@89
|
1602 scale0 = scale0 || axis.scale();
|
paulo@89
|
1603
|
paulo@89
|
1604 var fmt = axis.tickFormat();
|
paulo@89
|
1605 if (fmt == null) {
|
paulo@89
|
1606 fmt = scale0.tickFormat();
|
paulo@89
|
1607 }
|
paulo@89
|
1608
|
paulo@89
|
1609 var axisLabel = g.selectAll('text.nv-axislabel')
|
paulo@89
|
1610 .data([axisLabelText || null]);
|
paulo@89
|
1611 axisLabel.exit().remove();
|
paulo@89
|
1612
|
paulo@89
|
1613 var xLabelMargin;
|
paulo@89
|
1614 var axisMaxMin;
|
paulo@89
|
1615 var w;
|
paulo@89
|
1616 switch (axis.orient()) {
|
paulo@89
|
1617 case 'top':
|
paulo@89
|
1618 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
|
paulo@89
|
1619 if (scale.range().length < 2) {
|
paulo@89
|
1620 w = 0;
|
paulo@89
|
1621 } else if (scale.range().length === 2) {
|
paulo@89
|
1622 w = scale.range()[1];
|
paulo@89
|
1623 } else {
|
paulo@89
|
1624 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
|
paulo@89
|
1625 }
|
paulo@89
|
1626 axisLabel
|
paulo@89
|
1627 .attr('text-anchor', 'middle')
|
paulo@89
|
1628 .attr('y', 0)
|
paulo@89
|
1629 .attr('x', w/2);
|
paulo@89
|
1630 if (showMaxMin) {
|
paulo@89
|
1631 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
|
paulo@89
|
1632 .data(scale.domain());
|
paulo@89
|
1633 axisMaxMin.enter().append('g').attr('class',function(d,i){
|
paulo@89
|
1634 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
|
paulo@89
|
1635 }).append('text');
|
paulo@89
|
1636 axisMaxMin.exit().remove();
|
paulo@89
|
1637 axisMaxMin
|
paulo@89
|
1638 .attr('transform', function(d,i) {
|
paulo@89
|
1639 return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
|
paulo@89
|
1640 })
|
paulo@89
|
1641 .select('text')
|
paulo@89
|
1642 .attr('dy', '-0.5em')
|
paulo@89
|
1643 .attr('y', -axis.tickPadding())
|
paulo@89
|
1644 .attr('text-anchor', 'middle')
|
paulo@89
|
1645 .text(function(d,i) {
|
paulo@89
|
1646 var v = fmt(d);
|
paulo@89
|
1647 return ('' + v).match('NaN') ? '' : v;
|
paulo@89
|
1648 });
|
paulo@89
|
1649 axisMaxMin.watchTransition(renderWatch, 'min-max top')
|
paulo@89
|
1650 .attr('transform', function(d,i) {
|
paulo@89
|
1651 return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
|
paulo@89
|
1652 });
|
paulo@89
|
1653 }
|
paulo@89
|
1654 break;
|
paulo@89
|
1655 case 'bottom':
|
paulo@89
|
1656 xLabelMargin = axisLabelDistance + 36;
|
paulo@89
|
1657 var maxTextWidth = 30;
|
paulo@89
|
1658 var textHeight = 0;
|
paulo@89
|
1659 var xTicks = g.selectAll('g').select("text");
|
paulo@89
|
1660 var rotateLabelsRule = '';
|
paulo@89
|
1661 if (rotateLabels%360) {
|
paulo@89
|
1662 //Calculate the longest xTick width
|
paulo@89
|
1663 xTicks.each(function(d,i){
|
paulo@89
|
1664 var box = this.getBoundingClientRect();
|
paulo@89
|
1665 var width = box.width;
|
paulo@89
|
1666 textHeight = box.height;
|
paulo@89
|
1667 if(width > maxTextWidth) maxTextWidth = width;
|
paulo@89
|
1668 });
|
paulo@89
|
1669 rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')';
|
paulo@89
|
1670 //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
|
paulo@89
|
1671 var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
|
paulo@89
|
1672 xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
|
paulo@89
|
1673 //Rotate all xTicks
|
paulo@89
|
1674 xTicks
|
paulo@89
|
1675 .attr('transform', rotateLabelsRule)
|
paulo@89
|
1676 .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
|
paulo@89
|
1677 }
|
paulo@89
|
1678 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
|
paulo@89
|
1679 if (scale.range().length < 2) {
|
paulo@89
|
1680 w = 0;
|
paulo@89
|
1681 } else if (scale.range().length === 2) {
|
paulo@89
|
1682 w = scale.range()[1];
|
paulo@89
|
1683 } else {
|
paulo@89
|
1684 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
|
paulo@89
|
1685 }
|
paulo@89
|
1686 axisLabel
|
paulo@89
|
1687 .attr('text-anchor', 'middle')
|
paulo@89
|
1688 .attr('y', xLabelMargin)
|
paulo@89
|
1689 .attr('x', w/2);
|
paulo@89
|
1690 if (showMaxMin) {
|
paulo@89
|
1691 //if (showMaxMin && !isOrdinal) {
|
paulo@89
|
1692 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
|
paulo@89
|
1693 //.data(scale.domain())
|
paulo@89
|
1694 .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
|
paulo@89
|
1695 axisMaxMin.enter().append('g').attr('class',function(d,i){
|
paulo@89
|
1696 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
|
paulo@89
|
1697 }).append('text');
|
paulo@89
|
1698 axisMaxMin.exit().remove();
|
paulo@89
|
1699 axisMaxMin
|
paulo@89
|
1700 .attr('transform', function(d,i) {
|
paulo@89
|
1701 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
|
paulo@89
|
1702 })
|
paulo@89
|
1703 .select('text')
|
paulo@89
|
1704 .attr('dy', '.71em')
|
paulo@89
|
1705 .attr('y', axis.tickPadding())
|
paulo@89
|
1706 .attr('transform', rotateLabelsRule)
|
paulo@89
|
1707 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
|
paulo@89
|
1708 .text(function(d,i) {
|
paulo@89
|
1709 var v = fmt(d);
|
paulo@89
|
1710 return ('' + v).match('NaN') ? '' : v;
|
paulo@89
|
1711 });
|
paulo@89
|
1712 axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
|
paulo@89
|
1713 .attr('transform', function(d,i) {
|
paulo@89
|
1714 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
|
paulo@89
|
1715 });
|
paulo@89
|
1716 }
|
paulo@89
|
1717 if (staggerLabels)
|
paulo@89
|
1718 xTicks
|
paulo@89
|
1719 .attr('transform', function(d,i) {
|
paulo@89
|
1720 return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
|
paulo@89
|
1721 });
|
paulo@89
|
1722
|
paulo@89
|
1723 break;
|
paulo@89
|
1724 case 'right':
|
paulo@89
|
1725 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
|
paulo@89
|
1726 axisLabel
|
paulo@89
|
1727 .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
|
paulo@89
|
1728 .attr('transform', rotateYLabel ? 'rotate(90)' : '')
|
paulo@89
|
1729 .attr('y', rotateYLabel ? (-Math.max(margin.right, width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
|
paulo@89
|
1730 .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding());
|
paulo@89
|
1731 if (showMaxMin) {
|
paulo@89
|
1732 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
|
paulo@89
|
1733 .data(scale.domain());
|
paulo@89
|
1734 axisMaxMin.enter().append('g').attr('class',function(d,i){
|
paulo@89
|
1735 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
|
paulo@89
|
1736 }).append('text')
|
paulo@89
|
1737 .style('opacity', 0);
|
paulo@89
|
1738 axisMaxMin.exit().remove();
|
paulo@89
|
1739 axisMaxMin
|
paulo@89
|
1740 .attr('transform', function(d,i) {
|
paulo@89
|
1741 return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
|
paulo@89
|
1742 })
|
paulo@89
|
1743 .select('text')
|
paulo@89
|
1744 .attr('dy', '.32em')
|
paulo@89
|
1745 .attr('y', 0)
|
paulo@89
|
1746 .attr('x', axis.tickPadding())
|
paulo@89
|
1747 .style('text-anchor', 'start')
|
paulo@89
|
1748 .text(function(d, i) {
|
paulo@89
|
1749 var v = fmt(d);
|
paulo@89
|
1750 return ('' + v).match('NaN') ? '' : v;
|
paulo@89
|
1751 });
|
paulo@89
|
1752 axisMaxMin.watchTransition(renderWatch, 'min-max right')
|
paulo@89
|
1753 .attr('transform', function(d,i) {
|
paulo@89
|
1754 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
|
paulo@89
|
1755 })
|
paulo@89
|
1756 .select('text')
|
paulo@89
|
1757 .style('opacity', 1);
|
paulo@89
|
1758 }
|
paulo@89
|
1759 break;
|
paulo@89
|
1760 case 'left':
|
paulo@89
|
1761 /*
|
paulo@89
|
1762 //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
|
paulo@89
|
1763 var yTicks = g.selectAll('g').select("text");
|
paulo@89
|
1764 yTicks.each(function(d,i){
|
paulo@89
|
1765 var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
|
paulo@89
|
1766 if(labelPadding > width) width = labelPadding;
|
paulo@89
|
1767 });
|
paulo@89
|
1768 */
|
paulo@89
|
1769 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
|
paulo@89
|
1770 axisLabel
|
paulo@89
|
1771 .style('text-anchor', rotateYLabel ? 'middle' : 'end')
|
paulo@89
|
1772 .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
|
paulo@89
|
1773 .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10)
|
paulo@89
|
1774 .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding());
|
paulo@89
|
1775 if (showMaxMin) {
|
paulo@89
|
1776 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
|
paulo@89
|
1777 .data(scale.domain());
|
paulo@89
|
1778 axisMaxMin.enter().append('g').attr('class',function(d,i){
|
paulo@89
|
1779 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
|
paulo@89
|
1780 }).append('text')
|
paulo@89
|
1781 .style('opacity', 0);
|
paulo@89
|
1782 axisMaxMin.exit().remove();
|
paulo@89
|
1783 axisMaxMin
|
paulo@89
|
1784 .attr('transform', function(d,i) {
|
paulo@89
|
1785 return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
|
paulo@89
|
1786 })
|
paulo@89
|
1787 .select('text')
|
paulo@89
|
1788 .attr('dy', '.32em')
|
paulo@89
|
1789 .attr('y', 0)
|
paulo@89
|
1790 .attr('x', -axis.tickPadding())
|
paulo@89
|
1791 .attr('text-anchor', 'end')
|
paulo@89
|
1792 .text(function(d,i) {
|
paulo@89
|
1793 var v = fmt(d);
|
paulo@89
|
1794 return ('' + v).match('NaN') ? '' : v;
|
paulo@89
|
1795 });
|
paulo@89
|
1796 axisMaxMin.watchTransition(renderWatch, 'min-max right')
|
paulo@89
|
1797 .attr('transform', function(d,i) {
|
paulo@89
|
1798 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
|
paulo@89
|
1799 })
|
paulo@89
|
1800 .select('text')
|
paulo@89
|
1801 .style('opacity', 1);
|
paulo@89
|
1802 }
|
paulo@89
|
1803 break;
|
paulo@89
|
1804 }
|
paulo@89
|
1805 axisLabel.text(function(d) { return d });
|
paulo@89
|
1806
|
paulo@89
|
1807 if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
|
paulo@89
|
1808 //check if max and min overlap other values, if so, hide the values that overlap
|
paulo@89
|
1809 g.selectAll('g') // the g's wrapping each tick
|
paulo@89
|
1810 .each(function(d,i) {
|
paulo@89
|
1811 d3.select(this).select('text').attr('opacity', 1);
|
paulo@89
|
1812 if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it!
|
paulo@89
|
1813 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
|
paulo@89
|
1814 d3.select(this).attr('opacity', 0);
|
paulo@89
|
1815
|
paulo@89
|
1816 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
|
paulo@89
|
1817 }
|
paulo@89
|
1818 });
|
paulo@89
|
1819
|
paulo@89
|
1820 //if Max and Min = 0 only show min, Issue #281
|
paulo@89
|
1821 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
|
paulo@89
|
1822 wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
|
paulo@89
|
1823 return !i ? 1 : 0
|
paulo@89
|
1824 });
|
paulo@89
|
1825 }
|
paulo@89
|
1826 }
|
paulo@89
|
1827
|
paulo@89
|
1828 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
|
paulo@89
|
1829 var maxMinRange = [];
|
paulo@89
|
1830 wrap.selectAll('g.nv-axisMaxMin')
|
paulo@89
|
1831 .each(function(d,i) {
|
paulo@89
|
1832 try {
|
paulo@89
|
1833 if (i) // i== 1, max position
|
paulo@89
|
1834 maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
|
paulo@89
|
1835 else // i==0, min position
|
paulo@89
|
1836 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
|
paulo@89
|
1837 }catch (err) {
|
paulo@89
|
1838 if (i) // i== 1, max position
|
paulo@89
|
1839 maxMinRange.push(scale(d) - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
|
paulo@89
|
1840 else // i==0, min position
|
paulo@89
|
1841 maxMinRange.push(scale(d) + 4);
|
paulo@89
|
1842 }
|
paulo@89
|
1843 });
|
paulo@89
|
1844 // the g's wrapping each tick
|
paulo@89
|
1845 g.selectAll('g').each(function(d, i) {
|
paulo@89
|
1846 if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
|
paulo@89
|
1847 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
|
paulo@89
|
1848 d3.select(this).remove();
|
paulo@89
|
1849 else
|
paulo@89
|
1850 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
|
paulo@89
|
1851 }
|
paulo@89
|
1852 });
|
paulo@89
|
1853 }
|
paulo@89
|
1854
|
paulo@89
|
1855 //Highlight zero tick line
|
paulo@89
|
1856 g.selectAll('.tick')
|
paulo@89
|
1857 .filter(function (d) {
|
paulo@89
|
1858 /*
|
paulo@89
|
1859 The filter needs to return only ticks at or near zero.
|
paulo@89
|
1860 Numbers like 0.00001 need to count as zero as well,
|
paulo@89
|
1861 and the arithmetic trick below solves that.
|
paulo@89
|
1862 */
|
paulo@89
|
1863 return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined)
|
paulo@89
|
1864 })
|
paulo@89
|
1865 .classed('zero', true);
|
paulo@89
|
1866
|
paulo@89
|
1867 //store old scales for use in transitions on update
|
paulo@89
|
1868 scale0 = scale.copy();
|
paulo@89
|
1869
|
paulo@89
|
1870 });
|
paulo@89
|
1871
|
paulo@89
|
1872 renderWatch.renderEnd('axis immediate');
|
paulo@89
|
1873 return chart;
|
paulo@89
|
1874 }
|
paulo@89
|
1875
|
paulo@89
|
1876 //============================================================
|
paulo@89
|
1877 // Expose Public Variables
|
paulo@89
|
1878 //------------------------------------------------------------
|
paulo@89
|
1879
|
paulo@89
|
1880 // expose chart's sub-components
|
paulo@89
|
1881 chart.axis = axis;
|
paulo@89
|
1882 chart.dispatch = dispatch;
|
paulo@89
|
1883
|
paulo@89
|
1884 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
1885 chart._options = Object.create({}, {
|
paulo@89
|
1886 // simple options, just get/set the necessary values
|
paulo@89
|
1887 axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
|
paulo@89
|
1888 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
|
paulo@89
|
1889 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
|
paulo@89
|
1890 rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
|
paulo@89
|
1891 showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
|
paulo@89
|
1892 axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
|
paulo@89
|
1893 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
1894 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
|
paulo@89
|
1895 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
1896
|
paulo@89
|
1897 // options that require extra logic in the setter
|
paulo@89
|
1898 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
1899 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
1900 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
1901 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
1902 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
1903 }},
|
paulo@89
|
1904 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
1905 duration=_;
|
paulo@89
|
1906 renderWatch.reset(duration);
|
paulo@89
|
1907 }},
|
paulo@89
|
1908 scale: {get: function(){return scale;}, set: function(_){
|
paulo@89
|
1909 scale = _;
|
paulo@89
|
1910 axis.scale(scale);
|
paulo@89
|
1911 isOrdinal = typeof scale.rangeBands === 'function';
|
paulo@89
|
1912 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
|
paulo@89
|
1913 }}
|
paulo@89
|
1914 });
|
paulo@89
|
1915
|
paulo@89
|
1916 nv.utils.initOptions(chart);
|
paulo@89
|
1917 nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
|
paulo@89
|
1918 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
|
paulo@89
|
1919
|
paulo@89
|
1920 return chart;
|
paulo@89
|
1921 };
|
paulo@89
|
1922 nv.models.boxPlot = function() {
|
paulo@89
|
1923 "use strict";
|
paulo@89
|
1924
|
paulo@89
|
1925 //============================================================
|
paulo@89
|
1926 // Public Variables with Default Settings
|
paulo@89
|
1927 //------------------------------------------------------------
|
paulo@89
|
1928
|
paulo@89
|
1929 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
1930 , width = 960
|
paulo@89
|
1931 , height = 500
|
paulo@89
|
1932 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
1933 , x = d3.scale.ordinal()
|
paulo@89
|
1934 , y = d3.scale.linear()
|
paulo@89
|
1935 , getX = function(d) { return d.x }
|
paulo@89
|
1936 , getY = function(d) { return d.y }
|
paulo@89
|
1937 , color = nv.utils.defaultColor()
|
paulo@89
|
1938 , container = null
|
paulo@89
|
1939 , xDomain
|
paulo@89
|
1940 , yDomain
|
paulo@89
|
1941 , xRange
|
paulo@89
|
1942 , yRange
|
paulo@89
|
1943 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
|
paulo@89
|
1944 , duration = 250
|
paulo@89
|
1945 , maxBoxWidth = null
|
paulo@89
|
1946 ;
|
paulo@89
|
1947
|
paulo@89
|
1948 //============================================================
|
paulo@89
|
1949 // Private Variables
|
paulo@89
|
1950 //------------------------------------------------------------
|
paulo@89
|
1951
|
paulo@89
|
1952 var x0, y0;
|
paulo@89
|
1953 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
1954
|
paulo@89
|
1955 function chart(selection) {
|
paulo@89
|
1956 renderWatch.reset();
|
paulo@89
|
1957 selection.each(function(data) {
|
paulo@89
|
1958 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
1959 availableHeight = height - margin.top - margin.bottom;
|
paulo@89
|
1960
|
paulo@89
|
1961 container = d3.select(this);
|
paulo@89
|
1962 nv.utils.initSVG(container);
|
paulo@89
|
1963
|
paulo@89
|
1964 // Setup Scales
|
paulo@89
|
1965 x .domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
|
paulo@89
|
1966 .rangeBands(xRange || [0, availableWidth], .1);
|
paulo@89
|
1967
|
paulo@89
|
1968 // if we know yDomain, no need to calculate
|
paulo@89
|
1969 var yData = []
|
paulo@89
|
1970 if (!yDomain) {
|
paulo@89
|
1971 // (y-range is based on quartiles, whiskers and outliers)
|
paulo@89
|
1972
|
paulo@89
|
1973 // lower values
|
paulo@89
|
1974 var yMin = d3.min(data.map(function(d) {
|
paulo@89
|
1975 var min_arr = [];
|
paulo@89
|
1976
|
paulo@89
|
1977 min_arr.push(d.values.Q1);
|
paulo@89
|
1978 if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); }
|
paulo@89
|
1979 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); }
|
paulo@89
|
1980
|
paulo@89
|
1981 return d3.min(min_arr);
|
paulo@89
|
1982 }));
|
paulo@89
|
1983
|
paulo@89
|
1984 // upper values
|
paulo@89
|
1985 var yMax = d3.max(data.map(function(d) {
|
paulo@89
|
1986 var max_arr = [];
|
paulo@89
|
1987
|
paulo@89
|
1988 max_arr.push(d.values.Q3);
|
paulo@89
|
1989 if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); }
|
paulo@89
|
1990 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); }
|
paulo@89
|
1991
|
paulo@89
|
1992 return d3.max(max_arr);
|
paulo@89
|
1993 }));
|
paulo@89
|
1994
|
paulo@89
|
1995 yData = [ yMin, yMax ] ;
|
paulo@89
|
1996 }
|
paulo@89
|
1997
|
paulo@89
|
1998 y.domain(yDomain || yData);
|
paulo@89
|
1999 y.range(yRange || [availableHeight, 0]);
|
paulo@89
|
2000
|
paulo@89
|
2001 //store old scales if they exist
|
paulo@89
|
2002 x0 = x0 || x;
|
paulo@89
|
2003 y0 = y0 || y.copy().range([y(0),y(0)]);
|
paulo@89
|
2004
|
paulo@89
|
2005 // Setup containers and skeleton of chart
|
paulo@89
|
2006 var wrap = container.selectAll('g.nv-wrap').data([data]);
|
paulo@89
|
2007 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap');
|
paulo@89
|
2008 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
2009
|
paulo@89
|
2010 var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d });
|
paulo@89
|
2011 var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6);
|
paulo@89
|
2012 boxplots
|
paulo@89
|
2013 .attr('class', 'nv-boxplot')
|
paulo@89
|
2014 .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; })
|
paulo@89
|
2015 .classed('hover', function(d) { return d.hover });
|
paulo@89
|
2016 boxplots
|
paulo@89
|
2017 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
|
paulo@89
|
2018 .style('stroke-opacity', 1)
|
paulo@89
|
2019 .style('fill-opacity', .75)
|
paulo@89
|
2020 .delay(function(d,i) { return i * duration / data.length })
|
paulo@89
|
2021 .attr('transform', function(d,i) {
|
paulo@89
|
2022 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)';
|
paulo@89
|
2023 });
|
paulo@89
|
2024 boxplots.exit().remove();
|
paulo@89
|
2025
|
paulo@89
|
2026 // ----- add the SVG elements for each boxPlot -----
|
paulo@89
|
2027
|
paulo@89
|
2028 // conditionally append whisker lines
|
paulo@89
|
2029 boxEnter.each(function(d,i) {
|
paulo@89
|
2030 var box = d3.select(this);
|
paulo@89
|
2031
|
paulo@89
|
2032 ['low', 'high'].forEach(function(key) {
|
paulo@89
|
2033 if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) {
|
paulo@89
|
2034 box.append('line')
|
paulo@89
|
2035 .style('stroke', (d.color) ? d.color : color(d,i))
|
paulo@89
|
2036 .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
|
paulo@89
|
2037
|
paulo@89
|
2038 box.append('line')
|
paulo@89
|
2039 .style('stroke', (d.color) ? d.color : color(d,i))
|
paulo@89
|
2040 .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
|
paulo@89
|
2041 }
|
paulo@89
|
2042 });
|
paulo@89
|
2043 });
|
paulo@89
|
2044
|
paulo@89
|
2045 // outliers
|
paulo@89
|
2046 // TODO: support custom colors here
|
paulo@89
|
2047 var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
|
paulo@89
|
2048 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; }
|
paulo@89
|
2049 else { return []; }
|
paulo@89
|
2050 });
|
paulo@89
|
2051 outliers.enter().append('circle')
|
paulo@89
|
2052 .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) })
|
paulo@89
|
2053 .on('mouseover', function(d,i,j) {
|
paulo@89
|
2054 d3.select(this).classed('hover', true);
|
paulo@89
|
2055 dispatch.elementMouseover({
|
paulo@89
|
2056 series: { key: d, color: color(d,j) },
|
paulo@89
|
2057 e: d3.event
|
paulo@89
|
2058 });
|
paulo@89
|
2059 })
|
paulo@89
|
2060 .on('mouseout', function(d,i,j) {
|
paulo@89
|
2061 d3.select(this).classed('hover', false);
|
paulo@89
|
2062 dispatch.elementMouseout({
|
paulo@89
|
2063 series: { key: d, color: color(d,j) },
|
paulo@89
|
2064 e: d3.event
|
paulo@89
|
2065 });
|
paulo@89
|
2066 })
|
paulo@89
|
2067 .on('mousemove', function(d,i) {
|
paulo@89
|
2068 dispatch.elementMousemove({e: d3.event});
|
paulo@89
|
2069 });
|
paulo@89
|
2070
|
paulo@89
|
2071 outliers.attr('class', 'nv-boxplot-outlier');
|
paulo@89
|
2072 outliers
|
paulo@89
|
2073 .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
|
paulo@89
|
2074 .attr('cx', x.rangeBand() * .45)
|
paulo@89
|
2075 .attr('cy', function(d,i,j) { return y(d); })
|
paulo@89
|
2076 .attr('r', '3');
|
paulo@89
|
2077 outliers.exit().remove();
|
paulo@89
|
2078
|
paulo@89
|
2079 var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); };
|
paulo@89
|
2080 var box_left = function() { return x.rangeBand() * .45 - box_width()/2; };
|
paulo@89
|
2081 var box_right = function() { return x.rangeBand() * .45 + box_width()/2; };
|
paulo@89
|
2082
|
paulo@89
|
2083 // update whisker lines and ticks
|
paulo@89
|
2084 ['low', 'high'].forEach(function(key) {
|
paulo@89
|
2085 var endpoint = (key === 'low') ? 'Q1' : 'Q3';
|
paulo@89
|
2086
|
paulo@89
|
2087 boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
|
paulo@89
|
2088 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
|
paulo@89
|
2089 .attr('x1', x.rangeBand() * .45 )
|
paulo@89
|
2090 .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
|
paulo@89
|
2091 .attr('x2', x.rangeBand() * .45 )
|
paulo@89
|
2092 .attr('y2', function(d,i) { return y(d.values[endpoint]); });
|
paulo@89
|
2093
|
paulo@89
|
2094 boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
|
paulo@89
|
2095 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
|
paulo@89
|
2096 .attr('x1', box_left )
|
paulo@89
|
2097 .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
|
paulo@89
|
2098 .attr('x2', box_right )
|
paulo@89
|
2099 .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); });
|
paulo@89
|
2100 });
|
paulo@89
|
2101
|
paulo@89
|
2102 ['low', 'high'].forEach(function(key) {
|
paulo@89
|
2103 boxEnter.selectAll('.nv-boxplot-' + key)
|
paulo@89
|
2104 .on('mouseover', function(d,i,j) {
|
paulo@89
|
2105 d3.select(this).classed('hover', true);
|
paulo@89
|
2106 dispatch.elementMouseover({
|
paulo@89
|
2107 series: { key: d.values['whisker_' + key], color: color(d,j) },
|
paulo@89
|
2108 e: d3.event
|
paulo@89
|
2109 });
|
paulo@89
|
2110 })
|
paulo@89
|
2111 .on('mouseout', function(d,i,j) {
|
paulo@89
|
2112 d3.select(this).classed('hover', false);
|
paulo@89
|
2113 dispatch.elementMouseout({
|
paulo@89
|
2114 series: { key: d.values['whisker_' + key], color: color(d,j) },
|
paulo@89
|
2115 e: d3.event
|
paulo@89
|
2116 });
|
paulo@89
|
2117 })
|
paulo@89
|
2118 .on('mousemove', function(d,i) {
|
paulo@89
|
2119 dispatch.elementMousemove({e: d3.event});
|
paulo@89
|
2120 });
|
paulo@89
|
2121 });
|
paulo@89
|
2122
|
paulo@89
|
2123 // boxes
|
paulo@89
|
2124 boxEnter.append('rect')
|
paulo@89
|
2125 .attr('class', 'nv-boxplot-box')
|
paulo@89
|
2126 // tooltip events
|
paulo@89
|
2127 .on('mouseover', function(d,i) {
|
paulo@89
|
2128 d3.select(this).classed('hover', true);
|
paulo@89
|
2129 dispatch.elementMouseover({
|
paulo@89
|
2130 key: d.label,
|
paulo@89
|
2131 value: d.label,
|
paulo@89
|
2132 series: [
|
paulo@89
|
2133 { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
|
paulo@89
|
2134 { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
|
paulo@89
|
2135 { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
|
paulo@89
|
2136 ],
|
paulo@89
|
2137 data: d,
|
paulo@89
|
2138 index: i,
|
paulo@89
|
2139 e: d3.event
|
paulo@89
|
2140 });
|
paulo@89
|
2141 })
|
paulo@89
|
2142 .on('mouseout', function(d,i) {
|
paulo@89
|
2143 d3.select(this).classed('hover', false);
|
paulo@89
|
2144 dispatch.elementMouseout({
|
paulo@89
|
2145 key: d.label,
|
paulo@89
|
2146 value: d.label,
|
paulo@89
|
2147 series: [
|
paulo@89
|
2148 { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
|
paulo@89
|
2149 { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
|
paulo@89
|
2150 { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
|
paulo@89
|
2151 ],
|
paulo@89
|
2152 data: d,
|
paulo@89
|
2153 index: i,
|
paulo@89
|
2154 e: d3.event
|
paulo@89
|
2155 });
|
paulo@89
|
2156 })
|
paulo@89
|
2157 .on('mousemove', function(d,i) {
|
paulo@89
|
2158 dispatch.elementMousemove({e: d3.event});
|
paulo@89
|
2159 });
|
paulo@89
|
2160
|
paulo@89
|
2161 // box transitions
|
paulo@89
|
2162 boxplots.select('rect.nv-boxplot-box')
|
paulo@89
|
2163 .watchTransition(renderWatch, 'nv-boxplot: boxes')
|
paulo@89
|
2164 .attr('y', function(d,i) { return y(d.values.Q3); })
|
paulo@89
|
2165 .attr('width', box_width)
|
paulo@89
|
2166 .attr('x', box_left )
|
paulo@89
|
2167
|
paulo@89
|
2168 .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 })
|
paulo@89
|
2169 .style('fill', function(d,i) { return d.color || color(d,i) })
|
paulo@89
|
2170 .style('stroke', function(d,i) { return d.color || color(d,i) });
|
paulo@89
|
2171
|
paulo@89
|
2172 // median line
|
paulo@89
|
2173 boxEnter.append('line').attr('class', 'nv-boxplot-median');
|
paulo@89
|
2174
|
paulo@89
|
2175 boxplots.select('line.nv-boxplot-median')
|
paulo@89
|
2176 .watchTransition(renderWatch, 'nv-boxplot: boxplots line')
|
paulo@89
|
2177 .attr('x1', box_left)
|
paulo@89
|
2178 .attr('y1', function(d,i) { return y(d.values.Q2); })
|
paulo@89
|
2179 .attr('x2', box_right)
|
paulo@89
|
2180 .attr('y2', function(d,i) { return y(d.values.Q2); });
|
paulo@89
|
2181
|
paulo@89
|
2182 //store old scales for use in transitions on update
|
paulo@89
|
2183 x0 = x.copy();
|
paulo@89
|
2184 y0 = y.copy();
|
paulo@89
|
2185 });
|
paulo@89
|
2186
|
paulo@89
|
2187 renderWatch.renderEnd('nv-boxplot immediate');
|
paulo@89
|
2188 return chart;
|
paulo@89
|
2189 }
|
paulo@89
|
2190
|
paulo@89
|
2191 //============================================================
|
paulo@89
|
2192 // Expose Public Variables
|
paulo@89
|
2193 //------------------------------------------------------------
|
paulo@89
|
2194
|
paulo@89
|
2195 chart.dispatch = dispatch;
|
paulo@89
|
2196 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
2197
|
paulo@89
|
2198 chart._options = Object.create({}, {
|
paulo@89
|
2199 // simple options, just get/set the necessary values
|
paulo@89
|
2200 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
2201 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
2202 maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}},
|
paulo@89
|
2203 x: {get: function(){return getX;}, set: function(_){getX=_;}},
|
paulo@89
|
2204 y: {get: function(){return getY;}, set: function(_){getY=_;}},
|
paulo@89
|
2205 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
2206 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
2207 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
2208 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
2209 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
2210 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
2211 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
2212 // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
|
paulo@89
|
2213
|
paulo@89
|
2214 // options that require extra logic in the setter
|
paulo@89
|
2215 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
2216 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
2217 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
2218 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
2219 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
2220 }},
|
paulo@89
|
2221 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
2222 color = nv.utils.getColor(_);
|
paulo@89
|
2223 }},
|
paulo@89
|
2224 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
2225 duration = _;
|
paulo@89
|
2226 renderWatch.reset(duration);
|
paulo@89
|
2227 }}
|
paulo@89
|
2228 });
|
paulo@89
|
2229
|
paulo@89
|
2230 nv.utils.initOptions(chart);
|
paulo@89
|
2231
|
paulo@89
|
2232 return chart;
|
paulo@89
|
2233 };
|
paulo@89
|
2234 nv.models.boxPlotChart = function() {
|
paulo@89
|
2235 "use strict";
|
paulo@89
|
2236
|
paulo@89
|
2237 //============================================================
|
paulo@89
|
2238 // Public Variables with Default Settings
|
paulo@89
|
2239 //------------------------------------------------------------
|
paulo@89
|
2240
|
paulo@89
|
2241 var boxplot = nv.models.boxPlot()
|
paulo@89
|
2242 , xAxis = nv.models.axis()
|
paulo@89
|
2243 , yAxis = nv.models.axis()
|
paulo@89
|
2244 ;
|
paulo@89
|
2245
|
paulo@89
|
2246 var margin = {top: 15, right: 10, bottom: 50, left: 60}
|
paulo@89
|
2247 , width = null
|
paulo@89
|
2248 , height = null
|
paulo@89
|
2249 , color = nv.utils.getColor()
|
paulo@89
|
2250 , showXAxis = true
|
paulo@89
|
2251 , showYAxis = true
|
paulo@89
|
2252 , rightAlignYAxis = false
|
paulo@89
|
2253 , staggerLabels = false
|
paulo@89
|
2254 , tooltip = nv.models.tooltip()
|
paulo@89
|
2255 , x
|
paulo@89
|
2256 , y
|
paulo@89
|
2257 , noData = "No Data Available."
|
paulo@89
|
2258 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate', 'renderEnd')
|
paulo@89
|
2259 , duration = 250
|
paulo@89
|
2260 ;
|
paulo@89
|
2261
|
paulo@89
|
2262 xAxis
|
paulo@89
|
2263 .orient('bottom')
|
paulo@89
|
2264 .showMaxMin(false)
|
paulo@89
|
2265 .tickFormat(function(d) { return d })
|
paulo@89
|
2266 ;
|
paulo@89
|
2267 yAxis
|
paulo@89
|
2268 .orient((rightAlignYAxis) ? 'right' : 'left')
|
paulo@89
|
2269 .tickFormat(d3.format(',.1f'))
|
paulo@89
|
2270 ;
|
paulo@89
|
2271
|
paulo@89
|
2272 tooltip.duration(0);
|
paulo@89
|
2273
|
paulo@89
|
2274 //============================================================
|
paulo@89
|
2275 // Private Variables
|
paulo@89
|
2276 //------------------------------------------------------------
|
paulo@89
|
2277
|
paulo@89
|
2278 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
2279
|
paulo@89
|
2280 function chart(selection) {
|
paulo@89
|
2281 renderWatch.reset();
|
paulo@89
|
2282 renderWatch.models(boxplot);
|
paulo@89
|
2283 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
2284 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
2285
|
paulo@89
|
2286 selection.each(function(data) {
|
paulo@89
|
2287 var container = d3.select(this),
|
paulo@89
|
2288 that = this;
|
paulo@89
|
2289 nv.utils.initSVG(container);
|
paulo@89
|
2290 var availableWidth = (width || parseInt(container.style('width')) || 960)
|
paulo@89
|
2291 - margin.left - margin.right,
|
paulo@89
|
2292 availableHeight = (height || parseInt(container.style('height')) || 400)
|
paulo@89
|
2293 - margin.top - margin.bottom;
|
paulo@89
|
2294
|
paulo@89
|
2295 chart.update = function() {
|
paulo@89
|
2296 dispatch.beforeUpdate();
|
paulo@89
|
2297 container.transition().duration(duration).call(chart);
|
paulo@89
|
2298 };
|
paulo@89
|
2299 chart.container = this;
|
paulo@89
|
2300
|
paulo@89
|
2301 // Display No Data message if there's nothing to show. (quartiles required at minimum)
|
paulo@89
|
2302 if (!data || !data.length ||
|
paulo@89
|
2303 !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) {
|
paulo@89
|
2304 var noDataText = container.selectAll('.nv-noData').data([noData]);
|
paulo@89
|
2305
|
paulo@89
|
2306 noDataText.enter().append('text')
|
paulo@89
|
2307 .attr('class', 'nvd3 nv-noData')
|
paulo@89
|
2308 .attr('dy', '-.7em')
|
paulo@89
|
2309 .style('text-anchor', 'middle');
|
paulo@89
|
2310
|
paulo@89
|
2311 noDataText
|
paulo@89
|
2312 .attr('x', margin.left + availableWidth / 2)
|
paulo@89
|
2313 .attr('y', margin.top + availableHeight / 2)
|
paulo@89
|
2314 .text(function(d) { return d });
|
paulo@89
|
2315
|
paulo@89
|
2316 return chart;
|
paulo@89
|
2317 } else {
|
paulo@89
|
2318 container.selectAll('.nv-noData').remove();
|
paulo@89
|
2319 }
|
paulo@89
|
2320
|
paulo@89
|
2321 // Setup Scales
|
paulo@89
|
2322 x = boxplot.xScale();
|
paulo@89
|
2323 y = boxplot.yScale().clamp(true);
|
paulo@89
|
2324
|
paulo@89
|
2325 // Setup containers and skeleton of chart
|
paulo@89
|
2326 var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]);
|
paulo@89
|
2327 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g');
|
paulo@89
|
2328 var defsEnter = gEnter.append('defs');
|
paulo@89
|
2329 var g = wrap.select('g');
|
paulo@89
|
2330
|
paulo@89
|
2331 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
2332 gEnter.append('g').attr('class', 'nv-y nv-axis')
|
paulo@89
|
2333 .append('g').attr('class', 'nv-zeroLine')
|
paulo@89
|
2334 .append('line');
|
paulo@89
|
2335
|
paulo@89
|
2336 gEnter.append('g').attr('class', 'nv-barsWrap');
|
paulo@89
|
2337
|
paulo@89
|
2338 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
2339
|
paulo@89
|
2340 if (rightAlignYAxis) {
|
paulo@89
|
2341 g.select(".nv-y.nv-axis")
|
paulo@89
|
2342 .attr("transform", "translate(" + availableWidth + ",0)");
|
paulo@89
|
2343 }
|
paulo@89
|
2344
|
paulo@89
|
2345 // Main Chart Component(s)
|
paulo@89
|
2346 boxplot
|
paulo@89
|
2347 .width(availableWidth)
|
paulo@89
|
2348 .height(availableHeight);
|
paulo@89
|
2349
|
paulo@89
|
2350 var barsWrap = g.select('.nv-barsWrap')
|
paulo@89
|
2351 .datum(data.filter(function(d) { return !d.disabled }))
|
paulo@89
|
2352
|
paulo@89
|
2353 barsWrap.transition().call(boxplot);
|
paulo@89
|
2354
|
paulo@89
|
2355
|
paulo@89
|
2356 defsEnter.append('clipPath')
|
paulo@89
|
2357 .attr('id', 'nv-x-label-clip-' + boxplot.id())
|
paulo@89
|
2358 .append('rect');
|
paulo@89
|
2359
|
paulo@89
|
2360 g.select('#nv-x-label-clip-' + boxplot.id() + ' rect')
|
paulo@89
|
2361 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
|
paulo@89
|
2362 .attr('height', 16)
|
paulo@89
|
2363 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
|
paulo@89
|
2364
|
paulo@89
|
2365 // Setup Axes
|
paulo@89
|
2366 if (showXAxis) {
|
paulo@89
|
2367 xAxis
|
paulo@89
|
2368 .scale(x)
|
paulo@89
|
2369 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
2370 .tickSize(-availableHeight, 0);
|
paulo@89
|
2371
|
paulo@89
|
2372 g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')');
|
paulo@89
|
2373 g.select('.nv-x.nv-axis').call(xAxis);
|
paulo@89
|
2374
|
paulo@89
|
2375 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
|
paulo@89
|
2376 if (staggerLabels) {
|
paulo@89
|
2377 xTicks
|
paulo@89
|
2378 .selectAll('text')
|
paulo@89
|
2379 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
|
paulo@89
|
2380 }
|
paulo@89
|
2381 }
|
paulo@89
|
2382
|
paulo@89
|
2383 if (showYAxis) {
|
paulo@89
|
2384 yAxis
|
paulo@89
|
2385 .scale(y)
|
paulo@89
|
2386 .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data
|
paulo@89
|
2387 .tickSize( -availableWidth, 0);
|
paulo@89
|
2388
|
paulo@89
|
2389 g.select('.nv-y.nv-axis').call(yAxis);
|
paulo@89
|
2390 }
|
paulo@89
|
2391
|
paulo@89
|
2392 // Zero line
|
paulo@89
|
2393 g.select(".nv-zeroLine line")
|
paulo@89
|
2394 .attr("x1",0)
|
paulo@89
|
2395 .attr("x2",availableWidth)
|
paulo@89
|
2396 .attr("y1", y(0))
|
paulo@89
|
2397 .attr("y2", y(0))
|
paulo@89
|
2398 ;
|
paulo@89
|
2399
|
paulo@89
|
2400 //============================================================
|
paulo@89
|
2401 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
2402 //------------------------------------------------------------
|
paulo@89
|
2403 });
|
paulo@89
|
2404
|
paulo@89
|
2405 renderWatch.renderEnd('nv-boxplot chart immediate');
|
paulo@89
|
2406 return chart;
|
paulo@89
|
2407 }
|
paulo@89
|
2408
|
paulo@89
|
2409 //============================================================
|
paulo@89
|
2410 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
2411 //------------------------------------------------------------
|
paulo@89
|
2412
|
paulo@89
|
2413 boxplot.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
2414 tooltip.data(evt).hidden(false);
|
paulo@89
|
2415 });
|
paulo@89
|
2416
|
paulo@89
|
2417 boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
2418 tooltip.data(evt).hidden(true);
|
paulo@89
|
2419 });
|
paulo@89
|
2420
|
paulo@89
|
2421 boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
2422 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
2423 });
|
paulo@89
|
2424
|
paulo@89
|
2425 //============================================================
|
paulo@89
|
2426 // Expose Public Variables
|
paulo@89
|
2427 //------------------------------------------------------------
|
paulo@89
|
2428
|
paulo@89
|
2429 chart.dispatch = dispatch;
|
paulo@89
|
2430 chart.boxplot = boxplot;
|
paulo@89
|
2431 chart.xAxis = xAxis;
|
paulo@89
|
2432 chart.yAxis = yAxis;
|
paulo@89
|
2433 chart.tooltip = tooltip;
|
paulo@89
|
2434
|
paulo@89
|
2435 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
2436
|
paulo@89
|
2437 chart._options = Object.create({}, {
|
paulo@89
|
2438 // simple options, just get/set the necessary values
|
paulo@89
|
2439 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
2440 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
2441 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
|
paulo@89
|
2442 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
2443 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
2444 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
|
paulo@89
|
2445 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
|
paulo@89
|
2446 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
2447
|
paulo@89
|
2448 // options that require extra logic in the setter
|
paulo@89
|
2449 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
2450 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
2451 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
2452 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
2453 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
2454 }},
|
paulo@89
|
2455 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
2456 duration = _;
|
paulo@89
|
2457 renderWatch.reset(duration);
|
paulo@89
|
2458 boxplot.duration(duration);
|
paulo@89
|
2459 xAxis.duration(duration);
|
paulo@89
|
2460 yAxis.duration(duration);
|
paulo@89
|
2461 }},
|
paulo@89
|
2462 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
2463 color = nv.utils.getColor(_);
|
paulo@89
|
2464 boxplot.color(color);
|
paulo@89
|
2465 }},
|
paulo@89
|
2466 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
|
paulo@89
|
2467 rightAlignYAxis = _;
|
paulo@89
|
2468 yAxis.orient( (_) ? 'right' : 'left');
|
paulo@89
|
2469 }}
|
paulo@89
|
2470 });
|
paulo@89
|
2471
|
paulo@89
|
2472 nv.utils.inheritOptions(chart, boxplot);
|
paulo@89
|
2473 nv.utils.initOptions(chart);
|
paulo@89
|
2474
|
paulo@89
|
2475 return chart;
|
paulo@89
|
2476 }
|
paulo@89
|
2477 // Chart design based on the recommendations of Stephen Few. Implementation
|
paulo@89
|
2478 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
|
paulo@89
|
2479 // http://projects.instantcognition.com/protovis/bulletchart/
|
paulo@89
|
2480
|
paulo@89
|
2481 nv.models.bullet = function() {
|
paulo@89
|
2482 "use strict";
|
paulo@89
|
2483
|
paulo@89
|
2484 //============================================================
|
paulo@89
|
2485 // Public Variables with Default Settings
|
paulo@89
|
2486 //------------------------------------------------------------
|
paulo@89
|
2487
|
paulo@89
|
2488 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
2489 , orient = 'left' // TODO top & bottom
|
paulo@89
|
2490 , reverse = false
|
paulo@89
|
2491 , ranges = function(d) { return d.ranges }
|
paulo@89
|
2492 , markers = function(d) { return d.markers ? d.markers : [0] }
|
paulo@89
|
2493 , measures = function(d) { return d.measures }
|
paulo@89
|
2494 , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
|
paulo@89
|
2495 , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
|
paulo@89
|
2496 , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
|
paulo@89
|
2497 , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
|
paulo@89
|
2498 , width = 380
|
paulo@89
|
2499 , height = 30
|
paulo@89
|
2500 , container = null
|
paulo@89
|
2501 , tickFormat = null
|
paulo@89
|
2502 , color = nv.utils.getColor(['#1f77b4'])
|
paulo@89
|
2503 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove')
|
paulo@89
|
2504 ;
|
paulo@89
|
2505
|
paulo@89
|
2506 function chart(selection) {
|
paulo@89
|
2507 selection.each(function(d, i) {
|
paulo@89
|
2508 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
2509 availableHeight = height - margin.top - margin.bottom;
|
paulo@89
|
2510
|
paulo@89
|
2511 container = d3.select(this);
|
paulo@89
|
2512 nv.utils.initSVG(container);
|
paulo@89
|
2513
|
paulo@89
|
2514 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
|
paulo@89
|
2515 markerz = markers.call(this, d, i).slice().sort(d3.descending),
|
paulo@89
|
2516 measurez = measures.call(this, d, i).slice().sort(d3.descending),
|
paulo@89
|
2517 rangeLabelz = rangeLabels.call(this, d, i).slice(),
|
paulo@89
|
2518 markerLabelz = markerLabels.call(this, d, i).slice(),
|
paulo@89
|
2519 measureLabelz = measureLabels.call(this, d, i).slice();
|
paulo@89
|
2520
|
paulo@89
|
2521 // Setup Scales
|
paulo@89
|
2522 // Compute the new x-scale.
|
paulo@89
|
2523 var x1 = d3.scale.linear()
|
paulo@89
|
2524 .domain( d3.extent(d3.merge([forceX, rangez])) )
|
paulo@89
|
2525 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
|
paulo@89
|
2526
|
paulo@89
|
2527 // Retrieve the old x-scale, if this is an update.
|
paulo@89
|
2528 var x0 = this.__chart__ || d3.scale.linear()
|
paulo@89
|
2529 .domain([0, Infinity])
|
paulo@89
|
2530 .range(x1.range());
|
paulo@89
|
2531
|
paulo@89
|
2532 // Stash the new scale.
|
paulo@89
|
2533 this.__chart__ = x1;
|
paulo@89
|
2534
|
paulo@89
|
2535 var rangeMin = d3.min(rangez), //rangez[2]
|
paulo@89
|
2536 rangeMax = d3.max(rangez), //rangez[0]
|
paulo@89
|
2537 rangeAvg = rangez[1];
|
paulo@89
|
2538
|
paulo@89
|
2539 // Setup containers and skeleton of chart
|
paulo@89
|
2540 var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
|
paulo@89
|
2541 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
|
paulo@89
|
2542 var gEnter = wrapEnter.append('g');
|
paulo@89
|
2543 var g = wrap.select('g');
|
paulo@89
|
2544
|
paulo@89
|
2545 gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
|
paulo@89
|
2546 gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
|
paulo@89
|
2547 gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
|
paulo@89
|
2548 gEnter.append('rect').attr('class', 'nv-measure');
|
paulo@89
|
2549
|
paulo@89
|
2550 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
2551
|
paulo@89
|
2552 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
|
paulo@89
|
2553 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
|
paulo@89
|
2554 var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
|
paulo@89
|
2555 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
|
paulo@89
|
2556
|
paulo@89
|
2557 g.select('rect.nv-rangeMax')
|
paulo@89
|
2558 .attr('height', availableHeight)
|
paulo@89
|
2559 .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
|
paulo@89
|
2560 .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
|
paulo@89
|
2561 .datum(rangeMax > 0 ? rangeMax : rangeMin)
|
paulo@89
|
2562
|
paulo@89
|
2563 g.select('rect.nv-rangeAvg')
|
paulo@89
|
2564 .attr('height', availableHeight)
|
paulo@89
|
2565 .attr('width', w1(rangeAvg))
|
paulo@89
|
2566 .attr('x', xp1(rangeAvg))
|
paulo@89
|
2567 .datum(rangeAvg)
|
paulo@89
|
2568
|
paulo@89
|
2569 g.select('rect.nv-rangeMin')
|
paulo@89
|
2570 .attr('height', availableHeight)
|
paulo@89
|
2571 .attr('width', w1(rangeMax))
|
paulo@89
|
2572 .attr('x', xp1(rangeMax))
|
paulo@89
|
2573 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
|
paulo@89
|
2574 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
|
paulo@89
|
2575 .datum(rangeMax > 0 ? rangeMin : rangeMax)
|
paulo@89
|
2576
|
paulo@89
|
2577 g.select('rect.nv-measure')
|
paulo@89
|
2578 .style('fill', color)
|
paulo@89
|
2579 .attr('height', availableHeight / 3)
|
paulo@89
|
2580 .attr('y', availableHeight / 3)
|
paulo@89
|
2581 .attr('width', measurez < 0 ?
|
paulo@89
|
2582 x1(0) - x1(measurez[0])
|
paulo@89
|
2583 : x1(measurez[0]) - x1(0))
|
paulo@89
|
2584 .attr('x', xp1(measurez))
|
paulo@89
|
2585 .on('mouseover', function() {
|
paulo@89
|
2586 dispatch.elementMouseover({
|
paulo@89
|
2587 value: measurez[0],
|
paulo@89
|
2588 label: measureLabelz[0] || 'Current',
|
paulo@89
|
2589 color: d3.select(this).style("fill")
|
paulo@89
|
2590 })
|
paulo@89
|
2591 })
|
paulo@89
|
2592 .on('mousemove', function() {
|
paulo@89
|
2593 dispatch.elementMousemove({
|
paulo@89
|
2594 value: measurez[0],
|
paulo@89
|
2595 label: measureLabelz[0] || 'Current',
|
paulo@89
|
2596 color: d3.select(this).style("fill")
|
paulo@89
|
2597 })
|
paulo@89
|
2598 })
|
paulo@89
|
2599 .on('mouseout', function() {
|
paulo@89
|
2600 dispatch.elementMouseout({
|
paulo@89
|
2601 value: measurez[0],
|
paulo@89
|
2602 label: measureLabelz[0] || 'Current',
|
paulo@89
|
2603 color: d3.select(this).style("fill")
|
paulo@89
|
2604 })
|
paulo@89
|
2605 });
|
paulo@89
|
2606
|
paulo@89
|
2607 var h3 = availableHeight / 6;
|
paulo@89
|
2608
|
paulo@89
|
2609 var markerData = markerz.map( function(marker, index) {
|
paulo@89
|
2610 return {value: marker, label: markerLabelz[index]}
|
paulo@89
|
2611 });
|
paulo@89
|
2612 gEnter
|
paulo@89
|
2613 .selectAll("path.nv-markerTriangle")
|
paulo@89
|
2614 .data(markerData)
|
paulo@89
|
2615 .enter()
|
paulo@89
|
2616 .append('path')
|
paulo@89
|
2617 .attr('class', 'nv-markerTriangle')
|
paulo@89
|
2618 .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' })
|
paulo@89
|
2619 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
|
paulo@89
|
2620 .on('mouseover', function(d) {
|
paulo@89
|
2621 dispatch.elementMouseover({
|
paulo@89
|
2622 value: d.value,
|
paulo@89
|
2623 label: d.label || 'Previous',
|
paulo@89
|
2624 color: d3.select(this).style("fill"),
|
paulo@89
|
2625 pos: [x1(d.value), availableHeight/2]
|
paulo@89
|
2626 })
|
paulo@89
|
2627
|
paulo@89
|
2628 })
|
paulo@89
|
2629 .on('mousemove', function(d) {
|
paulo@89
|
2630 dispatch.elementMousemove({
|
paulo@89
|
2631 value: d.value,
|
paulo@89
|
2632 label: d.label || 'Previous',
|
paulo@89
|
2633 color: d3.select(this).style("fill")
|
paulo@89
|
2634 })
|
paulo@89
|
2635 })
|
paulo@89
|
2636 .on('mouseout', function(d, i) {
|
paulo@89
|
2637 dispatch.elementMouseout({
|
paulo@89
|
2638 value: d.value,
|
paulo@89
|
2639 label: d.label || 'Previous',
|
paulo@89
|
2640 color: d3.select(this).style("fill")
|
paulo@89
|
2641 })
|
paulo@89
|
2642 });
|
paulo@89
|
2643
|
paulo@89
|
2644 wrap.selectAll('.nv-range')
|
paulo@89
|
2645 .on('mouseover', function(d,i) {
|
paulo@89
|
2646 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
|
paulo@89
|
2647 dispatch.elementMouseover({
|
paulo@89
|
2648 value: d,
|
paulo@89
|
2649 label: label,
|
paulo@89
|
2650 color: d3.select(this).style("fill")
|
paulo@89
|
2651 })
|
paulo@89
|
2652 })
|
paulo@89
|
2653 .on('mousemove', function() {
|
paulo@89
|
2654 dispatch.elementMousemove({
|
paulo@89
|
2655 value: measurez[0],
|
paulo@89
|
2656 label: measureLabelz[0] || 'Previous',
|
paulo@89
|
2657 color: d3.select(this).style("fill")
|
paulo@89
|
2658 })
|
paulo@89
|
2659 })
|
paulo@89
|
2660 .on('mouseout', function(d,i) {
|
paulo@89
|
2661 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
|
paulo@89
|
2662 dispatch.elementMouseout({
|
paulo@89
|
2663 value: d,
|
paulo@89
|
2664 label: label,
|
paulo@89
|
2665 color: d3.select(this).style("fill")
|
paulo@89
|
2666 })
|
paulo@89
|
2667 });
|
paulo@89
|
2668 });
|
paulo@89
|
2669
|
paulo@89
|
2670 return chart;
|
paulo@89
|
2671 }
|
paulo@89
|
2672
|
paulo@89
|
2673 //============================================================
|
paulo@89
|
2674 // Expose Public Variables
|
paulo@89
|
2675 //------------------------------------------------------------
|
paulo@89
|
2676
|
paulo@89
|
2677 chart.dispatch = dispatch;
|
paulo@89
|
2678 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
2679
|
paulo@89
|
2680 chart._options = Object.create({}, {
|
paulo@89
|
2681 // simple options, just get/set the necessary values
|
paulo@89
|
2682 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
|
paulo@89
|
2683 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
|
paulo@89
|
2684 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
|
paulo@89
|
2685 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
|
paulo@89
|
2686 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
2687 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
2688 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
|
paulo@89
|
2689
|
paulo@89
|
2690 // options that require extra logic in the setter
|
paulo@89
|
2691 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
2692 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
2693 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
2694 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
2695 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
2696 }},
|
paulo@89
|
2697 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
|
paulo@89
|
2698 orient = _;
|
paulo@89
|
2699 reverse = orient == 'right' || orient == 'bottom';
|
paulo@89
|
2700 }},
|
paulo@89
|
2701 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
2702 color = nv.utils.getColor(_);
|
paulo@89
|
2703 }}
|
paulo@89
|
2704 });
|
paulo@89
|
2705
|
paulo@89
|
2706 nv.utils.initOptions(chart);
|
paulo@89
|
2707 return chart;
|
paulo@89
|
2708 };
|
paulo@89
|
2709
|
paulo@89
|
2710
|
paulo@89
|
2711
|
paulo@89
|
2712 // Chart design based on the recommendations of Stephen Few. Implementation
|
paulo@89
|
2713 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
|
paulo@89
|
2714 // http://projects.instantcognition.com/protovis/bulletchart/
|
paulo@89
|
2715 nv.models.bulletChart = function() {
|
paulo@89
|
2716 "use strict";
|
paulo@89
|
2717
|
paulo@89
|
2718 //============================================================
|
paulo@89
|
2719 // Public Variables with Default Settings
|
paulo@89
|
2720 //------------------------------------------------------------
|
paulo@89
|
2721
|
paulo@89
|
2722 var bullet = nv.models.bullet();
|
paulo@89
|
2723 var tooltip = nv.models.tooltip();
|
paulo@89
|
2724
|
paulo@89
|
2725 var orient = 'left' // TODO top & bottom
|
paulo@89
|
2726 , reverse = false
|
paulo@89
|
2727 , margin = {top: 5, right: 40, bottom: 20, left: 120}
|
paulo@89
|
2728 , ranges = function(d) { return d.ranges }
|
paulo@89
|
2729 , markers = function(d) { return d.markers ? d.markers : [0] }
|
paulo@89
|
2730 , measures = function(d) { return d.measures }
|
paulo@89
|
2731 , width = null
|
paulo@89
|
2732 , height = 55
|
paulo@89
|
2733 , tickFormat = null
|
paulo@89
|
2734 , ticks = null
|
paulo@89
|
2735 , noData = null
|
paulo@89
|
2736 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
|
paulo@89
|
2737 ;
|
paulo@89
|
2738
|
paulo@89
|
2739 tooltip.duration(0).headerEnabled(false);
|
paulo@89
|
2740
|
paulo@89
|
2741 function chart(selection) {
|
paulo@89
|
2742 selection.each(function(d, i) {
|
paulo@89
|
2743 var container = d3.select(this);
|
paulo@89
|
2744 nv.utils.initSVG(container);
|
paulo@89
|
2745
|
paulo@89
|
2746 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
2747 availableHeight = height - margin.top - margin.bottom,
|
paulo@89
|
2748 that = this;
|
paulo@89
|
2749
|
paulo@89
|
2750 chart.update = function() { chart(selection) };
|
paulo@89
|
2751 chart.container = this;
|
paulo@89
|
2752
|
paulo@89
|
2753 // Display No Data message if there's nothing to show.
|
paulo@89
|
2754 if (!d || !ranges.call(this, d, i)) {
|
paulo@89
|
2755 nv.utils.noData(chart, container)
|
paulo@89
|
2756 return chart;
|
paulo@89
|
2757 } else {
|
paulo@89
|
2758 container.selectAll('.nv-noData').remove();
|
paulo@89
|
2759 }
|
paulo@89
|
2760
|
paulo@89
|
2761 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
|
paulo@89
|
2762 markerz = markers.call(this, d, i).slice().sort(d3.descending),
|
paulo@89
|
2763 measurez = measures.call(this, d, i).slice().sort(d3.descending);
|
paulo@89
|
2764
|
paulo@89
|
2765 // Setup containers and skeleton of chart
|
paulo@89
|
2766 var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
|
paulo@89
|
2767 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
|
paulo@89
|
2768 var gEnter = wrapEnter.append('g');
|
paulo@89
|
2769 var g = wrap.select('g');
|
paulo@89
|
2770
|
paulo@89
|
2771 gEnter.append('g').attr('class', 'nv-bulletWrap');
|
paulo@89
|
2772 gEnter.append('g').attr('class', 'nv-titles');
|
paulo@89
|
2773
|
paulo@89
|
2774 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
2775
|
paulo@89
|
2776 // Compute the new x-scale.
|
paulo@89
|
2777 var x1 = d3.scale.linear()
|
paulo@89
|
2778 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
|
paulo@89
|
2779 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
|
paulo@89
|
2780
|
paulo@89
|
2781 // Retrieve the old x-scale, if this is an update.
|
paulo@89
|
2782 var x0 = this.__chart__ || d3.scale.linear()
|
paulo@89
|
2783 .domain([0, Infinity])
|
paulo@89
|
2784 .range(x1.range());
|
paulo@89
|
2785
|
paulo@89
|
2786 // Stash the new scale.
|
paulo@89
|
2787 this.__chart__ = x1;
|
paulo@89
|
2788
|
paulo@89
|
2789 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
|
paulo@89
|
2790 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
|
paulo@89
|
2791
|
paulo@89
|
2792 var title = gEnter.select('.nv-titles').append('g')
|
paulo@89
|
2793 .attr('text-anchor', 'end')
|
paulo@89
|
2794 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
|
paulo@89
|
2795 title.append('text')
|
paulo@89
|
2796 .attr('class', 'nv-title')
|
paulo@89
|
2797 .text(function(d) { return d.title; });
|
paulo@89
|
2798
|
paulo@89
|
2799 title.append('text')
|
paulo@89
|
2800 .attr('class', 'nv-subtitle')
|
paulo@89
|
2801 .attr('dy', '1em')
|
paulo@89
|
2802 .text(function(d) { return d.subtitle; });
|
paulo@89
|
2803
|
paulo@89
|
2804 bullet
|
paulo@89
|
2805 .width(availableWidth)
|
paulo@89
|
2806 .height(availableHeight)
|
paulo@89
|
2807
|
paulo@89
|
2808 var bulletWrap = g.select('.nv-bulletWrap');
|
paulo@89
|
2809 d3.transition(bulletWrap).call(bullet);
|
paulo@89
|
2810
|
paulo@89
|
2811 // Compute the tick format.
|
paulo@89
|
2812 var format = tickFormat || x1.tickFormat( availableWidth / 100 );
|
paulo@89
|
2813
|
paulo@89
|
2814 // Update the tick groups.
|
paulo@89
|
2815 var tick = g.selectAll('g.nv-tick')
|
paulo@89
|
2816 .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) {
|
paulo@89
|
2817 return this.textContent || format(d);
|
paulo@89
|
2818 });
|
paulo@89
|
2819
|
paulo@89
|
2820 // Initialize the ticks with the old scale, x0.
|
paulo@89
|
2821 var tickEnter = tick.enter().append('g')
|
paulo@89
|
2822 .attr('class', 'nv-tick')
|
paulo@89
|
2823 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
|
paulo@89
|
2824 .style('opacity', 1e-6);
|
paulo@89
|
2825
|
paulo@89
|
2826 tickEnter.append('line')
|
paulo@89
|
2827 .attr('y1', availableHeight)
|
paulo@89
|
2828 .attr('y2', availableHeight * 7 / 6);
|
paulo@89
|
2829
|
paulo@89
|
2830 tickEnter.append('text')
|
paulo@89
|
2831 .attr('text-anchor', 'middle')
|
paulo@89
|
2832 .attr('dy', '1em')
|
paulo@89
|
2833 .attr('y', availableHeight * 7 / 6)
|
paulo@89
|
2834 .text(format);
|
paulo@89
|
2835
|
paulo@89
|
2836 // Transition the updating ticks to the new scale, x1.
|
paulo@89
|
2837 var tickUpdate = d3.transition(tick)
|
paulo@89
|
2838 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
|
paulo@89
|
2839 .style('opacity', 1);
|
paulo@89
|
2840
|
paulo@89
|
2841 tickUpdate.select('line')
|
paulo@89
|
2842 .attr('y1', availableHeight)
|
paulo@89
|
2843 .attr('y2', availableHeight * 7 / 6);
|
paulo@89
|
2844
|
paulo@89
|
2845 tickUpdate.select('text')
|
paulo@89
|
2846 .attr('y', availableHeight * 7 / 6);
|
paulo@89
|
2847
|
paulo@89
|
2848 // Transition the exiting ticks to the new scale, x1.
|
paulo@89
|
2849 d3.transition(tick.exit())
|
paulo@89
|
2850 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
|
paulo@89
|
2851 .style('opacity', 1e-6)
|
paulo@89
|
2852 .remove();
|
paulo@89
|
2853 });
|
paulo@89
|
2854
|
paulo@89
|
2855 d3.timer.flush();
|
paulo@89
|
2856 return chart;
|
paulo@89
|
2857 }
|
paulo@89
|
2858
|
paulo@89
|
2859 //============================================================
|
paulo@89
|
2860 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
2861 //------------------------------------------------------------
|
paulo@89
|
2862
|
paulo@89
|
2863 bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
2864 evt['series'] = {
|
paulo@89
|
2865 key: evt.label,
|
paulo@89
|
2866 value: evt.value,
|
paulo@89
|
2867 color: evt.color
|
paulo@89
|
2868 };
|
paulo@89
|
2869 tooltip.data(evt).hidden(false);
|
paulo@89
|
2870 });
|
paulo@89
|
2871
|
paulo@89
|
2872 bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
2873 tooltip.hidden(true);
|
paulo@89
|
2874 });
|
paulo@89
|
2875
|
paulo@89
|
2876 bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
2877 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
2878 });
|
paulo@89
|
2879
|
paulo@89
|
2880 //============================================================
|
paulo@89
|
2881 // Expose Public Variables
|
paulo@89
|
2882 //------------------------------------------------------------
|
paulo@89
|
2883
|
paulo@89
|
2884 chart.bullet = bullet;
|
paulo@89
|
2885 chart.dispatch = dispatch;
|
paulo@89
|
2886 chart.tooltip = tooltip;
|
paulo@89
|
2887
|
paulo@89
|
2888 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
2889
|
paulo@89
|
2890 chart._options = Object.create({}, {
|
paulo@89
|
2891 // simple options, just get/set the necessary values
|
paulo@89
|
2892 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
|
paulo@89
|
2893 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
|
paulo@89
|
2894 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
|
paulo@89
|
2895 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
2896 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
2897 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
|
paulo@89
|
2898 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
|
paulo@89
|
2899 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
2900
|
paulo@89
|
2901 // deprecated options
|
paulo@89
|
2902 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
2903 // deprecated after 1.7.1
|
paulo@89
|
2904 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
2905 tooltip.enabled(!!_);
|
paulo@89
|
2906 }},
|
paulo@89
|
2907 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
2908 // deprecated after 1.7.1
|
paulo@89
|
2909 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
2910 tooltip.contentGenerator(_);
|
paulo@89
|
2911 }},
|
paulo@89
|
2912
|
paulo@89
|
2913 // options that require extra logic in the setter
|
paulo@89
|
2914 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
2915 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
2916 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
2917 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
2918 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
2919 }},
|
paulo@89
|
2920 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
|
paulo@89
|
2921 orient = _;
|
paulo@89
|
2922 reverse = orient == 'right' || orient == 'bottom';
|
paulo@89
|
2923 }}
|
paulo@89
|
2924 });
|
paulo@89
|
2925
|
paulo@89
|
2926 nv.utils.inheritOptions(chart, bullet);
|
paulo@89
|
2927 nv.utils.initOptions(chart);
|
paulo@89
|
2928
|
paulo@89
|
2929 return chart;
|
paulo@89
|
2930 };
|
paulo@89
|
2931
|
paulo@89
|
2932
|
paulo@89
|
2933
|
paulo@89
|
2934 nv.models.candlestickBar = function() {
|
paulo@89
|
2935 "use strict";
|
paulo@89
|
2936
|
paulo@89
|
2937 //============================================================
|
paulo@89
|
2938 // Public Variables with Default Settings
|
paulo@89
|
2939 //------------------------------------------------------------
|
paulo@89
|
2940
|
paulo@89
|
2941 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
2942 , width = null
|
paulo@89
|
2943 , height = null
|
paulo@89
|
2944 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
2945 , container
|
paulo@89
|
2946 , x = d3.scale.linear()
|
paulo@89
|
2947 , y = d3.scale.linear()
|
paulo@89
|
2948 , getX = function(d) { return d.x }
|
paulo@89
|
2949 , getY = function(d) { return d.y }
|
paulo@89
|
2950 , getOpen = function(d) { return d.open }
|
paulo@89
|
2951 , getClose = function(d) { return d.close }
|
paulo@89
|
2952 , getHigh = function(d) { return d.high }
|
paulo@89
|
2953 , getLow = function(d) { return d.low }
|
paulo@89
|
2954 , forceX = []
|
paulo@89
|
2955 , forceY = []
|
paulo@89
|
2956 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
|
paulo@89
|
2957 , clipEdge = true
|
paulo@89
|
2958 , color = nv.utils.defaultColor()
|
paulo@89
|
2959 , interactive = false
|
paulo@89
|
2960 , xDomain
|
paulo@89
|
2961 , yDomain
|
paulo@89
|
2962 , xRange
|
paulo@89
|
2963 , yRange
|
paulo@89
|
2964 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
|
paulo@89
|
2965 ;
|
paulo@89
|
2966
|
paulo@89
|
2967 //============================================================
|
paulo@89
|
2968 // Private Variables
|
paulo@89
|
2969 //------------------------------------------------------------
|
paulo@89
|
2970
|
paulo@89
|
2971 function chart(selection) {
|
paulo@89
|
2972 selection.each(function(data) {
|
paulo@89
|
2973 container = d3.select(this);
|
paulo@89
|
2974 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
2975 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
2976
|
paulo@89
|
2977 nv.utils.initSVG(container);
|
paulo@89
|
2978
|
paulo@89
|
2979 // Width of the candlestick bars.
|
paulo@89
|
2980 var barWidth = (availableWidth / data[0].values.length) * .45;
|
paulo@89
|
2981
|
paulo@89
|
2982 // Setup Scales
|
paulo@89
|
2983 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
|
paulo@89
|
2984
|
paulo@89
|
2985 if (padData)
|
paulo@89
|
2986 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
|
paulo@89
|
2987 else
|
paulo@89
|
2988 x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]);
|
paulo@89
|
2989
|
paulo@89
|
2990 y.domain(yDomain || [
|
paulo@89
|
2991 d3.min(data[0].values.map(getLow).concat(forceY)),
|
paulo@89
|
2992 d3.max(data[0].values.map(getHigh).concat(forceY))
|
paulo@89
|
2993 ]
|
paulo@89
|
2994 ).range(yRange || [availableHeight, 0]);
|
paulo@89
|
2995
|
paulo@89
|
2996 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
|
paulo@89
|
2997 if (x.domain()[0] === x.domain()[1])
|
paulo@89
|
2998 x.domain()[0] ?
|
paulo@89
|
2999 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
|
paulo@89
|
3000 : x.domain([-1,1]);
|
paulo@89
|
3001
|
paulo@89
|
3002 if (y.domain()[0] === y.domain()[1])
|
paulo@89
|
3003 y.domain()[0] ?
|
paulo@89
|
3004 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
|
paulo@89
|
3005 : y.domain([-1,1]);
|
paulo@89
|
3006
|
paulo@89
|
3007 // Setup containers and skeleton of chart
|
paulo@89
|
3008 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]);
|
paulo@89
|
3009 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar');
|
paulo@89
|
3010 var defsEnter = wrapEnter.append('defs');
|
paulo@89
|
3011 var gEnter = wrapEnter.append('g');
|
paulo@89
|
3012 var g = wrap.select('g');
|
paulo@89
|
3013
|
paulo@89
|
3014 gEnter.append('g').attr('class', 'nv-ticks');
|
paulo@89
|
3015
|
paulo@89
|
3016 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
3017
|
paulo@89
|
3018 container
|
paulo@89
|
3019 .on('click', function(d,i) {
|
paulo@89
|
3020 dispatch.chartClick({
|
paulo@89
|
3021 data: d,
|
paulo@89
|
3022 index: i,
|
paulo@89
|
3023 pos: d3.event,
|
paulo@89
|
3024 id: id
|
paulo@89
|
3025 });
|
paulo@89
|
3026 });
|
paulo@89
|
3027
|
paulo@89
|
3028 defsEnter.append('clipPath')
|
paulo@89
|
3029 .attr('id', 'nv-chart-clip-path-' + id)
|
paulo@89
|
3030 .append('rect');
|
paulo@89
|
3031
|
paulo@89
|
3032 wrap.select('#nv-chart-clip-path-' + id + ' rect')
|
paulo@89
|
3033 .attr('width', availableWidth)
|
paulo@89
|
3034 .attr('height', availableHeight);
|
paulo@89
|
3035
|
paulo@89
|
3036 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
|
paulo@89
|
3037
|
paulo@89
|
3038 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
|
paulo@89
|
3039 .data(function(d) { return d });
|
paulo@89
|
3040 ticks.exit().remove();
|
paulo@89
|
3041
|
paulo@89
|
3042 // The colors are currently controlled by CSS.
|
paulo@89
|
3043 var tickGroups = ticks.enter().append('g')
|
paulo@89
|
3044 .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i});
|
paulo@89
|
3045
|
paulo@89
|
3046 var lines = tickGroups.append('line')
|
paulo@89
|
3047 .attr('class', 'nv-candlestick-lines')
|
paulo@89
|
3048 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
|
paulo@89
|
3049 .attr('x1', 0)
|
paulo@89
|
3050 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
|
paulo@89
|
3051 .attr('x2', 0)
|
paulo@89
|
3052 .attr('y2', function(d, i) { return y(getLow(d, i)); });
|
paulo@89
|
3053
|
paulo@89
|
3054 var rects = tickGroups.append('rect')
|
paulo@89
|
3055 .attr('class', 'nv-candlestick-rects nv-bars')
|
paulo@89
|
3056 .attr('transform', function(d, i) {
|
paulo@89
|
3057 return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
|
paulo@89
|
3058 + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
|
paulo@89
|
3059 + ')';
|
paulo@89
|
3060 })
|
paulo@89
|
3061 .attr('x', 0)
|
paulo@89
|
3062 .attr('y', 0)
|
paulo@89
|
3063 .attr('width', barWidth)
|
paulo@89
|
3064 .attr('height', function(d, i) {
|
paulo@89
|
3065 var open = getOpen(d, i);
|
paulo@89
|
3066 var close = getClose(d, i);
|
paulo@89
|
3067 return open > close ? y(close) - y(open) : y(open) - y(close);
|
paulo@89
|
3068 });
|
paulo@89
|
3069
|
paulo@89
|
3070 container.selectAll('.nv-candlestick-lines').transition()
|
paulo@89
|
3071 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
|
paulo@89
|
3072 .attr('x1', 0)
|
paulo@89
|
3073 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
|
paulo@89
|
3074 .attr('x2', 0)
|
paulo@89
|
3075 .attr('y2', function(d, i) { return y(getLow(d, i)); });
|
paulo@89
|
3076
|
paulo@89
|
3077 container.selectAll('.nv-candlestick-rects').transition()
|
paulo@89
|
3078 .attr('transform', function(d, i) {
|
paulo@89
|
3079 return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
|
paulo@89
|
3080 + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
|
paulo@89
|
3081 + ')';
|
paulo@89
|
3082 })
|
paulo@89
|
3083 .attr('x', 0)
|
paulo@89
|
3084 .attr('y', 0)
|
paulo@89
|
3085 .attr('width', barWidth)
|
paulo@89
|
3086 .attr('height', function(d, i) {
|
paulo@89
|
3087 var open = getOpen(d, i);
|
paulo@89
|
3088 var close = getClose(d, i);
|
paulo@89
|
3089 return open > close ? y(close) - y(open) : y(open) - y(close);
|
paulo@89
|
3090 });
|
paulo@89
|
3091 });
|
paulo@89
|
3092
|
paulo@89
|
3093 return chart;
|
paulo@89
|
3094 }
|
paulo@89
|
3095
|
paulo@89
|
3096
|
paulo@89
|
3097 //Create methods to allow outside functions to highlight a specific bar.
|
paulo@89
|
3098 chart.highlightPoint = function(pointIndex, isHoverOver) {
|
paulo@89
|
3099 chart.clearHighlights();
|
paulo@89
|
3100 container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex)
|
paulo@89
|
3101 .classed("hover", isHoverOver)
|
paulo@89
|
3102 ;
|
paulo@89
|
3103 };
|
paulo@89
|
3104
|
paulo@89
|
3105 chart.clearHighlights = function() {
|
paulo@89
|
3106 container.select(".nv-candlestickBar .nv-tick.hover")
|
paulo@89
|
3107 .classed("hover", false)
|
paulo@89
|
3108 ;
|
paulo@89
|
3109 };
|
paulo@89
|
3110
|
paulo@89
|
3111 //============================================================
|
paulo@89
|
3112 // Expose Public Variables
|
paulo@89
|
3113 //------------------------------------------------------------
|
paulo@89
|
3114
|
paulo@89
|
3115 chart.dispatch = dispatch;
|
paulo@89
|
3116 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
3117
|
paulo@89
|
3118 chart._options = Object.create({}, {
|
paulo@89
|
3119 // simple options, just get/set the necessary values
|
paulo@89
|
3120 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
3121 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
3122 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
3123 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
3124 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
3125 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
3126 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
3127 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
3128 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
|
paulo@89
|
3129 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
|
paulo@89
|
3130 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
|
paulo@89
|
3131 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
|
paulo@89
|
3132 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
3133 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
|
paulo@89
|
3134
|
paulo@89
|
3135 x: {get: function(){return getX;}, set: function(_){getX=_;}},
|
paulo@89
|
3136 y: {get: function(){return getY;}, set: function(_){getY=_;}},
|
paulo@89
|
3137 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
|
paulo@89
|
3138 close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
|
paulo@89
|
3139 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
|
paulo@89
|
3140 low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
|
paulo@89
|
3141
|
paulo@89
|
3142 // options that require extra logic in the setter
|
paulo@89
|
3143 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
3144 margin.top = _.top != undefined ? _.top : margin.top;
|
paulo@89
|
3145 margin.right = _.right != undefined ? _.right : margin.right;
|
paulo@89
|
3146 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
|
paulo@89
|
3147 margin.left = _.left != undefined ? _.left : margin.left;
|
paulo@89
|
3148 }},
|
paulo@89
|
3149 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
3150 color = nv.utils.getColor(_);
|
paulo@89
|
3151 }}
|
paulo@89
|
3152 });
|
paulo@89
|
3153
|
paulo@89
|
3154 nv.utils.initOptions(chart);
|
paulo@89
|
3155 return chart;
|
paulo@89
|
3156 };
|
paulo@89
|
3157
|
paulo@89
|
3158 nv.models.cumulativeLineChart = function() {
|
paulo@89
|
3159 "use strict";
|
paulo@89
|
3160
|
paulo@89
|
3161 //============================================================
|
paulo@89
|
3162 // Public Variables with Default Settings
|
paulo@89
|
3163 //------------------------------------------------------------
|
paulo@89
|
3164
|
paulo@89
|
3165 var lines = nv.models.line()
|
paulo@89
|
3166 , xAxis = nv.models.axis()
|
paulo@89
|
3167 , yAxis = nv.models.axis()
|
paulo@89
|
3168 , legend = nv.models.legend()
|
paulo@89
|
3169 , controls = nv.models.legend()
|
paulo@89
|
3170 , interactiveLayer = nv.interactiveGuideline()
|
paulo@89
|
3171 , tooltip = nv.models.tooltip()
|
paulo@89
|
3172 ;
|
paulo@89
|
3173
|
paulo@89
|
3174 var margin = {top: 30, right: 30, bottom: 50, left: 60}
|
paulo@89
|
3175 , color = nv.utils.defaultColor()
|
paulo@89
|
3176 , width = null
|
paulo@89
|
3177 , height = null
|
paulo@89
|
3178 , showLegend = true
|
paulo@89
|
3179 , showXAxis = true
|
paulo@89
|
3180 , showYAxis = true
|
paulo@89
|
3181 , rightAlignYAxis = false
|
paulo@89
|
3182 , showControls = true
|
paulo@89
|
3183 , useInteractiveGuideline = false
|
paulo@89
|
3184 , rescaleY = true
|
paulo@89
|
3185 , x //can be accessed via chart.xScale()
|
paulo@89
|
3186 , y //can be accessed via chart.yScale()
|
paulo@89
|
3187 , id = lines.id()
|
paulo@89
|
3188 , state = nv.utils.state()
|
paulo@89
|
3189 , defaultState = null
|
paulo@89
|
3190 , noData = null
|
paulo@89
|
3191 , average = function(d) { return d.average }
|
paulo@89
|
3192 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
|
paulo@89
|
3193 , transitionDuration = 250
|
paulo@89
|
3194 , duration = 250
|
paulo@89
|
3195 , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
|
paulo@89
|
3196 ;
|
paulo@89
|
3197
|
paulo@89
|
3198 state.index = 0;
|
paulo@89
|
3199 state.rescaleY = rescaleY;
|
paulo@89
|
3200
|
paulo@89
|
3201 xAxis.orient('bottom').tickPadding(7);
|
paulo@89
|
3202 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
|
paulo@89
|
3203
|
paulo@89
|
3204 tooltip.valueFormatter(function(d, i) {
|
paulo@89
|
3205 return yAxis.tickFormat()(d, i);
|
paulo@89
|
3206 }).headerFormatter(function(d, i) {
|
paulo@89
|
3207 return xAxis.tickFormat()(d, i);
|
paulo@89
|
3208 });
|
paulo@89
|
3209
|
paulo@89
|
3210 controls.updateState(false);
|
paulo@89
|
3211
|
paulo@89
|
3212 //============================================================
|
paulo@89
|
3213 // Private Variables
|
paulo@89
|
3214 //------------------------------------------------------------
|
paulo@89
|
3215
|
paulo@89
|
3216 var dx = d3.scale.linear()
|
paulo@89
|
3217 , index = {i: 0, x: 0}
|
paulo@89
|
3218 , renderWatch = nv.utils.renderWatch(dispatch, duration)
|
paulo@89
|
3219 ;
|
paulo@89
|
3220
|
paulo@89
|
3221 var stateGetter = function(data) {
|
paulo@89
|
3222 return function(){
|
paulo@89
|
3223 return {
|
paulo@89
|
3224 active: data.map(function(d) { return !d.disabled }),
|
paulo@89
|
3225 index: index.i,
|
paulo@89
|
3226 rescaleY: rescaleY
|
paulo@89
|
3227 };
|
paulo@89
|
3228 }
|
paulo@89
|
3229 };
|
paulo@89
|
3230
|
paulo@89
|
3231 var stateSetter = function(data) {
|
paulo@89
|
3232 return function(state) {
|
paulo@89
|
3233 if (state.index !== undefined)
|
paulo@89
|
3234 index.i = state.index;
|
paulo@89
|
3235 if (state.rescaleY !== undefined)
|
paulo@89
|
3236 rescaleY = state.rescaleY;
|
paulo@89
|
3237 if (state.active !== undefined)
|
paulo@89
|
3238 data.forEach(function(series,i) {
|
paulo@89
|
3239 series.disabled = !state.active[i];
|
paulo@89
|
3240 });
|
paulo@89
|
3241 }
|
paulo@89
|
3242 };
|
paulo@89
|
3243
|
paulo@89
|
3244 function chart(selection) {
|
paulo@89
|
3245 renderWatch.reset();
|
paulo@89
|
3246 renderWatch.models(lines);
|
paulo@89
|
3247 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
3248 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
3249 selection.each(function(data) {
|
paulo@89
|
3250 var container = d3.select(this);
|
paulo@89
|
3251 nv.utils.initSVG(container);
|
paulo@89
|
3252 container.classed('nv-chart-' + id, true);
|
paulo@89
|
3253 var that = this;
|
paulo@89
|
3254
|
paulo@89
|
3255 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
3256 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
3257
|
paulo@89
|
3258 chart.update = function() {
|
paulo@89
|
3259 if (duration === 0)
|
paulo@89
|
3260 container.call(chart);
|
paulo@89
|
3261 else
|
paulo@89
|
3262 container.transition().duration(duration).call(chart)
|
paulo@89
|
3263 };
|
paulo@89
|
3264 chart.container = this;
|
paulo@89
|
3265
|
paulo@89
|
3266 state
|
paulo@89
|
3267 .setter(stateSetter(data), chart.update)
|
paulo@89
|
3268 .getter(stateGetter(data))
|
paulo@89
|
3269 .update();
|
paulo@89
|
3270
|
paulo@89
|
3271 // DEPRECATED set state.disableddisabled
|
paulo@89
|
3272 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
3273
|
paulo@89
|
3274 if (!defaultState) {
|
paulo@89
|
3275 var key;
|
paulo@89
|
3276 defaultState = {};
|
paulo@89
|
3277 for (key in state) {
|
paulo@89
|
3278 if (state[key] instanceof Array)
|
paulo@89
|
3279 defaultState[key] = state[key].slice(0);
|
paulo@89
|
3280 else
|
paulo@89
|
3281 defaultState[key] = state[key];
|
paulo@89
|
3282 }
|
paulo@89
|
3283 }
|
paulo@89
|
3284
|
paulo@89
|
3285 var indexDrag = d3.behavior.drag()
|
paulo@89
|
3286 .on('dragstart', dragStart)
|
paulo@89
|
3287 .on('drag', dragMove)
|
paulo@89
|
3288 .on('dragend', dragEnd);
|
paulo@89
|
3289
|
paulo@89
|
3290
|
paulo@89
|
3291 function dragStart(d,i) {
|
paulo@89
|
3292 d3.select(chart.container)
|
paulo@89
|
3293 .style('cursor', 'ew-resize');
|
paulo@89
|
3294 }
|
paulo@89
|
3295
|
paulo@89
|
3296 function dragMove(d,i) {
|
paulo@89
|
3297 index.x = d3.event.x;
|
paulo@89
|
3298 index.i = Math.round(dx.invert(index.x));
|
paulo@89
|
3299 updateZero();
|
paulo@89
|
3300 }
|
paulo@89
|
3301
|
paulo@89
|
3302 function dragEnd(d,i) {
|
paulo@89
|
3303 d3.select(chart.container)
|
paulo@89
|
3304 .style('cursor', 'auto');
|
paulo@89
|
3305
|
paulo@89
|
3306 // update state and send stateChange with new index
|
paulo@89
|
3307 state.index = index.i;
|
paulo@89
|
3308 dispatch.stateChange(state);
|
paulo@89
|
3309 }
|
paulo@89
|
3310
|
paulo@89
|
3311 // Display No Data message if there's nothing to show.
|
paulo@89
|
3312 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
3313 nv.utils.noData(chart, container)
|
paulo@89
|
3314 return chart;
|
paulo@89
|
3315 } else {
|
paulo@89
|
3316 container.selectAll('.nv-noData').remove();
|
paulo@89
|
3317 }
|
paulo@89
|
3318
|
paulo@89
|
3319 // Setup Scales
|
paulo@89
|
3320 x = lines.xScale();
|
paulo@89
|
3321 y = lines.yScale();
|
paulo@89
|
3322
|
paulo@89
|
3323 if (!rescaleY) {
|
paulo@89
|
3324 var seriesDomains = data
|
paulo@89
|
3325 .filter(function(series) { return !series.disabled })
|
paulo@89
|
3326 .map(function(series,i) {
|
paulo@89
|
3327 var initialDomain = d3.extent(series.values, lines.y());
|
paulo@89
|
3328
|
paulo@89
|
3329 //account for series being disabled when losing 95% or more
|
paulo@89
|
3330 if (initialDomain[0] < -.95) initialDomain[0] = -.95;
|
paulo@89
|
3331
|
paulo@89
|
3332 return [
|
paulo@89
|
3333 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
|
paulo@89
|
3334 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
|
paulo@89
|
3335 ];
|
paulo@89
|
3336 });
|
paulo@89
|
3337
|
paulo@89
|
3338 var completeDomain = [
|
paulo@89
|
3339 d3.min(seriesDomains, function(d) { return d[0] }),
|
paulo@89
|
3340 d3.max(seriesDomains, function(d) { return d[1] })
|
paulo@89
|
3341 ];
|
paulo@89
|
3342
|
paulo@89
|
3343 lines.yDomain(completeDomain);
|
paulo@89
|
3344 } else {
|
paulo@89
|
3345 lines.yDomain(null);
|
paulo@89
|
3346 }
|
paulo@89
|
3347
|
paulo@89
|
3348 dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
|
paulo@89
|
3349 .range([0, availableWidth])
|
paulo@89
|
3350 .clamp(true);
|
paulo@89
|
3351
|
paulo@89
|
3352 var data = indexify(index.i, data);
|
paulo@89
|
3353
|
paulo@89
|
3354 // Setup containers and skeleton of chart
|
paulo@89
|
3355 var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
|
paulo@89
|
3356 var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
|
paulo@89
|
3357 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
|
paulo@89
|
3358 var g = wrap.select('g');
|
paulo@89
|
3359
|
paulo@89
|
3360 gEnter.append('g').attr('class', 'nv-interactive');
|
paulo@89
|
3361 gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
|
paulo@89
|
3362 gEnter.append('g').attr('class', 'nv-y nv-axis');
|
paulo@89
|
3363 gEnter.append('g').attr('class', 'nv-background');
|
paulo@89
|
3364 gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
|
paulo@89
|
3365 gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
|
paulo@89
|
3366 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
3367 gEnter.append('g').attr('class', 'nv-controlsWrap');
|
paulo@89
|
3368
|
paulo@89
|
3369 // Legend
|
paulo@89
|
3370 if (showLegend) {
|
paulo@89
|
3371 legend.width(availableWidth);
|
paulo@89
|
3372
|
paulo@89
|
3373 g.select('.nv-legendWrap')
|
paulo@89
|
3374 .datum(data)
|
paulo@89
|
3375 .call(legend);
|
paulo@89
|
3376
|
paulo@89
|
3377 if ( margin.top != legend.height()) {
|
paulo@89
|
3378 margin.top = legend.height();
|
paulo@89
|
3379 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
3380 }
|
paulo@89
|
3381
|
paulo@89
|
3382 g.select('.nv-legendWrap')
|
paulo@89
|
3383 .attr('transform', 'translate(0,' + (-margin.top) +')')
|
paulo@89
|
3384 }
|
paulo@89
|
3385
|
paulo@89
|
3386 // Controls
|
paulo@89
|
3387 if (showControls) {
|
paulo@89
|
3388 var controlsData = [
|
paulo@89
|
3389 { key: 'Re-scale y-axis', disabled: !rescaleY }
|
paulo@89
|
3390 ];
|
paulo@89
|
3391
|
paulo@89
|
3392 controls
|
paulo@89
|
3393 .width(140)
|
paulo@89
|
3394 .color(['#444', '#444', '#444'])
|
paulo@89
|
3395 .rightAlign(false)
|
paulo@89
|
3396 .margin({top: 5, right: 0, bottom: 5, left: 20})
|
paulo@89
|
3397 ;
|
paulo@89
|
3398
|
paulo@89
|
3399 g.select('.nv-controlsWrap')
|
paulo@89
|
3400 .datum(controlsData)
|
paulo@89
|
3401 .attr('transform', 'translate(0,' + (-margin.top) +')')
|
paulo@89
|
3402 .call(controls);
|
paulo@89
|
3403 }
|
paulo@89
|
3404
|
paulo@89
|
3405 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
3406
|
paulo@89
|
3407 if (rightAlignYAxis) {
|
paulo@89
|
3408 g.select(".nv-y.nv-axis")
|
paulo@89
|
3409 .attr("transform", "translate(" + availableWidth + ",0)");
|
paulo@89
|
3410 }
|
paulo@89
|
3411
|
paulo@89
|
3412 // Show error if series goes below 100%
|
paulo@89
|
3413 var tempDisabled = data.filter(function(d) { return d.tempDisabled });
|
paulo@89
|
3414
|
paulo@89
|
3415 wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
|
paulo@89
|
3416 if (tempDisabled.length) {
|
paulo@89
|
3417 wrap.append('text').attr('class', 'tempDisabled')
|
paulo@89
|
3418 .attr('x', availableWidth / 2)
|
paulo@89
|
3419 .attr('y', '-.71em')
|
paulo@89
|
3420 .style('text-anchor', 'end')
|
paulo@89
|
3421 .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
|
paulo@89
|
3422 }
|
paulo@89
|
3423
|
paulo@89
|
3424 //Set up interactive layer
|
paulo@89
|
3425 if (useInteractiveGuideline) {
|
paulo@89
|
3426 interactiveLayer
|
paulo@89
|
3427 .width(availableWidth)
|
paulo@89
|
3428 .height(availableHeight)
|
paulo@89
|
3429 .margin({left:margin.left,top:margin.top})
|
paulo@89
|
3430 .svgContainer(container)
|
paulo@89
|
3431 .xScale(x);
|
paulo@89
|
3432 wrap.select(".nv-interactive").call(interactiveLayer);
|
paulo@89
|
3433 }
|
paulo@89
|
3434
|
paulo@89
|
3435 gEnter.select('.nv-background')
|
paulo@89
|
3436 .append('rect');
|
paulo@89
|
3437
|
paulo@89
|
3438 g.select('.nv-background rect')
|
paulo@89
|
3439 .attr('width', availableWidth)
|
paulo@89
|
3440 .attr('height', availableHeight);
|
paulo@89
|
3441
|
paulo@89
|
3442 lines
|
paulo@89
|
3443 //.x(function(d) { return d.x })
|
paulo@89
|
3444 .y(function(d) { return d.display.y })
|
paulo@89
|
3445 .width(availableWidth)
|
paulo@89
|
3446 .height(availableHeight)
|
paulo@89
|
3447 .color(data.map(function(d,i) {
|
paulo@89
|
3448 return d.color || color(d, i);
|
paulo@89
|
3449 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
|
paulo@89
|
3450
|
paulo@89
|
3451 var linesWrap = g.select('.nv-linesWrap')
|
paulo@89
|
3452 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
|
paulo@89
|
3453
|
paulo@89
|
3454 linesWrap.call(lines);
|
paulo@89
|
3455
|
paulo@89
|
3456 //Store a series index number in the data array.
|
paulo@89
|
3457 data.forEach(function(d,i) {
|
paulo@89
|
3458 d.seriesIndex = i;
|
paulo@89
|
3459 });
|
paulo@89
|
3460
|
paulo@89
|
3461 var avgLineData = data.filter(function(d) {
|
paulo@89
|
3462 return !d.disabled && !!average(d);
|
paulo@89
|
3463 });
|
paulo@89
|
3464
|
paulo@89
|
3465 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
|
paulo@89
|
3466 .data(avgLineData, function(d) { return d.key; });
|
paulo@89
|
3467
|
paulo@89
|
3468 var getAvgLineY = function(d) {
|
paulo@89
|
3469 //If average lines go off the svg element, clamp them to the svg bounds.
|
paulo@89
|
3470 var yVal = y(average(d));
|
paulo@89
|
3471 if (yVal < 0) return 0;
|
paulo@89
|
3472 if (yVal > availableHeight) return availableHeight;
|
paulo@89
|
3473 return yVal;
|
paulo@89
|
3474 };
|
paulo@89
|
3475
|
paulo@89
|
3476 avgLines.enter()
|
paulo@89
|
3477 .append('line')
|
paulo@89
|
3478 .style('stroke-width',2)
|
paulo@89
|
3479 .style('stroke-dasharray','10,10')
|
paulo@89
|
3480 .style('stroke',function (d,i) {
|
paulo@89
|
3481 return lines.color()(d,d.seriesIndex);
|
paulo@89
|
3482 })
|
paulo@89
|
3483 .attr('x1',0)
|
paulo@89
|
3484 .attr('x2',availableWidth)
|
paulo@89
|
3485 .attr('y1', getAvgLineY)
|
paulo@89
|
3486 .attr('y2', getAvgLineY);
|
paulo@89
|
3487
|
paulo@89
|
3488 avgLines
|
paulo@89
|
3489 .style('stroke-opacity',function(d){
|
paulo@89
|
3490 //If average lines go offscreen, make them transparent
|
paulo@89
|
3491 var yVal = y(average(d));
|
paulo@89
|
3492 if (yVal < 0 || yVal > availableHeight) return 0;
|
paulo@89
|
3493 return 1;
|
paulo@89
|
3494 })
|
paulo@89
|
3495 .attr('x1',0)
|
paulo@89
|
3496 .attr('x2',availableWidth)
|
paulo@89
|
3497 .attr('y1', getAvgLineY)
|
paulo@89
|
3498 .attr('y2', getAvgLineY);
|
paulo@89
|
3499
|
paulo@89
|
3500 avgLines.exit().remove();
|
paulo@89
|
3501
|
paulo@89
|
3502 //Create index line
|
paulo@89
|
3503 var indexLine = linesWrap.selectAll('.nv-indexLine')
|
paulo@89
|
3504 .data([index]);
|
paulo@89
|
3505 indexLine.enter().append('rect').attr('class', 'nv-indexLine')
|
paulo@89
|
3506 .attr('width', 3)
|
paulo@89
|
3507 .attr('x', -2)
|
paulo@89
|
3508 .attr('fill', 'red')
|
paulo@89
|
3509 .attr('fill-opacity', .5)
|
paulo@89
|
3510 .style("pointer-events","all")
|
paulo@89
|
3511 .call(indexDrag);
|
paulo@89
|
3512
|
paulo@89
|
3513 indexLine
|
paulo@89
|
3514 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
|
paulo@89
|
3515 .attr('height', availableHeight);
|
paulo@89
|
3516
|
paulo@89
|
3517 // Setup Axes
|
paulo@89
|
3518 if (showXAxis) {
|
paulo@89
|
3519 xAxis
|
paulo@89
|
3520 .scale(x)
|
paulo@89
|
3521 ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
|
paulo@89
|
3522 .tickSize(-availableHeight, 0);
|
paulo@89
|
3523
|
paulo@89
|
3524 g.select('.nv-x.nv-axis')
|
paulo@89
|
3525 .attr('transform', 'translate(0,' + y.range()[0] + ')');
|
paulo@89
|
3526 g.select('.nv-x.nv-axis')
|
paulo@89
|
3527 .call(xAxis);
|
paulo@89
|
3528 }
|
paulo@89
|
3529
|
paulo@89
|
3530 if (showYAxis) {
|
paulo@89
|
3531 yAxis
|
paulo@89
|
3532 .scale(y)
|
paulo@89
|
3533 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
|
paulo@89
|
3534 .tickSize( -availableWidth, 0);
|
paulo@89
|
3535
|
paulo@89
|
3536 g.select('.nv-y.nv-axis')
|
paulo@89
|
3537 .call(yAxis);
|
paulo@89
|
3538 }
|
paulo@89
|
3539
|
paulo@89
|
3540 //============================================================
|
paulo@89
|
3541 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
3542 //------------------------------------------------------------
|
paulo@89
|
3543
|
paulo@89
|
3544 function updateZero() {
|
paulo@89
|
3545 indexLine
|
paulo@89
|
3546 .data([index]);
|
paulo@89
|
3547
|
paulo@89
|
3548 //When dragging the index line, turn off line transitions.
|
paulo@89
|
3549 // Then turn them back on when done dragging.
|
paulo@89
|
3550 var oldDuration = chart.duration();
|
paulo@89
|
3551 chart.duration(0);
|
paulo@89
|
3552 chart.update();
|
paulo@89
|
3553 chart.duration(oldDuration);
|
paulo@89
|
3554 }
|
paulo@89
|
3555
|
paulo@89
|
3556 g.select('.nv-background rect')
|
paulo@89
|
3557 .on('click', function() {
|
paulo@89
|
3558 index.x = d3.mouse(this)[0];
|
paulo@89
|
3559 index.i = Math.round(dx.invert(index.x));
|
paulo@89
|
3560
|
paulo@89
|
3561 // update state and send stateChange with new index
|
paulo@89
|
3562 state.index = index.i;
|
paulo@89
|
3563 dispatch.stateChange(state);
|
paulo@89
|
3564
|
paulo@89
|
3565 updateZero();
|
paulo@89
|
3566 });
|
paulo@89
|
3567
|
paulo@89
|
3568 lines.dispatch.on('elementClick', function(e) {
|
paulo@89
|
3569 index.i = e.pointIndex;
|
paulo@89
|
3570 index.x = dx(index.i);
|
paulo@89
|
3571
|
paulo@89
|
3572 // update state and send stateChange with new index
|
paulo@89
|
3573 state.index = index.i;
|
paulo@89
|
3574 dispatch.stateChange(state);
|
paulo@89
|
3575
|
paulo@89
|
3576 updateZero();
|
paulo@89
|
3577 });
|
paulo@89
|
3578
|
paulo@89
|
3579 controls.dispatch.on('legendClick', function(d,i) {
|
paulo@89
|
3580 d.disabled = !d.disabled;
|
paulo@89
|
3581 rescaleY = !d.disabled;
|
paulo@89
|
3582
|
paulo@89
|
3583 state.rescaleY = rescaleY;
|
paulo@89
|
3584 dispatch.stateChange(state);
|
paulo@89
|
3585 chart.update();
|
paulo@89
|
3586 });
|
paulo@89
|
3587
|
paulo@89
|
3588 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
3589 for (var key in newState)
|
paulo@89
|
3590 state[key] = newState[key];
|
paulo@89
|
3591 dispatch.stateChange(state);
|
paulo@89
|
3592 chart.update();
|
paulo@89
|
3593 });
|
paulo@89
|
3594
|
paulo@89
|
3595 interactiveLayer.dispatch.on('elementMousemove', function(e) {
|
paulo@89
|
3596 lines.clearHighlights();
|
paulo@89
|
3597 var singlePoint, pointIndex, pointXLocation, allData = [];
|
paulo@89
|
3598
|
paulo@89
|
3599 data
|
paulo@89
|
3600 .filter(function(series, i) {
|
paulo@89
|
3601 series.seriesIndex = i;
|
paulo@89
|
3602 return !series.disabled;
|
paulo@89
|
3603 })
|
paulo@89
|
3604 .forEach(function(series,i) {
|
paulo@89
|
3605 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
|
paulo@89
|
3606 lines.highlightPoint(i, pointIndex, true);
|
paulo@89
|
3607 var point = series.values[pointIndex];
|
paulo@89
|
3608 if (typeof point === 'undefined') return;
|
paulo@89
|
3609 if (typeof singlePoint === 'undefined') singlePoint = point;
|
paulo@89
|
3610 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
|
paulo@89
|
3611 allData.push({
|
paulo@89
|
3612 key: series.key,
|
paulo@89
|
3613 value: chart.y()(point, pointIndex),
|
paulo@89
|
3614 color: color(series,series.seriesIndex)
|
paulo@89
|
3615 });
|
paulo@89
|
3616 });
|
paulo@89
|
3617
|
paulo@89
|
3618 //Highlight the tooltip entry based on which point the mouse is closest to.
|
paulo@89
|
3619 if (allData.length > 2) {
|
paulo@89
|
3620 var yValue = chart.yScale().invert(e.mouseY);
|
paulo@89
|
3621 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
|
paulo@89
|
3622 var threshold = 0.03 * domainExtent;
|
paulo@89
|
3623 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
|
paulo@89
|
3624 if (indexToHighlight !== null)
|
paulo@89
|
3625 allData[indexToHighlight].highlight = true;
|
paulo@89
|
3626 }
|
paulo@89
|
3627
|
paulo@89
|
3628 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
|
paulo@89
|
3629 interactiveLayer.tooltip
|
paulo@89
|
3630 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
|
paulo@89
|
3631 .chartContainer(that.parentNode)
|
paulo@89
|
3632 .valueFormatter(function(d,i) {
|
paulo@89
|
3633 return yAxis.tickFormat()(d);
|
paulo@89
|
3634 })
|
paulo@89
|
3635 .data(
|
paulo@89
|
3636 {
|
paulo@89
|
3637 value: xValue,
|
paulo@89
|
3638 series: allData
|
paulo@89
|
3639 }
|
paulo@89
|
3640 )();
|
paulo@89
|
3641
|
paulo@89
|
3642 interactiveLayer.renderGuideLine(pointXLocation);
|
paulo@89
|
3643 });
|
paulo@89
|
3644
|
paulo@89
|
3645 interactiveLayer.dispatch.on("elementMouseout",function(e) {
|
paulo@89
|
3646 lines.clearHighlights();
|
paulo@89
|
3647 });
|
paulo@89
|
3648
|
paulo@89
|
3649 // Update chart from a state object passed to event handler
|
paulo@89
|
3650 dispatch.on('changeState', function(e) {
|
paulo@89
|
3651 if (typeof e.disabled !== 'undefined') {
|
paulo@89
|
3652 data.forEach(function(series,i) {
|
paulo@89
|
3653 series.disabled = e.disabled[i];
|
paulo@89
|
3654 });
|
paulo@89
|
3655
|
paulo@89
|
3656 state.disabled = e.disabled;
|
paulo@89
|
3657 }
|
paulo@89
|
3658
|
paulo@89
|
3659 if (typeof e.index !== 'undefined') {
|
paulo@89
|
3660 index.i = e.index;
|
paulo@89
|
3661 index.x = dx(index.i);
|
paulo@89
|
3662
|
paulo@89
|
3663 state.index = e.index;
|
paulo@89
|
3664
|
paulo@89
|
3665 indexLine
|
paulo@89
|
3666 .data([index]);
|
paulo@89
|
3667 }
|
paulo@89
|
3668
|
paulo@89
|
3669 if (typeof e.rescaleY !== 'undefined') {
|
paulo@89
|
3670 rescaleY = e.rescaleY;
|
paulo@89
|
3671 }
|
paulo@89
|
3672
|
paulo@89
|
3673 chart.update();
|
paulo@89
|
3674 });
|
paulo@89
|
3675
|
paulo@89
|
3676 });
|
paulo@89
|
3677
|
paulo@89
|
3678 renderWatch.renderEnd('cumulativeLineChart immediate');
|
paulo@89
|
3679
|
paulo@89
|
3680 return chart;
|
paulo@89
|
3681 }
|
paulo@89
|
3682
|
paulo@89
|
3683 //============================================================
|
paulo@89
|
3684 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
3685 //------------------------------------------------------------
|
paulo@89
|
3686
|
paulo@89
|
3687 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
3688 var point = {
|
paulo@89
|
3689 x: chart.x()(evt.point),
|
paulo@89
|
3690 y: chart.y()(evt.point),
|
paulo@89
|
3691 color: evt.point.color
|
paulo@89
|
3692 };
|
paulo@89
|
3693 evt.point = point;
|
paulo@89
|
3694 tooltip.data(evt).position(evt.pos).hidden(false);
|
paulo@89
|
3695 });
|
paulo@89
|
3696
|
paulo@89
|
3697 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
3698 tooltip.hidden(true)
|
paulo@89
|
3699 });
|
paulo@89
|
3700
|
paulo@89
|
3701 //============================================================
|
paulo@89
|
3702 // Functions
|
paulo@89
|
3703 //------------------------------------------------------------
|
paulo@89
|
3704
|
paulo@89
|
3705 var indexifyYGetter = null;
|
paulo@89
|
3706 /* Normalize the data according to an index point. */
|
paulo@89
|
3707 function indexify(idx, data) {
|
paulo@89
|
3708 if (!indexifyYGetter) indexifyYGetter = lines.y();
|
paulo@89
|
3709 return data.map(function(line, i) {
|
paulo@89
|
3710 if (!line.values) {
|
paulo@89
|
3711 return line;
|
paulo@89
|
3712 }
|
paulo@89
|
3713 var indexValue = line.values[idx];
|
paulo@89
|
3714 if (indexValue == null) {
|
paulo@89
|
3715 return line;
|
paulo@89
|
3716 }
|
paulo@89
|
3717 var v = indexifyYGetter(indexValue, idx);
|
paulo@89
|
3718
|
paulo@89
|
3719 //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
|
paulo@89
|
3720 if (v < -.95 && !noErrorCheck) {
|
paulo@89
|
3721 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
|
paulo@89
|
3722
|
paulo@89
|
3723 line.tempDisabled = true;
|
paulo@89
|
3724 return line;
|
paulo@89
|
3725 }
|
paulo@89
|
3726
|
paulo@89
|
3727 line.tempDisabled = false;
|
paulo@89
|
3728
|
paulo@89
|
3729 line.values = line.values.map(function(point, pointIndex) {
|
paulo@89
|
3730 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
|
paulo@89
|
3731 return point;
|
paulo@89
|
3732 });
|
paulo@89
|
3733
|
paulo@89
|
3734 return line;
|
paulo@89
|
3735 })
|
paulo@89
|
3736 }
|
paulo@89
|
3737
|
paulo@89
|
3738 //============================================================
|
paulo@89
|
3739 // Expose Public Variables
|
paulo@89
|
3740 //------------------------------------------------------------
|
paulo@89
|
3741
|
paulo@89
|
3742 // expose chart's sub-components
|
paulo@89
|
3743 chart.dispatch = dispatch;
|
paulo@89
|
3744 chart.lines = lines;
|
paulo@89
|
3745 chart.legend = legend;
|
paulo@89
|
3746 chart.controls = controls;
|
paulo@89
|
3747 chart.xAxis = xAxis;
|
paulo@89
|
3748 chart.yAxis = yAxis;
|
paulo@89
|
3749 chart.interactiveLayer = interactiveLayer;
|
paulo@89
|
3750 chart.state = state;
|
paulo@89
|
3751 chart.tooltip = tooltip;
|
paulo@89
|
3752
|
paulo@89
|
3753 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
3754
|
paulo@89
|
3755 chart._options = Object.create({}, {
|
paulo@89
|
3756 // simple options, just get/set the necessary values
|
paulo@89
|
3757 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
3758 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
3759 rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
|
paulo@89
|
3760 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
|
paulo@89
|
3761 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
3762 average: {get: function(){return average;}, set: function(_){average=_;}},
|
paulo@89
|
3763 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
3764 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
3765 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
3766 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
3767 noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
|
paulo@89
|
3768
|
paulo@89
|
3769 // deprecated options
|
paulo@89
|
3770 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
3771 // deprecated after 1.7.1
|
paulo@89
|
3772 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
3773 tooltip.enabled(!!_);
|
paulo@89
|
3774 }},
|
paulo@89
|
3775 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
3776 // deprecated after 1.7.1
|
paulo@89
|
3777 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
3778 tooltip.contentGenerator(_);
|
paulo@89
|
3779 }},
|
paulo@89
|
3780
|
paulo@89
|
3781 // options that require extra logic in the setter
|
paulo@89
|
3782 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
3783 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
3784 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
3785 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
3786 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
3787 }},
|
paulo@89
|
3788 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
3789 color = nv.utils.getColor(_);
|
paulo@89
|
3790 legend.color(color);
|
paulo@89
|
3791 }},
|
paulo@89
|
3792 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
|
paulo@89
|
3793 useInteractiveGuideline = _;
|
paulo@89
|
3794 if (_ === true) {
|
paulo@89
|
3795 chart.interactive(false);
|
paulo@89
|
3796 chart.useVoronoi(false);
|
paulo@89
|
3797 }
|
paulo@89
|
3798 }},
|
paulo@89
|
3799 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
|
paulo@89
|
3800 rightAlignYAxis = _;
|
paulo@89
|
3801 yAxis.orient( (_) ? 'right' : 'left');
|
paulo@89
|
3802 }},
|
paulo@89
|
3803 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
3804 duration = _;
|
paulo@89
|
3805 lines.duration(duration);
|
paulo@89
|
3806 xAxis.duration(duration);
|
paulo@89
|
3807 yAxis.duration(duration);
|
paulo@89
|
3808 renderWatch.reset(duration);
|
paulo@89
|
3809 }}
|
paulo@89
|
3810 });
|
paulo@89
|
3811
|
paulo@89
|
3812 nv.utils.inheritOptions(chart, lines);
|
paulo@89
|
3813 nv.utils.initOptions(chart);
|
paulo@89
|
3814
|
paulo@89
|
3815 return chart;
|
paulo@89
|
3816 };
|
paulo@89
|
3817 //TODO: consider deprecating by adding necessary features to multiBar model
|
paulo@89
|
3818 nv.models.discreteBar = function() {
|
paulo@89
|
3819 "use strict";
|
paulo@89
|
3820
|
paulo@89
|
3821 //============================================================
|
paulo@89
|
3822 // Public Variables with Default Settings
|
paulo@89
|
3823 //------------------------------------------------------------
|
paulo@89
|
3824
|
paulo@89
|
3825 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
3826 , width = 960
|
paulo@89
|
3827 , height = 500
|
paulo@89
|
3828 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
3829 , container
|
paulo@89
|
3830 , x = d3.scale.ordinal()
|
paulo@89
|
3831 , y = d3.scale.linear()
|
paulo@89
|
3832 , getX = function(d) { return d.x }
|
paulo@89
|
3833 , getY = function(d) { return d.y }
|
paulo@89
|
3834 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
|
paulo@89
|
3835 , color = nv.utils.defaultColor()
|
paulo@89
|
3836 , showValues = false
|
paulo@89
|
3837 , valueFormat = d3.format(',.2f')
|
paulo@89
|
3838 , xDomain
|
paulo@89
|
3839 , yDomain
|
paulo@89
|
3840 , xRange
|
paulo@89
|
3841 , yRange
|
paulo@89
|
3842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
|
paulo@89
|
3843 , rectClass = 'discreteBar'
|
paulo@89
|
3844 , duration = 250
|
paulo@89
|
3845 ;
|
paulo@89
|
3846
|
paulo@89
|
3847 //============================================================
|
paulo@89
|
3848 // Private Variables
|
paulo@89
|
3849 //------------------------------------------------------------
|
paulo@89
|
3850
|
paulo@89
|
3851 var x0, y0;
|
paulo@89
|
3852 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
3853
|
paulo@89
|
3854 function chart(selection) {
|
paulo@89
|
3855 renderWatch.reset();
|
paulo@89
|
3856 selection.each(function(data) {
|
paulo@89
|
3857 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
3858 availableHeight = height - margin.top - margin.bottom;
|
paulo@89
|
3859
|
paulo@89
|
3860 container = d3.select(this);
|
paulo@89
|
3861 nv.utils.initSVG(container);
|
paulo@89
|
3862
|
paulo@89
|
3863 //add series index to each data point for reference
|
paulo@89
|
3864 data.forEach(function(series, i) {
|
paulo@89
|
3865 series.values.forEach(function(point) {
|
paulo@89
|
3866 point.series = i;
|
paulo@89
|
3867 });
|
paulo@89
|
3868 });
|
paulo@89
|
3869
|
paulo@89
|
3870 // Setup Scales
|
paulo@89
|
3871 // remap and flatten the data for use in calculating the scales' domains
|
paulo@89
|
3872 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
|
paulo@89
|
3873 data.map(function(d) {
|
paulo@89
|
3874 return d.values.map(function(d,i) {
|
paulo@89
|
3875 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
|
paulo@89
|
3876 })
|
paulo@89
|
3877 });
|
paulo@89
|
3878
|
paulo@89
|
3879 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
|
paulo@89
|
3880 .rangeBands(xRange || [0, availableWidth], .1);
|
paulo@89
|
3881 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
|
paulo@89
|
3882
|
paulo@89
|
3883 // If showValues, pad the Y axis range to account for label height
|
paulo@89
|
3884 if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
|
paulo@89
|
3885 else y.range(yRange || [availableHeight, 0]);
|
paulo@89
|
3886
|
paulo@89
|
3887 //store old scales if they exist
|
paulo@89
|
3888 x0 = x0 || x;
|
paulo@89
|
3889 y0 = y0 || y.copy().range([y(0),y(0)]);
|
paulo@89
|
3890
|
paulo@89
|
3891 // Setup containers and skeleton of chart
|
paulo@89
|
3892 var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
|
paulo@89
|
3893 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
|
paulo@89
|
3894 var gEnter = wrapEnter.append('g');
|
paulo@89
|
3895 var g = wrap.select('g');
|
paulo@89
|
3896
|
paulo@89
|
3897 gEnter.append('g').attr('class', 'nv-groups');
|
paulo@89
|
3898 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
3899
|
paulo@89
|
3900 //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
|
paulo@89
|
3901 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
|
paulo@89
|
3902 .data(function(d) { return d }, function(d) { return d.key });
|
paulo@89
|
3903 groups.enter().append('g')
|
paulo@89
|
3904 .style('stroke-opacity', 1e-6)
|
paulo@89
|
3905 .style('fill-opacity', 1e-6);
|
paulo@89
|
3906 groups.exit()
|
paulo@89
|
3907 .watchTransition(renderWatch, 'discreteBar: exit groups')
|
paulo@89
|
3908 .style('stroke-opacity', 1e-6)
|
paulo@89
|
3909 .style('fill-opacity', 1e-6)
|
paulo@89
|
3910 .remove();
|
paulo@89
|
3911 groups
|
paulo@89
|
3912 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
|
paulo@89
|
3913 .classed('hover', function(d) { return d.hover });
|
paulo@89
|
3914 groups
|
paulo@89
|
3915 .watchTransition(renderWatch, 'discreteBar: groups')
|
paulo@89
|
3916 .style('stroke-opacity', 1)
|
paulo@89
|
3917 .style('fill-opacity', .75);
|
paulo@89
|
3918
|
paulo@89
|
3919 var bars = groups.selectAll('g.nv-bar')
|
paulo@89
|
3920 .data(function(d) { return d.values });
|
paulo@89
|
3921 bars.exit().remove();
|
paulo@89
|
3922
|
paulo@89
|
3923 var barsEnter = bars.enter().append('g')
|
paulo@89
|
3924 .attr('transform', function(d,i,j) {
|
paulo@89
|
3925 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
|
paulo@89
|
3926 })
|
paulo@89
|
3927 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
|
paulo@89
|
3928 d3.select(this).classed('hover', true);
|
paulo@89
|
3929 dispatch.elementMouseover({
|
paulo@89
|
3930 data: d,
|
paulo@89
|
3931 index: i,
|
paulo@89
|
3932 color: d3.select(this).style("fill")
|
paulo@89
|
3933 });
|
paulo@89
|
3934 })
|
paulo@89
|
3935 .on('mouseout', function(d,i) {
|
paulo@89
|
3936 d3.select(this).classed('hover', false);
|
paulo@89
|
3937 dispatch.elementMouseout({
|
paulo@89
|
3938 data: d,
|
paulo@89
|
3939 index: i,
|
paulo@89
|
3940 color: d3.select(this).style("fill")
|
paulo@89
|
3941 });
|
paulo@89
|
3942 })
|
paulo@89
|
3943 .on('mousemove', function(d,i) {
|
paulo@89
|
3944 dispatch.elementMousemove({
|
paulo@89
|
3945 data: d,
|
paulo@89
|
3946 index: i,
|
paulo@89
|
3947 color: d3.select(this).style("fill")
|
paulo@89
|
3948 });
|
paulo@89
|
3949 })
|
paulo@89
|
3950 .on('click', function(d,i) {
|
paulo@89
|
3951 dispatch.elementClick({
|
paulo@89
|
3952 data: d,
|
paulo@89
|
3953 index: i,
|
paulo@89
|
3954 color: d3.select(this).style("fill")
|
paulo@89
|
3955 });
|
paulo@89
|
3956 d3.event.stopPropagation();
|
paulo@89
|
3957 })
|
paulo@89
|
3958 .on('dblclick', function(d,i) {
|
paulo@89
|
3959 dispatch.elementDblClick({
|
paulo@89
|
3960 data: d,
|
paulo@89
|
3961 index: i,
|
paulo@89
|
3962 color: d3.select(this).style("fill")
|
paulo@89
|
3963 });
|
paulo@89
|
3964 d3.event.stopPropagation();
|
paulo@89
|
3965 });
|
paulo@89
|
3966
|
paulo@89
|
3967 barsEnter.append('rect')
|
paulo@89
|
3968 .attr('height', 0)
|
paulo@89
|
3969 .attr('width', x.rangeBand() * .9 / data.length )
|
paulo@89
|
3970
|
paulo@89
|
3971 if (showValues) {
|
paulo@89
|
3972 barsEnter.append('text')
|
paulo@89
|
3973 .attr('text-anchor', 'middle')
|
paulo@89
|
3974 ;
|
paulo@89
|
3975
|
paulo@89
|
3976 bars.select('text')
|
paulo@89
|
3977 .text(function(d,i) { return valueFormat(getY(d,i)) })
|
paulo@89
|
3978 .watchTransition(renderWatch, 'discreteBar: bars text')
|
paulo@89
|
3979 .attr('x', x.rangeBand() * .9 / 2)
|
paulo@89
|
3980 .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
|
paulo@89
|
3981
|
paulo@89
|
3982 ;
|
paulo@89
|
3983 } else {
|
paulo@89
|
3984 bars.selectAll('text').remove();
|
paulo@89
|
3985 }
|
paulo@89
|
3986
|
paulo@89
|
3987 bars
|
paulo@89
|
3988 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
|
paulo@89
|
3989 .style('fill', function(d,i) { return d.color || color(d,i) })
|
paulo@89
|
3990 .style('stroke', function(d,i) { return d.color || color(d,i) })
|
paulo@89
|
3991 .select('rect')
|
paulo@89
|
3992 .attr('class', rectClass)
|
paulo@89
|
3993 .watchTransition(renderWatch, 'discreteBar: bars rect')
|
paulo@89
|
3994 .attr('width', x.rangeBand() * .9 / data.length);
|
paulo@89
|
3995 bars.watchTransition(renderWatch, 'discreteBar: bars')
|
paulo@89
|
3996 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
|
paulo@89
|
3997 .attr('transform', function(d,i) {
|
paulo@89
|
3998 var left = x(getX(d,i)) + x.rangeBand() * .05,
|
paulo@89
|
3999 top = getY(d,i) < 0 ?
|
paulo@89
|
4000 y(0) :
|
paulo@89
|
4001 y(0) - y(getY(d,i)) < 1 ?
|
paulo@89
|
4002 y(0) - 1 : //make 1 px positive bars show up above y=0
|
paulo@89
|
4003 y(getY(d,i));
|
paulo@89
|
4004
|
paulo@89
|
4005 return 'translate(' + left + ', ' + top + ')'
|
paulo@89
|
4006 })
|
paulo@89
|
4007 .select('rect')
|
paulo@89
|
4008 .attr('height', function(d,i) {
|
paulo@89
|
4009 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
|
paulo@89
|
4010 });
|
paulo@89
|
4011
|
paulo@89
|
4012
|
paulo@89
|
4013 //store old scales for use in transitions on update
|
paulo@89
|
4014 x0 = x.copy();
|
paulo@89
|
4015 y0 = y.copy();
|
paulo@89
|
4016
|
paulo@89
|
4017 });
|
paulo@89
|
4018
|
paulo@89
|
4019 renderWatch.renderEnd('discreteBar immediate');
|
paulo@89
|
4020 return chart;
|
paulo@89
|
4021 }
|
paulo@89
|
4022
|
paulo@89
|
4023 //============================================================
|
paulo@89
|
4024 // Expose Public Variables
|
paulo@89
|
4025 //------------------------------------------------------------
|
paulo@89
|
4026
|
paulo@89
|
4027 chart.dispatch = dispatch;
|
paulo@89
|
4028 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
4029
|
paulo@89
|
4030 chart._options = Object.create({}, {
|
paulo@89
|
4031 // simple options, just get/set the necessary values
|
paulo@89
|
4032 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
4033 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
4034 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
|
paulo@89
|
4035 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
|
paulo@89
|
4036 x: {get: function(){return getX;}, set: function(_){getX=_;}},
|
paulo@89
|
4037 y: {get: function(){return getY;}, set: function(_){getY=_;}},
|
paulo@89
|
4038 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
4039 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
4040 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
4041 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
4042 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
4043 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
4044 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
|
paulo@89
|
4045 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
4046 rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
|
paulo@89
|
4047
|
paulo@89
|
4048 // options that require extra logic in the setter
|
paulo@89
|
4049 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
4050 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
4051 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
4052 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
4053 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
4054 }},
|
paulo@89
|
4055 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
4056 color = nv.utils.getColor(_);
|
paulo@89
|
4057 }},
|
paulo@89
|
4058 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
4059 duration = _;
|
paulo@89
|
4060 renderWatch.reset(duration);
|
paulo@89
|
4061 }}
|
paulo@89
|
4062 });
|
paulo@89
|
4063
|
paulo@89
|
4064 nv.utils.initOptions(chart);
|
paulo@89
|
4065
|
paulo@89
|
4066 return chart;
|
paulo@89
|
4067 };
|
paulo@89
|
4068
|
paulo@89
|
4069 nv.models.discreteBarChart = function() {
|
paulo@89
|
4070 "use strict";
|
paulo@89
|
4071
|
paulo@89
|
4072 //============================================================
|
paulo@89
|
4073 // Public Variables with Default Settings
|
paulo@89
|
4074 //------------------------------------------------------------
|
paulo@89
|
4075
|
paulo@89
|
4076 var discretebar = nv.models.discreteBar()
|
paulo@89
|
4077 , xAxis = nv.models.axis()
|
paulo@89
|
4078 , yAxis = nv.models.axis()
|
paulo@89
|
4079 , tooltip = nv.models.tooltip()
|
paulo@89
|
4080 ;
|
paulo@89
|
4081
|
paulo@89
|
4082 var margin = {top: 15, right: 10, bottom: 50, left: 60}
|
paulo@89
|
4083 , width = null
|
paulo@89
|
4084 , height = null
|
paulo@89
|
4085 , color = nv.utils.getColor()
|
paulo@89
|
4086 , showXAxis = true
|
paulo@89
|
4087 , showYAxis = true
|
paulo@89
|
4088 , rightAlignYAxis = false
|
paulo@89
|
4089 , staggerLabels = false
|
paulo@89
|
4090 , x
|
paulo@89
|
4091 , y
|
paulo@89
|
4092 , noData = null
|
paulo@89
|
4093 , dispatch = d3.dispatch('beforeUpdate','renderEnd')
|
paulo@89
|
4094 , duration = 250
|
paulo@89
|
4095 ;
|
paulo@89
|
4096
|
paulo@89
|
4097 xAxis
|
paulo@89
|
4098 .orient('bottom')
|
paulo@89
|
4099 .showMaxMin(false)
|
paulo@89
|
4100 .tickFormat(function(d) { return d })
|
paulo@89
|
4101 ;
|
paulo@89
|
4102 yAxis
|
paulo@89
|
4103 .orient((rightAlignYAxis) ? 'right' : 'left')
|
paulo@89
|
4104 .tickFormat(d3.format(',.1f'))
|
paulo@89
|
4105 ;
|
paulo@89
|
4106
|
paulo@89
|
4107 tooltip
|
paulo@89
|
4108 .duration(0)
|
paulo@89
|
4109 .headerEnabled(false)
|
paulo@89
|
4110 .valueFormatter(function(d, i) {
|
paulo@89
|
4111 return yAxis.tickFormat()(d, i);
|
paulo@89
|
4112 })
|
paulo@89
|
4113 .keyFormatter(function(d, i) {
|
paulo@89
|
4114 return xAxis.tickFormat()(d, i);
|
paulo@89
|
4115 });
|
paulo@89
|
4116
|
paulo@89
|
4117 //============================================================
|
paulo@89
|
4118 // Private Variables
|
paulo@89
|
4119 //------------------------------------------------------------
|
paulo@89
|
4120
|
paulo@89
|
4121 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
4122
|
paulo@89
|
4123 function chart(selection) {
|
paulo@89
|
4124 renderWatch.reset();
|
paulo@89
|
4125 renderWatch.models(discretebar);
|
paulo@89
|
4126 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
4127 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
4128
|
paulo@89
|
4129 selection.each(function(data) {
|
paulo@89
|
4130 var container = d3.select(this),
|
paulo@89
|
4131 that = this;
|
paulo@89
|
4132 nv.utils.initSVG(container);
|
paulo@89
|
4133 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
4134 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
4135
|
paulo@89
|
4136 chart.update = function() {
|
paulo@89
|
4137 dispatch.beforeUpdate();
|
paulo@89
|
4138 container.transition().duration(duration).call(chart);
|
paulo@89
|
4139 };
|
paulo@89
|
4140 chart.container = this;
|
paulo@89
|
4141
|
paulo@89
|
4142 // Display No Data message if there's nothing to show.
|
paulo@89
|
4143 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
4144 nv.utils.noData(chart, container);
|
paulo@89
|
4145 return chart;
|
paulo@89
|
4146 } else {
|
paulo@89
|
4147 container.selectAll('.nv-noData').remove();
|
paulo@89
|
4148 }
|
paulo@89
|
4149
|
paulo@89
|
4150 // Setup Scales
|
paulo@89
|
4151 x = discretebar.xScale();
|
paulo@89
|
4152 y = discretebar.yScale().clamp(true);
|
paulo@89
|
4153
|
paulo@89
|
4154 // Setup containers and skeleton of chart
|
paulo@89
|
4155 var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
|
paulo@89
|
4156 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
|
paulo@89
|
4157 var defsEnter = gEnter.append('defs');
|
paulo@89
|
4158 var g = wrap.select('g');
|
paulo@89
|
4159
|
paulo@89
|
4160 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
4161 gEnter.append('g').attr('class', 'nv-y nv-axis')
|
paulo@89
|
4162 .append('g').attr('class', 'nv-zeroLine')
|
paulo@89
|
4163 .append('line');
|
paulo@89
|
4164
|
paulo@89
|
4165 gEnter.append('g').attr('class', 'nv-barsWrap');
|
paulo@89
|
4166
|
paulo@89
|
4167 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
4168
|
paulo@89
|
4169 if (rightAlignYAxis) {
|
paulo@89
|
4170 g.select(".nv-y.nv-axis")
|
paulo@89
|
4171 .attr("transform", "translate(" + availableWidth + ",0)");
|
paulo@89
|
4172 }
|
paulo@89
|
4173
|
paulo@89
|
4174 // Main Chart Component(s)
|
paulo@89
|
4175 discretebar
|
paulo@89
|
4176 .width(availableWidth)
|
paulo@89
|
4177 .height(availableHeight);
|
paulo@89
|
4178
|
paulo@89
|
4179 var barsWrap = g.select('.nv-barsWrap')
|
paulo@89
|
4180 .datum(data.filter(function(d) { return !d.disabled }));
|
paulo@89
|
4181
|
paulo@89
|
4182 barsWrap.transition().call(discretebar);
|
paulo@89
|
4183
|
paulo@89
|
4184
|
paulo@89
|
4185 defsEnter.append('clipPath')
|
paulo@89
|
4186 .attr('id', 'nv-x-label-clip-' + discretebar.id())
|
paulo@89
|
4187 .append('rect');
|
paulo@89
|
4188
|
paulo@89
|
4189 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
|
paulo@89
|
4190 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
|
paulo@89
|
4191 .attr('height', 16)
|
paulo@89
|
4192 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
|
paulo@89
|
4193
|
paulo@89
|
4194 // Setup Axes
|
paulo@89
|
4195 if (showXAxis) {
|
paulo@89
|
4196 xAxis
|
paulo@89
|
4197 .scale(x)
|
paulo@89
|
4198 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
4199 .tickSize(-availableHeight, 0);
|
paulo@89
|
4200
|
paulo@89
|
4201 g.select('.nv-x.nv-axis')
|
paulo@89
|
4202 .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
|
paulo@89
|
4203 g.select('.nv-x.nv-axis').call(xAxis);
|
paulo@89
|
4204
|
paulo@89
|
4205 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
|
paulo@89
|
4206 if (staggerLabels) {
|
paulo@89
|
4207 xTicks
|
paulo@89
|
4208 .selectAll('text')
|
paulo@89
|
4209 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
|
paulo@89
|
4210 }
|
paulo@89
|
4211 }
|
paulo@89
|
4212
|
paulo@89
|
4213 if (showYAxis) {
|
paulo@89
|
4214 yAxis
|
paulo@89
|
4215 .scale(y)
|
paulo@89
|
4216 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
|
paulo@89
|
4217 .tickSize( -availableWidth, 0);
|
paulo@89
|
4218
|
paulo@89
|
4219 g.select('.nv-y.nv-axis').call(yAxis);
|
paulo@89
|
4220 }
|
paulo@89
|
4221
|
paulo@89
|
4222 // Zero line
|
paulo@89
|
4223 g.select(".nv-zeroLine line")
|
paulo@89
|
4224 .attr("x1",0)
|
paulo@89
|
4225 .attr("x2",availableWidth)
|
paulo@89
|
4226 .attr("y1", y(0))
|
paulo@89
|
4227 .attr("y2", y(0))
|
paulo@89
|
4228 ;
|
paulo@89
|
4229 });
|
paulo@89
|
4230
|
paulo@89
|
4231 renderWatch.renderEnd('discreteBar chart immediate');
|
paulo@89
|
4232 return chart;
|
paulo@89
|
4233 }
|
paulo@89
|
4234
|
paulo@89
|
4235 //============================================================
|
paulo@89
|
4236 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
4237 //------------------------------------------------------------
|
paulo@89
|
4238
|
paulo@89
|
4239 discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
4240 evt['series'] = {
|
paulo@89
|
4241 key: chart.x()(evt.data),
|
paulo@89
|
4242 value: chart.y()(evt.data),
|
paulo@89
|
4243 color: evt.color
|
paulo@89
|
4244 };
|
paulo@89
|
4245 tooltip.data(evt).hidden(false);
|
paulo@89
|
4246 });
|
paulo@89
|
4247
|
paulo@89
|
4248 discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
4249 tooltip.hidden(true);
|
paulo@89
|
4250 });
|
paulo@89
|
4251
|
paulo@89
|
4252 discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
4253 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
4254 });
|
paulo@89
|
4255
|
paulo@89
|
4256 //============================================================
|
paulo@89
|
4257 // Expose Public Variables
|
paulo@89
|
4258 //------------------------------------------------------------
|
paulo@89
|
4259
|
paulo@89
|
4260 chart.dispatch = dispatch;
|
paulo@89
|
4261 chart.discretebar = discretebar;
|
paulo@89
|
4262 chart.xAxis = xAxis;
|
paulo@89
|
4263 chart.yAxis = yAxis;
|
paulo@89
|
4264 chart.tooltip = tooltip;
|
paulo@89
|
4265
|
paulo@89
|
4266 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
4267
|
paulo@89
|
4268 chart._options = Object.create({}, {
|
paulo@89
|
4269 // simple options, just get/set the necessary values
|
paulo@89
|
4270 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
4271 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
4272 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
|
paulo@89
|
4273 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
4274 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
4275 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
4276
|
paulo@89
|
4277 // deprecated options
|
paulo@89
|
4278 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
4279 // deprecated after 1.7.1
|
paulo@89
|
4280 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
4281 tooltip.enabled(!!_);
|
paulo@89
|
4282 }},
|
paulo@89
|
4283 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
4284 // deprecated after 1.7.1
|
paulo@89
|
4285 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
4286 tooltip.contentGenerator(_);
|
paulo@89
|
4287 }},
|
paulo@89
|
4288
|
paulo@89
|
4289 // options that require extra logic in the setter
|
paulo@89
|
4290 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
4291 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
4292 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
4293 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
4294 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
4295 }},
|
paulo@89
|
4296 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
4297 duration = _;
|
paulo@89
|
4298 renderWatch.reset(duration);
|
paulo@89
|
4299 discretebar.duration(duration);
|
paulo@89
|
4300 xAxis.duration(duration);
|
paulo@89
|
4301 yAxis.duration(duration);
|
paulo@89
|
4302 }},
|
paulo@89
|
4303 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
4304 color = nv.utils.getColor(_);
|
paulo@89
|
4305 discretebar.color(color);
|
paulo@89
|
4306 }},
|
paulo@89
|
4307 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
|
paulo@89
|
4308 rightAlignYAxis = _;
|
paulo@89
|
4309 yAxis.orient( (_) ? 'right' : 'left');
|
paulo@89
|
4310 }}
|
paulo@89
|
4311 });
|
paulo@89
|
4312
|
paulo@89
|
4313 nv.utils.inheritOptions(chart, discretebar);
|
paulo@89
|
4314 nv.utils.initOptions(chart);
|
paulo@89
|
4315
|
paulo@89
|
4316 return chart;
|
paulo@89
|
4317 }
|
paulo@89
|
4318
|
paulo@89
|
4319 nv.models.distribution = function() {
|
paulo@89
|
4320 "use strict";
|
paulo@89
|
4321 //============================================================
|
paulo@89
|
4322 // Public Variables with Default Settings
|
paulo@89
|
4323 //------------------------------------------------------------
|
paulo@89
|
4324
|
paulo@89
|
4325 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
4326 , width = 400 //technically width or height depending on x or y....
|
paulo@89
|
4327 , size = 8
|
paulo@89
|
4328 , axis = 'x' // 'x' or 'y'... horizontal or vertical
|
paulo@89
|
4329 , getData = function(d) { return d[axis] } // defaults d.x or d.y
|
paulo@89
|
4330 , color = nv.utils.defaultColor()
|
paulo@89
|
4331 , scale = d3.scale.linear()
|
paulo@89
|
4332 , domain
|
paulo@89
|
4333 , duration = 250
|
paulo@89
|
4334 , dispatch = d3.dispatch('renderEnd')
|
paulo@89
|
4335 ;
|
paulo@89
|
4336
|
paulo@89
|
4337 //============================================================
|
paulo@89
|
4338
|
paulo@89
|
4339
|
paulo@89
|
4340 //============================================================
|
paulo@89
|
4341 // Private Variables
|
paulo@89
|
4342 //------------------------------------------------------------
|
paulo@89
|
4343
|
paulo@89
|
4344 var scale0;
|
paulo@89
|
4345 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
4346
|
paulo@89
|
4347 //============================================================
|
paulo@89
|
4348
|
paulo@89
|
4349
|
paulo@89
|
4350 function chart(selection) {
|
paulo@89
|
4351 renderWatch.reset();
|
paulo@89
|
4352 selection.each(function(data) {
|
paulo@89
|
4353 var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
|
paulo@89
|
4354 naxis = axis == 'x' ? 'y' : 'x',
|
paulo@89
|
4355 container = d3.select(this);
|
paulo@89
|
4356 nv.utils.initSVG(container);
|
paulo@89
|
4357
|
paulo@89
|
4358 //------------------------------------------------------------
|
paulo@89
|
4359 // Setup Scales
|
paulo@89
|
4360
|
paulo@89
|
4361 scale0 = scale0 || scale;
|
paulo@89
|
4362
|
paulo@89
|
4363 //------------------------------------------------------------
|
paulo@89
|
4364
|
paulo@89
|
4365
|
paulo@89
|
4366 //------------------------------------------------------------
|
paulo@89
|
4367 // Setup containers and skeleton of chart
|
paulo@89
|
4368
|
paulo@89
|
4369 var wrap = container.selectAll('g.nv-distribution').data([data]);
|
paulo@89
|
4370 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
|
paulo@89
|
4371 var gEnter = wrapEnter.append('g');
|
paulo@89
|
4372 var g = wrap.select('g');
|
paulo@89
|
4373
|
paulo@89
|
4374 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
|
paulo@89
|
4375
|
paulo@89
|
4376 //------------------------------------------------------------
|
paulo@89
|
4377
|
paulo@89
|
4378
|
paulo@89
|
4379 var distWrap = g.selectAll('g.nv-dist')
|
paulo@89
|
4380 .data(function(d) { return d }, function(d) { return d.key });
|
paulo@89
|
4381
|
paulo@89
|
4382 distWrap.enter().append('g');
|
paulo@89
|
4383 distWrap
|
paulo@89
|
4384 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
|
paulo@89
|
4385 .style('stroke', function(d,i) { return color(d, i) });
|
paulo@89
|
4386
|
paulo@89
|
4387 var dist = distWrap.selectAll('line.nv-dist' + axis)
|
paulo@89
|
4388 .data(function(d) { return d.values })
|
paulo@89
|
4389 dist.enter().append('line')
|
paulo@89
|
4390 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
|
paulo@89
|
4391 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
|
paulo@89
|
4392 renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
|
paulo@89
|
4393 // .transition()
|
paulo@89
|
4394 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
|
paulo@89
|
4395 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
|
paulo@89
|
4396 .style('stroke-opacity', 0)
|
paulo@89
|
4397 .remove();
|
paulo@89
|
4398 dist
|
paulo@89
|
4399 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
|
paulo@89
|
4400 .attr(naxis + '1', 0)
|
paulo@89
|
4401 .attr(naxis + '2', size);
|
paulo@89
|
4402 renderWatch.transition(dist, 'dist')
|
paulo@89
|
4403 // .transition()
|
paulo@89
|
4404 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
|
paulo@89
|
4405 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
|
paulo@89
|
4406
|
paulo@89
|
4407
|
paulo@89
|
4408 scale0 = scale.copy();
|
paulo@89
|
4409
|
paulo@89
|
4410 });
|
paulo@89
|
4411 renderWatch.renderEnd('distribution immediate');
|
paulo@89
|
4412 return chart;
|
paulo@89
|
4413 }
|
paulo@89
|
4414
|
paulo@89
|
4415
|
paulo@89
|
4416 //============================================================
|
paulo@89
|
4417 // Expose Public Variables
|
paulo@89
|
4418 //------------------------------------------------------------
|
paulo@89
|
4419 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
4420 chart.dispatch = dispatch;
|
paulo@89
|
4421
|
paulo@89
|
4422 chart.margin = function(_) {
|
paulo@89
|
4423 if (!arguments.length) return margin;
|
paulo@89
|
4424 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
|
paulo@89
|
4425 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
|
paulo@89
|
4426 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
|
paulo@89
|
4427 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
|
paulo@89
|
4428 return chart;
|
paulo@89
|
4429 };
|
paulo@89
|
4430
|
paulo@89
|
4431 chart.width = function(_) {
|
paulo@89
|
4432 if (!arguments.length) return width;
|
paulo@89
|
4433 width = _;
|
paulo@89
|
4434 return chart;
|
paulo@89
|
4435 };
|
paulo@89
|
4436
|
paulo@89
|
4437 chart.axis = function(_) {
|
paulo@89
|
4438 if (!arguments.length) return axis;
|
paulo@89
|
4439 axis = _;
|
paulo@89
|
4440 return chart;
|
paulo@89
|
4441 };
|
paulo@89
|
4442
|
paulo@89
|
4443 chart.size = function(_) {
|
paulo@89
|
4444 if (!arguments.length) return size;
|
paulo@89
|
4445 size = _;
|
paulo@89
|
4446 return chart;
|
paulo@89
|
4447 };
|
paulo@89
|
4448
|
paulo@89
|
4449 chart.getData = function(_) {
|
paulo@89
|
4450 if (!arguments.length) return getData;
|
paulo@89
|
4451 getData = d3.functor(_);
|
paulo@89
|
4452 return chart;
|
paulo@89
|
4453 };
|
paulo@89
|
4454
|
paulo@89
|
4455 chart.scale = function(_) {
|
paulo@89
|
4456 if (!arguments.length) return scale;
|
paulo@89
|
4457 scale = _;
|
paulo@89
|
4458 return chart;
|
paulo@89
|
4459 };
|
paulo@89
|
4460
|
paulo@89
|
4461 chart.color = function(_) {
|
paulo@89
|
4462 if (!arguments.length) return color;
|
paulo@89
|
4463 color = nv.utils.getColor(_);
|
paulo@89
|
4464 return chart;
|
paulo@89
|
4465 };
|
paulo@89
|
4466
|
paulo@89
|
4467 chart.duration = function(_) {
|
paulo@89
|
4468 if (!arguments.length) return duration;
|
paulo@89
|
4469 duration = _;
|
paulo@89
|
4470 renderWatch.reset(duration);
|
paulo@89
|
4471 return chart;
|
paulo@89
|
4472 };
|
paulo@89
|
4473 //============================================================
|
paulo@89
|
4474
|
paulo@89
|
4475
|
paulo@89
|
4476 return chart;
|
paulo@89
|
4477 }
|
paulo@89
|
4478 nv.models.furiousLegend = function() {
|
paulo@89
|
4479 "use strict";
|
paulo@89
|
4480
|
paulo@89
|
4481 //============================================================
|
paulo@89
|
4482 // Public Variables with Default Settings
|
paulo@89
|
4483 //------------------------------------------------------------
|
paulo@89
|
4484
|
paulo@89
|
4485 var margin = {top: 5, right: 0, bottom: 5, left: 0}
|
paulo@89
|
4486 , width = 400
|
paulo@89
|
4487 , height = 20
|
paulo@89
|
4488 , getKey = function(d) { return d.key }
|
paulo@89
|
4489 , color = nv.utils.getColor()
|
paulo@89
|
4490 , align = true
|
paulo@89
|
4491 , padding = 28 //define how much space between legend items. - recommend 32 for furious version
|
paulo@89
|
4492 , rightAlign = true
|
paulo@89
|
4493 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
|
paulo@89
|
4494 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
|
paulo@89
|
4495 , expanded = false
|
paulo@89
|
4496 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
|
paulo@89
|
4497 , vers = 'classic' //Options are "classic" and "furious"
|
paulo@89
|
4498 ;
|
paulo@89
|
4499
|
paulo@89
|
4500 function chart(selection) {
|
paulo@89
|
4501 selection.each(function(data) {
|
paulo@89
|
4502 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
4503 container = d3.select(this);
|
paulo@89
|
4504 nv.utils.initSVG(container);
|
paulo@89
|
4505
|
paulo@89
|
4506 // Setup containers and skeleton of chart
|
paulo@89
|
4507 var wrap = container.selectAll('g.nv-legend').data([data]);
|
paulo@89
|
4508 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
|
paulo@89
|
4509 var g = wrap.select('g');
|
paulo@89
|
4510
|
paulo@89
|
4511 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
4512
|
paulo@89
|
4513 var series = g.selectAll('.nv-series')
|
paulo@89
|
4514 .data(function(d) {
|
paulo@89
|
4515 if(vers != 'furious') return d;
|
paulo@89
|
4516
|
paulo@89
|
4517 return d.filter(function(n) {
|
paulo@89
|
4518 return expanded ? true : !n.disengaged;
|
paulo@89
|
4519 });
|
paulo@89
|
4520 });
|
paulo@89
|
4521 var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
|
paulo@89
|
4522
|
paulo@89
|
4523 var seriesShape;
|
paulo@89
|
4524
|
paulo@89
|
4525 if(vers == 'classic') {
|
paulo@89
|
4526 seriesEnter.append('circle')
|
paulo@89
|
4527 .style('stroke-width', 2)
|
paulo@89
|
4528 .attr('class','nv-legend-symbol')
|
paulo@89
|
4529 .attr('r', 5);
|
paulo@89
|
4530
|
paulo@89
|
4531 seriesShape = series.select('circle');
|
paulo@89
|
4532 } else if (vers == 'furious') {
|
paulo@89
|
4533 seriesEnter.append('rect')
|
paulo@89
|
4534 .style('stroke-width', 2)
|
paulo@89
|
4535 .attr('class','nv-legend-symbol')
|
paulo@89
|
4536 .attr('rx', 3)
|
paulo@89
|
4537 .attr('ry', 3);
|
paulo@89
|
4538
|
paulo@89
|
4539 seriesShape = series.select('rect');
|
paulo@89
|
4540
|
paulo@89
|
4541 seriesEnter.append('g')
|
paulo@89
|
4542 .attr('class', 'nv-check-box')
|
paulo@89
|
4543 .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
|
paulo@89
|
4544 .attr('transform', 'translate(-10,-8)scale(0.5)');
|
paulo@89
|
4545
|
paulo@89
|
4546 var seriesCheckbox = series.select('.nv-check-box');
|
paulo@89
|
4547
|
paulo@89
|
4548 seriesCheckbox.each(function(d,i) {
|
paulo@89
|
4549 d3.select(this).selectAll('path')
|
paulo@89
|
4550 .attr('stroke', setTextColor(d,i));
|
paulo@89
|
4551 });
|
paulo@89
|
4552 }
|
paulo@89
|
4553
|
paulo@89
|
4554 seriesEnter.append('text')
|
paulo@89
|
4555 .attr('text-anchor', 'start')
|
paulo@89
|
4556 .attr('class','nv-legend-text')
|
paulo@89
|
4557 .attr('dy', '.32em')
|
paulo@89
|
4558 .attr('dx', '8');
|
paulo@89
|
4559
|
paulo@89
|
4560 var seriesText = series.select('text.nv-legend-text');
|
paulo@89
|
4561
|
paulo@89
|
4562 series
|
paulo@89
|
4563 .on('mouseover', function(d,i) {
|
paulo@89
|
4564 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
|
paulo@89
|
4565 })
|
paulo@89
|
4566 .on('mouseout', function(d,i) {
|
paulo@89
|
4567 dispatch.legendMouseout(d,i);
|
paulo@89
|
4568 })
|
paulo@89
|
4569 .on('click', function(d,i) {
|
paulo@89
|
4570 dispatch.legendClick(d,i);
|
paulo@89
|
4571 // make sure we re-get data in case it was modified
|
paulo@89
|
4572 var data = series.data();
|
paulo@89
|
4573 if (updateState) {
|
paulo@89
|
4574 if(vers =='classic') {
|
paulo@89
|
4575 if (radioButtonMode) {
|
paulo@89
|
4576 //Radio button mode: set every series to disabled,
|
paulo@89
|
4577 // and enable the clicked series.
|
paulo@89
|
4578 data.forEach(function(series) { series.disabled = true});
|
paulo@89
|
4579 d.disabled = false;
|
paulo@89
|
4580 }
|
paulo@89
|
4581 else {
|
paulo@89
|
4582 d.disabled = !d.disabled;
|
paulo@89
|
4583 if (data.every(function(series) { return series.disabled})) {
|
paulo@89
|
4584 //the default behavior of NVD3 legends is, if every single series
|
paulo@89
|
4585 // is disabled, turn all series' back on.
|
paulo@89
|
4586 data.forEach(function(series) { series.disabled = false});
|
paulo@89
|
4587 }
|
paulo@89
|
4588 }
|
paulo@89
|
4589 } else if(vers == 'furious') {
|
paulo@89
|
4590 if(expanded) {
|
paulo@89
|
4591 d.disengaged = !d.disengaged;
|
paulo@89
|
4592 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
|
paulo@89
|
4593 d.disabled = d.disengaged || d.userDisabled;
|
paulo@89
|
4594 } else if (!expanded) {
|
paulo@89
|
4595 d.disabled = !d.disabled;
|
paulo@89
|
4596 d.userDisabled = d.disabled;
|
paulo@89
|
4597 var engaged = data.filter(function(d) { return !d.disengaged; });
|
paulo@89
|
4598 if (engaged.every(function(series) { return series.userDisabled })) {
|
paulo@89
|
4599 //the default behavior of NVD3 legends is, if every single series
|
paulo@89
|
4600 // is disabled, turn all series' back on.
|
paulo@89
|
4601 data.forEach(function(series) {
|
paulo@89
|
4602 series.disabled = series.userDisabled = false;
|
paulo@89
|
4603 });
|
paulo@89
|
4604 }
|
paulo@89
|
4605 }
|
paulo@89
|
4606 }
|
paulo@89
|
4607 dispatch.stateChange({
|
paulo@89
|
4608 disabled: data.map(function(d) { return !!d.disabled }),
|
paulo@89
|
4609 disengaged: data.map(function(d) { return !!d.disengaged })
|
paulo@89
|
4610 });
|
paulo@89
|
4611
|
paulo@89
|
4612 }
|
paulo@89
|
4613 })
|
paulo@89
|
4614 .on('dblclick', function(d,i) {
|
paulo@89
|
4615 if(vers == 'furious' && expanded) return;
|
paulo@89
|
4616 dispatch.legendDblclick(d,i);
|
paulo@89
|
4617 if (updateState) {
|
paulo@89
|
4618 // make sure we re-get data in case it was modified
|
paulo@89
|
4619 var data = series.data();
|
paulo@89
|
4620 //the default behavior of NVD3 legends, when double clicking one,
|
paulo@89
|
4621 // is to set all other series' to false, and make the double clicked series enabled.
|
paulo@89
|
4622 data.forEach(function(series) {
|
paulo@89
|
4623 series.disabled = true;
|
paulo@89
|
4624 if(vers == 'furious') series.userDisabled = series.disabled;
|
paulo@89
|
4625 });
|
paulo@89
|
4626 d.disabled = false;
|
paulo@89
|
4627 if(vers == 'furious') d.userDisabled = d.disabled;
|
paulo@89
|
4628 dispatch.stateChange({
|
paulo@89
|
4629 disabled: data.map(function(d) { return !!d.disabled })
|
paulo@89
|
4630 });
|
paulo@89
|
4631 }
|
paulo@89
|
4632 });
|
paulo@89
|
4633
|
paulo@89
|
4634 series.classed('nv-disabled', function(d) { return d.userDisabled });
|
paulo@89
|
4635 series.exit().remove();
|
paulo@89
|
4636
|
paulo@89
|
4637 seriesText
|
paulo@89
|
4638 .attr('fill', setTextColor)
|
paulo@89
|
4639 .text(getKey);
|
paulo@89
|
4640
|
paulo@89
|
4641 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
|
paulo@89
|
4642 // NEW ALIGNING CODE, TODO: clean up
|
paulo@89
|
4643
|
paulo@89
|
4644 var versPadding;
|
paulo@89
|
4645 switch(vers) {
|
paulo@89
|
4646 case 'furious' :
|
paulo@89
|
4647 versPadding = 23;
|
paulo@89
|
4648 break;
|
paulo@89
|
4649 case 'classic' :
|
paulo@89
|
4650 versPadding = 20;
|
paulo@89
|
4651 }
|
paulo@89
|
4652
|
paulo@89
|
4653 if (align) {
|
paulo@89
|
4654
|
paulo@89
|
4655 var seriesWidths = [];
|
paulo@89
|
4656 series.each(function(d,i) {
|
paulo@89
|
4657 var legendText = d3.select(this).select('text');
|
paulo@89
|
4658 var nodeTextLength;
|
paulo@89
|
4659 try {
|
paulo@89
|
4660 nodeTextLength = legendText.node().getComputedTextLength();
|
paulo@89
|
4661 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
|
paulo@89
|
4662 if(nodeTextLength <= 0) throw Error();
|
paulo@89
|
4663 }
|
paulo@89
|
4664 catch(e) {
|
paulo@89
|
4665 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
|
paulo@89
|
4666 }
|
paulo@89
|
4667
|
paulo@89
|
4668 seriesWidths.push(nodeTextLength + padding);
|
paulo@89
|
4669 });
|
paulo@89
|
4670
|
paulo@89
|
4671 var seriesPerRow = 0;
|
paulo@89
|
4672 var legendWidth = 0;
|
paulo@89
|
4673 var columnWidths = [];
|
paulo@89
|
4674
|
paulo@89
|
4675 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
|
paulo@89
|
4676 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
|
paulo@89
|
4677 legendWidth += seriesWidths[seriesPerRow++];
|
paulo@89
|
4678 }
|
paulo@89
|
4679 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
|
paulo@89
|
4680
|
paulo@89
|
4681 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
|
paulo@89
|
4682 columnWidths = [];
|
paulo@89
|
4683 seriesPerRow--;
|
paulo@89
|
4684
|
paulo@89
|
4685 for (var k = 0; k < seriesWidths.length; k++) {
|
paulo@89
|
4686 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
|
paulo@89
|
4687 columnWidths[k % seriesPerRow] = seriesWidths[k];
|
paulo@89
|
4688 }
|
paulo@89
|
4689
|
paulo@89
|
4690 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
|
paulo@89
|
4691 return prev + cur;
|
paulo@89
|
4692 });
|
paulo@89
|
4693 }
|
paulo@89
|
4694
|
paulo@89
|
4695 var xPositions = [];
|
paulo@89
|
4696 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
|
paulo@89
|
4697 xPositions[i] = curX;
|
paulo@89
|
4698 curX += columnWidths[i];
|
paulo@89
|
4699 }
|
paulo@89
|
4700
|
paulo@89
|
4701 series
|
paulo@89
|
4702 .attr('transform', function(d, i) {
|
paulo@89
|
4703 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
|
paulo@89
|
4704 });
|
paulo@89
|
4705
|
paulo@89
|
4706 //position legend as far right as possible within the total width
|
paulo@89
|
4707 if (rightAlign) {
|
paulo@89
|
4708 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
|
paulo@89
|
4709 }
|
paulo@89
|
4710 else {
|
paulo@89
|
4711 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
|
paulo@89
|
4712 }
|
paulo@89
|
4713
|
paulo@89
|
4714 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
|
paulo@89
|
4715
|
paulo@89
|
4716 } else {
|
paulo@89
|
4717
|
paulo@89
|
4718 var ypos = 5,
|
paulo@89
|
4719 newxpos = 5,
|
paulo@89
|
4720 maxwidth = 0,
|
paulo@89
|
4721 xpos;
|
paulo@89
|
4722 series
|
paulo@89
|
4723 .attr('transform', function(d, i) {
|
paulo@89
|
4724 var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
|
paulo@89
|
4725 xpos = newxpos;
|
paulo@89
|
4726
|
paulo@89
|
4727 if (width < margin.left + margin.right + xpos + length) {
|
paulo@89
|
4728 newxpos = xpos = 5;
|
paulo@89
|
4729 ypos += versPadding;
|
paulo@89
|
4730 }
|
paulo@89
|
4731
|
paulo@89
|
4732 newxpos += length;
|
paulo@89
|
4733 if (newxpos > maxwidth) maxwidth = newxpos;
|
paulo@89
|
4734
|
paulo@89
|
4735 return 'translate(' + xpos + ',' + ypos + ')';
|
paulo@89
|
4736 });
|
paulo@89
|
4737
|
paulo@89
|
4738 //position legend as far right as possible within the total width
|
paulo@89
|
4739 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
|
paulo@89
|
4740
|
paulo@89
|
4741 height = margin.top + margin.bottom + ypos + 15;
|
paulo@89
|
4742 }
|
paulo@89
|
4743
|
paulo@89
|
4744 if(vers == 'furious') {
|
paulo@89
|
4745 // Size rectangles after text is placed
|
paulo@89
|
4746 seriesShape
|
paulo@89
|
4747 .attr('width', function(d,i) {
|
paulo@89
|
4748 return seriesText[0][i].getComputedTextLength() + 27;
|
paulo@89
|
4749 })
|
paulo@89
|
4750 .attr('height', 18)
|
paulo@89
|
4751 .attr('y', -9)
|
paulo@89
|
4752 .attr('x', -15)
|
paulo@89
|
4753 }
|
paulo@89
|
4754
|
paulo@89
|
4755 seriesShape
|
paulo@89
|
4756 .style('fill', setBGColor)
|
paulo@89
|
4757 .style('stroke', function(d,i) { return d.color || color(d, i) });
|
paulo@89
|
4758 });
|
paulo@89
|
4759
|
paulo@89
|
4760 function setTextColor(d,i) {
|
paulo@89
|
4761 if(vers != 'furious') return '#000';
|
paulo@89
|
4762 if(expanded) {
|
paulo@89
|
4763 return d.disengaged ? color(d,i) : '#fff';
|
paulo@89
|
4764 } else if (!expanded) {
|
paulo@89
|
4765 return !!d.disabled ? color(d,i) : '#fff';
|
paulo@89
|
4766 }
|
paulo@89
|
4767 }
|
paulo@89
|
4768
|
paulo@89
|
4769 function setBGColor(d,i) {
|
paulo@89
|
4770 if(expanded && vers == 'furious') {
|
paulo@89
|
4771 return d.disengaged ? '#fff' : color(d,i);
|
paulo@89
|
4772 } else {
|
paulo@89
|
4773 return !!d.disabled ? '#fff' : color(d,i);
|
paulo@89
|
4774 }
|
paulo@89
|
4775 }
|
paulo@89
|
4776
|
paulo@89
|
4777 return chart;
|
paulo@89
|
4778 }
|
paulo@89
|
4779
|
paulo@89
|
4780 //============================================================
|
paulo@89
|
4781 // Expose Public Variables
|
paulo@89
|
4782 //------------------------------------------------------------
|
paulo@89
|
4783
|
paulo@89
|
4784 chart.dispatch = dispatch;
|
paulo@89
|
4785 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
4786
|
paulo@89
|
4787 chart._options = Object.create({}, {
|
paulo@89
|
4788 // simple options, just get/set the necessary values
|
paulo@89
|
4789 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
4790 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
4791 key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
|
paulo@89
|
4792 align: {get: function(){return align;}, set: function(_){align=_;}},
|
paulo@89
|
4793 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
|
paulo@89
|
4794 padding: {get: function(){return padding;}, set: function(_){padding=_;}},
|
paulo@89
|
4795 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
|
paulo@89
|
4796 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
|
paulo@89
|
4797 expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
|
paulo@89
|
4798 vers: {get: function(){return vers;}, set: function(_){vers=_;}},
|
paulo@89
|
4799
|
paulo@89
|
4800 // options that require extra logic in the setter
|
paulo@89
|
4801 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
4802 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
4803 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
4804 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
4805 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
4806 }},
|
paulo@89
|
4807 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
4808 color = nv.utils.getColor(_);
|
paulo@89
|
4809 }}
|
paulo@89
|
4810 });
|
paulo@89
|
4811
|
paulo@89
|
4812 nv.utils.initOptions(chart);
|
paulo@89
|
4813
|
paulo@89
|
4814 return chart;
|
paulo@89
|
4815 };
|
paulo@89
|
4816 //TODO: consider deprecating and using multibar with single series for this
|
paulo@89
|
4817 nv.models.historicalBar = function() {
|
paulo@89
|
4818 "use strict";
|
paulo@89
|
4819
|
paulo@89
|
4820 //============================================================
|
paulo@89
|
4821 // Public Variables with Default Settings
|
paulo@89
|
4822 //------------------------------------------------------------
|
paulo@89
|
4823
|
paulo@89
|
4824 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
4825 , width = null
|
paulo@89
|
4826 , height = null
|
paulo@89
|
4827 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
4828 , container = null
|
paulo@89
|
4829 , x = d3.scale.linear()
|
paulo@89
|
4830 , y = d3.scale.linear()
|
paulo@89
|
4831 , getX = function(d) { return d.x }
|
paulo@89
|
4832 , getY = function(d) { return d.y }
|
paulo@89
|
4833 , forceX = []
|
paulo@89
|
4834 , forceY = [0]
|
paulo@89
|
4835 , padData = false
|
paulo@89
|
4836 , clipEdge = true
|
paulo@89
|
4837 , color = nv.utils.defaultColor()
|
paulo@89
|
4838 , xDomain
|
paulo@89
|
4839 , yDomain
|
paulo@89
|
4840 , xRange
|
paulo@89
|
4841 , yRange
|
paulo@89
|
4842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
|
paulo@89
|
4843 , interactive = true
|
paulo@89
|
4844 ;
|
paulo@89
|
4845
|
paulo@89
|
4846 var renderWatch = nv.utils.renderWatch(dispatch, 0);
|
paulo@89
|
4847
|
paulo@89
|
4848 function chart(selection) {
|
paulo@89
|
4849 selection.each(function(data) {
|
paulo@89
|
4850 renderWatch.reset();
|
paulo@89
|
4851
|
paulo@89
|
4852 container = d3.select(this);
|
paulo@89
|
4853 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
4854 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
4855
|
paulo@89
|
4856 nv.utils.initSVG(container);
|
paulo@89
|
4857
|
paulo@89
|
4858 // Setup Scales
|
paulo@89
|
4859 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
|
paulo@89
|
4860
|
paulo@89
|
4861 if (padData)
|
paulo@89
|
4862 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
|
paulo@89
|
4863 else
|
paulo@89
|
4864 x.range(xRange || [0, availableWidth]);
|
paulo@89
|
4865
|
paulo@89
|
4866 y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
|
paulo@89
|
4867 .range(yRange || [availableHeight, 0]);
|
paulo@89
|
4868
|
paulo@89
|
4869 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
|
paulo@89
|
4870 if (x.domain()[0] === x.domain()[1])
|
paulo@89
|
4871 x.domain()[0] ?
|
paulo@89
|
4872 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
|
paulo@89
|
4873 : x.domain([-1,1]);
|
paulo@89
|
4874
|
paulo@89
|
4875 if (y.domain()[0] === y.domain()[1])
|
paulo@89
|
4876 y.domain()[0] ?
|
paulo@89
|
4877 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
|
paulo@89
|
4878 : y.domain([-1,1]);
|
paulo@89
|
4879
|
paulo@89
|
4880 // Setup containers and skeleton of chart
|
paulo@89
|
4881 var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
|
paulo@89
|
4882 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
|
paulo@89
|
4883 var defsEnter = wrapEnter.append('defs');
|
paulo@89
|
4884 var gEnter = wrapEnter.append('g');
|
paulo@89
|
4885 var g = wrap.select('g');
|
paulo@89
|
4886
|
paulo@89
|
4887 gEnter.append('g').attr('class', 'nv-bars');
|
paulo@89
|
4888 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
4889
|
paulo@89
|
4890 container
|
paulo@89
|
4891 .on('click', function(d,i) {
|
paulo@89
|
4892 dispatch.chartClick({
|
paulo@89
|
4893 data: d,
|
paulo@89
|
4894 index: i,
|
paulo@89
|
4895 pos: d3.event,
|
paulo@89
|
4896 id: id
|
paulo@89
|
4897 });
|
paulo@89
|
4898 });
|
paulo@89
|
4899
|
paulo@89
|
4900 defsEnter.append('clipPath')
|
paulo@89
|
4901 .attr('id', 'nv-chart-clip-path-' + id)
|
paulo@89
|
4902 .append('rect');
|
paulo@89
|
4903
|
paulo@89
|
4904 wrap.select('#nv-chart-clip-path-' + id + ' rect')
|
paulo@89
|
4905 .attr('width', availableWidth)
|
paulo@89
|
4906 .attr('height', availableHeight);
|
paulo@89
|
4907
|
paulo@89
|
4908 g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
|
paulo@89
|
4909
|
paulo@89
|
4910 var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
|
paulo@89
|
4911 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
|
paulo@89
|
4912 bars.exit().remove();
|
paulo@89
|
4913
|
paulo@89
|
4914 bars.enter().append('rect')
|
paulo@89
|
4915 .attr('x', 0 )
|
paulo@89
|
4916 .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
|
paulo@89
|
4917 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
|
paulo@89
|
4918 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
|
paulo@89
|
4919 .on('mouseover', function(d,i) {
|
paulo@89
|
4920 if (!interactive) return;
|
paulo@89
|
4921 d3.select(this).classed('hover', true);
|
paulo@89
|
4922 dispatch.elementMouseover({
|
paulo@89
|
4923 data: d,
|
paulo@89
|
4924 index: i,
|
paulo@89
|
4925 color: d3.select(this).style("fill")
|
paulo@89
|
4926 });
|
paulo@89
|
4927
|
paulo@89
|
4928 })
|
paulo@89
|
4929 .on('mouseout', function(d,i) {
|
paulo@89
|
4930 if (!interactive) return;
|
paulo@89
|
4931 d3.select(this).classed('hover', false);
|
paulo@89
|
4932 dispatch.elementMouseout({
|
paulo@89
|
4933 data: d,
|
paulo@89
|
4934 index: i,
|
paulo@89
|
4935 color: d3.select(this).style("fill")
|
paulo@89
|
4936 });
|
paulo@89
|
4937 })
|
paulo@89
|
4938 .on('mousemove', function(d,i) {
|
paulo@89
|
4939 if (!interactive) return;
|
paulo@89
|
4940 dispatch.elementMousemove({
|
paulo@89
|
4941 data: d,
|
paulo@89
|
4942 index: i,
|
paulo@89
|
4943 color: d3.select(this).style("fill")
|
paulo@89
|
4944 });
|
paulo@89
|
4945 })
|
paulo@89
|
4946 .on('click', function(d,i) {
|
paulo@89
|
4947 if (!interactive) return;
|
paulo@89
|
4948 dispatch.elementClick({
|
paulo@89
|
4949 data: d,
|
paulo@89
|
4950 index: i,
|
paulo@89
|
4951 color: d3.select(this).style("fill")
|
paulo@89
|
4952 });
|
paulo@89
|
4953 d3.event.stopPropagation();
|
paulo@89
|
4954 })
|
paulo@89
|
4955 .on('dblclick', function(d,i) {
|
paulo@89
|
4956 if (!interactive) return;
|
paulo@89
|
4957 dispatch.elementDblClick({
|
paulo@89
|
4958 data: d,
|
paulo@89
|
4959 index: i,
|
paulo@89
|
4960 color: d3.select(this).style("fill")
|
paulo@89
|
4961 });
|
paulo@89
|
4962 d3.event.stopPropagation();
|
paulo@89
|
4963 });
|
paulo@89
|
4964
|
paulo@89
|
4965 bars
|
paulo@89
|
4966 .attr('fill', function(d,i) { return color(d, i); })
|
paulo@89
|
4967 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
|
paulo@89
|
4968 .watchTransition(renderWatch, 'bars')
|
paulo@89
|
4969 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
|
paulo@89
|
4970 //TODO: better width calculations that don't assume always uniform data spacing;w
|
paulo@89
|
4971 .attr('width', (availableWidth / data[0].values.length) * .9 );
|
paulo@89
|
4972
|
paulo@89
|
4973 bars.watchTransition(renderWatch, 'bars')
|
paulo@89
|
4974 .attr('y', function(d,i) {
|
paulo@89
|
4975 var rval = getY(d,i) < 0 ?
|
paulo@89
|
4976 y(0) :
|
paulo@89
|
4977 y(0) - y(getY(d,i)) < 1 ?
|
paulo@89
|
4978 y(0) - 1 :
|
paulo@89
|
4979 y(getY(d,i));
|
paulo@89
|
4980 return nv.utils.NaNtoZero(rval);
|
paulo@89
|
4981 })
|
paulo@89
|
4982 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
|
paulo@89
|
4983
|
paulo@89
|
4984 });
|
paulo@89
|
4985
|
paulo@89
|
4986 renderWatch.renderEnd('historicalBar immediate');
|
paulo@89
|
4987 return chart;
|
paulo@89
|
4988 }
|
paulo@89
|
4989
|
paulo@89
|
4990 //Create methods to allow outside functions to highlight a specific bar.
|
paulo@89
|
4991 chart.highlightPoint = function(pointIndex, isHoverOver) {
|
paulo@89
|
4992 container
|
paulo@89
|
4993 .select(".nv-bars .nv-bar-0-" + pointIndex)
|
paulo@89
|
4994 .classed("hover", isHoverOver)
|
paulo@89
|
4995 ;
|
paulo@89
|
4996 };
|
paulo@89
|
4997
|
paulo@89
|
4998 chart.clearHighlights = function() {
|
paulo@89
|
4999 container
|
paulo@89
|
5000 .select(".nv-bars .nv-bar.hover")
|
paulo@89
|
5001 .classed("hover", false)
|
paulo@89
|
5002 ;
|
paulo@89
|
5003 };
|
paulo@89
|
5004
|
paulo@89
|
5005 //============================================================
|
paulo@89
|
5006 // Expose Public Variables
|
paulo@89
|
5007 //------------------------------------------------------------
|
paulo@89
|
5008
|
paulo@89
|
5009 chart.dispatch = dispatch;
|
paulo@89
|
5010 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
5011
|
paulo@89
|
5012 chart._options = Object.create({}, {
|
paulo@89
|
5013 // simple options, just get/set the necessary values
|
paulo@89
|
5014 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
5015 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
5016 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
|
paulo@89
|
5017 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
|
paulo@89
|
5018 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
|
paulo@89
|
5019 x: {get: function(){return getX;}, set: function(_){getX=_;}},
|
paulo@89
|
5020 y: {get: function(){return getY;}, set: function(_){getY=_;}},
|
paulo@89
|
5021 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
5022 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
5023 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
5024 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
5025 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
5026 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
5027 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
|
paulo@89
|
5028 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
5029 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
|
paulo@89
|
5030
|
paulo@89
|
5031 // options that require extra logic in the setter
|
paulo@89
|
5032 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
5033 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
5034 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
5035 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
5036 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
5037 }},
|
paulo@89
|
5038 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
5039 color = nv.utils.getColor(_);
|
paulo@89
|
5040 }}
|
paulo@89
|
5041 });
|
paulo@89
|
5042
|
paulo@89
|
5043 nv.utils.initOptions(chart);
|
paulo@89
|
5044
|
paulo@89
|
5045 return chart;
|
paulo@89
|
5046 };
|
paulo@89
|
5047
|
paulo@89
|
5048 nv.models.historicalBarChart = function(bar_model) {
|
paulo@89
|
5049 "use strict";
|
paulo@89
|
5050
|
paulo@89
|
5051 //============================================================
|
paulo@89
|
5052 // Public Variables with Default Settings
|
paulo@89
|
5053 //------------------------------------------------------------
|
paulo@89
|
5054
|
paulo@89
|
5055 var bars = bar_model || nv.models.historicalBar()
|
paulo@89
|
5056 , xAxis = nv.models.axis()
|
paulo@89
|
5057 , yAxis = nv.models.axis()
|
paulo@89
|
5058 , legend = nv.models.legend()
|
paulo@89
|
5059 , interactiveLayer = nv.interactiveGuideline()
|
paulo@89
|
5060 , tooltip = nv.models.tooltip()
|
paulo@89
|
5061 ;
|
paulo@89
|
5062
|
paulo@89
|
5063
|
paulo@89
|
5064 var margin = {top: 30, right: 90, bottom: 50, left: 90}
|
paulo@89
|
5065 , color = nv.utils.defaultColor()
|
paulo@89
|
5066 , width = null
|
paulo@89
|
5067 , height = null
|
paulo@89
|
5068 , showLegend = false
|
paulo@89
|
5069 , showXAxis = true
|
paulo@89
|
5070 , showYAxis = true
|
paulo@89
|
5071 , rightAlignYAxis = false
|
paulo@89
|
5072 , useInteractiveGuideline = false
|
paulo@89
|
5073 , x
|
paulo@89
|
5074 , y
|
paulo@89
|
5075 , state = {}
|
paulo@89
|
5076 , defaultState = null
|
paulo@89
|
5077 , noData = null
|
paulo@89
|
5078 , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
|
paulo@89
|
5079 , transitionDuration = 250
|
paulo@89
|
5080 ;
|
paulo@89
|
5081
|
paulo@89
|
5082 xAxis.orient('bottom').tickPadding(7);
|
paulo@89
|
5083 yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
|
paulo@89
|
5084 tooltip
|
paulo@89
|
5085 .duration(0)
|
paulo@89
|
5086 .headerEnabled(false)
|
paulo@89
|
5087 .valueFormatter(function(d, i) {
|
paulo@89
|
5088 return yAxis.tickFormat()(d, i);
|
paulo@89
|
5089 })
|
paulo@89
|
5090 .headerFormatter(function(d, i) {
|
paulo@89
|
5091 return xAxis.tickFormat()(d, i);
|
paulo@89
|
5092 });
|
paulo@89
|
5093
|
paulo@89
|
5094
|
paulo@89
|
5095 //============================================================
|
paulo@89
|
5096 // Private Variables
|
paulo@89
|
5097 //------------------------------------------------------------
|
paulo@89
|
5098
|
paulo@89
|
5099 var renderWatch = nv.utils.renderWatch(dispatch, 0);
|
paulo@89
|
5100
|
paulo@89
|
5101 function chart(selection) {
|
paulo@89
|
5102 selection.each(function(data) {
|
paulo@89
|
5103 renderWatch.reset();
|
paulo@89
|
5104 renderWatch.models(bars);
|
paulo@89
|
5105 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
5106 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
5107
|
paulo@89
|
5108 var container = d3.select(this),
|
paulo@89
|
5109 that = this;
|
paulo@89
|
5110 nv.utils.initSVG(container);
|
paulo@89
|
5111 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
5112 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
5113
|
paulo@89
|
5114 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
|
paulo@89
|
5115 chart.container = this;
|
paulo@89
|
5116
|
paulo@89
|
5117 //set state.disabled
|
paulo@89
|
5118 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
5119
|
paulo@89
|
5120 if (!defaultState) {
|
paulo@89
|
5121 var key;
|
paulo@89
|
5122 defaultState = {};
|
paulo@89
|
5123 for (key in state) {
|
paulo@89
|
5124 if (state[key] instanceof Array)
|
paulo@89
|
5125 defaultState[key] = state[key].slice(0);
|
paulo@89
|
5126 else
|
paulo@89
|
5127 defaultState[key] = state[key];
|
paulo@89
|
5128 }
|
paulo@89
|
5129 }
|
paulo@89
|
5130
|
paulo@89
|
5131 // Display noData message if there's nothing to show.
|
paulo@89
|
5132 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
5133 nv.utils.noData(chart, container)
|
paulo@89
|
5134 return chart;
|
paulo@89
|
5135 } else {
|
paulo@89
|
5136 container.selectAll('.nv-noData').remove();
|
paulo@89
|
5137 }
|
paulo@89
|
5138
|
paulo@89
|
5139 // Setup Scales
|
paulo@89
|
5140 x = bars.xScale();
|
paulo@89
|
5141 y = bars.yScale();
|
paulo@89
|
5142
|
paulo@89
|
5143 // Setup containers and skeleton of chart
|
paulo@89
|
5144 var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
|
paulo@89
|
5145 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
|
paulo@89
|
5146 var g = wrap.select('g');
|
paulo@89
|
5147
|
paulo@89
|
5148 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
5149 gEnter.append('g').attr('class', 'nv-y nv-axis');
|
paulo@89
|
5150 gEnter.append('g').attr('class', 'nv-barsWrap');
|
paulo@89
|
5151 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
5152 gEnter.append('g').attr('class', 'nv-interactive');
|
paulo@89
|
5153
|
paulo@89
|
5154 // Legend
|
paulo@89
|
5155 if (showLegend) {
|
paulo@89
|
5156 legend.width(availableWidth);
|
paulo@89
|
5157
|
paulo@89
|
5158 g.select('.nv-legendWrap')
|
paulo@89
|
5159 .datum(data)
|
paulo@89
|
5160 .call(legend);
|
paulo@89
|
5161
|
paulo@89
|
5162 if ( margin.top != legend.height()) {
|
paulo@89
|
5163 margin.top = legend.height();
|
paulo@89
|
5164 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
5165 }
|
paulo@89
|
5166
|
paulo@89
|
5167 wrap.select('.nv-legendWrap')
|
paulo@89
|
5168 .attr('transform', 'translate(0,' + (-margin.top) +')')
|
paulo@89
|
5169 }
|
paulo@89
|
5170 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
5171
|
paulo@89
|
5172 if (rightAlignYAxis) {
|
paulo@89
|
5173 g.select(".nv-y.nv-axis")
|
paulo@89
|
5174 .attr("transform", "translate(" + availableWidth + ",0)");
|
paulo@89
|
5175 }
|
paulo@89
|
5176
|
paulo@89
|
5177 //Set up interactive layer
|
paulo@89
|
5178 if (useInteractiveGuideline) {
|
paulo@89
|
5179 interactiveLayer
|
paulo@89
|
5180 .width(availableWidth)
|
paulo@89
|
5181 .height(availableHeight)
|
paulo@89
|
5182 .margin({left:margin.left, top:margin.top})
|
paulo@89
|
5183 .svgContainer(container)
|
paulo@89
|
5184 .xScale(x);
|
paulo@89
|
5185 wrap.select(".nv-interactive").call(interactiveLayer);
|
paulo@89
|
5186 }
|
paulo@89
|
5187 bars
|
paulo@89
|
5188 .width(availableWidth)
|
paulo@89
|
5189 .height(availableHeight)
|
paulo@89
|
5190 .color(data.map(function(d,i) {
|
paulo@89
|
5191 return d.color || color(d, i);
|
paulo@89
|
5192 }).filter(function(d,i) { return !data[i].disabled }));
|
paulo@89
|
5193
|
paulo@89
|
5194 var barsWrap = g.select('.nv-barsWrap')
|
paulo@89
|
5195 .datum(data.filter(function(d) { return !d.disabled }));
|
paulo@89
|
5196 barsWrap.transition().call(bars);
|
paulo@89
|
5197
|
paulo@89
|
5198 // Setup Axes
|
paulo@89
|
5199 if (showXAxis) {
|
paulo@89
|
5200 xAxis
|
paulo@89
|
5201 .scale(x)
|
paulo@89
|
5202 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
5203 .tickSize(-availableHeight, 0);
|
paulo@89
|
5204
|
paulo@89
|
5205 g.select('.nv-x.nv-axis')
|
paulo@89
|
5206 .attr('transform', 'translate(0,' + y.range()[0] + ')');
|
paulo@89
|
5207 g.select('.nv-x.nv-axis')
|
paulo@89
|
5208 .transition()
|
paulo@89
|
5209 .call(xAxis);
|
paulo@89
|
5210 }
|
paulo@89
|
5211
|
paulo@89
|
5212 if (showYAxis) {
|
paulo@89
|
5213 yAxis
|
paulo@89
|
5214 .scale(y)
|
paulo@89
|
5215 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
|
paulo@89
|
5216 .tickSize( -availableWidth, 0);
|
paulo@89
|
5217
|
paulo@89
|
5218 g.select('.nv-y.nv-axis')
|
paulo@89
|
5219 .transition()
|
paulo@89
|
5220 .call(yAxis);
|
paulo@89
|
5221 }
|
paulo@89
|
5222
|
paulo@89
|
5223 //============================================================
|
paulo@89
|
5224 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
5225 //------------------------------------------------------------
|
paulo@89
|
5226
|
paulo@89
|
5227 interactiveLayer.dispatch.on('elementMousemove', function(e) {
|
paulo@89
|
5228 bars.clearHighlights();
|
paulo@89
|
5229
|
paulo@89
|
5230 var singlePoint, pointIndex, pointXLocation, allData = [];
|
paulo@89
|
5231 data
|
paulo@89
|
5232 .filter(function(series, i) {
|
paulo@89
|
5233 series.seriesIndex = i;
|
paulo@89
|
5234 return !series.disabled;
|
paulo@89
|
5235 })
|
paulo@89
|
5236 .forEach(function(series,i) {
|
paulo@89
|
5237 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
|
paulo@89
|
5238 bars.highlightPoint(pointIndex,true);
|
paulo@89
|
5239 var point = series.values[pointIndex];
|
paulo@89
|
5240 if (point === undefined) return;
|
paulo@89
|
5241 if (singlePoint === undefined) singlePoint = point;
|
paulo@89
|
5242 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
|
paulo@89
|
5243 allData.push({
|
paulo@89
|
5244 key: series.key,
|
paulo@89
|
5245 value: chart.y()(point, pointIndex),
|
paulo@89
|
5246 color: color(series,series.seriesIndex),
|
paulo@89
|
5247 data: series.values[pointIndex]
|
paulo@89
|
5248 });
|
paulo@89
|
5249 });
|
paulo@89
|
5250
|
paulo@89
|
5251 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
|
paulo@89
|
5252 interactiveLayer.tooltip
|
paulo@89
|
5253 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
|
paulo@89
|
5254 .chartContainer(that.parentNode)
|
paulo@89
|
5255 .valueFormatter(function(d,i) {
|
paulo@89
|
5256 return yAxis.tickFormat()(d);
|
paulo@89
|
5257 })
|
paulo@89
|
5258 .data({
|
paulo@89
|
5259 value: xValue,
|
paulo@89
|
5260 index: pointIndex,
|
paulo@89
|
5261 series: allData
|
paulo@89
|
5262 })();
|
paulo@89
|
5263
|
paulo@89
|
5264 interactiveLayer.renderGuideLine(pointXLocation);
|
paulo@89
|
5265
|
paulo@89
|
5266 });
|
paulo@89
|
5267
|
paulo@89
|
5268 interactiveLayer.dispatch.on("elementMouseout",function(e) {
|
paulo@89
|
5269 dispatch.tooltipHide();
|
paulo@89
|
5270 bars.clearHighlights();
|
paulo@89
|
5271 });
|
paulo@89
|
5272
|
paulo@89
|
5273 legend.dispatch.on('legendClick', function(d,i) {
|
paulo@89
|
5274 d.disabled = !d.disabled;
|
paulo@89
|
5275
|
paulo@89
|
5276 if (!data.filter(function(d) { return !d.disabled }).length) {
|
paulo@89
|
5277 data.map(function(d) {
|
paulo@89
|
5278 d.disabled = false;
|
paulo@89
|
5279 wrap.selectAll('.nv-series').classed('disabled', false);
|
paulo@89
|
5280 return d;
|
paulo@89
|
5281 });
|
paulo@89
|
5282 }
|
paulo@89
|
5283
|
paulo@89
|
5284 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
5285 dispatch.stateChange(state);
|
paulo@89
|
5286
|
paulo@89
|
5287 selection.transition().call(chart);
|
paulo@89
|
5288 });
|
paulo@89
|
5289
|
paulo@89
|
5290 legend.dispatch.on('legendDblclick', function(d) {
|
paulo@89
|
5291 //Double clicking should always enable current series, and disabled all others.
|
paulo@89
|
5292 data.forEach(function(d) {
|
paulo@89
|
5293 d.disabled = true;
|
paulo@89
|
5294 });
|
paulo@89
|
5295 d.disabled = false;
|
paulo@89
|
5296
|
paulo@89
|
5297 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
5298 dispatch.stateChange(state);
|
paulo@89
|
5299 chart.update();
|
paulo@89
|
5300 });
|
paulo@89
|
5301
|
paulo@89
|
5302 dispatch.on('changeState', function(e) {
|
paulo@89
|
5303 if (typeof e.disabled !== 'undefined') {
|
paulo@89
|
5304 data.forEach(function(series,i) {
|
paulo@89
|
5305 series.disabled = e.disabled[i];
|
paulo@89
|
5306 });
|
paulo@89
|
5307
|
paulo@89
|
5308 state.disabled = e.disabled;
|
paulo@89
|
5309 }
|
paulo@89
|
5310
|
paulo@89
|
5311 chart.update();
|
paulo@89
|
5312 });
|
paulo@89
|
5313 });
|
paulo@89
|
5314
|
paulo@89
|
5315 renderWatch.renderEnd('historicalBarChart immediate');
|
paulo@89
|
5316 return chart;
|
paulo@89
|
5317 }
|
paulo@89
|
5318
|
paulo@89
|
5319 //============================================================
|
paulo@89
|
5320 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
5321 //------------------------------------------------------------
|
paulo@89
|
5322
|
paulo@89
|
5323 bars.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
5324 evt['series'] = {
|
paulo@89
|
5325 key: chart.x()(evt.data),
|
paulo@89
|
5326 value: chart.y()(evt.data),
|
paulo@89
|
5327 color: evt.color
|
paulo@89
|
5328 };
|
paulo@89
|
5329 tooltip.data(evt).hidden(false);
|
paulo@89
|
5330 });
|
paulo@89
|
5331
|
paulo@89
|
5332 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
5333 tooltip.hidden(true);
|
paulo@89
|
5334 });
|
paulo@89
|
5335
|
paulo@89
|
5336 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
5337 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
5338 });
|
paulo@89
|
5339
|
paulo@89
|
5340 //============================================================
|
paulo@89
|
5341 // Expose Public Variables
|
paulo@89
|
5342 //------------------------------------------------------------
|
paulo@89
|
5343
|
paulo@89
|
5344 // expose chart's sub-components
|
paulo@89
|
5345 chart.dispatch = dispatch;
|
paulo@89
|
5346 chart.bars = bars;
|
paulo@89
|
5347 chart.legend = legend;
|
paulo@89
|
5348 chart.xAxis = xAxis;
|
paulo@89
|
5349 chart.yAxis = yAxis;
|
paulo@89
|
5350 chart.interactiveLayer = interactiveLayer;
|
paulo@89
|
5351 chart.tooltip = tooltip;
|
paulo@89
|
5352
|
paulo@89
|
5353 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
5354
|
paulo@89
|
5355 chart._options = Object.create({}, {
|
paulo@89
|
5356 // simple options, just get/set the necessary values
|
paulo@89
|
5357 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
5358 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
5359 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
5360 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
5361 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
5362 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
5363 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
5364
|
paulo@89
|
5365 // deprecated options
|
paulo@89
|
5366 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
5367 // deprecated after 1.7.1
|
paulo@89
|
5368 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
5369 tooltip.enabled(!!_);
|
paulo@89
|
5370 }},
|
paulo@89
|
5371 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
5372 // deprecated after 1.7.1
|
paulo@89
|
5373 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
5374 tooltip.contentGenerator(_);
|
paulo@89
|
5375 }},
|
paulo@89
|
5376
|
paulo@89
|
5377 // options that require extra logic in the setter
|
paulo@89
|
5378 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
5379 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
5380 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
5381 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
5382 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
5383 }},
|
paulo@89
|
5384 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
5385 color = nv.utils.getColor(_);
|
paulo@89
|
5386 legend.color(color);
|
paulo@89
|
5387 bars.color(color);
|
paulo@89
|
5388 }},
|
paulo@89
|
5389 duration: {get: function(){return transitionDuration;}, set: function(_){
|
paulo@89
|
5390 transitionDuration=_;
|
paulo@89
|
5391 renderWatch.reset(transitionDuration);
|
paulo@89
|
5392 yAxis.duration(transitionDuration);
|
paulo@89
|
5393 xAxis.duration(transitionDuration);
|
paulo@89
|
5394 }},
|
paulo@89
|
5395 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
|
paulo@89
|
5396 rightAlignYAxis = _;
|
paulo@89
|
5397 yAxis.orient( (_) ? 'right' : 'left');
|
paulo@89
|
5398 }},
|
paulo@89
|
5399 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
|
paulo@89
|
5400 useInteractiveGuideline = _;
|
paulo@89
|
5401 if (_ === true) {
|
paulo@89
|
5402 chart.interactive(false);
|
paulo@89
|
5403 }
|
paulo@89
|
5404 }}
|
paulo@89
|
5405 });
|
paulo@89
|
5406
|
paulo@89
|
5407 nv.utils.inheritOptions(chart, bars);
|
paulo@89
|
5408 nv.utils.initOptions(chart);
|
paulo@89
|
5409
|
paulo@89
|
5410 return chart;
|
paulo@89
|
5411 };
|
paulo@89
|
5412
|
paulo@89
|
5413
|
paulo@89
|
5414 // ohlcChart is just a historical chart with ohlc bars and some tweaks
|
paulo@89
|
5415 nv.models.ohlcBarChart = function() {
|
paulo@89
|
5416 var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
|
paulo@89
|
5417
|
paulo@89
|
5418 // special default tooltip since we show multiple values per x
|
paulo@89
|
5419 chart.useInteractiveGuideline(true);
|
paulo@89
|
5420 chart.interactiveLayer.tooltip.contentGenerator(function(data) {
|
paulo@89
|
5421 // we assume only one series exists for this chart
|
paulo@89
|
5422 var d = data.series[0].data;
|
paulo@89
|
5423 // match line colors as defined in nv.d3.css
|
paulo@89
|
5424 var color = d.open < d.close ? "2ca02c" : "d62728";
|
paulo@89
|
5425 return '' +
|
paulo@89
|
5426 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
|
paulo@89
|
5427 '<table>' +
|
paulo@89
|
5428 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
|
paulo@89
|
5429 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
|
paulo@89
|
5430 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
|
paulo@89
|
5431 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
|
paulo@89
|
5432 '</table>';
|
paulo@89
|
5433 });
|
paulo@89
|
5434 return chart;
|
paulo@89
|
5435 };
|
paulo@89
|
5436
|
paulo@89
|
5437 // candlestickChart is just a historical chart with candlestick bars and some tweaks
|
paulo@89
|
5438 nv.models.candlestickBarChart = function() {
|
paulo@89
|
5439 var chart = nv.models.historicalBarChart(nv.models.candlestickBar());
|
paulo@89
|
5440
|
paulo@89
|
5441 // special default tooltip since we show multiple values per x
|
paulo@89
|
5442 chart.useInteractiveGuideline(true);
|
paulo@89
|
5443 chart.interactiveLayer.tooltip.contentGenerator(function(data) {
|
paulo@89
|
5444 // we assume only one series exists for this chart
|
paulo@89
|
5445 var d = data.series[0].data;
|
paulo@89
|
5446 // match line colors as defined in nv.d3.css
|
paulo@89
|
5447 var color = d.open < d.close ? "2ca02c" : "d62728";
|
paulo@89
|
5448 return '' +
|
paulo@89
|
5449 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
|
paulo@89
|
5450 '<table>' +
|
paulo@89
|
5451 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
|
paulo@89
|
5452 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
|
paulo@89
|
5453 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
|
paulo@89
|
5454 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
|
paulo@89
|
5455 '</table>';
|
paulo@89
|
5456 });
|
paulo@89
|
5457 return chart;
|
paulo@89
|
5458 };
|
paulo@89
|
5459 nv.models.legend = function() {
|
paulo@89
|
5460 "use strict";
|
paulo@89
|
5461
|
paulo@89
|
5462 //============================================================
|
paulo@89
|
5463 // Public Variables with Default Settings
|
paulo@89
|
5464 //------------------------------------------------------------
|
paulo@89
|
5465
|
paulo@89
|
5466 var margin = {top: 5, right: 0, bottom: 5, left: 0}
|
paulo@89
|
5467 , width = 400
|
paulo@89
|
5468 , height = 20
|
paulo@89
|
5469 , getKey = function(d) { return d.key }
|
paulo@89
|
5470 , color = nv.utils.getColor()
|
paulo@89
|
5471 , align = true
|
paulo@89
|
5472 , padding = 32 //define how much space between legend items. - recommend 32 for furious version
|
paulo@89
|
5473 , rightAlign = true
|
paulo@89
|
5474 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
|
paulo@89
|
5475 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
|
paulo@89
|
5476 , expanded = false
|
paulo@89
|
5477 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
|
paulo@89
|
5478 , vers = 'classic' //Options are "classic" and "furious"
|
paulo@89
|
5479 ;
|
paulo@89
|
5480
|
paulo@89
|
5481 function chart(selection) {
|
paulo@89
|
5482 selection.each(function(data) {
|
paulo@89
|
5483 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
5484 container = d3.select(this);
|
paulo@89
|
5485 nv.utils.initSVG(container);
|
paulo@89
|
5486
|
paulo@89
|
5487 // Setup containers and skeleton of chart
|
paulo@89
|
5488 var wrap = container.selectAll('g.nv-legend').data([data]);
|
paulo@89
|
5489 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
|
paulo@89
|
5490 var g = wrap.select('g');
|
paulo@89
|
5491
|
paulo@89
|
5492 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
5493
|
paulo@89
|
5494 var series = g.selectAll('.nv-series')
|
paulo@89
|
5495 .data(function(d) {
|
paulo@89
|
5496 if(vers != 'furious') return d;
|
paulo@89
|
5497
|
paulo@89
|
5498 return d.filter(function(n) {
|
paulo@89
|
5499 return expanded ? true : !n.disengaged;
|
paulo@89
|
5500 });
|
paulo@89
|
5501 });
|
paulo@89
|
5502
|
paulo@89
|
5503 var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
|
paulo@89
|
5504 var seriesShape;
|
paulo@89
|
5505
|
paulo@89
|
5506 var versPadding;
|
paulo@89
|
5507 switch(vers) {
|
paulo@89
|
5508 case 'furious' :
|
paulo@89
|
5509 versPadding = 23;
|
paulo@89
|
5510 break;
|
paulo@89
|
5511 case 'classic' :
|
paulo@89
|
5512 versPadding = 20;
|
paulo@89
|
5513 }
|
paulo@89
|
5514
|
paulo@89
|
5515 if(vers == 'classic') {
|
paulo@89
|
5516 seriesEnter.append('circle')
|
paulo@89
|
5517 .style('stroke-width', 2)
|
paulo@89
|
5518 .attr('class','nv-legend-symbol')
|
paulo@89
|
5519 .attr('r', 5);
|
paulo@89
|
5520
|
paulo@89
|
5521 seriesShape = series.select('circle');
|
paulo@89
|
5522 } else if (vers == 'furious') {
|
paulo@89
|
5523 seriesEnter.append('rect')
|
paulo@89
|
5524 .style('stroke-width', 2)
|
paulo@89
|
5525 .attr('class','nv-legend-symbol')
|
paulo@89
|
5526 .attr('rx', 3)
|
paulo@89
|
5527 .attr('ry', 3);
|
paulo@89
|
5528
|
paulo@89
|
5529 seriesShape = series.select('.nv-legend-symbol');
|
paulo@89
|
5530
|
paulo@89
|
5531 seriesEnter.append('g')
|
paulo@89
|
5532 .attr('class', 'nv-check-box')
|
paulo@89
|
5533 .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
|
paulo@89
|
5534 .attr('transform', 'translate(-10,-8)scale(0.5)');
|
paulo@89
|
5535
|
paulo@89
|
5536 var seriesCheckbox = series.select('.nv-check-box');
|
paulo@89
|
5537
|
paulo@89
|
5538 seriesCheckbox.each(function(d,i) {
|
paulo@89
|
5539 d3.select(this).selectAll('path')
|
paulo@89
|
5540 .attr('stroke', setTextColor(d,i));
|
paulo@89
|
5541 });
|
paulo@89
|
5542 }
|
paulo@89
|
5543
|
paulo@89
|
5544 seriesEnter.append('text')
|
paulo@89
|
5545 .attr('text-anchor', 'start')
|
paulo@89
|
5546 .attr('class','nv-legend-text')
|
paulo@89
|
5547 .attr('dy', '.32em')
|
paulo@89
|
5548 .attr('dx', '8');
|
paulo@89
|
5549
|
paulo@89
|
5550 var seriesText = series.select('text.nv-legend-text');
|
paulo@89
|
5551
|
paulo@89
|
5552 series
|
paulo@89
|
5553 .on('mouseover', function(d,i) {
|
paulo@89
|
5554 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
|
paulo@89
|
5555 })
|
paulo@89
|
5556 .on('mouseout', function(d,i) {
|
paulo@89
|
5557 dispatch.legendMouseout(d,i);
|
paulo@89
|
5558 })
|
paulo@89
|
5559 .on('click', function(d,i) {
|
paulo@89
|
5560 dispatch.legendClick(d,i);
|
paulo@89
|
5561 // make sure we re-get data in case it was modified
|
paulo@89
|
5562 var data = series.data();
|
paulo@89
|
5563 if (updateState) {
|
paulo@89
|
5564 if(vers =='classic') {
|
paulo@89
|
5565 if (radioButtonMode) {
|
paulo@89
|
5566 //Radio button mode: set every series to disabled,
|
paulo@89
|
5567 // and enable the clicked series.
|
paulo@89
|
5568 data.forEach(function(series) { series.disabled = true});
|
paulo@89
|
5569 d.disabled = false;
|
paulo@89
|
5570 }
|
paulo@89
|
5571 else {
|
paulo@89
|
5572 d.disabled = !d.disabled;
|
paulo@89
|
5573 if (data.every(function(series) { return series.disabled})) {
|
paulo@89
|
5574 //the default behavior of NVD3 legends is, if every single series
|
paulo@89
|
5575 // is disabled, turn all series' back on.
|
paulo@89
|
5576 data.forEach(function(series) { series.disabled = false});
|
paulo@89
|
5577 }
|
paulo@89
|
5578 }
|
paulo@89
|
5579 } else if(vers == 'furious') {
|
paulo@89
|
5580 if(expanded) {
|
paulo@89
|
5581 d.disengaged = !d.disengaged;
|
paulo@89
|
5582 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
|
paulo@89
|
5583 d.disabled = d.disengaged || d.userDisabled;
|
paulo@89
|
5584 } else if (!expanded) {
|
paulo@89
|
5585 d.disabled = !d.disabled;
|
paulo@89
|
5586 d.userDisabled = d.disabled;
|
paulo@89
|
5587 var engaged = data.filter(function(d) { return !d.disengaged; });
|
paulo@89
|
5588 if (engaged.every(function(series) { return series.userDisabled })) {
|
paulo@89
|
5589 //the default behavior of NVD3 legends is, if every single series
|
paulo@89
|
5590 // is disabled, turn all series' back on.
|
paulo@89
|
5591 data.forEach(function(series) {
|
paulo@89
|
5592 series.disabled = series.userDisabled = false;
|
paulo@89
|
5593 });
|
paulo@89
|
5594 }
|
paulo@89
|
5595 }
|
paulo@89
|
5596 }
|
paulo@89
|
5597 dispatch.stateChange({
|
paulo@89
|
5598 disabled: data.map(function(d) { return !!d.disabled }),
|
paulo@89
|
5599 disengaged: data.map(function(d) { return !!d.disengaged })
|
paulo@89
|
5600 });
|
paulo@89
|
5601
|
paulo@89
|
5602 }
|
paulo@89
|
5603 })
|
paulo@89
|
5604 .on('dblclick', function(d,i) {
|
paulo@89
|
5605 if(vers == 'furious' && expanded) return;
|
paulo@89
|
5606 dispatch.legendDblclick(d,i);
|
paulo@89
|
5607 if (updateState) {
|
paulo@89
|
5608 // make sure we re-get data in case it was modified
|
paulo@89
|
5609 var data = series.data();
|
paulo@89
|
5610 //the default behavior of NVD3 legends, when double clicking one,
|
paulo@89
|
5611 // is to set all other series' to false, and make the double clicked series enabled.
|
paulo@89
|
5612 data.forEach(function(series) {
|
paulo@89
|
5613 series.disabled = true;
|
paulo@89
|
5614 if(vers == 'furious') series.userDisabled = series.disabled;
|
paulo@89
|
5615 });
|
paulo@89
|
5616 d.disabled = false;
|
paulo@89
|
5617 if(vers == 'furious') d.userDisabled = d.disabled;
|
paulo@89
|
5618 dispatch.stateChange({
|
paulo@89
|
5619 disabled: data.map(function(d) { return !!d.disabled })
|
paulo@89
|
5620 });
|
paulo@89
|
5621 }
|
paulo@89
|
5622 });
|
paulo@89
|
5623
|
paulo@89
|
5624 series.classed('nv-disabled', function(d) { return d.userDisabled });
|
paulo@89
|
5625 series.exit().remove();
|
paulo@89
|
5626
|
paulo@89
|
5627 seriesText
|
paulo@89
|
5628 .attr('fill', setTextColor)
|
paulo@89
|
5629 .text(getKey);
|
paulo@89
|
5630
|
paulo@89
|
5631 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
|
paulo@89
|
5632 // NEW ALIGNING CODE, TODO: clean up
|
paulo@89
|
5633 var legendWidth = 0;
|
paulo@89
|
5634 if (align) {
|
paulo@89
|
5635
|
paulo@89
|
5636 var seriesWidths = [];
|
paulo@89
|
5637 series.each(function(d,i) {
|
paulo@89
|
5638 var legendText = d3.select(this).select('text');
|
paulo@89
|
5639 var nodeTextLength;
|
paulo@89
|
5640 try {
|
paulo@89
|
5641 nodeTextLength = legendText.node().getComputedTextLength();
|
paulo@89
|
5642 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
|
paulo@89
|
5643 if(nodeTextLength <= 0) throw Error();
|
paulo@89
|
5644 }
|
paulo@89
|
5645 catch(e) {
|
paulo@89
|
5646 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
|
paulo@89
|
5647 }
|
paulo@89
|
5648
|
paulo@89
|
5649 seriesWidths.push(nodeTextLength + padding);
|
paulo@89
|
5650 });
|
paulo@89
|
5651
|
paulo@89
|
5652 var seriesPerRow = 0;
|
paulo@89
|
5653 var columnWidths = [];
|
paulo@89
|
5654 legendWidth = 0;
|
paulo@89
|
5655
|
paulo@89
|
5656 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
|
paulo@89
|
5657 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
|
paulo@89
|
5658 legendWidth += seriesWidths[seriesPerRow++];
|
paulo@89
|
5659 }
|
paulo@89
|
5660 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
|
paulo@89
|
5661
|
paulo@89
|
5662 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
|
paulo@89
|
5663 columnWidths = [];
|
paulo@89
|
5664 seriesPerRow--;
|
paulo@89
|
5665
|
paulo@89
|
5666 for (var k = 0; k < seriesWidths.length; k++) {
|
paulo@89
|
5667 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
|
paulo@89
|
5668 columnWidths[k % seriesPerRow] = seriesWidths[k];
|
paulo@89
|
5669 }
|
paulo@89
|
5670
|
paulo@89
|
5671 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
|
paulo@89
|
5672 return prev + cur;
|
paulo@89
|
5673 });
|
paulo@89
|
5674 }
|
paulo@89
|
5675
|
paulo@89
|
5676 var xPositions = [];
|
paulo@89
|
5677 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
|
paulo@89
|
5678 xPositions[i] = curX;
|
paulo@89
|
5679 curX += columnWidths[i];
|
paulo@89
|
5680 }
|
paulo@89
|
5681
|
paulo@89
|
5682 series
|
paulo@89
|
5683 .attr('transform', function(d, i) {
|
paulo@89
|
5684 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
|
paulo@89
|
5685 });
|
paulo@89
|
5686
|
paulo@89
|
5687 //position legend as far right as possible within the total width
|
paulo@89
|
5688 if (rightAlign) {
|
paulo@89
|
5689 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
|
paulo@89
|
5690 }
|
paulo@89
|
5691 else {
|
paulo@89
|
5692 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
|
paulo@89
|
5693 }
|
paulo@89
|
5694
|
paulo@89
|
5695 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
|
paulo@89
|
5696
|
paulo@89
|
5697 } else {
|
paulo@89
|
5698
|
paulo@89
|
5699 var ypos = 5,
|
paulo@89
|
5700 newxpos = 5,
|
paulo@89
|
5701 maxwidth = 0,
|
paulo@89
|
5702 xpos;
|
paulo@89
|
5703 series
|
paulo@89
|
5704 .attr('transform', function(d, i) {
|
paulo@89
|
5705 var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
|
paulo@89
|
5706 xpos = newxpos;
|
paulo@89
|
5707
|
paulo@89
|
5708 if (width < margin.left + margin.right + xpos + length) {
|
paulo@89
|
5709 newxpos = xpos = 5;
|
paulo@89
|
5710 ypos += versPadding;
|
paulo@89
|
5711 }
|
paulo@89
|
5712
|
paulo@89
|
5713 newxpos += length;
|
paulo@89
|
5714 if (newxpos > maxwidth) maxwidth = newxpos;
|
paulo@89
|
5715
|
paulo@89
|
5716 if(legendWidth < xpos + maxwidth) {
|
paulo@89
|
5717 legendWidth = xpos + maxwidth;
|
paulo@89
|
5718 }
|
paulo@89
|
5719 return 'translate(' + xpos + ',' + ypos + ')';
|
paulo@89
|
5720 });
|
paulo@89
|
5721
|
paulo@89
|
5722 //position legend as far right as possible within the total width
|
paulo@89
|
5723 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
|
paulo@89
|
5724
|
paulo@89
|
5725 height = margin.top + margin.bottom + ypos + 15;
|
paulo@89
|
5726 }
|
paulo@89
|
5727
|
paulo@89
|
5728 if(vers == 'furious') {
|
paulo@89
|
5729 // Size rectangles after text is placed
|
paulo@89
|
5730 seriesShape
|
paulo@89
|
5731 .attr('width', function(d,i) {
|
paulo@89
|
5732 return seriesText[0][i].getComputedTextLength() + 27;
|
paulo@89
|
5733 })
|
paulo@89
|
5734 .attr('height', 18)
|
paulo@89
|
5735 .attr('y', -9)
|
paulo@89
|
5736 .attr('x', -15);
|
paulo@89
|
5737
|
paulo@89
|
5738 // The background for the expanded legend (UI)
|
paulo@89
|
5739 gEnter.insert('rect',':first-child')
|
paulo@89
|
5740 .attr('class', 'nv-legend-bg')
|
paulo@89
|
5741 .attr('fill', '#eee')
|
paulo@89
|
5742 // .attr('stroke', '#444')
|
paulo@89
|
5743 .attr('opacity',0);
|
paulo@89
|
5744
|
paulo@89
|
5745 var seriesBG = g.select('.nv-legend-bg');
|
paulo@89
|
5746
|
paulo@89
|
5747 seriesBG
|
paulo@89
|
5748 .transition().duration(300)
|
paulo@89
|
5749 .attr('x', -versPadding )
|
paulo@89
|
5750 .attr('width', legendWidth + versPadding - 12)
|
paulo@89
|
5751 .attr('height', height + 10)
|
paulo@89
|
5752 .attr('y', -margin.top - 10)
|
paulo@89
|
5753 .attr('opacity', expanded ? 1 : 0);
|
paulo@89
|
5754
|
paulo@89
|
5755
|
paulo@89
|
5756 }
|
paulo@89
|
5757
|
paulo@89
|
5758 seriesShape
|
paulo@89
|
5759 .style('fill', setBGColor)
|
paulo@89
|
5760 .style('fill-opacity', setBGOpacity)
|
paulo@89
|
5761 .style('stroke', setBGColor);
|
paulo@89
|
5762 });
|
paulo@89
|
5763
|
paulo@89
|
5764 function setTextColor(d,i) {
|
paulo@89
|
5765 if(vers != 'furious') return '#000';
|
paulo@89
|
5766 if(expanded) {
|
paulo@89
|
5767 return d.disengaged ? '#000' : '#fff';
|
paulo@89
|
5768 } else if (!expanded) {
|
paulo@89
|
5769 if(!d.color) d.color = color(d,i);
|
paulo@89
|
5770 return !!d.disabled ? d.color : '#fff';
|
paulo@89
|
5771 }
|
paulo@89
|
5772 }
|
paulo@89
|
5773
|
paulo@89
|
5774 function setBGColor(d,i) {
|
paulo@89
|
5775 if(expanded && vers == 'furious') {
|
paulo@89
|
5776 return d.disengaged ? '#eee' : d.color || color(d,i);
|
paulo@89
|
5777 } else {
|
paulo@89
|
5778 return d.color || color(d,i);
|
paulo@89
|
5779 }
|
paulo@89
|
5780 }
|
paulo@89
|
5781
|
paulo@89
|
5782
|
paulo@89
|
5783 function setBGOpacity(d,i) {
|
paulo@89
|
5784 if(expanded && vers == 'furious') {
|
paulo@89
|
5785 return 1;
|
paulo@89
|
5786 } else {
|
paulo@89
|
5787 return !!d.disabled ? 0 : 1;
|
paulo@89
|
5788 }
|
paulo@89
|
5789 }
|
paulo@89
|
5790
|
paulo@89
|
5791 return chart;
|
paulo@89
|
5792 }
|
paulo@89
|
5793
|
paulo@89
|
5794 //============================================================
|
paulo@89
|
5795 // Expose Public Variables
|
paulo@89
|
5796 //------------------------------------------------------------
|
paulo@89
|
5797
|
paulo@89
|
5798 chart.dispatch = dispatch;
|
paulo@89
|
5799 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
5800
|
paulo@89
|
5801 chart._options = Object.create({}, {
|
paulo@89
|
5802 // simple options, just get/set the necessary values
|
paulo@89
|
5803 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
5804 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
5805 key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
|
paulo@89
|
5806 align: {get: function(){return align;}, set: function(_){align=_;}},
|
paulo@89
|
5807 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
|
paulo@89
|
5808 padding: {get: function(){return padding;}, set: function(_){padding=_;}},
|
paulo@89
|
5809 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
|
paulo@89
|
5810 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
|
paulo@89
|
5811 expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
|
paulo@89
|
5812 vers: {get: function(){return vers;}, set: function(_){vers=_;}},
|
paulo@89
|
5813
|
paulo@89
|
5814 // options that require extra logic in the setter
|
paulo@89
|
5815 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
5816 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
5817 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
5818 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
5819 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
5820 }},
|
paulo@89
|
5821 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
5822 color = nv.utils.getColor(_);
|
paulo@89
|
5823 }}
|
paulo@89
|
5824 });
|
paulo@89
|
5825
|
paulo@89
|
5826 nv.utils.initOptions(chart);
|
paulo@89
|
5827
|
paulo@89
|
5828 return chart;
|
paulo@89
|
5829 };
|
paulo@89
|
5830
|
paulo@89
|
5831 nv.models.line = function() {
|
paulo@89
|
5832 "use strict";
|
paulo@89
|
5833 //============================================================
|
paulo@89
|
5834 // Public Variables with Default Settings
|
paulo@89
|
5835 //------------------------------------------------------------
|
paulo@89
|
5836
|
paulo@89
|
5837 var scatter = nv.models.scatter()
|
paulo@89
|
5838 ;
|
paulo@89
|
5839
|
paulo@89
|
5840 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
5841 , width = 960
|
paulo@89
|
5842 , height = 500
|
paulo@89
|
5843 , container = null
|
paulo@89
|
5844 , strokeWidth = 1.5
|
paulo@89
|
5845 , color = nv.utils.defaultColor() // a function that returns a color
|
paulo@89
|
5846 , getX = function(d) { return d.x } // accessor to get the x value from a data point
|
paulo@89
|
5847 , getY = function(d) { return d.y } // accessor to get the y value from a data point
|
paulo@89
|
5848 , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined
|
paulo@89
|
5849 , isArea = function(d) { return d.area } // decides if a line is an area or just a line
|
paulo@89
|
5850 , clipEdge = false // if true, masks lines within x and y scale
|
paulo@89
|
5851 , x //can be accessed via chart.xScale()
|
paulo@89
|
5852 , y //can be accessed via chart.yScale()
|
paulo@89
|
5853 , interpolate = "linear" // controls the line interpolation
|
paulo@89
|
5854 , duration = 250
|
paulo@89
|
5855 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
|
paulo@89
|
5856 ;
|
paulo@89
|
5857
|
paulo@89
|
5858 scatter
|
paulo@89
|
5859 .pointSize(16) // default size
|
paulo@89
|
5860 .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
|
paulo@89
|
5861 ;
|
paulo@89
|
5862
|
paulo@89
|
5863 //============================================================
|
paulo@89
|
5864
|
paulo@89
|
5865
|
paulo@89
|
5866 //============================================================
|
paulo@89
|
5867 // Private Variables
|
paulo@89
|
5868 //------------------------------------------------------------
|
paulo@89
|
5869
|
paulo@89
|
5870 var x0, y0 //used to store previous scales
|
paulo@89
|
5871 , renderWatch = nv.utils.renderWatch(dispatch, duration)
|
paulo@89
|
5872 ;
|
paulo@89
|
5873
|
paulo@89
|
5874 //============================================================
|
paulo@89
|
5875
|
paulo@89
|
5876
|
paulo@89
|
5877 function chart(selection) {
|
paulo@89
|
5878 renderWatch.reset();
|
paulo@89
|
5879 renderWatch.models(scatter);
|
paulo@89
|
5880 selection.each(function(data) {
|
paulo@89
|
5881 container = d3.select(this);
|
paulo@89
|
5882 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
5883 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
5884 nv.utils.initSVG(container);
|
paulo@89
|
5885
|
paulo@89
|
5886 // Setup Scales
|
paulo@89
|
5887 x = scatter.xScale();
|
paulo@89
|
5888 y = scatter.yScale();
|
paulo@89
|
5889
|
paulo@89
|
5890 x0 = x0 || x;
|
paulo@89
|
5891 y0 = y0 || y;
|
paulo@89
|
5892
|
paulo@89
|
5893 // Setup containers and skeleton of chart
|
paulo@89
|
5894 var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
|
paulo@89
|
5895 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
|
paulo@89
|
5896 var defsEnter = wrapEnter.append('defs');
|
paulo@89
|
5897 var gEnter = wrapEnter.append('g');
|
paulo@89
|
5898 var g = wrap.select('g');
|
paulo@89
|
5899
|
paulo@89
|
5900 gEnter.append('g').attr('class', 'nv-groups');
|
paulo@89
|
5901 gEnter.append('g').attr('class', 'nv-scatterWrap');
|
paulo@89
|
5902
|
paulo@89
|
5903 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
5904
|
paulo@89
|
5905 scatter
|
paulo@89
|
5906 .width(availableWidth)
|
paulo@89
|
5907 .height(availableHeight);
|
paulo@89
|
5908
|
paulo@89
|
5909 var scatterWrap = wrap.select('.nv-scatterWrap');
|
paulo@89
|
5910 scatterWrap.call(scatter);
|
paulo@89
|
5911
|
paulo@89
|
5912 defsEnter.append('clipPath')
|
paulo@89
|
5913 .attr('id', 'nv-edge-clip-' + scatter.id())
|
paulo@89
|
5914 .append('rect');
|
paulo@89
|
5915
|
paulo@89
|
5916 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
|
paulo@89
|
5917 .attr('width', availableWidth)
|
paulo@89
|
5918 .attr('height', (availableHeight > 0) ? availableHeight : 0);
|
paulo@89
|
5919
|
paulo@89
|
5920 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
|
paulo@89
|
5921 scatterWrap
|
paulo@89
|
5922 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
|
paulo@89
|
5923
|
paulo@89
|
5924 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
|
paulo@89
|
5925 .data(function(d) { return d }, function(d) { return d.key });
|
paulo@89
|
5926 groups.enter().append('g')
|
paulo@89
|
5927 .style('stroke-opacity', 1e-6)
|
paulo@89
|
5928 .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth })
|
paulo@89
|
5929 .style('fill-opacity', 1e-6);
|
paulo@89
|
5930
|
paulo@89
|
5931 groups.exit().remove();
|
paulo@89
|
5932
|
paulo@89
|
5933 groups
|
paulo@89
|
5934 .attr('class', function(d,i) {
|
paulo@89
|
5935 return (d.classed || '') + ' nv-group nv-series-' + i;
|
paulo@89
|
5936 })
|
paulo@89
|
5937 .classed('hover', function(d) { return d.hover })
|
paulo@89
|
5938 .style('fill', function(d,i){ return color(d, i) })
|
paulo@89
|
5939 .style('stroke', function(d,i){ return color(d, i)});
|
paulo@89
|
5940 groups.watchTransition(renderWatch, 'line: groups')
|
paulo@89
|
5941 .style('stroke-opacity', 1)
|
paulo@89
|
5942 .style('fill-opacity', function(d) { return d.fillOpacity || .5});
|
paulo@89
|
5943
|
paulo@89
|
5944 var areaPaths = groups.selectAll('path.nv-area')
|
paulo@89
|
5945 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
|
paulo@89
|
5946 areaPaths.enter().append('path')
|
paulo@89
|
5947 .attr('class', 'nv-area')
|
paulo@89
|
5948 .attr('d', function(d) {
|
paulo@89
|
5949 return d3.svg.area()
|
paulo@89
|
5950 .interpolate(interpolate)
|
paulo@89
|
5951 .defined(defined)
|
paulo@89
|
5952 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
|
paulo@89
|
5953 .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
|
paulo@89
|
5954 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
|
paulo@89
|
5955 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
|
paulo@89
|
5956 .apply(this, [d.values])
|
paulo@89
|
5957 });
|
paulo@89
|
5958 groups.exit().selectAll('path.nv-area')
|
paulo@89
|
5959 .remove();
|
paulo@89
|
5960
|
paulo@89
|
5961 areaPaths.watchTransition(renderWatch, 'line: areaPaths')
|
paulo@89
|
5962 .attr('d', function(d) {
|
paulo@89
|
5963 return d3.svg.area()
|
paulo@89
|
5964 .interpolate(interpolate)
|
paulo@89
|
5965 .defined(defined)
|
paulo@89
|
5966 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
|
paulo@89
|
5967 .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
|
paulo@89
|
5968 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
|
paulo@89
|
5969 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
|
paulo@89
|
5970 .apply(this, [d.values])
|
paulo@89
|
5971 });
|
paulo@89
|
5972
|
paulo@89
|
5973 var linePaths = groups.selectAll('path.nv-line')
|
paulo@89
|
5974 .data(function(d) { return [d.values] });
|
paulo@89
|
5975
|
paulo@89
|
5976 linePaths.enter().append('path')
|
paulo@89
|
5977 .attr('class', 'nv-line')
|
paulo@89
|
5978 .attr('d',
|
paulo@89
|
5979 d3.svg.line()
|
paulo@89
|
5980 .interpolate(interpolate)
|
paulo@89
|
5981 .defined(defined)
|
paulo@89
|
5982 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
|
paulo@89
|
5983 .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
|
paulo@89
|
5984 );
|
paulo@89
|
5985
|
paulo@89
|
5986 linePaths.watchTransition(renderWatch, 'line: linePaths')
|
paulo@89
|
5987 .attr('d',
|
paulo@89
|
5988 d3.svg.line()
|
paulo@89
|
5989 .interpolate(interpolate)
|
paulo@89
|
5990 .defined(defined)
|
paulo@89
|
5991 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
|
paulo@89
|
5992 .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
|
paulo@89
|
5993 );
|
paulo@89
|
5994
|
paulo@89
|
5995 //store old scales for use in transitions on update
|
paulo@89
|
5996 x0 = x.copy();
|
paulo@89
|
5997 y0 = y.copy();
|
paulo@89
|
5998 });
|
paulo@89
|
5999 renderWatch.renderEnd('line immediate');
|
paulo@89
|
6000 return chart;
|
paulo@89
|
6001 }
|
paulo@89
|
6002
|
paulo@89
|
6003
|
paulo@89
|
6004 //============================================================
|
paulo@89
|
6005 // Expose Public Variables
|
paulo@89
|
6006 //------------------------------------------------------------
|
paulo@89
|
6007
|
paulo@89
|
6008 chart.dispatch = dispatch;
|
paulo@89
|
6009 chart.scatter = scatter;
|
paulo@89
|
6010 // Pass through events
|
paulo@89
|
6011 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
|
paulo@89
|
6012 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
|
paulo@89
|
6013 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
|
paulo@89
|
6014
|
paulo@89
|
6015 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
6016
|
paulo@89
|
6017 chart._options = Object.create({}, {
|
paulo@89
|
6018 // simple options, just get/set the necessary values
|
paulo@89
|
6019 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
6020 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
6021 defined: {get: function(){return defined;}, set: function(_){defined=_;}},
|
paulo@89
|
6022 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
|
paulo@89
|
6023 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
|
paulo@89
|
6024
|
paulo@89
|
6025 // options that require extra logic in the setter
|
paulo@89
|
6026 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
6027 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
6028 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
6029 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
6030 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
6031 }},
|
paulo@89
|
6032 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
6033 duration = _;
|
paulo@89
|
6034 renderWatch.reset(duration);
|
paulo@89
|
6035 scatter.duration(duration);
|
paulo@89
|
6036 }},
|
paulo@89
|
6037 isArea: {get: function(){return isArea;}, set: function(_){
|
paulo@89
|
6038 isArea = d3.functor(_);
|
paulo@89
|
6039 }},
|
paulo@89
|
6040 x: {get: function(){return getX;}, set: function(_){
|
paulo@89
|
6041 getX = _;
|
paulo@89
|
6042 scatter.x(_);
|
paulo@89
|
6043 }},
|
paulo@89
|
6044 y: {get: function(){return getY;}, set: function(_){
|
paulo@89
|
6045 getY = _;
|
paulo@89
|
6046 scatter.y(_);
|
paulo@89
|
6047 }},
|
paulo@89
|
6048 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
6049 color = nv.utils.getColor(_);
|
paulo@89
|
6050 scatter.color(color);
|
paulo@89
|
6051 }}
|
paulo@89
|
6052 });
|
paulo@89
|
6053
|
paulo@89
|
6054 nv.utils.inheritOptions(chart, scatter);
|
paulo@89
|
6055 nv.utils.initOptions(chart);
|
paulo@89
|
6056
|
paulo@89
|
6057 return chart;
|
paulo@89
|
6058 };
|
paulo@89
|
6059 nv.models.lineChart = function() {
|
paulo@89
|
6060 "use strict";
|
paulo@89
|
6061
|
paulo@89
|
6062 //============================================================
|
paulo@89
|
6063 // Public Variables with Default Settings
|
paulo@89
|
6064 //------------------------------------------------------------
|
paulo@89
|
6065
|
paulo@89
|
6066 var lines = nv.models.line()
|
paulo@89
|
6067 , xAxis = nv.models.axis()
|
paulo@89
|
6068 , yAxis = nv.models.axis()
|
paulo@89
|
6069 , legend = nv.models.legend()
|
paulo@89
|
6070 , interactiveLayer = nv.interactiveGuideline()
|
paulo@89
|
6071 , tooltip = nv.models.tooltip()
|
paulo@89
|
6072 ;
|
paulo@89
|
6073
|
paulo@89
|
6074 var margin = {top: 30, right: 20, bottom: 50, left: 60}
|
paulo@89
|
6075 , color = nv.utils.defaultColor()
|
paulo@89
|
6076 , width = null
|
paulo@89
|
6077 , height = null
|
paulo@89
|
6078 , showLegend = true
|
paulo@89
|
6079 , showXAxis = true
|
paulo@89
|
6080 , showYAxis = true
|
paulo@89
|
6081 , rightAlignYAxis = false
|
paulo@89
|
6082 , useInteractiveGuideline = false
|
paulo@89
|
6083 , x
|
paulo@89
|
6084 , y
|
paulo@89
|
6085 , state = nv.utils.state()
|
paulo@89
|
6086 , defaultState = null
|
paulo@89
|
6087 , noData = null
|
paulo@89
|
6088 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
|
paulo@89
|
6089 , duration = 250
|
paulo@89
|
6090 ;
|
paulo@89
|
6091
|
paulo@89
|
6092 // set options on sub-objects for this chart
|
paulo@89
|
6093 xAxis.orient('bottom').tickPadding(7);
|
paulo@89
|
6094 yAxis.orient(rightAlignYAxis ? 'right' : 'left');
|
paulo@89
|
6095 tooltip.valueFormatter(function(d, i) {
|
paulo@89
|
6096 return yAxis.tickFormat()(d, i);
|
paulo@89
|
6097 }).headerFormatter(function(d, i) {
|
paulo@89
|
6098 return xAxis.tickFormat()(d, i);
|
paulo@89
|
6099 });
|
paulo@89
|
6100
|
paulo@89
|
6101
|
paulo@89
|
6102 //============================================================
|
paulo@89
|
6103 // Private Variables
|
paulo@89
|
6104 //------------------------------------------------------------
|
paulo@89
|
6105
|
paulo@89
|
6106 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
6107
|
paulo@89
|
6108 var stateGetter = function(data) {
|
paulo@89
|
6109 return function(){
|
paulo@89
|
6110 return {
|
paulo@89
|
6111 active: data.map(function(d) { return !d.disabled })
|
paulo@89
|
6112 };
|
paulo@89
|
6113 }
|
paulo@89
|
6114 };
|
paulo@89
|
6115
|
paulo@89
|
6116 var stateSetter = function(data) {
|
paulo@89
|
6117 return function(state) {
|
paulo@89
|
6118 if (state.active !== undefined)
|
paulo@89
|
6119 data.forEach(function(series,i) {
|
paulo@89
|
6120 series.disabled = !state.active[i];
|
paulo@89
|
6121 });
|
paulo@89
|
6122 }
|
paulo@89
|
6123 };
|
paulo@89
|
6124
|
paulo@89
|
6125 function chart(selection) {
|
paulo@89
|
6126 renderWatch.reset();
|
paulo@89
|
6127 renderWatch.models(lines);
|
paulo@89
|
6128 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
6129 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
6130
|
paulo@89
|
6131 selection.each(function(data) {
|
paulo@89
|
6132 var container = d3.select(this),
|
paulo@89
|
6133 that = this;
|
paulo@89
|
6134 nv.utils.initSVG(container);
|
paulo@89
|
6135 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
6136 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
6137
|
paulo@89
|
6138 chart.update = function() {
|
paulo@89
|
6139 if (duration === 0)
|
paulo@89
|
6140 container.call(chart);
|
paulo@89
|
6141 else
|
paulo@89
|
6142 container.transition().duration(duration).call(chart)
|
paulo@89
|
6143 };
|
paulo@89
|
6144 chart.container = this;
|
paulo@89
|
6145
|
paulo@89
|
6146 state
|
paulo@89
|
6147 .setter(stateSetter(data), chart.update)
|
paulo@89
|
6148 .getter(stateGetter(data))
|
paulo@89
|
6149 .update();
|
paulo@89
|
6150
|
paulo@89
|
6151 // DEPRECATED set state.disableddisabled
|
paulo@89
|
6152 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
6153
|
paulo@89
|
6154 if (!defaultState) {
|
paulo@89
|
6155 var key;
|
paulo@89
|
6156 defaultState = {};
|
paulo@89
|
6157 for (key in state) {
|
paulo@89
|
6158 if (state[key] instanceof Array)
|
paulo@89
|
6159 defaultState[key] = state[key].slice(0);
|
paulo@89
|
6160 else
|
paulo@89
|
6161 defaultState[key] = state[key];
|
paulo@89
|
6162 }
|
paulo@89
|
6163 }
|
paulo@89
|
6164
|
paulo@89
|
6165 // Display noData message if there's nothing to show.
|
paulo@89
|
6166 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
6167 nv.utils.noData(chart, container)
|
paulo@89
|
6168 return chart;
|
paulo@89
|
6169 } else {
|
paulo@89
|
6170 container.selectAll('.nv-noData').remove();
|
paulo@89
|
6171 }
|
paulo@89
|
6172
|
paulo@89
|
6173
|
paulo@89
|
6174 // Setup Scales
|
paulo@89
|
6175 x = lines.xScale();
|
paulo@89
|
6176 y = lines.yScale();
|
paulo@89
|
6177
|
paulo@89
|
6178 // Setup containers and skeleton of chart
|
paulo@89
|
6179 var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
|
paulo@89
|
6180 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
|
paulo@89
|
6181 var g = wrap.select('g');
|
paulo@89
|
6182
|
paulo@89
|
6183 gEnter.append("rect").style("opacity",0);
|
paulo@89
|
6184 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
6185 gEnter.append('g').attr('class', 'nv-y nv-axis');
|
paulo@89
|
6186 gEnter.append('g').attr('class', 'nv-linesWrap');
|
paulo@89
|
6187 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
6188 gEnter.append('g').attr('class', 'nv-interactive');
|
paulo@89
|
6189
|
paulo@89
|
6190 g.select("rect")
|
paulo@89
|
6191 .attr("width",availableWidth)
|
paulo@89
|
6192 .attr("height",(availableHeight > 0) ? availableHeight : 0);
|
paulo@89
|
6193
|
paulo@89
|
6194 // Legend
|
paulo@89
|
6195 if (showLegend) {
|
paulo@89
|
6196 legend.width(availableWidth);
|
paulo@89
|
6197
|
paulo@89
|
6198 g.select('.nv-legendWrap')
|
paulo@89
|
6199 .datum(data)
|
paulo@89
|
6200 .call(legend);
|
paulo@89
|
6201
|
paulo@89
|
6202 if ( margin.top != legend.height()) {
|
paulo@89
|
6203 margin.top = legend.height();
|
paulo@89
|
6204 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
6205 }
|
paulo@89
|
6206
|
paulo@89
|
6207 wrap.select('.nv-legendWrap')
|
paulo@89
|
6208 .attr('transform', 'translate(0,' + (-margin.top) +')')
|
paulo@89
|
6209 }
|
paulo@89
|
6210
|
paulo@89
|
6211 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
6212
|
paulo@89
|
6213 if (rightAlignYAxis) {
|
paulo@89
|
6214 g.select(".nv-y.nv-axis")
|
paulo@89
|
6215 .attr("transform", "translate(" + availableWidth + ",0)");
|
paulo@89
|
6216 }
|
paulo@89
|
6217
|
paulo@89
|
6218 //Set up interactive layer
|
paulo@89
|
6219 if (useInteractiveGuideline) {
|
paulo@89
|
6220 interactiveLayer
|
paulo@89
|
6221 .width(availableWidth)
|
paulo@89
|
6222 .height(availableHeight)
|
paulo@89
|
6223 .margin({left:margin.left, top:margin.top})
|
paulo@89
|
6224 .svgContainer(container)
|
paulo@89
|
6225 .xScale(x);
|
paulo@89
|
6226 wrap.select(".nv-interactive").call(interactiveLayer);
|
paulo@89
|
6227 }
|
paulo@89
|
6228
|
paulo@89
|
6229 lines
|
paulo@89
|
6230 .width(availableWidth)
|
paulo@89
|
6231 .height(availableHeight)
|
paulo@89
|
6232 .color(data.map(function(d,i) {
|
paulo@89
|
6233 return d.color || color(d, i);
|
paulo@89
|
6234 }).filter(function(d,i) { return !data[i].disabled }));
|
paulo@89
|
6235
|
paulo@89
|
6236
|
paulo@89
|
6237 var linesWrap = g.select('.nv-linesWrap')
|
paulo@89
|
6238 .datum(data.filter(function(d) { return !d.disabled }));
|
paulo@89
|
6239
|
paulo@89
|
6240 linesWrap.call(lines);
|
paulo@89
|
6241
|
paulo@89
|
6242 // Setup Axes
|
paulo@89
|
6243 if (showXAxis) {
|
paulo@89
|
6244 xAxis
|
paulo@89
|
6245 .scale(x)
|
paulo@89
|
6246 ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
6247 .tickSize(-availableHeight, 0);
|
paulo@89
|
6248
|
paulo@89
|
6249 g.select('.nv-x.nv-axis')
|
paulo@89
|
6250 .attr('transform', 'translate(0,' + y.range()[0] + ')');
|
paulo@89
|
6251 g.select('.nv-x.nv-axis')
|
paulo@89
|
6252 .call(xAxis);
|
paulo@89
|
6253 }
|
paulo@89
|
6254
|
paulo@89
|
6255 if (showYAxis) {
|
paulo@89
|
6256 yAxis
|
paulo@89
|
6257 .scale(y)
|
paulo@89
|
6258 ._ticks(nv.utils.calcTicksY(availableHeight/36, data) )
|
paulo@89
|
6259 .tickSize( -availableWidth, 0);
|
paulo@89
|
6260
|
paulo@89
|
6261 g.select('.nv-y.nv-axis')
|
paulo@89
|
6262 .call(yAxis);
|
paulo@89
|
6263 }
|
paulo@89
|
6264
|
paulo@89
|
6265 //============================================================
|
paulo@89
|
6266 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
6267 //------------------------------------------------------------
|
paulo@89
|
6268
|
paulo@89
|
6269 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
6270 for (var key in newState)
|
paulo@89
|
6271 state[key] = newState[key];
|
paulo@89
|
6272 dispatch.stateChange(state);
|
paulo@89
|
6273 chart.update();
|
paulo@89
|
6274 });
|
paulo@89
|
6275
|
paulo@89
|
6276 interactiveLayer.dispatch.on('elementMousemove', function(e) {
|
paulo@89
|
6277 lines.clearHighlights();
|
paulo@89
|
6278 var singlePoint, pointIndex, pointXLocation, allData = [];
|
paulo@89
|
6279 data
|
paulo@89
|
6280 .filter(function(series, i) {
|
paulo@89
|
6281 series.seriesIndex = i;
|
paulo@89
|
6282 return !series.disabled;
|
paulo@89
|
6283 })
|
paulo@89
|
6284 .forEach(function(series,i) {
|
paulo@89
|
6285 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
|
paulo@89
|
6286 var point = series.values[pointIndex];
|
paulo@89
|
6287 var pointYValue = chart.y()(point, pointIndex);
|
paulo@89
|
6288 if (pointYValue != null) {
|
paulo@89
|
6289 lines.highlightPoint(i, pointIndex, true);
|
paulo@89
|
6290 }
|
paulo@89
|
6291 if (point === undefined) return;
|
paulo@89
|
6292 if (singlePoint === undefined) singlePoint = point;
|
paulo@89
|
6293 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
|
paulo@89
|
6294 allData.push({
|
paulo@89
|
6295 key: series.key,
|
paulo@89
|
6296 value: pointYValue,
|
paulo@89
|
6297 color: color(series,series.seriesIndex)
|
paulo@89
|
6298 });
|
paulo@89
|
6299 });
|
paulo@89
|
6300 //Highlight the tooltip entry based on which point the mouse is closest to.
|
paulo@89
|
6301 if (allData.length > 2) {
|
paulo@89
|
6302 var yValue = chart.yScale().invert(e.mouseY);
|
paulo@89
|
6303 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
|
paulo@89
|
6304 var threshold = 0.03 * domainExtent;
|
paulo@89
|
6305 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
|
paulo@89
|
6306 if (indexToHighlight !== null)
|
paulo@89
|
6307 allData[indexToHighlight].highlight = true;
|
paulo@89
|
6308 }
|
paulo@89
|
6309
|
paulo@89
|
6310 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
|
paulo@89
|
6311 interactiveLayer.tooltip
|
paulo@89
|
6312 .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top})
|
paulo@89
|
6313 .chartContainer(that.parentNode)
|
paulo@89
|
6314 .valueFormatter(function(d,i) {
|
paulo@89
|
6315 return d == null ? "N/A" : yAxis.tickFormat()(d);
|
paulo@89
|
6316 })
|
paulo@89
|
6317 .data({
|
paulo@89
|
6318 value: xValue,
|
paulo@89
|
6319 index: pointIndex,
|
paulo@89
|
6320 series: allData
|
paulo@89
|
6321 })();
|
paulo@89
|
6322
|
paulo@89
|
6323 interactiveLayer.renderGuideLine(pointXLocation);
|
paulo@89
|
6324
|
paulo@89
|
6325 });
|
paulo@89
|
6326
|
paulo@89
|
6327 interactiveLayer.dispatch.on('elementClick', function(e) {
|
paulo@89
|
6328 var pointXLocation, allData = [];
|
paulo@89
|
6329
|
paulo@89
|
6330 data.filter(function(series, i) {
|
paulo@89
|
6331 series.seriesIndex = i;
|
paulo@89
|
6332 return !series.disabled;
|
paulo@89
|
6333 }).forEach(function(series) {
|
paulo@89
|
6334 var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
|
paulo@89
|
6335 var point = series.values[pointIndex];
|
paulo@89
|
6336 if (typeof point === 'undefined') return;
|
paulo@89
|
6337 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
|
paulo@89
|
6338 var yPos = chart.yScale()(chart.y()(point,pointIndex));
|
paulo@89
|
6339 allData.push({
|
paulo@89
|
6340 point: point,
|
paulo@89
|
6341 pointIndex: pointIndex,
|
paulo@89
|
6342 pos: [pointXLocation, yPos],
|
paulo@89
|
6343 seriesIndex: series.seriesIndex,
|
paulo@89
|
6344 series: series
|
paulo@89
|
6345 });
|
paulo@89
|
6346 });
|
paulo@89
|
6347
|
paulo@89
|
6348 lines.dispatch.elementClick(allData);
|
paulo@89
|
6349 });
|
paulo@89
|
6350
|
paulo@89
|
6351 interactiveLayer.dispatch.on("elementMouseout",function(e) {
|
paulo@89
|
6352 lines.clearHighlights();
|
paulo@89
|
6353 });
|
paulo@89
|
6354
|
paulo@89
|
6355 dispatch.on('changeState', function(e) {
|
paulo@89
|
6356 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
|
paulo@89
|
6357 data.forEach(function(series,i) {
|
paulo@89
|
6358 series.disabled = e.disabled[i];
|
paulo@89
|
6359 });
|
paulo@89
|
6360
|
paulo@89
|
6361 state.disabled = e.disabled;
|
paulo@89
|
6362 }
|
paulo@89
|
6363
|
paulo@89
|
6364 chart.update();
|
paulo@89
|
6365 });
|
paulo@89
|
6366
|
paulo@89
|
6367 });
|
paulo@89
|
6368
|
paulo@89
|
6369 renderWatch.renderEnd('lineChart immediate');
|
paulo@89
|
6370 return chart;
|
paulo@89
|
6371 }
|
paulo@89
|
6372
|
paulo@89
|
6373 //============================================================
|
paulo@89
|
6374 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
6375 //------------------------------------------------------------
|
paulo@89
|
6376
|
paulo@89
|
6377 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
6378 tooltip.data(evt).position(evt.pos).hidden(false);
|
paulo@89
|
6379 });
|
paulo@89
|
6380
|
paulo@89
|
6381 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
6382 tooltip.hidden(true)
|
paulo@89
|
6383 });
|
paulo@89
|
6384
|
paulo@89
|
6385 //============================================================
|
paulo@89
|
6386 // Expose Public Variables
|
paulo@89
|
6387 //------------------------------------------------------------
|
paulo@89
|
6388
|
paulo@89
|
6389 // expose chart's sub-components
|
paulo@89
|
6390 chart.dispatch = dispatch;
|
paulo@89
|
6391 chart.lines = lines;
|
paulo@89
|
6392 chart.legend = legend;
|
paulo@89
|
6393 chart.xAxis = xAxis;
|
paulo@89
|
6394 chart.yAxis = yAxis;
|
paulo@89
|
6395 chart.interactiveLayer = interactiveLayer;
|
paulo@89
|
6396 chart.tooltip = tooltip;
|
paulo@89
|
6397
|
paulo@89
|
6398 chart.dispatch = dispatch;
|
paulo@89
|
6399 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
6400
|
paulo@89
|
6401 chart._options = Object.create({}, {
|
paulo@89
|
6402 // simple options, just get/set the necessary values
|
paulo@89
|
6403 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
6404 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
6405 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
6406 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
6407 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
6408 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
6409 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
6410
|
paulo@89
|
6411 // deprecated options
|
paulo@89
|
6412 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
6413 // deprecated after 1.7.1
|
paulo@89
|
6414 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
6415 tooltip.enabled(!!_);
|
paulo@89
|
6416 }},
|
paulo@89
|
6417 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
6418 // deprecated after 1.7.1
|
paulo@89
|
6419 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
6420 tooltip.contentGenerator(_);
|
paulo@89
|
6421 }},
|
paulo@89
|
6422
|
paulo@89
|
6423 // options that require extra logic in the setter
|
paulo@89
|
6424 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
6425 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
6426 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
6427 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
6428 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
6429 }},
|
paulo@89
|
6430 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
6431 duration = _;
|
paulo@89
|
6432 renderWatch.reset(duration);
|
paulo@89
|
6433 lines.duration(duration);
|
paulo@89
|
6434 xAxis.duration(duration);
|
paulo@89
|
6435 yAxis.duration(duration);
|
paulo@89
|
6436 }},
|
paulo@89
|
6437 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
6438 color = nv.utils.getColor(_);
|
paulo@89
|
6439 legend.color(color);
|
paulo@89
|
6440 lines.color(color);
|
paulo@89
|
6441 }},
|
paulo@89
|
6442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
|
paulo@89
|
6443 rightAlignYAxis = _;
|
paulo@89
|
6444 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
|
paulo@89
|
6445 }},
|
paulo@89
|
6446 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
|
paulo@89
|
6447 useInteractiveGuideline = _;
|
paulo@89
|
6448 if (useInteractiveGuideline) {
|
paulo@89
|
6449 lines.interactive(false);
|
paulo@89
|
6450 lines.useVoronoi(false);
|
paulo@89
|
6451 }
|
paulo@89
|
6452 }}
|
paulo@89
|
6453 });
|
paulo@89
|
6454
|
paulo@89
|
6455 nv.utils.inheritOptions(chart, lines);
|
paulo@89
|
6456 nv.utils.initOptions(chart);
|
paulo@89
|
6457
|
paulo@89
|
6458 return chart;
|
paulo@89
|
6459 };
|
paulo@89
|
6460 nv.models.linePlusBarChart = function() {
|
paulo@89
|
6461 "use strict";
|
paulo@89
|
6462
|
paulo@89
|
6463 //============================================================
|
paulo@89
|
6464 // Public Variables with Default Settings
|
paulo@89
|
6465 //------------------------------------------------------------
|
paulo@89
|
6466
|
paulo@89
|
6467 var lines = nv.models.line()
|
paulo@89
|
6468 , lines2 = nv.models.line()
|
paulo@89
|
6469 , bars = nv.models.historicalBar()
|
paulo@89
|
6470 , bars2 = nv.models.historicalBar()
|
paulo@89
|
6471 , xAxis = nv.models.axis()
|
paulo@89
|
6472 , x2Axis = nv.models.axis()
|
paulo@89
|
6473 , y1Axis = nv.models.axis()
|
paulo@89
|
6474 , y2Axis = nv.models.axis()
|
paulo@89
|
6475 , y3Axis = nv.models.axis()
|
paulo@89
|
6476 , y4Axis = nv.models.axis()
|
paulo@89
|
6477 , legend = nv.models.legend()
|
paulo@89
|
6478 , brush = d3.svg.brush()
|
paulo@89
|
6479 , tooltip = nv.models.tooltip()
|
paulo@89
|
6480 ;
|
paulo@89
|
6481
|
paulo@89
|
6482 var margin = {top: 30, right: 30, bottom: 30, left: 60}
|
paulo@89
|
6483 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
|
paulo@89
|
6484 , width = null
|
paulo@89
|
6485 , height = null
|
paulo@89
|
6486 , getX = function(d) { return d.x }
|
paulo@89
|
6487 , getY = function(d) { return d.y }
|
paulo@89
|
6488 , color = nv.utils.defaultColor()
|
paulo@89
|
6489 , showLegend = true
|
paulo@89
|
6490 , focusEnable = true
|
paulo@89
|
6491 , focusShowAxisY = false
|
paulo@89
|
6492 , focusShowAxisX = true
|
paulo@89
|
6493 , focusHeight = 50
|
paulo@89
|
6494 , extent
|
paulo@89
|
6495 , brushExtent = null
|
paulo@89
|
6496 , x
|
paulo@89
|
6497 , x2
|
paulo@89
|
6498 , y1
|
paulo@89
|
6499 , y2
|
paulo@89
|
6500 , y3
|
paulo@89
|
6501 , y4
|
paulo@89
|
6502 , noData = null
|
paulo@89
|
6503 , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
|
paulo@89
|
6504 , transitionDuration = 0
|
paulo@89
|
6505 , state = nv.utils.state()
|
paulo@89
|
6506 , defaultState = null
|
paulo@89
|
6507 , legendLeftAxisHint = ' (left axis)'
|
paulo@89
|
6508 , legendRightAxisHint = ' (right axis)'
|
paulo@89
|
6509 ;
|
paulo@89
|
6510
|
paulo@89
|
6511 lines.clipEdge(true);
|
paulo@89
|
6512 lines2.interactive(false);
|
paulo@89
|
6513 xAxis.orient('bottom').tickPadding(5);
|
paulo@89
|
6514 y1Axis.orient('left');
|
paulo@89
|
6515 y2Axis.orient('right');
|
paulo@89
|
6516 x2Axis.orient('bottom').tickPadding(5);
|
paulo@89
|
6517 y3Axis.orient('left');
|
paulo@89
|
6518 y4Axis.orient('right');
|
paulo@89
|
6519
|
paulo@89
|
6520 tooltip.headerEnabled(true).headerFormatter(function(d, i) {
|
paulo@89
|
6521 return xAxis.tickFormat()(d, i);
|
paulo@89
|
6522 });
|
paulo@89
|
6523
|
paulo@89
|
6524 //============================================================
|
paulo@89
|
6525 // Private Variables
|
paulo@89
|
6526 //------------------------------------------------------------
|
paulo@89
|
6527
|
paulo@89
|
6528 var stateGetter = function(data) {
|
paulo@89
|
6529 return function(){
|
paulo@89
|
6530 return {
|
paulo@89
|
6531 active: data.map(function(d) { return !d.disabled })
|
paulo@89
|
6532 };
|
paulo@89
|
6533 }
|
paulo@89
|
6534 };
|
paulo@89
|
6535
|
paulo@89
|
6536 var stateSetter = function(data) {
|
paulo@89
|
6537 return function(state) {
|
paulo@89
|
6538 if (state.active !== undefined)
|
paulo@89
|
6539 data.forEach(function(series,i) {
|
paulo@89
|
6540 series.disabled = !state.active[i];
|
paulo@89
|
6541 });
|
paulo@89
|
6542 }
|
paulo@89
|
6543 };
|
paulo@89
|
6544
|
paulo@89
|
6545 function chart(selection) {
|
paulo@89
|
6546 selection.each(function(data) {
|
paulo@89
|
6547 var container = d3.select(this),
|
paulo@89
|
6548 that = this;
|
paulo@89
|
6549 nv.utils.initSVG(container);
|
paulo@89
|
6550 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
6551 availableHeight1 = nv.utils.availableHeight(height, container, margin)
|
paulo@89
|
6552 - (focusEnable ? focusHeight : 0),
|
paulo@89
|
6553 availableHeight2 = focusHeight - margin2.top - margin2.bottom;
|
paulo@89
|
6554
|
paulo@89
|
6555 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
|
paulo@89
|
6556 chart.container = this;
|
paulo@89
|
6557
|
paulo@89
|
6558 state
|
paulo@89
|
6559 .setter(stateSetter(data), chart.update)
|
paulo@89
|
6560 .getter(stateGetter(data))
|
paulo@89
|
6561 .update();
|
paulo@89
|
6562
|
paulo@89
|
6563 // DEPRECATED set state.disableddisabled
|
paulo@89
|
6564 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
6565
|
paulo@89
|
6566 if (!defaultState) {
|
paulo@89
|
6567 var key;
|
paulo@89
|
6568 defaultState = {};
|
paulo@89
|
6569 for (key in state) {
|
paulo@89
|
6570 if (state[key] instanceof Array)
|
paulo@89
|
6571 defaultState[key] = state[key].slice(0);
|
paulo@89
|
6572 else
|
paulo@89
|
6573 defaultState[key] = state[key];
|
paulo@89
|
6574 }
|
paulo@89
|
6575 }
|
paulo@89
|
6576
|
paulo@89
|
6577 // Display No Data message if there's nothing to show.
|
paulo@89
|
6578 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
6579 nv.utils.noData(chart, container)
|
paulo@89
|
6580 return chart;
|
paulo@89
|
6581 } else {
|
paulo@89
|
6582 container.selectAll('.nv-noData').remove();
|
paulo@89
|
6583 }
|
paulo@89
|
6584
|
paulo@89
|
6585 // Setup Scales
|
paulo@89
|
6586 var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
|
paulo@89
|
6587 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
|
paulo@89
|
6588
|
paulo@89
|
6589 x = bars.xScale();
|
paulo@89
|
6590 x2 = x2Axis.scale();
|
paulo@89
|
6591 y1 = bars.yScale();
|
paulo@89
|
6592 y2 = lines.yScale();
|
paulo@89
|
6593 y3 = bars2.yScale();
|
paulo@89
|
6594 y4 = lines2.yScale();
|
paulo@89
|
6595
|
paulo@89
|
6596 var series1 = data
|
paulo@89
|
6597 .filter(function(d) { return !d.disabled && d.bar })
|
paulo@89
|
6598 .map(function(d) {
|
paulo@89
|
6599 return d.values.map(function(d,i) {
|
paulo@89
|
6600 return { x: getX(d,i), y: getY(d,i) }
|
paulo@89
|
6601 })
|
paulo@89
|
6602 });
|
paulo@89
|
6603
|
paulo@89
|
6604 var series2 = data
|
paulo@89
|
6605 .filter(function(d) { return !d.disabled && !d.bar })
|
paulo@89
|
6606 .map(function(d) {
|
paulo@89
|
6607 return d.values.map(function(d,i) {
|
paulo@89
|
6608 return { x: getX(d,i), y: getY(d,i) }
|
paulo@89
|
6609 })
|
paulo@89
|
6610 });
|
paulo@89
|
6611
|
paulo@89
|
6612 x.range([0, availableWidth]);
|
paulo@89
|
6613
|
paulo@89
|
6614 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
|
paulo@89
|
6615 .range([0, availableWidth]);
|
paulo@89
|
6616
|
paulo@89
|
6617 // Setup containers and skeleton of chart
|
paulo@89
|
6618 var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
|
paulo@89
|
6619 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
|
paulo@89
|
6620 var g = wrap.select('g');
|
paulo@89
|
6621
|
paulo@89
|
6622 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
6623
|
paulo@89
|
6624 // this is the main chart
|
paulo@89
|
6625 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
|
paulo@89
|
6626 focusEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
6627 focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
|
paulo@89
|
6628 focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
|
paulo@89
|
6629 focusEnter.append('g').attr('class', 'nv-barsWrap');
|
paulo@89
|
6630 focusEnter.append('g').attr('class', 'nv-linesWrap');
|
paulo@89
|
6631
|
paulo@89
|
6632 // context chart is where you can focus in
|
paulo@89
|
6633 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
|
paulo@89
|
6634 contextEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
6635 contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
|
paulo@89
|
6636 contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
|
paulo@89
|
6637 contextEnter.append('g').attr('class', 'nv-barsWrap');
|
paulo@89
|
6638 contextEnter.append('g').attr('class', 'nv-linesWrap');
|
paulo@89
|
6639 contextEnter.append('g').attr('class', 'nv-brushBackground');
|
paulo@89
|
6640 contextEnter.append('g').attr('class', 'nv-x nv-brush');
|
paulo@89
|
6641
|
paulo@89
|
6642 //============================================================
|
paulo@89
|
6643 // Legend
|
paulo@89
|
6644 //------------------------------------------------------------
|
paulo@89
|
6645
|
paulo@89
|
6646 if (showLegend) {
|
paulo@89
|
6647 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
|
paulo@89
|
6648 var legendXPosition = legend.align() ? legendWidth : 0;
|
paulo@89
|
6649
|
paulo@89
|
6650 legend.width(legendWidth);
|
paulo@89
|
6651
|
paulo@89
|
6652 g.select('.nv-legendWrap')
|
paulo@89
|
6653 .datum(data.map(function(series) {
|
paulo@89
|
6654 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
|
paulo@89
|
6655 series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint);
|
paulo@89
|
6656 return series;
|
paulo@89
|
6657 }))
|
paulo@89
|
6658 .call(legend);
|
paulo@89
|
6659
|
paulo@89
|
6660 if ( margin.top != legend.height()) {
|
paulo@89
|
6661 margin.top = legend.height();
|
paulo@89
|
6662 // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"?
|
paulo@89
|
6663 availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight;
|
paulo@89
|
6664 }
|
paulo@89
|
6665
|
paulo@89
|
6666 g.select('.nv-legendWrap')
|
paulo@89
|
6667 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
|
paulo@89
|
6668 }
|
paulo@89
|
6669
|
paulo@89
|
6670 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
6671
|
paulo@89
|
6672 //============================================================
|
paulo@89
|
6673 // Context chart (focus chart) components
|
paulo@89
|
6674 //------------------------------------------------------------
|
paulo@89
|
6675
|
paulo@89
|
6676 // hide or show the focus context chart
|
paulo@89
|
6677 g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
|
paulo@89
|
6678
|
paulo@89
|
6679 bars2
|
paulo@89
|
6680 .width(availableWidth)
|
paulo@89
|
6681 .height(availableHeight2)
|
paulo@89
|
6682 .color(data.map(function (d, i) {
|
paulo@89
|
6683 return d.color || color(d, i);
|
paulo@89
|
6684 }).filter(function (d, i) {
|
paulo@89
|
6685 return !data[i].disabled && data[i].bar
|
paulo@89
|
6686 }));
|
paulo@89
|
6687 lines2
|
paulo@89
|
6688 .width(availableWidth)
|
paulo@89
|
6689 .height(availableHeight2)
|
paulo@89
|
6690 .color(data.map(function (d, i) {
|
paulo@89
|
6691 return d.color || color(d, i);
|
paulo@89
|
6692 }).filter(function (d, i) {
|
paulo@89
|
6693 return !data[i].disabled && !data[i].bar
|
paulo@89
|
6694 }));
|
paulo@89
|
6695
|
paulo@89
|
6696 var bars2Wrap = g.select('.nv-context .nv-barsWrap')
|
paulo@89
|
6697 .datum(dataBars.length ? dataBars : [
|
paulo@89
|
6698 {values: []}
|
paulo@89
|
6699 ]);
|
paulo@89
|
6700 var lines2Wrap = g.select('.nv-context .nv-linesWrap')
|
paulo@89
|
6701 .datum(!dataLines[0].disabled ? dataLines : [
|
paulo@89
|
6702 {values: []}
|
paulo@89
|
6703 ]);
|
paulo@89
|
6704
|
paulo@89
|
6705 g.select('.nv-context')
|
paulo@89
|
6706 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
|
paulo@89
|
6707
|
paulo@89
|
6708 bars2Wrap.transition().call(bars2);
|
paulo@89
|
6709 lines2Wrap.transition().call(lines2);
|
paulo@89
|
6710
|
paulo@89
|
6711 // context (focus chart) axis controls
|
paulo@89
|
6712 if (focusShowAxisX) {
|
paulo@89
|
6713 x2Axis
|
paulo@89
|
6714 ._ticks( nv.utils.calcTicksX(availableWidth / 100, data))
|
paulo@89
|
6715 .tickSize(-availableHeight2, 0);
|
paulo@89
|
6716 g.select('.nv-context .nv-x.nv-axis')
|
paulo@89
|
6717 .attr('transform', 'translate(0,' + y3.range()[0] + ')');
|
paulo@89
|
6718 g.select('.nv-context .nv-x.nv-axis').transition()
|
paulo@89
|
6719 .call(x2Axis);
|
paulo@89
|
6720 }
|
paulo@89
|
6721
|
paulo@89
|
6722 if (focusShowAxisY) {
|
paulo@89
|
6723 y3Axis
|
paulo@89
|
6724 .scale(y3)
|
paulo@89
|
6725 ._ticks( availableHeight2 / 36 )
|
paulo@89
|
6726 .tickSize( -availableWidth, 0);
|
paulo@89
|
6727 y4Axis
|
paulo@89
|
6728 .scale(y4)
|
paulo@89
|
6729 ._ticks( availableHeight2 / 36 )
|
paulo@89
|
6730 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
|
paulo@89
|
6731
|
paulo@89
|
6732 g.select('.nv-context .nv-y3.nv-axis')
|
paulo@89
|
6733 .style('opacity', dataBars.length ? 1 : 0)
|
paulo@89
|
6734 .attr('transform', 'translate(0,' + x2.range()[0] + ')');
|
paulo@89
|
6735 g.select('.nv-context .nv-y2.nv-axis')
|
paulo@89
|
6736 .style('opacity', dataLines.length ? 1 : 0)
|
paulo@89
|
6737 .attr('transform', 'translate(' + x2.range()[1] + ',0)');
|
paulo@89
|
6738
|
paulo@89
|
6739 g.select('.nv-context .nv-y1.nv-axis').transition()
|
paulo@89
|
6740 .call(y3Axis);
|
paulo@89
|
6741 g.select('.nv-context .nv-y2.nv-axis').transition()
|
paulo@89
|
6742 .call(y4Axis);
|
paulo@89
|
6743 }
|
paulo@89
|
6744
|
paulo@89
|
6745 // Setup Brush
|
paulo@89
|
6746 brush.x(x2).on('brush', onBrush);
|
paulo@89
|
6747
|
paulo@89
|
6748 if (brushExtent) brush.extent(brushExtent);
|
paulo@89
|
6749
|
paulo@89
|
6750 var brushBG = g.select('.nv-brushBackground').selectAll('g')
|
paulo@89
|
6751 .data([brushExtent || brush.extent()]);
|
paulo@89
|
6752
|
paulo@89
|
6753 var brushBGenter = brushBG.enter()
|
paulo@89
|
6754 .append('g');
|
paulo@89
|
6755
|
paulo@89
|
6756 brushBGenter.append('rect')
|
paulo@89
|
6757 .attr('class', 'left')
|
paulo@89
|
6758 .attr('x', 0)
|
paulo@89
|
6759 .attr('y', 0)
|
paulo@89
|
6760 .attr('height', availableHeight2);
|
paulo@89
|
6761
|
paulo@89
|
6762 brushBGenter.append('rect')
|
paulo@89
|
6763 .attr('class', 'right')
|
paulo@89
|
6764 .attr('x', 0)
|
paulo@89
|
6765 .attr('y', 0)
|
paulo@89
|
6766 .attr('height', availableHeight2);
|
paulo@89
|
6767
|
paulo@89
|
6768 var gBrush = g.select('.nv-x.nv-brush')
|
paulo@89
|
6769 .call(brush);
|
paulo@89
|
6770 gBrush.selectAll('rect')
|
paulo@89
|
6771 //.attr('y', -5)
|
paulo@89
|
6772 .attr('height', availableHeight2);
|
paulo@89
|
6773 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
|
paulo@89
|
6774
|
paulo@89
|
6775 //============================================================
|
paulo@89
|
6776 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
6777 //------------------------------------------------------------
|
paulo@89
|
6778
|
paulo@89
|
6779 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
6780 for (var key in newState)
|
paulo@89
|
6781 state[key] = newState[key];
|
paulo@89
|
6782 dispatch.stateChange(state);
|
paulo@89
|
6783 chart.update();
|
paulo@89
|
6784 });
|
paulo@89
|
6785
|
paulo@89
|
6786 // Update chart from a state object passed to event handler
|
paulo@89
|
6787 dispatch.on('changeState', function(e) {
|
paulo@89
|
6788 if (typeof e.disabled !== 'undefined') {
|
paulo@89
|
6789 data.forEach(function(series,i) {
|
paulo@89
|
6790 series.disabled = e.disabled[i];
|
paulo@89
|
6791 });
|
paulo@89
|
6792 state.disabled = e.disabled;
|
paulo@89
|
6793 }
|
paulo@89
|
6794 chart.update();
|
paulo@89
|
6795 });
|
paulo@89
|
6796
|
paulo@89
|
6797 //============================================================
|
paulo@89
|
6798 // Functions
|
paulo@89
|
6799 //------------------------------------------------------------
|
paulo@89
|
6800
|
paulo@89
|
6801 // Taken from crossfilter (http://square.github.com/crossfilter/)
|
paulo@89
|
6802 function resizePath(d) {
|
paulo@89
|
6803 var e = +(d == 'e'),
|
paulo@89
|
6804 x = e ? 1 : -1,
|
paulo@89
|
6805 y = availableHeight2 / 3;
|
paulo@89
|
6806 return 'M' + (.5 * x) + ',' + y
|
paulo@89
|
6807 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
|
paulo@89
|
6808 + 'V' + (2 * y - 6)
|
paulo@89
|
6809 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
|
paulo@89
|
6810 + 'Z'
|
paulo@89
|
6811 + 'M' + (2.5 * x) + ',' + (y + 8)
|
paulo@89
|
6812 + 'V' + (2 * y - 8)
|
paulo@89
|
6813 + 'M' + (4.5 * x) + ',' + (y + 8)
|
paulo@89
|
6814 + 'V' + (2 * y - 8);
|
paulo@89
|
6815 }
|
paulo@89
|
6816
|
paulo@89
|
6817
|
paulo@89
|
6818 function updateBrushBG() {
|
paulo@89
|
6819 if (!brush.empty()) brush.extent(brushExtent);
|
paulo@89
|
6820 brushBG
|
paulo@89
|
6821 .data([brush.empty() ? x2.domain() : brushExtent])
|
paulo@89
|
6822 .each(function(d,i) {
|
paulo@89
|
6823 var leftWidth = x2(d[0]) - x2.range()[0],
|
paulo@89
|
6824 rightWidth = x2.range()[1] - x2(d[1]);
|
paulo@89
|
6825 d3.select(this).select('.left')
|
paulo@89
|
6826 .attr('width', leftWidth < 0 ? 0 : leftWidth);
|
paulo@89
|
6827
|
paulo@89
|
6828 d3.select(this).select('.right')
|
paulo@89
|
6829 .attr('x', x2(d[1]))
|
paulo@89
|
6830 .attr('width', rightWidth < 0 ? 0 : rightWidth);
|
paulo@89
|
6831 });
|
paulo@89
|
6832 }
|
paulo@89
|
6833
|
paulo@89
|
6834 function onBrush() {
|
paulo@89
|
6835 brushExtent = brush.empty() ? null : brush.extent();
|
paulo@89
|
6836 extent = brush.empty() ? x2.domain() : brush.extent();
|
paulo@89
|
6837 dispatch.brush({extent: extent, brush: brush});
|
paulo@89
|
6838 updateBrushBG();
|
paulo@89
|
6839
|
paulo@89
|
6840 // Prepare Main (Focus) Bars and Lines
|
paulo@89
|
6841 bars
|
paulo@89
|
6842 .width(availableWidth)
|
paulo@89
|
6843 .height(availableHeight1)
|
paulo@89
|
6844 .color(data.map(function(d,i) {
|
paulo@89
|
6845 return d.color || color(d, i);
|
paulo@89
|
6846 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
|
paulo@89
|
6847
|
paulo@89
|
6848 lines
|
paulo@89
|
6849 .width(availableWidth)
|
paulo@89
|
6850 .height(availableHeight1)
|
paulo@89
|
6851 .color(data.map(function(d,i) {
|
paulo@89
|
6852 return d.color || color(d, i);
|
paulo@89
|
6853 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
|
paulo@89
|
6854
|
paulo@89
|
6855 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
|
paulo@89
|
6856 .datum(!dataBars.length ? [{values:[]}] :
|
paulo@89
|
6857 dataBars
|
paulo@89
|
6858 .map(function(d,i) {
|
paulo@89
|
6859 return {
|
paulo@89
|
6860 key: d.key,
|
paulo@89
|
6861 values: d.values.filter(function(d,i) {
|
paulo@89
|
6862 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
|
paulo@89
|
6863 })
|
paulo@89
|
6864 }
|
paulo@89
|
6865 })
|
paulo@89
|
6866 );
|
paulo@89
|
6867
|
paulo@89
|
6868 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
|
paulo@89
|
6869 .datum(dataLines[0].disabled ? [{values:[]}] :
|
paulo@89
|
6870 dataLines
|
paulo@89
|
6871 .map(function(d,i) {
|
paulo@89
|
6872 return {
|
paulo@89
|
6873 area: d.area,
|
paulo@89
|
6874 fillOpacity: d.fillOpacity,
|
paulo@89
|
6875 key: d.key,
|
paulo@89
|
6876 values: d.values.filter(function(d,i) {
|
paulo@89
|
6877 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
|
paulo@89
|
6878 })
|
paulo@89
|
6879 }
|
paulo@89
|
6880 })
|
paulo@89
|
6881 );
|
paulo@89
|
6882
|
paulo@89
|
6883 // Update Main (Focus) X Axis
|
paulo@89
|
6884 if (dataBars.length) {
|
paulo@89
|
6885 x = bars.xScale();
|
paulo@89
|
6886 } else {
|
paulo@89
|
6887 x = lines.xScale();
|
paulo@89
|
6888 }
|
paulo@89
|
6889
|
paulo@89
|
6890 xAxis
|
paulo@89
|
6891 .scale(x)
|
paulo@89
|
6892 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
6893 .tickSize(-availableHeight1, 0);
|
paulo@89
|
6894
|
paulo@89
|
6895 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
|
paulo@89
|
6896
|
paulo@89
|
6897 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
|
paulo@89
|
6898 .call(xAxis);
|
paulo@89
|
6899
|
paulo@89
|
6900 // Update Main (Focus) Bars and Lines
|
paulo@89
|
6901 focusBarsWrap.transition().duration(transitionDuration).call(bars);
|
paulo@89
|
6902 focusLinesWrap.transition().duration(transitionDuration).call(lines);
|
paulo@89
|
6903
|
paulo@89
|
6904 // Setup and Update Main (Focus) Y Axes
|
paulo@89
|
6905 g.select('.nv-focus .nv-x.nv-axis')
|
paulo@89
|
6906 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
|
paulo@89
|
6907
|
paulo@89
|
6908 y1Axis
|
paulo@89
|
6909 .scale(y1)
|
paulo@89
|
6910 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
|
paulo@89
|
6911 .tickSize(-availableWidth, 0);
|
paulo@89
|
6912 y2Axis
|
paulo@89
|
6913 .scale(y2)
|
paulo@89
|
6914 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
|
paulo@89
|
6915 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
|
paulo@89
|
6916
|
paulo@89
|
6917 g.select('.nv-focus .nv-y1.nv-axis')
|
paulo@89
|
6918 .style('opacity', dataBars.length ? 1 : 0);
|
paulo@89
|
6919 g.select('.nv-focus .nv-y2.nv-axis')
|
paulo@89
|
6920 .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0)
|
paulo@89
|
6921 .attr('transform', 'translate(' + x.range()[1] + ',0)');
|
paulo@89
|
6922
|
paulo@89
|
6923 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
|
paulo@89
|
6924 .call(y1Axis);
|
paulo@89
|
6925 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
|
paulo@89
|
6926 .call(y2Axis);
|
paulo@89
|
6927 }
|
paulo@89
|
6928
|
paulo@89
|
6929 onBrush();
|
paulo@89
|
6930
|
paulo@89
|
6931 });
|
paulo@89
|
6932
|
paulo@89
|
6933 return chart;
|
paulo@89
|
6934 }
|
paulo@89
|
6935
|
paulo@89
|
6936 //============================================================
|
paulo@89
|
6937 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
6938 //------------------------------------------------------------
|
paulo@89
|
6939
|
paulo@89
|
6940 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
6941 tooltip
|
paulo@89
|
6942 .duration(100)
|
paulo@89
|
6943 .valueFormatter(function(d, i) {
|
paulo@89
|
6944 return y2Axis.tickFormat()(d, i);
|
paulo@89
|
6945 })
|
paulo@89
|
6946 .data(evt)
|
paulo@89
|
6947 .position(evt.pos)
|
paulo@89
|
6948 .hidden(false);
|
paulo@89
|
6949 });
|
paulo@89
|
6950
|
paulo@89
|
6951 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
6952 tooltip.hidden(true)
|
paulo@89
|
6953 });
|
paulo@89
|
6954
|
paulo@89
|
6955 bars.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
6956 evt.value = chart.x()(evt.data);
|
paulo@89
|
6957 evt['series'] = {
|
paulo@89
|
6958 value: chart.y()(evt.data),
|
paulo@89
|
6959 color: evt.color
|
paulo@89
|
6960 };
|
paulo@89
|
6961 tooltip
|
paulo@89
|
6962 .duration(0)
|
paulo@89
|
6963 .valueFormatter(function(d, i) {
|
paulo@89
|
6964 return y1Axis.tickFormat()(d, i);
|
paulo@89
|
6965 })
|
paulo@89
|
6966 .data(evt)
|
paulo@89
|
6967 .hidden(false);
|
paulo@89
|
6968 });
|
paulo@89
|
6969
|
paulo@89
|
6970 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
6971 tooltip.hidden(true);
|
paulo@89
|
6972 });
|
paulo@89
|
6973
|
paulo@89
|
6974 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
6975 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
6976 });
|
paulo@89
|
6977
|
paulo@89
|
6978 //============================================================
|
paulo@89
|
6979
|
paulo@89
|
6980
|
paulo@89
|
6981 //============================================================
|
paulo@89
|
6982 // Expose Public Variables
|
paulo@89
|
6983 //------------------------------------------------------------
|
paulo@89
|
6984
|
paulo@89
|
6985 // expose chart's sub-components
|
paulo@89
|
6986 chart.dispatch = dispatch;
|
paulo@89
|
6987 chart.legend = legend;
|
paulo@89
|
6988 chart.lines = lines;
|
paulo@89
|
6989 chart.lines2 = lines2;
|
paulo@89
|
6990 chart.bars = bars;
|
paulo@89
|
6991 chart.bars2 = bars2;
|
paulo@89
|
6992 chart.xAxis = xAxis;
|
paulo@89
|
6993 chart.x2Axis = x2Axis;
|
paulo@89
|
6994 chart.y1Axis = y1Axis;
|
paulo@89
|
6995 chart.y2Axis = y2Axis;
|
paulo@89
|
6996 chart.y3Axis = y3Axis;
|
paulo@89
|
6997 chart.y4Axis = y4Axis;
|
paulo@89
|
6998 chart.tooltip = tooltip;
|
paulo@89
|
6999
|
paulo@89
|
7000 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
7001
|
paulo@89
|
7002 chart._options = Object.create({}, {
|
paulo@89
|
7003 // simple options, just get/set the necessary values
|
paulo@89
|
7004 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
7005 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
7006 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
7007 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
|
paulo@89
|
7008 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
7009 focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
|
paulo@89
|
7010 focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
|
paulo@89
|
7011 focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
|
paulo@89
|
7012 focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
|
paulo@89
|
7013 legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}},
|
paulo@89
|
7014 legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
|
paulo@89
|
7015
|
paulo@89
|
7016 // deprecated options
|
paulo@89
|
7017 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
7018 // deprecated after 1.7.1
|
paulo@89
|
7019 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
7020 tooltip.enabled(!!_);
|
paulo@89
|
7021 }},
|
paulo@89
|
7022 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
7023 // deprecated after 1.7.1
|
paulo@89
|
7024 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
7025 tooltip.contentGenerator(_);
|
paulo@89
|
7026 }},
|
paulo@89
|
7027
|
paulo@89
|
7028 // options that require extra logic in the setter
|
paulo@89
|
7029 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
7030 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
7031 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
7032 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
7033 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
7034 }},
|
paulo@89
|
7035 duration: {get: function(){return transitionDuration;}, set: function(_){
|
paulo@89
|
7036 transitionDuration = _;
|
paulo@89
|
7037 }},
|
paulo@89
|
7038 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
7039 color = nv.utils.getColor(_);
|
paulo@89
|
7040 legend.color(color);
|
paulo@89
|
7041 }},
|
paulo@89
|
7042 x: {get: function(){return getX;}, set: function(_){
|
paulo@89
|
7043 getX = _;
|
paulo@89
|
7044 lines.x(_);
|
paulo@89
|
7045 lines2.x(_);
|
paulo@89
|
7046 bars.x(_);
|
paulo@89
|
7047 bars2.x(_);
|
paulo@89
|
7048 }},
|
paulo@89
|
7049 y: {get: function(){return getY;}, set: function(_){
|
paulo@89
|
7050 getY = _;
|
paulo@89
|
7051 lines.y(_);
|
paulo@89
|
7052 lines2.y(_);
|
paulo@89
|
7053 bars.y(_);
|
paulo@89
|
7054 bars2.y(_);
|
paulo@89
|
7055 }}
|
paulo@89
|
7056 });
|
paulo@89
|
7057
|
paulo@89
|
7058 nv.utils.inheritOptions(chart, lines);
|
paulo@89
|
7059 nv.utils.initOptions(chart);
|
paulo@89
|
7060
|
paulo@89
|
7061 return chart;
|
paulo@89
|
7062 };
|
paulo@89
|
7063 nv.models.lineWithFocusChart = function() {
|
paulo@89
|
7064 "use strict";
|
paulo@89
|
7065
|
paulo@89
|
7066 //============================================================
|
paulo@89
|
7067 // Public Variables with Default Settings
|
paulo@89
|
7068 //------------------------------------------------------------
|
paulo@89
|
7069
|
paulo@89
|
7070 var lines = nv.models.line()
|
paulo@89
|
7071 , lines2 = nv.models.line()
|
paulo@89
|
7072 , xAxis = nv.models.axis()
|
paulo@89
|
7073 , yAxis = nv.models.axis()
|
paulo@89
|
7074 , x2Axis = nv.models.axis()
|
paulo@89
|
7075 , y2Axis = nv.models.axis()
|
paulo@89
|
7076 , legend = nv.models.legend()
|
paulo@89
|
7077 , brush = d3.svg.brush()
|
paulo@89
|
7078 , tooltip = nv.models.tooltip()
|
paulo@89
|
7079 , interactiveLayer = nv.interactiveGuideline()
|
paulo@89
|
7080 ;
|
paulo@89
|
7081
|
paulo@89
|
7082 var margin = {top: 30, right: 30, bottom: 30, left: 60}
|
paulo@89
|
7083 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
|
paulo@89
|
7084 , color = nv.utils.defaultColor()
|
paulo@89
|
7085 , width = null
|
paulo@89
|
7086 , height = null
|
paulo@89
|
7087 , height2 = 50
|
paulo@89
|
7088 , useInteractiveGuideline = false
|
paulo@89
|
7089 , x
|
paulo@89
|
7090 , y
|
paulo@89
|
7091 , x2
|
paulo@89
|
7092 , y2
|
paulo@89
|
7093 , showLegend = true
|
paulo@89
|
7094 , brushExtent = null
|
paulo@89
|
7095 , noData = null
|
paulo@89
|
7096 , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
|
paulo@89
|
7097 , transitionDuration = 250
|
paulo@89
|
7098 , state = nv.utils.state()
|
paulo@89
|
7099 , defaultState = null
|
paulo@89
|
7100 ;
|
paulo@89
|
7101
|
paulo@89
|
7102 lines.clipEdge(true).duration(0);
|
paulo@89
|
7103 lines2.interactive(false);
|
paulo@89
|
7104 xAxis.orient('bottom').tickPadding(5);
|
paulo@89
|
7105 yAxis.orient('left');
|
paulo@89
|
7106 x2Axis.orient('bottom').tickPadding(5);
|
paulo@89
|
7107 y2Axis.orient('left');
|
paulo@89
|
7108
|
paulo@89
|
7109 tooltip.valueFormatter(function(d, i) {
|
paulo@89
|
7110 return yAxis.tickFormat()(d, i);
|
paulo@89
|
7111 }).headerFormatter(function(d, i) {
|
paulo@89
|
7112 return xAxis.tickFormat()(d, i);
|
paulo@89
|
7113 });
|
paulo@89
|
7114
|
paulo@89
|
7115 //============================================================
|
paulo@89
|
7116 // Private Variables
|
paulo@89
|
7117 //------------------------------------------------------------
|
paulo@89
|
7118
|
paulo@89
|
7119 var stateGetter = function(data) {
|
paulo@89
|
7120 return function(){
|
paulo@89
|
7121 return {
|
paulo@89
|
7122 active: data.map(function(d) { return !d.disabled })
|
paulo@89
|
7123 };
|
paulo@89
|
7124 }
|
paulo@89
|
7125 };
|
paulo@89
|
7126
|
paulo@89
|
7127 var stateSetter = function(data) {
|
paulo@89
|
7128 return function(state) {
|
paulo@89
|
7129 if (state.active !== undefined)
|
paulo@89
|
7130 data.forEach(function(series,i) {
|
paulo@89
|
7131 series.disabled = !state.active[i];
|
paulo@89
|
7132 });
|
paulo@89
|
7133 }
|
paulo@89
|
7134 };
|
paulo@89
|
7135
|
paulo@89
|
7136 function chart(selection) {
|
paulo@89
|
7137 selection.each(function(data) {
|
paulo@89
|
7138 var container = d3.select(this),
|
paulo@89
|
7139 that = this;
|
paulo@89
|
7140 nv.utils.initSVG(container);
|
paulo@89
|
7141 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
7142 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2,
|
paulo@89
|
7143 availableHeight2 = height2 - margin2.top - margin2.bottom;
|
paulo@89
|
7144
|
paulo@89
|
7145 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
|
paulo@89
|
7146 chart.container = this;
|
paulo@89
|
7147
|
paulo@89
|
7148 state
|
paulo@89
|
7149 .setter(stateSetter(data), chart.update)
|
paulo@89
|
7150 .getter(stateGetter(data))
|
paulo@89
|
7151 .update();
|
paulo@89
|
7152
|
paulo@89
|
7153 // DEPRECATED set state.disableddisabled
|
paulo@89
|
7154 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
7155
|
paulo@89
|
7156 if (!defaultState) {
|
paulo@89
|
7157 var key;
|
paulo@89
|
7158 defaultState = {};
|
paulo@89
|
7159 for (key in state) {
|
paulo@89
|
7160 if (state[key] instanceof Array)
|
paulo@89
|
7161 defaultState[key] = state[key].slice(0);
|
paulo@89
|
7162 else
|
paulo@89
|
7163 defaultState[key] = state[key];
|
paulo@89
|
7164 }
|
paulo@89
|
7165 }
|
paulo@89
|
7166
|
paulo@89
|
7167 // Display No Data message if there's nothing to show.
|
paulo@89
|
7168 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
7169 nv.utils.noData(chart, container)
|
paulo@89
|
7170 return chart;
|
paulo@89
|
7171 } else {
|
paulo@89
|
7172 container.selectAll('.nv-noData').remove();
|
paulo@89
|
7173 }
|
paulo@89
|
7174
|
paulo@89
|
7175 // Setup Scales
|
paulo@89
|
7176 x = lines.xScale();
|
paulo@89
|
7177 y = lines.yScale();
|
paulo@89
|
7178 x2 = lines2.xScale();
|
paulo@89
|
7179 y2 = lines2.yScale();
|
paulo@89
|
7180
|
paulo@89
|
7181 // Setup containers and skeleton of chart
|
paulo@89
|
7182 var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
|
paulo@89
|
7183 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
|
paulo@89
|
7184 var g = wrap.select('g');
|
paulo@89
|
7185
|
paulo@89
|
7186 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
7187
|
paulo@89
|
7188 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
|
paulo@89
|
7189 focusEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
7190 focusEnter.append('g').attr('class', 'nv-y nv-axis');
|
paulo@89
|
7191 focusEnter.append('g').attr('class', 'nv-linesWrap');
|
paulo@89
|
7192 focusEnter.append('g').attr('class', 'nv-interactive');
|
paulo@89
|
7193
|
paulo@89
|
7194 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
|
paulo@89
|
7195 contextEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
7196 contextEnter.append('g').attr('class', 'nv-y nv-axis');
|
paulo@89
|
7197 contextEnter.append('g').attr('class', 'nv-linesWrap');
|
paulo@89
|
7198 contextEnter.append('g').attr('class', 'nv-brushBackground');
|
paulo@89
|
7199 contextEnter.append('g').attr('class', 'nv-x nv-brush');
|
paulo@89
|
7200
|
paulo@89
|
7201 // Legend
|
paulo@89
|
7202 if (showLegend) {
|
paulo@89
|
7203 legend.width(availableWidth);
|
paulo@89
|
7204
|
paulo@89
|
7205 g.select('.nv-legendWrap')
|
paulo@89
|
7206 .datum(data)
|
paulo@89
|
7207 .call(legend);
|
paulo@89
|
7208
|
paulo@89
|
7209 if ( margin.top != legend.height()) {
|
paulo@89
|
7210 margin.top = legend.height();
|
paulo@89
|
7211 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2;
|
paulo@89
|
7212 }
|
paulo@89
|
7213
|
paulo@89
|
7214 g.select('.nv-legendWrap')
|
paulo@89
|
7215 .attr('transform', 'translate(0,' + (-margin.top) +')')
|
paulo@89
|
7216 }
|
paulo@89
|
7217
|
paulo@89
|
7218 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
7219
|
paulo@89
|
7220
|
paulo@89
|
7221 //Set up interactive layer
|
paulo@89
|
7222 if (useInteractiveGuideline) {
|
paulo@89
|
7223 interactiveLayer
|
paulo@89
|
7224 .width(availableWidth)
|
paulo@89
|
7225 .height(availableHeight1)
|
paulo@89
|
7226 .margin({left:margin.left, top:margin.top})
|
paulo@89
|
7227 .svgContainer(container)
|
paulo@89
|
7228 .xScale(x);
|
paulo@89
|
7229 wrap.select(".nv-interactive").call(interactiveLayer);
|
paulo@89
|
7230 }
|
paulo@89
|
7231
|
paulo@89
|
7232 // Main Chart Component(s)
|
paulo@89
|
7233 lines
|
paulo@89
|
7234 .width(availableWidth)
|
paulo@89
|
7235 .height(availableHeight1)
|
paulo@89
|
7236 .color(
|
paulo@89
|
7237 data
|
paulo@89
|
7238 .map(function(d,i) {
|
paulo@89
|
7239 return d.color || color(d, i);
|
paulo@89
|
7240 })
|
paulo@89
|
7241 .filter(function(d,i) {
|
paulo@89
|
7242 return !data[i].disabled;
|
paulo@89
|
7243 })
|
paulo@89
|
7244 );
|
paulo@89
|
7245
|
paulo@89
|
7246 lines2
|
paulo@89
|
7247 .defined(lines.defined())
|
paulo@89
|
7248 .width(availableWidth)
|
paulo@89
|
7249 .height(availableHeight2)
|
paulo@89
|
7250 .color(
|
paulo@89
|
7251 data
|
paulo@89
|
7252 .map(function(d,i) {
|
paulo@89
|
7253 return d.color || color(d, i);
|
paulo@89
|
7254 })
|
paulo@89
|
7255 .filter(function(d,i) {
|
paulo@89
|
7256 return !data[i].disabled;
|
paulo@89
|
7257 })
|
paulo@89
|
7258 );
|
paulo@89
|
7259
|
paulo@89
|
7260 g.select('.nv-context')
|
paulo@89
|
7261 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
|
paulo@89
|
7262
|
paulo@89
|
7263 var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
|
paulo@89
|
7264 .datum(data.filter(function(d) { return !d.disabled }))
|
paulo@89
|
7265
|
paulo@89
|
7266 d3.transition(contextLinesWrap).call(lines2);
|
paulo@89
|
7267
|
paulo@89
|
7268 // Setup Main (Focus) Axes
|
paulo@89
|
7269 xAxis
|
paulo@89
|
7270 .scale(x)
|
paulo@89
|
7271 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
7272 .tickSize(-availableHeight1, 0);
|
paulo@89
|
7273
|
paulo@89
|
7274 yAxis
|
paulo@89
|
7275 .scale(y)
|
paulo@89
|
7276 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
|
paulo@89
|
7277 .tickSize( -availableWidth, 0);
|
paulo@89
|
7278
|
paulo@89
|
7279 g.select('.nv-focus .nv-x.nv-axis')
|
paulo@89
|
7280 .attr('transform', 'translate(0,' + availableHeight1 + ')');
|
paulo@89
|
7281
|
paulo@89
|
7282 // Setup Brush
|
paulo@89
|
7283 brush
|
paulo@89
|
7284 .x(x2)
|
paulo@89
|
7285 .on('brush', function() {
|
paulo@89
|
7286 onBrush();
|
paulo@89
|
7287 });
|
paulo@89
|
7288
|
paulo@89
|
7289 if (brushExtent) brush.extent(brushExtent);
|
paulo@89
|
7290
|
paulo@89
|
7291 var brushBG = g.select('.nv-brushBackground').selectAll('g')
|
paulo@89
|
7292 .data([brushExtent || brush.extent()])
|
paulo@89
|
7293
|
paulo@89
|
7294 var brushBGenter = brushBG.enter()
|
paulo@89
|
7295 .append('g');
|
paulo@89
|
7296
|
paulo@89
|
7297 brushBGenter.append('rect')
|
paulo@89
|
7298 .attr('class', 'left')
|
paulo@89
|
7299 .attr('x', 0)
|
paulo@89
|
7300 .attr('y', 0)
|
paulo@89
|
7301 .attr('height', availableHeight2);
|
paulo@89
|
7302
|
paulo@89
|
7303 brushBGenter.append('rect')
|
paulo@89
|
7304 .attr('class', 'right')
|
paulo@89
|
7305 .attr('x', 0)
|
paulo@89
|
7306 .attr('y', 0)
|
paulo@89
|
7307 .attr('height', availableHeight2);
|
paulo@89
|
7308
|
paulo@89
|
7309 var gBrush = g.select('.nv-x.nv-brush')
|
paulo@89
|
7310 .call(brush);
|
paulo@89
|
7311 gBrush.selectAll('rect')
|
paulo@89
|
7312 .attr('height', availableHeight2);
|
paulo@89
|
7313 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
|
paulo@89
|
7314
|
paulo@89
|
7315 onBrush();
|
paulo@89
|
7316
|
paulo@89
|
7317 // Setup Secondary (Context) Axes
|
paulo@89
|
7318 x2Axis
|
paulo@89
|
7319 .scale(x2)
|
paulo@89
|
7320 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
7321 .tickSize(-availableHeight2, 0);
|
paulo@89
|
7322
|
paulo@89
|
7323 g.select('.nv-context .nv-x.nv-axis')
|
paulo@89
|
7324 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
|
paulo@89
|
7325 d3.transition(g.select('.nv-context .nv-x.nv-axis'))
|
paulo@89
|
7326 .call(x2Axis);
|
paulo@89
|
7327
|
paulo@89
|
7328 y2Axis
|
paulo@89
|
7329 .scale(y2)
|
paulo@89
|
7330 ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
|
paulo@89
|
7331 .tickSize( -availableWidth, 0);
|
paulo@89
|
7332
|
paulo@89
|
7333 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
|
paulo@89
|
7334 .call(y2Axis);
|
paulo@89
|
7335
|
paulo@89
|
7336 g.select('.nv-context .nv-x.nv-axis')
|
paulo@89
|
7337 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
|
paulo@89
|
7338
|
paulo@89
|
7339 //============================================================
|
paulo@89
|
7340 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
7341 //------------------------------------------------------------
|
paulo@89
|
7342
|
paulo@89
|
7343 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
7344 for (var key in newState)
|
paulo@89
|
7345 state[key] = newState[key];
|
paulo@89
|
7346 dispatch.stateChange(state);
|
paulo@89
|
7347 chart.update();
|
paulo@89
|
7348 });
|
paulo@89
|
7349
|
paulo@89
|
7350 interactiveLayer.dispatch.on('elementMousemove', function(e) {
|
paulo@89
|
7351 lines.clearHighlights();
|
paulo@89
|
7352 var singlePoint, pointIndex, pointXLocation, allData = [];
|
paulo@89
|
7353 data
|
paulo@89
|
7354 .filter(function(series, i) {
|
paulo@89
|
7355 series.seriesIndex = i;
|
paulo@89
|
7356 return !series.disabled;
|
paulo@89
|
7357 })
|
paulo@89
|
7358 .forEach(function(series,i) {
|
paulo@89
|
7359 var extent = brush.empty() ? x2.domain() : brush.extent();
|
paulo@89
|
7360 var currentValues = series.values.filter(function(d,i) {
|
paulo@89
|
7361 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
|
paulo@89
|
7362 });
|
paulo@89
|
7363
|
paulo@89
|
7364 pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
|
paulo@89
|
7365 var point = currentValues[pointIndex];
|
paulo@89
|
7366 var pointYValue = chart.y()(point, pointIndex);
|
paulo@89
|
7367 if (pointYValue != null) {
|
paulo@89
|
7368 lines.highlightPoint(i, pointIndex, true);
|
paulo@89
|
7369 }
|
paulo@89
|
7370 if (point === undefined) return;
|
paulo@89
|
7371 if (singlePoint === undefined) singlePoint = point;
|
paulo@89
|
7372 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
|
paulo@89
|
7373 allData.push({
|
paulo@89
|
7374 key: series.key,
|
paulo@89
|
7375 value: chart.y()(point, pointIndex),
|
paulo@89
|
7376 color: color(series,series.seriesIndex)
|
paulo@89
|
7377 });
|
paulo@89
|
7378 });
|
paulo@89
|
7379 //Highlight the tooltip entry based on which point the mouse is closest to.
|
paulo@89
|
7380 if (allData.length > 2) {
|
paulo@89
|
7381 var yValue = chart.yScale().invert(e.mouseY);
|
paulo@89
|
7382 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
|
paulo@89
|
7383 var threshold = 0.03 * domainExtent;
|
paulo@89
|
7384 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
|
paulo@89
|
7385 if (indexToHighlight !== null)
|
paulo@89
|
7386 allData[indexToHighlight].highlight = true;
|
paulo@89
|
7387 }
|
paulo@89
|
7388
|
paulo@89
|
7389 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
|
paulo@89
|
7390 interactiveLayer.tooltip
|
paulo@89
|
7391 .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top})
|
paulo@89
|
7392 .chartContainer(that.parentNode)
|
paulo@89
|
7393 .valueFormatter(function(d,i) {
|
paulo@89
|
7394 return d == null ? "N/A" : yAxis.tickFormat()(d);
|
paulo@89
|
7395 })
|
paulo@89
|
7396 .data({
|
paulo@89
|
7397 value: xValue,
|
paulo@89
|
7398 index: pointIndex,
|
paulo@89
|
7399 series: allData
|
paulo@89
|
7400 })();
|
paulo@89
|
7401
|
paulo@89
|
7402 interactiveLayer.renderGuideLine(pointXLocation);
|
paulo@89
|
7403
|
paulo@89
|
7404 });
|
paulo@89
|
7405
|
paulo@89
|
7406 interactiveLayer.dispatch.on("elementMouseout",function(e) {
|
paulo@89
|
7407 lines.clearHighlights();
|
paulo@89
|
7408 });
|
paulo@89
|
7409
|
paulo@89
|
7410 dispatch.on('changeState', function(e) {
|
paulo@89
|
7411 if (typeof e.disabled !== 'undefined') {
|
paulo@89
|
7412 data.forEach(function(series,i) {
|
paulo@89
|
7413 series.disabled = e.disabled[i];
|
paulo@89
|
7414 });
|
paulo@89
|
7415 }
|
paulo@89
|
7416 chart.update();
|
paulo@89
|
7417 });
|
paulo@89
|
7418
|
paulo@89
|
7419 //============================================================
|
paulo@89
|
7420 // Functions
|
paulo@89
|
7421 //------------------------------------------------------------
|
paulo@89
|
7422
|
paulo@89
|
7423 // Taken from crossfilter (http://square.github.com/crossfilter/)
|
paulo@89
|
7424 function resizePath(d) {
|
paulo@89
|
7425 var e = +(d == 'e'),
|
paulo@89
|
7426 x = e ? 1 : -1,
|
paulo@89
|
7427 y = availableHeight2 / 3;
|
paulo@89
|
7428 return 'M' + (.5 * x) + ',' + y
|
paulo@89
|
7429 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
|
paulo@89
|
7430 + 'V' + (2 * y - 6)
|
paulo@89
|
7431 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
|
paulo@89
|
7432 + 'Z'
|
paulo@89
|
7433 + 'M' + (2.5 * x) + ',' + (y + 8)
|
paulo@89
|
7434 + 'V' + (2 * y - 8)
|
paulo@89
|
7435 + 'M' + (4.5 * x) + ',' + (y + 8)
|
paulo@89
|
7436 + 'V' + (2 * y - 8);
|
paulo@89
|
7437 }
|
paulo@89
|
7438
|
paulo@89
|
7439
|
paulo@89
|
7440 function updateBrushBG() {
|
paulo@89
|
7441 if (!brush.empty()) brush.extent(brushExtent);
|
paulo@89
|
7442 brushBG
|
paulo@89
|
7443 .data([brush.empty() ? x2.domain() : brushExtent])
|
paulo@89
|
7444 .each(function(d,i) {
|
paulo@89
|
7445 var leftWidth = x2(d[0]) - x.range()[0],
|
paulo@89
|
7446 rightWidth = availableWidth - x2(d[1]);
|
paulo@89
|
7447 d3.select(this).select('.left')
|
paulo@89
|
7448 .attr('width', leftWidth < 0 ? 0 : leftWidth);
|
paulo@89
|
7449
|
paulo@89
|
7450 d3.select(this).select('.right')
|
paulo@89
|
7451 .attr('x', x2(d[1]))
|
paulo@89
|
7452 .attr('width', rightWidth < 0 ? 0 : rightWidth);
|
paulo@89
|
7453 });
|
paulo@89
|
7454 }
|
paulo@89
|
7455
|
paulo@89
|
7456
|
paulo@89
|
7457 function onBrush() {
|
paulo@89
|
7458 brushExtent = brush.empty() ? null : brush.extent();
|
paulo@89
|
7459 var extent = brush.empty() ? x2.domain() : brush.extent();
|
paulo@89
|
7460
|
paulo@89
|
7461 //The brush extent cannot be less than one. If it is, don't update the line chart.
|
paulo@89
|
7462 if (Math.abs(extent[0] - extent[1]) <= 1) {
|
paulo@89
|
7463 return;
|
paulo@89
|
7464 }
|
paulo@89
|
7465
|
paulo@89
|
7466 dispatch.brush({extent: extent, brush: brush});
|
paulo@89
|
7467
|
paulo@89
|
7468
|
paulo@89
|
7469 updateBrushBG();
|
paulo@89
|
7470
|
paulo@89
|
7471 // Update Main (Focus)
|
paulo@89
|
7472 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
|
paulo@89
|
7473 .datum(
|
paulo@89
|
7474 data
|
paulo@89
|
7475 .filter(function(d) { return !d.disabled })
|
paulo@89
|
7476 .map(function(d,i) {
|
paulo@89
|
7477 return {
|
paulo@89
|
7478 key: d.key,
|
paulo@89
|
7479 area: d.area,
|
paulo@89
|
7480 values: d.values.filter(function(d,i) {
|
paulo@89
|
7481 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
|
paulo@89
|
7482 })
|
paulo@89
|
7483 }
|
paulo@89
|
7484 })
|
paulo@89
|
7485 );
|
paulo@89
|
7486 focusLinesWrap.transition().duration(transitionDuration).call(lines);
|
paulo@89
|
7487
|
paulo@89
|
7488
|
paulo@89
|
7489 // Update Main (Focus) Axes
|
paulo@89
|
7490 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
|
paulo@89
|
7491 .call(xAxis);
|
paulo@89
|
7492 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
|
paulo@89
|
7493 .call(yAxis);
|
paulo@89
|
7494 }
|
paulo@89
|
7495 });
|
paulo@89
|
7496
|
paulo@89
|
7497 return chart;
|
paulo@89
|
7498 }
|
paulo@89
|
7499
|
paulo@89
|
7500 //============================================================
|
paulo@89
|
7501 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
7502 //------------------------------------------------------------
|
paulo@89
|
7503
|
paulo@89
|
7504 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
7505 tooltip.data(evt).position(evt.pos).hidden(false);
|
paulo@89
|
7506 });
|
paulo@89
|
7507
|
paulo@89
|
7508 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
7509 tooltip.hidden(true)
|
paulo@89
|
7510 });
|
paulo@89
|
7511
|
paulo@89
|
7512 //============================================================
|
paulo@89
|
7513 // Expose Public Variables
|
paulo@89
|
7514 //------------------------------------------------------------
|
paulo@89
|
7515
|
paulo@89
|
7516 // expose chart's sub-components
|
paulo@89
|
7517 chart.dispatch = dispatch;
|
paulo@89
|
7518 chart.legend = legend;
|
paulo@89
|
7519 chart.lines = lines;
|
paulo@89
|
7520 chart.lines2 = lines2;
|
paulo@89
|
7521 chart.xAxis = xAxis;
|
paulo@89
|
7522 chart.yAxis = yAxis;
|
paulo@89
|
7523 chart.x2Axis = x2Axis;
|
paulo@89
|
7524 chart.y2Axis = y2Axis;
|
paulo@89
|
7525 chart.interactiveLayer = interactiveLayer;
|
paulo@89
|
7526 chart.tooltip = tooltip;
|
paulo@89
|
7527
|
paulo@89
|
7528 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
7529
|
paulo@89
|
7530 chart._options = Object.create({}, {
|
paulo@89
|
7531 // simple options, just get/set the necessary values
|
paulo@89
|
7532 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
7533 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
7534 focusHeight: {get: function(){return height2;}, set: function(_){height2=_;}},
|
paulo@89
|
7535 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
7536 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
|
paulo@89
|
7537 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
7538 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
7539
|
paulo@89
|
7540 // deprecated options
|
paulo@89
|
7541 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
7542 // deprecated after 1.7.1
|
paulo@89
|
7543 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
7544 tooltip.enabled(!!_);
|
paulo@89
|
7545 }},
|
paulo@89
|
7546 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
7547 // deprecated after 1.7.1
|
paulo@89
|
7548 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
7549 tooltip.contentGenerator(_);
|
paulo@89
|
7550 }},
|
paulo@89
|
7551
|
paulo@89
|
7552 // options that require extra logic in the setter
|
paulo@89
|
7553 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
7554 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
7555 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
7556 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
7557 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
7558 }},
|
paulo@89
|
7559 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
7560 color = nv.utils.getColor(_);
|
paulo@89
|
7561 legend.color(color);
|
paulo@89
|
7562 // line color is handled above?
|
paulo@89
|
7563 }},
|
paulo@89
|
7564 interpolate: {get: function(){return lines.interpolate();}, set: function(_){
|
paulo@89
|
7565 lines.interpolate(_);
|
paulo@89
|
7566 lines2.interpolate(_);
|
paulo@89
|
7567 }},
|
paulo@89
|
7568 xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
|
paulo@89
|
7569 xAxis.tickFormat(_);
|
paulo@89
|
7570 x2Axis.tickFormat(_);
|
paulo@89
|
7571 }},
|
paulo@89
|
7572 yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
|
paulo@89
|
7573 yAxis.tickFormat(_);
|
paulo@89
|
7574 y2Axis.tickFormat(_);
|
paulo@89
|
7575 }},
|
paulo@89
|
7576 duration: {get: function(){return transitionDuration;}, set: function(_){
|
paulo@89
|
7577 transitionDuration=_;
|
paulo@89
|
7578 yAxis.duration(transitionDuration);
|
paulo@89
|
7579 y2Axis.duration(transitionDuration);
|
paulo@89
|
7580 xAxis.duration(transitionDuration);
|
paulo@89
|
7581 x2Axis.duration(transitionDuration);
|
paulo@89
|
7582 }},
|
paulo@89
|
7583 x: {get: function(){return lines.x();}, set: function(_){
|
paulo@89
|
7584 lines.x(_);
|
paulo@89
|
7585 lines2.x(_);
|
paulo@89
|
7586 }},
|
paulo@89
|
7587 y: {get: function(){return lines.y();}, set: function(_){
|
paulo@89
|
7588 lines.y(_);
|
paulo@89
|
7589 lines2.y(_);
|
paulo@89
|
7590 }},
|
paulo@89
|
7591 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
|
paulo@89
|
7592 useInteractiveGuideline = _;
|
paulo@89
|
7593 if (useInteractiveGuideline) {
|
paulo@89
|
7594 lines.interactive(false);
|
paulo@89
|
7595 lines.useVoronoi(false);
|
paulo@89
|
7596 }
|
paulo@89
|
7597 }}
|
paulo@89
|
7598 });
|
paulo@89
|
7599
|
paulo@89
|
7600 nv.utils.inheritOptions(chart, lines);
|
paulo@89
|
7601 nv.utils.initOptions(chart);
|
paulo@89
|
7602
|
paulo@89
|
7603 return chart;
|
paulo@89
|
7604 };
|
paulo@89
|
7605
|
paulo@89
|
7606 nv.models.multiBar = function() {
|
paulo@89
|
7607 "use strict";
|
paulo@89
|
7608
|
paulo@89
|
7609 //============================================================
|
paulo@89
|
7610 // Public Variables with Default Settings
|
paulo@89
|
7611 //------------------------------------------------------------
|
paulo@89
|
7612
|
paulo@89
|
7613 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
7614 , width = 960
|
paulo@89
|
7615 , height = 500
|
paulo@89
|
7616 , x = d3.scale.ordinal()
|
paulo@89
|
7617 , y = d3.scale.linear()
|
paulo@89
|
7618 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
7619 , container = null
|
paulo@89
|
7620 , getX = function(d) { return d.x }
|
paulo@89
|
7621 , getY = function(d) { return d.y }
|
paulo@89
|
7622 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
|
paulo@89
|
7623 , clipEdge = true
|
paulo@89
|
7624 , stacked = false
|
paulo@89
|
7625 , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
|
paulo@89
|
7626 , color = nv.utils.defaultColor()
|
paulo@89
|
7627 , hideable = false
|
paulo@89
|
7628 , barColor = null // adding the ability to set the color for each rather than the whole group
|
paulo@89
|
7629 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
|
paulo@89
|
7630 , duration = 500
|
paulo@89
|
7631 , xDomain
|
paulo@89
|
7632 , yDomain
|
paulo@89
|
7633 , xRange
|
paulo@89
|
7634 , yRange
|
paulo@89
|
7635 , groupSpacing = 0.1
|
paulo@89
|
7636 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
|
paulo@89
|
7637 ;
|
paulo@89
|
7638
|
paulo@89
|
7639 //============================================================
|
paulo@89
|
7640 // Private Variables
|
paulo@89
|
7641 //------------------------------------------------------------
|
paulo@89
|
7642
|
paulo@89
|
7643 var x0, y0 //used to store previous scales
|
paulo@89
|
7644 , renderWatch = nv.utils.renderWatch(dispatch, duration)
|
paulo@89
|
7645 ;
|
paulo@89
|
7646
|
paulo@89
|
7647 var last_datalength = 0;
|
paulo@89
|
7648
|
paulo@89
|
7649 function chart(selection) {
|
paulo@89
|
7650 renderWatch.reset();
|
paulo@89
|
7651 selection.each(function(data) {
|
paulo@89
|
7652 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
7653 availableHeight = height - margin.top - margin.bottom;
|
paulo@89
|
7654
|
paulo@89
|
7655 container = d3.select(this);
|
paulo@89
|
7656 nv.utils.initSVG(container);
|
paulo@89
|
7657 var nonStackableCount = 0;
|
paulo@89
|
7658 // This function defines the requirements for render complete
|
paulo@89
|
7659 var endFn = function(d, i) {
|
paulo@89
|
7660 if (d.series === data.length - 1 && i === data[0].values.length - 1)
|
paulo@89
|
7661 return true;
|
paulo@89
|
7662 return false;
|
paulo@89
|
7663 };
|
paulo@89
|
7664
|
paulo@89
|
7665 if(hideable && data.length) hideable = [{
|
paulo@89
|
7666 values: data[0].values.map(function(d) {
|
paulo@89
|
7667 return {
|
paulo@89
|
7668 x: d.x,
|
paulo@89
|
7669 y: 0,
|
paulo@89
|
7670 series: d.series,
|
paulo@89
|
7671 size: 0.01
|
paulo@89
|
7672 };}
|
paulo@89
|
7673 )}];
|
paulo@89
|
7674
|
paulo@89
|
7675 if (stacked) {
|
paulo@89
|
7676 var parsed = d3.layout.stack()
|
paulo@89
|
7677 .offset(stackOffset)
|
paulo@89
|
7678 .values(function(d){ return d.values })
|
paulo@89
|
7679 .y(getY)
|
paulo@89
|
7680 (!data.length && hideable ? hideable : data);
|
paulo@89
|
7681
|
paulo@89
|
7682 parsed.forEach(function(series, i){
|
paulo@89
|
7683 // if series is non-stackable, use un-parsed data
|
paulo@89
|
7684 if (series.nonStackable) {
|
paulo@89
|
7685 data[i].nonStackableSeries = nonStackableCount++;
|
paulo@89
|
7686 parsed[i] = data[i];
|
paulo@89
|
7687 } else {
|
paulo@89
|
7688 // don't stack this seires on top of the nonStackable seriees
|
paulo@89
|
7689 if (i > 0 && parsed[i - 1].nonStackable){
|
paulo@89
|
7690 parsed[i].values.map(function(d,j){
|
paulo@89
|
7691 d.y0 -= parsed[i - 1].values[j].y;
|
paulo@89
|
7692 d.y1 = d.y0 + d.y;
|
paulo@89
|
7693 });
|
paulo@89
|
7694 }
|
paulo@89
|
7695 }
|
paulo@89
|
7696 });
|
paulo@89
|
7697 data = parsed;
|
paulo@89
|
7698 }
|
paulo@89
|
7699 //add series index and key to each data point for reference
|
paulo@89
|
7700 data.forEach(function(series, i) {
|
paulo@89
|
7701 series.values.forEach(function(point) {
|
paulo@89
|
7702 point.series = i;
|
paulo@89
|
7703 point.key = series.key;
|
paulo@89
|
7704 });
|
paulo@89
|
7705 });
|
paulo@89
|
7706
|
paulo@89
|
7707 // HACK for negative value stacking
|
paulo@89
|
7708 if (stacked) {
|
paulo@89
|
7709 data[0].values.map(function(d,i) {
|
paulo@89
|
7710 var posBase = 0, negBase = 0;
|
paulo@89
|
7711 data.map(function(d, idx) {
|
paulo@89
|
7712 if (!data[idx].nonStackable) {
|
paulo@89
|
7713 var f = d.values[i]
|
paulo@89
|
7714 f.size = Math.abs(f.y);
|
paulo@89
|
7715 if (f.y<0) {
|
paulo@89
|
7716 f.y1 = negBase;
|
paulo@89
|
7717 negBase = negBase - f.size;
|
paulo@89
|
7718 } else
|
paulo@89
|
7719 {
|
paulo@89
|
7720 f.y1 = f.size + posBase;
|
paulo@89
|
7721 posBase = posBase + f.size;
|
paulo@89
|
7722 }
|
paulo@89
|
7723 }
|
paulo@89
|
7724
|
paulo@89
|
7725 });
|
paulo@89
|
7726 });
|
paulo@89
|
7727 }
|
paulo@89
|
7728 // Setup Scales
|
paulo@89
|
7729 // remap and flatten the data for use in calculating the scales' domains
|
paulo@89
|
7730 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
|
paulo@89
|
7731 data.map(function(d, idx) {
|
paulo@89
|
7732 return d.values.map(function(d,i) {
|
paulo@89
|
7733 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx }
|
paulo@89
|
7734 })
|
paulo@89
|
7735 });
|
paulo@89
|
7736
|
paulo@89
|
7737 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
|
paulo@89
|
7738 .rangeBands(xRange || [0, availableWidth], groupSpacing);
|
paulo@89
|
7739
|
paulo@89
|
7740 y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) {
|
paulo@89
|
7741 var domain = d.y;
|
paulo@89
|
7742 // increase the domain range if this series is stackable
|
paulo@89
|
7743 if (stacked && !data[d.idx].nonStackable) {
|
paulo@89
|
7744 if (d.y > 0){
|
paulo@89
|
7745 domain = d.y1
|
paulo@89
|
7746 } else {
|
paulo@89
|
7747 domain = d.y1 + d.y
|
paulo@89
|
7748 }
|
paulo@89
|
7749 }
|
paulo@89
|
7750 return domain;
|
paulo@89
|
7751 }).concat(forceY)))
|
paulo@89
|
7752 .range(yRange || [availableHeight, 0]);
|
paulo@89
|
7753
|
paulo@89
|
7754 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
|
paulo@89
|
7755 if (x.domain()[0] === x.domain()[1])
|
paulo@89
|
7756 x.domain()[0] ?
|
paulo@89
|
7757 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
|
paulo@89
|
7758 : x.domain([-1,1]);
|
paulo@89
|
7759
|
paulo@89
|
7760 if (y.domain()[0] === y.domain()[1])
|
paulo@89
|
7761 y.domain()[0] ?
|
paulo@89
|
7762 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
|
paulo@89
|
7763 : y.domain([-1,1]);
|
paulo@89
|
7764
|
paulo@89
|
7765 x0 = x0 || x;
|
paulo@89
|
7766 y0 = y0 || y;
|
paulo@89
|
7767
|
paulo@89
|
7768 // Setup containers and skeleton of chart
|
paulo@89
|
7769 var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
|
paulo@89
|
7770 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
|
paulo@89
|
7771 var defsEnter = wrapEnter.append('defs');
|
paulo@89
|
7772 var gEnter = wrapEnter.append('g');
|
paulo@89
|
7773 var g = wrap.select('g');
|
paulo@89
|
7774
|
paulo@89
|
7775 gEnter.append('g').attr('class', 'nv-groups');
|
paulo@89
|
7776 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
7777
|
paulo@89
|
7778 defsEnter.append('clipPath')
|
paulo@89
|
7779 .attr('id', 'nv-edge-clip-' + id)
|
paulo@89
|
7780 .append('rect');
|
paulo@89
|
7781 wrap.select('#nv-edge-clip-' + id + ' rect')
|
paulo@89
|
7782 .attr('width', availableWidth)
|
paulo@89
|
7783 .attr('height', availableHeight);
|
paulo@89
|
7784
|
paulo@89
|
7785 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
|
paulo@89
|
7786
|
paulo@89
|
7787 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
|
paulo@89
|
7788 .data(function(d) { return d }, function(d,i) { return i });
|
paulo@89
|
7789 groups.enter().append('g')
|
paulo@89
|
7790 .style('stroke-opacity', 1e-6)
|
paulo@89
|
7791 .style('fill-opacity', 1e-6);
|
paulo@89
|
7792
|
paulo@89
|
7793 var exitTransition = renderWatch
|
paulo@89
|
7794 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
|
paulo@89
|
7795 .attr('y', function(d, i, j) {
|
paulo@89
|
7796 var yVal = y0(0) || 0;
|
paulo@89
|
7797 if (stacked) {
|
paulo@89
|
7798 if (data[d.series] && !data[d.series].nonStackable) {
|
paulo@89
|
7799 yVal = y0(d.y0);
|
paulo@89
|
7800 }
|
paulo@89
|
7801 }
|
paulo@89
|
7802 return yVal;
|
paulo@89
|
7803 })
|
paulo@89
|
7804 .attr('height', 0)
|
paulo@89
|
7805 .remove();
|
paulo@89
|
7806 if (exitTransition.delay)
|
paulo@89
|
7807 exitTransition.delay(function(d,i) {
|
paulo@89
|
7808 var delay = i * (duration / (last_datalength + 1)) - i;
|
paulo@89
|
7809 return delay;
|
paulo@89
|
7810 });
|
paulo@89
|
7811 groups
|
paulo@89
|
7812 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
|
paulo@89
|
7813 .classed('hover', function(d) { return d.hover })
|
paulo@89
|
7814 .style('fill', function(d,i){ return color(d, i) })
|
paulo@89
|
7815 .style('stroke', function(d,i){ return color(d, i) });
|
paulo@89
|
7816 groups
|
paulo@89
|
7817 .style('stroke-opacity', 1)
|
paulo@89
|
7818 .style('fill-opacity', 0.75);
|
paulo@89
|
7819
|
paulo@89
|
7820 var bars = groups.selectAll('rect.nv-bar')
|
paulo@89
|
7821 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
|
paulo@89
|
7822 bars.exit().remove();
|
paulo@89
|
7823
|
paulo@89
|
7824 var barsEnter = bars.enter().append('rect')
|
paulo@89
|
7825 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
|
paulo@89
|
7826 .attr('x', function(d,i,j) {
|
paulo@89
|
7827 return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length )
|
paulo@89
|
7828 })
|
paulo@89
|
7829 .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
|
paulo@89
|
7830 .attr('height', 0)
|
paulo@89
|
7831 .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) })
|
paulo@89
|
7832 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
|
paulo@89
|
7833 ;
|
paulo@89
|
7834 bars
|
paulo@89
|
7835 .style('fill', function(d,i,j){ return color(d, j, i); })
|
paulo@89
|
7836 .style('stroke', function(d,i,j){ return color(d, j, i); })
|
paulo@89
|
7837 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
|
paulo@89
|
7838 d3.select(this).classed('hover', true);
|
paulo@89
|
7839 dispatch.elementMouseover({
|
paulo@89
|
7840 data: d,
|
paulo@89
|
7841 index: i,
|
paulo@89
|
7842 color: d3.select(this).style("fill")
|
paulo@89
|
7843 });
|
paulo@89
|
7844 })
|
paulo@89
|
7845 .on('mouseout', function(d,i) {
|
paulo@89
|
7846 d3.select(this).classed('hover', false);
|
paulo@89
|
7847 dispatch.elementMouseout({
|
paulo@89
|
7848 data: d,
|
paulo@89
|
7849 index: i,
|
paulo@89
|
7850 color: d3.select(this).style("fill")
|
paulo@89
|
7851 });
|
paulo@89
|
7852 })
|
paulo@89
|
7853 .on('mousemove', function(d,i) {
|
paulo@89
|
7854 dispatch.elementMousemove({
|
paulo@89
|
7855 data: d,
|
paulo@89
|
7856 index: i,
|
paulo@89
|
7857 color: d3.select(this).style("fill")
|
paulo@89
|
7858 });
|
paulo@89
|
7859 })
|
paulo@89
|
7860 .on('click', function(d,i) {
|
paulo@89
|
7861 dispatch.elementClick({
|
paulo@89
|
7862 data: d,
|
paulo@89
|
7863 index: i,
|
paulo@89
|
7864 color: d3.select(this).style("fill")
|
paulo@89
|
7865 });
|
paulo@89
|
7866 d3.event.stopPropagation();
|
paulo@89
|
7867 })
|
paulo@89
|
7868 .on('dblclick', function(d,i) {
|
paulo@89
|
7869 dispatch.elementDblClick({
|
paulo@89
|
7870 data: d,
|
paulo@89
|
7871 index: i,
|
paulo@89
|
7872 color: d3.select(this).style("fill")
|
paulo@89
|
7873 });
|
paulo@89
|
7874 d3.event.stopPropagation();
|
paulo@89
|
7875 });
|
paulo@89
|
7876 bars
|
paulo@89
|
7877 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
|
paulo@89
|
7878 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
|
paulo@89
|
7879
|
paulo@89
|
7880 if (barColor) {
|
paulo@89
|
7881 if (!disabled) disabled = data.map(function() { return true });
|
paulo@89
|
7882 bars
|
paulo@89
|
7883 .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
|
paulo@89
|
7884 .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
|
paulo@89
|
7885 }
|
paulo@89
|
7886
|
paulo@89
|
7887 var barSelection =
|
paulo@89
|
7888 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
|
paulo@89
|
7889 .delay(function(d,i) {
|
paulo@89
|
7890 return i * duration / data[0].values.length;
|
paulo@89
|
7891 });
|
paulo@89
|
7892 if (stacked){
|
paulo@89
|
7893 barSelection
|
paulo@89
|
7894 .attr('y', function(d,i,j) {
|
paulo@89
|
7895 var yVal = 0;
|
paulo@89
|
7896 // if stackable, stack it on top of the previous series
|
paulo@89
|
7897 if (!data[j].nonStackable) {
|
paulo@89
|
7898 yVal = y(d.y1);
|
paulo@89
|
7899 } else {
|
paulo@89
|
7900 if (getY(d,i) < 0){
|
paulo@89
|
7901 yVal = y(0);
|
paulo@89
|
7902 } else {
|
paulo@89
|
7903 if (y(0) - y(getY(d,i)) < -1){
|
paulo@89
|
7904 yVal = y(0) - 1;
|
paulo@89
|
7905 } else {
|
paulo@89
|
7906 yVal = y(getY(d, i)) || 0;
|
paulo@89
|
7907 }
|
paulo@89
|
7908 }
|
paulo@89
|
7909 }
|
paulo@89
|
7910 return yVal;
|
paulo@89
|
7911 })
|
paulo@89
|
7912 .attr('height', function(d,i,j) {
|
paulo@89
|
7913 if (!data[j].nonStackable) {
|
paulo@89
|
7914 return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 1);
|
paulo@89
|
7915 } else {
|
paulo@89
|
7916 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
|
paulo@89
|
7917 }
|
paulo@89
|
7918 })
|
paulo@89
|
7919 .attr('x', function(d,i,j) {
|
paulo@89
|
7920 var width = 0;
|
paulo@89
|
7921 if (data[j].nonStackable) {
|
paulo@89
|
7922 width = d.series * x.rangeBand() / data.length;
|
paulo@89
|
7923 if (data.length !== nonStackableCount){
|
paulo@89
|
7924 width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2);
|
paulo@89
|
7925 }
|
paulo@89
|
7926 }
|
paulo@89
|
7927 return width;
|
paulo@89
|
7928 })
|
paulo@89
|
7929 .attr('width', function(d,i,j){
|
paulo@89
|
7930 if (!data[j].nonStackable) {
|
paulo@89
|
7931 return x.rangeBand();
|
paulo@89
|
7932 } else {
|
paulo@89
|
7933 // if all series are nonStacable, take the full width
|
paulo@89
|
7934 var width = (x.rangeBand() / nonStackableCount);
|
paulo@89
|
7935 // otherwise, nonStackable graph will be only taking the half-width
|
paulo@89
|
7936 // of the x rangeBand
|
paulo@89
|
7937 if (data.length !== nonStackableCount) {
|
paulo@89
|
7938 width = x.rangeBand()/(nonStackableCount*2);
|
paulo@89
|
7939 }
|
paulo@89
|
7940 return width;
|
paulo@89
|
7941 }
|
paulo@89
|
7942 });
|
paulo@89
|
7943 }
|
paulo@89
|
7944 else {
|
paulo@89
|
7945 barSelection
|
paulo@89
|
7946 .attr('x', function(d,i) {
|
paulo@89
|
7947 return d.series * x.rangeBand() / data.length;
|
paulo@89
|
7948 })
|
paulo@89
|
7949 .attr('width', x.rangeBand() / data.length)
|
paulo@89
|
7950 .attr('y', function(d,i) {
|
paulo@89
|
7951 return getY(d,i) < 0 ?
|
paulo@89
|
7952 y(0) :
|
paulo@89
|
7953 y(0) - y(getY(d,i)) < 1 ?
|
paulo@89
|
7954 y(0) - 1 :
|
paulo@89
|
7955 y(getY(d,i)) || 0;
|
paulo@89
|
7956 })
|
paulo@89
|
7957 .attr('height', function(d,i) {
|
paulo@89
|
7958 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
|
paulo@89
|
7959 });
|
paulo@89
|
7960 }
|
paulo@89
|
7961
|
paulo@89
|
7962 //store old scales for use in transitions on update
|
paulo@89
|
7963 x0 = x.copy();
|
paulo@89
|
7964 y0 = y.copy();
|
paulo@89
|
7965
|
paulo@89
|
7966 // keep track of the last data value length for transition calculations
|
paulo@89
|
7967 if (data[0] && data[0].values) {
|
paulo@89
|
7968 last_datalength = data[0].values.length;
|
paulo@89
|
7969 }
|
paulo@89
|
7970
|
paulo@89
|
7971 });
|
paulo@89
|
7972
|
paulo@89
|
7973 renderWatch.renderEnd('multibar immediate');
|
paulo@89
|
7974
|
paulo@89
|
7975 return chart;
|
paulo@89
|
7976 }
|
paulo@89
|
7977
|
paulo@89
|
7978 //============================================================
|
paulo@89
|
7979 // Expose Public Variables
|
paulo@89
|
7980 //------------------------------------------------------------
|
paulo@89
|
7981
|
paulo@89
|
7982 chart.dispatch = dispatch;
|
paulo@89
|
7983
|
paulo@89
|
7984 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
7985
|
paulo@89
|
7986 chart._options = Object.create({}, {
|
paulo@89
|
7987 // simple options, just get/set the necessary values
|
paulo@89
|
7988 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
7989 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
7990 x: {get: function(){return getX;}, set: function(_){getX=_;}},
|
paulo@89
|
7991 y: {get: function(){return getY;}, set: function(_){getY=_;}},
|
paulo@89
|
7992 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
7993 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
7994 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
7995 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
7996 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
7997 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
7998 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
|
paulo@89
|
7999 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
|
paulo@89
|
8000 stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
|
paulo@89
|
8001 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
|
paulo@89
|
8002 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
|
paulo@89
|
8003 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
8004 hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}},
|
paulo@89
|
8005 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
|
paulo@89
|
8006
|
paulo@89
|
8007 // options that require extra logic in the setter
|
paulo@89
|
8008 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
8009 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
8010 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
8011 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
8012 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
8013 }},
|
paulo@89
|
8014 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
8015 duration = _;
|
paulo@89
|
8016 renderWatch.reset(duration);
|
paulo@89
|
8017 }},
|
paulo@89
|
8018 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
8019 color = nv.utils.getColor(_);
|
paulo@89
|
8020 }},
|
paulo@89
|
8021 barColor: {get: function(){return barColor;}, set: function(_){
|
paulo@89
|
8022 barColor = _ ? nv.utils.getColor(_) : null;
|
paulo@89
|
8023 }}
|
paulo@89
|
8024 });
|
paulo@89
|
8025
|
paulo@89
|
8026 nv.utils.initOptions(chart);
|
paulo@89
|
8027
|
paulo@89
|
8028 return chart;
|
paulo@89
|
8029 };
|
paulo@89
|
8030 nv.models.multiBarChart = function() {
|
paulo@89
|
8031 "use strict";
|
paulo@89
|
8032
|
paulo@89
|
8033 //============================================================
|
paulo@89
|
8034 // Public Variables with Default Settings
|
paulo@89
|
8035 //------------------------------------------------------------
|
paulo@89
|
8036
|
paulo@89
|
8037 var multibar = nv.models.multiBar()
|
paulo@89
|
8038 , xAxis = nv.models.axis()
|
paulo@89
|
8039 , yAxis = nv.models.axis()
|
paulo@89
|
8040 , legend = nv.models.legend()
|
paulo@89
|
8041 , controls = nv.models.legend()
|
paulo@89
|
8042 , tooltip = nv.models.tooltip()
|
paulo@89
|
8043 ;
|
paulo@89
|
8044
|
paulo@89
|
8045 var margin = {top: 30, right: 20, bottom: 50, left: 60}
|
paulo@89
|
8046 , width = null
|
paulo@89
|
8047 , height = null
|
paulo@89
|
8048 , color = nv.utils.defaultColor()
|
paulo@89
|
8049 , showControls = true
|
paulo@89
|
8050 , controlLabels = {}
|
paulo@89
|
8051 , showLegend = true
|
paulo@89
|
8052 , showXAxis = true
|
paulo@89
|
8053 , showYAxis = true
|
paulo@89
|
8054 , rightAlignYAxis = false
|
paulo@89
|
8055 , reduceXTicks = true // if false a tick will show for every data point
|
paulo@89
|
8056 , staggerLabels = false
|
paulo@89
|
8057 , rotateLabels = 0
|
paulo@89
|
8058 , x //can be accessed via chart.xScale()
|
paulo@89
|
8059 , y //can be accessed via chart.yScale()
|
paulo@89
|
8060 , state = nv.utils.state()
|
paulo@89
|
8061 , defaultState = null
|
paulo@89
|
8062 , noData = null
|
paulo@89
|
8063 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
|
paulo@89
|
8064 , controlWidth = function() { return showControls ? 180 : 0 }
|
paulo@89
|
8065 , duration = 250
|
paulo@89
|
8066 ;
|
paulo@89
|
8067
|
paulo@89
|
8068 state.stacked = false // DEPRECATED Maintained for backward compatibility
|
paulo@89
|
8069
|
paulo@89
|
8070 multibar.stacked(false);
|
paulo@89
|
8071 xAxis
|
paulo@89
|
8072 .orient('bottom')
|
paulo@89
|
8073 .tickPadding(7)
|
paulo@89
|
8074 .showMaxMin(false)
|
paulo@89
|
8075 .tickFormat(function(d) { return d })
|
paulo@89
|
8076 ;
|
paulo@89
|
8077 yAxis
|
paulo@89
|
8078 .orient((rightAlignYAxis) ? 'right' : 'left')
|
paulo@89
|
8079 .tickFormat(d3.format(',.1f'))
|
paulo@89
|
8080 ;
|
paulo@89
|
8081
|
paulo@89
|
8082 tooltip
|
paulo@89
|
8083 .duration(0)
|
paulo@89
|
8084 .valueFormatter(function(d, i) {
|
paulo@89
|
8085 return yAxis.tickFormat()(d, i);
|
paulo@89
|
8086 })
|
paulo@89
|
8087 .headerFormatter(function(d, i) {
|
paulo@89
|
8088 return xAxis.tickFormat()(d, i);
|
paulo@89
|
8089 });
|
paulo@89
|
8090
|
paulo@89
|
8091 controls.updateState(false);
|
paulo@89
|
8092
|
paulo@89
|
8093 //============================================================
|
paulo@89
|
8094 // Private Variables
|
paulo@89
|
8095 //------------------------------------------------------------
|
paulo@89
|
8096
|
paulo@89
|
8097 var renderWatch = nv.utils.renderWatch(dispatch);
|
paulo@89
|
8098 var stacked = false;
|
paulo@89
|
8099
|
paulo@89
|
8100 var stateGetter = function(data) {
|
paulo@89
|
8101 return function(){
|
paulo@89
|
8102 return {
|
paulo@89
|
8103 active: data.map(function(d) { return !d.disabled }),
|
paulo@89
|
8104 stacked: stacked
|
paulo@89
|
8105 };
|
paulo@89
|
8106 }
|
paulo@89
|
8107 };
|
paulo@89
|
8108
|
paulo@89
|
8109 var stateSetter = function(data) {
|
paulo@89
|
8110 return function(state) {
|
paulo@89
|
8111 if (state.stacked !== undefined)
|
paulo@89
|
8112 stacked = state.stacked;
|
paulo@89
|
8113 if (state.active !== undefined)
|
paulo@89
|
8114 data.forEach(function(series,i) {
|
paulo@89
|
8115 series.disabled = !state.active[i];
|
paulo@89
|
8116 });
|
paulo@89
|
8117 }
|
paulo@89
|
8118 };
|
paulo@89
|
8119
|
paulo@89
|
8120 function chart(selection) {
|
paulo@89
|
8121 renderWatch.reset();
|
paulo@89
|
8122 renderWatch.models(multibar);
|
paulo@89
|
8123 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
8124 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
8125
|
paulo@89
|
8126 selection.each(function(data) {
|
paulo@89
|
8127 var container = d3.select(this),
|
paulo@89
|
8128 that = this;
|
paulo@89
|
8129 nv.utils.initSVG(container);
|
paulo@89
|
8130 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
8131 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
8132
|
paulo@89
|
8133 chart.update = function() {
|
paulo@89
|
8134 if (duration === 0)
|
paulo@89
|
8135 container.call(chart);
|
paulo@89
|
8136 else
|
paulo@89
|
8137 container.transition()
|
paulo@89
|
8138 .duration(duration)
|
paulo@89
|
8139 .call(chart);
|
paulo@89
|
8140 };
|
paulo@89
|
8141 chart.container = this;
|
paulo@89
|
8142
|
paulo@89
|
8143 state
|
paulo@89
|
8144 .setter(stateSetter(data), chart.update)
|
paulo@89
|
8145 .getter(stateGetter(data))
|
paulo@89
|
8146 .update();
|
paulo@89
|
8147
|
paulo@89
|
8148 // DEPRECATED set state.disableddisabled
|
paulo@89
|
8149 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
8150
|
paulo@89
|
8151 if (!defaultState) {
|
paulo@89
|
8152 var key;
|
paulo@89
|
8153 defaultState = {};
|
paulo@89
|
8154 for (key in state) {
|
paulo@89
|
8155 if (state[key] instanceof Array)
|
paulo@89
|
8156 defaultState[key] = state[key].slice(0);
|
paulo@89
|
8157 else
|
paulo@89
|
8158 defaultState[key] = state[key];
|
paulo@89
|
8159 }
|
paulo@89
|
8160 }
|
paulo@89
|
8161
|
paulo@89
|
8162 // Display noData message if there's nothing to show.
|
paulo@89
|
8163 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
8164 nv.utils.noData(chart, container)
|
paulo@89
|
8165 return chart;
|
paulo@89
|
8166 } else {
|
paulo@89
|
8167 container.selectAll('.nv-noData').remove();
|
paulo@89
|
8168 }
|
paulo@89
|
8169
|
paulo@89
|
8170 // Setup Scales
|
paulo@89
|
8171 x = multibar.xScale();
|
paulo@89
|
8172 y = multibar.yScale();
|
paulo@89
|
8173
|
paulo@89
|
8174 // Setup containers and skeleton of chart
|
paulo@89
|
8175 var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
|
paulo@89
|
8176 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
|
paulo@89
|
8177 var g = wrap.select('g');
|
paulo@89
|
8178
|
paulo@89
|
8179 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
8180 gEnter.append('g').attr('class', 'nv-y nv-axis');
|
paulo@89
|
8181 gEnter.append('g').attr('class', 'nv-barsWrap');
|
paulo@89
|
8182 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
8183 gEnter.append('g').attr('class', 'nv-controlsWrap');
|
paulo@89
|
8184
|
paulo@89
|
8185 // Legend
|
paulo@89
|
8186 if (showLegend) {
|
paulo@89
|
8187 legend.width(availableWidth - controlWidth());
|
paulo@89
|
8188
|
paulo@89
|
8189 g.select('.nv-legendWrap')
|
paulo@89
|
8190 .datum(data)
|
paulo@89
|
8191 .call(legend);
|
paulo@89
|
8192
|
paulo@89
|
8193 if ( margin.top != legend.height()) {
|
paulo@89
|
8194 margin.top = legend.height();
|
paulo@89
|
8195 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
8196 }
|
paulo@89
|
8197
|
paulo@89
|
8198 g.select('.nv-legendWrap')
|
paulo@89
|
8199 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
|
paulo@89
|
8200 }
|
paulo@89
|
8201
|
paulo@89
|
8202 // Controls
|
paulo@89
|
8203 if (showControls) {
|
paulo@89
|
8204 var controlsData = [
|
paulo@89
|
8205 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
|
paulo@89
|
8206 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
|
paulo@89
|
8207 ];
|
paulo@89
|
8208
|
paulo@89
|
8209 controls.width(controlWidth()).color(['#444', '#444', '#444']);
|
paulo@89
|
8210 g.select('.nv-controlsWrap')
|
paulo@89
|
8211 .datum(controlsData)
|
paulo@89
|
8212 .attr('transform', 'translate(0,' + (-margin.top) +')')
|
paulo@89
|
8213 .call(controls);
|
paulo@89
|
8214 }
|
paulo@89
|
8215
|
paulo@89
|
8216 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
8217 if (rightAlignYAxis) {
|
paulo@89
|
8218 g.select(".nv-y.nv-axis")
|
paulo@89
|
8219 .attr("transform", "translate(" + availableWidth + ",0)");
|
paulo@89
|
8220 }
|
paulo@89
|
8221
|
paulo@89
|
8222 // Main Chart Component(s)
|
paulo@89
|
8223 multibar
|
paulo@89
|
8224 .disabled(data.map(function(series) { return series.disabled }))
|
paulo@89
|
8225 .width(availableWidth)
|
paulo@89
|
8226 .height(availableHeight)
|
paulo@89
|
8227 .color(data.map(function(d,i) {
|
paulo@89
|
8228 return d.color || color(d, i);
|
paulo@89
|
8229 }).filter(function(d,i) { return !data[i].disabled }));
|
paulo@89
|
8230
|
paulo@89
|
8231
|
paulo@89
|
8232 var barsWrap = g.select('.nv-barsWrap')
|
paulo@89
|
8233 .datum(data.filter(function(d) { return !d.disabled }));
|
paulo@89
|
8234
|
paulo@89
|
8235 barsWrap.call(multibar);
|
paulo@89
|
8236
|
paulo@89
|
8237 // Setup Axes
|
paulo@89
|
8238 if (showXAxis) {
|
paulo@89
|
8239 xAxis
|
paulo@89
|
8240 .scale(x)
|
paulo@89
|
8241 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
8242 .tickSize(-availableHeight, 0);
|
paulo@89
|
8243
|
paulo@89
|
8244 g.select('.nv-x.nv-axis')
|
paulo@89
|
8245 .attr('transform', 'translate(0,' + y.range()[0] + ')');
|
paulo@89
|
8246 g.select('.nv-x.nv-axis')
|
paulo@89
|
8247 .call(xAxis);
|
paulo@89
|
8248
|
paulo@89
|
8249 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
|
paulo@89
|
8250
|
paulo@89
|
8251 xTicks
|
paulo@89
|
8252 .selectAll('line, text')
|
paulo@89
|
8253 .style('opacity', 1)
|
paulo@89
|
8254
|
paulo@89
|
8255 if (staggerLabels) {
|
paulo@89
|
8256 var getTranslate = function(x,y) {
|
paulo@89
|
8257 return "translate(" + x + "," + y + ")";
|
paulo@89
|
8258 };
|
paulo@89
|
8259
|
paulo@89
|
8260 var staggerUp = 5, staggerDown = 17; //pixels to stagger by
|
paulo@89
|
8261 // Issue #140
|
paulo@89
|
8262 xTicks
|
paulo@89
|
8263 .selectAll("text")
|
paulo@89
|
8264 .attr('transform', function(d,i,j) {
|
paulo@89
|
8265 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
|
paulo@89
|
8266 });
|
paulo@89
|
8267
|
paulo@89
|
8268 var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
|
paulo@89
|
8269 g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
|
paulo@89
|
8270 .attr("transform", function(d,i) {
|
paulo@89
|
8271 return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
|
paulo@89
|
8272 });
|
paulo@89
|
8273 }
|
paulo@89
|
8274
|
paulo@89
|
8275 if (reduceXTicks)
|
paulo@89
|
8276 xTicks
|
paulo@89
|
8277 .filter(function(d,i) {
|
paulo@89
|
8278 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
|
paulo@89
|
8279 })
|
paulo@89
|
8280 .selectAll('text, line')
|
paulo@89
|
8281 .style('opacity', 0);
|
paulo@89
|
8282
|
paulo@89
|
8283 if(rotateLabels)
|
paulo@89
|
8284 xTicks
|
paulo@89
|
8285 .selectAll('.tick text')
|
paulo@89
|
8286 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
|
paulo@89
|
8287 .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
|
paulo@89
|
8288
|
paulo@89
|
8289 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
|
paulo@89
|
8290 .style('opacity', 1);
|
paulo@89
|
8291 }
|
paulo@89
|
8292
|
paulo@89
|
8293 if (showYAxis) {
|
paulo@89
|
8294 yAxis
|
paulo@89
|
8295 .scale(y)
|
paulo@89
|
8296 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
|
paulo@89
|
8297 .tickSize( -availableWidth, 0);
|
paulo@89
|
8298
|
paulo@89
|
8299 g.select('.nv-y.nv-axis')
|
paulo@89
|
8300 .call(yAxis);
|
paulo@89
|
8301 }
|
paulo@89
|
8302
|
paulo@89
|
8303 //============================================================
|
paulo@89
|
8304 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
8305 //------------------------------------------------------------
|
paulo@89
|
8306
|
paulo@89
|
8307 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
8308 for (var key in newState)
|
paulo@89
|
8309 state[key] = newState[key];
|
paulo@89
|
8310 dispatch.stateChange(state);
|
paulo@89
|
8311 chart.update();
|
paulo@89
|
8312 });
|
paulo@89
|
8313
|
paulo@89
|
8314 controls.dispatch.on('legendClick', function(d,i) {
|
paulo@89
|
8315 if (!d.disabled) return;
|
paulo@89
|
8316 controlsData = controlsData.map(function(s) {
|
paulo@89
|
8317 s.disabled = true;
|
paulo@89
|
8318 return s;
|
paulo@89
|
8319 });
|
paulo@89
|
8320 d.disabled = false;
|
paulo@89
|
8321
|
paulo@89
|
8322 switch (d.key) {
|
paulo@89
|
8323 case 'Grouped':
|
paulo@89
|
8324 case controlLabels.grouped:
|
paulo@89
|
8325 multibar.stacked(false);
|
paulo@89
|
8326 break;
|
paulo@89
|
8327 case 'Stacked':
|
paulo@89
|
8328 case controlLabels.stacked:
|
paulo@89
|
8329 multibar.stacked(true);
|
paulo@89
|
8330 break;
|
paulo@89
|
8331 }
|
paulo@89
|
8332
|
paulo@89
|
8333 state.stacked = multibar.stacked();
|
paulo@89
|
8334 dispatch.stateChange(state);
|
paulo@89
|
8335 chart.update();
|
paulo@89
|
8336 });
|
paulo@89
|
8337
|
paulo@89
|
8338 // Update chart from a state object passed to event handler
|
paulo@89
|
8339 dispatch.on('changeState', function(e) {
|
paulo@89
|
8340 if (typeof e.disabled !== 'undefined') {
|
paulo@89
|
8341 data.forEach(function(series,i) {
|
paulo@89
|
8342 series.disabled = e.disabled[i];
|
paulo@89
|
8343 });
|
paulo@89
|
8344 state.disabled = e.disabled;
|
paulo@89
|
8345 }
|
paulo@89
|
8346 if (typeof e.stacked !== 'undefined') {
|
paulo@89
|
8347 multibar.stacked(e.stacked);
|
paulo@89
|
8348 state.stacked = e.stacked;
|
paulo@89
|
8349 stacked = e.stacked;
|
paulo@89
|
8350 }
|
paulo@89
|
8351 chart.update();
|
paulo@89
|
8352 });
|
paulo@89
|
8353 });
|
paulo@89
|
8354
|
paulo@89
|
8355 renderWatch.renderEnd('multibarchart immediate');
|
paulo@89
|
8356 return chart;
|
paulo@89
|
8357 }
|
paulo@89
|
8358
|
paulo@89
|
8359 //============================================================
|
paulo@89
|
8360 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
8361 //------------------------------------------------------------
|
paulo@89
|
8362
|
paulo@89
|
8363 multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
8364 evt.value = chart.x()(evt.data);
|
paulo@89
|
8365 evt['series'] = {
|
paulo@89
|
8366 key: evt.data.key,
|
paulo@89
|
8367 value: chart.y()(evt.data),
|
paulo@89
|
8368 color: evt.color
|
paulo@89
|
8369 };
|
paulo@89
|
8370 tooltip.data(evt).hidden(false);
|
paulo@89
|
8371 });
|
paulo@89
|
8372
|
paulo@89
|
8373 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
8374 tooltip.hidden(true);
|
paulo@89
|
8375 });
|
paulo@89
|
8376
|
paulo@89
|
8377 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
8378 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
8379 });
|
paulo@89
|
8380
|
paulo@89
|
8381 //============================================================
|
paulo@89
|
8382 // Expose Public Variables
|
paulo@89
|
8383 //------------------------------------------------------------
|
paulo@89
|
8384
|
paulo@89
|
8385 // expose chart's sub-components
|
paulo@89
|
8386 chart.dispatch = dispatch;
|
paulo@89
|
8387 chart.multibar = multibar;
|
paulo@89
|
8388 chart.legend = legend;
|
paulo@89
|
8389 chart.controls = controls;
|
paulo@89
|
8390 chart.xAxis = xAxis;
|
paulo@89
|
8391 chart.yAxis = yAxis;
|
paulo@89
|
8392 chart.state = state;
|
paulo@89
|
8393 chart.tooltip = tooltip;
|
paulo@89
|
8394
|
paulo@89
|
8395 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
8396
|
paulo@89
|
8397 chart._options = Object.create({}, {
|
paulo@89
|
8398 // simple options, just get/set the necessary values
|
paulo@89
|
8399 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
8400 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
8401 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
8402 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
|
paulo@89
|
8403 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
|
paulo@89
|
8404 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
8405 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
8406 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
8407 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
8408 reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
|
paulo@89
|
8409 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
|
paulo@89
|
8410 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
|
paulo@89
|
8411
|
paulo@89
|
8412 // deprecated options
|
paulo@89
|
8413 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
8414 // deprecated after 1.7.1
|
paulo@89
|
8415 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
8416 tooltip.enabled(!!_);
|
paulo@89
|
8417 }},
|
paulo@89
|
8418 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
8419 // deprecated after 1.7.1
|
paulo@89
|
8420 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
8421 tooltip.contentGenerator(_);
|
paulo@89
|
8422 }},
|
paulo@89
|
8423
|
paulo@89
|
8424 // options that require extra logic in the setter
|
paulo@89
|
8425 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
8426 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
8427 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
8428 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
8429 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
8430 }},
|
paulo@89
|
8431 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
8432 duration = _;
|
paulo@89
|
8433 multibar.duration(duration);
|
paulo@89
|
8434 xAxis.duration(duration);
|
paulo@89
|
8435 yAxis.duration(duration);
|
paulo@89
|
8436 renderWatch.reset(duration);
|
paulo@89
|
8437 }},
|
paulo@89
|
8438 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
8439 color = nv.utils.getColor(_);
|
paulo@89
|
8440 legend.color(color);
|
paulo@89
|
8441 }},
|
paulo@89
|
8442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
|
paulo@89
|
8443 rightAlignYAxis = _;
|
paulo@89
|
8444 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
|
paulo@89
|
8445 }},
|
paulo@89
|
8446 barColor: {get: function(){return multibar.barColor;}, set: function(_){
|
paulo@89
|
8447 multibar.barColor(_);
|
paulo@89
|
8448 legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
|
paulo@89
|
8449 }}
|
paulo@89
|
8450 });
|
paulo@89
|
8451
|
paulo@89
|
8452 nv.utils.inheritOptions(chart, multibar);
|
paulo@89
|
8453 nv.utils.initOptions(chart);
|
paulo@89
|
8454
|
paulo@89
|
8455 return chart;
|
paulo@89
|
8456 };
|
paulo@89
|
8457
|
paulo@89
|
8458 nv.models.multiBarHorizontal = function() {
|
paulo@89
|
8459 "use strict";
|
paulo@89
|
8460
|
paulo@89
|
8461 //============================================================
|
paulo@89
|
8462 // Public Variables with Default Settings
|
paulo@89
|
8463 //------------------------------------------------------------
|
paulo@89
|
8464
|
paulo@89
|
8465 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
8466 , width = 960
|
paulo@89
|
8467 , height = 500
|
paulo@89
|
8468 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
8469 , container = null
|
paulo@89
|
8470 , x = d3.scale.ordinal()
|
paulo@89
|
8471 , y = d3.scale.linear()
|
paulo@89
|
8472 , getX = function(d) { return d.x }
|
paulo@89
|
8473 , getY = function(d) { return d.y }
|
paulo@89
|
8474 , getYerr = function(d) { return d.yErr }
|
paulo@89
|
8475 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
|
paulo@89
|
8476 , color = nv.utils.defaultColor()
|
paulo@89
|
8477 , barColor = null // adding the ability to set the color for each rather than the whole group
|
paulo@89
|
8478 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
|
paulo@89
|
8479 , stacked = false
|
paulo@89
|
8480 , showValues = false
|
paulo@89
|
8481 , showBarLabels = false
|
paulo@89
|
8482 , valuePadding = 60
|
paulo@89
|
8483 , groupSpacing = 0.1
|
paulo@89
|
8484 , valueFormat = d3.format(',.2f')
|
paulo@89
|
8485 , delay = 1200
|
paulo@89
|
8486 , xDomain
|
paulo@89
|
8487 , yDomain
|
paulo@89
|
8488 , xRange
|
paulo@89
|
8489 , yRange
|
paulo@89
|
8490 , duration = 250
|
paulo@89
|
8491 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
|
paulo@89
|
8492 ;
|
paulo@89
|
8493
|
paulo@89
|
8494 //============================================================
|
paulo@89
|
8495 // Private Variables
|
paulo@89
|
8496 //------------------------------------------------------------
|
paulo@89
|
8497
|
paulo@89
|
8498 var x0, y0; //used to store previous scales
|
paulo@89
|
8499 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
8500
|
paulo@89
|
8501 function chart(selection) {
|
paulo@89
|
8502 renderWatch.reset();
|
paulo@89
|
8503 selection.each(function(data) {
|
paulo@89
|
8504 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
8505 availableHeight = height - margin.top - margin.bottom;
|
paulo@89
|
8506
|
paulo@89
|
8507 container = d3.select(this);
|
paulo@89
|
8508 nv.utils.initSVG(container);
|
paulo@89
|
8509
|
paulo@89
|
8510 if (stacked)
|
paulo@89
|
8511 data = d3.layout.stack()
|
paulo@89
|
8512 .offset('zero')
|
paulo@89
|
8513 .values(function(d){ return d.values })
|
paulo@89
|
8514 .y(getY)
|
paulo@89
|
8515 (data);
|
paulo@89
|
8516
|
paulo@89
|
8517 //add series index and key to each data point for reference
|
paulo@89
|
8518 data.forEach(function(series, i) {
|
paulo@89
|
8519 series.values.forEach(function(point) {
|
paulo@89
|
8520 point.series = i;
|
paulo@89
|
8521 point.key = series.key;
|
paulo@89
|
8522 });
|
paulo@89
|
8523 });
|
paulo@89
|
8524
|
paulo@89
|
8525 // HACK for negative value stacking
|
paulo@89
|
8526 if (stacked)
|
paulo@89
|
8527 data[0].values.map(function(d,i) {
|
paulo@89
|
8528 var posBase = 0, negBase = 0;
|
paulo@89
|
8529 data.map(function(d) {
|
paulo@89
|
8530 var f = d.values[i]
|
paulo@89
|
8531 f.size = Math.abs(f.y);
|
paulo@89
|
8532 if (f.y<0) {
|
paulo@89
|
8533 f.y1 = negBase - f.size;
|
paulo@89
|
8534 negBase = negBase - f.size;
|
paulo@89
|
8535 } else
|
paulo@89
|
8536 {
|
paulo@89
|
8537 f.y1 = posBase;
|
paulo@89
|
8538 posBase = posBase + f.size;
|
paulo@89
|
8539 }
|
paulo@89
|
8540 });
|
paulo@89
|
8541 });
|
paulo@89
|
8542
|
paulo@89
|
8543 // Setup Scales
|
paulo@89
|
8544 // remap and flatten the data for use in calculating the scales' domains
|
paulo@89
|
8545 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
|
paulo@89
|
8546 data.map(function(d) {
|
paulo@89
|
8547 return d.values.map(function(d,i) {
|
paulo@89
|
8548 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
|
paulo@89
|
8549 })
|
paulo@89
|
8550 });
|
paulo@89
|
8551
|
paulo@89
|
8552 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
|
paulo@89
|
8553 .rangeBands(xRange || [0, availableHeight], groupSpacing);
|
paulo@89
|
8554
|
paulo@89
|
8555 y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY)))
|
paulo@89
|
8556
|
paulo@89
|
8557 if (showValues && !stacked)
|
paulo@89
|
8558 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
|
paulo@89
|
8559 else
|
paulo@89
|
8560 y.range(yRange || [0, availableWidth]);
|
paulo@89
|
8561
|
paulo@89
|
8562 x0 = x0 || x;
|
paulo@89
|
8563 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
|
paulo@89
|
8564
|
paulo@89
|
8565 // Setup containers and skeleton of chart
|
paulo@89
|
8566 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
|
paulo@89
|
8567 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
|
paulo@89
|
8568 var defsEnter = wrapEnter.append('defs');
|
paulo@89
|
8569 var gEnter = wrapEnter.append('g');
|
paulo@89
|
8570 var g = wrap.select('g');
|
paulo@89
|
8571
|
paulo@89
|
8572 gEnter.append('g').attr('class', 'nv-groups');
|
paulo@89
|
8573 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
8574
|
paulo@89
|
8575 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
|
paulo@89
|
8576 .data(function(d) { return d }, function(d,i) { return i });
|
paulo@89
|
8577 groups.enter().append('g')
|
paulo@89
|
8578 .style('stroke-opacity', 1e-6)
|
paulo@89
|
8579 .style('fill-opacity', 1e-6);
|
paulo@89
|
8580 groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
|
paulo@89
|
8581 .style('stroke-opacity', 1e-6)
|
paulo@89
|
8582 .style('fill-opacity', 1e-6)
|
paulo@89
|
8583 .remove();
|
paulo@89
|
8584 groups
|
paulo@89
|
8585 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
|
paulo@89
|
8586 .classed('hover', function(d) { return d.hover })
|
paulo@89
|
8587 .style('fill', function(d,i){ return color(d, i) })
|
paulo@89
|
8588 .style('stroke', function(d,i){ return color(d, i) });
|
paulo@89
|
8589 groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
|
paulo@89
|
8590 .style('stroke-opacity', 1)
|
paulo@89
|
8591 .style('fill-opacity', .75);
|
paulo@89
|
8592
|
paulo@89
|
8593 var bars = groups.selectAll('g.nv-bar')
|
paulo@89
|
8594 .data(function(d) { return d.values });
|
paulo@89
|
8595 bars.exit().remove();
|
paulo@89
|
8596
|
paulo@89
|
8597 var barsEnter = bars.enter().append('g')
|
paulo@89
|
8598 .attr('transform', function(d,i,j) {
|
paulo@89
|
8599 return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
|
paulo@89
|
8600 });
|
paulo@89
|
8601
|
paulo@89
|
8602 barsEnter.append('rect')
|
paulo@89
|
8603 .attr('width', 0)
|
paulo@89
|
8604 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
|
paulo@89
|
8605
|
paulo@89
|
8606 bars
|
paulo@89
|
8607 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
|
paulo@89
|
8608 d3.select(this).classed('hover', true);
|
paulo@89
|
8609 dispatch.elementMouseover({
|
paulo@89
|
8610 data: d,
|
paulo@89
|
8611 index: i,
|
paulo@89
|
8612 color: d3.select(this).style("fill")
|
paulo@89
|
8613 });
|
paulo@89
|
8614 })
|
paulo@89
|
8615 .on('mouseout', function(d,i) {
|
paulo@89
|
8616 d3.select(this).classed('hover', false);
|
paulo@89
|
8617 dispatch.elementMouseout({
|
paulo@89
|
8618 data: d,
|
paulo@89
|
8619 index: i,
|
paulo@89
|
8620 color: d3.select(this).style("fill")
|
paulo@89
|
8621 });
|
paulo@89
|
8622 })
|
paulo@89
|
8623 .on('mouseout', function(d,i) {
|
paulo@89
|
8624 dispatch.elementMouseout({
|
paulo@89
|
8625 data: d,
|
paulo@89
|
8626 index: i,
|
paulo@89
|
8627 color: d3.select(this).style("fill")
|
paulo@89
|
8628 });
|
paulo@89
|
8629 })
|
paulo@89
|
8630 .on('mousemove', function(d,i) {
|
paulo@89
|
8631 dispatch.elementMousemove({
|
paulo@89
|
8632 data: d,
|
paulo@89
|
8633 index: i,
|
paulo@89
|
8634 color: d3.select(this).style("fill")
|
paulo@89
|
8635 });
|
paulo@89
|
8636 })
|
paulo@89
|
8637 .on('click', function(d,i) {
|
paulo@89
|
8638 dispatch.elementClick({
|
paulo@89
|
8639 data: d,
|
paulo@89
|
8640 index: i,
|
paulo@89
|
8641 color: d3.select(this).style("fill")
|
paulo@89
|
8642 });
|
paulo@89
|
8643 d3.event.stopPropagation();
|
paulo@89
|
8644 })
|
paulo@89
|
8645 .on('dblclick', function(d,i) {
|
paulo@89
|
8646 dispatch.elementDblClick({
|
paulo@89
|
8647 data: d,
|
paulo@89
|
8648 index: i,
|
paulo@89
|
8649 color: d3.select(this).style("fill")
|
paulo@89
|
8650 });
|
paulo@89
|
8651 d3.event.stopPropagation();
|
paulo@89
|
8652 });
|
paulo@89
|
8653
|
paulo@89
|
8654 if (getYerr(data[0],0)) {
|
paulo@89
|
8655 barsEnter.append('polyline');
|
paulo@89
|
8656
|
paulo@89
|
8657 bars.select('polyline')
|
paulo@89
|
8658 .attr('fill', 'none')
|
paulo@89
|
8659 .attr('points', function(d,i) {
|
paulo@89
|
8660 var xerr = getYerr(d,i)
|
paulo@89
|
8661 , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
|
paulo@89
|
8662 xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
|
paulo@89
|
8663 xerr = xerr.map(function(e) { return y(e) - y(0); });
|
paulo@89
|
8664 var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
|
paulo@89
|
8665 return a.map(function (path) { return path.join(',') }).join(' ');
|
paulo@89
|
8666 })
|
paulo@89
|
8667 .attr('transform', function(d,i) {
|
paulo@89
|
8668 var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
|
paulo@89
|
8669 return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
|
paulo@89
|
8670 });
|
paulo@89
|
8671 }
|
paulo@89
|
8672
|
paulo@89
|
8673 barsEnter.append('text');
|
paulo@89
|
8674
|
paulo@89
|
8675 if (showValues && !stacked) {
|
paulo@89
|
8676 bars.select('text')
|
paulo@89
|
8677 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
|
paulo@89
|
8678 .attr('y', x.rangeBand() / (data.length * 2))
|
paulo@89
|
8679 .attr('dy', '.32em')
|
paulo@89
|
8680 .text(function(d,i) {
|
paulo@89
|
8681 var t = valueFormat(getY(d,i))
|
paulo@89
|
8682 , yerr = getYerr(d,i);
|
paulo@89
|
8683 if (yerr === undefined)
|
paulo@89
|
8684 return t;
|
paulo@89
|
8685 if (!yerr.length)
|
paulo@89
|
8686 return t + '±' + valueFormat(Math.abs(yerr));
|
paulo@89
|
8687 return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
|
paulo@89
|
8688 });
|
paulo@89
|
8689 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
|
paulo@89
|
8690 .select('text')
|
paulo@89
|
8691 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
|
paulo@89
|
8692 } else {
|
paulo@89
|
8693 bars.selectAll('text').text('');
|
paulo@89
|
8694 }
|
paulo@89
|
8695
|
paulo@89
|
8696 if (showBarLabels && !stacked) {
|
paulo@89
|
8697 barsEnter.append('text').classed('nv-bar-label',true);
|
paulo@89
|
8698 bars.select('text.nv-bar-label')
|
paulo@89
|
8699 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
|
paulo@89
|
8700 .attr('y', x.rangeBand() / (data.length * 2))
|
paulo@89
|
8701 .attr('dy', '.32em')
|
paulo@89
|
8702 .text(function(d,i) { return getX(d,i) });
|
paulo@89
|
8703 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
|
paulo@89
|
8704 .select('text.nv-bar-label')
|
paulo@89
|
8705 .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
|
paulo@89
|
8706 }
|
paulo@89
|
8707 else {
|
paulo@89
|
8708 bars.selectAll('text.nv-bar-label').text('');
|
paulo@89
|
8709 }
|
paulo@89
|
8710
|
paulo@89
|
8711 bars
|
paulo@89
|
8712 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
|
paulo@89
|
8713
|
paulo@89
|
8714 if (barColor) {
|
paulo@89
|
8715 if (!disabled) disabled = data.map(function() { return true });
|
paulo@89
|
8716 bars
|
paulo@89
|
8717 .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
|
paulo@89
|
8718 .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
|
paulo@89
|
8719 }
|
paulo@89
|
8720
|
paulo@89
|
8721 if (stacked)
|
paulo@89
|
8722 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
|
paulo@89
|
8723 .attr('transform', function(d,i) {
|
paulo@89
|
8724 return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
|
paulo@89
|
8725 })
|
paulo@89
|
8726 .select('rect')
|
paulo@89
|
8727 .attr('width', function(d,i) {
|
paulo@89
|
8728 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
|
paulo@89
|
8729 })
|
paulo@89
|
8730 .attr('height', x.rangeBand() );
|
paulo@89
|
8731 else
|
paulo@89
|
8732 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
|
paulo@89
|
8733 .attr('transform', function(d,i) {
|
paulo@89
|
8734 //TODO: stacked must be all positive or all negative, not both?
|
paulo@89
|
8735 return 'translate(' +
|
paulo@89
|
8736 (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
|
paulo@89
|
8737 + ',' +
|
paulo@89
|
8738 (d.series * x.rangeBand() / data.length
|
paulo@89
|
8739 +
|
paulo@89
|
8740 x(getX(d,i)) )
|
paulo@89
|
8741 + ')'
|
paulo@89
|
8742 })
|
paulo@89
|
8743 .select('rect')
|
paulo@89
|
8744 .attr('height', x.rangeBand() / data.length )
|
paulo@89
|
8745 .attr('width', function(d,i) {
|
paulo@89
|
8746 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
|
paulo@89
|
8747 });
|
paulo@89
|
8748
|
paulo@89
|
8749 //store old scales for use in transitions on update
|
paulo@89
|
8750 x0 = x.copy();
|
paulo@89
|
8751 y0 = y.copy();
|
paulo@89
|
8752
|
paulo@89
|
8753 });
|
paulo@89
|
8754
|
paulo@89
|
8755 renderWatch.renderEnd('multibarHorizontal immediate');
|
paulo@89
|
8756 return chart;
|
paulo@89
|
8757 }
|
paulo@89
|
8758
|
paulo@89
|
8759 //============================================================
|
paulo@89
|
8760 // Expose Public Variables
|
paulo@89
|
8761 //------------------------------------------------------------
|
paulo@89
|
8762
|
paulo@89
|
8763 chart.dispatch = dispatch;
|
paulo@89
|
8764
|
paulo@89
|
8765 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
8766
|
paulo@89
|
8767 chart._options = Object.create({}, {
|
paulo@89
|
8768 // simple options, just get/set the necessary values
|
paulo@89
|
8769 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
8770 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
8771 x: {get: function(){return getX;}, set: function(_){getX=_;}},
|
paulo@89
|
8772 y: {get: function(){return getY;}, set: function(_){getY=_;}},
|
paulo@89
|
8773 yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
|
paulo@89
|
8774 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
8775 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
8776 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
8777 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
8778 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
8779 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
8780 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
|
paulo@89
|
8781 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
|
paulo@89
|
8782 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
|
paulo@89
|
8783 // this shows the group name, seems pointless?
|
paulo@89
|
8784 //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
|
paulo@89
|
8785 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
|
paulo@89
|
8786 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
8787 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
|
paulo@89
|
8788 valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
|
paulo@89
|
8789 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
|
paulo@89
|
8790
|
paulo@89
|
8791 // options that require extra logic in the setter
|
paulo@89
|
8792 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
8793 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
8794 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
8795 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
8796 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
8797 }},
|
paulo@89
|
8798 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
8799 duration = _;
|
paulo@89
|
8800 renderWatch.reset(duration);
|
paulo@89
|
8801 }},
|
paulo@89
|
8802 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
8803 color = nv.utils.getColor(_);
|
paulo@89
|
8804 }},
|
paulo@89
|
8805 barColor: {get: function(){return barColor;}, set: function(_){
|
paulo@89
|
8806 barColor = _ ? nv.utils.getColor(_) : null;
|
paulo@89
|
8807 }}
|
paulo@89
|
8808 });
|
paulo@89
|
8809
|
paulo@89
|
8810 nv.utils.initOptions(chart);
|
paulo@89
|
8811
|
paulo@89
|
8812 return chart;
|
paulo@89
|
8813 };
|
paulo@89
|
8814
|
paulo@89
|
8815 nv.models.multiBarHorizontalChart = function() {
|
paulo@89
|
8816 "use strict";
|
paulo@89
|
8817
|
paulo@89
|
8818 //============================================================
|
paulo@89
|
8819 // Public Variables with Default Settings
|
paulo@89
|
8820 //------------------------------------------------------------
|
paulo@89
|
8821
|
paulo@89
|
8822 var multibar = nv.models.multiBarHorizontal()
|
paulo@89
|
8823 , xAxis = nv.models.axis()
|
paulo@89
|
8824 , yAxis = nv.models.axis()
|
paulo@89
|
8825 , legend = nv.models.legend().height(30)
|
paulo@89
|
8826 , controls = nv.models.legend().height(30)
|
paulo@89
|
8827 , tooltip = nv.models.tooltip()
|
paulo@89
|
8828 ;
|
paulo@89
|
8829
|
paulo@89
|
8830 var margin = {top: 30, right: 20, bottom: 50, left: 60}
|
paulo@89
|
8831 , width = null
|
paulo@89
|
8832 , height = null
|
paulo@89
|
8833 , color = nv.utils.defaultColor()
|
paulo@89
|
8834 , showControls = true
|
paulo@89
|
8835 , controlLabels = {}
|
paulo@89
|
8836 , showLegend = true
|
paulo@89
|
8837 , showXAxis = true
|
paulo@89
|
8838 , showYAxis = true
|
paulo@89
|
8839 , stacked = false
|
paulo@89
|
8840 , x //can be accessed via chart.xScale()
|
paulo@89
|
8841 , y //can be accessed via chart.yScale()
|
paulo@89
|
8842 , state = nv.utils.state()
|
paulo@89
|
8843 , defaultState = null
|
paulo@89
|
8844 , noData = null
|
paulo@89
|
8845 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
|
paulo@89
|
8846 , controlWidth = function() { return showControls ? 180 : 0 }
|
paulo@89
|
8847 , duration = 250
|
paulo@89
|
8848 ;
|
paulo@89
|
8849
|
paulo@89
|
8850 state.stacked = false; // DEPRECATED Maintained for backward compatibility
|
paulo@89
|
8851
|
paulo@89
|
8852 multibar.stacked(stacked);
|
paulo@89
|
8853
|
paulo@89
|
8854 xAxis
|
paulo@89
|
8855 .orient('left')
|
paulo@89
|
8856 .tickPadding(5)
|
paulo@89
|
8857 .showMaxMin(false)
|
paulo@89
|
8858 .tickFormat(function(d) { return d })
|
paulo@89
|
8859 ;
|
paulo@89
|
8860 yAxis
|
paulo@89
|
8861 .orient('bottom')
|
paulo@89
|
8862 .tickFormat(d3.format(',.1f'))
|
paulo@89
|
8863 ;
|
paulo@89
|
8864
|
paulo@89
|
8865 tooltip
|
paulo@89
|
8866 .duration(0)
|
paulo@89
|
8867 .valueFormatter(function(d, i) {
|
paulo@89
|
8868 return yAxis.tickFormat()(d, i);
|
paulo@89
|
8869 })
|
paulo@89
|
8870 .headerFormatter(function(d, i) {
|
paulo@89
|
8871 return xAxis.tickFormat()(d, i);
|
paulo@89
|
8872 });
|
paulo@89
|
8873
|
paulo@89
|
8874 controls.updateState(false);
|
paulo@89
|
8875
|
paulo@89
|
8876 //============================================================
|
paulo@89
|
8877 // Private Variables
|
paulo@89
|
8878 //------------------------------------------------------------
|
paulo@89
|
8879
|
paulo@89
|
8880 var stateGetter = function(data) {
|
paulo@89
|
8881 return function(){
|
paulo@89
|
8882 return {
|
paulo@89
|
8883 active: data.map(function(d) { return !d.disabled }),
|
paulo@89
|
8884 stacked: stacked
|
paulo@89
|
8885 };
|
paulo@89
|
8886 }
|
paulo@89
|
8887 };
|
paulo@89
|
8888
|
paulo@89
|
8889 var stateSetter = function(data) {
|
paulo@89
|
8890 return function(state) {
|
paulo@89
|
8891 if (state.stacked !== undefined)
|
paulo@89
|
8892 stacked = state.stacked;
|
paulo@89
|
8893 if (state.active !== undefined)
|
paulo@89
|
8894 data.forEach(function(series,i) {
|
paulo@89
|
8895 series.disabled = !state.active[i];
|
paulo@89
|
8896 });
|
paulo@89
|
8897 }
|
paulo@89
|
8898 };
|
paulo@89
|
8899
|
paulo@89
|
8900 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
8901
|
paulo@89
|
8902 function chart(selection) {
|
paulo@89
|
8903 renderWatch.reset();
|
paulo@89
|
8904 renderWatch.models(multibar);
|
paulo@89
|
8905 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
8906 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
8907
|
paulo@89
|
8908 selection.each(function(data) {
|
paulo@89
|
8909 var container = d3.select(this),
|
paulo@89
|
8910 that = this;
|
paulo@89
|
8911 nv.utils.initSVG(container);
|
paulo@89
|
8912 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
8913 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
8914
|
paulo@89
|
8915 chart.update = function() { container.transition().duration(duration).call(chart) };
|
paulo@89
|
8916 chart.container = this;
|
paulo@89
|
8917
|
paulo@89
|
8918 stacked = multibar.stacked();
|
paulo@89
|
8919
|
paulo@89
|
8920 state
|
paulo@89
|
8921 .setter(stateSetter(data), chart.update)
|
paulo@89
|
8922 .getter(stateGetter(data))
|
paulo@89
|
8923 .update();
|
paulo@89
|
8924
|
paulo@89
|
8925 // DEPRECATED set state.disableddisabled
|
paulo@89
|
8926 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
8927
|
paulo@89
|
8928 if (!defaultState) {
|
paulo@89
|
8929 var key;
|
paulo@89
|
8930 defaultState = {};
|
paulo@89
|
8931 for (key in state) {
|
paulo@89
|
8932 if (state[key] instanceof Array)
|
paulo@89
|
8933 defaultState[key] = state[key].slice(0);
|
paulo@89
|
8934 else
|
paulo@89
|
8935 defaultState[key] = state[key];
|
paulo@89
|
8936 }
|
paulo@89
|
8937 }
|
paulo@89
|
8938
|
paulo@89
|
8939 // Display No Data message if there's nothing to show.
|
paulo@89
|
8940 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
8941 nv.utils.noData(chart, container)
|
paulo@89
|
8942 return chart;
|
paulo@89
|
8943 } else {
|
paulo@89
|
8944 container.selectAll('.nv-noData').remove();
|
paulo@89
|
8945 }
|
paulo@89
|
8946
|
paulo@89
|
8947 // Setup Scales
|
paulo@89
|
8948 x = multibar.xScale();
|
paulo@89
|
8949 y = multibar.yScale();
|
paulo@89
|
8950
|
paulo@89
|
8951 // Setup containers and skeleton of chart
|
paulo@89
|
8952 var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
|
paulo@89
|
8953 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
|
paulo@89
|
8954 var g = wrap.select('g');
|
paulo@89
|
8955
|
paulo@89
|
8956 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
8957 gEnter.append('g').attr('class', 'nv-y nv-axis')
|
paulo@89
|
8958 .append('g').attr('class', 'nv-zeroLine')
|
paulo@89
|
8959 .append('line');
|
paulo@89
|
8960 gEnter.append('g').attr('class', 'nv-barsWrap');
|
paulo@89
|
8961 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
8962 gEnter.append('g').attr('class', 'nv-controlsWrap');
|
paulo@89
|
8963
|
paulo@89
|
8964 // Legend
|
paulo@89
|
8965 if (showLegend) {
|
paulo@89
|
8966 legend.width(availableWidth - controlWidth());
|
paulo@89
|
8967
|
paulo@89
|
8968 g.select('.nv-legendWrap')
|
paulo@89
|
8969 .datum(data)
|
paulo@89
|
8970 .call(legend);
|
paulo@89
|
8971
|
paulo@89
|
8972 if ( margin.top != legend.height()) {
|
paulo@89
|
8973 margin.top = legend.height();
|
paulo@89
|
8974 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
8975 }
|
paulo@89
|
8976
|
paulo@89
|
8977 g.select('.nv-legendWrap')
|
paulo@89
|
8978 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
|
paulo@89
|
8979 }
|
paulo@89
|
8980
|
paulo@89
|
8981 // Controls
|
paulo@89
|
8982 if (showControls) {
|
paulo@89
|
8983 var controlsData = [
|
paulo@89
|
8984 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
|
paulo@89
|
8985 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
|
paulo@89
|
8986 ];
|
paulo@89
|
8987
|
paulo@89
|
8988 controls.width(controlWidth()).color(['#444', '#444', '#444']);
|
paulo@89
|
8989 g.select('.nv-controlsWrap')
|
paulo@89
|
8990 .datum(controlsData)
|
paulo@89
|
8991 .attr('transform', 'translate(0,' + (-margin.top) +')')
|
paulo@89
|
8992 .call(controls);
|
paulo@89
|
8993 }
|
paulo@89
|
8994
|
paulo@89
|
8995 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
8996
|
paulo@89
|
8997 // Main Chart Component(s)
|
paulo@89
|
8998 multibar
|
paulo@89
|
8999 .disabled(data.map(function(series) { return series.disabled }))
|
paulo@89
|
9000 .width(availableWidth)
|
paulo@89
|
9001 .height(availableHeight)
|
paulo@89
|
9002 .color(data.map(function(d,i) {
|
paulo@89
|
9003 return d.color || color(d, i);
|
paulo@89
|
9004 }).filter(function(d,i) { return !data[i].disabled }));
|
paulo@89
|
9005
|
paulo@89
|
9006 var barsWrap = g.select('.nv-barsWrap')
|
paulo@89
|
9007 .datum(data.filter(function(d) { return !d.disabled }));
|
paulo@89
|
9008
|
paulo@89
|
9009 barsWrap.transition().call(multibar);
|
paulo@89
|
9010
|
paulo@89
|
9011 // Setup Axes
|
paulo@89
|
9012 if (showXAxis) {
|
paulo@89
|
9013 xAxis
|
paulo@89
|
9014 .scale(x)
|
paulo@89
|
9015 ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
|
paulo@89
|
9016 .tickSize(-availableWidth, 0);
|
paulo@89
|
9017
|
paulo@89
|
9018 g.select('.nv-x.nv-axis').call(xAxis);
|
paulo@89
|
9019
|
paulo@89
|
9020 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
|
paulo@89
|
9021
|
paulo@89
|
9022 xTicks
|
paulo@89
|
9023 .selectAll('line, text');
|
paulo@89
|
9024 }
|
paulo@89
|
9025
|
paulo@89
|
9026 if (showYAxis) {
|
paulo@89
|
9027 yAxis
|
paulo@89
|
9028 .scale(y)
|
paulo@89
|
9029 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
9030 .tickSize( -availableHeight, 0);
|
paulo@89
|
9031
|
paulo@89
|
9032 g.select('.nv-y.nv-axis')
|
paulo@89
|
9033 .attr('transform', 'translate(0,' + availableHeight + ')');
|
paulo@89
|
9034 g.select('.nv-y.nv-axis').call(yAxis);
|
paulo@89
|
9035 }
|
paulo@89
|
9036
|
paulo@89
|
9037 // Zero line
|
paulo@89
|
9038 g.select(".nv-zeroLine line")
|
paulo@89
|
9039 .attr("x1", y(0))
|
paulo@89
|
9040 .attr("x2", y(0))
|
paulo@89
|
9041 .attr("y1", 0)
|
paulo@89
|
9042 .attr("y2", -availableHeight)
|
paulo@89
|
9043 ;
|
paulo@89
|
9044
|
paulo@89
|
9045 //============================================================
|
paulo@89
|
9046 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
9047 //------------------------------------------------------------
|
paulo@89
|
9048
|
paulo@89
|
9049 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
9050 for (var key in newState)
|
paulo@89
|
9051 state[key] = newState[key];
|
paulo@89
|
9052 dispatch.stateChange(state);
|
paulo@89
|
9053 chart.update();
|
paulo@89
|
9054 });
|
paulo@89
|
9055
|
paulo@89
|
9056 controls.dispatch.on('legendClick', function(d,i) {
|
paulo@89
|
9057 if (!d.disabled) return;
|
paulo@89
|
9058 controlsData = controlsData.map(function(s) {
|
paulo@89
|
9059 s.disabled = true;
|
paulo@89
|
9060 return s;
|
paulo@89
|
9061 });
|
paulo@89
|
9062 d.disabled = false;
|
paulo@89
|
9063
|
paulo@89
|
9064 switch (d.key) {
|
paulo@89
|
9065 case 'Grouped':
|
paulo@89
|
9066 multibar.stacked(false);
|
paulo@89
|
9067 break;
|
paulo@89
|
9068 case 'Stacked':
|
paulo@89
|
9069 multibar.stacked(true);
|
paulo@89
|
9070 break;
|
paulo@89
|
9071 }
|
paulo@89
|
9072
|
paulo@89
|
9073 state.stacked = multibar.stacked();
|
paulo@89
|
9074 dispatch.stateChange(state);
|
paulo@89
|
9075 stacked = multibar.stacked();
|
paulo@89
|
9076
|
paulo@89
|
9077 chart.update();
|
paulo@89
|
9078 });
|
paulo@89
|
9079
|
paulo@89
|
9080 // Update chart from a state object passed to event handler
|
paulo@89
|
9081 dispatch.on('changeState', function(e) {
|
paulo@89
|
9082
|
paulo@89
|
9083 if (typeof e.disabled !== 'undefined') {
|
paulo@89
|
9084 data.forEach(function(series,i) {
|
paulo@89
|
9085 series.disabled = e.disabled[i];
|
paulo@89
|
9086 });
|
paulo@89
|
9087
|
paulo@89
|
9088 state.disabled = e.disabled;
|
paulo@89
|
9089 }
|
paulo@89
|
9090
|
paulo@89
|
9091 if (typeof e.stacked !== 'undefined') {
|
paulo@89
|
9092 multibar.stacked(e.stacked);
|
paulo@89
|
9093 state.stacked = e.stacked;
|
paulo@89
|
9094 stacked = e.stacked;
|
paulo@89
|
9095 }
|
paulo@89
|
9096
|
paulo@89
|
9097 chart.update();
|
paulo@89
|
9098 });
|
paulo@89
|
9099 });
|
paulo@89
|
9100 renderWatch.renderEnd('multibar horizontal chart immediate');
|
paulo@89
|
9101 return chart;
|
paulo@89
|
9102 }
|
paulo@89
|
9103
|
paulo@89
|
9104 //============================================================
|
paulo@89
|
9105 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
9106 //------------------------------------------------------------
|
paulo@89
|
9107
|
paulo@89
|
9108 multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
9109 evt.value = chart.x()(evt.data);
|
paulo@89
|
9110 evt['series'] = {
|
paulo@89
|
9111 key: evt.data.key,
|
paulo@89
|
9112 value: chart.y()(evt.data),
|
paulo@89
|
9113 color: evt.color
|
paulo@89
|
9114 };
|
paulo@89
|
9115 tooltip.data(evt).hidden(false);
|
paulo@89
|
9116 });
|
paulo@89
|
9117
|
paulo@89
|
9118 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
9119 tooltip.hidden(true);
|
paulo@89
|
9120 });
|
paulo@89
|
9121
|
paulo@89
|
9122 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
9123 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
9124 });
|
paulo@89
|
9125
|
paulo@89
|
9126 //============================================================
|
paulo@89
|
9127 // Expose Public Variables
|
paulo@89
|
9128 //------------------------------------------------------------
|
paulo@89
|
9129
|
paulo@89
|
9130 // expose chart's sub-components
|
paulo@89
|
9131 chart.dispatch = dispatch;
|
paulo@89
|
9132 chart.multibar = multibar;
|
paulo@89
|
9133 chart.legend = legend;
|
paulo@89
|
9134 chart.controls = controls;
|
paulo@89
|
9135 chart.xAxis = xAxis;
|
paulo@89
|
9136 chart.yAxis = yAxis;
|
paulo@89
|
9137 chart.state = state;
|
paulo@89
|
9138 chart.tooltip = tooltip;
|
paulo@89
|
9139
|
paulo@89
|
9140 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
9141
|
paulo@89
|
9142 chart._options = Object.create({}, {
|
paulo@89
|
9143 // simple options, just get/set the necessary values
|
paulo@89
|
9144 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
9145 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
9146 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
9147 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
|
paulo@89
|
9148 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
|
paulo@89
|
9149 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
9150 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
9151 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
9152 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
9153
|
paulo@89
|
9154 // deprecated options
|
paulo@89
|
9155 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
9156 // deprecated after 1.7.1
|
paulo@89
|
9157 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
9158 tooltip.enabled(!!_);
|
paulo@89
|
9159 }},
|
paulo@89
|
9160 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
9161 // deprecated after 1.7.1
|
paulo@89
|
9162 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
9163 tooltip.contentGenerator(_);
|
paulo@89
|
9164 }},
|
paulo@89
|
9165
|
paulo@89
|
9166 // options that require extra logic in the setter
|
paulo@89
|
9167 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
9168 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
9169 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
9170 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
9171 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
9172 }},
|
paulo@89
|
9173 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
9174 duration = _;
|
paulo@89
|
9175 renderWatch.reset(duration);
|
paulo@89
|
9176 multibar.duration(duration);
|
paulo@89
|
9177 xAxis.duration(duration);
|
paulo@89
|
9178 yAxis.duration(duration);
|
paulo@89
|
9179 }},
|
paulo@89
|
9180 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
9181 color = nv.utils.getColor(_);
|
paulo@89
|
9182 legend.color(color);
|
paulo@89
|
9183 }},
|
paulo@89
|
9184 barColor: {get: function(){return multibar.barColor;}, set: function(_){
|
paulo@89
|
9185 multibar.barColor(_);
|
paulo@89
|
9186 legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
|
paulo@89
|
9187 }}
|
paulo@89
|
9188 });
|
paulo@89
|
9189
|
paulo@89
|
9190 nv.utils.inheritOptions(chart, multibar);
|
paulo@89
|
9191 nv.utils.initOptions(chart);
|
paulo@89
|
9192
|
paulo@89
|
9193 return chart;
|
paulo@89
|
9194 };
|
paulo@89
|
9195 nv.models.multiChart = function() {
|
paulo@89
|
9196 "use strict";
|
paulo@89
|
9197
|
paulo@89
|
9198 //============================================================
|
paulo@89
|
9199 // Public Variables with Default Settings
|
paulo@89
|
9200 //------------------------------------------------------------
|
paulo@89
|
9201
|
paulo@89
|
9202 var margin = {top: 30, right: 20, bottom: 50, left: 60},
|
paulo@89
|
9203 color = nv.utils.defaultColor(),
|
paulo@89
|
9204 width = null,
|
paulo@89
|
9205 height = null,
|
paulo@89
|
9206 showLegend = true,
|
paulo@89
|
9207 noData = null,
|
paulo@89
|
9208 yDomain1,
|
paulo@89
|
9209 yDomain2,
|
paulo@89
|
9210 getX = function(d) { return d.x },
|
paulo@89
|
9211 getY = function(d) { return d.y},
|
paulo@89
|
9212 interpolate = 'monotone',
|
paulo@89
|
9213 useVoronoi = true
|
paulo@89
|
9214 ;
|
paulo@89
|
9215
|
paulo@89
|
9216 //============================================================
|
paulo@89
|
9217 // Private Variables
|
paulo@89
|
9218 //------------------------------------------------------------
|
paulo@89
|
9219
|
paulo@89
|
9220 var x = d3.scale.linear(),
|
paulo@89
|
9221 yScale1 = d3.scale.linear(),
|
paulo@89
|
9222 yScale2 = d3.scale.linear(),
|
paulo@89
|
9223
|
paulo@89
|
9224 lines1 = nv.models.line().yScale(yScale1),
|
paulo@89
|
9225 lines2 = nv.models.line().yScale(yScale2),
|
paulo@89
|
9226
|
paulo@89
|
9227 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
|
paulo@89
|
9228 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
|
paulo@89
|
9229
|
paulo@89
|
9230 stack1 = nv.models.stackedArea().yScale(yScale1),
|
paulo@89
|
9231 stack2 = nv.models.stackedArea().yScale(yScale2),
|
paulo@89
|
9232
|
paulo@89
|
9233 xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
|
paulo@89
|
9234 yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
|
paulo@89
|
9235 yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
|
paulo@89
|
9236
|
paulo@89
|
9237 legend = nv.models.legend().height(30),
|
paulo@89
|
9238 tooltip = nv.models.tooltip(),
|
paulo@89
|
9239 dispatch = d3.dispatch();
|
paulo@89
|
9240
|
paulo@89
|
9241 function chart(selection) {
|
paulo@89
|
9242 selection.each(function(data) {
|
paulo@89
|
9243 var container = d3.select(this),
|
paulo@89
|
9244 that = this;
|
paulo@89
|
9245 nv.utils.initSVG(container);
|
paulo@89
|
9246
|
paulo@89
|
9247 chart.update = function() { container.transition().call(chart); };
|
paulo@89
|
9248 chart.container = this;
|
paulo@89
|
9249
|
paulo@89
|
9250 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
9251 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
9252
|
paulo@89
|
9253 var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
|
paulo@89
|
9254 var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
|
paulo@89
|
9255 var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1});
|
paulo@89
|
9256 var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2});
|
paulo@89
|
9257 var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
|
paulo@89
|
9258 var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
|
paulo@89
|
9259
|
paulo@89
|
9260 // Display noData message if there's nothing to show.
|
paulo@89
|
9261 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
9262 nv.utils.noData(chart, container);
|
paulo@89
|
9263 return chart;
|
paulo@89
|
9264 } else {
|
paulo@89
|
9265 container.selectAll('.nv-noData').remove();
|
paulo@89
|
9266 }
|
paulo@89
|
9267
|
paulo@89
|
9268 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
|
paulo@89
|
9269 .map(function(d) {
|
paulo@89
|
9270 return d.values.map(function(d,i) {
|
paulo@89
|
9271 return { x: d.x, y: d.y }
|
paulo@89
|
9272 })
|
paulo@89
|
9273 });
|
paulo@89
|
9274
|
paulo@89
|
9275 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
|
paulo@89
|
9276 .map(function(d) {
|
paulo@89
|
9277 return d.values.map(function(d,i) {
|
paulo@89
|
9278 return { x: d.x, y: d.y }
|
paulo@89
|
9279 })
|
paulo@89
|
9280 });
|
paulo@89
|
9281
|
paulo@89
|
9282 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
|
paulo@89
|
9283 .range([0, availableWidth]);
|
paulo@89
|
9284
|
paulo@89
|
9285 var wrap = container.selectAll('g.wrap.multiChart').data([data]);
|
paulo@89
|
9286 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
|
paulo@89
|
9287
|
paulo@89
|
9288 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
9289 gEnter.append('g').attr('class', 'nv-y1 nv-axis');
|
paulo@89
|
9290 gEnter.append('g').attr('class', 'nv-y2 nv-axis');
|
paulo@89
|
9291 gEnter.append('g').attr('class', 'lines1Wrap');
|
paulo@89
|
9292 gEnter.append('g').attr('class', 'lines2Wrap');
|
paulo@89
|
9293 gEnter.append('g').attr('class', 'bars1Wrap');
|
paulo@89
|
9294 gEnter.append('g').attr('class', 'bars2Wrap');
|
paulo@89
|
9295 gEnter.append('g').attr('class', 'stack1Wrap');
|
paulo@89
|
9296 gEnter.append('g').attr('class', 'stack2Wrap');
|
paulo@89
|
9297 gEnter.append('g').attr('class', 'legendWrap');
|
paulo@89
|
9298
|
paulo@89
|
9299 var g = wrap.select('g');
|
paulo@89
|
9300
|
paulo@89
|
9301 var color_array = data.map(function(d,i) {
|
paulo@89
|
9302 return data[i].color || color(d, i);
|
paulo@89
|
9303 });
|
paulo@89
|
9304
|
paulo@89
|
9305 if (showLegend) {
|
paulo@89
|
9306 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
|
paulo@89
|
9307 var legendXPosition = legend.align() ? legendWidth : 0;
|
paulo@89
|
9308
|
paulo@89
|
9309 legend.width(legendWidth);
|
paulo@89
|
9310 legend.color(color_array);
|
paulo@89
|
9311
|
paulo@89
|
9312 g.select('.legendWrap')
|
paulo@89
|
9313 .datum(data.map(function(series) {
|
paulo@89
|
9314 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
|
paulo@89
|
9315 series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
|
paulo@89
|
9316 return series;
|
paulo@89
|
9317 }))
|
paulo@89
|
9318 .call(legend);
|
paulo@89
|
9319
|
paulo@89
|
9320 if ( margin.top != legend.height()) {
|
paulo@89
|
9321 margin.top = legend.height();
|
paulo@89
|
9322 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
9323 }
|
paulo@89
|
9324
|
paulo@89
|
9325 g.select('.legendWrap')
|
paulo@89
|
9326 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
|
paulo@89
|
9327 }
|
paulo@89
|
9328
|
paulo@89
|
9329 lines1
|
paulo@89
|
9330 .width(availableWidth)
|
paulo@89
|
9331 .height(availableHeight)
|
paulo@89
|
9332 .interpolate(interpolate)
|
paulo@89
|
9333 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
|
paulo@89
|
9334 lines2
|
paulo@89
|
9335 .width(availableWidth)
|
paulo@89
|
9336 .height(availableHeight)
|
paulo@89
|
9337 .interpolate(interpolate)
|
paulo@89
|
9338 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
|
paulo@89
|
9339 bars1
|
paulo@89
|
9340 .width(availableWidth)
|
paulo@89
|
9341 .height(availableHeight)
|
paulo@89
|
9342 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
|
paulo@89
|
9343 bars2
|
paulo@89
|
9344 .width(availableWidth)
|
paulo@89
|
9345 .height(availableHeight)
|
paulo@89
|
9346 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
|
paulo@89
|
9347 stack1
|
paulo@89
|
9348 .width(availableWidth)
|
paulo@89
|
9349 .height(availableHeight)
|
paulo@89
|
9350 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
|
paulo@89
|
9351 stack2
|
paulo@89
|
9352 .width(availableWidth)
|
paulo@89
|
9353 .height(availableHeight)
|
paulo@89
|
9354 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
|
paulo@89
|
9355
|
paulo@89
|
9356 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
9357
|
paulo@89
|
9358 var lines1Wrap = g.select('.lines1Wrap')
|
paulo@89
|
9359 .datum(dataLines1.filter(function(d){return !d.disabled}));
|
paulo@89
|
9360 var bars1Wrap = g.select('.bars1Wrap')
|
paulo@89
|
9361 .datum(dataBars1.filter(function(d){return !d.disabled}));
|
paulo@89
|
9362 var stack1Wrap = g.select('.stack1Wrap')
|
paulo@89
|
9363 .datum(dataStack1.filter(function(d){return !d.disabled}));
|
paulo@89
|
9364 var lines2Wrap = g.select('.lines2Wrap')
|
paulo@89
|
9365 .datum(dataLines2.filter(function(d){return !d.disabled}));
|
paulo@89
|
9366 var bars2Wrap = g.select('.bars2Wrap')
|
paulo@89
|
9367 .datum(dataBars2.filter(function(d){return !d.disabled}));
|
paulo@89
|
9368 var stack2Wrap = g.select('.stack2Wrap')
|
paulo@89
|
9369 .datum(dataStack2.filter(function(d){return !d.disabled}));
|
paulo@89
|
9370
|
paulo@89
|
9371 var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
|
paulo@89
|
9372 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
|
paulo@89
|
9373 }).concat([{x:0, y:0}]) : [];
|
paulo@89
|
9374 var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
|
paulo@89
|
9375 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
|
paulo@89
|
9376 }).concat([{x:0, y:0}]) : [];
|
paulo@89
|
9377
|
paulo@89
|
9378 yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
|
paulo@89
|
9379 .range([0, availableHeight]);
|
paulo@89
|
9380
|
paulo@89
|
9381 yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
|
paulo@89
|
9382 .range([0, availableHeight]);
|
paulo@89
|
9383
|
paulo@89
|
9384 lines1.yDomain(yScale1.domain());
|
paulo@89
|
9385 bars1.yDomain(yScale1.domain());
|
paulo@89
|
9386 stack1.yDomain(yScale1.domain());
|
paulo@89
|
9387
|
paulo@89
|
9388 lines2.yDomain(yScale2.domain());
|
paulo@89
|
9389 bars2.yDomain(yScale2.domain());
|
paulo@89
|
9390 stack2.yDomain(yScale2.domain());
|
paulo@89
|
9391
|
paulo@89
|
9392 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
|
paulo@89
|
9393 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
|
paulo@89
|
9394
|
paulo@89
|
9395 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
|
paulo@89
|
9396 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
|
paulo@89
|
9397
|
paulo@89
|
9398 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
|
paulo@89
|
9399 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
|
paulo@89
|
9400
|
paulo@89
|
9401 xAxis
|
paulo@89
|
9402 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
9403 .tickSize(-availableHeight, 0);
|
paulo@89
|
9404
|
paulo@89
|
9405 g.select('.nv-x.nv-axis')
|
paulo@89
|
9406 .attr('transform', 'translate(0,' + availableHeight + ')');
|
paulo@89
|
9407 d3.transition(g.select('.nv-x.nv-axis'))
|
paulo@89
|
9408 .call(xAxis);
|
paulo@89
|
9409
|
paulo@89
|
9410 yAxis1
|
paulo@89
|
9411 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
|
paulo@89
|
9412 .tickSize( -availableWidth, 0);
|
paulo@89
|
9413
|
paulo@89
|
9414
|
paulo@89
|
9415 d3.transition(g.select('.nv-y1.nv-axis'))
|
paulo@89
|
9416 .call(yAxis1);
|
paulo@89
|
9417
|
paulo@89
|
9418 yAxis2
|
paulo@89
|
9419 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
|
paulo@89
|
9420 .tickSize( -availableWidth, 0);
|
paulo@89
|
9421
|
paulo@89
|
9422 d3.transition(g.select('.nv-y2.nv-axis'))
|
paulo@89
|
9423 .call(yAxis2);
|
paulo@89
|
9424
|
paulo@89
|
9425 g.select('.nv-y1.nv-axis')
|
paulo@89
|
9426 .classed('nv-disabled', series1.length ? false : true)
|
paulo@89
|
9427 .attr('transform', 'translate(' + x.range()[0] + ',0)');
|
paulo@89
|
9428
|
paulo@89
|
9429 g.select('.nv-y2.nv-axis')
|
paulo@89
|
9430 .classed('nv-disabled', series2.length ? false : true)
|
paulo@89
|
9431 .attr('transform', 'translate(' + x.range()[1] + ',0)');
|
paulo@89
|
9432
|
paulo@89
|
9433 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
9434 chart.update();
|
paulo@89
|
9435 });
|
paulo@89
|
9436
|
paulo@89
|
9437 //============================================================
|
paulo@89
|
9438 // Event Handling/Dispatching
|
paulo@89
|
9439 //------------------------------------------------------------
|
paulo@89
|
9440
|
paulo@89
|
9441 function mouseover_line(evt) {
|
paulo@89
|
9442 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
|
paulo@89
|
9443 evt.value = evt.point.x;
|
paulo@89
|
9444 evt.series = {
|
paulo@89
|
9445 value: evt.point.y,
|
paulo@89
|
9446 color: evt.point.color
|
paulo@89
|
9447 };
|
paulo@89
|
9448 tooltip
|
paulo@89
|
9449 .duration(100)
|
paulo@89
|
9450 .valueFormatter(function(d, i) {
|
paulo@89
|
9451 return yaxis.tickFormat()(d, i);
|
paulo@89
|
9452 })
|
paulo@89
|
9453 .data(evt)
|
paulo@89
|
9454 .position(evt.pos)
|
paulo@89
|
9455 .hidden(false);
|
paulo@89
|
9456 }
|
paulo@89
|
9457
|
paulo@89
|
9458 function mouseover_stack(evt) {
|
paulo@89
|
9459 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
|
paulo@89
|
9460 evt.point['x'] = stack1.x()(evt.point);
|
paulo@89
|
9461 evt.point['y'] = stack1.y()(evt.point);
|
paulo@89
|
9462 tooltip
|
paulo@89
|
9463 .duration(100)
|
paulo@89
|
9464 .valueFormatter(function(d, i) {
|
paulo@89
|
9465 return yaxis.tickFormat()(d, i);
|
paulo@89
|
9466 })
|
paulo@89
|
9467 .data(evt)
|
paulo@89
|
9468 .position(evt.pos)
|
paulo@89
|
9469 .hidden(false);
|
paulo@89
|
9470 }
|
paulo@89
|
9471
|
paulo@89
|
9472 function mouseover_bar(evt) {
|
paulo@89
|
9473 var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
|
paulo@89
|
9474
|
paulo@89
|
9475 evt.value = bars1.x()(evt.data);
|
paulo@89
|
9476 evt['series'] = {
|
paulo@89
|
9477 value: bars1.y()(evt.data),
|
paulo@89
|
9478 color: evt.color
|
paulo@89
|
9479 };
|
paulo@89
|
9480 tooltip
|
paulo@89
|
9481 .duration(0)
|
paulo@89
|
9482 .valueFormatter(function(d, i) {
|
paulo@89
|
9483 return yaxis.tickFormat()(d, i);
|
paulo@89
|
9484 })
|
paulo@89
|
9485 .data(evt)
|
paulo@89
|
9486 .hidden(false);
|
paulo@89
|
9487 }
|
paulo@89
|
9488
|
paulo@89
|
9489 lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
|
paulo@89
|
9490 lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
|
paulo@89
|
9491 lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
9492 tooltip.hidden(true)
|
paulo@89
|
9493 });
|
paulo@89
|
9494 lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
9495 tooltip.hidden(true)
|
paulo@89
|
9496 });
|
paulo@89
|
9497
|
paulo@89
|
9498 stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack);
|
paulo@89
|
9499 stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack);
|
paulo@89
|
9500 stack1.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
9501 tooltip.hidden(true)
|
paulo@89
|
9502 });
|
paulo@89
|
9503 stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
9504 tooltip.hidden(true)
|
paulo@89
|
9505 });
|
paulo@89
|
9506
|
paulo@89
|
9507 bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
|
paulo@89
|
9508 bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);
|
paulo@89
|
9509
|
paulo@89
|
9510 bars1.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
9511 tooltip.hidden(true);
|
paulo@89
|
9512 });
|
paulo@89
|
9513 bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
9514 tooltip.hidden(true);
|
paulo@89
|
9515 });
|
paulo@89
|
9516 bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
9517 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
9518 });
|
paulo@89
|
9519 bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
9520 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
9521 });
|
paulo@89
|
9522
|
paulo@89
|
9523 });
|
paulo@89
|
9524
|
paulo@89
|
9525 return chart;
|
paulo@89
|
9526 }
|
paulo@89
|
9527
|
paulo@89
|
9528 //============================================================
|
paulo@89
|
9529 // Global getters and setters
|
paulo@89
|
9530 //------------------------------------------------------------
|
paulo@89
|
9531
|
paulo@89
|
9532 chart.dispatch = dispatch;
|
paulo@89
|
9533 chart.lines1 = lines1;
|
paulo@89
|
9534 chart.lines2 = lines2;
|
paulo@89
|
9535 chart.bars1 = bars1;
|
paulo@89
|
9536 chart.bars2 = bars2;
|
paulo@89
|
9537 chart.stack1 = stack1;
|
paulo@89
|
9538 chart.stack2 = stack2;
|
paulo@89
|
9539 chart.xAxis = xAxis;
|
paulo@89
|
9540 chart.yAxis1 = yAxis1;
|
paulo@89
|
9541 chart.yAxis2 = yAxis2;
|
paulo@89
|
9542 chart.tooltip = tooltip;
|
paulo@89
|
9543
|
paulo@89
|
9544 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
9545
|
paulo@89
|
9546 chart._options = Object.create({}, {
|
paulo@89
|
9547 // simple options, just get/set the necessary values
|
paulo@89
|
9548 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
9549 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
9550 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
9551 yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
|
paulo@89
|
9552 yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
|
paulo@89
|
9553 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
9554 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
|
paulo@89
|
9555
|
paulo@89
|
9556 // deprecated options
|
paulo@89
|
9557 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
9558 // deprecated after 1.7.1
|
paulo@89
|
9559 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
9560 tooltip.enabled(!!_);
|
paulo@89
|
9561 }},
|
paulo@89
|
9562 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
9563 // deprecated after 1.7.1
|
paulo@89
|
9564 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
9565 tooltip.contentGenerator(_);
|
paulo@89
|
9566 }},
|
paulo@89
|
9567
|
paulo@89
|
9568 // options that require extra logic in the setter
|
paulo@89
|
9569 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
9570 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
9571 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
9572 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
9573 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
9574 }},
|
paulo@89
|
9575 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
9576 color = nv.utils.getColor(_);
|
paulo@89
|
9577 }},
|
paulo@89
|
9578 x: {get: function(){return getX;}, set: function(_){
|
paulo@89
|
9579 getX = _;
|
paulo@89
|
9580 lines1.x(_);
|
paulo@89
|
9581 lines2.x(_);
|
paulo@89
|
9582 bars1.x(_);
|
paulo@89
|
9583 bars2.x(_);
|
paulo@89
|
9584 stack1.x(_);
|
paulo@89
|
9585 stack2.x(_);
|
paulo@89
|
9586 }},
|
paulo@89
|
9587 y: {get: function(){return getY;}, set: function(_){
|
paulo@89
|
9588 getY = _;
|
paulo@89
|
9589 lines1.y(_);
|
paulo@89
|
9590 lines2.y(_);
|
paulo@89
|
9591 stack1.y(_);
|
paulo@89
|
9592 stack2.y(_);
|
paulo@89
|
9593 bars1.y(_);
|
paulo@89
|
9594 bars2.y(_);
|
paulo@89
|
9595 }},
|
paulo@89
|
9596 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
|
paulo@89
|
9597 useVoronoi=_;
|
paulo@89
|
9598 lines1.useVoronoi(_);
|
paulo@89
|
9599 lines2.useVoronoi(_);
|
paulo@89
|
9600 stack1.useVoronoi(_);
|
paulo@89
|
9601 stack2.useVoronoi(_);
|
paulo@89
|
9602 }}
|
paulo@89
|
9603 });
|
paulo@89
|
9604
|
paulo@89
|
9605 nv.utils.initOptions(chart);
|
paulo@89
|
9606
|
paulo@89
|
9607 return chart;
|
paulo@89
|
9608 };
|
paulo@89
|
9609
|
paulo@89
|
9610
|
paulo@89
|
9611 nv.models.ohlcBar = function() {
|
paulo@89
|
9612 "use strict";
|
paulo@89
|
9613
|
paulo@89
|
9614 //============================================================
|
paulo@89
|
9615 // Public Variables with Default Settings
|
paulo@89
|
9616 //------------------------------------------------------------
|
paulo@89
|
9617
|
paulo@89
|
9618 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
9619 , width = null
|
paulo@89
|
9620 , height = null
|
paulo@89
|
9621 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
9622 , container = null
|
paulo@89
|
9623 , x = d3.scale.linear()
|
paulo@89
|
9624 , y = d3.scale.linear()
|
paulo@89
|
9625 , getX = function(d) { return d.x }
|
paulo@89
|
9626 , getY = function(d) { return d.y }
|
paulo@89
|
9627 , getOpen = function(d) { return d.open }
|
paulo@89
|
9628 , getClose = function(d) { return d.close }
|
paulo@89
|
9629 , getHigh = function(d) { return d.high }
|
paulo@89
|
9630 , getLow = function(d) { return d.low }
|
paulo@89
|
9631 , forceX = []
|
paulo@89
|
9632 , forceY = []
|
paulo@89
|
9633 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
|
paulo@89
|
9634 , clipEdge = true
|
paulo@89
|
9635 , color = nv.utils.defaultColor()
|
paulo@89
|
9636 , interactive = false
|
paulo@89
|
9637 , xDomain
|
paulo@89
|
9638 , yDomain
|
paulo@89
|
9639 , xRange
|
paulo@89
|
9640 , yRange
|
paulo@89
|
9641 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
|
paulo@89
|
9642 ;
|
paulo@89
|
9643
|
paulo@89
|
9644 //============================================================
|
paulo@89
|
9645 // Private Variables
|
paulo@89
|
9646 //------------------------------------------------------------
|
paulo@89
|
9647
|
paulo@89
|
9648 function chart(selection) {
|
paulo@89
|
9649 selection.each(function(data) {
|
paulo@89
|
9650 container = d3.select(this);
|
paulo@89
|
9651 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
9652 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
9653
|
paulo@89
|
9654 nv.utils.initSVG(container);
|
paulo@89
|
9655
|
paulo@89
|
9656 // ohlc bar width.
|
paulo@89
|
9657 var w = (availableWidth / data[0].values.length) * .9;
|
paulo@89
|
9658
|
paulo@89
|
9659 // Setup Scales
|
paulo@89
|
9660 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
|
paulo@89
|
9661
|
paulo@89
|
9662 if (padData)
|
paulo@89
|
9663 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
|
paulo@89
|
9664 else
|
paulo@89
|
9665 x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]);
|
paulo@89
|
9666
|
paulo@89
|
9667 y.domain(yDomain || [
|
paulo@89
|
9668 d3.min(data[0].values.map(getLow).concat(forceY)),
|
paulo@89
|
9669 d3.max(data[0].values.map(getHigh).concat(forceY))
|
paulo@89
|
9670 ]
|
paulo@89
|
9671 ).range(yRange || [availableHeight, 0]);
|
paulo@89
|
9672
|
paulo@89
|
9673 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
|
paulo@89
|
9674 if (x.domain()[0] === x.domain()[1])
|
paulo@89
|
9675 x.domain()[0] ?
|
paulo@89
|
9676 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
|
paulo@89
|
9677 : x.domain([-1,1]);
|
paulo@89
|
9678
|
paulo@89
|
9679 if (y.domain()[0] === y.domain()[1])
|
paulo@89
|
9680 y.domain()[0] ?
|
paulo@89
|
9681 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
|
paulo@89
|
9682 : y.domain([-1,1]);
|
paulo@89
|
9683
|
paulo@89
|
9684 // Setup containers and skeleton of chart
|
paulo@89
|
9685 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
|
paulo@89
|
9686 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
|
paulo@89
|
9687 var defsEnter = wrapEnter.append('defs');
|
paulo@89
|
9688 var gEnter = wrapEnter.append('g');
|
paulo@89
|
9689 var g = wrap.select('g');
|
paulo@89
|
9690
|
paulo@89
|
9691 gEnter.append('g').attr('class', 'nv-ticks');
|
paulo@89
|
9692
|
paulo@89
|
9693 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
9694
|
paulo@89
|
9695 container
|
paulo@89
|
9696 .on('click', function(d,i) {
|
paulo@89
|
9697 dispatch.chartClick({
|
paulo@89
|
9698 data: d,
|
paulo@89
|
9699 index: i,
|
paulo@89
|
9700 pos: d3.event,
|
paulo@89
|
9701 id: id
|
paulo@89
|
9702 });
|
paulo@89
|
9703 });
|
paulo@89
|
9704
|
paulo@89
|
9705 defsEnter.append('clipPath')
|
paulo@89
|
9706 .attr('id', 'nv-chart-clip-path-' + id)
|
paulo@89
|
9707 .append('rect');
|
paulo@89
|
9708
|
paulo@89
|
9709 wrap.select('#nv-chart-clip-path-' + id + ' rect')
|
paulo@89
|
9710 .attr('width', availableWidth)
|
paulo@89
|
9711 .attr('height', availableHeight);
|
paulo@89
|
9712
|
paulo@89
|
9713 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
|
paulo@89
|
9714
|
paulo@89
|
9715 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
|
paulo@89
|
9716 .data(function(d) { return d });
|
paulo@89
|
9717 ticks.exit().remove();
|
paulo@89
|
9718
|
paulo@89
|
9719 ticks.enter().append('path')
|
paulo@89
|
9720 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
|
paulo@89
|
9721 .attr('d', function(d,i) {
|
paulo@89
|
9722 return 'm0,0l0,'
|
paulo@89
|
9723 + (y(getOpen(d,i))
|
paulo@89
|
9724 - y(getHigh(d,i)))
|
paulo@89
|
9725 + 'l'
|
paulo@89
|
9726 + (-w/2)
|
paulo@89
|
9727 + ',0l'
|
paulo@89
|
9728 + (w/2)
|
paulo@89
|
9729 + ',0l0,'
|
paulo@89
|
9730 + (y(getLow(d,i)) - y(getOpen(d,i)))
|
paulo@89
|
9731 + 'l0,'
|
paulo@89
|
9732 + (y(getClose(d,i))
|
paulo@89
|
9733 - y(getLow(d,i)))
|
paulo@89
|
9734 + 'l'
|
paulo@89
|
9735 + (w/2)
|
paulo@89
|
9736 + ',0l'
|
paulo@89
|
9737 + (-w/2)
|
paulo@89
|
9738 + ',0z';
|
paulo@89
|
9739 })
|
paulo@89
|
9740 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
|
paulo@89
|
9741 .attr('fill', function(d,i) { return color[0]; })
|
paulo@89
|
9742 .attr('stroke', function(d,i) { return color[0]; })
|
paulo@89
|
9743 .attr('x', 0 )
|
paulo@89
|
9744 .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
|
paulo@89
|
9745 .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
|
paulo@89
|
9746
|
paulo@89
|
9747 // the bar colors are controlled by CSS currently
|
paulo@89
|
9748 ticks.attr('class', function(d,i,j) {
|
paulo@89
|
9749 return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
|
paulo@89
|
9750 });
|
paulo@89
|
9751
|
paulo@89
|
9752 d3.transition(ticks)
|
paulo@89
|
9753 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
|
paulo@89
|
9754 .attr('d', function(d,i) {
|
paulo@89
|
9755 var w = (availableWidth / data[0].values.length) * .9;
|
paulo@89
|
9756 return 'm0,0l0,'
|
paulo@89
|
9757 + (y(getOpen(d,i))
|
paulo@89
|
9758 - y(getHigh(d,i)))
|
paulo@89
|
9759 + 'l'
|
paulo@89
|
9760 + (-w/2)
|
paulo@89
|
9761 + ',0l'
|
paulo@89
|
9762 + (w/2)
|
paulo@89
|
9763 + ',0l0,'
|
paulo@89
|
9764 + (y(getLow(d,i))
|
paulo@89
|
9765 - y(getOpen(d,i)))
|
paulo@89
|
9766 + 'l0,'
|
paulo@89
|
9767 + (y(getClose(d,i))
|
paulo@89
|
9768 - y(getLow(d,i)))
|
paulo@89
|
9769 + 'l'
|
paulo@89
|
9770 + (w/2)
|
paulo@89
|
9771 + ',0l'
|
paulo@89
|
9772 + (-w/2)
|
paulo@89
|
9773 + ',0z';
|
paulo@89
|
9774 });
|
paulo@89
|
9775 });
|
paulo@89
|
9776
|
paulo@89
|
9777 return chart;
|
paulo@89
|
9778 }
|
paulo@89
|
9779
|
paulo@89
|
9780
|
paulo@89
|
9781 //Create methods to allow outside functions to highlight a specific bar.
|
paulo@89
|
9782 chart.highlightPoint = function(pointIndex, isHoverOver) {
|
paulo@89
|
9783 chart.clearHighlights();
|
paulo@89
|
9784 container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
|
paulo@89
|
9785 .classed("hover", isHoverOver)
|
paulo@89
|
9786 ;
|
paulo@89
|
9787 };
|
paulo@89
|
9788
|
paulo@89
|
9789 chart.clearHighlights = function() {
|
paulo@89
|
9790 container.select(".nv-ohlcBar .nv-tick.hover")
|
paulo@89
|
9791 .classed("hover", false)
|
paulo@89
|
9792 ;
|
paulo@89
|
9793 };
|
paulo@89
|
9794
|
paulo@89
|
9795 //============================================================
|
paulo@89
|
9796 // Expose Public Variables
|
paulo@89
|
9797 //------------------------------------------------------------
|
paulo@89
|
9798
|
paulo@89
|
9799 chart.dispatch = dispatch;
|
paulo@89
|
9800 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
9801
|
paulo@89
|
9802 chart._options = Object.create({}, {
|
paulo@89
|
9803 // simple options, just get/set the necessary values
|
paulo@89
|
9804 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
9805 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
9806 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
9807 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
9808 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
9809 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
9810 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
9811 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
9812 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
|
paulo@89
|
9813 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
|
paulo@89
|
9814 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
|
paulo@89
|
9815 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
|
paulo@89
|
9816 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
9817 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
|
paulo@89
|
9818
|
paulo@89
|
9819 x: {get: function(){return getX;}, set: function(_){getX=_;}},
|
paulo@89
|
9820 y: {get: function(){return getY;}, set: function(_){getY=_;}},
|
paulo@89
|
9821 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
|
paulo@89
|
9822 close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
|
paulo@89
|
9823 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
|
paulo@89
|
9824 low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
|
paulo@89
|
9825
|
paulo@89
|
9826 // options that require extra logic in the setter
|
paulo@89
|
9827 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
9828 margin.top = _.top != undefined ? _.top : margin.top;
|
paulo@89
|
9829 margin.right = _.right != undefined ? _.right : margin.right;
|
paulo@89
|
9830 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
|
paulo@89
|
9831 margin.left = _.left != undefined ? _.left : margin.left;
|
paulo@89
|
9832 }},
|
paulo@89
|
9833 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
9834 color = nv.utils.getColor(_);
|
paulo@89
|
9835 }}
|
paulo@89
|
9836 });
|
paulo@89
|
9837
|
paulo@89
|
9838 nv.utils.initOptions(chart);
|
paulo@89
|
9839 return chart;
|
paulo@89
|
9840 };
|
paulo@89
|
9841 // Code adapted from Jason Davies' "Parallel Coordinates"
|
paulo@89
|
9842 // http://bl.ocks.org/jasondavies/1341281
|
paulo@89
|
9843 nv.models.parallelCoordinates = function() {
|
paulo@89
|
9844 "use strict";
|
paulo@89
|
9845
|
paulo@89
|
9846 //============================================================
|
paulo@89
|
9847 // Public Variables with Default Settings
|
paulo@89
|
9848 //------------------------------------------------------------
|
paulo@89
|
9849
|
paulo@89
|
9850 var margin = {top: 30, right: 0, bottom: 10, left: 0}
|
paulo@89
|
9851 , width = null
|
paulo@89
|
9852 , height = null
|
paulo@89
|
9853 , x = d3.scale.ordinal()
|
paulo@89
|
9854 , y = {}
|
paulo@89
|
9855 , dimensionNames = []
|
paulo@89
|
9856 , dimensionFormats = []
|
paulo@89
|
9857 , color = nv.utils.defaultColor()
|
paulo@89
|
9858 , filters = []
|
paulo@89
|
9859 , active = []
|
paulo@89
|
9860 , dragging = []
|
paulo@89
|
9861 , lineTension = 1
|
paulo@89
|
9862 , dispatch = d3.dispatch('brush', 'elementMouseover', 'elementMouseout')
|
paulo@89
|
9863 ;
|
paulo@89
|
9864
|
paulo@89
|
9865 //============================================================
|
paulo@89
|
9866 // Private Variables
|
paulo@89
|
9867 //------------------------------------------------------------
|
paulo@89
|
9868
|
paulo@89
|
9869 function chart(selection) {
|
paulo@89
|
9870 selection.each(function(data) {
|
paulo@89
|
9871 var container = d3.select(this);
|
paulo@89
|
9872 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
9873 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
9874
|
paulo@89
|
9875 nv.utils.initSVG(container);
|
paulo@89
|
9876
|
paulo@89
|
9877 active = data; //set all active before first brush call
|
paulo@89
|
9878
|
paulo@89
|
9879 // Setup Scales
|
paulo@89
|
9880 x.rangePoints([0, availableWidth], 1).domain(dimensionNames);
|
paulo@89
|
9881
|
paulo@89
|
9882 //Set as true if all values on an axis are missing.
|
paulo@89
|
9883 var onlyNanValues = {};
|
paulo@89
|
9884 // Extract the list of dimensions and create a scale for each.
|
paulo@89
|
9885 dimensionNames.forEach(function(d) {
|
paulo@89
|
9886 var extent = d3.extent(data, function(p) { return +p[d]; });
|
paulo@89
|
9887 onlyNanValues[d] = false;
|
paulo@89
|
9888 //If there is no values to display on an axis, set the extent to 0
|
paulo@89
|
9889 if (extent[0] === undefined) {
|
paulo@89
|
9890 onlyNanValues[d] = true;
|
paulo@89
|
9891 extent[0] = 0;
|
paulo@89
|
9892 extent[1] = 0;
|
paulo@89
|
9893 }
|
paulo@89
|
9894 //Scale axis if there is only one value
|
paulo@89
|
9895 if (extent[0] === extent[1]) {
|
paulo@89
|
9896 extent[0] = extent[0] - 1;
|
paulo@89
|
9897 extent[1] = extent[1] + 1;
|
paulo@89
|
9898 }
|
paulo@89
|
9899 //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text.
|
paulo@89
|
9900 //The remaining 10% are used to display the missingValue line.
|
paulo@89
|
9901 y[d] = d3.scale.linear()
|
paulo@89
|
9902 .domain(extent)
|
paulo@89
|
9903 .range([(availableHeight - 12) * 0.9, 0]);
|
paulo@89
|
9904
|
paulo@89
|
9905 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
|
paulo@89
|
9906
|
paulo@89
|
9907 return d != 'name';
|
paulo@89
|
9908 });
|
paulo@89
|
9909
|
paulo@89
|
9910 // Setup containers and skeleton of chart
|
paulo@89
|
9911 var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
|
paulo@89
|
9912 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
|
paulo@89
|
9913 var gEnter = wrapEnter.append('g');
|
paulo@89
|
9914 var g = wrap.select('g');
|
paulo@89
|
9915
|
paulo@89
|
9916 gEnter.append('g').attr('class', 'nv-parallelCoordinates background');
|
paulo@89
|
9917 gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground');
|
paulo@89
|
9918 gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline');
|
paulo@89
|
9919
|
paulo@89
|
9920 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
9921
|
paulo@89
|
9922 var line = d3.svg.line().interpolate('cardinal').tension(lineTension),
|
paulo@89
|
9923 axis = d3.svg.axis().orient('left'),
|
paulo@89
|
9924 axisDrag = d3.behavior.drag()
|
paulo@89
|
9925 .on('dragstart', dragStart)
|
paulo@89
|
9926 .on('drag', dragMove)
|
paulo@89
|
9927 .on('dragend', dragEnd);
|
paulo@89
|
9928
|
paulo@89
|
9929 //Add missing value line at the bottom of the chart
|
paulo@89
|
9930 var missingValuesline, missingValueslineText;
|
paulo@89
|
9931 var step = x.range()[1] - x.range()[0];
|
paulo@89
|
9932 var axisWithMissingValues = [];
|
paulo@89
|
9933 var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12];
|
paulo@89
|
9934 missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]);
|
paulo@89
|
9935 missingValuesline.enter().append('line');
|
paulo@89
|
9936 missingValuesline.exit().remove();
|
paulo@89
|
9937 missingValuesline.attr("x1", function(d) { return d[0]; })
|
paulo@89
|
9938 .attr("y1", function(d) { return d[1]; })
|
paulo@89
|
9939 .attr("x2", function(d) { return d[2]; })
|
paulo@89
|
9940 .attr("y2", function(d) { return d[3]; });
|
paulo@89
|
9941
|
paulo@89
|
9942 //Add the text "undefined values" under the missing value line
|
paulo@89
|
9943 missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]);
|
paulo@89
|
9944 missingValueslineText.append('text').data(["undefined values"]);
|
paulo@89
|
9945 missingValueslineText.enter().append('text');
|
paulo@89
|
9946 missingValueslineText.exit().remove();
|
paulo@89
|
9947 missingValueslineText.attr("y", availableHeight)
|
paulo@89
|
9948 //To have the text right align with the missingValues line, substract 92 representing the text size.
|
paulo@89
|
9949 .attr("x", availableWidth - 92 - step / 2)
|
paulo@89
|
9950 .text(function(d) { return d; });
|
paulo@89
|
9951
|
paulo@89
|
9952 // Add grey background lines for context.
|
paulo@89
|
9953 var background = wrap.select('.background').selectAll('path').data(data);
|
paulo@89
|
9954 background.enter().append('path');
|
paulo@89
|
9955 background.exit().remove();
|
paulo@89
|
9956 background.attr('d', path);
|
paulo@89
|
9957
|
paulo@89
|
9958 // Add blue foreground lines for focus.
|
paulo@89
|
9959 var foreground = wrap.select('.foreground').selectAll('path').data(data);
|
paulo@89
|
9960 foreground.enter().append('path')
|
paulo@89
|
9961 foreground.exit().remove();
|
paulo@89
|
9962 foreground.attr('d', path).attr('stroke', color);
|
paulo@89
|
9963 foreground.on("mouseover", function (d, i) {
|
paulo@89
|
9964 d3.select(this).classed('hover', true);
|
paulo@89
|
9965 dispatch.elementMouseover({
|
paulo@89
|
9966 label: d.name,
|
paulo@89
|
9967 data: d.data,
|
paulo@89
|
9968 index: i,
|
paulo@89
|
9969 pos: [d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]]
|
paulo@89
|
9970 });
|
paulo@89
|
9971
|
paulo@89
|
9972 });
|
paulo@89
|
9973 foreground.on("mouseout", function (d, i) {
|
paulo@89
|
9974 d3.select(this).classed('hover', false);
|
paulo@89
|
9975 dispatch.elementMouseout({
|
paulo@89
|
9976 label: d.name,
|
paulo@89
|
9977 data: d.data,
|
paulo@89
|
9978 index: i
|
paulo@89
|
9979 });
|
paulo@89
|
9980 });
|
paulo@89
|
9981
|
paulo@89
|
9982 // Add a group element for each dimension.
|
paulo@89
|
9983 var dimensions = g.selectAll('.dimension').data(dimensionNames);
|
paulo@89
|
9984 var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension');
|
paulo@89
|
9985 dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates nv-axis');
|
paulo@89
|
9986 dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates-brush');
|
paulo@89
|
9987 dimensionsEnter.append('text').attr('class', 'nv-parallelCoordinates nv-label');
|
paulo@89
|
9988
|
paulo@89
|
9989 dimensions.attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; });
|
paulo@89
|
9990 dimensions.exit().remove();
|
paulo@89
|
9991
|
paulo@89
|
9992 // Add an axis and title.
|
paulo@89
|
9993 dimensions.select('.nv-label')
|
paulo@89
|
9994 .style("cursor", "move")
|
paulo@89
|
9995 .attr('dy', '-1em')
|
paulo@89
|
9996 .attr('text-anchor', 'middle')
|
paulo@89
|
9997 .text(String)
|
paulo@89
|
9998 .on("mouseover", function(d, i) {
|
paulo@89
|
9999 dispatch.elementMouseover({
|
paulo@89
|
10000 dim: d,
|
paulo@89
|
10001 pos: [d3.mouse(this.parentNode.parentNode)[0], d3.mouse(this.parentNode.parentNode)[1]]
|
paulo@89
|
10002 });
|
paulo@89
|
10003 })
|
paulo@89
|
10004 .on("mouseout", function(d, i) {
|
paulo@89
|
10005 dispatch.elementMouseout({
|
paulo@89
|
10006 dim: d
|
paulo@89
|
10007 });
|
paulo@89
|
10008 })
|
paulo@89
|
10009 .call(axisDrag);
|
paulo@89
|
10010
|
paulo@89
|
10011 dimensions.select('.nv-axis')
|
paulo@89
|
10012 .each(function (d, i) {
|
paulo@89
|
10013 d3.select(this).call(axis.scale(y[d]).tickFormat(d3.format(dimensionFormats[i])));
|
paulo@89
|
10014 });
|
paulo@89
|
10015
|
paulo@89
|
10016 dimensions.select('.nv-parallelCoordinates-brush')
|
paulo@89
|
10017 .each(function (d) {
|
paulo@89
|
10018 d3.select(this).call(y[d].brush);
|
paulo@89
|
10019 })
|
paulo@89
|
10020 .selectAll('rect')
|
paulo@89
|
10021 .attr('x', -8)
|
paulo@89
|
10022 .attr('width', 16);
|
paulo@89
|
10023
|
paulo@89
|
10024 // Returns the path for a given data point.
|
paulo@89
|
10025 function path(d) {
|
paulo@89
|
10026 return line(dimensionNames.map(function (p) {
|
paulo@89
|
10027 //If value if missing, put the value on the missing value line
|
paulo@89
|
10028 if(isNaN(d[p]) || isNaN(parseFloat(d[p]))) {
|
paulo@89
|
10029 var domain = y[p].domain();
|
paulo@89
|
10030 var range = y[p].range();
|
paulo@89
|
10031 var min = domain[0] - (domain[1] - domain[0]) / 9;
|
paulo@89
|
10032
|
paulo@89
|
10033 //If it's not already the case, allow brush to select undefined values
|
paulo@89
|
10034 if(axisWithMissingValues.indexOf(p) < 0) {
|
paulo@89
|
10035
|
paulo@89
|
10036 var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]);
|
paulo@89
|
10037 y[p].brush.y(newscale);
|
paulo@89
|
10038 axisWithMissingValues.push(p);
|
paulo@89
|
10039 }
|
paulo@89
|
10040
|
paulo@89
|
10041 return [x(p), y[p](min)];
|
paulo@89
|
10042 }
|
paulo@89
|
10043
|
paulo@89
|
10044 //If parallelCoordinate contain missing values show the missing values line otherwise, hide it.
|
paulo@89
|
10045 if(axisWithMissingValues.length > 0) {
|
paulo@89
|
10046 missingValuesline.style("display", "inline");
|
paulo@89
|
10047 missingValueslineText.style("display", "inline");
|
paulo@89
|
10048 } else {
|
paulo@89
|
10049 missingValuesline.style("display", "none");
|
paulo@89
|
10050 missingValueslineText.style("display", "none");
|
paulo@89
|
10051 }
|
paulo@89
|
10052
|
paulo@89
|
10053 return [x(p), y[p](d[p])];
|
paulo@89
|
10054 }));
|
paulo@89
|
10055 }
|
paulo@89
|
10056
|
paulo@89
|
10057 // Handles a brush event, toggling the display of foreground lines.
|
paulo@89
|
10058 function brush() {
|
paulo@89
|
10059 var actives = dimensionNames.filter(function(p) { return !y[p].brush.empty(); }),
|
paulo@89
|
10060 extents = actives.map(function(p) { return y[p].brush.extent(); });
|
paulo@89
|
10061
|
paulo@89
|
10062 filters = []; //erase current filters
|
paulo@89
|
10063 actives.forEach(function(d,i) {
|
paulo@89
|
10064 filters[i] = {
|
paulo@89
|
10065 dimension: d,
|
paulo@89
|
10066 extent: extents[i]
|
paulo@89
|
10067 }
|
paulo@89
|
10068 });
|
paulo@89
|
10069
|
paulo@89
|
10070 active = []; //erase current active list
|
paulo@89
|
10071 foreground.style('display', function(d) {
|
paulo@89
|
10072 var isActive = actives.every(function(p, i) {
|
paulo@89
|
10073 if(isNaN(d[p]) && extents[i][0] == y[p].brush.y().domain()[0]) return true;
|
paulo@89
|
10074 return extents[i][0] <= d[p] && d[p] <= extents[i][1];
|
paulo@89
|
10075 });
|
paulo@89
|
10076 if (isActive) active.push(d);
|
paulo@89
|
10077 return isActive ? null : 'none';
|
paulo@89
|
10078 });
|
paulo@89
|
10079
|
paulo@89
|
10080 dispatch.brush({
|
paulo@89
|
10081 filters: filters,
|
paulo@89
|
10082 active: active
|
paulo@89
|
10083 });
|
paulo@89
|
10084 }
|
paulo@89
|
10085
|
paulo@89
|
10086 function dragStart(d, i) {
|
paulo@89
|
10087 dragging[d] = this.parentNode.__origin__ = x(d);
|
paulo@89
|
10088 background.attr("visibility", "hidden");
|
paulo@89
|
10089
|
paulo@89
|
10090 }
|
paulo@89
|
10091
|
paulo@89
|
10092 function dragMove(d, i) {
|
paulo@89
|
10093 dragging[d] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
|
paulo@89
|
10094 foreground.attr("d", path);
|
paulo@89
|
10095 dimensionNames.sort(function (a, b) { return position(a) - position(b); });
|
paulo@89
|
10096 x.domain(dimensionNames);
|
paulo@89
|
10097 dimensions.attr("transform", function(d) { return "translate(" + position(d) + ")"; });
|
paulo@89
|
10098 }
|
paulo@89
|
10099
|
paulo@89
|
10100 function dragEnd(d, i) {
|
paulo@89
|
10101 delete this.parentNode.__origin__;
|
paulo@89
|
10102 delete dragging[d];
|
paulo@89
|
10103 d3.select(this.parentNode).attr("transform", "translate(" + x(d) + ")");
|
paulo@89
|
10104 foreground
|
paulo@89
|
10105 .attr("d", path);
|
paulo@89
|
10106 background
|
paulo@89
|
10107 .attr("d", path)
|
paulo@89
|
10108 .attr("visibility", null);
|
paulo@89
|
10109
|
paulo@89
|
10110 }
|
paulo@89
|
10111
|
paulo@89
|
10112 function position(d) {
|
paulo@89
|
10113 var v = dragging[d];
|
paulo@89
|
10114 return v == null ? x(d) : v;
|
paulo@89
|
10115 }
|
paulo@89
|
10116 });
|
paulo@89
|
10117
|
paulo@89
|
10118 return chart;
|
paulo@89
|
10119 }
|
paulo@89
|
10120
|
paulo@89
|
10121 //============================================================
|
paulo@89
|
10122 // Expose Public Variables
|
paulo@89
|
10123 //------------------------------------------------------------
|
paulo@89
|
10124
|
paulo@89
|
10125 chart.dispatch = dispatch;
|
paulo@89
|
10126 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
10127
|
paulo@89
|
10128 chart._options = Object.create({}, {
|
paulo@89
|
10129 // simple options, just get/set the necessary values
|
paulo@89
|
10130 width: {get: function(){return width;}, set: function(_){width= _;}},
|
paulo@89
|
10131 height: {get: function(){return height;}, set: function(_){height= _;}},
|
paulo@89
|
10132 dimensionNames: {get: function() { return dimensionNames;}, set: function(_){dimensionNames= _;}},
|
paulo@89
|
10133 dimensionFormats : {get: function(){return dimensionFormats;}, set: function (_){dimensionFormats=_;}},
|
paulo@89
|
10134 lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}},
|
paulo@89
|
10135
|
paulo@89
|
10136 // deprecated options
|
paulo@89
|
10137 dimensions: {get: function (){return dimensionNames;}, set: function(_){
|
paulo@89
|
10138 // deprecated after 1.8.1
|
paulo@89
|
10139 nv.deprecated('dimensions', 'use dimensionNames instead');
|
paulo@89
|
10140 dimensionNames = _;
|
paulo@89
|
10141 }},
|
paulo@89
|
10142
|
paulo@89
|
10143 // options that require extra logic in the setter
|
paulo@89
|
10144 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
10145 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
10146 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
10147 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
10148 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
10149 }},
|
paulo@89
|
10150 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
10151 color = nv.utils.getColor(_);
|
paulo@89
|
10152 }}
|
paulo@89
|
10153 });
|
paulo@89
|
10154
|
paulo@89
|
10155 nv.utils.initOptions(chart);
|
paulo@89
|
10156 return chart;
|
paulo@89
|
10157 };
|
paulo@89
|
10158 nv.models.pie = function() {
|
paulo@89
|
10159 "use strict";
|
paulo@89
|
10160
|
paulo@89
|
10161 //============================================================
|
paulo@89
|
10162 // Public Variables with Default Settings
|
paulo@89
|
10163 //------------------------------------------------------------
|
paulo@89
|
10164
|
paulo@89
|
10165 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
10166 , width = 500
|
paulo@89
|
10167 , height = 500
|
paulo@89
|
10168 , getX = function(d) { return d.x }
|
paulo@89
|
10169 , getY = function(d) { return d.y }
|
paulo@89
|
10170 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
10171 , container = null
|
paulo@89
|
10172 , color = nv.utils.defaultColor()
|
paulo@89
|
10173 , valueFormat = d3.format(',.2f')
|
paulo@89
|
10174 , showLabels = true
|
paulo@89
|
10175 , labelsOutside = false
|
paulo@89
|
10176 , labelType = "key"
|
paulo@89
|
10177 , labelThreshold = .02 //if slice percentage is under this, don't show label
|
paulo@89
|
10178 , donut = false
|
paulo@89
|
10179 , title = false
|
paulo@89
|
10180 , growOnHover = true
|
paulo@89
|
10181 , titleOffset = 0
|
paulo@89
|
10182 , labelSunbeamLayout = false
|
paulo@89
|
10183 , startAngle = false
|
paulo@89
|
10184 , padAngle = false
|
paulo@89
|
10185 , endAngle = false
|
paulo@89
|
10186 , cornerRadius = 0
|
paulo@89
|
10187 , donutRatio = 0.5
|
paulo@89
|
10188 , arcsRadius = []
|
paulo@89
|
10189 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
|
paulo@89
|
10190 ;
|
paulo@89
|
10191
|
paulo@89
|
10192 var arcs = [];
|
paulo@89
|
10193 var arcsOver = [];
|
paulo@89
|
10194
|
paulo@89
|
10195 //============================================================
|
paulo@89
|
10196 // chart function
|
paulo@89
|
10197 //------------------------------------------------------------
|
paulo@89
|
10198
|
paulo@89
|
10199 var renderWatch = nv.utils.renderWatch(dispatch);
|
paulo@89
|
10200
|
paulo@89
|
10201 function chart(selection) {
|
paulo@89
|
10202 renderWatch.reset();
|
paulo@89
|
10203 selection.each(function(data) {
|
paulo@89
|
10204 var availableWidth = width - margin.left - margin.right
|
paulo@89
|
10205 , availableHeight = height - margin.top - margin.bottom
|
paulo@89
|
10206 , radius = Math.min(availableWidth, availableHeight) / 2
|
paulo@89
|
10207 , arcsRadiusOuter = []
|
paulo@89
|
10208 , arcsRadiusInner = []
|
paulo@89
|
10209 ;
|
paulo@89
|
10210
|
paulo@89
|
10211 container = d3.select(this)
|
paulo@89
|
10212 if (arcsRadius.length === 0) {
|
paulo@89
|
10213 var outer = radius - radius / 5;
|
paulo@89
|
10214 var inner = donutRatio * radius;
|
paulo@89
|
10215 for (var i = 0; i < data[0].length; i++) {
|
paulo@89
|
10216 arcsRadiusOuter.push(outer);
|
paulo@89
|
10217 arcsRadiusInner.push(inner);
|
paulo@89
|
10218 }
|
paulo@89
|
10219 } else {
|
paulo@89
|
10220 arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
|
paulo@89
|
10221 arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
|
paulo@89
|
10222 donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
|
paulo@89
|
10223 }
|
paulo@89
|
10224 nv.utils.initSVG(container);
|
paulo@89
|
10225
|
paulo@89
|
10226 // Setup containers and skeleton of chart
|
paulo@89
|
10227 var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
|
paulo@89
|
10228 var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
|
paulo@89
|
10229 var gEnter = wrapEnter.append('g');
|
paulo@89
|
10230 var g = wrap.select('g');
|
paulo@89
|
10231 var g_pie = gEnter.append('g').attr('class', 'nv-pie');
|
paulo@89
|
10232 gEnter.append('g').attr('class', 'nv-pieLabels');
|
paulo@89
|
10233
|
paulo@89
|
10234 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
10235 g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
|
paulo@89
|
10236 g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
|
paulo@89
|
10237
|
paulo@89
|
10238 //
|
paulo@89
|
10239 container.on('click', function(d,i) {
|
paulo@89
|
10240 dispatch.chartClick({
|
paulo@89
|
10241 data: d,
|
paulo@89
|
10242 index: i,
|
paulo@89
|
10243 pos: d3.event,
|
paulo@89
|
10244 id: id
|
paulo@89
|
10245 });
|
paulo@89
|
10246 });
|
paulo@89
|
10247
|
paulo@89
|
10248 arcs = [];
|
paulo@89
|
10249 arcsOver = [];
|
paulo@89
|
10250 for (var i = 0; i < data[0].length; i++) {
|
paulo@89
|
10251
|
paulo@89
|
10252 var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
|
paulo@89
|
10253 var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);
|
paulo@89
|
10254
|
paulo@89
|
10255 if (startAngle !== false) {
|
paulo@89
|
10256 arc.startAngle(startAngle);
|
paulo@89
|
10257 arcOver.startAngle(startAngle);
|
paulo@89
|
10258 }
|
paulo@89
|
10259 if (endAngle !== false) {
|
paulo@89
|
10260 arc.endAngle(endAngle);
|
paulo@89
|
10261 arcOver.endAngle(endAngle);
|
paulo@89
|
10262 }
|
paulo@89
|
10263 if (donut) {
|
paulo@89
|
10264 arc.innerRadius(arcsRadiusInner[i]);
|
paulo@89
|
10265 arcOver.innerRadius(arcsRadiusInner[i]);
|
paulo@89
|
10266 }
|
paulo@89
|
10267
|
paulo@89
|
10268 if (arc.cornerRadius && cornerRadius) {
|
paulo@89
|
10269 arc.cornerRadius(cornerRadius);
|
paulo@89
|
10270 arcOver.cornerRadius(cornerRadius);
|
paulo@89
|
10271 }
|
paulo@89
|
10272
|
paulo@89
|
10273 arcs.push(arc);
|
paulo@89
|
10274 arcsOver.push(arcOver);
|
paulo@89
|
10275 }
|
paulo@89
|
10276
|
paulo@89
|
10277 // Setup the Pie chart and choose the data element
|
paulo@89
|
10278 var pie = d3.layout.pie()
|
paulo@89
|
10279 .sort(null)
|
paulo@89
|
10280 .value(function(d) { return d.disabled ? 0 : getY(d) });
|
paulo@89
|
10281
|
paulo@89
|
10282 // padAngle added in d3 3.5
|
paulo@89
|
10283 if (pie.padAngle && padAngle) {
|
paulo@89
|
10284 pie.padAngle(padAngle);
|
paulo@89
|
10285 }
|
paulo@89
|
10286
|
paulo@89
|
10287 // if title is specified and donut, put it in the middle
|
paulo@89
|
10288 if (donut && title) {
|
paulo@89
|
10289 g_pie.append("text").attr('class', 'nv-pie-title');
|
paulo@89
|
10290
|
paulo@89
|
10291 wrap.select('.nv-pie-title')
|
paulo@89
|
10292 .style("text-anchor", "middle")
|
paulo@89
|
10293 .text(function (d) {
|
paulo@89
|
10294 return title;
|
paulo@89
|
10295 })
|
paulo@89
|
10296 .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px")
|
paulo@89
|
10297 .attr("dy", "0.35em") // trick to vertically center text
|
paulo@89
|
10298 .attr('transform', function(d, i) {
|
paulo@89
|
10299 return 'translate(0, '+ titleOffset + ')';
|
paulo@89
|
10300 });
|
paulo@89
|
10301 }
|
paulo@89
|
10302
|
paulo@89
|
10303 var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
|
paulo@89
|
10304 var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
|
paulo@89
|
10305
|
paulo@89
|
10306 slices.exit().remove();
|
paulo@89
|
10307 pieLabels.exit().remove();
|
paulo@89
|
10308
|
paulo@89
|
10309 var ae = slices.enter().append('g');
|
paulo@89
|
10310 ae.attr('class', 'nv-slice');
|
paulo@89
|
10311 ae.on('mouseover', function(d, i) {
|
paulo@89
|
10312 d3.select(this).classed('hover', true);
|
paulo@89
|
10313 if (growOnHover) {
|
paulo@89
|
10314 d3.select(this).select("path").transition()
|
paulo@89
|
10315 .duration(70)
|
paulo@89
|
10316 .attr("d", arcsOver[i]);
|
paulo@89
|
10317 }
|
paulo@89
|
10318 dispatch.elementMouseover({
|
paulo@89
|
10319 data: d.data,
|
paulo@89
|
10320 index: i,
|
paulo@89
|
10321 color: d3.select(this).style("fill")
|
paulo@89
|
10322 });
|
paulo@89
|
10323 });
|
paulo@89
|
10324 ae.on('mouseout', function(d, i) {
|
paulo@89
|
10325 d3.select(this).classed('hover', false);
|
paulo@89
|
10326 if (growOnHover) {
|
paulo@89
|
10327 d3.select(this).select("path").transition()
|
paulo@89
|
10328 .duration(50)
|
paulo@89
|
10329 .attr("d", arcs[i]);
|
paulo@89
|
10330 }
|
paulo@89
|
10331 dispatch.elementMouseout({data: d.data, index: i});
|
paulo@89
|
10332 });
|
paulo@89
|
10333 ae.on('mousemove', function(d, i) {
|
paulo@89
|
10334 dispatch.elementMousemove({data: d.data, index: i});
|
paulo@89
|
10335 });
|
paulo@89
|
10336 ae.on('click', function(d, i) {
|
paulo@89
|
10337 dispatch.elementClick({
|
paulo@89
|
10338 data: d.data,
|
paulo@89
|
10339 index: i,
|
paulo@89
|
10340 color: d3.select(this).style("fill")
|
paulo@89
|
10341 });
|
paulo@89
|
10342 });
|
paulo@89
|
10343 ae.on('dblclick', function(d, i) {
|
paulo@89
|
10344 dispatch.elementDblClick({
|
paulo@89
|
10345 data: d.data,
|
paulo@89
|
10346 index: i,
|
paulo@89
|
10347 color: d3.select(this).style("fill")
|
paulo@89
|
10348 });
|
paulo@89
|
10349 });
|
paulo@89
|
10350
|
paulo@89
|
10351 slices.attr('fill', function(d,i) { return color(d.data, i); });
|
paulo@89
|
10352 slices.attr('stroke', function(d,i) { return color(d.data, i); });
|
paulo@89
|
10353
|
paulo@89
|
10354 var paths = ae.append('path').each(function(d) {
|
paulo@89
|
10355 this._current = d;
|
paulo@89
|
10356 });
|
paulo@89
|
10357
|
paulo@89
|
10358 slices.select('path')
|
paulo@89
|
10359 .transition()
|
paulo@89
|
10360 .attr('d', function (d, i) { return arcs[i](d); })
|
paulo@89
|
10361 .attrTween('d', arcTween);
|
paulo@89
|
10362
|
paulo@89
|
10363 if (showLabels) {
|
paulo@89
|
10364 // This does the normal label
|
paulo@89
|
10365 var labelsArc = [];
|
paulo@89
|
10366 for (var i = 0; i < data[0].length; i++) {
|
paulo@89
|
10367 labelsArc.push(arcs[i]);
|
paulo@89
|
10368
|
paulo@89
|
10369 if (labelsOutside) {
|
paulo@89
|
10370 if (donut) {
|
paulo@89
|
10371 labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius());
|
paulo@89
|
10372 if (startAngle !== false) labelsArc[i].startAngle(startAngle);
|
paulo@89
|
10373 if (endAngle !== false) labelsArc[i].endAngle(endAngle);
|
paulo@89
|
10374 }
|
paulo@89
|
10375 } else if (!donut) {
|
paulo@89
|
10376 labelsArc[i].innerRadius(0);
|
paulo@89
|
10377 }
|
paulo@89
|
10378 }
|
paulo@89
|
10379
|
paulo@89
|
10380 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
|
paulo@89
|
10381 var group = d3.select(this);
|
paulo@89
|
10382
|
paulo@89
|
10383 group.attr('transform', function (d, i) {
|
paulo@89
|
10384 if (labelSunbeamLayout) {
|
paulo@89
|
10385 d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
|
paulo@89
|
10386 d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
|
paulo@89
|
10387 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
|
paulo@89
|
10388 if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
|
paulo@89
|
10389 rotateAngle -= 90;
|
paulo@89
|
10390 } else {
|
paulo@89
|
10391 rotateAngle += 90;
|
paulo@89
|
10392 }
|
paulo@89
|
10393 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
|
paulo@89
|
10394 } else {
|
paulo@89
|
10395 d.outerRadius = radius + 10; // Set Outer Coordinate
|
paulo@89
|
10396 d.innerRadius = radius + 15; // Set Inner Coordinate
|
paulo@89
|
10397 return 'translate(' + labelsArc[i].centroid(d) + ')'
|
paulo@89
|
10398 }
|
paulo@89
|
10399 });
|
paulo@89
|
10400
|
paulo@89
|
10401 group.append('rect')
|
paulo@89
|
10402 .style('stroke', '#fff')
|
paulo@89
|
10403 .style('fill', '#fff')
|
paulo@89
|
10404 .attr("rx", 3)
|
paulo@89
|
10405 .attr("ry", 3);
|
paulo@89
|
10406
|
paulo@89
|
10407 group.append('text')
|
paulo@89
|
10408 .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned
|
paulo@89
|
10409 .style('fill', '#000')
|
paulo@89
|
10410 });
|
paulo@89
|
10411
|
paulo@89
|
10412 var labelLocationHash = {};
|
paulo@89
|
10413 var avgHeight = 14;
|
paulo@89
|
10414 var avgWidth = 140;
|
paulo@89
|
10415 var createHashKey = function(coordinates) {
|
paulo@89
|
10416 return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
|
paulo@89
|
10417 };
|
paulo@89
|
10418
|
paulo@89
|
10419 pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) {
|
paulo@89
|
10420 if (labelSunbeamLayout) {
|
paulo@89
|
10421 d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
|
paulo@89
|
10422 d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
|
paulo@89
|
10423 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
|
paulo@89
|
10424 if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
|
paulo@89
|
10425 rotateAngle -= 90;
|
paulo@89
|
10426 } else {
|
paulo@89
|
10427 rotateAngle += 90;
|
paulo@89
|
10428 }
|
paulo@89
|
10429 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
|
paulo@89
|
10430 } else {
|
paulo@89
|
10431 d.outerRadius = radius + 10; // Set Outer Coordinate
|
paulo@89
|
10432 d.innerRadius = radius + 15; // Set Inner Coordinate
|
paulo@89
|
10433
|
paulo@89
|
10434 /*
|
paulo@89
|
10435 Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
|
paulo@89
|
10436 Each label location is hashed, and if a hash collision occurs, we assume an overlap.
|
paulo@89
|
10437 Adjust the label's y-position to remove the overlap.
|
paulo@89
|
10438 */
|
paulo@89
|
10439 var center = labelsArc[i].centroid(d);
|
paulo@89
|
10440 if (d.value) {
|
paulo@89
|
10441 var hashKey = createHashKey(center);
|
paulo@89
|
10442 if (labelLocationHash[hashKey]) {
|
paulo@89
|
10443 center[1] -= avgHeight;
|
paulo@89
|
10444 }
|
paulo@89
|
10445 labelLocationHash[createHashKey(center)] = true;
|
paulo@89
|
10446 }
|
paulo@89
|
10447 return 'translate(' + center + ')'
|
paulo@89
|
10448 }
|
paulo@89
|
10449 });
|
paulo@89
|
10450
|
paulo@89
|
10451 pieLabels.select(".nv-label text")
|
paulo@89
|
10452 .style('text-anchor', function(d,i) {
|
paulo@89
|
10453 //center the text on it's origin or begin/end if orthogonal aligned
|
paulo@89
|
10454 return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle';
|
paulo@89
|
10455 })
|
paulo@89
|
10456 .text(function(d, i) {
|
paulo@89
|
10457 var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
|
paulo@89
|
10458 var label = '';
|
paulo@89
|
10459 if (!d.value || percent < labelThreshold) return '';
|
paulo@89
|
10460
|
paulo@89
|
10461 if(typeof labelType === 'function') {
|
paulo@89
|
10462 label = labelType(d, i, {
|
paulo@89
|
10463 'key': getX(d.data),
|
paulo@89
|
10464 'value': getY(d.data),
|
paulo@89
|
10465 'percent': valueFormat(percent)
|
paulo@89
|
10466 });
|
paulo@89
|
10467 } else {
|
paulo@89
|
10468 switch (labelType) {
|
paulo@89
|
10469 case 'key':
|
paulo@89
|
10470 label = getX(d.data);
|
paulo@89
|
10471 break;
|
paulo@89
|
10472 case 'value':
|
paulo@89
|
10473 label = valueFormat(getY(d.data));
|
paulo@89
|
10474 break;
|
paulo@89
|
10475 case 'percent':
|
paulo@89
|
10476 label = d3.format('%')(percent);
|
paulo@89
|
10477 break;
|
paulo@89
|
10478 }
|
paulo@89
|
10479 }
|
paulo@89
|
10480 return label;
|
paulo@89
|
10481 })
|
paulo@89
|
10482 ;
|
paulo@89
|
10483 }
|
paulo@89
|
10484
|
paulo@89
|
10485
|
paulo@89
|
10486 // Computes the angle of an arc, converting from radians to degrees.
|
paulo@89
|
10487 function angle(d) {
|
paulo@89
|
10488 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
|
paulo@89
|
10489 return a > 90 ? a - 180 : a;
|
paulo@89
|
10490 }
|
paulo@89
|
10491
|
paulo@89
|
10492 function arcTween(a, idx) {
|
paulo@89
|
10493 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
|
paulo@89
|
10494 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
|
paulo@89
|
10495 if (!donut) a.innerRadius = 0;
|
paulo@89
|
10496 var i = d3.interpolate(this._current, a);
|
paulo@89
|
10497 this._current = i(0);
|
paulo@89
|
10498 return function (t) {
|
paulo@89
|
10499 return arcs[idx](i(t));
|
paulo@89
|
10500 };
|
paulo@89
|
10501 }
|
paulo@89
|
10502 });
|
paulo@89
|
10503
|
paulo@89
|
10504 renderWatch.renderEnd('pie immediate');
|
paulo@89
|
10505 return chart;
|
paulo@89
|
10506 }
|
paulo@89
|
10507
|
paulo@89
|
10508 //============================================================
|
paulo@89
|
10509 // Expose Public Variables
|
paulo@89
|
10510 //------------------------------------------------------------
|
paulo@89
|
10511
|
paulo@89
|
10512 chart.dispatch = dispatch;
|
paulo@89
|
10513 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
10514
|
paulo@89
|
10515 chart._options = Object.create({}, {
|
paulo@89
|
10516 // simple options, just get/set the necessary values
|
paulo@89
|
10517 arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } },
|
paulo@89
|
10518 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
10519 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
10520 showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
|
paulo@89
|
10521 title: {get: function(){return title;}, set: function(_){title=_;}},
|
paulo@89
|
10522 titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
|
paulo@89
|
10523 labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
|
paulo@89
|
10524 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
|
paulo@89
|
10525 x: {get: function(){return getX;}, set: function(_){getX=_;}},
|
paulo@89
|
10526 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
10527 endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
|
paulo@89
|
10528 startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
|
paulo@89
|
10529 padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
|
paulo@89
|
10530 cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
|
paulo@89
|
10531 donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
|
paulo@89
|
10532 labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}},
|
paulo@89
|
10533 labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
|
paulo@89
|
10534 donut: {get: function(){return donut;}, set: function(_){donut=_;}},
|
paulo@89
|
10535 growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
|
paulo@89
|
10536
|
paulo@89
|
10537 // depreciated after 1.7.1
|
paulo@89
|
10538 pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
|
paulo@89
|
10539 labelsOutside=_;
|
paulo@89
|
10540 nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
|
paulo@89
|
10541 }},
|
paulo@89
|
10542 // depreciated after 1.7.1
|
paulo@89
|
10543 donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
|
paulo@89
|
10544 labelsOutside=_;
|
paulo@89
|
10545 nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
|
paulo@89
|
10546 }},
|
paulo@89
|
10547 // deprecated after 1.7.1
|
paulo@89
|
10548 labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
|
paulo@89
|
10549 valueFormat=_;
|
paulo@89
|
10550 nv.deprecated('labelFormat','use valueFormat instead');
|
paulo@89
|
10551 }},
|
paulo@89
|
10552
|
paulo@89
|
10553 // options that require extra logic in the setter
|
paulo@89
|
10554 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
10555 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
|
paulo@89
|
10556 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
|
paulo@89
|
10557 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
|
paulo@89
|
10558 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
|
paulo@89
|
10559 }},
|
paulo@89
|
10560 y: {get: function(){return getY;}, set: function(_){
|
paulo@89
|
10561 getY=d3.functor(_);
|
paulo@89
|
10562 }},
|
paulo@89
|
10563 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
10564 color=nv.utils.getColor(_);
|
paulo@89
|
10565 }},
|
paulo@89
|
10566 labelType: {get: function(){return labelType;}, set: function(_){
|
paulo@89
|
10567 labelType= _ || 'key';
|
paulo@89
|
10568 }}
|
paulo@89
|
10569 });
|
paulo@89
|
10570
|
paulo@89
|
10571 nv.utils.initOptions(chart);
|
paulo@89
|
10572 return chart;
|
paulo@89
|
10573 };
|
paulo@89
|
10574 nv.models.pieChart = function() {
|
paulo@89
|
10575 "use strict";
|
paulo@89
|
10576
|
paulo@89
|
10577 //============================================================
|
paulo@89
|
10578 // Public Variables with Default Settings
|
paulo@89
|
10579 //------------------------------------------------------------
|
paulo@89
|
10580
|
paulo@89
|
10581 var pie = nv.models.pie();
|
paulo@89
|
10582 var legend = nv.models.legend();
|
paulo@89
|
10583 var tooltip = nv.models.tooltip();
|
paulo@89
|
10584
|
paulo@89
|
10585 var margin = {top: 30, right: 20, bottom: 20, left: 20}
|
paulo@89
|
10586 , width = null
|
paulo@89
|
10587 , height = null
|
paulo@89
|
10588 , showLegend = true
|
paulo@89
|
10589 , legendPosition = "top"
|
paulo@89
|
10590 , color = nv.utils.defaultColor()
|
paulo@89
|
10591 , state = nv.utils.state()
|
paulo@89
|
10592 , defaultState = null
|
paulo@89
|
10593 , noData = null
|
paulo@89
|
10594 , duration = 250
|
paulo@89
|
10595 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
|
paulo@89
|
10596 ;
|
paulo@89
|
10597
|
paulo@89
|
10598 tooltip
|
paulo@89
|
10599 .headerEnabled(false)
|
paulo@89
|
10600 .duration(0)
|
paulo@89
|
10601 .valueFormatter(function(d, i) {
|
paulo@89
|
10602 return pie.valueFormat()(d, i);
|
paulo@89
|
10603 });
|
paulo@89
|
10604
|
paulo@89
|
10605 //============================================================
|
paulo@89
|
10606 // Private Variables
|
paulo@89
|
10607 //------------------------------------------------------------
|
paulo@89
|
10608
|
paulo@89
|
10609 var renderWatch = nv.utils.renderWatch(dispatch);
|
paulo@89
|
10610
|
paulo@89
|
10611 var stateGetter = function(data) {
|
paulo@89
|
10612 return function(){
|
paulo@89
|
10613 return {
|
paulo@89
|
10614 active: data.map(function(d) { return !d.disabled })
|
paulo@89
|
10615 };
|
paulo@89
|
10616 }
|
paulo@89
|
10617 };
|
paulo@89
|
10618
|
paulo@89
|
10619 var stateSetter = function(data) {
|
paulo@89
|
10620 return function(state) {
|
paulo@89
|
10621 if (state.active !== undefined) {
|
paulo@89
|
10622 data.forEach(function (series, i) {
|
paulo@89
|
10623 series.disabled = !state.active[i];
|
paulo@89
|
10624 });
|
paulo@89
|
10625 }
|
paulo@89
|
10626 }
|
paulo@89
|
10627 };
|
paulo@89
|
10628
|
paulo@89
|
10629 //============================================================
|
paulo@89
|
10630 // Chart function
|
paulo@89
|
10631 //------------------------------------------------------------
|
paulo@89
|
10632
|
paulo@89
|
10633 function chart(selection) {
|
paulo@89
|
10634 renderWatch.reset();
|
paulo@89
|
10635 renderWatch.models(pie);
|
paulo@89
|
10636
|
paulo@89
|
10637 selection.each(function(data) {
|
paulo@89
|
10638 var container = d3.select(this);
|
paulo@89
|
10639 nv.utils.initSVG(container);
|
paulo@89
|
10640
|
paulo@89
|
10641 var that = this;
|
paulo@89
|
10642 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
10643 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
10644
|
paulo@89
|
10645 chart.update = function() { container.transition().call(chart); };
|
paulo@89
|
10646 chart.container = this;
|
paulo@89
|
10647
|
paulo@89
|
10648 state.setter(stateSetter(data), chart.update)
|
paulo@89
|
10649 .getter(stateGetter(data))
|
paulo@89
|
10650 .update();
|
paulo@89
|
10651
|
paulo@89
|
10652 //set state.disabled
|
paulo@89
|
10653 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
10654
|
paulo@89
|
10655 if (!defaultState) {
|
paulo@89
|
10656 var key;
|
paulo@89
|
10657 defaultState = {};
|
paulo@89
|
10658 for (key in state) {
|
paulo@89
|
10659 if (state[key] instanceof Array)
|
paulo@89
|
10660 defaultState[key] = state[key].slice(0);
|
paulo@89
|
10661 else
|
paulo@89
|
10662 defaultState[key] = state[key];
|
paulo@89
|
10663 }
|
paulo@89
|
10664 }
|
paulo@89
|
10665
|
paulo@89
|
10666 // Display No Data message if there's nothing to show.
|
paulo@89
|
10667 if (!data || !data.length) {
|
paulo@89
|
10668 nv.utils.noData(chart, container);
|
paulo@89
|
10669 return chart;
|
paulo@89
|
10670 } else {
|
paulo@89
|
10671 container.selectAll('.nv-noData').remove();
|
paulo@89
|
10672 }
|
paulo@89
|
10673
|
paulo@89
|
10674 // Setup containers and skeleton of chart
|
paulo@89
|
10675 var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
|
paulo@89
|
10676 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
|
paulo@89
|
10677 var g = wrap.select('g');
|
paulo@89
|
10678
|
paulo@89
|
10679 gEnter.append('g').attr('class', 'nv-pieWrap');
|
paulo@89
|
10680 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
10681
|
paulo@89
|
10682 // Legend
|
paulo@89
|
10683 if (showLegend) {
|
paulo@89
|
10684 if (legendPosition === "top") {
|
paulo@89
|
10685 legend.width( availableWidth ).key(pie.x());
|
paulo@89
|
10686
|
paulo@89
|
10687 wrap.select('.nv-legendWrap')
|
paulo@89
|
10688 .datum(data)
|
paulo@89
|
10689 .call(legend);
|
paulo@89
|
10690
|
paulo@89
|
10691 if ( margin.top != legend.height()) {
|
paulo@89
|
10692 margin.top = legend.height();
|
paulo@89
|
10693 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
10694 }
|
paulo@89
|
10695
|
paulo@89
|
10696 wrap.select('.nv-legendWrap')
|
paulo@89
|
10697 .attr('transform', 'translate(0,' + (-margin.top) +')');
|
paulo@89
|
10698 } else if (legendPosition === "right") {
|
paulo@89
|
10699 var legendWidth = nv.models.legend().width();
|
paulo@89
|
10700 if (availableWidth / 2 < legendWidth) {
|
paulo@89
|
10701 legendWidth = (availableWidth / 2)
|
paulo@89
|
10702 }
|
paulo@89
|
10703 legend.height(availableHeight).key(pie.x());
|
paulo@89
|
10704 legend.width(legendWidth);
|
paulo@89
|
10705 availableWidth -= legend.width();
|
paulo@89
|
10706
|
paulo@89
|
10707 wrap.select('.nv-legendWrap')
|
paulo@89
|
10708 .datum(data)
|
paulo@89
|
10709 .call(legend)
|
paulo@89
|
10710 .attr('transform', 'translate(' + (availableWidth) +',0)');
|
paulo@89
|
10711 }
|
paulo@89
|
10712 }
|
paulo@89
|
10713 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
10714
|
paulo@89
|
10715 // Main Chart Component(s)
|
paulo@89
|
10716 pie.width(availableWidth).height(availableHeight);
|
paulo@89
|
10717 var pieWrap = g.select('.nv-pieWrap').datum([data]);
|
paulo@89
|
10718 d3.transition(pieWrap).call(pie);
|
paulo@89
|
10719
|
paulo@89
|
10720 //============================================================
|
paulo@89
|
10721 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
10722 //------------------------------------------------------------
|
paulo@89
|
10723
|
paulo@89
|
10724 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
10725 for (var key in newState) {
|
paulo@89
|
10726 state[key] = newState[key];
|
paulo@89
|
10727 }
|
paulo@89
|
10728 dispatch.stateChange(state);
|
paulo@89
|
10729 chart.update();
|
paulo@89
|
10730 });
|
paulo@89
|
10731
|
paulo@89
|
10732 // Update chart from a state object passed to event handler
|
paulo@89
|
10733 dispatch.on('changeState', function(e) {
|
paulo@89
|
10734 if (typeof e.disabled !== 'undefined') {
|
paulo@89
|
10735 data.forEach(function(series,i) {
|
paulo@89
|
10736 series.disabled = e.disabled[i];
|
paulo@89
|
10737 });
|
paulo@89
|
10738 state.disabled = e.disabled;
|
paulo@89
|
10739 }
|
paulo@89
|
10740 chart.update();
|
paulo@89
|
10741 });
|
paulo@89
|
10742 });
|
paulo@89
|
10743
|
paulo@89
|
10744 renderWatch.renderEnd('pieChart immediate');
|
paulo@89
|
10745 return chart;
|
paulo@89
|
10746 }
|
paulo@89
|
10747
|
paulo@89
|
10748 //============================================================
|
paulo@89
|
10749 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
10750 //------------------------------------------------------------
|
paulo@89
|
10751
|
paulo@89
|
10752 pie.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
10753 evt['series'] = {
|
paulo@89
|
10754 key: chart.x()(evt.data),
|
paulo@89
|
10755 value: chart.y()(evt.data),
|
paulo@89
|
10756 color: evt.color
|
paulo@89
|
10757 };
|
paulo@89
|
10758 tooltip.data(evt).hidden(false);
|
paulo@89
|
10759 });
|
paulo@89
|
10760
|
paulo@89
|
10761 pie.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
10762 tooltip.hidden(true);
|
paulo@89
|
10763 });
|
paulo@89
|
10764
|
paulo@89
|
10765 pie.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
10766 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
10767 });
|
paulo@89
|
10768
|
paulo@89
|
10769 //============================================================
|
paulo@89
|
10770 // Expose Public Variables
|
paulo@89
|
10771 //------------------------------------------------------------
|
paulo@89
|
10772
|
paulo@89
|
10773 // expose chart's sub-components
|
paulo@89
|
10774 chart.legend = legend;
|
paulo@89
|
10775 chart.dispatch = dispatch;
|
paulo@89
|
10776 chart.pie = pie;
|
paulo@89
|
10777 chart.tooltip = tooltip;
|
paulo@89
|
10778 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
10779
|
paulo@89
|
10780 // use Object get/set functionality to map between vars and chart functions
|
paulo@89
|
10781 chart._options = Object.create({}, {
|
paulo@89
|
10782 // simple options, just get/set the necessary values
|
paulo@89
|
10783 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
10784 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
10785 legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
|
paulo@89
|
10786 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
10787
|
paulo@89
|
10788 // deprecated options
|
paulo@89
|
10789 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
10790 // deprecated after 1.7.1
|
paulo@89
|
10791 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
10792 tooltip.enabled(!!_);
|
paulo@89
|
10793 }},
|
paulo@89
|
10794 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
10795 // deprecated after 1.7.1
|
paulo@89
|
10796 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
10797 tooltip.contentGenerator(_);
|
paulo@89
|
10798 }},
|
paulo@89
|
10799
|
paulo@89
|
10800 // options that require extra logic in the setter
|
paulo@89
|
10801 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
10802 color = _;
|
paulo@89
|
10803 legend.color(color);
|
paulo@89
|
10804 pie.color(color);
|
paulo@89
|
10805 }},
|
paulo@89
|
10806 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
10807 duration = _;
|
paulo@89
|
10808 renderWatch.reset(duration);
|
paulo@89
|
10809 }},
|
paulo@89
|
10810 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
10811 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
10812 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
10813 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
10814 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
10815 }}
|
paulo@89
|
10816 });
|
paulo@89
|
10817 nv.utils.inheritOptions(chart, pie);
|
paulo@89
|
10818 nv.utils.initOptions(chart);
|
paulo@89
|
10819 return chart;
|
paulo@89
|
10820 };
|
paulo@89
|
10821
|
paulo@89
|
10822 nv.models.scatter = function() {
|
paulo@89
|
10823 "use strict";
|
paulo@89
|
10824
|
paulo@89
|
10825 //============================================================
|
paulo@89
|
10826 // Public Variables with Default Settings
|
paulo@89
|
10827 //------------------------------------------------------------
|
paulo@89
|
10828
|
paulo@89
|
10829 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
10830 , width = null
|
paulo@89
|
10831 , height = null
|
paulo@89
|
10832 , color = nv.utils.defaultColor() // chooses color
|
paulo@89
|
10833 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
|
paulo@89
|
10834 , container = null
|
paulo@89
|
10835 , x = d3.scale.linear()
|
paulo@89
|
10836 , y = d3.scale.linear()
|
paulo@89
|
10837 , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
|
paulo@89
|
10838 , getX = function(d) { return d.x } // accessor to get the x value
|
paulo@89
|
10839 , getY = function(d) { return d.y } // accessor to get the y value
|
paulo@89
|
10840 , getSize = function(d) { return d.size || 1} // accessor to get the point size
|
paulo@89
|
10841 , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
|
paulo@89
|
10842 , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
|
paulo@89
|
10843 , forceY = [] // List of numbers to Force into the Y scale
|
paulo@89
|
10844 , forceSize = [] // List of numbers to Force into the Size scale
|
paulo@89
|
10845 , interactive = true // If true, plots a voronoi overlay for advanced point intersection
|
paulo@89
|
10846 , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
|
paulo@89
|
10847 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
|
paulo@89
|
10848 , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
|
paulo@89
|
10849 , clipEdge = false // if true, masks points within x and y scale
|
paulo@89
|
10850 , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
|
paulo@89
|
10851 , showVoronoi = false // display the voronoi areas
|
paulo@89
|
10852 , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
|
paulo@89
|
10853 , xDomain = null // Override x domain (skips the calculation from data)
|
paulo@89
|
10854 , yDomain = null // Override y domain
|
paulo@89
|
10855 , xRange = null // Override x range
|
paulo@89
|
10856 , yRange = null // Override y range
|
paulo@89
|
10857 , sizeDomain = null // Override point size domain
|
paulo@89
|
10858 , sizeRange = null
|
paulo@89
|
10859 , singlePoint = false
|
paulo@89
|
10860 , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
|
paulo@89
|
10861 , useVoronoi = true
|
paulo@89
|
10862 , duration = 250
|
paulo@89
|
10863 ;
|
paulo@89
|
10864
|
paulo@89
|
10865
|
paulo@89
|
10866 //============================================================
|
paulo@89
|
10867 // Private Variables
|
paulo@89
|
10868 //------------------------------------------------------------
|
paulo@89
|
10869
|
paulo@89
|
10870 var x0, y0, z0 // used to store previous scales
|
paulo@89
|
10871 , timeoutID
|
paulo@89
|
10872 , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
|
paulo@89
|
10873 , renderWatch = nv.utils.renderWatch(dispatch, duration)
|
paulo@89
|
10874 , _sizeRange_def = [16, 256]
|
paulo@89
|
10875 ;
|
paulo@89
|
10876
|
paulo@89
|
10877 function chart(selection) {
|
paulo@89
|
10878 renderWatch.reset();
|
paulo@89
|
10879 selection.each(function(data) {
|
paulo@89
|
10880 container = d3.select(this);
|
paulo@89
|
10881 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
10882 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
10883
|
paulo@89
|
10884 nv.utils.initSVG(container);
|
paulo@89
|
10885
|
paulo@89
|
10886 //add series index to each data point for reference
|
paulo@89
|
10887 data.forEach(function(series, i) {
|
paulo@89
|
10888 series.values.forEach(function(point) {
|
paulo@89
|
10889 point.series = i;
|
paulo@89
|
10890 });
|
paulo@89
|
10891 });
|
paulo@89
|
10892
|
paulo@89
|
10893 // Setup Scales
|
paulo@89
|
10894 // remap and flatten the data for use in calculating the scales' domains
|
paulo@89
|
10895 var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
|
paulo@89
|
10896 d3.merge(
|
paulo@89
|
10897 data.map(function(d) {
|
paulo@89
|
10898 return d.values.map(function(d,i) {
|
paulo@89
|
10899 return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
|
paulo@89
|
10900 })
|
paulo@89
|
10901 })
|
paulo@89
|
10902 );
|
paulo@89
|
10903
|
paulo@89
|
10904 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
|
paulo@89
|
10905
|
paulo@89
|
10906 if (padData && data[0])
|
paulo@89
|
10907 x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
|
paulo@89
|
10908 //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
|
paulo@89
|
10909 else
|
paulo@89
|
10910 x.range(xRange || [0, availableWidth]);
|
paulo@89
|
10911
|
paulo@89
|
10912 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
|
paulo@89
|
10913 .range(yRange || [availableHeight, 0]);
|
paulo@89
|
10914
|
paulo@89
|
10915 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
|
paulo@89
|
10916 .range(sizeRange || _sizeRange_def);
|
paulo@89
|
10917
|
paulo@89
|
10918 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
|
paulo@89
|
10919 singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1];
|
paulo@89
|
10920
|
paulo@89
|
10921 if (x.domain()[0] === x.domain()[1])
|
paulo@89
|
10922 x.domain()[0] ?
|
paulo@89
|
10923 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
|
paulo@89
|
10924 : x.domain([-1,1]);
|
paulo@89
|
10925
|
paulo@89
|
10926 if (y.domain()[0] === y.domain()[1])
|
paulo@89
|
10927 y.domain()[0] ?
|
paulo@89
|
10928 y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
|
paulo@89
|
10929 : y.domain([-1,1]);
|
paulo@89
|
10930
|
paulo@89
|
10931 if ( isNaN(x.domain()[0])) {
|
paulo@89
|
10932 x.domain([-1,1]);
|
paulo@89
|
10933 }
|
paulo@89
|
10934
|
paulo@89
|
10935 if ( isNaN(y.domain()[0])) {
|
paulo@89
|
10936 y.domain([-1,1]);
|
paulo@89
|
10937 }
|
paulo@89
|
10938
|
paulo@89
|
10939 x0 = x0 || x;
|
paulo@89
|
10940 y0 = y0 || y;
|
paulo@89
|
10941 z0 = z0 || z;
|
paulo@89
|
10942
|
paulo@89
|
10943 // Setup containers and skeleton of chart
|
paulo@89
|
10944 var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
|
paulo@89
|
10945 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
|
paulo@89
|
10946 var defsEnter = wrapEnter.append('defs');
|
paulo@89
|
10947 var gEnter = wrapEnter.append('g');
|
paulo@89
|
10948 var g = wrap.select('g');
|
paulo@89
|
10949
|
paulo@89
|
10950 wrap.classed('nv-single-point', singlePoint);
|
paulo@89
|
10951 gEnter.append('g').attr('class', 'nv-groups');
|
paulo@89
|
10952 gEnter.append('g').attr('class', 'nv-point-paths');
|
paulo@89
|
10953 wrapEnter.append('g').attr('class', 'nv-point-clips');
|
paulo@89
|
10954
|
paulo@89
|
10955 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
10956
|
paulo@89
|
10957 defsEnter.append('clipPath')
|
paulo@89
|
10958 .attr('id', 'nv-edge-clip-' + id)
|
paulo@89
|
10959 .append('rect');
|
paulo@89
|
10960
|
paulo@89
|
10961 wrap.select('#nv-edge-clip-' + id + ' rect')
|
paulo@89
|
10962 .attr('width', availableWidth)
|
paulo@89
|
10963 .attr('height', (availableHeight > 0) ? availableHeight : 0);
|
paulo@89
|
10964
|
paulo@89
|
10965 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
|
paulo@89
|
10966
|
paulo@89
|
10967 function updateInteractiveLayer() {
|
paulo@89
|
10968 // Always clear needs-update flag regardless of whether or not
|
paulo@89
|
10969 // we will actually do anything (avoids needless invocations).
|
paulo@89
|
10970 needsUpdate = false;
|
paulo@89
|
10971
|
paulo@89
|
10972 if (!interactive) return false;
|
paulo@89
|
10973
|
paulo@89
|
10974 // inject series and point index for reference into voronoi
|
paulo@89
|
10975 if (useVoronoi === true) {
|
paulo@89
|
10976 var vertices = d3.merge(data.map(function(group, groupIndex) {
|
paulo@89
|
10977 return group.values
|
paulo@89
|
10978 .map(function(point, pointIndex) {
|
paulo@89
|
10979 // *Adding noise to make duplicates very unlikely
|
paulo@89
|
10980 // *Injecting series and point index for reference
|
paulo@89
|
10981 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
|
paulo@89
|
10982 */
|
paulo@89
|
10983 var pX = getX(point,pointIndex);
|
paulo@89
|
10984 var pY = getY(point,pointIndex);
|
paulo@89
|
10985
|
paulo@89
|
10986 return [x(pX)+ Math.random() * 1e-4,
|
paulo@89
|
10987 y(pY)+ Math.random() * 1e-4,
|
paulo@89
|
10988 groupIndex,
|
paulo@89
|
10989 pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
|
paulo@89
|
10990 })
|
paulo@89
|
10991 .filter(function(pointArray, pointIndex) {
|
paulo@89
|
10992 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
|
paulo@89
|
10993 })
|
paulo@89
|
10994 })
|
paulo@89
|
10995 );
|
paulo@89
|
10996
|
paulo@89
|
10997 if (vertices.length == 0) return false; // No active points, we're done
|
paulo@89
|
10998 if (vertices.length < 3) {
|
paulo@89
|
10999 // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
|
paulo@89
|
11000 vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
|
paulo@89
|
11001 vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
|
paulo@89
|
11002 vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
|
paulo@89
|
11003 vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
|
paulo@89
|
11004 }
|
paulo@89
|
11005
|
paulo@89
|
11006 // keep voronoi sections from going more than 10 outside of graph
|
paulo@89
|
11007 // to avoid overlap with other things like legend etc
|
paulo@89
|
11008 var bounds = d3.geom.polygon([
|
paulo@89
|
11009 [-10,-10],
|
paulo@89
|
11010 [-10,height + 10],
|
paulo@89
|
11011 [width + 10,height + 10],
|
paulo@89
|
11012 [width + 10,-10]
|
paulo@89
|
11013 ]);
|
paulo@89
|
11014
|
paulo@89
|
11015 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
|
paulo@89
|
11016 return {
|
paulo@89
|
11017 'data': bounds.clip(d),
|
paulo@89
|
11018 'series': vertices[i][2],
|
paulo@89
|
11019 'point': vertices[i][3]
|
paulo@89
|
11020 }
|
paulo@89
|
11021 });
|
paulo@89
|
11022
|
paulo@89
|
11023 // nuke all voronoi paths on reload and recreate them
|
paulo@89
|
11024 wrap.select('.nv-point-paths').selectAll('path').remove();
|
paulo@89
|
11025 var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
|
paulo@89
|
11026 var vPointPaths = pointPaths
|
paulo@89
|
11027 .enter().append("svg:path")
|
paulo@89
|
11028 .attr("d", function(d) {
|
paulo@89
|
11029 if (!d || !d.data || d.data.length === 0)
|
paulo@89
|
11030 return 'M 0 0';
|
paulo@89
|
11031 else
|
paulo@89
|
11032 return "M" + d.data.join(",") + "Z";
|
paulo@89
|
11033 })
|
paulo@89
|
11034 .attr("id", function(d,i) {
|
paulo@89
|
11035 return "nv-path-"+i; })
|
paulo@89
|
11036 .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; })
|
paulo@89
|
11037 ;
|
paulo@89
|
11038
|
paulo@89
|
11039 // good for debugging point hover issues
|
paulo@89
|
11040 if (showVoronoi) {
|
paulo@89
|
11041 vPointPaths.style("fill", d3.rgb(230, 230, 230))
|
paulo@89
|
11042 .style('fill-opacity', 0.4)
|
paulo@89
|
11043 .style('stroke-opacity', 1)
|
paulo@89
|
11044 .style("stroke", d3.rgb(200,200,200));
|
paulo@89
|
11045 }
|
paulo@89
|
11046
|
paulo@89
|
11047 if (clipVoronoi) {
|
paulo@89
|
11048 // voronoi sections are already set to clip,
|
paulo@89
|
11049 // just create the circles with the IDs they expect
|
paulo@89
|
11050 wrap.select('.nv-point-clips').selectAll('clipPath').remove();
|
paulo@89
|
11051 wrap.select('.nv-point-clips').selectAll("clipPath")
|
paulo@89
|
11052 .data(vertices)
|
paulo@89
|
11053 .enter().append("svg:clipPath")
|
paulo@89
|
11054 .attr("id", function(d, i) { return "nv-clip-"+i;})
|
paulo@89
|
11055 .append("svg:circle")
|
paulo@89
|
11056 .attr('cx', function(d) { return d[0]; })
|
paulo@89
|
11057 .attr('cy', function(d) { return d[1]; })
|
paulo@89
|
11058 .attr('r', clipRadius);
|
paulo@89
|
11059 }
|
paulo@89
|
11060
|
paulo@89
|
11061 var mouseEventCallback = function(d, mDispatch) {
|
paulo@89
|
11062 if (needsUpdate) return 0;
|
paulo@89
|
11063 var series = data[d.series];
|
paulo@89
|
11064 if (series === undefined) return;
|
paulo@89
|
11065 var point = series.values[d.point];
|
paulo@89
|
11066 point['color'] = color(series, d.series);
|
paulo@89
|
11067
|
paulo@89
|
11068 // standardize attributes for tooltip.
|
paulo@89
|
11069 point['x'] = getX(point);
|
paulo@89
|
11070 point['y'] = getY(point);
|
paulo@89
|
11071
|
paulo@89
|
11072 // can't just get box of event node since it's actually a voronoi polygon
|
paulo@89
|
11073 var box = container.node().getBoundingClientRect();
|
paulo@89
|
11074 var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
paulo@89
|
11075 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
paulo@89
|
11076
|
paulo@89
|
11077 var pos = {
|
paulo@89
|
11078 left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10,
|
paulo@89
|
11079 top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10
|
paulo@89
|
11080 };
|
paulo@89
|
11081
|
paulo@89
|
11082 mDispatch({
|
paulo@89
|
11083 point: point,
|
paulo@89
|
11084 series: series,
|
paulo@89
|
11085 pos: pos,
|
paulo@89
|
11086 seriesIndex: d.series,
|
paulo@89
|
11087 pointIndex: d.point
|
paulo@89
|
11088 });
|
paulo@89
|
11089 };
|
paulo@89
|
11090
|
paulo@89
|
11091 pointPaths
|
paulo@89
|
11092 .on('click', function(d) {
|
paulo@89
|
11093 mouseEventCallback(d, dispatch.elementClick);
|
paulo@89
|
11094 })
|
paulo@89
|
11095 .on('dblclick', function(d) {
|
paulo@89
|
11096 mouseEventCallback(d, dispatch.elementDblClick);
|
paulo@89
|
11097 })
|
paulo@89
|
11098 .on('mouseover', function(d) {
|
paulo@89
|
11099 mouseEventCallback(d, dispatch.elementMouseover);
|
paulo@89
|
11100 })
|
paulo@89
|
11101 .on('mouseout', function(d, i) {
|
paulo@89
|
11102 mouseEventCallback(d, dispatch.elementMouseout);
|
paulo@89
|
11103 });
|
paulo@89
|
11104
|
paulo@89
|
11105 } else {
|
paulo@89
|
11106 // add event handlers to points instead voronoi paths
|
paulo@89
|
11107 wrap.select('.nv-groups').selectAll('.nv-group')
|
paulo@89
|
11108 .selectAll('.nv-point')
|
paulo@89
|
11109 //.data(dataWithPoints)
|
paulo@89
|
11110 //.style('pointer-events', 'auto') // recativate events, disabled by css
|
paulo@89
|
11111 .on('click', function(d,i) {
|
paulo@89
|
11112 //nv.log('test', d, i);
|
paulo@89
|
11113 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
|
paulo@89
|
11114 var series = data[d.series],
|
paulo@89
|
11115 point = series.values[i];
|
paulo@89
|
11116
|
paulo@89
|
11117 dispatch.elementClick({
|
paulo@89
|
11118 point: point,
|
paulo@89
|
11119 series: series,
|
paulo@89
|
11120 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
|
paulo@89
|
11121 seriesIndex: d.series,
|
paulo@89
|
11122 pointIndex: i
|
paulo@89
|
11123 });
|
paulo@89
|
11124 })
|
paulo@89
|
11125 .on('dblclick', function(d,i) {
|
paulo@89
|
11126 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
|
paulo@89
|
11127 var series = data[d.series],
|
paulo@89
|
11128 point = series.values[i];
|
paulo@89
|
11129
|
paulo@89
|
11130 dispatch.elementDblClick({
|
paulo@89
|
11131 point: point,
|
paulo@89
|
11132 series: series,
|
paulo@89
|
11133 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
|
paulo@89
|
11134 seriesIndex: d.series,
|
paulo@89
|
11135 pointIndex: i
|
paulo@89
|
11136 });
|
paulo@89
|
11137 })
|
paulo@89
|
11138 .on('mouseover', function(d,i) {
|
paulo@89
|
11139 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
|
paulo@89
|
11140 var series = data[d.series],
|
paulo@89
|
11141 point = series.values[i];
|
paulo@89
|
11142
|
paulo@89
|
11143 dispatch.elementMouseover({
|
paulo@89
|
11144 point: point,
|
paulo@89
|
11145 series: series,
|
paulo@89
|
11146 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
|
paulo@89
|
11147 seriesIndex: d.series,
|
paulo@89
|
11148 pointIndex: i,
|
paulo@89
|
11149 color: color(d, i)
|
paulo@89
|
11150 });
|
paulo@89
|
11151 })
|
paulo@89
|
11152 .on('mouseout', function(d,i) {
|
paulo@89
|
11153 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
|
paulo@89
|
11154 var series = data[d.series],
|
paulo@89
|
11155 point = series.values[i];
|
paulo@89
|
11156
|
paulo@89
|
11157 dispatch.elementMouseout({
|
paulo@89
|
11158 point: point,
|
paulo@89
|
11159 series: series,
|
paulo@89
|
11160 seriesIndex: d.series,
|
paulo@89
|
11161 pointIndex: i,
|
paulo@89
|
11162 color: color(d, i)
|
paulo@89
|
11163 });
|
paulo@89
|
11164 });
|
paulo@89
|
11165 }
|
paulo@89
|
11166 }
|
paulo@89
|
11167
|
paulo@89
|
11168 needsUpdate = true;
|
paulo@89
|
11169 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
|
paulo@89
|
11170 .data(function(d) { return d }, function(d) { return d.key });
|
paulo@89
|
11171 groups.enter().append('g')
|
paulo@89
|
11172 .style('stroke-opacity', 1e-6)
|
paulo@89
|
11173 .style('fill-opacity', 1e-6);
|
paulo@89
|
11174 groups.exit()
|
paulo@89
|
11175 .remove();
|
paulo@89
|
11176 groups
|
paulo@89
|
11177 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
|
paulo@89
|
11178 .classed('hover', function(d) { return d.hover });
|
paulo@89
|
11179 groups.watchTransition(renderWatch, 'scatter: groups')
|
paulo@89
|
11180 .style('fill', function(d,i) { return color(d, i) })
|
paulo@89
|
11181 .style('stroke', function(d,i) { return color(d, i) })
|
paulo@89
|
11182 .style('stroke-opacity', 1)
|
paulo@89
|
11183 .style('fill-opacity', .5);
|
paulo@89
|
11184
|
paulo@89
|
11185 // create the points, maintaining their IDs from the original data set
|
paulo@89
|
11186 var points = groups.selectAll('path.nv-point')
|
paulo@89
|
11187 .data(function(d) {
|
paulo@89
|
11188 return d.values.map(
|
paulo@89
|
11189 function (point, pointIndex) {
|
paulo@89
|
11190 return [point, pointIndex]
|
paulo@89
|
11191 }).filter(
|
paulo@89
|
11192 function(pointArray, pointIndex) {
|
paulo@89
|
11193 return pointActive(pointArray[0], pointIndex)
|
paulo@89
|
11194 })
|
paulo@89
|
11195 });
|
paulo@89
|
11196 points.enter().append('path')
|
paulo@89
|
11197 .style('fill', function (d) { return d.color })
|
paulo@89
|
11198 .style('stroke', function (d) { return d.color })
|
paulo@89
|
11199 .attr('transform', function(d) {
|
paulo@89
|
11200 return 'translate(' + x0(getX(d[0],d[1])) + ',' + y0(getY(d[0],d[1])) + ')'
|
paulo@89
|
11201 })
|
paulo@89
|
11202 .attr('d',
|
paulo@89
|
11203 nv.utils.symbol()
|
paulo@89
|
11204 .type(function(d) { return getShape(d[0]); })
|
paulo@89
|
11205 .size(function(d) { return z(getSize(d[0],d[1])) })
|
paulo@89
|
11206 );
|
paulo@89
|
11207 points.exit().remove();
|
paulo@89
|
11208 groups.exit().selectAll('path.nv-point')
|
paulo@89
|
11209 .watchTransition(renderWatch, 'scatter exit')
|
paulo@89
|
11210 .attr('transform', function(d) {
|
paulo@89
|
11211 return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')'
|
paulo@89
|
11212 })
|
paulo@89
|
11213 .remove();
|
paulo@89
|
11214 points.each(function(d) {
|
paulo@89
|
11215 d3.select(this)
|
paulo@89
|
11216 .classed('nv-point', true)
|
paulo@89
|
11217 .classed('nv-point-' + d[1], true)
|
paulo@89
|
11218 .classed('nv-noninteractive', !interactive)
|
paulo@89
|
11219 .classed('hover',false)
|
paulo@89
|
11220 ;
|
paulo@89
|
11221 });
|
paulo@89
|
11222 points
|
paulo@89
|
11223 .watchTransition(renderWatch, 'scatter points')
|
paulo@89
|
11224 .attr('transform', function(d) {
|
paulo@89
|
11225 //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
|
paulo@89
|
11226 return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')'
|
paulo@89
|
11227 })
|
paulo@89
|
11228 .attr('d',
|
paulo@89
|
11229 nv.utils.symbol()
|
paulo@89
|
11230 .type(function(d) { return getShape(d[0]); })
|
paulo@89
|
11231 .size(function(d) { return z(getSize(d[0],d[1])) })
|
paulo@89
|
11232 );
|
paulo@89
|
11233
|
paulo@89
|
11234 // Delay updating the invisible interactive layer for smoother animation
|
paulo@89
|
11235 clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
|
paulo@89
|
11236 timeoutID = setTimeout(updateInteractiveLayer, 300);
|
paulo@89
|
11237 //updateInteractiveLayer();
|
paulo@89
|
11238
|
paulo@89
|
11239 //store old scales for use in transitions on update
|
paulo@89
|
11240 x0 = x.copy();
|
paulo@89
|
11241 y0 = y.copy();
|
paulo@89
|
11242 z0 = z.copy();
|
paulo@89
|
11243
|
paulo@89
|
11244 });
|
paulo@89
|
11245 renderWatch.renderEnd('scatter immediate');
|
paulo@89
|
11246 return chart;
|
paulo@89
|
11247 }
|
paulo@89
|
11248
|
paulo@89
|
11249 //============================================================
|
paulo@89
|
11250 // Expose Public Variables
|
paulo@89
|
11251 //------------------------------------------------------------
|
paulo@89
|
11252
|
paulo@89
|
11253 chart.dispatch = dispatch;
|
paulo@89
|
11254 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
11255
|
paulo@89
|
11256 // utility function calls provided by this chart
|
paulo@89
|
11257 chart._calls = new function() {
|
paulo@89
|
11258 this.clearHighlights = function () {
|
paulo@89
|
11259 nv.dom.write(function() {
|
paulo@89
|
11260 container.selectAll(".nv-point.hover").classed("hover", false);
|
paulo@89
|
11261 });
|
paulo@89
|
11262 return null;
|
paulo@89
|
11263 };
|
paulo@89
|
11264 this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
|
paulo@89
|
11265 nv.dom.write(function() {
|
paulo@89
|
11266 container.select(" .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
|
paulo@89
|
11267 .classed("hover", isHoverOver);
|
paulo@89
|
11268 });
|
paulo@89
|
11269 };
|
paulo@89
|
11270 };
|
paulo@89
|
11271
|
paulo@89
|
11272 // trigger calls from events too
|
paulo@89
|
11273 dispatch.on('elementMouseover.point', function(d) {
|
paulo@89
|
11274 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
|
paulo@89
|
11275 });
|
paulo@89
|
11276
|
paulo@89
|
11277 dispatch.on('elementMouseout.point', function(d) {
|
paulo@89
|
11278 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
|
paulo@89
|
11279 });
|
paulo@89
|
11280
|
paulo@89
|
11281 chart._options = Object.create({}, {
|
paulo@89
|
11282 // simple options, just get/set the necessary values
|
paulo@89
|
11283 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
11284 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
11285 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
11286 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
11287 pointScale: {get: function(){return z;}, set: function(_){z=_;}},
|
paulo@89
|
11288 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
11289 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
11290 pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
|
paulo@89
|
11291 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
11292 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
11293 pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
|
paulo@89
|
11294 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
|
paulo@89
|
11295 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
|
paulo@89
|
11296 forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
|
paulo@89
|
11297 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
|
paulo@89
|
11298 pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
|
paulo@89
|
11299 padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
|
paulo@89
|
11300 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
|
paulo@89
|
11301 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
|
paulo@89
|
11302 clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
|
paulo@89
|
11303 clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
|
paulo@89
|
11304 showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
|
paulo@89
|
11305 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
11306
|
paulo@89
|
11307
|
paulo@89
|
11308 // simple functor options
|
paulo@89
|
11309 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
|
paulo@89
|
11310 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
|
paulo@89
|
11311 pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
|
paulo@89
|
11312 pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
|
paulo@89
|
11313
|
paulo@89
|
11314 // options that require extra logic in the setter
|
paulo@89
|
11315 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
11316 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
11317 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
11318 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
11319 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
11320 }},
|
paulo@89
|
11321 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
11322 duration = _;
|
paulo@89
|
11323 renderWatch.reset(duration);
|
paulo@89
|
11324 }},
|
paulo@89
|
11325 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
11326 color = nv.utils.getColor(_);
|
paulo@89
|
11327 }},
|
paulo@89
|
11328 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
|
paulo@89
|
11329 useVoronoi = _;
|
paulo@89
|
11330 if (useVoronoi === false) {
|
paulo@89
|
11331 clipVoronoi = false;
|
paulo@89
|
11332 }
|
paulo@89
|
11333 }}
|
paulo@89
|
11334 });
|
paulo@89
|
11335
|
paulo@89
|
11336 nv.utils.initOptions(chart);
|
paulo@89
|
11337 return chart;
|
paulo@89
|
11338 };
|
paulo@89
|
11339
|
paulo@89
|
11340 nv.models.scatterChart = function() {
|
paulo@89
|
11341 "use strict";
|
paulo@89
|
11342
|
paulo@89
|
11343 //============================================================
|
paulo@89
|
11344 // Public Variables with Default Settings
|
paulo@89
|
11345 //------------------------------------------------------------
|
paulo@89
|
11346
|
paulo@89
|
11347 var scatter = nv.models.scatter()
|
paulo@89
|
11348 , xAxis = nv.models.axis()
|
paulo@89
|
11349 , yAxis = nv.models.axis()
|
paulo@89
|
11350 , legend = nv.models.legend()
|
paulo@89
|
11351 , distX = nv.models.distribution()
|
paulo@89
|
11352 , distY = nv.models.distribution()
|
paulo@89
|
11353 , tooltip = nv.models.tooltip()
|
paulo@89
|
11354 ;
|
paulo@89
|
11355
|
paulo@89
|
11356 var margin = {top: 30, right: 20, bottom: 50, left: 75}
|
paulo@89
|
11357 , width = null
|
paulo@89
|
11358 , height = null
|
paulo@89
|
11359 , container = null
|
paulo@89
|
11360 , color = nv.utils.defaultColor()
|
paulo@89
|
11361 , x = scatter.xScale()
|
paulo@89
|
11362 , y = scatter.yScale()
|
paulo@89
|
11363 , showDistX = false
|
paulo@89
|
11364 , showDistY = false
|
paulo@89
|
11365 , showLegend = true
|
paulo@89
|
11366 , showXAxis = true
|
paulo@89
|
11367 , showYAxis = true
|
paulo@89
|
11368 , rightAlignYAxis = false
|
paulo@89
|
11369 , state = nv.utils.state()
|
paulo@89
|
11370 , defaultState = null
|
paulo@89
|
11371 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
|
paulo@89
|
11372 , noData = null
|
paulo@89
|
11373 , duration = 250
|
paulo@89
|
11374 ;
|
paulo@89
|
11375
|
paulo@89
|
11376 scatter.xScale(x).yScale(y);
|
paulo@89
|
11377 xAxis.orient('bottom').tickPadding(10);
|
paulo@89
|
11378 yAxis
|
paulo@89
|
11379 .orient((rightAlignYAxis) ? 'right' : 'left')
|
paulo@89
|
11380 .tickPadding(10)
|
paulo@89
|
11381 ;
|
paulo@89
|
11382 distX.axis('x');
|
paulo@89
|
11383 distY.axis('y');
|
paulo@89
|
11384 tooltip
|
paulo@89
|
11385 .headerFormatter(function(d, i) {
|
paulo@89
|
11386 return xAxis.tickFormat()(d, i);
|
paulo@89
|
11387 })
|
paulo@89
|
11388 .valueFormatter(function(d, i) {
|
paulo@89
|
11389 return yAxis.tickFormat()(d, i);
|
paulo@89
|
11390 });
|
paulo@89
|
11391
|
paulo@89
|
11392 //============================================================
|
paulo@89
|
11393 // Private Variables
|
paulo@89
|
11394 //------------------------------------------------------------
|
paulo@89
|
11395
|
paulo@89
|
11396 var x0, y0
|
paulo@89
|
11397 , renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
11398
|
paulo@89
|
11399 var stateGetter = function(data) {
|
paulo@89
|
11400 return function(){
|
paulo@89
|
11401 return {
|
paulo@89
|
11402 active: data.map(function(d) { return !d.disabled })
|
paulo@89
|
11403 };
|
paulo@89
|
11404 }
|
paulo@89
|
11405 };
|
paulo@89
|
11406
|
paulo@89
|
11407 var stateSetter = function(data) {
|
paulo@89
|
11408 return function(state) {
|
paulo@89
|
11409 if (state.active !== undefined)
|
paulo@89
|
11410 data.forEach(function(series,i) {
|
paulo@89
|
11411 series.disabled = !state.active[i];
|
paulo@89
|
11412 });
|
paulo@89
|
11413 }
|
paulo@89
|
11414 };
|
paulo@89
|
11415
|
paulo@89
|
11416 function chart(selection) {
|
paulo@89
|
11417 renderWatch.reset();
|
paulo@89
|
11418 renderWatch.models(scatter);
|
paulo@89
|
11419 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
11420 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
11421 if (showDistX) renderWatch.models(distX);
|
paulo@89
|
11422 if (showDistY) renderWatch.models(distY);
|
paulo@89
|
11423
|
paulo@89
|
11424 selection.each(function(data) {
|
paulo@89
|
11425 var that = this;
|
paulo@89
|
11426
|
paulo@89
|
11427 container = d3.select(this);
|
paulo@89
|
11428 nv.utils.initSVG(container);
|
paulo@89
|
11429
|
paulo@89
|
11430 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
11431 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
11432
|
paulo@89
|
11433 chart.update = function() {
|
paulo@89
|
11434 if (duration === 0)
|
paulo@89
|
11435 container.call(chart);
|
paulo@89
|
11436 else
|
paulo@89
|
11437 container.transition().duration(duration).call(chart);
|
paulo@89
|
11438 };
|
paulo@89
|
11439 chart.container = this;
|
paulo@89
|
11440
|
paulo@89
|
11441 state
|
paulo@89
|
11442 .setter(stateSetter(data), chart.update)
|
paulo@89
|
11443 .getter(stateGetter(data))
|
paulo@89
|
11444 .update();
|
paulo@89
|
11445
|
paulo@89
|
11446 // DEPRECATED set state.disableddisabled
|
paulo@89
|
11447 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
11448
|
paulo@89
|
11449 if (!defaultState) {
|
paulo@89
|
11450 var key;
|
paulo@89
|
11451 defaultState = {};
|
paulo@89
|
11452 for (key in state) {
|
paulo@89
|
11453 if (state[key] instanceof Array)
|
paulo@89
|
11454 defaultState[key] = state[key].slice(0);
|
paulo@89
|
11455 else
|
paulo@89
|
11456 defaultState[key] = state[key];
|
paulo@89
|
11457 }
|
paulo@89
|
11458 }
|
paulo@89
|
11459
|
paulo@89
|
11460 // Display noData message if there's nothing to show.
|
paulo@89
|
11461 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
11462 nv.utils.noData(chart, container);
|
paulo@89
|
11463 renderWatch.renderEnd('scatter immediate');
|
paulo@89
|
11464 return chart;
|
paulo@89
|
11465 } else {
|
paulo@89
|
11466 container.selectAll('.nv-noData').remove();
|
paulo@89
|
11467 }
|
paulo@89
|
11468
|
paulo@89
|
11469 // Setup Scales
|
paulo@89
|
11470 x = scatter.xScale();
|
paulo@89
|
11471 y = scatter.yScale();
|
paulo@89
|
11472
|
paulo@89
|
11473 // Setup containers and skeleton of chart
|
paulo@89
|
11474 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
|
paulo@89
|
11475 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
|
paulo@89
|
11476 var gEnter = wrapEnter.append('g');
|
paulo@89
|
11477 var g = wrap.select('g');
|
paulo@89
|
11478
|
paulo@89
|
11479 // background for pointer events
|
paulo@89
|
11480 gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
|
paulo@89
|
11481
|
paulo@89
|
11482 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
11483 gEnter.append('g').attr('class', 'nv-y nv-axis');
|
paulo@89
|
11484 gEnter.append('g').attr('class', 'nv-scatterWrap');
|
paulo@89
|
11485 gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
|
paulo@89
|
11486 gEnter.append('g').attr('class', 'nv-distWrap');
|
paulo@89
|
11487 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
11488
|
paulo@89
|
11489 if (rightAlignYAxis) {
|
paulo@89
|
11490 g.select(".nv-y.nv-axis")
|
paulo@89
|
11491 .attr("transform", "translate(" + availableWidth + ",0)");
|
paulo@89
|
11492 }
|
paulo@89
|
11493
|
paulo@89
|
11494 // Legend
|
paulo@89
|
11495 if (showLegend) {
|
paulo@89
|
11496 var legendWidth = availableWidth;
|
paulo@89
|
11497 legend.width(legendWidth);
|
paulo@89
|
11498
|
paulo@89
|
11499 wrap.select('.nv-legendWrap')
|
paulo@89
|
11500 .datum(data)
|
paulo@89
|
11501 .call(legend);
|
paulo@89
|
11502
|
paulo@89
|
11503 if ( margin.top != legend.height()) {
|
paulo@89
|
11504 margin.top = legend.height();
|
paulo@89
|
11505 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
11506 }
|
paulo@89
|
11507
|
paulo@89
|
11508 wrap.select('.nv-legendWrap')
|
paulo@89
|
11509 .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
|
paulo@89
|
11510 }
|
paulo@89
|
11511
|
paulo@89
|
11512 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
11513
|
paulo@89
|
11514 // Main Chart Component(s)
|
paulo@89
|
11515 scatter
|
paulo@89
|
11516 .width(availableWidth)
|
paulo@89
|
11517 .height(availableHeight)
|
paulo@89
|
11518 .color(data.map(function(d,i) {
|
paulo@89
|
11519 d.color = d.color || color(d, i);
|
paulo@89
|
11520 return d.color;
|
paulo@89
|
11521 }).filter(function(d,i) { return !data[i].disabled }));
|
paulo@89
|
11522
|
paulo@89
|
11523 wrap.select('.nv-scatterWrap')
|
paulo@89
|
11524 .datum(data.filter(function(d) { return !d.disabled }))
|
paulo@89
|
11525 .call(scatter);
|
paulo@89
|
11526
|
paulo@89
|
11527
|
paulo@89
|
11528 wrap.select('.nv-regressionLinesWrap')
|
paulo@89
|
11529 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
|
paulo@89
|
11530
|
paulo@89
|
11531 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
|
paulo@89
|
11532 .data(function (d) {
|
paulo@89
|
11533 return d;
|
paulo@89
|
11534 });
|
paulo@89
|
11535
|
paulo@89
|
11536 regWrap.enter().append('g').attr('class', 'nv-regLines');
|
paulo@89
|
11537
|
paulo@89
|
11538 var regLine = regWrap.selectAll('.nv-regLine')
|
paulo@89
|
11539 .data(function (d) {
|
paulo@89
|
11540 return [d]
|
paulo@89
|
11541 });
|
paulo@89
|
11542
|
paulo@89
|
11543 regLine.enter()
|
paulo@89
|
11544 .append('line').attr('class', 'nv-regLine')
|
paulo@89
|
11545 .style('stroke-opacity', 0);
|
paulo@89
|
11546
|
paulo@89
|
11547 // don't add lines unless we have slope and intercept to use
|
paulo@89
|
11548 regLine.filter(function(d) {
|
paulo@89
|
11549 return d.intercept && d.slope;
|
paulo@89
|
11550 })
|
paulo@89
|
11551 .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
|
paulo@89
|
11552 .attr('x1', x.range()[0])
|
paulo@89
|
11553 .attr('x2', x.range()[1])
|
paulo@89
|
11554 .attr('y1', function (d, i) {
|
paulo@89
|
11555 return y(x.domain()[0] * d.slope + d.intercept)
|
paulo@89
|
11556 })
|
paulo@89
|
11557 .attr('y2', function (d, i) {
|
paulo@89
|
11558 return y(x.domain()[1] * d.slope + d.intercept)
|
paulo@89
|
11559 })
|
paulo@89
|
11560 .style('stroke', function (d, i, j) {
|
paulo@89
|
11561 return color(d, j)
|
paulo@89
|
11562 })
|
paulo@89
|
11563 .style('stroke-opacity', function (d, i) {
|
paulo@89
|
11564 return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
|
paulo@89
|
11565 });
|
paulo@89
|
11566
|
paulo@89
|
11567 // Setup Axes
|
paulo@89
|
11568 if (showXAxis) {
|
paulo@89
|
11569 xAxis
|
paulo@89
|
11570 .scale(x)
|
paulo@89
|
11571 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
11572 .tickSize( -availableHeight , 0);
|
paulo@89
|
11573
|
paulo@89
|
11574 g.select('.nv-x.nv-axis')
|
paulo@89
|
11575 .attr('transform', 'translate(0,' + y.range()[0] + ')')
|
paulo@89
|
11576 .call(xAxis);
|
paulo@89
|
11577 }
|
paulo@89
|
11578
|
paulo@89
|
11579 if (showYAxis) {
|
paulo@89
|
11580 yAxis
|
paulo@89
|
11581 .scale(y)
|
paulo@89
|
11582 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
|
paulo@89
|
11583 .tickSize( -availableWidth, 0);
|
paulo@89
|
11584
|
paulo@89
|
11585 g.select('.nv-y.nv-axis')
|
paulo@89
|
11586 .call(yAxis);
|
paulo@89
|
11587 }
|
paulo@89
|
11588
|
paulo@89
|
11589
|
paulo@89
|
11590 if (showDistX) {
|
paulo@89
|
11591 distX
|
paulo@89
|
11592 .getData(scatter.x())
|
paulo@89
|
11593 .scale(x)
|
paulo@89
|
11594 .width(availableWidth)
|
paulo@89
|
11595 .color(data.map(function(d,i) {
|
paulo@89
|
11596 return d.color || color(d, i);
|
paulo@89
|
11597 }).filter(function(d,i) { return !data[i].disabled }));
|
paulo@89
|
11598 gEnter.select('.nv-distWrap').append('g')
|
paulo@89
|
11599 .attr('class', 'nv-distributionX');
|
paulo@89
|
11600 g.select('.nv-distributionX')
|
paulo@89
|
11601 .attr('transform', 'translate(0,' + y.range()[0] + ')')
|
paulo@89
|
11602 .datum(data.filter(function(d) { return !d.disabled }))
|
paulo@89
|
11603 .call(distX);
|
paulo@89
|
11604 }
|
paulo@89
|
11605
|
paulo@89
|
11606 if (showDistY) {
|
paulo@89
|
11607 distY
|
paulo@89
|
11608 .getData(scatter.y())
|
paulo@89
|
11609 .scale(y)
|
paulo@89
|
11610 .width(availableHeight)
|
paulo@89
|
11611 .color(data.map(function(d,i) {
|
paulo@89
|
11612 return d.color || color(d, i);
|
paulo@89
|
11613 }).filter(function(d,i) { return !data[i].disabled }));
|
paulo@89
|
11614 gEnter.select('.nv-distWrap').append('g')
|
paulo@89
|
11615 .attr('class', 'nv-distributionY');
|
paulo@89
|
11616 g.select('.nv-distributionY')
|
paulo@89
|
11617 .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
|
paulo@89
|
11618 .datum(data.filter(function(d) { return !d.disabled }))
|
paulo@89
|
11619 .call(distY);
|
paulo@89
|
11620 }
|
paulo@89
|
11621
|
paulo@89
|
11622 //============================================================
|
paulo@89
|
11623 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
11624 //------------------------------------------------------------
|
paulo@89
|
11625
|
paulo@89
|
11626 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
11627 for (var key in newState)
|
paulo@89
|
11628 state[key] = newState[key];
|
paulo@89
|
11629 dispatch.stateChange(state);
|
paulo@89
|
11630 chart.update();
|
paulo@89
|
11631 });
|
paulo@89
|
11632
|
paulo@89
|
11633 // Update chart from a state object passed to event handler
|
paulo@89
|
11634 dispatch.on('changeState', function(e) {
|
paulo@89
|
11635 if (typeof e.disabled !== 'undefined') {
|
paulo@89
|
11636 data.forEach(function(series,i) {
|
paulo@89
|
11637 series.disabled = e.disabled[i];
|
paulo@89
|
11638 });
|
paulo@89
|
11639 state.disabled = e.disabled;
|
paulo@89
|
11640 }
|
paulo@89
|
11641 chart.update();
|
paulo@89
|
11642 });
|
paulo@89
|
11643
|
paulo@89
|
11644 // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
|
paulo@89
|
11645 scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
11646 tooltip.hidden(true);
|
paulo@89
|
11647 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
|
paulo@89
|
11648 .attr('y1', 0);
|
paulo@89
|
11649 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
|
paulo@89
|
11650 .attr('x2', distY.size());
|
paulo@89
|
11651 });
|
paulo@89
|
11652
|
paulo@89
|
11653 scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
11654 container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
|
paulo@89
|
11655 .attr('y1', evt.pos.top - availableHeight - margin.top);
|
paulo@89
|
11656 container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
|
paulo@89
|
11657 .attr('x2', evt.pos.left + distX.size() - margin.left);
|
paulo@89
|
11658 tooltip.position(evt.pos).data(evt).hidden(false);
|
paulo@89
|
11659 });
|
paulo@89
|
11660
|
paulo@89
|
11661 //store old scales for use in transitions on update
|
paulo@89
|
11662 x0 = x.copy();
|
paulo@89
|
11663 y0 = y.copy();
|
paulo@89
|
11664
|
paulo@89
|
11665 });
|
paulo@89
|
11666
|
paulo@89
|
11667 renderWatch.renderEnd('scatter with line immediate');
|
paulo@89
|
11668 return chart;
|
paulo@89
|
11669 }
|
paulo@89
|
11670
|
paulo@89
|
11671 //============================================================
|
paulo@89
|
11672 // Expose Public Variables
|
paulo@89
|
11673 //------------------------------------------------------------
|
paulo@89
|
11674
|
paulo@89
|
11675 // expose chart's sub-components
|
paulo@89
|
11676 chart.dispatch = dispatch;
|
paulo@89
|
11677 chart.scatter = scatter;
|
paulo@89
|
11678 chart.legend = legend;
|
paulo@89
|
11679 chart.xAxis = xAxis;
|
paulo@89
|
11680 chart.yAxis = yAxis;
|
paulo@89
|
11681 chart.distX = distX;
|
paulo@89
|
11682 chart.distY = distY;
|
paulo@89
|
11683 chart.tooltip = tooltip;
|
paulo@89
|
11684
|
paulo@89
|
11685 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
11686 chart._options = Object.create({}, {
|
paulo@89
|
11687 // simple options, just get/set the necessary values
|
paulo@89
|
11688 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
11689 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
11690 container: {get: function(){return container;}, set: function(_){container=_;}},
|
paulo@89
|
11691 showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
|
paulo@89
|
11692 showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
|
paulo@89
|
11693 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
11694 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
11695 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
11696 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
11697 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
11698 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
|
paulo@89
|
11699
|
paulo@89
|
11700 // deprecated options
|
paulo@89
|
11701 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
11702 // deprecated after 1.7.1
|
paulo@89
|
11703 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
11704 tooltip.enabled(!!_);
|
paulo@89
|
11705 }},
|
paulo@89
|
11706 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
11707 // deprecated after 1.7.1
|
paulo@89
|
11708 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
11709 tooltip.contentGenerator(_);
|
paulo@89
|
11710 }},
|
paulo@89
|
11711 tooltipXContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
11712 // deprecated after 1.7.1
|
paulo@89
|
11713 nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.');
|
paulo@89
|
11714 }},
|
paulo@89
|
11715 tooltipYContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
11716 // deprecated after 1.7.1
|
paulo@89
|
11717 nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.');
|
paulo@89
|
11718 }},
|
paulo@89
|
11719
|
paulo@89
|
11720 // options that require extra logic in the setter
|
paulo@89
|
11721 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
11722 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
11723 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
11724 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
11725 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
11726 }},
|
paulo@89
|
11727 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
|
paulo@89
|
11728 rightAlignYAxis = _;
|
paulo@89
|
11729 yAxis.orient( (_) ? 'right' : 'left');
|
paulo@89
|
11730 }},
|
paulo@89
|
11731 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
11732 color = nv.utils.getColor(_);
|
paulo@89
|
11733 legend.color(color);
|
paulo@89
|
11734 distX.color(color);
|
paulo@89
|
11735 distY.color(color);
|
paulo@89
|
11736 }}
|
paulo@89
|
11737 });
|
paulo@89
|
11738
|
paulo@89
|
11739 nv.utils.inheritOptions(chart, scatter);
|
paulo@89
|
11740 nv.utils.initOptions(chart);
|
paulo@89
|
11741 return chart;
|
paulo@89
|
11742 };
|
paulo@89
|
11743
|
paulo@89
|
11744 nv.models.sparkline = function() {
|
paulo@89
|
11745 "use strict";
|
paulo@89
|
11746
|
paulo@89
|
11747 //============================================================
|
paulo@89
|
11748 // Public Variables with Default Settings
|
paulo@89
|
11749 //------------------------------------------------------------
|
paulo@89
|
11750
|
paulo@89
|
11751 var margin = {top: 2, right: 0, bottom: 2, left: 0}
|
paulo@89
|
11752 , width = 400
|
paulo@89
|
11753 , height = 32
|
paulo@89
|
11754 , container = null
|
paulo@89
|
11755 , animate = true
|
paulo@89
|
11756 , x = d3.scale.linear()
|
paulo@89
|
11757 , y = d3.scale.linear()
|
paulo@89
|
11758 , getX = function(d) { return d.x }
|
paulo@89
|
11759 , getY = function(d) { return d.y }
|
paulo@89
|
11760 , color = nv.utils.getColor(['#000'])
|
paulo@89
|
11761 , xDomain
|
paulo@89
|
11762 , yDomain
|
paulo@89
|
11763 , xRange
|
paulo@89
|
11764 , yRange
|
paulo@89
|
11765 ;
|
paulo@89
|
11766
|
paulo@89
|
11767 function chart(selection) {
|
paulo@89
|
11768 selection.each(function(data) {
|
paulo@89
|
11769 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
11770 availableHeight = height - margin.top - margin.bottom;
|
paulo@89
|
11771
|
paulo@89
|
11772 container = d3.select(this);
|
paulo@89
|
11773 nv.utils.initSVG(container);
|
paulo@89
|
11774
|
paulo@89
|
11775 // Setup Scales
|
paulo@89
|
11776 x .domain(xDomain || d3.extent(data, getX ))
|
paulo@89
|
11777 .range(xRange || [0, availableWidth]);
|
paulo@89
|
11778
|
paulo@89
|
11779 y .domain(yDomain || d3.extent(data, getY ))
|
paulo@89
|
11780 .range(yRange || [availableHeight, 0]);
|
paulo@89
|
11781
|
paulo@89
|
11782 // Setup containers and skeleton of chart
|
paulo@89
|
11783 var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
|
paulo@89
|
11784 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
|
paulo@89
|
11785 var gEnter = wrapEnter.append('g');
|
paulo@89
|
11786 var g = wrap.select('g');
|
paulo@89
|
11787
|
paulo@89
|
11788 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
|
paulo@89
|
11789
|
paulo@89
|
11790 var paths = wrap.selectAll('path')
|
paulo@89
|
11791 .data(function(d) { return [d] });
|
paulo@89
|
11792 paths.enter().append('path');
|
paulo@89
|
11793 paths.exit().remove();
|
paulo@89
|
11794 paths
|
paulo@89
|
11795 .style('stroke', function(d,i) { return d.color || color(d, i) })
|
paulo@89
|
11796 .attr('d', d3.svg.line()
|
paulo@89
|
11797 .x(function(d,i) { return x(getX(d,i)) })
|
paulo@89
|
11798 .y(function(d,i) { return y(getY(d,i)) })
|
paulo@89
|
11799 );
|
paulo@89
|
11800
|
paulo@89
|
11801 // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
|
paulo@89
|
11802 var points = wrap.selectAll('circle.nv-point')
|
paulo@89
|
11803 .data(function(data) {
|
paulo@89
|
11804 var yValues = data.map(function(d, i) { return getY(d,i); });
|
paulo@89
|
11805 function pointIndex(index) {
|
paulo@89
|
11806 if (index != -1) {
|
paulo@89
|
11807 var result = data[index];
|
paulo@89
|
11808 result.pointIndex = index;
|
paulo@89
|
11809 return result;
|
paulo@89
|
11810 } else {
|
paulo@89
|
11811 return null;
|
paulo@89
|
11812 }
|
paulo@89
|
11813 }
|
paulo@89
|
11814 var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
|
paulo@89
|
11815 minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
|
paulo@89
|
11816 currentPoint = pointIndex(yValues.length - 1);
|
paulo@89
|
11817 return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
|
paulo@89
|
11818 });
|
paulo@89
|
11819 points.enter().append('circle');
|
paulo@89
|
11820 points.exit().remove();
|
paulo@89
|
11821 points
|
paulo@89
|
11822 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
|
paulo@89
|
11823 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
|
paulo@89
|
11824 .attr('r', 2)
|
paulo@89
|
11825 .attr('class', function(d,i) {
|
paulo@89
|
11826 return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
|
paulo@89
|
11827 getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
|
paulo@89
|
11828 });
|
paulo@89
|
11829 });
|
paulo@89
|
11830
|
paulo@89
|
11831 return chart;
|
paulo@89
|
11832 }
|
paulo@89
|
11833
|
paulo@89
|
11834 //============================================================
|
paulo@89
|
11835 // Expose Public Variables
|
paulo@89
|
11836 //------------------------------------------------------------
|
paulo@89
|
11837
|
paulo@89
|
11838 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
11839
|
paulo@89
|
11840 chart._options = Object.create({}, {
|
paulo@89
|
11841 // simple options, just get/set the necessary values
|
paulo@89
|
11842 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
11843 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
11844 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
|
paulo@89
|
11845 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
|
paulo@89
|
11846 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
|
paulo@89
|
11847 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
|
paulo@89
|
11848 xScale: {get: function(){return x;}, set: function(_){x=_;}},
|
paulo@89
|
11849 yScale: {get: function(){return y;}, set: function(_){y=_;}},
|
paulo@89
|
11850 animate: {get: function(){return animate;}, set: function(_){animate=_;}},
|
paulo@89
|
11851
|
paulo@89
|
11852 //functor options
|
paulo@89
|
11853 x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
|
paulo@89
|
11854 y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
|
paulo@89
|
11855
|
paulo@89
|
11856 // options that require extra logic in the setter
|
paulo@89
|
11857 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
11858 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
11859 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
11860 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
11861 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
11862 }},
|
paulo@89
|
11863 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
11864 color = nv.utils.getColor(_);
|
paulo@89
|
11865 }}
|
paulo@89
|
11866 });
|
paulo@89
|
11867
|
paulo@89
|
11868 nv.utils.initOptions(chart);
|
paulo@89
|
11869 return chart;
|
paulo@89
|
11870 };
|
paulo@89
|
11871
|
paulo@89
|
11872 nv.models.sparklinePlus = function() {
|
paulo@89
|
11873 "use strict";
|
paulo@89
|
11874
|
paulo@89
|
11875 //============================================================
|
paulo@89
|
11876 // Public Variables with Default Settings
|
paulo@89
|
11877 //------------------------------------------------------------
|
paulo@89
|
11878
|
paulo@89
|
11879 var sparkline = nv.models.sparkline();
|
paulo@89
|
11880
|
paulo@89
|
11881 var margin = {top: 15, right: 100, bottom: 10, left: 50}
|
paulo@89
|
11882 , width = null
|
paulo@89
|
11883 , height = null
|
paulo@89
|
11884 , x
|
paulo@89
|
11885 , y
|
paulo@89
|
11886 , index = []
|
paulo@89
|
11887 , paused = false
|
paulo@89
|
11888 , xTickFormat = d3.format(',r')
|
paulo@89
|
11889 , yTickFormat = d3.format(',.2f')
|
paulo@89
|
11890 , showLastValue = true
|
paulo@89
|
11891 , alignValue = true
|
paulo@89
|
11892 , rightAlignValue = false
|
paulo@89
|
11893 , noData = null
|
paulo@89
|
11894 ;
|
paulo@89
|
11895
|
paulo@89
|
11896 function chart(selection) {
|
paulo@89
|
11897 selection.each(function(data) {
|
paulo@89
|
11898 var container = d3.select(this);
|
paulo@89
|
11899 nv.utils.initSVG(container);
|
paulo@89
|
11900
|
paulo@89
|
11901 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
11902 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
11903
|
paulo@89
|
11904 chart.update = function() { container.call(chart); };
|
paulo@89
|
11905 chart.container = this;
|
paulo@89
|
11906
|
paulo@89
|
11907 // Display No Data message if there's nothing to show.
|
paulo@89
|
11908 if (!data || !data.length) {
|
paulo@89
|
11909 nv.utils.noData(chart, container)
|
paulo@89
|
11910 return chart;
|
paulo@89
|
11911 } else {
|
paulo@89
|
11912 container.selectAll('.nv-noData').remove();
|
paulo@89
|
11913 }
|
paulo@89
|
11914
|
paulo@89
|
11915 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
|
paulo@89
|
11916
|
paulo@89
|
11917 // Setup Scales
|
paulo@89
|
11918 x = sparkline.xScale();
|
paulo@89
|
11919 y = sparkline.yScale();
|
paulo@89
|
11920
|
paulo@89
|
11921 // Setup containers and skeleton of chart
|
paulo@89
|
11922 var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
|
paulo@89
|
11923 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
|
paulo@89
|
11924 var gEnter = wrapEnter.append('g');
|
paulo@89
|
11925 var g = wrap.select('g');
|
paulo@89
|
11926
|
paulo@89
|
11927 gEnter.append('g').attr('class', 'nv-sparklineWrap');
|
paulo@89
|
11928 gEnter.append('g').attr('class', 'nv-valueWrap');
|
paulo@89
|
11929 gEnter.append('g').attr('class', 'nv-hoverArea');
|
paulo@89
|
11930
|
paulo@89
|
11931 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
11932
|
paulo@89
|
11933 // Main Chart Component(s)
|
paulo@89
|
11934 var sparklineWrap = g.select('.nv-sparklineWrap');
|
paulo@89
|
11935
|
paulo@89
|
11936 sparkline.width(availableWidth).height(availableHeight);
|
paulo@89
|
11937 sparklineWrap.call(sparkline);
|
paulo@89
|
11938
|
paulo@89
|
11939 if (showLastValue) {
|
paulo@89
|
11940 var valueWrap = g.select('.nv-valueWrap');
|
paulo@89
|
11941 var value = valueWrap.selectAll('.nv-currentValue')
|
paulo@89
|
11942 .data([currentValue]);
|
paulo@89
|
11943
|
paulo@89
|
11944 value.enter().append('text').attr('class', 'nv-currentValue')
|
paulo@89
|
11945 .attr('dx', rightAlignValue ? -8 : 8)
|
paulo@89
|
11946 .attr('dy', '.9em')
|
paulo@89
|
11947 .style('text-anchor', rightAlignValue ? 'end' : 'start');
|
paulo@89
|
11948
|
paulo@89
|
11949 value
|
paulo@89
|
11950 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
|
paulo@89
|
11951 .attr('y', alignValue ? function (d) {
|
paulo@89
|
11952 return y(d)
|
paulo@89
|
11953 } : 0)
|
paulo@89
|
11954 .style('fill', sparkline.color()(data[data.length - 1], data.length - 1))
|
paulo@89
|
11955 .text(yTickFormat(currentValue));
|
paulo@89
|
11956 }
|
paulo@89
|
11957
|
paulo@89
|
11958 gEnter.select('.nv-hoverArea').append('rect')
|
paulo@89
|
11959 .on('mousemove', sparklineHover)
|
paulo@89
|
11960 .on('click', function() { paused = !paused })
|
paulo@89
|
11961 .on('mouseout', function() { index = []; updateValueLine(); });
|
paulo@89
|
11962
|
paulo@89
|
11963 g.select('.nv-hoverArea rect')
|
paulo@89
|
11964 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
|
paulo@89
|
11965 .attr('width', availableWidth + margin.left + margin.right)
|
paulo@89
|
11966 .attr('height', availableHeight + margin.top);
|
paulo@89
|
11967
|
paulo@89
|
11968 //index is currently global (within the chart), may or may not keep it that way
|
paulo@89
|
11969 function updateValueLine() {
|
paulo@89
|
11970 if (paused) return;
|
paulo@89
|
11971
|
paulo@89
|
11972 var hoverValue = g.selectAll('.nv-hoverValue').data(index);
|
paulo@89
|
11973
|
paulo@89
|
11974 var hoverEnter = hoverValue.enter()
|
paulo@89
|
11975 .append('g').attr('class', 'nv-hoverValue')
|
paulo@89
|
11976 .style('stroke-opacity', 0)
|
paulo@89
|
11977 .style('fill-opacity', 0);
|
paulo@89
|
11978
|
paulo@89
|
11979 hoverValue.exit()
|
paulo@89
|
11980 .transition().duration(250)
|
paulo@89
|
11981 .style('stroke-opacity', 0)
|
paulo@89
|
11982 .style('fill-opacity', 0)
|
paulo@89
|
11983 .remove();
|
paulo@89
|
11984
|
paulo@89
|
11985 hoverValue
|
paulo@89
|
11986 .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
|
paulo@89
|
11987 .transition().duration(250)
|
paulo@89
|
11988 .style('stroke-opacity', 1)
|
paulo@89
|
11989 .style('fill-opacity', 1);
|
paulo@89
|
11990
|
paulo@89
|
11991 if (!index.length) return;
|
paulo@89
|
11992
|
paulo@89
|
11993 hoverEnter.append('line')
|
paulo@89
|
11994 .attr('x1', 0)
|
paulo@89
|
11995 .attr('y1', -margin.top)
|
paulo@89
|
11996 .attr('x2', 0)
|
paulo@89
|
11997 .attr('y2', availableHeight);
|
paulo@89
|
11998
|
paulo@89
|
11999 hoverEnter.append('text').attr('class', 'nv-xValue')
|
paulo@89
|
12000 .attr('x', -6)
|
paulo@89
|
12001 .attr('y', -margin.top)
|
paulo@89
|
12002 .attr('text-anchor', 'end')
|
paulo@89
|
12003 .attr('dy', '.9em');
|
paulo@89
|
12004
|
paulo@89
|
12005 g.select('.nv-hoverValue .nv-xValue')
|
paulo@89
|
12006 .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
|
paulo@89
|
12007
|
paulo@89
|
12008 hoverEnter.append('text').attr('class', 'nv-yValue')
|
paulo@89
|
12009 .attr('x', 6)
|
paulo@89
|
12010 .attr('y', -margin.top)
|
paulo@89
|
12011 .attr('text-anchor', 'start')
|
paulo@89
|
12012 .attr('dy', '.9em');
|
paulo@89
|
12013
|
paulo@89
|
12014 g.select('.nv-hoverValue .nv-yValue')
|
paulo@89
|
12015 .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
|
paulo@89
|
12016 }
|
paulo@89
|
12017
|
paulo@89
|
12018 function sparklineHover() {
|
paulo@89
|
12019 if (paused) return;
|
paulo@89
|
12020
|
paulo@89
|
12021 var pos = d3.mouse(this)[0] - margin.left;
|
paulo@89
|
12022
|
paulo@89
|
12023 function getClosestIndex(data, x) {
|
paulo@89
|
12024 var distance = Math.abs(sparkline.x()(data[0], 0) - x);
|
paulo@89
|
12025 var closestIndex = 0;
|
paulo@89
|
12026 for (var i = 0; i < data.length; i++){
|
paulo@89
|
12027 if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
|
paulo@89
|
12028 distance = Math.abs(sparkline.x()(data[i], i) - x);
|
paulo@89
|
12029 closestIndex = i;
|
paulo@89
|
12030 }
|
paulo@89
|
12031 }
|
paulo@89
|
12032 return closestIndex;
|
paulo@89
|
12033 }
|
paulo@89
|
12034
|
paulo@89
|
12035 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
|
paulo@89
|
12036 updateValueLine();
|
paulo@89
|
12037 }
|
paulo@89
|
12038
|
paulo@89
|
12039 });
|
paulo@89
|
12040
|
paulo@89
|
12041 return chart;
|
paulo@89
|
12042 }
|
paulo@89
|
12043
|
paulo@89
|
12044 //============================================================
|
paulo@89
|
12045 // Expose Public Variables
|
paulo@89
|
12046 //------------------------------------------------------------
|
paulo@89
|
12047
|
paulo@89
|
12048 // expose chart's sub-components
|
paulo@89
|
12049 chart.sparkline = sparkline;
|
paulo@89
|
12050
|
paulo@89
|
12051 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
12052
|
paulo@89
|
12053 chart._options = Object.create({}, {
|
paulo@89
|
12054 // simple options, just get/set the necessary values
|
paulo@89
|
12055 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
12056 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
12057 xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
|
paulo@89
|
12058 yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
|
paulo@89
|
12059 showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}},
|
paulo@89
|
12060 alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
|
paulo@89
|
12061 rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
|
paulo@89
|
12062 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
12063
|
paulo@89
|
12064 // options that require extra logic in the setter
|
paulo@89
|
12065 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
12066 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
12067 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
12068 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
12069 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
12070 }}
|
paulo@89
|
12071 });
|
paulo@89
|
12072
|
paulo@89
|
12073 nv.utils.inheritOptions(chart, sparkline);
|
paulo@89
|
12074 nv.utils.initOptions(chart);
|
paulo@89
|
12075
|
paulo@89
|
12076 return chart;
|
paulo@89
|
12077 };
|
paulo@89
|
12078
|
paulo@89
|
12079 nv.models.stackedArea = function() {
|
paulo@89
|
12080 "use strict";
|
paulo@89
|
12081
|
paulo@89
|
12082 //============================================================
|
paulo@89
|
12083 // Public Variables with Default Settings
|
paulo@89
|
12084 //------------------------------------------------------------
|
paulo@89
|
12085
|
paulo@89
|
12086 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
12087 , width = 960
|
paulo@89
|
12088 , height = 500
|
paulo@89
|
12089 , color = nv.utils.defaultColor() // a function that computes the color
|
paulo@89
|
12090 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
|
paulo@89
|
12091 , container = null
|
paulo@89
|
12092 , getX = function(d) { return d.x } // accessor to get the x value from a data point
|
paulo@89
|
12093 , getY = function(d) { return d.y } // accessor to get the y value from a data point
|
paulo@89
|
12094 , style = 'stack'
|
paulo@89
|
12095 , offset = 'zero'
|
paulo@89
|
12096 , order = 'default'
|
paulo@89
|
12097 , interpolate = 'linear' // controls the line interpolation
|
paulo@89
|
12098 , clipEdge = false // if true, masks lines within x and y scale
|
paulo@89
|
12099 , x //can be accessed via chart.xScale()
|
paulo@89
|
12100 , y //can be accessed via chart.yScale()
|
paulo@89
|
12101 , scatter = nv.models.scatter()
|
paulo@89
|
12102 , duration = 250
|
paulo@89
|
12103 , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
|
paulo@89
|
12104 ;
|
paulo@89
|
12105
|
paulo@89
|
12106 scatter
|
paulo@89
|
12107 .pointSize(2.2) // default size
|
paulo@89
|
12108 .pointDomain([2.2, 2.2]) // all the same size by default
|
paulo@89
|
12109 ;
|
paulo@89
|
12110
|
paulo@89
|
12111 /************************************
|
paulo@89
|
12112 * offset:
|
paulo@89
|
12113 * 'wiggle' (stream)
|
paulo@89
|
12114 * 'zero' (stacked)
|
paulo@89
|
12115 * 'expand' (normalize to 100%)
|
paulo@89
|
12116 * 'silhouette' (simple centered)
|
paulo@89
|
12117 *
|
paulo@89
|
12118 * order:
|
paulo@89
|
12119 * 'inside-out' (stream)
|
paulo@89
|
12120 * 'default' (input order)
|
paulo@89
|
12121 ************************************/
|
paulo@89
|
12122
|
paulo@89
|
12123 var renderWatch = nv.utils.renderWatch(dispatch, duration);
|
paulo@89
|
12124
|
paulo@89
|
12125 function chart(selection) {
|
paulo@89
|
12126 renderWatch.reset();
|
paulo@89
|
12127 renderWatch.models(scatter);
|
paulo@89
|
12128 selection.each(function(data) {
|
paulo@89
|
12129 var availableWidth = width - margin.left - margin.right,
|
paulo@89
|
12130 availableHeight = height - margin.top - margin.bottom;
|
paulo@89
|
12131
|
paulo@89
|
12132 container = d3.select(this);
|
paulo@89
|
12133 nv.utils.initSVG(container);
|
paulo@89
|
12134
|
paulo@89
|
12135 // Setup Scales
|
paulo@89
|
12136 x = scatter.xScale();
|
paulo@89
|
12137 y = scatter.yScale();
|
paulo@89
|
12138
|
paulo@89
|
12139 var dataRaw = data;
|
paulo@89
|
12140 // Injecting point index into each point because d3.layout.stack().out does not give index
|
paulo@89
|
12141 data.forEach(function(aseries, i) {
|
paulo@89
|
12142 aseries.seriesIndex = i;
|
paulo@89
|
12143 aseries.values = aseries.values.map(function(d, j) {
|
paulo@89
|
12144 d.index = j;
|
paulo@89
|
12145 d.seriesIndex = i;
|
paulo@89
|
12146 return d;
|
paulo@89
|
12147 });
|
paulo@89
|
12148 });
|
paulo@89
|
12149
|
paulo@89
|
12150 var dataFiltered = data.filter(function(series) {
|
paulo@89
|
12151 return !series.disabled;
|
paulo@89
|
12152 });
|
paulo@89
|
12153
|
paulo@89
|
12154 data = d3.layout.stack()
|
paulo@89
|
12155 .order(order)
|
paulo@89
|
12156 .offset(offset)
|
paulo@89
|
12157 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
|
paulo@89
|
12158 .x(getX)
|
paulo@89
|
12159 .y(getY)
|
paulo@89
|
12160 .out(function(d, y0, y) {
|
paulo@89
|
12161 d.display = {
|
paulo@89
|
12162 y: y,
|
paulo@89
|
12163 y0: y0
|
paulo@89
|
12164 };
|
paulo@89
|
12165 })
|
paulo@89
|
12166 (dataFiltered);
|
paulo@89
|
12167
|
paulo@89
|
12168 // Setup containers and skeleton of chart
|
paulo@89
|
12169 var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
|
paulo@89
|
12170 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
|
paulo@89
|
12171 var defsEnter = wrapEnter.append('defs');
|
paulo@89
|
12172 var gEnter = wrapEnter.append('g');
|
paulo@89
|
12173 var g = wrap.select('g');
|
paulo@89
|
12174
|
paulo@89
|
12175 gEnter.append('g').attr('class', 'nv-areaWrap');
|
paulo@89
|
12176 gEnter.append('g').attr('class', 'nv-scatterWrap');
|
paulo@89
|
12177
|
paulo@89
|
12178 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
12179
|
paulo@89
|
12180 // If the user has not specified forceY, make sure 0 is included in the domain
|
paulo@89
|
12181 // Otherwise, use user-specified values for forceY
|
paulo@89
|
12182 if (scatter.forceY().length == 0) {
|
paulo@89
|
12183 scatter.forceY().push(0);
|
paulo@89
|
12184 }
|
paulo@89
|
12185
|
paulo@89
|
12186 scatter
|
paulo@89
|
12187 .width(availableWidth)
|
paulo@89
|
12188 .height(availableHeight)
|
paulo@89
|
12189 .x(getX)
|
paulo@89
|
12190 .y(function(d) { return d.display.y + d.display.y0 })
|
paulo@89
|
12191 .forceY([0])
|
paulo@89
|
12192 .color(data.map(function(d,i) {
|
paulo@89
|
12193 return d.color || color(d, d.seriesIndex);
|
paulo@89
|
12194 }));
|
paulo@89
|
12195
|
paulo@89
|
12196 var scatterWrap = g.select('.nv-scatterWrap')
|
paulo@89
|
12197 .datum(data);
|
paulo@89
|
12198
|
paulo@89
|
12199 scatterWrap.call(scatter);
|
paulo@89
|
12200
|
paulo@89
|
12201 defsEnter.append('clipPath')
|
paulo@89
|
12202 .attr('id', 'nv-edge-clip-' + id)
|
paulo@89
|
12203 .append('rect');
|
paulo@89
|
12204
|
paulo@89
|
12205 wrap.select('#nv-edge-clip-' + id + ' rect')
|
paulo@89
|
12206 .attr('width', availableWidth)
|
paulo@89
|
12207 .attr('height', availableHeight);
|
paulo@89
|
12208
|
paulo@89
|
12209 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
|
paulo@89
|
12210
|
paulo@89
|
12211 var area = d3.svg.area()
|
paulo@89
|
12212 .x(function(d,i) { return x(getX(d,i)) })
|
paulo@89
|
12213 .y0(function(d) {
|
paulo@89
|
12214 return y(d.display.y0)
|
paulo@89
|
12215 })
|
paulo@89
|
12216 .y1(function(d) {
|
paulo@89
|
12217 return y(d.display.y + d.display.y0)
|
paulo@89
|
12218 })
|
paulo@89
|
12219 .interpolate(interpolate);
|
paulo@89
|
12220
|
paulo@89
|
12221 var zeroArea = d3.svg.area()
|
paulo@89
|
12222 .x(function(d,i) { return x(getX(d,i)) })
|
paulo@89
|
12223 .y0(function(d) { return y(d.display.y0) })
|
paulo@89
|
12224 .y1(function(d) { return y(d.display.y0) });
|
paulo@89
|
12225
|
paulo@89
|
12226 var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
|
paulo@89
|
12227 .data(function(d) { return d });
|
paulo@89
|
12228
|
paulo@89
|
12229 path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
|
paulo@89
|
12230 .attr('d', function(d,i){
|
paulo@89
|
12231 return zeroArea(d.values, d.seriesIndex);
|
paulo@89
|
12232 })
|
paulo@89
|
12233 .on('mouseover', function(d,i) {
|
paulo@89
|
12234 d3.select(this).classed('hover', true);
|
paulo@89
|
12235 dispatch.areaMouseover({
|
paulo@89
|
12236 point: d,
|
paulo@89
|
12237 series: d.key,
|
paulo@89
|
12238 pos: [d3.event.pageX, d3.event.pageY],
|
paulo@89
|
12239 seriesIndex: d.seriesIndex
|
paulo@89
|
12240 });
|
paulo@89
|
12241 })
|
paulo@89
|
12242 .on('mouseout', function(d,i) {
|
paulo@89
|
12243 d3.select(this).classed('hover', false);
|
paulo@89
|
12244 dispatch.areaMouseout({
|
paulo@89
|
12245 point: d,
|
paulo@89
|
12246 series: d.key,
|
paulo@89
|
12247 pos: [d3.event.pageX, d3.event.pageY],
|
paulo@89
|
12248 seriesIndex: d.seriesIndex
|
paulo@89
|
12249 });
|
paulo@89
|
12250 })
|
paulo@89
|
12251 .on('click', function(d,i) {
|
paulo@89
|
12252 d3.select(this).classed('hover', false);
|
paulo@89
|
12253 dispatch.areaClick({
|
paulo@89
|
12254 point: d,
|
paulo@89
|
12255 series: d.key,
|
paulo@89
|
12256 pos: [d3.event.pageX, d3.event.pageY],
|
paulo@89
|
12257 seriesIndex: d.seriesIndex
|
paulo@89
|
12258 });
|
paulo@89
|
12259 });
|
paulo@89
|
12260
|
paulo@89
|
12261 path.exit().remove();
|
paulo@89
|
12262 path.style('fill', function(d,i){
|
paulo@89
|
12263 return d.color || color(d, d.seriesIndex)
|
paulo@89
|
12264 })
|
paulo@89
|
12265 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
|
paulo@89
|
12266 path.watchTransition(renderWatch,'stackedArea path')
|
paulo@89
|
12267 .attr('d', function(d,i) {
|
paulo@89
|
12268 return area(d.values,i)
|
paulo@89
|
12269 });
|
paulo@89
|
12270
|
paulo@89
|
12271 //============================================================
|
paulo@89
|
12272 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
12273 //------------------------------------------------------------
|
paulo@89
|
12274
|
paulo@89
|
12275 scatter.dispatch.on('elementMouseover.area', function(e) {
|
paulo@89
|
12276 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
|
paulo@89
|
12277 });
|
paulo@89
|
12278 scatter.dispatch.on('elementMouseout.area', function(e) {
|
paulo@89
|
12279 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
|
paulo@89
|
12280 });
|
paulo@89
|
12281
|
paulo@89
|
12282 //Special offset functions
|
paulo@89
|
12283 chart.d3_stackedOffset_stackPercent = function(stackData) {
|
paulo@89
|
12284 var n = stackData.length, //How many series
|
paulo@89
|
12285 m = stackData[0].length, //how many points per series
|
paulo@89
|
12286 i,
|
paulo@89
|
12287 j,
|
paulo@89
|
12288 o,
|
paulo@89
|
12289 y0 = [];
|
paulo@89
|
12290
|
paulo@89
|
12291 for (j = 0; j < m; ++j) { //Looping through all points
|
paulo@89
|
12292 for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series
|
paulo@89
|
12293 o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time.
|
paulo@89
|
12294 }
|
paulo@89
|
12295
|
paulo@89
|
12296 if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0
|
paulo@89
|
12297 stackData[i][j][1] /= o;
|
paulo@89
|
12298 } else { //(total y value of all series at point in time i) == 0
|
paulo@89
|
12299 for (i = 0; i < n; i++) {
|
paulo@89
|
12300 stackData[i][j][1] = 0;
|
paulo@89
|
12301 }
|
paulo@89
|
12302 }
|
paulo@89
|
12303 }
|
paulo@89
|
12304 for (j = 0; j < m; ++j) y0[j] = 0;
|
paulo@89
|
12305 return y0;
|
paulo@89
|
12306 };
|
paulo@89
|
12307
|
paulo@89
|
12308 });
|
paulo@89
|
12309
|
paulo@89
|
12310 renderWatch.renderEnd('stackedArea immediate');
|
paulo@89
|
12311 return chart;
|
paulo@89
|
12312 }
|
paulo@89
|
12313
|
paulo@89
|
12314 //============================================================
|
paulo@89
|
12315 // Global getters and setters
|
paulo@89
|
12316 //------------------------------------------------------------
|
paulo@89
|
12317
|
paulo@89
|
12318 chart.dispatch = dispatch;
|
paulo@89
|
12319 chart.scatter = scatter;
|
paulo@89
|
12320
|
paulo@89
|
12321 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
|
paulo@89
|
12322 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
|
paulo@89
|
12323 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
|
paulo@89
|
12324
|
paulo@89
|
12325 chart.interpolate = function(_) {
|
paulo@89
|
12326 if (!arguments.length) return interpolate;
|
paulo@89
|
12327 interpolate = _;
|
paulo@89
|
12328 return chart;
|
paulo@89
|
12329 };
|
paulo@89
|
12330
|
paulo@89
|
12331 chart.duration = function(_) {
|
paulo@89
|
12332 if (!arguments.length) return duration;
|
paulo@89
|
12333 duration = _;
|
paulo@89
|
12334 renderWatch.reset(duration);
|
paulo@89
|
12335 scatter.duration(duration);
|
paulo@89
|
12336 return chart;
|
paulo@89
|
12337 };
|
paulo@89
|
12338
|
paulo@89
|
12339 chart.dispatch = dispatch;
|
paulo@89
|
12340 chart.scatter = scatter;
|
paulo@89
|
12341 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
12342
|
paulo@89
|
12343 chart._options = Object.create({}, {
|
paulo@89
|
12344 // simple options, just get/set the necessary values
|
paulo@89
|
12345 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
12346 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
12347 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
|
paulo@89
|
12348 offset: {get: function(){return offset;}, set: function(_){offset=_;}},
|
paulo@89
|
12349 order: {get: function(){return order;}, set: function(_){order=_;}},
|
paulo@89
|
12350 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
|
paulo@89
|
12351
|
paulo@89
|
12352 // simple functor options
|
paulo@89
|
12353 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
|
paulo@89
|
12354 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
|
paulo@89
|
12355
|
paulo@89
|
12356 // options that require extra logic in the setter
|
paulo@89
|
12357 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
12358 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
12359 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
12360 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
12361 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
12362 }},
|
paulo@89
|
12363 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
12364 color = nv.utils.getColor(_);
|
paulo@89
|
12365 }},
|
paulo@89
|
12366 style: {get: function(){return style;}, set: function(_){
|
paulo@89
|
12367 style = _;
|
paulo@89
|
12368 switch (style) {
|
paulo@89
|
12369 case 'stack':
|
paulo@89
|
12370 chart.offset('zero');
|
paulo@89
|
12371 chart.order('default');
|
paulo@89
|
12372 break;
|
paulo@89
|
12373 case 'stream':
|
paulo@89
|
12374 chart.offset('wiggle');
|
paulo@89
|
12375 chart.order('inside-out');
|
paulo@89
|
12376 break;
|
paulo@89
|
12377 case 'stream-center':
|
paulo@89
|
12378 chart.offset('silhouette');
|
paulo@89
|
12379 chart.order('inside-out');
|
paulo@89
|
12380 break;
|
paulo@89
|
12381 case 'expand':
|
paulo@89
|
12382 chart.offset('expand');
|
paulo@89
|
12383 chart.order('default');
|
paulo@89
|
12384 break;
|
paulo@89
|
12385 case 'stack_percent':
|
paulo@89
|
12386 chart.offset(chart.d3_stackedOffset_stackPercent);
|
paulo@89
|
12387 chart.order('default');
|
paulo@89
|
12388 break;
|
paulo@89
|
12389 }
|
paulo@89
|
12390 }},
|
paulo@89
|
12391 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
12392 duration = _;
|
paulo@89
|
12393 renderWatch.reset(duration);
|
paulo@89
|
12394 scatter.duration(duration);
|
paulo@89
|
12395 }}
|
paulo@89
|
12396 });
|
paulo@89
|
12397
|
paulo@89
|
12398 nv.utils.inheritOptions(chart, scatter);
|
paulo@89
|
12399 nv.utils.initOptions(chart);
|
paulo@89
|
12400
|
paulo@89
|
12401 return chart;
|
paulo@89
|
12402 };
|
paulo@89
|
12403
|
paulo@89
|
12404 nv.models.stackedAreaChart = function() {
|
paulo@89
|
12405 "use strict";
|
paulo@89
|
12406
|
paulo@89
|
12407 //============================================================
|
paulo@89
|
12408 // Public Variables with Default Settings
|
paulo@89
|
12409 //------------------------------------------------------------
|
paulo@89
|
12410
|
paulo@89
|
12411 var stacked = nv.models.stackedArea()
|
paulo@89
|
12412 , xAxis = nv.models.axis()
|
paulo@89
|
12413 , yAxis = nv.models.axis()
|
paulo@89
|
12414 , legend = nv.models.legend()
|
paulo@89
|
12415 , controls = nv.models.legend()
|
paulo@89
|
12416 , interactiveLayer = nv.interactiveGuideline()
|
paulo@89
|
12417 , tooltip = nv.models.tooltip()
|
paulo@89
|
12418 ;
|
paulo@89
|
12419
|
paulo@89
|
12420 var margin = {top: 30, right: 25, bottom: 50, left: 60}
|
paulo@89
|
12421 , width = null
|
paulo@89
|
12422 , height = null
|
paulo@89
|
12423 , color = nv.utils.defaultColor()
|
paulo@89
|
12424 , showControls = true
|
paulo@89
|
12425 , showLegend = true
|
paulo@89
|
12426 , showXAxis = true
|
paulo@89
|
12427 , showYAxis = true
|
paulo@89
|
12428 , rightAlignYAxis = false
|
paulo@89
|
12429 , useInteractiveGuideline = false
|
paulo@89
|
12430 , x //can be accessed via chart.xScale()
|
paulo@89
|
12431 , y //can be accessed via chart.yScale()
|
paulo@89
|
12432 , state = nv.utils.state()
|
paulo@89
|
12433 , defaultState = null
|
paulo@89
|
12434 , noData = null
|
paulo@89
|
12435 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
|
paulo@89
|
12436 , controlWidth = 250
|
paulo@89
|
12437 , controlOptions = ['Stacked','Stream','Expanded']
|
paulo@89
|
12438 , controlLabels = {}
|
paulo@89
|
12439 , duration = 250
|
paulo@89
|
12440 ;
|
paulo@89
|
12441
|
paulo@89
|
12442 state.style = stacked.style();
|
paulo@89
|
12443 xAxis.orient('bottom').tickPadding(7);
|
paulo@89
|
12444 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
|
paulo@89
|
12445
|
paulo@89
|
12446 tooltip
|
paulo@89
|
12447 .headerFormatter(function(d, i) {
|
paulo@89
|
12448 return xAxis.tickFormat()(d, i);
|
paulo@89
|
12449 })
|
paulo@89
|
12450 .valueFormatter(function(d, i) {
|
paulo@89
|
12451 return yAxis.tickFormat()(d, i);
|
paulo@89
|
12452 });
|
paulo@89
|
12453
|
paulo@89
|
12454 interactiveLayer.tooltip
|
paulo@89
|
12455 .headerFormatter(function(d, i) {
|
paulo@89
|
12456 return xAxis.tickFormat()(d, i);
|
paulo@89
|
12457 })
|
paulo@89
|
12458 .valueFormatter(function(d, i) {
|
paulo@89
|
12459 return yAxis.tickFormat()(d, i);
|
paulo@89
|
12460 });
|
paulo@89
|
12461
|
paulo@89
|
12462 var oldYTickFormat = null,
|
paulo@89
|
12463 oldValueFormatter = null;
|
paulo@89
|
12464
|
paulo@89
|
12465 controls.updateState(false);
|
paulo@89
|
12466
|
paulo@89
|
12467 //============================================================
|
paulo@89
|
12468 // Private Variables
|
paulo@89
|
12469 //------------------------------------------------------------
|
paulo@89
|
12470
|
paulo@89
|
12471 var renderWatch = nv.utils.renderWatch(dispatch);
|
paulo@89
|
12472 var style = stacked.style();
|
paulo@89
|
12473
|
paulo@89
|
12474 var stateGetter = function(data) {
|
paulo@89
|
12475 return function(){
|
paulo@89
|
12476 return {
|
paulo@89
|
12477 active: data.map(function(d) { return !d.disabled }),
|
paulo@89
|
12478 style: stacked.style()
|
paulo@89
|
12479 };
|
paulo@89
|
12480 }
|
paulo@89
|
12481 };
|
paulo@89
|
12482
|
paulo@89
|
12483 var stateSetter = function(data) {
|
paulo@89
|
12484 return function(state) {
|
paulo@89
|
12485 if (state.style !== undefined)
|
paulo@89
|
12486 style = state.style;
|
paulo@89
|
12487 if (state.active !== undefined)
|
paulo@89
|
12488 data.forEach(function(series,i) {
|
paulo@89
|
12489 series.disabled = !state.active[i];
|
paulo@89
|
12490 });
|
paulo@89
|
12491 }
|
paulo@89
|
12492 };
|
paulo@89
|
12493
|
paulo@89
|
12494 var percentFormatter = d3.format('%');
|
paulo@89
|
12495
|
paulo@89
|
12496 function chart(selection) {
|
paulo@89
|
12497 renderWatch.reset();
|
paulo@89
|
12498 renderWatch.models(stacked);
|
paulo@89
|
12499 if (showXAxis) renderWatch.models(xAxis);
|
paulo@89
|
12500 if (showYAxis) renderWatch.models(yAxis);
|
paulo@89
|
12501
|
paulo@89
|
12502 selection.each(function(data) {
|
paulo@89
|
12503 var container = d3.select(this),
|
paulo@89
|
12504 that = this;
|
paulo@89
|
12505 nv.utils.initSVG(container);
|
paulo@89
|
12506
|
paulo@89
|
12507 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
12508 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
12509
|
paulo@89
|
12510 chart.update = function() { container.transition().duration(duration).call(chart); };
|
paulo@89
|
12511 chart.container = this;
|
paulo@89
|
12512
|
paulo@89
|
12513 state
|
paulo@89
|
12514 .setter(stateSetter(data), chart.update)
|
paulo@89
|
12515 .getter(stateGetter(data))
|
paulo@89
|
12516 .update();
|
paulo@89
|
12517
|
paulo@89
|
12518 // DEPRECATED set state.disabled
|
paulo@89
|
12519 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
12520
|
paulo@89
|
12521 if (!defaultState) {
|
paulo@89
|
12522 var key;
|
paulo@89
|
12523 defaultState = {};
|
paulo@89
|
12524 for (key in state) {
|
paulo@89
|
12525 if (state[key] instanceof Array)
|
paulo@89
|
12526 defaultState[key] = state[key].slice(0);
|
paulo@89
|
12527 else
|
paulo@89
|
12528 defaultState[key] = state[key];
|
paulo@89
|
12529 }
|
paulo@89
|
12530 }
|
paulo@89
|
12531
|
paulo@89
|
12532 // Display No Data message if there's nothing to show.
|
paulo@89
|
12533 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
|
paulo@89
|
12534 nv.utils.noData(chart, container)
|
paulo@89
|
12535 return chart;
|
paulo@89
|
12536 } else {
|
paulo@89
|
12537 container.selectAll('.nv-noData').remove();
|
paulo@89
|
12538 }
|
paulo@89
|
12539
|
paulo@89
|
12540 // Setup Scales
|
paulo@89
|
12541 x = stacked.xScale();
|
paulo@89
|
12542 y = stacked.yScale();
|
paulo@89
|
12543
|
paulo@89
|
12544 // Setup containers and skeleton of chart
|
paulo@89
|
12545 var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
|
paulo@89
|
12546 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
|
paulo@89
|
12547 var g = wrap.select('g');
|
paulo@89
|
12548
|
paulo@89
|
12549 gEnter.append("rect").style("opacity",0);
|
paulo@89
|
12550 gEnter.append('g').attr('class', 'nv-x nv-axis');
|
paulo@89
|
12551 gEnter.append('g').attr('class', 'nv-y nv-axis');
|
paulo@89
|
12552 gEnter.append('g').attr('class', 'nv-stackedWrap');
|
paulo@89
|
12553 gEnter.append('g').attr('class', 'nv-legendWrap');
|
paulo@89
|
12554 gEnter.append('g').attr('class', 'nv-controlsWrap');
|
paulo@89
|
12555 gEnter.append('g').attr('class', 'nv-interactive');
|
paulo@89
|
12556
|
paulo@89
|
12557 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
|
paulo@89
|
12558
|
paulo@89
|
12559 // Legend
|
paulo@89
|
12560 if (showLegend) {
|
paulo@89
|
12561 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
|
paulo@89
|
12562
|
paulo@89
|
12563 legend.width(legendWidth);
|
paulo@89
|
12564 g.select('.nv-legendWrap').datum(data).call(legend);
|
paulo@89
|
12565
|
paulo@89
|
12566 if ( margin.top != legend.height()) {
|
paulo@89
|
12567 margin.top = legend.height();
|
paulo@89
|
12568 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
12569 }
|
paulo@89
|
12570
|
paulo@89
|
12571 g.select('.nv-legendWrap')
|
paulo@89
|
12572 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
|
paulo@89
|
12573 }
|
paulo@89
|
12574
|
paulo@89
|
12575 // Controls
|
paulo@89
|
12576 if (showControls) {
|
paulo@89
|
12577 var controlsData = [
|
paulo@89
|
12578 {
|
paulo@89
|
12579 key: controlLabels.stacked || 'Stacked',
|
paulo@89
|
12580 metaKey: 'Stacked',
|
paulo@89
|
12581 disabled: stacked.style() != 'stack',
|
paulo@89
|
12582 style: 'stack'
|
paulo@89
|
12583 },
|
paulo@89
|
12584 {
|
paulo@89
|
12585 key: controlLabels.stream || 'Stream',
|
paulo@89
|
12586 metaKey: 'Stream',
|
paulo@89
|
12587 disabled: stacked.style() != 'stream',
|
paulo@89
|
12588 style: 'stream'
|
paulo@89
|
12589 },
|
paulo@89
|
12590 {
|
paulo@89
|
12591 key: controlLabels.expanded || 'Expanded',
|
paulo@89
|
12592 metaKey: 'Expanded',
|
paulo@89
|
12593 disabled: stacked.style() != 'expand',
|
paulo@89
|
12594 style: 'expand'
|
paulo@89
|
12595 },
|
paulo@89
|
12596 {
|
paulo@89
|
12597 key: controlLabels.stack_percent || 'Stack %',
|
paulo@89
|
12598 metaKey: 'Stack_Percent',
|
paulo@89
|
12599 disabled: stacked.style() != 'stack_percent',
|
paulo@89
|
12600 style: 'stack_percent'
|
paulo@89
|
12601 }
|
paulo@89
|
12602 ];
|
paulo@89
|
12603
|
paulo@89
|
12604 controlWidth = (controlOptions.length/3) * 260;
|
paulo@89
|
12605 controlsData = controlsData.filter(function(d) {
|
paulo@89
|
12606 return controlOptions.indexOf(d.metaKey) !== -1;
|
paulo@89
|
12607 });
|
paulo@89
|
12608
|
paulo@89
|
12609 controls
|
paulo@89
|
12610 .width( controlWidth )
|
paulo@89
|
12611 .color(['#444', '#444', '#444']);
|
paulo@89
|
12612
|
paulo@89
|
12613 g.select('.nv-controlsWrap')
|
paulo@89
|
12614 .datum(controlsData)
|
paulo@89
|
12615 .call(controls);
|
paulo@89
|
12616
|
paulo@89
|
12617 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
|
paulo@89
|
12618 margin.top = Math.max(controls.height(), legend.height());
|
paulo@89
|
12619 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
12620 }
|
paulo@89
|
12621
|
paulo@89
|
12622 g.select('.nv-controlsWrap')
|
paulo@89
|
12623 .attr('transform', 'translate(0,' + (-margin.top) +')');
|
paulo@89
|
12624 }
|
paulo@89
|
12625
|
paulo@89
|
12626 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
12627
|
paulo@89
|
12628 if (rightAlignYAxis) {
|
paulo@89
|
12629 g.select(".nv-y.nv-axis")
|
paulo@89
|
12630 .attr("transform", "translate(" + availableWidth + ",0)");
|
paulo@89
|
12631 }
|
paulo@89
|
12632
|
paulo@89
|
12633 //Set up interactive layer
|
paulo@89
|
12634 if (useInteractiveGuideline) {
|
paulo@89
|
12635 interactiveLayer
|
paulo@89
|
12636 .width(availableWidth)
|
paulo@89
|
12637 .height(availableHeight)
|
paulo@89
|
12638 .margin({left: margin.left, top: margin.top})
|
paulo@89
|
12639 .svgContainer(container)
|
paulo@89
|
12640 .xScale(x);
|
paulo@89
|
12641 wrap.select(".nv-interactive").call(interactiveLayer);
|
paulo@89
|
12642 }
|
paulo@89
|
12643
|
paulo@89
|
12644 stacked
|
paulo@89
|
12645 .width(availableWidth)
|
paulo@89
|
12646 .height(availableHeight);
|
paulo@89
|
12647
|
paulo@89
|
12648 var stackedWrap = g.select('.nv-stackedWrap')
|
paulo@89
|
12649 .datum(data);
|
paulo@89
|
12650
|
paulo@89
|
12651 stackedWrap.transition().call(stacked);
|
paulo@89
|
12652
|
paulo@89
|
12653 // Setup Axes
|
paulo@89
|
12654 if (showXAxis) {
|
paulo@89
|
12655 xAxis.scale(x)
|
paulo@89
|
12656 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
|
paulo@89
|
12657 .tickSize( -availableHeight, 0);
|
paulo@89
|
12658
|
paulo@89
|
12659 g.select('.nv-x.nv-axis')
|
paulo@89
|
12660 .attr('transform', 'translate(0,' + availableHeight + ')');
|
paulo@89
|
12661
|
paulo@89
|
12662 g.select('.nv-x.nv-axis')
|
paulo@89
|
12663 .transition().duration(0)
|
paulo@89
|
12664 .call(xAxis);
|
paulo@89
|
12665 }
|
paulo@89
|
12666
|
paulo@89
|
12667 if (showYAxis) {
|
paulo@89
|
12668 var ticks;
|
paulo@89
|
12669 if (stacked.offset() === 'wiggle') {
|
paulo@89
|
12670 ticks = 0;
|
paulo@89
|
12671 }
|
paulo@89
|
12672 else {
|
paulo@89
|
12673 ticks = nv.utils.calcTicksY(availableHeight/36, data);
|
paulo@89
|
12674 }
|
paulo@89
|
12675 yAxis.scale(y)
|
paulo@89
|
12676 ._ticks(ticks)
|
paulo@89
|
12677 .tickSize(-availableWidth, 0);
|
paulo@89
|
12678
|
paulo@89
|
12679 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
|
paulo@89
|
12680 var currentFormat = yAxis.tickFormat();
|
paulo@89
|
12681
|
paulo@89
|
12682 if ( !oldYTickFormat || currentFormat !== percentFormatter )
|
paulo@89
|
12683 oldYTickFormat = currentFormat;
|
paulo@89
|
12684
|
paulo@89
|
12685 //Forces the yAxis to use percentage in 'expand' mode.
|
paulo@89
|
12686 yAxis.tickFormat(percentFormatter);
|
paulo@89
|
12687 }
|
paulo@89
|
12688 else {
|
paulo@89
|
12689 if (oldYTickFormat) {
|
paulo@89
|
12690 yAxis.tickFormat(oldYTickFormat);
|
paulo@89
|
12691 oldYTickFormat = null;
|
paulo@89
|
12692 }
|
paulo@89
|
12693 }
|
paulo@89
|
12694
|
paulo@89
|
12695 g.select('.nv-y.nv-axis')
|
paulo@89
|
12696 .transition().duration(0)
|
paulo@89
|
12697 .call(yAxis);
|
paulo@89
|
12698 }
|
paulo@89
|
12699
|
paulo@89
|
12700 //============================================================
|
paulo@89
|
12701 // Event Handling/Dispatching (in chart's scope)
|
paulo@89
|
12702 //------------------------------------------------------------
|
paulo@89
|
12703
|
paulo@89
|
12704 stacked.dispatch.on('areaClick.toggle', function(e) {
|
paulo@89
|
12705 if (data.filter(function(d) { return !d.disabled }).length === 1)
|
paulo@89
|
12706 data.forEach(function(d) {
|
paulo@89
|
12707 d.disabled = false;
|
paulo@89
|
12708 });
|
paulo@89
|
12709 else
|
paulo@89
|
12710 data.forEach(function(d,i) {
|
paulo@89
|
12711 d.disabled = (i != e.seriesIndex);
|
paulo@89
|
12712 });
|
paulo@89
|
12713
|
paulo@89
|
12714 state.disabled = data.map(function(d) { return !!d.disabled });
|
paulo@89
|
12715 dispatch.stateChange(state);
|
paulo@89
|
12716
|
paulo@89
|
12717 chart.update();
|
paulo@89
|
12718 });
|
paulo@89
|
12719
|
paulo@89
|
12720 legend.dispatch.on('stateChange', function(newState) {
|
paulo@89
|
12721 for (var key in newState)
|
paulo@89
|
12722 state[key] = newState[key];
|
paulo@89
|
12723 dispatch.stateChange(state);
|
paulo@89
|
12724 chart.update();
|
paulo@89
|
12725 });
|
paulo@89
|
12726
|
paulo@89
|
12727 controls.dispatch.on('legendClick', function(d,i) {
|
paulo@89
|
12728 if (!d.disabled) return;
|
paulo@89
|
12729
|
paulo@89
|
12730 controlsData = controlsData.map(function(s) {
|
paulo@89
|
12731 s.disabled = true;
|
paulo@89
|
12732 return s;
|
paulo@89
|
12733 });
|
paulo@89
|
12734 d.disabled = false;
|
paulo@89
|
12735
|
paulo@89
|
12736 stacked.style(d.style);
|
paulo@89
|
12737
|
paulo@89
|
12738
|
paulo@89
|
12739 state.style = stacked.style();
|
paulo@89
|
12740 dispatch.stateChange(state);
|
paulo@89
|
12741
|
paulo@89
|
12742 chart.update();
|
paulo@89
|
12743 });
|
paulo@89
|
12744
|
paulo@89
|
12745 interactiveLayer.dispatch.on('elementMousemove', function(e) {
|
paulo@89
|
12746 stacked.clearHighlights();
|
paulo@89
|
12747 var singlePoint, pointIndex, pointXLocation, allData = [];
|
paulo@89
|
12748 data
|
paulo@89
|
12749 .filter(function(series, i) {
|
paulo@89
|
12750 series.seriesIndex = i;
|
paulo@89
|
12751 return !series.disabled;
|
paulo@89
|
12752 })
|
paulo@89
|
12753 .forEach(function(series,i) {
|
paulo@89
|
12754 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
|
paulo@89
|
12755 var point = series.values[pointIndex];
|
paulo@89
|
12756 var pointYValue = chart.y()(point, pointIndex);
|
paulo@89
|
12757 if (pointYValue != null) {
|
paulo@89
|
12758 stacked.highlightPoint(i, pointIndex, true);
|
paulo@89
|
12759 }
|
paulo@89
|
12760 if (typeof point === 'undefined') return;
|
paulo@89
|
12761 if (typeof singlePoint === 'undefined') singlePoint = point;
|
paulo@89
|
12762 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
|
paulo@89
|
12763
|
paulo@89
|
12764 //If we are in 'expand' mode, use the stacked percent value instead of raw value.
|
paulo@89
|
12765 var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
|
paulo@89
|
12766 allData.push({
|
paulo@89
|
12767 key: series.key,
|
paulo@89
|
12768 value: tooltipValue,
|
paulo@89
|
12769 color: color(series,series.seriesIndex),
|
paulo@89
|
12770 stackedValue: point.display
|
paulo@89
|
12771 });
|
paulo@89
|
12772 });
|
paulo@89
|
12773
|
paulo@89
|
12774 allData.reverse();
|
paulo@89
|
12775
|
paulo@89
|
12776 //Highlight the tooltip entry based on which stack the mouse is closest to.
|
paulo@89
|
12777 if (allData.length > 2) {
|
paulo@89
|
12778 var yValue = chart.yScale().invert(e.mouseY);
|
paulo@89
|
12779 var yDistMax = Infinity, indexToHighlight = null;
|
paulo@89
|
12780 allData.forEach(function(series,i) {
|
paulo@89
|
12781
|
paulo@89
|
12782 //To handle situation where the stacked area chart is negative, we need to use absolute values
|
paulo@89
|
12783 //when checking if the mouse Y value is within the stack area.
|
paulo@89
|
12784 yValue = Math.abs(yValue);
|
paulo@89
|
12785 var stackedY0 = Math.abs(series.stackedValue.y0);
|
paulo@89
|
12786 var stackedY = Math.abs(series.stackedValue.y);
|
paulo@89
|
12787 if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
|
paulo@89
|
12788 {
|
paulo@89
|
12789 indexToHighlight = i;
|
paulo@89
|
12790 return;
|
paulo@89
|
12791 }
|
paulo@89
|
12792 });
|
paulo@89
|
12793 if (indexToHighlight != null)
|
paulo@89
|
12794 allData[indexToHighlight].highlight = true;
|
paulo@89
|
12795 }
|
paulo@89
|
12796
|
paulo@89
|
12797 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
|
paulo@89
|
12798
|
paulo@89
|
12799 var valueFormatter = interactiveLayer.tooltip.valueFormatter();
|
paulo@89
|
12800 // Keeps track of the tooltip valueFormatter if the chart changes to expanded view
|
paulo@89
|
12801 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
|
paulo@89
|
12802 if ( !oldValueFormatter ) {
|
paulo@89
|
12803 oldValueFormatter = valueFormatter;
|
paulo@89
|
12804 }
|
paulo@89
|
12805 //Forces the tooltip to use percentage in 'expand' mode.
|
paulo@89
|
12806 valueFormatter = d3.format(".1%");
|
paulo@89
|
12807 }
|
paulo@89
|
12808 else {
|
paulo@89
|
12809 if (oldValueFormatter) {
|
paulo@89
|
12810 valueFormatter = oldValueFormatter;
|
paulo@89
|
12811 oldValueFormatter = null;
|
paulo@89
|
12812 }
|
paulo@89
|
12813 }
|
paulo@89
|
12814
|
paulo@89
|
12815 interactiveLayer.tooltip
|
paulo@89
|
12816 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
|
paulo@89
|
12817 .chartContainer(that.parentNode)
|
paulo@89
|
12818 .valueFormatter(valueFormatter)
|
paulo@89
|
12819 .data(
|
paulo@89
|
12820 {
|
paulo@89
|
12821 value: xValue,
|
paulo@89
|
12822 series: allData
|
paulo@89
|
12823 }
|
paulo@89
|
12824 )();
|
paulo@89
|
12825
|
paulo@89
|
12826 interactiveLayer.renderGuideLine(pointXLocation);
|
paulo@89
|
12827
|
paulo@89
|
12828 });
|
paulo@89
|
12829
|
paulo@89
|
12830 interactiveLayer.dispatch.on("elementMouseout",function(e) {
|
paulo@89
|
12831 stacked.clearHighlights();
|
paulo@89
|
12832 });
|
paulo@89
|
12833
|
paulo@89
|
12834 // Update chart from a state object passed to event handler
|
paulo@89
|
12835 dispatch.on('changeState', function(e) {
|
paulo@89
|
12836
|
paulo@89
|
12837 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
|
paulo@89
|
12838 data.forEach(function(series,i) {
|
paulo@89
|
12839 series.disabled = e.disabled[i];
|
paulo@89
|
12840 });
|
paulo@89
|
12841
|
paulo@89
|
12842 state.disabled = e.disabled;
|
paulo@89
|
12843 }
|
paulo@89
|
12844
|
paulo@89
|
12845 if (typeof e.style !== 'undefined') {
|
paulo@89
|
12846 stacked.style(e.style);
|
paulo@89
|
12847 style = e.style;
|
paulo@89
|
12848 }
|
paulo@89
|
12849
|
paulo@89
|
12850 chart.update();
|
paulo@89
|
12851 });
|
paulo@89
|
12852
|
paulo@89
|
12853 });
|
paulo@89
|
12854
|
paulo@89
|
12855 renderWatch.renderEnd('stacked Area chart immediate');
|
paulo@89
|
12856 return chart;
|
paulo@89
|
12857 }
|
paulo@89
|
12858
|
paulo@89
|
12859 //============================================================
|
paulo@89
|
12860 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
12861 //------------------------------------------------------------
|
paulo@89
|
12862
|
paulo@89
|
12863 stacked.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
12864 evt.point['x'] = stacked.x()(evt.point);
|
paulo@89
|
12865 evt.point['y'] = stacked.y()(evt.point);
|
paulo@89
|
12866 tooltip.data(evt).position(evt.pos).hidden(false);
|
paulo@89
|
12867 });
|
paulo@89
|
12868
|
paulo@89
|
12869 stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
12870 tooltip.hidden(true)
|
paulo@89
|
12871 });
|
paulo@89
|
12872
|
paulo@89
|
12873 //============================================================
|
paulo@89
|
12874 // Expose Public Variables
|
paulo@89
|
12875 //------------------------------------------------------------
|
paulo@89
|
12876
|
paulo@89
|
12877 // expose chart's sub-components
|
paulo@89
|
12878 chart.dispatch = dispatch;
|
paulo@89
|
12879 chart.stacked = stacked;
|
paulo@89
|
12880 chart.legend = legend;
|
paulo@89
|
12881 chart.controls = controls;
|
paulo@89
|
12882 chart.xAxis = xAxis;
|
paulo@89
|
12883 chart.yAxis = yAxis;
|
paulo@89
|
12884 chart.interactiveLayer = interactiveLayer;
|
paulo@89
|
12885 chart.tooltip = tooltip;
|
paulo@89
|
12886
|
paulo@89
|
12887 chart.dispatch = dispatch;
|
paulo@89
|
12888 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
12889
|
paulo@89
|
12890 chart._options = Object.create({}, {
|
paulo@89
|
12891 // simple options, just get/set the necessary values
|
paulo@89
|
12892 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
12893 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
12894 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
|
paulo@89
|
12895 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
|
paulo@89
|
12896 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
|
paulo@89
|
12897 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
12898 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
12899 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
|
paulo@89
|
12900 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
|
paulo@89
|
12901 controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}},
|
paulo@89
|
12902
|
paulo@89
|
12903 // deprecated options
|
paulo@89
|
12904 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
|
paulo@89
|
12905 // deprecated after 1.7.1
|
paulo@89
|
12906 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
|
paulo@89
|
12907 tooltip.enabled(!!_);
|
paulo@89
|
12908 }},
|
paulo@89
|
12909 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
|
paulo@89
|
12910 // deprecated after 1.7.1
|
paulo@89
|
12911 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
|
paulo@89
|
12912 tooltip.contentGenerator(_);
|
paulo@89
|
12913 }},
|
paulo@89
|
12914
|
paulo@89
|
12915 // options that require extra logic in the setter
|
paulo@89
|
12916 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
12917 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
12918 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
12919 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
12920 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
12921 }},
|
paulo@89
|
12922 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
12923 duration = _;
|
paulo@89
|
12924 renderWatch.reset(duration);
|
paulo@89
|
12925 stacked.duration(duration);
|
paulo@89
|
12926 xAxis.duration(duration);
|
paulo@89
|
12927 yAxis.duration(duration);
|
paulo@89
|
12928 }},
|
paulo@89
|
12929 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
12930 color = nv.utils.getColor(_);
|
paulo@89
|
12931 legend.color(color);
|
paulo@89
|
12932 stacked.color(color);
|
paulo@89
|
12933 }},
|
paulo@89
|
12934 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
|
paulo@89
|
12935 rightAlignYAxis = _;
|
paulo@89
|
12936 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
|
paulo@89
|
12937 }},
|
paulo@89
|
12938 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
|
paulo@89
|
12939 useInteractiveGuideline = !!_;
|
paulo@89
|
12940 chart.interactive(!_);
|
paulo@89
|
12941 chart.useVoronoi(!_);
|
paulo@89
|
12942 stacked.scatter.interactive(!_);
|
paulo@89
|
12943 }}
|
paulo@89
|
12944 });
|
paulo@89
|
12945
|
paulo@89
|
12946 nv.utils.inheritOptions(chart, stacked);
|
paulo@89
|
12947 nv.utils.initOptions(chart);
|
paulo@89
|
12948
|
paulo@89
|
12949 return chart;
|
paulo@89
|
12950 };
|
paulo@89
|
12951 // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
|
paulo@89
|
12952 nv.models.sunburst = function() {
|
paulo@89
|
12953 "use strict";
|
paulo@89
|
12954
|
paulo@89
|
12955 //============================================================
|
paulo@89
|
12956 // Public Variables with Default Settings
|
paulo@89
|
12957 //------------------------------------------------------------
|
paulo@89
|
12958
|
paulo@89
|
12959 var margin = {top: 0, right: 0, bottom: 0, left: 0}
|
paulo@89
|
12960 , width = null
|
paulo@89
|
12961 , height = null
|
paulo@89
|
12962 , mode = "count"
|
paulo@89
|
12963 , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }}
|
paulo@89
|
12964 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
|
paulo@89
|
12965 , container = null
|
paulo@89
|
12966 , color = nv.utils.defaultColor()
|
paulo@89
|
12967 , duration = 500
|
paulo@89
|
12968 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd')
|
paulo@89
|
12969 ;
|
paulo@89
|
12970
|
paulo@89
|
12971 var x = d3.scale.linear().range([0, 2 * Math.PI]);
|
paulo@89
|
12972 var y = d3.scale.sqrt();
|
paulo@89
|
12973
|
paulo@89
|
12974 var partition = d3.layout.partition()
|
paulo@89
|
12975 .sort(null)
|
paulo@89
|
12976 .value(function(d) { return 1; });
|
paulo@89
|
12977
|
paulo@89
|
12978 var arc = d3.svg.arc()
|
paulo@89
|
12979 .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
|
paulo@89
|
12980 .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
|
paulo@89
|
12981 .innerRadius(function(d) { return Math.max(0, y(d.y)); })
|
paulo@89
|
12982 .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
|
paulo@89
|
12983
|
paulo@89
|
12984 // Keep track of the current and previous node being displayed as the root.
|
paulo@89
|
12985 var node, prevNode;
|
paulo@89
|
12986 // Keep track of the root node
|
paulo@89
|
12987 var rootNode;
|
paulo@89
|
12988
|
paulo@89
|
12989 //============================================================
|
paulo@89
|
12990 // chart function
|
paulo@89
|
12991 //------------------------------------------------------------
|
paulo@89
|
12992
|
paulo@89
|
12993 var renderWatch = nv.utils.renderWatch(dispatch);
|
paulo@89
|
12994
|
paulo@89
|
12995 function chart(selection) {
|
paulo@89
|
12996 renderWatch.reset();
|
paulo@89
|
12997 selection.each(function(data) {
|
paulo@89
|
12998 container = d3.select(this);
|
paulo@89
|
12999 var availableWidth = nv.utils.availableWidth(width, container, margin);
|
paulo@89
|
13000 var availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
13001 var radius = Math.min(availableWidth, availableHeight) / 2;
|
paulo@89
|
13002 var path;
|
paulo@89
|
13003
|
paulo@89
|
13004 nv.utils.initSVG(container);
|
paulo@89
|
13005
|
paulo@89
|
13006 // Setup containers and skeleton of chart
|
paulo@89
|
13007 var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data);
|
paulo@89
|
13008 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id);
|
paulo@89
|
13009
|
paulo@89
|
13010 var g = wrapEnter.selectAll('nv-sunburst');
|
paulo@89
|
13011
|
paulo@89
|
13012 wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
|
paulo@89
|
13013
|
paulo@89
|
13014 container.on('click', function (d, i) {
|
paulo@89
|
13015 dispatch.chartClick({
|
paulo@89
|
13016 data: d,
|
paulo@89
|
13017 index: i,
|
paulo@89
|
13018 pos: d3.event,
|
paulo@89
|
13019 id: id
|
paulo@89
|
13020 });
|
paulo@89
|
13021 });
|
paulo@89
|
13022
|
paulo@89
|
13023 y.range([0, radius]);
|
paulo@89
|
13024
|
paulo@89
|
13025 node = node || data;
|
paulo@89
|
13026 rootNode = data[0];
|
paulo@89
|
13027 partition.value(modes[mode] || modes["count"]);
|
paulo@89
|
13028 path = g.data(partition.nodes).enter()
|
paulo@89
|
13029 .append("path")
|
paulo@89
|
13030 .attr("d", arc)
|
paulo@89
|
13031 .style("fill", function (d) {
|
paulo@89
|
13032 return color((d.children ? d : d.parent).name);
|
paulo@89
|
13033 })
|
paulo@89
|
13034 .style("stroke", "#FFF")
|
paulo@89
|
13035 .on("click", function(d) {
|
paulo@89
|
13036 if (prevNode !== node && node !== d) prevNode = node;
|
paulo@89
|
13037 node = d;
|
paulo@89
|
13038 path.transition()
|
paulo@89
|
13039 .duration(duration)
|
paulo@89
|
13040 .attrTween("d", arcTweenZoom(d));
|
paulo@89
|
13041 })
|
paulo@89
|
13042 .each(stash)
|
paulo@89
|
13043 .on("dblclick", function(d) {
|
paulo@89
|
13044 if (prevNode.parent == d) {
|
paulo@89
|
13045 path.transition()
|
paulo@89
|
13046 .duration(duration)
|
paulo@89
|
13047 .attrTween("d", arcTweenZoom(rootNode));
|
paulo@89
|
13048 }
|
paulo@89
|
13049 })
|
paulo@89
|
13050 .each(stash)
|
paulo@89
|
13051 .on('mouseover', function(d,i){
|
paulo@89
|
13052 d3.select(this).classed('hover', true).style('opacity', 0.8);
|
paulo@89
|
13053 dispatch.elementMouseover({
|
paulo@89
|
13054 data: d,
|
paulo@89
|
13055 color: d3.select(this).style("fill")
|
paulo@89
|
13056 });
|
paulo@89
|
13057 })
|
paulo@89
|
13058 .on('mouseout', function(d,i){
|
paulo@89
|
13059 d3.select(this).classed('hover', false).style('opacity', 1);
|
paulo@89
|
13060 dispatch.elementMouseout({
|
paulo@89
|
13061 data: d
|
paulo@89
|
13062 });
|
paulo@89
|
13063 })
|
paulo@89
|
13064 .on('mousemove', function(d,i){
|
paulo@89
|
13065 dispatch.elementMousemove({
|
paulo@89
|
13066 data: d
|
paulo@89
|
13067 });
|
paulo@89
|
13068 });
|
paulo@89
|
13069
|
paulo@89
|
13070
|
paulo@89
|
13071
|
paulo@89
|
13072 // Setup for switching data: stash the old values for transition.
|
paulo@89
|
13073 function stash(d) {
|
paulo@89
|
13074 d.x0 = d.x;
|
paulo@89
|
13075 d.dx0 = d.dx;
|
paulo@89
|
13076 }
|
paulo@89
|
13077
|
paulo@89
|
13078 // When switching data: interpolate the arcs in data space.
|
paulo@89
|
13079 function arcTweenData(a, i) {
|
paulo@89
|
13080 var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a);
|
paulo@89
|
13081
|
paulo@89
|
13082 function tween(t) {
|
paulo@89
|
13083 var b = oi(t);
|
paulo@89
|
13084 a.x0 = b.x;
|
paulo@89
|
13085 a.dx0 = b.dx;
|
paulo@89
|
13086 return arc(b);
|
paulo@89
|
13087 }
|
paulo@89
|
13088
|
paulo@89
|
13089 if (i == 0) {
|
paulo@89
|
13090 // If we are on the first arc, adjust the x domain to match the root node
|
paulo@89
|
13091 // at the current zoom level. (We only need to do this once.)
|
paulo@89
|
13092 var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]);
|
paulo@89
|
13093 return function (t) {
|
paulo@89
|
13094 x.domain(xd(t));
|
paulo@89
|
13095 return tween(t);
|
paulo@89
|
13096 };
|
paulo@89
|
13097 } else {
|
paulo@89
|
13098 return tween;
|
paulo@89
|
13099 }
|
paulo@89
|
13100 }
|
paulo@89
|
13101
|
paulo@89
|
13102 // When zooming: interpolate the scales.
|
paulo@89
|
13103 function arcTweenZoom(d) {
|
paulo@89
|
13104 var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
|
paulo@89
|
13105 yd = d3.interpolate(y.domain(), [d.y, 1]),
|
paulo@89
|
13106 yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
|
paulo@89
|
13107 return function (d, i) {
|
paulo@89
|
13108 return i
|
paulo@89
|
13109 ? function (t) {
|
paulo@89
|
13110 return arc(d);
|
paulo@89
|
13111 }
|
paulo@89
|
13112 : function (t) {
|
paulo@89
|
13113 x.domain(xd(t));
|
paulo@89
|
13114 y.domain(yd(t)).range(yr(t));
|
paulo@89
|
13115 return arc(d);
|
paulo@89
|
13116 };
|
paulo@89
|
13117 };
|
paulo@89
|
13118 }
|
paulo@89
|
13119
|
paulo@89
|
13120 });
|
paulo@89
|
13121
|
paulo@89
|
13122 renderWatch.renderEnd('sunburst immediate');
|
paulo@89
|
13123 return chart;
|
paulo@89
|
13124 }
|
paulo@89
|
13125
|
paulo@89
|
13126 //============================================================
|
paulo@89
|
13127 // Expose Public Variables
|
paulo@89
|
13128 //------------------------------------------------------------
|
paulo@89
|
13129
|
paulo@89
|
13130 chart.dispatch = dispatch;
|
paulo@89
|
13131 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
13132
|
paulo@89
|
13133 chart._options = Object.create({}, {
|
paulo@89
|
13134 // simple options, just get/set the necessary values
|
paulo@89
|
13135 width: {get: function(){return width;}, set: function(_){width=_;}},
|
paulo@89
|
13136 height: {get: function(){return height;}, set: function(_){height=_;}},
|
paulo@89
|
13137 mode: {get: function(){return mode;}, set: function(_){mode=_;}},
|
paulo@89
|
13138 id: {get: function(){return id;}, set: function(_){id=_;}},
|
paulo@89
|
13139 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
|
paulo@89
|
13140
|
paulo@89
|
13141 // options that require extra logic in the setter
|
paulo@89
|
13142 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
13143 margin.top = _.top != undefined ? _.top : margin.top;
|
paulo@89
|
13144 margin.right = _.right != undefined ? _.right : margin.right;
|
paulo@89
|
13145 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
|
paulo@89
|
13146 margin.left = _.left != undefined ? _.left : margin.left;
|
paulo@89
|
13147 }},
|
paulo@89
|
13148 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
13149 color=nv.utils.getColor(_);
|
paulo@89
|
13150 }}
|
paulo@89
|
13151 });
|
paulo@89
|
13152
|
paulo@89
|
13153 nv.utils.initOptions(chart);
|
paulo@89
|
13154 return chart;
|
paulo@89
|
13155 };
|
paulo@89
|
13156 nv.models.sunburstChart = function() {
|
paulo@89
|
13157 "use strict";
|
paulo@89
|
13158
|
paulo@89
|
13159 //============================================================
|
paulo@89
|
13160 // Public Variables with Default Settings
|
paulo@89
|
13161 //------------------------------------------------------------
|
paulo@89
|
13162
|
paulo@89
|
13163 var sunburst = nv.models.sunburst();
|
paulo@89
|
13164 var tooltip = nv.models.tooltip();
|
paulo@89
|
13165
|
paulo@89
|
13166 var margin = {top: 30, right: 20, bottom: 20, left: 20}
|
paulo@89
|
13167 , width = null
|
paulo@89
|
13168 , height = null
|
paulo@89
|
13169 , color = nv.utils.defaultColor()
|
paulo@89
|
13170 , id = Math.round(Math.random() * 100000)
|
paulo@89
|
13171 , defaultState = null
|
paulo@89
|
13172 , noData = null
|
paulo@89
|
13173 , duration = 250
|
paulo@89
|
13174 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
|
paulo@89
|
13175 ;
|
paulo@89
|
13176
|
paulo@89
|
13177 //============================================================
|
paulo@89
|
13178 // Private Variables
|
paulo@89
|
13179 //------------------------------------------------------------
|
paulo@89
|
13180
|
paulo@89
|
13181 var renderWatch = nv.utils.renderWatch(dispatch);
|
paulo@89
|
13182 tooltip.headerEnabled(false).duration(0).valueFormatter(function(d, i) {
|
paulo@89
|
13183 return d;
|
paulo@89
|
13184 });
|
paulo@89
|
13185
|
paulo@89
|
13186 //============================================================
|
paulo@89
|
13187 // Chart function
|
paulo@89
|
13188 //------------------------------------------------------------
|
paulo@89
|
13189
|
paulo@89
|
13190 function chart(selection) {
|
paulo@89
|
13191 renderWatch.reset();
|
paulo@89
|
13192 renderWatch.models(sunburst);
|
paulo@89
|
13193
|
paulo@89
|
13194 selection.each(function(data) {
|
paulo@89
|
13195 var container = d3.select(this);
|
paulo@89
|
13196 nv.utils.initSVG(container);
|
paulo@89
|
13197
|
paulo@89
|
13198 var that = this;
|
paulo@89
|
13199 var availableWidth = nv.utils.availableWidth(width, container, margin),
|
paulo@89
|
13200 availableHeight = nv.utils.availableHeight(height, container, margin);
|
paulo@89
|
13201
|
paulo@89
|
13202 chart.update = function() {
|
paulo@89
|
13203 if (duration === 0)
|
paulo@89
|
13204 container.call(chart);
|
paulo@89
|
13205 else
|
paulo@89
|
13206 container.transition().duration(duration).call(chart)
|
paulo@89
|
13207 };
|
paulo@89
|
13208 chart.container = this;
|
paulo@89
|
13209
|
paulo@89
|
13210 // Display No Data message if there's nothing to show.
|
paulo@89
|
13211 if (!data || !data.length) {
|
paulo@89
|
13212 nv.utils.noData(chart, container);
|
paulo@89
|
13213 return chart;
|
paulo@89
|
13214 } else {
|
paulo@89
|
13215 container.selectAll('.nv-noData').remove();
|
paulo@89
|
13216 }
|
paulo@89
|
13217
|
paulo@89
|
13218 // Setup containers and skeleton of chart
|
paulo@89
|
13219 var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data);
|
paulo@89
|
13220 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g');
|
paulo@89
|
13221 var g = wrap.select('g');
|
paulo@89
|
13222
|
paulo@89
|
13223 gEnter.append('g').attr('class', 'nv-sunburstWrap');
|
paulo@89
|
13224
|
paulo@89
|
13225 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
paulo@89
|
13226
|
paulo@89
|
13227 // Main Chart Component(s)
|
paulo@89
|
13228 sunburst.width(availableWidth).height(availableHeight);
|
paulo@89
|
13229 var sunWrap = g.select('.nv-sunburstWrap').datum(data);
|
paulo@89
|
13230 d3.transition(sunWrap).call(sunburst);
|
paulo@89
|
13231
|
paulo@89
|
13232 });
|
paulo@89
|
13233
|
paulo@89
|
13234 renderWatch.renderEnd('sunburstChart immediate');
|
paulo@89
|
13235 return chart;
|
paulo@89
|
13236 }
|
paulo@89
|
13237
|
paulo@89
|
13238 //============================================================
|
paulo@89
|
13239 // Event Handling/Dispatching (out of chart's scope)
|
paulo@89
|
13240 //------------------------------------------------------------
|
paulo@89
|
13241
|
paulo@89
|
13242 sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
|
paulo@89
|
13243 evt['series'] = {
|
paulo@89
|
13244 key: evt.data.name,
|
paulo@89
|
13245 value: evt.data.size,
|
paulo@89
|
13246 color: evt.color
|
paulo@89
|
13247 };
|
paulo@89
|
13248 tooltip.data(evt).hidden(false);
|
paulo@89
|
13249 });
|
paulo@89
|
13250
|
paulo@89
|
13251 sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
|
paulo@89
|
13252 tooltip.hidden(true);
|
paulo@89
|
13253 });
|
paulo@89
|
13254
|
paulo@89
|
13255 sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
|
paulo@89
|
13256 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
|
paulo@89
|
13257 });
|
paulo@89
|
13258
|
paulo@89
|
13259 //============================================================
|
paulo@89
|
13260 // Expose Public Variables
|
paulo@89
|
13261 //------------------------------------------------------------
|
paulo@89
|
13262
|
paulo@89
|
13263 // expose chart's sub-components
|
paulo@89
|
13264 chart.dispatch = dispatch;
|
paulo@89
|
13265 chart.sunburst = sunburst;
|
paulo@89
|
13266 chart.tooltip = tooltip;
|
paulo@89
|
13267 chart.options = nv.utils.optionsFunc.bind(chart);
|
paulo@89
|
13268
|
paulo@89
|
13269 // use Object get/set functionality to map between vars and chart functions
|
paulo@89
|
13270 chart._options = Object.create({}, {
|
paulo@89
|
13271 // simple options, just get/set the necessary values
|
paulo@89
|
13272 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
|
paulo@89
|
13273 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
|
paulo@89
|
13274
|
paulo@89
|
13275 // options that require extra logic in the setter
|
paulo@89
|
13276 color: {get: function(){return color;}, set: function(_){
|
paulo@89
|
13277 color = _;
|
paulo@89
|
13278 sunburst.color(color);
|
paulo@89
|
13279 }},
|
paulo@89
|
13280 duration: {get: function(){return duration;}, set: function(_){
|
paulo@89
|
13281 duration = _;
|
paulo@89
|
13282 renderWatch.reset(duration);
|
paulo@89
|
13283 sunburst.duration(duration);
|
paulo@89
|
13284 }},
|
paulo@89
|
13285 margin: {get: function(){return margin;}, set: function(_){
|
paulo@89
|
13286 margin.top = _.top !== undefined ? _.top : margin.top;
|
paulo@89
|
13287 margin.right = _.right !== undefined ? _.right : margin.right;
|
paulo@89
|
13288 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
|
paulo@89
|
13289 margin.left = _.left !== undefined ? _.left : margin.left;
|
paulo@89
|
13290 }}
|
paulo@89
|
13291 });
|
paulo@89
|
13292 nv.utils.inheritOptions(chart, sunburst);
|
paulo@89
|
13293 nv.utils.initOptions(chart);
|
paulo@89
|
13294 return chart;
|
paulo@89
|
13295 };
|
paulo@89
|
13296
|
paulo@89
|
13297 nv.version = "1.8.1";
|
paulo@89
|
13298 })(); |