Mercurial > hg > index.fcgi > www > www-1
comparison gtc/nv.d3.js @ 144:90f3021e3137
myrss2: FEEDS: Remove longform.org; add propublic.org
author | paulo |
---|---|
date | Tue, 28 May 2024 06:23:58 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d85e49336d74 |
---|---|
1 /* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */ | |
2 (function(){ | |
3 | |
4 // set up main nv object | |
5 var nv = {}; | |
6 | |
7 // the major global objects under the nv namespace | |
8 nv.dev = false; //set false when in production | |
9 nv.tooltip = nv.tooltip || {}; // For the tooltip system | |
10 nv.utils = nv.utils || {}; // Utility subsystem | |
11 nv.models = nv.models || {}; //stores all the possible models/components | |
12 nv.charts = {}; //stores all the ready to use charts | |
13 nv.logs = {}; //stores some statistics and potential error messages | |
14 nv.dom = {}; //DOM manipulation functions | |
15 | |
16 nv.dispatch = d3.dispatch('render_start', 'render_end'); | |
17 | |
18 // Function bind polyfill | |
19 // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment | |
20 // https://github.com/ariya/phantomjs/issues/10522 | |
21 // http://kangax.github.io/compat-table/es5/#Function.prototype.bind | |
22 // phantomJS is used for running the test suite | |
23 if (!Function.prototype.bind) { | |
24 Function.prototype.bind = function (oThis) { | |
25 if (typeof this !== "function") { | |
26 // closest thing possible to the ECMAScript 5 internal IsCallable function | |
27 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); | |
28 } | |
29 | |
30 var aArgs = Array.prototype.slice.call(arguments, 1), | |
31 fToBind = this, | |
32 fNOP = function () {}, | |
33 fBound = function () { | |
34 return fToBind.apply(this instanceof fNOP && oThis | |
35 ? this | |
36 : oThis, | |
37 aArgs.concat(Array.prototype.slice.call(arguments))); | |
38 }; | |
39 | |
40 fNOP.prototype = this.prototype; | |
41 fBound.prototype = new fNOP(); | |
42 return fBound; | |
43 }; | |
44 } | |
45 | |
46 // Development render timers - disabled if dev = false | |
47 if (nv.dev) { | |
48 nv.dispatch.on('render_start', function(e) { | |
49 nv.logs.startTime = +new Date(); | |
50 }); | |
51 | |
52 nv.dispatch.on('render_end', function(e) { | |
53 nv.logs.endTime = +new Date(); | |
54 nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime; | |
55 nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times | |
56 }); | |
57 } | |
58 | |
59 // Logs all arguments, and returns the last so you can test things in place | |
60 // Note: in IE8 console.log is an object not a function, and if modernizr is used | |
61 // then calling Function.prototype.bind with with anything other than a function | |
62 // causes a TypeError to be thrown. | |
63 nv.log = function() { | |
64 if (nv.dev && window.console && console.log && console.log.apply) | |
65 console.log.apply(console, arguments); | |
66 else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) { | |
67 var log = Function.prototype.bind.call(console.log, console); | |
68 log.apply(console, arguments); | |
69 } | |
70 return arguments[arguments.length - 1]; | |
71 }; | |
72 | |
73 // print console warning, should be used by deprecated functions | |
74 nv.deprecated = function(name, info) { | |
75 if (console && console.warn) { | |
76 console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || ''); | |
77 } | |
78 }; | |
79 | |
80 // The nv.render function is used to queue up chart rendering | |
81 // in non-blocking async functions. | |
82 // When all queued charts are done rendering, nv.dispatch.render_end is invoked. | |
83 nv.render = function render(step) { | |
84 // number of graphs to generate in each timeout loop | |
85 step = step || 1; | |
86 | |
87 nv.render.active = true; | |
88 nv.dispatch.render_start(); | |
89 | |
90 var renderLoop = function() { | |
91 var chart, graph; | |
92 | |
93 for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) { | |
94 chart = graph.generate(); | |
95 if (typeof graph.callback == typeof(Function)) graph.callback(chart); | |
96 } | |
97 | |
98 nv.render.queue.splice(0, i); | |
99 | |
100 if (nv.render.queue.length) { | |
101 setTimeout(renderLoop); | |
102 } | |
103 else { | |
104 nv.dispatch.render_end(); | |
105 nv.render.active = false; | |
106 } | |
107 }; | |
108 | |
109 setTimeout(renderLoop); | |
110 }; | |
111 | |
112 nv.render.active = false; | |
113 nv.render.queue = []; | |
114 | |
115 /* | |
116 Adds a chart to the async rendering queue. This method can take arguments in two forms: | |
117 nv.addGraph({ | |
118 generate: <Function> | |
119 callback: <Function> | |
120 }) | |
121 | |
122 or | |
123 | |
124 nv.addGraph(<generate Function>, <callback Function>) | |
125 | |
126 The generate function should contain code that creates the NVD3 model, sets options | |
127 on it, adds data to an SVG element, and invokes the chart model. The generate function | |
128 should return the chart model. See examples/lineChart.html for a usage example. | |
129 | |
130 The callback function is optional, and it is called when the generate function completes. | |
131 */ | |
132 nv.addGraph = function(obj) { | |
133 if (typeof arguments[0] === typeof(Function)) { | |
134 obj = {generate: arguments[0], callback: arguments[1]}; | |
135 } | |
136 | |
137 nv.render.queue.push(obj); | |
138 | |
139 if (!nv.render.active) { | |
140 nv.render(); | |
141 } | |
142 }; | |
143 | |
144 // Node/CommonJS exports | |
145 if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') { | |
146 module.exports = nv; | |
147 } | |
148 | |
149 if (typeof(window) !== 'undefined') { | |
150 window.nv = nv; | |
151 } | |
152 /* Facade for queueing DOM write operations | |
153 * with Fastdom (https://github.com/wilsonpage/fastdom) | |
154 * if available. | |
155 * This could easily be extended to support alternate | |
156 * implementations in the future. | |
157 */ | |
158 nv.dom.write = function(callback) { | |
159 if (window.fastdom !== undefined) { | |
160 return fastdom.write(callback); | |
161 } | |
162 return callback(); | |
163 }; | |
164 | |
165 /* Facade for queueing DOM read operations | |
166 * with Fastdom (https://github.com/wilsonpage/fastdom) | |
167 * if available. | |
168 * This could easily be extended to support alternate | |
169 * implementations in the future. | |
170 */ | |
171 nv.dom.read = function(callback) { | |
172 if (window.fastdom !== undefined) { | |
173 return fastdom.read(callback); | |
174 } | |
175 return callback(); | |
176 };/* Utility class to handle creation of an interactive layer. | |
177 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch | |
178 containing the X-coordinate. It can also render a vertical line where the mouse is located. | |
179 | |
180 dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over | |
181 the rectangle. The dispatch is given one object which contains the mouseX/Y location. | |
182 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale. | |
183 */ | |
184 nv.interactiveGuideline = function() { | |
185 "use strict"; | |
186 | |
187 var tooltip = nv.models.tooltip(); | |
188 tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false); | |
189 | |
190 //Public settings | |
191 var width = null; | |
192 var height = null; | |
193 | |
194 //Please pass in the bounding chart's top and left margins | |
195 //This is important for calculating the correct mouseX/Y positions. | |
196 var margin = {left: 0, top: 0} | |
197 , xScale = d3.scale.linear() | |
198 , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick') | |
199 , showGuideLine = true; | |
200 //Must pass in the bounding chart's <svg> container. | |
201 //The mousemove event is attached to this container. | |
202 var svgContainer = null; | |
203 | |
204 // check if IE by looking for activeX | |
205 var isMSIE = "ActiveXObject" in window; | |
206 | |
207 | |
208 function layer(selection) { | |
209 selection.each(function(data) { | |
210 var container = d3.select(this); | |
211 var availableWidth = (width || 960), availableHeight = (height || 400); | |
212 var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer") | |
213 .data([data]); | |
214 var wrapEnter = wrap.enter() | |
215 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer"); | |
216 wrapEnter.append("g").attr("class","nv-interactiveGuideLine"); | |
217 | |
218 if (!svgContainer) { | |
219 return; | |
220 } | |
221 | |
222 function mouseHandler() { | |
223 var d3mouse = d3.mouse(this); | |
224 var mouseX = d3mouse[0]; | |
225 var mouseY = d3mouse[1]; | |
226 var subtractMargin = true; | |
227 var mouseOutAnyReason = false; | |
228 if (isMSIE) { | |
229 /* | |
230 D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10. | |
231 d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving | |
232 over a rect in IE 10. | |
233 However, d3.event.offsetX/Y also returns the mouse coordinates | |
234 relative to the triggering <rect>. So we use offsetX/Y on IE. | |
235 */ | |
236 mouseX = d3.event.offsetX; | |
237 mouseY = d3.event.offsetY; | |
238 | |
239 /* | |
240 On IE, if you attach a mouse event listener to the <svg> container, | |
241 it will actually trigger it for all the child elements (like <path>, <circle>, etc). | |
242 When this happens on IE, the offsetX/Y is set to where ever the child element | |
243 is located. | |
244 As a result, we do NOT need to subtract margins to figure out the mouse X/Y | |
245 position under this scenario. Removing the line below *will* cause | |
246 the interactive layer to not work right on IE. | |
247 */ | |
248 if(d3.event.target.tagName !== "svg") { | |
249 subtractMargin = false; | |
250 } | |
251 | |
252 if (d3.event.target.className.baseVal.match("nv-legend")) { | |
253 mouseOutAnyReason = true; | |
254 } | |
255 | |
256 } | |
257 | |
258 if(subtractMargin) { | |
259 mouseX -= margin.left; | |
260 mouseY -= margin.top; | |
261 } | |
262 | |
263 /* If mouseX/Y is outside of the chart's bounds, | |
264 trigger a mouseOut event. | |
265 */ | |
266 if (mouseX < 0 || mouseY < 0 | |
267 || mouseX > availableWidth || mouseY > availableHeight | |
268 || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined) | |
269 || mouseOutAnyReason | |
270 ) { | |
271 | |
272 if (isMSIE) { | |
273 if (d3.event.relatedTarget | |
274 && d3.event.relatedTarget.ownerSVGElement === undefined | |
275 && (d3.event.relatedTarget.className === undefined | |
276 || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) { | |
277 | |
278 return; | |
279 } | |
280 } | |
281 dispatch.elementMouseout({ | |
282 mouseX: mouseX, | |
283 mouseY: mouseY | |
284 }); | |
285 layer.renderGuideLine(null); //hide the guideline | |
286 tooltip.hidden(true); | |
287 return; | |
288 } else { | |
289 tooltip.hidden(false); | |
290 } | |
291 | |
292 var pointXValue = xScale.invert(mouseX); | |
293 dispatch.elementMousemove({ | |
294 mouseX: mouseX, | |
295 mouseY: mouseY, | |
296 pointXValue: pointXValue | |
297 }); | |
298 | |
299 //If user double clicks the layer, fire a elementDblclick | |
300 if (d3.event.type === "dblclick") { | |
301 dispatch.elementDblclick({ | |
302 mouseX: mouseX, | |
303 mouseY: mouseY, | |
304 pointXValue: pointXValue | |
305 }); | |
306 } | |
307 | |
308 // if user single clicks the layer, fire elementClick | |
309 if (d3.event.type === 'click') { | |
310 dispatch.elementClick({ | |
311 mouseX: mouseX, | |
312 mouseY: mouseY, | |
313 pointXValue: pointXValue | |
314 }); | |
315 } | |
316 } | |
317 | |
318 svgContainer | |
319 .on("touchmove",mouseHandler) | |
320 .on("mousemove",mouseHandler, true) | |
321 .on("mouseout" ,mouseHandler,true) | |
322 .on("dblclick" ,mouseHandler) | |
323 .on("click", mouseHandler) | |
324 ; | |
325 | |
326 layer.guideLine = null; | |
327 //Draws a vertical guideline at the given X postion. | |
328 layer.renderGuideLine = function(x) { | |
329 if (!showGuideLine) return; | |
330 if (layer.guideLine && layer.guideLine.attr("x1") === x) return; | |
331 nv.dom.write(function() { | |
332 var line = wrap.select(".nv-interactiveGuideLine") | |
333 .selectAll("line") | |
334 .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String); | |
335 line.enter() | |
336 .append("line") | |
337 .attr("class", "nv-guideline") | |
338 .attr("x1", function(d) { return d;}) | |
339 .attr("x2", function(d) { return d;}) | |
340 .attr("y1", availableHeight) | |
341 .attr("y2",0); | |
342 line.exit().remove(); | |
343 }); | |
344 } | |
345 }); | |
346 } | |
347 | |
348 layer.dispatch = dispatch; | |
349 layer.tooltip = tooltip; | |
350 | |
351 layer.margin = function(_) { | |
352 if (!arguments.length) return margin; | |
353 margin.top = typeof _.top != 'undefined' ? _.top : margin.top; | |
354 margin.left = typeof _.left != 'undefined' ? _.left : margin.left; | |
355 return layer; | |
356 }; | |
357 | |
358 layer.width = function(_) { | |
359 if (!arguments.length) return width; | |
360 width = _; | |
361 return layer; | |
362 }; | |
363 | |
364 layer.height = function(_) { | |
365 if (!arguments.length) return height; | |
366 height = _; | |
367 return layer; | |
368 }; | |
369 | |
370 layer.xScale = function(_) { | |
371 if (!arguments.length) return xScale; | |
372 xScale = _; | |
373 return layer; | |
374 }; | |
375 | |
376 layer.showGuideLine = function(_) { | |
377 if (!arguments.length) return showGuideLine; | |
378 showGuideLine = _; | |
379 return layer; | |
380 }; | |
381 | |
382 layer.svgContainer = function(_) { | |
383 if (!arguments.length) return svgContainer; | |
384 svgContainer = _; | |
385 return layer; | |
386 }; | |
387 | |
388 return layer; | |
389 }; | |
390 | |
391 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted. | |
392 This is different from normal bisectLeft; this function finds the nearest index to insert the search value. | |
393 | |
394 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28. | |
395 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5 | |
396 because 28 is closer to 30 than 10. | |
397 | |
398 Unit tests can be found in: interactiveBisectTest.html | |
399 | |
400 Has the following known issues: | |
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. | |
402 * Won't work if there are duplicate x coordinate values. | |
403 */ | |
404 nv.interactiveBisect = function (values, searchVal, xAccessor) { | |
405 "use strict"; | |
406 if (! (values instanceof Array)) { | |
407 return null; | |
408 } | |
409 var _xAccessor; | |
410 if (typeof xAccessor !== 'function') { | |
411 _xAccessor = function(d) { | |
412 return d.x; | |
413 } | |
414 } else { | |
415 _xAccessor = xAccessor; | |
416 } | |
417 var _cmp = function(d, v) { | |
418 // Accessors are no longer passed the index of the element along with | |
419 // the element itself when invoked by d3.bisector. | |
420 // | |
421 // Starting at D3 v3.4.4, d3.bisector() started inspecting the | |
422 // function passed to determine if it should consider it an accessor | |
423 // or a comparator. This meant that accessors that take two arguments | |
424 // (expecting an index as the second parameter) are treated as | |
425 // comparators where the second argument is the search value against | |
426 // which the first argument is compared. | |
427 return _xAccessor(d) - v; | |
428 }; | |
429 | |
430 var bisect = d3.bisector(_cmp).left; | |
431 var index = d3.max([0, bisect(values,searchVal) - 1]); | |
432 var currentValue = _xAccessor(values[index]); | |
433 | |
434 if (typeof currentValue === 'undefined') { | |
435 currentValue = index; | |
436 } | |
437 | |
438 if (currentValue === searchVal) { | |
439 return index; //found exact match | |
440 } | |
441 | |
442 var nextIndex = d3.min([index+1, values.length - 1]); | |
443 var nextValue = _xAccessor(values[nextIndex]); | |
444 | |
445 if (typeof nextValue === 'undefined') { | |
446 nextValue = nextIndex; | |
447 } | |
448 | |
449 if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) { | |
450 return index; | |
451 } else { | |
452 return nextIndex | |
453 } | |
454 }; | |
455 | |
456 /* | |
457 Returns the index in the array "values" that is closest to searchVal. | |
458 Only returns an index if searchVal is within some "threshold". | |
459 Otherwise, returns null. | |
460 */ | |
461 nv.nearestValueIndex = function (values, searchVal, threshold) { | |
462 "use strict"; | |
463 var yDistMax = Infinity, indexToHighlight = null; | |
464 values.forEach(function(d,i) { | |
465 var delta = Math.abs(searchVal - d); | |
466 if ( d != null && delta <= yDistMax && delta < threshold) { | |
467 yDistMax = delta; | |
468 indexToHighlight = i; | |
469 } | |
470 }); | |
471 return indexToHighlight; | |
472 }; | |
473 /* Tooltip rendering model for nvd3 charts. | |
474 window.nv.models.tooltip is the updated,new way to render tooltips. | |
475 | |
476 window.nv.tooltip.show is the old tooltip code. | |
477 window.nv.tooltip.* also has various helper methods. | |
478 */ | |
479 (function() { | |
480 "use strict"; | |
481 | |
482 /* Model which can be instantiated to handle tooltip rendering. | |
483 Example usage: | |
484 var tip = nv.models.tooltip().gravity('w').distance(23) | |
485 .data(myDataObject); | |
486 | |
487 tip(); //just invoke the returned function to render tooltip. | |
488 */ | |
489 nv.models.tooltip = function() { | |
490 | |
491 /* | |
492 Tooltip data. If data is given in the proper format, a consistent tooltip is generated. | |
493 Example Format of data: | |
494 { | |
495 key: "Date", | |
496 value: "August 2009", | |
497 series: [ | |
498 {key: "Series 1", value: "Value 1", color: "#000"}, | |
499 {key: "Series 2", value: "Value 2", color: "#00f"} | |
500 ] | |
501 } | |
502 */ | |
503 var data = null; | |
504 var gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned. | |
505 , distance = 25 //Distance to offset tooltip from the mouse location. | |
506 , snapDistance = 0 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect) | |
507 , fixedTop = null //If not null, this fixes the top position of the tooltip. | |
508 , classes = null //Attaches additional CSS classes to the tooltip DIV that is created. | |
509 , chartContainer = null //Parent dom element of the SVG that holds the chart. | |
510 , hidden = true // start off hidden, toggle with hide/show functions below | |
511 , hideDelay = 400 // delay before the tooltip hides after calling hide() | |
512 , tooltip = null // d3 select of tooltipElem below | |
513 , tooltipElem = null //actual DOM element representing the tooltip. | |
514 , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer. | |
515 , offset = {left: 0, top: 0} //Offset of tooltip against the pointer | |
516 , enabled = true //True -> tooltips are rendered. False -> don't render tooltips. | |
517 , duration = 100 // duration for tooltip movement | |
518 , headerEnabled = true | |
519 ; | |
520 | |
521 // set to true by interactive layer to adjust tooltip positions | |
522 // eventually we should probably fix interactive layer to get the position better. | |
523 // for now this is needed if you want to set chartContainer for normal tooltips, else it "fixes" it to broken | |
524 var isInteractiveLayer = false; | |
525 | |
526 //Generates a unique id when you create a new tooltip() object | |
527 var id = "nvtooltip-" + Math.floor(Math.random() * 100000); | |
528 | |
529 //CSS class to specify whether element should not have mouse events. | |
530 var nvPointerEventsClass = "nv-pointer-events-none"; | |
531 | |
532 //Format function for the tooltip values column | |
533 var valueFormatter = function(d,i) { | |
534 return d; | |
535 }; | |
536 | |
537 //Format function for the tooltip header value. | |
538 var headerFormatter = function(d) { | |
539 return d; | |
540 }; | |
541 | |
542 var keyFormatter = function(d, i) { | |
543 return d; | |
544 }; | |
545 | |
546 //By default, the tooltip model renders a beautiful table inside a DIV. | |
547 //You can override this function if a custom tooltip is desired. | |
548 var contentGenerator = function(d) { | |
549 if (d === null) { | |
550 return ''; | |
551 } | |
552 | |
553 var table = d3.select(document.createElement("table")); | |
554 if (headerEnabled) { | |
555 var theadEnter = table.selectAll("thead") | |
556 .data([d]) | |
557 .enter().append("thead"); | |
558 | |
559 theadEnter.append("tr") | |
560 .append("td") | |
561 .attr("colspan", 3) | |
562 .append("strong") | |
563 .classed("x-value", true) | |
564 .html(headerFormatter(d.value)); | |
565 } | |
566 | |
567 var tbodyEnter = table.selectAll("tbody") | |
568 .data([d]) | |
569 .enter().append("tbody"); | |
570 | |
571 var trowEnter = tbodyEnter.selectAll("tr") | |
572 .data(function(p) { return p.series}) | |
573 .enter() | |
574 .append("tr") | |
575 .classed("highlight", function(p) { return p.highlight}); | |
576 | |
577 trowEnter.append("td") | |
578 .classed("legend-color-guide",true) | |
579 .append("div") | |
580 .style("background-color", function(p) { return p.color}); | |
581 | |
582 trowEnter.append("td") | |
583 .classed("key",true) | |
584 .html(function(p, i) {return keyFormatter(p.key, i)}); | |
585 | |
586 trowEnter.append("td") | |
587 .classed("value",true) | |
588 .html(function(p, i) { return valueFormatter(p.value, i) }); | |
589 | |
590 | |
591 trowEnter.selectAll("td").each(function(p) { | |
592 if (p.highlight) { | |
593 var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]); | |
594 var opacity = 0.6; | |
595 d3.select(this) | |
596 .style("border-bottom-color", opacityScale(opacity)) | |
597 .style("border-top-color", opacityScale(opacity)) | |
598 ; | |
599 } | |
600 }); | |
601 | |
602 var html = table.node().outerHTML; | |
603 if (d.footer !== undefined) | |
604 html += "<div class='footer'>" + d.footer + "</div>"; | |
605 return html; | |
606 | |
607 }; | |
608 | |
609 var dataSeriesExists = function(d) { | |
610 if (d && d.series) { | |
611 if (d.series instanceof Array) { | |
612 return !!d.series.length; | |
613 } | |
614 // if object, it's okay just convert to array of the object | |
615 if (d.series instanceof Object) { | |
616 d.series = [d.series]; | |
617 return true; | |
618 } | |
619 } | |
620 return false; | |
621 }; | |
622 | |
623 var calcTooltipPosition = function(pos) { | |
624 if (!tooltipElem) return; | |
625 | |
626 nv.dom.read(function() { | |
627 var height = parseInt(tooltipElem.offsetHeight, 10), | |
628 width = parseInt(tooltipElem.offsetWidth, 10), | |
629 windowWidth = nv.utils.windowSize().width, | |
630 windowHeight = nv.utils.windowSize().height, | |
631 scrollTop = window.pageYOffset, | |
632 scrollLeft = window.pageXOffset, | |
633 left, top; | |
634 | |
635 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16; | |
636 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16; | |
637 | |
638 | |
639 //Helper functions to find the total offsets of a given DOM element. | |
640 //Looks up the entire ancestry of an element, up to the first relatively positioned element. | |
641 var tooltipTop = function ( Elem ) { | |
642 var offsetTop = top; | |
643 do { | |
644 if( !isNaN( Elem.offsetTop ) ) { | |
645 offsetTop += (Elem.offsetTop); | |
646 } | |
647 Elem = Elem.offsetParent; | |
648 } while( Elem ); | |
649 return offsetTop; | |
650 }; | |
651 var tooltipLeft = function ( Elem ) { | |
652 var offsetLeft = left; | |
653 do { | |
654 if( !isNaN( Elem.offsetLeft ) ) { | |
655 offsetLeft += (Elem.offsetLeft); | |
656 } | |
657 Elem = Elem.offsetParent; | |
658 } while( Elem ); | |
659 return offsetLeft; | |
660 }; | |
661 | |
662 // calculate position based on gravity | |
663 var tLeft, tTop; | |
664 switch (gravity) { | |
665 case 'e': | |
666 left = pos[0] - width - distance; | |
667 top = pos[1] - (height / 2); | |
668 tLeft = tooltipLeft(tooltipElem); | |
669 tTop = tooltipTop(tooltipElem); | |
670 if (tLeft < scrollLeft) left = pos[0] + distance > scrollLeft ? pos[0] + distance : scrollLeft - tLeft + left; | |
671 if (tTop < scrollTop) top = scrollTop - tTop + top; | |
672 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; | |
673 break; | |
674 case 'w': | |
675 left = pos[0] + distance; | |
676 top = pos[1] - (height / 2); | |
677 tLeft = tooltipLeft(tooltipElem); | |
678 tTop = tooltipTop(tooltipElem); | |
679 if (tLeft + width > windowWidth) left = pos[0] - width - distance; | |
680 if (tTop < scrollTop) top = scrollTop + 5; | |
681 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; | |
682 break; | |
683 case 'n': | |
684 left = pos[0] - (width / 2) - 5; | |
685 top = pos[1] + distance; | |
686 tLeft = tooltipLeft(tooltipElem); | |
687 tTop = tooltipTop(tooltipElem); | |
688 if (tLeft < scrollLeft) left = scrollLeft + 5; | |
689 if (tLeft + width > windowWidth) left = left - width/2 + 5; | |
690 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height; | |
691 break; | |
692 case 's': | |
693 left = pos[0] - (width / 2); | |
694 top = pos[1] - height - distance; | |
695 tLeft = tooltipLeft(tooltipElem); | |
696 tTop = tooltipTop(tooltipElem); | |
697 if (tLeft < scrollLeft) left = scrollLeft + 5; | |
698 if (tLeft + width > windowWidth) left = left - width/2 + 5; | |
699 if (scrollTop > tTop) top = scrollTop; | |
700 break; | |
701 case 'none': | |
702 left = pos[0]; | |
703 top = pos[1] - distance; | |
704 tLeft = tooltipLeft(tooltipElem); | |
705 tTop = tooltipTop(tooltipElem); | |
706 break; | |
707 } | |
708 | |
709 // adjust tooltip offsets | |
710 left -= offset.left; | |
711 top -= offset.top; | |
712 | |
713 // using tooltip.style('transform') returns values un-usable for tween | |
714 var box = tooltipElem.getBoundingClientRect(); | |
715 var scrollTop = window.pageYOffset || document.documentElement.scrollTop; | |
716 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; | |
717 var old_translate = 'translate(' + (box.left + scrollLeft) + 'px, ' + (box.top + scrollTop) + 'px)'; | |
718 var new_translate = 'translate(' + left + 'px, ' + top + 'px)'; | |
719 var translateInterpolator = d3.interpolateString(old_translate, new_translate); | |
720 | |
721 var is_hidden = tooltip.style('opacity') < 0.1; | |
722 | |
723 // delay hiding a bit to avoid flickering | |
724 if (hidden) { | |
725 tooltip | |
726 .transition() | |
727 .delay(hideDelay) | |
728 .duration(0) | |
729 .style('opacity', 0); | |
730 } else { | |
731 tooltip | |
732 .interrupt() // cancel running transitions | |
733 .transition() | |
734 .duration(is_hidden ? 0 : duration) | |
735 // using tween since some versions of d3 can't auto-tween a translate on a div | |
736 .styleTween('transform', function (d) { | |
737 return translateInterpolator; | |
738 }, 'important') | |
739 // Safari has its own `-webkit-transform` and does not support `transform` | |
740 // transform tooltip without transition only in Safari | |
741 .style('-webkit-transform', new_translate) | |
742 .style('opacity', 1); | |
743 } | |
744 | |
745 | |
746 | |
747 }); | |
748 }; | |
749 | |
750 //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed. | |
751 function convertViewBoxRatio() { | |
752 if (chartContainer) { | |
753 var svg = d3.select(chartContainer); | |
754 if (svg.node().tagName !== "svg") { | |
755 svg = svg.select("svg"); | |
756 } | |
757 var viewBox = (svg.node()) ? svg.attr('viewBox') : null; | |
758 if (viewBox) { | |
759 viewBox = viewBox.split(' '); | |
760 var ratio = parseInt(svg.style('width'), 10) / viewBox[2]; | |
761 | |
762 position.left = position.left * ratio; | |
763 position.top = position.top * ratio; | |
764 } | |
765 } | |
766 } | |
767 | |
768 //Creates new tooltip container, or uses existing one on DOM. | |
769 function initTooltip() { | |
770 if (!tooltip) { | |
771 var body; | |
772 if (chartContainer) { | |
773 body = chartContainer; | |
774 } else { | |
775 body = document.body; | |
776 } | |
777 //Create new tooltip div if it doesn't exist on DOM. | |
778 tooltip = d3.select(body).append("div") | |
779 .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip")) | |
780 .attr("id", id); | |
781 tooltip.style("top", 0).style("left", 0); | |
782 tooltip.style('opacity', 0); | |
783 tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true); | |
784 tooltip.classed(nvPointerEventsClass, true); | |
785 tooltipElem = tooltip.node(); | |
786 } | |
787 } | |
788 | |
789 //Draw the tooltip onto the DOM. | |
790 function nvtooltip() { | |
791 if (!enabled) return; | |
792 if (!dataSeriesExists(data)) return; | |
793 | |
794 convertViewBoxRatio(); | |
795 | |
796 var left = position.left; | |
797 var top = (fixedTop !== null) ? fixedTop : position.top; | |
798 | |
799 nv.dom.write(function () { | |
800 initTooltip(); | |
801 // generate data and set it into tooltip | |
802 // Bonus - If you override contentGenerator and return falsey you can use something like | |
803 // React or Knockout to bind the data for your tooltip | |
804 var newContent = contentGenerator(data); | |
805 if (newContent) { | |
806 tooltipElem.innerHTML = newContent; | |
807 } | |
808 | |
809 if (chartContainer && isInteractiveLayer) { | |
810 nv.dom.read(function() { | |
811 var svgComp = chartContainer.getElementsByTagName("svg")[0]; | |
812 var svgOffset = {left:0,top:0}; | |
813 if (svgComp) { | |
814 var svgBound = svgComp.getBoundingClientRect(); | |
815 var chartBound = chartContainer.getBoundingClientRect(); | |
816 var svgBoundTop = svgBound.top; | |
817 | |
818 //Defensive code. Sometimes, svgBoundTop can be a really negative | |
819 // number, like -134254. That's a bug. | |
820 // If such a number is found, use zero instead. FireFox bug only | |
821 if (svgBoundTop < 0) { | |
822 var containerBound = chartContainer.getBoundingClientRect(); | |
823 svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop; | |
824 } | |
825 svgOffset.top = Math.abs(svgBoundTop - chartBound.top); | |
826 svgOffset.left = Math.abs(svgBound.left - chartBound.left); | |
827 } | |
828 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets. | |
829 //You need to also add any offset between the <svg> element and its containing <div> | |
830 //Finally, add any offset of the containing <div> on the whole page. | |
831 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft; | |
832 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop; | |
833 | |
834 if (snapDistance && snapDistance > 0) { | |
835 top = Math.floor(top/snapDistance) * snapDistance; | |
836 } | |
837 calcTooltipPosition([left,top]); | |
838 }); | |
839 } else { | |
840 calcTooltipPosition([left,top]); | |
841 } | |
842 }); | |
843 | |
844 return nvtooltip; | |
845 } | |
846 | |
847 nvtooltip.nvPointerEventsClass = nvPointerEventsClass; | |
848 nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip); | |
849 | |
850 nvtooltip._options = Object.create({}, { | |
851 // simple read/write options | |
852 duration: {get: function(){return duration;}, set: function(_){duration=_;}}, | |
853 gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}}, | |
854 distance: {get: function(){return distance;}, set: function(_){distance=_;}}, | |
855 snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}}, | |
856 classes: {get: function(){return classes;}, set: function(_){classes=_;}}, | |
857 chartContainer: {get: function(){return chartContainer;}, set: function(_){chartContainer=_;}}, | |
858 fixedTop: {get: function(){return fixedTop;}, set: function(_){fixedTop=_;}}, | |
859 enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}}, | |
860 hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}}, | |
861 contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}}, | |
862 valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}}, | |
863 headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}}, | |
864 keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}}, | |
865 headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}}, | |
866 | |
867 // internal use only, set by interactive layer to adjust position. | |
868 _isInteractiveLayer: {get: function(){return isInteractiveLayer;}, set: function(_){isInteractiveLayer=!!_;}}, | |
869 | |
870 // options with extra logic | |
871 position: {get: function(){return position;}, set: function(_){ | |
872 position.left = _.left !== undefined ? _.left : position.left; | |
873 position.top = _.top !== undefined ? _.top : position.top; | |
874 }}, | |
875 offset: {get: function(){return offset;}, set: function(_){ | |
876 offset.left = _.left !== undefined ? _.left : offset.left; | |
877 offset.top = _.top !== undefined ? _.top : offset.top; | |
878 }}, | |
879 hidden: {get: function(){return hidden;}, set: function(_){ | |
880 if (hidden != _) { | |
881 hidden = !!_; | |
882 nvtooltip(); | |
883 } | |
884 }}, | |
885 data: {get: function(){return data;}, set: function(_){ | |
886 // if showing a single data point, adjust data format with that | |
887 if (_.point) { | |
888 _.value = _.point.x; | |
889 _.series = _.series || {}; | |
890 _.series.value = _.point.y; | |
891 _.series.color = _.point.color || _.series.color; | |
892 } | |
893 data = _; | |
894 }}, | |
895 | |
896 // read only properties | |
897 tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}}, | |
898 id: {get: function(){return id;}, set: function(_){}} | |
899 }); | |
900 | |
901 nv.utils.initOptions(nvtooltip); | |
902 return nvtooltip; | |
903 }; | |
904 | |
905 })(); | |
906 | |
907 | |
908 /* | |
909 Gets the browser window size | |
910 | |
911 Returns object with height and width properties | |
912 */ | |
913 nv.utils.windowSize = function() { | |
914 // Sane defaults | |
915 var size = {width: 640, height: 480}; | |
916 | |
917 // Most recent browsers use | |
918 if (window.innerWidth && window.innerHeight) { | |
919 size.width = window.innerWidth; | |
920 size.height = window.innerHeight; | |
921 return (size); | |
922 } | |
923 | |
924 // IE can use depending on mode it is in | |
925 if (document.compatMode=='CSS1Compat' && | |
926 document.documentElement && | |
927 document.documentElement.offsetWidth ) { | |
928 | |
929 size.width = document.documentElement.offsetWidth; | |
930 size.height = document.documentElement.offsetHeight; | |
931 return (size); | |
932 } | |
933 | |
934 // Earlier IE uses Doc.body | |
935 if (document.body && document.body.offsetWidth) { | |
936 size.width = document.body.offsetWidth; | |
937 size.height = document.body.offsetHeight; | |
938 return (size); | |
939 } | |
940 | |
941 return (size); | |
942 }; | |
943 | |
944 /* | |
945 Binds callback function to run when window is resized | |
946 */ | |
947 nv.utils.windowResize = function(handler) { | |
948 if (window.addEventListener) { | |
949 window.addEventListener('resize', handler); | |
950 } else { | |
951 nv.log("ERROR: Failed to bind to window.resize with: ", handler); | |
952 } | |
953 // return object with clear function to remove the single added callback. | |
954 return { | |
955 callback: handler, | |
956 clear: function() { | |
957 window.removeEventListener('resize', handler); | |
958 } | |
959 } | |
960 }; | |
961 | |
962 | |
963 /* | |
964 Backwards compatible way to implement more d3-like coloring of graphs. | |
965 Can take in nothing, an array, or a function/scale | |
966 To use a normal scale, get the range and pass that because we must be able | |
967 to take two arguments and use the index to keep backward compatibility | |
968 */ | |
969 nv.utils.getColor = function(color) { | |
970 //if you pass in nothing, get default colors back | |
971 if (color === undefined) { | |
972 return nv.utils.defaultColor(); | |
973 | |
974 //if passed an array, turn it into a color scale | |
975 // use isArray, instanceof fails if d3 range is created in an iframe | |
976 } else if(Array.isArray(color)) { | |
977 var color_scale = d3.scale.ordinal().range(color); | |
978 return function(d, i) { | |
979 var key = i === undefined ? d : i; | |
980 return d.color || color_scale(key); | |
981 }; | |
982 | |
983 //if passed a function or scale, return it, or whatever it may be | |
984 //external libs, such as angularjs-nvd3-directives use this | |
985 } else { | |
986 //can't really help it if someone passes rubbish as color | |
987 return color; | |
988 } | |
989 }; | |
990 | |
991 | |
992 /* | |
993 Default color chooser uses a color scale of 20 colors from D3 | |
994 https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors | |
995 */ | |
996 nv.utils.defaultColor = function() { | |
997 // get range of the scale so we'll turn it into our own function. | |
998 return nv.utils.getColor(d3.scale.category20().range()); | |
999 }; | |
1000 | |
1001 | |
1002 /* | |
1003 Returns a color function that takes the result of 'getKey' for each series and | |
1004 looks for a corresponding color from the dictionary | |
1005 */ | |
1006 nv.utils.customTheme = function(dictionary, getKey, defaultColors) { | |
1007 // use default series.key if getKey is undefined | |
1008 getKey = getKey || function(series) { return series.key }; | |
1009 defaultColors = defaultColors || d3.scale.category20().range(); | |
1010 | |
1011 // start at end of default color list and walk back to index 0 | |
1012 var defIndex = defaultColors.length; | |
1013 | |
1014 return function(series, index) { | |
1015 var key = getKey(series); | |
1016 if (typeof dictionary[key] === 'function') { | |
1017 return dictionary[key](); | |
1018 } else if (dictionary[key] !== undefined) { | |
1019 return dictionary[key]; | |
1020 } else { | |
1021 // no match in dictionary, use a default color | |
1022 if (!defIndex) { | |
1023 // used all the default colors, start over | |
1024 defIndex = defaultColors.length; | |
1025 } | |
1026 defIndex = defIndex - 1; | |
1027 return defaultColors[defIndex]; | |
1028 } | |
1029 }; | |
1030 }; | |
1031 | |
1032 | |
1033 /* | |
1034 From the PJAX example on d3js.org, while this is not really directly needed | |
1035 it's a very cool method for doing pjax, I may expand upon it a little bit, | |
1036 open to suggestions on anything that may be useful | |
1037 */ | |
1038 nv.utils.pjax = function(links, content) { | |
1039 | |
1040 var load = function(href) { | |
1041 d3.html(href, function(fragment) { | |
1042 var target = d3.select(content).node(); | |
1043 target.parentNode.replaceChild( | |
1044 d3.select(fragment).select(content).node(), | |
1045 target); | |
1046 nv.utils.pjax(links, content); | |
1047 }); | |
1048 }; | |
1049 | |
1050 d3.selectAll(links).on("click", function() { | |
1051 history.pushState(this.href, this.textContent, this.href); | |
1052 load(this.href); | |
1053 d3.event.preventDefault(); | |
1054 }); | |
1055 | |
1056 d3.select(window).on("popstate", function() { | |
1057 if (d3.event.state) { | |
1058 load(d3.event.state); | |
1059 } | |
1060 }); | |
1061 }; | |
1062 | |
1063 | |
1064 /* | |
1065 For when we want to approximate the width in pixels for an SVG:text element. | |
1066 Most common instance is when the element is in a display:none; container. | |
1067 Forumla is : text.length * font-size * constant_factor | |
1068 */ | |
1069 nv.utils.calcApproxTextWidth = function (svgTextElem) { | |
1070 if (typeof svgTextElem.style === 'function' | |
1071 && typeof svgTextElem.text === 'function') { | |
1072 | |
1073 var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10); | |
1074 var textLength = svgTextElem.text().length; | |
1075 return textLength * fontSize * 0.5; | |
1076 } | |
1077 return 0; | |
1078 }; | |
1079 | |
1080 | |
1081 /* | |
1082 Numbers that are undefined, null or NaN, convert them to zeros. | |
1083 */ | |
1084 nv.utils.NaNtoZero = function(n) { | |
1085 if (typeof n !== 'number' | |
1086 || isNaN(n) | |
1087 || n === null | |
1088 || n === Infinity | |
1089 || n === -Infinity) { | |
1090 | |
1091 return 0; | |
1092 } | |
1093 return n; | |
1094 }; | |
1095 | |
1096 /* | |
1097 Add a way to watch for d3 transition ends to d3 | |
1098 */ | |
1099 d3.selection.prototype.watchTransition = function(renderWatch){ | |
1100 var args = [this].concat([].slice.call(arguments, 1)); | |
1101 return renderWatch.transition.apply(renderWatch, args); | |
1102 }; | |
1103 | |
1104 | |
1105 /* | |
1106 Helper object to watch when d3 has rendered something | |
1107 */ | |
1108 nv.utils.renderWatch = function(dispatch, duration) { | |
1109 if (!(this instanceof nv.utils.renderWatch)) { | |
1110 return new nv.utils.renderWatch(dispatch, duration); | |
1111 } | |
1112 | |
1113 var _duration = duration !== undefined ? duration : 250; | |
1114 var renderStack = []; | |
1115 var self = this; | |
1116 | |
1117 this.models = function(models) { | |
1118 models = [].slice.call(arguments, 0); | |
1119 models.forEach(function(model){ | |
1120 model.__rendered = false; | |
1121 (function(m){ | |
1122 m.dispatch.on('renderEnd', function(arg){ | |
1123 m.__rendered = true; | |
1124 self.renderEnd('model'); | |
1125 }); | |
1126 })(model); | |
1127 | |
1128 if (renderStack.indexOf(model) < 0) { | |
1129 renderStack.push(model); | |
1130 } | |
1131 }); | |
1132 return this; | |
1133 }; | |
1134 | |
1135 this.reset = function(duration) { | |
1136 if (duration !== undefined) { | |
1137 _duration = duration; | |
1138 } | |
1139 renderStack = []; | |
1140 }; | |
1141 | |
1142 this.transition = function(selection, args, duration) { | |
1143 args = arguments.length > 1 ? [].slice.call(arguments, 1) : []; | |
1144 | |
1145 if (args.length > 1) { | |
1146 duration = args.pop(); | |
1147 } else { | |
1148 duration = _duration !== undefined ? _duration : 250; | |
1149 } | |
1150 selection.__rendered = false; | |
1151 | |
1152 if (renderStack.indexOf(selection) < 0) { | |
1153 renderStack.push(selection); | |
1154 } | |
1155 | |
1156 if (duration === 0) { | |
1157 selection.__rendered = true; | |
1158 selection.delay = function() { return this; }; | |
1159 selection.duration = function() { return this; }; | |
1160 return selection; | |
1161 } else { | |
1162 if (selection.length === 0) { | |
1163 selection.__rendered = true; | |
1164 } else if (selection.every( function(d){ return !d.length; } )) { | |
1165 selection.__rendered = true; | |
1166 } else { | |
1167 selection.__rendered = false; | |
1168 } | |
1169 | |
1170 var n = 0; | |
1171 return selection | |
1172 .transition() | |
1173 .duration(duration) | |
1174 .each(function(){ ++n; }) | |
1175 .each('end', function(d, i) { | |
1176 if (--n === 0) { | |
1177 selection.__rendered = true; | |
1178 self.renderEnd.apply(this, args); | |
1179 } | |
1180 }); | |
1181 } | |
1182 }; | |
1183 | |
1184 this.renderEnd = function() { | |
1185 if (renderStack.every( function(d){ return d.__rendered; } )) { | |
1186 renderStack.forEach( function(d){ d.__rendered = false; }); | |
1187 dispatch.renderEnd.apply(this, arguments); | |
1188 } | |
1189 } | |
1190 | |
1191 }; | |
1192 | |
1193 | |
1194 /* | |
1195 Takes multiple objects and combines them into the first one (dst) | |
1196 example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4}); | |
1197 gives: {a: 2, b: 3, c: 4} | |
1198 */ | |
1199 nv.utils.deepExtend = function(dst){ | |
1200 var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : []; | |
1201 sources.forEach(function(source) { | |
1202 for (var key in source) { | |
1203 var isArray = dst[key] instanceof Array; | |
1204 var isObject = typeof dst[key] === 'object'; | |
1205 var srcObj = typeof source[key] === 'object'; | |
1206 | |
1207 if (isObject && !isArray && srcObj) { | |
1208 nv.utils.deepExtend(dst[key], source[key]); | |
1209 } else { | |
1210 dst[key] = source[key]; | |
1211 } | |
1212 } | |
1213 }); | |
1214 }; | |
1215 | |
1216 | |
1217 /* | |
1218 state utility object, used to track d3 states in the models | |
1219 */ | |
1220 nv.utils.state = function(){ | |
1221 if (!(this instanceof nv.utils.state)) { | |
1222 return new nv.utils.state(); | |
1223 } | |
1224 var state = {}; | |
1225 var _self = this; | |
1226 var _setState = function(){}; | |
1227 var _getState = function(){ return {}; }; | |
1228 var init = null; | |
1229 var changed = null; | |
1230 | |
1231 this.dispatch = d3.dispatch('change', 'set'); | |
1232 | |
1233 this.dispatch.on('set', function(state){ | |
1234 _setState(state, true); | |
1235 }); | |
1236 | |
1237 this.getter = function(fn){ | |
1238 _getState = fn; | |
1239 return this; | |
1240 }; | |
1241 | |
1242 this.setter = function(fn, callback) { | |
1243 if (!callback) { | |
1244 callback = function(){}; | |
1245 } | |
1246 _setState = function(state, update){ | |
1247 fn(state); | |
1248 if (update) { | |
1249 callback(); | |
1250 } | |
1251 }; | |
1252 return this; | |
1253 }; | |
1254 | |
1255 this.init = function(state){ | |
1256 init = init || {}; | |
1257 nv.utils.deepExtend(init, state); | |
1258 }; | |
1259 | |
1260 var _set = function(){ | |
1261 var settings = _getState(); | |
1262 | |
1263 if (JSON.stringify(settings) === JSON.stringify(state)) { | |
1264 return false; | |
1265 } | |
1266 | |
1267 for (var key in settings) { | |
1268 if (state[key] === undefined) { | |
1269 state[key] = {}; | |
1270 } | |
1271 state[key] = settings[key]; | |
1272 changed = true; | |
1273 } | |
1274 return true; | |
1275 }; | |
1276 | |
1277 this.update = function(){ | |
1278 if (init) { | |
1279 _setState(init, false); | |
1280 init = null; | |
1281 } | |
1282 if (_set.call(this)) { | |
1283 this.dispatch.change(state); | |
1284 } | |
1285 }; | |
1286 | |
1287 }; | |
1288 | |
1289 | |
1290 /* | |
1291 Snippet of code you can insert into each nv.models.* to give you the ability to | |
1292 do things like: | |
1293 chart.options({ | |
1294 showXAxis: true, | |
1295 tooltips: true | |
1296 }); | |
1297 | |
1298 To enable in the chart: | |
1299 chart.options = nv.utils.optionsFunc.bind(chart); | |
1300 */ | |
1301 nv.utils.optionsFunc = function(args) { | |
1302 if (args) { | |
1303 d3.map(args).forEach((function(key,value) { | |
1304 if (typeof this[key] === "function") { | |
1305 this[key](value); | |
1306 } | |
1307 }).bind(this)); | |
1308 } | |
1309 return this; | |
1310 }; | |
1311 | |
1312 | |
1313 /* | |
1314 numTicks: requested number of ticks | |
1315 data: the chart data | |
1316 | |
1317 returns the number of ticks to actually use on X axis, based on chart data | |
1318 to avoid duplicate ticks with the same value | |
1319 */ | |
1320 nv.utils.calcTicksX = function(numTicks, data) { | |
1321 // find max number of values from all data streams | |
1322 var numValues = 1; | |
1323 var i = 0; | |
1324 for (i; i < data.length; i += 1) { | |
1325 var stream_len = data[i] && data[i].values ? data[i].values.length : 0; | |
1326 numValues = stream_len > numValues ? stream_len : numValues; | |
1327 } | |
1328 nv.log("Requested number of ticks: ", numTicks); | |
1329 nv.log("Calculated max values to be: ", numValues); | |
1330 // make sure we don't have more ticks than values to avoid duplicates | |
1331 numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks; | |
1332 // make sure we have at least one tick | |
1333 numTicks = numTicks < 1 ? 1 : numTicks; | |
1334 // make sure it's an integer | |
1335 numTicks = Math.floor(numTicks); | |
1336 nv.log("Calculating tick count as: ", numTicks); | |
1337 return numTicks; | |
1338 }; | |
1339 | |
1340 | |
1341 /* | |
1342 returns number of ticks to actually use on Y axis, based on chart data | |
1343 */ | |
1344 nv.utils.calcTicksY = function(numTicks, data) { | |
1345 // currently uses the same logic but we can adjust here if needed later | |
1346 return nv.utils.calcTicksX(numTicks, data); | |
1347 }; | |
1348 | |
1349 | |
1350 /* | |
1351 Add a particular option from an options object onto chart | |
1352 Options exposed on a chart are a getter/setter function that returns chart | |
1353 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b'); | |
1354 | |
1355 option objects should be generated via Object.create() to provide | |
1356 the option of manipulating data via get/set functions. | |
1357 */ | |
1358 nv.utils.initOption = function(chart, name) { | |
1359 // if it's a call option, just call it directly, otherwise do get/set | |
1360 if (chart._calls && chart._calls[name]) { | |
1361 chart[name] = chart._calls[name]; | |
1362 } else { | |
1363 chart[name] = function (_) { | |
1364 if (!arguments.length) return chart._options[name]; | |
1365 chart._overrides[name] = true; | |
1366 chart._options[name] = _; | |
1367 return chart; | |
1368 }; | |
1369 // calling the option as _option will ignore if set by option already | |
1370 // so nvd3 can set options internally but the stop if set manually | |
1371 chart['_' + name] = function(_) { | |
1372 if (!arguments.length) return chart._options[name]; | |
1373 if (!chart._overrides[name]) { | |
1374 chart._options[name] = _; | |
1375 } | |
1376 return chart; | |
1377 } | |
1378 } | |
1379 }; | |
1380 | |
1381 | |
1382 /* | |
1383 Add all options in an options object to the chart | |
1384 */ | |
1385 nv.utils.initOptions = function(chart) { | |
1386 chart._overrides = chart._overrides || {}; | |
1387 var ops = Object.getOwnPropertyNames(chart._options || {}); | |
1388 var calls = Object.getOwnPropertyNames(chart._calls || {}); | |
1389 ops = ops.concat(calls); | |
1390 for (var i in ops) { | |
1391 nv.utils.initOption(chart, ops[i]); | |
1392 } | |
1393 }; | |
1394 | |
1395 | |
1396 /* | |
1397 Inherit options from a D3 object | |
1398 d3.rebind makes calling the function on target actually call it on source | |
1399 Also use _d3options so we can track what we inherit for documentation and chained inheritance | |
1400 */ | |
1401 nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) { | |
1402 target._d3options = oplist.concat(target._d3options || []); | |
1403 oplist.unshift(d3_source); | |
1404 oplist.unshift(target); | |
1405 d3.rebind.apply(this, oplist); | |
1406 }; | |
1407 | |
1408 | |
1409 /* | |
1410 Remove duplicates from an array | |
1411 */ | |
1412 nv.utils.arrayUnique = function(a) { | |
1413 return a.sort().filter(function(item, pos) { | |
1414 return !pos || item != a[pos - 1]; | |
1415 }); | |
1416 }; | |
1417 | |
1418 | |
1419 /* | |
1420 Keeps a list of custom symbols to draw from in addition to d3.svg.symbol | |
1421 Necessary since d3 doesn't let you extend its list -_- | |
1422 Add new symbols by doing nv.utils.symbols.set('name', function(size){...}); | |
1423 */ | |
1424 nv.utils.symbolMap = d3.map(); | |
1425 | |
1426 | |
1427 /* | |
1428 Replaces d3.svg.symbol so that we can look both there and our own map | |
1429 */ | |
1430 nv.utils.symbol = function() { | |
1431 var type, | |
1432 size = 64; | |
1433 function symbol(d,i) { | |
1434 var t = type.call(this,d,i); | |
1435 var s = size.call(this,d,i); | |
1436 if (d3.svg.symbolTypes.indexOf(t) !== -1) { | |
1437 return d3.svg.symbol().type(t).size(s)(); | |
1438 } else { | |
1439 return nv.utils.symbolMap.get(t)(s); | |
1440 } | |
1441 } | |
1442 symbol.type = function(_) { | |
1443 if (!arguments.length) return type; | |
1444 type = d3.functor(_); | |
1445 return symbol; | |
1446 }; | |
1447 symbol.size = function(_) { | |
1448 if (!arguments.length) return size; | |
1449 size = d3.functor(_); | |
1450 return symbol; | |
1451 }; | |
1452 return symbol; | |
1453 }; | |
1454 | |
1455 | |
1456 /* | |
1457 Inherit option getter/setter functions from source to target | |
1458 d3.rebind makes calling the function on target actually call it on source | |
1459 Also track via _inherited and _d3options so we can track what we inherit | |
1460 for documentation generation purposes and chained inheritance | |
1461 */ | |
1462 nv.utils.inheritOptions = function(target, source) { | |
1463 // inherit all the things | |
1464 var ops = Object.getOwnPropertyNames(source._options || {}); | |
1465 var calls = Object.getOwnPropertyNames(source._calls || {}); | |
1466 var inherited = source._inherited || []; | |
1467 var d3ops = source._d3options || []; | |
1468 var args = ops.concat(calls).concat(inherited).concat(d3ops); | |
1469 args.unshift(source); | |
1470 args.unshift(target); | |
1471 d3.rebind.apply(this, args); | |
1472 // pass along the lists to keep track of them, don't allow duplicates | |
1473 target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || [])); | |
1474 target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || [])); | |
1475 }; | |
1476 | |
1477 | |
1478 /* | |
1479 Runs common initialize code on the svg before the chart builds | |
1480 */ | |
1481 nv.utils.initSVG = function(svg) { | |
1482 svg.classed({'nvd3-svg':true}); | |
1483 }; | |
1484 | |
1485 | |
1486 /* | |
1487 Sanitize and provide default for the container height. | |
1488 */ | |
1489 nv.utils.sanitizeHeight = function(height, container) { | |
1490 return (height || parseInt(container.style('height'), 10) || 400); | |
1491 }; | |
1492 | |
1493 | |
1494 /* | |
1495 Sanitize and provide default for the container width. | |
1496 */ | |
1497 nv.utils.sanitizeWidth = function(width, container) { | |
1498 return (width || parseInt(container.style('width'), 10) || 960); | |
1499 }; | |
1500 | |
1501 | |
1502 /* | |
1503 Calculate the available height for a chart. | |
1504 */ | |
1505 nv.utils.availableHeight = function(height, container, margin) { | |
1506 return nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom; | |
1507 }; | |
1508 | |
1509 /* | |
1510 Calculate the available width for a chart. | |
1511 */ | |
1512 nv.utils.availableWidth = function(width, container, margin) { | |
1513 return nv.utils.sanitizeWidth(width, container) - margin.left - margin.right; | |
1514 }; | |
1515 | |
1516 /* | |
1517 Clear any rendered chart components and display a chart's 'noData' message | |
1518 */ | |
1519 nv.utils.noData = function(chart, container) { | |
1520 var opt = chart.options(), | |
1521 margin = opt.margin(), | |
1522 noData = opt.noData(), | |
1523 data = (noData == null) ? ["No Data Available."] : [noData], | |
1524 height = nv.utils.availableHeight(opt.height(), container, margin), | |
1525 width = nv.utils.availableWidth(opt.width(), container, margin), | |
1526 x = margin.left + width/2, | |
1527 y = margin.top + height/2; | |
1528 | |
1529 //Remove any previously created chart components | |
1530 container.selectAll('g').remove(); | |
1531 | |
1532 var noDataText = container.selectAll('.nv-noData').data(data); | |
1533 | |
1534 noDataText.enter().append('text') | |
1535 .attr('class', 'nvd3 nv-noData') | |
1536 .attr('dy', '-.7em') | |
1537 .style('text-anchor', 'middle'); | |
1538 | |
1539 noDataText | |
1540 .attr('x', x) | |
1541 .attr('y', y) | |
1542 .text(function(t){ return t; }); | |
1543 }; | |
1544 | |
1545 nv.models.axis = function() { | |
1546 "use strict"; | |
1547 | |
1548 //============================================================ | |
1549 // Public Variables with Default Settings | |
1550 //------------------------------------------------------------ | |
1551 | |
1552 var axis = d3.svg.axis(); | |
1553 var scale = d3.scale.linear(); | |
1554 | |
1555 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
1556 , width = 75 //only used for tickLabel currently | |
1557 , height = 60 //only used for tickLabel currently | |
1558 , axisLabelText = null | |
1559 , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes | |
1560 , rotateLabels = 0 | |
1561 , rotateYLabel = true | |
1562 , staggerLabels = false | |
1563 , isOrdinal = false | |
1564 , ticks = null | |
1565 , axisLabelDistance = 0 | |
1566 , duration = 250 | |
1567 , dispatch = d3.dispatch('renderEnd') | |
1568 ; | |
1569 axis | |
1570 .scale(scale) | |
1571 .orient('bottom') | |
1572 .tickFormat(function(d) { return d }) | |
1573 ; | |
1574 | |
1575 //============================================================ | |
1576 // Private Variables | |
1577 //------------------------------------------------------------ | |
1578 | |
1579 var scale0; | |
1580 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
1581 | |
1582 function chart(selection) { | |
1583 renderWatch.reset(); | |
1584 selection.each(function(data) { | |
1585 var container = d3.select(this); | |
1586 nv.utils.initSVG(container); | |
1587 | |
1588 // Setup containers and skeleton of chart | |
1589 var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]); | |
1590 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis'); | |
1591 var gEnter = wrapEnter.append('g'); | |
1592 var g = wrap.select('g'); | |
1593 | |
1594 if (ticks !== null) | |
1595 axis.ticks(ticks); | |
1596 else if (axis.orient() == 'top' || axis.orient() == 'bottom') | |
1597 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100); | |
1598 | |
1599 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component | |
1600 g.watchTransition(renderWatch, 'axis').call(axis); | |
1601 | |
1602 scale0 = scale0 || axis.scale(); | |
1603 | |
1604 var fmt = axis.tickFormat(); | |
1605 if (fmt == null) { | |
1606 fmt = scale0.tickFormat(); | |
1607 } | |
1608 | |
1609 var axisLabel = g.selectAll('text.nv-axislabel') | |
1610 .data([axisLabelText || null]); | |
1611 axisLabel.exit().remove(); | |
1612 | |
1613 var xLabelMargin; | |
1614 var axisMaxMin; | |
1615 var w; | |
1616 switch (axis.orient()) { | |
1617 case 'top': | |
1618 axisLabel.enter().append('text').attr('class', 'nv-axislabel'); | |
1619 if (scale.range().length < 2) { | |
1620 w = 0; | |
1621 } else if (scale.range().length === 2) { | |
1622 w = scale.range()[1]; | |
1623 } else { | |
1624 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]); | |
1625 } | |
1626 axisLabel | |
1627 .attr('text-anchor', 'middle') | |
1628 .attr('y', 0) | |
1629 .attr('x', w/2); | |
1630 if (showMaxMin) { | |
1631 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') | |
1632 .data(scale.domain()); | |
1633 axisMaxMin.enter().append('g').attr('class',function(d,i){ | |
1634 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ') | |
1635 }).append('text'); | |
1636 axisMaxMin.exit().remove(); | |
1637 axisMaxMin | |
1638 .attr('transform', function(d,i) { | |
1639 return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)' | |
1640 }) | |
1641 .select('text') | |
1642 .attr('dy', '-0.5em') | |
1643 .attr('y', -axis.tickPadding()) | |
1644 .attr('text-anchor', 'middle') | |
1645 .text(function(d,i) { | |
1646 var v = fmt(d); | |
1647 return ('' + v).match('NaN') ? '' : v; | |
1648 }); | |
1649 axisMaxMin.watchTransition(renderWatch, 'min-max top') | |
1650 .attr('transform', function(d,i) { | |
1651 return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)' | |
1652 }); | |
1653 } | |
1654 break; | |
1655 case 'bottom': | |
1656 xLabelMargin = axisLabelDistance + 36; | |
1657 var maxTextWidth = 30; | |
1658 var textHeight = 0; | |
1659 var xTicks = g.selectAll('g').select("text"); | |
1660 var rotateLabelsRule = ''; | |
1661 if (rotateLabels%360) { | |
1662 //Calculate the longest xTick width | |
1663 xTicks.each(function(d,i){ | |
1664 var box = this.getBoundingClientRect(); | |
1665 var width = box.width; | |
1666 textHeight = box.height; | |
1667 if(width > maxTextWidth) maxTextWidth = width; | |
1668 }); | |
1669 rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')'; | |
1670 //Convert to radians before calculating sin. Add 30 to margin for healthy padding. | |
1671 var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180)); | |
1672 xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30; | |
1673 //Rotate all xTicks | |
1674 xTicks | |
1675 .attr('transform', rotateLabelsRule) | |
1676 .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end'); | |
1677 } | |
1678 axisLabel.enter().append('text').attr('class', 'nv-axislabel'); | |
1679 if (scale.range().length < 2) { | |
1680 w = 0; | |
1681 } else if (scale.range().length === 2) { | |
1682 w = scale.range()[1]; | |
1683 } else { | |
1684 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]); | |
1685 } | |
1686 axisLabel | |
1687 .attr('text-anchor', 'middle') | |
1688 .attr('y', xLabelMargin) | |
1689 .attr('x', w/2); | |
1690 if (showMaxMin) { | |
1691 //if (showMaxMin && !isOrdinal) { | |
1692 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') | |
1693 //.data(scale.domain()) | |
1694 .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]); | |
1695 axisMaxMin.enter().append('g').attr('class',function(d,i){ | |
1696 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ') | |
1697 }).append('text'); | |
1698 axisMaxMin.exit().remove(); | |
1699 axisMaxMin | |
1700 .attr('transform', function(d,i) { | |
1701 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)' | |
1702 }) | |
1703 .select('text') | |
1704 .attr('dy', '.71em') | |
1705 .attr('y', axis.tickPadding()) | |
1706 .attr('transform', rotateLabelsRule) | |
1707 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle') | |
1708 .text(function(d,i) { | |
1709 var v = fmt(d); | |
1710 return ('' + v).match('NaN') ? '' : v; | |
1711 }); | |
1712 axisMaxMin.watchTransition(renderWatch, 'min-max bottom') | |
1713 .attr('transform', function(d,i) { | |
1714 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)' | |
1715 }); | |
1716 } | |
1717 if (staggerLabels) | |
1718 xTicks | |
1719 .attr('transform', function(d,i) { | |
1720 return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' | |
1721 }); | |
1722 | |
1723 break; | |
1724 case 'right': | |
1725 axisLabel.enter().append('text').attr('class', 'nv-axislabel'); | |
1726 axisLabel | |
1727 .style('text-anchor', rotateYLabel ? 'middle' : 'begin') | |
1728 .attr('transform', rotateYLabel ? 'rotate(90)' : '') | |
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 | |
1730 .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding()); | |
1731 if (showMaxMin) { | |
1732 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') | |
1733 .data(scale.domain()); | |
1734 axisMaxMin.enter().append('g').attr('class',function(d,i){ | |
1735 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ') | |
1736 }).append('text') | |
1737 .style('opacity', 0); | |
1738 axisMaxMin.exit().remove(); | |
1739 axisMaxMin | |
1740 .attr('transform', function(d,i) { | |
1741 return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')' | |
1742 }) | |
1743 .select('text') | |
1744 .attr('dy', '.32em') | |
1745 .attr('y', 0) | |
1746 .attr('x', axis.tickPadding()) | |
1747 .style('text-anchor', 'start') | |
1748 .text(function(d, i) { | |
1749 var v = fmt(d); | |
1750 return ('' + v).match('NaN') ? '' : v; | |
1751 }); | |
1752 axisMaxMin.watchTransition(renderWatch, 'min-max right') | |
1753 .attr('transform', function(d,i) { | |
1754 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')' | |
1755 }) | |
1756 .select('text') | |
1757 .style('opacity', 1); | |
1758 } | |
1759 break; | |
1760 case 'left': | |
1761 /* | |
1762 //For dynamically placing the label. Can be used with dynamically-sized chart axis margins | |
1763 var yTicks = g.selectAll('g').select("text"); | |
1764 yTicks.each(function(d,i){ | |
1765 var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16; | |
1766 if(labelPadding > width) width = labelPadding; | |
1767 }); | |
1768 */ | |
1769 axisLabel.enter().append('text').attr('class', 'nv-axislabel'); | |
1770 axisLabel | |
1771 .style('text-anchor', rotateYLabel ? 'middle' : 'end') | |
1772 .attr('transform', rotateYLabel ? 'rotate(-90)' : '') | |
1773 .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10) | |
1774 .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding()); | |
1775 if (showMaxMin) { | |
1776 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin') | |
1777 .data(scale.domain()); | |
1778 axisMaxMin.enter().append('g').attr('class',function(d,i){ | |
1779 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ') | |
1780 }).append('text') | |
1781 .style('opacity', 0); | |
1782 axisMaxMin.exit().remove(); | |
1783 axisMaxMin | |
1784 .attr('transform', function(d,i) { | |
1785 return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')' | |
1786 }) | |
1787 .select('text') | |
1788 .attr('dy', '.32em') | |
1789 .attr('y', 0) | |
1790 .attr('x', -axis.tickPadding()) | |
1791 .attr('text-anchor', 'end') | |
1792 .text(function(d,i) { | |
1793 var v = fmt(d); | |
1794 return ('' + v).match('NaN') ? '' : v; | |
1795 }); | |
1796 axisMaxMin.watchTransition(renderWatch, 'min-max right') | |
1797 .attr('transform', function(d,i) { | |
1798 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')' | |
1799 }) | |
1800 .select('text') | |
1801 .style('opacity', 1); | |
1802 } | |
1803 break; | |
1804 } | |
1805 axisLabel.text(function(d) { return d }); | |
1806 | |
1807 if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) { | |
1808 //check if max and min overlap other values, if so, hide the values that overlap | |
1809 g.selectAll('g') // the g's wrapping each tick | |
1810 .each(function(d,i) { | |
1811 d3.select(this).select('text').attr('opacity', 1); | |
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! | |
1813 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL | |
1814 d3.select(this).attr('opacity', 0); | |
1815 | |
1816 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!! | |
1817 } | |
1818 }); | |
1819 | |
1820 //if Max and Min = 0 only show min, Issue #281 | |
1821 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) { | |
1822 wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) { | |
1823 return !i ? 1 : 0 | |
1824 }); | |
1825 } | |
1826 } | |
1827 | |
1828 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) { | |
1829 var maxMinRange = []; | |
1830 wrap.selectAll('g.nv-axisMaxMin') | |
1831 .each(function(d,i) { | |
1832 try { | |
1833 if (i) // i== 1, max position | |
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) | |
1835 else // i==0, min position | |
1836 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4) | |
1837 }catch (err) { | |
1838 if (i) // i== 1, max position | |
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) | |
1840 else // i==0, min position | |
1841 maxMinRange.push(scale(d) + 4); | |
1842 } | |
1843 }); | |
1844 // the g's wrapping each tick | |
1845 g.selectAll('g').each(function(d, i) { | |
1846 if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) { | |
1847 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL | |
1848 d3.select(this).remove(); | |
1849 else | |
1850 d3.select(this).select('text').remove(); // Don't remove the ZERO line!! | |
1851 } | |
1852 }); | |
1853 } | |
1854 | |
1855 //Highlight zero tick line | |
1856 g.selectAll('.tick') | |
1857 .filter(function (d) { | |
1858 /* | |
1859 The filter needs to return only ticks at or near zero. | |
1860 Numbers like 0.00001 need to count as zero as well, | |
1861 and the arithmetic trick below solves that. | |
1862 */ | |
1863 return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined) | |
1864 }) | |
1865 .classed('zero', true); | |
1866 | |
1867 //store old scales for use in transitions on update | |
1868 scale0 = scale.copy(); | |
1869 | |
1870 }); | |
1871 | |
1872 renderWatch.renderEnd('axis immediate'); | |
1873 return chart; | |
1874 } | |
1875 | |
1876 //============================================================ | |
1877 // Expose Public Variables | |
1878 //------------------------------------------------------------ | |
1879 | |
1880 // expose chart's sub-components | |
1881 chart.axis = axis; | |
1882 chart.dispatch = dispatch; | |
1883 | |
1884 chart.options = nv.utils.optionsFunc.bind(chart); | |
1885 chart._options = Object.create({}, { | |
1886 // simple options, just get/set the necessary values | |
1887 axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}}, | |
1888 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, | |
1889 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}}, | |
1890 rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}}, | |
1891 showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}}, | |
1892 axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}}, | |
1893 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
1894 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, | |
1895 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
1896 | |
1897 // options that require extra logic in the setter | |
1898 margin: {get: function(){return margin;}, set: function(_){ | |
1899 margin.top = _.top !== undefined ? _.top : margin.top; | |
1900 margin.right = _.right !== undefined ? _.right : margin.right; | |
1901 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
1902 margin.left = _.left !== undefined ? _.left : margin.left; | |
1903 }}, | |
1904 duration: {get: function(){return duration;}, set: function(_){ | |
1905 duration=_; | |
1906 renderWatch.reset(duration); | |
1907 }}, | |
1908 scale: {get: function(){return scale;}, set: function(_){ | |
1909 scale = _; | |
1910 axis.scale(scale); | |
1911 isOrdinal = typeof scale.rangeBands === 'function'; | |
1912 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']); | |
1913 }} | |
1914 }); | |
1915 | |
1916 nv.utils.initOptions(chart); | |
1917 nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']); | |
1918 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']); | |
1919 | |
1920 return chart; | |
1921 }; | |
1922 nv.models.boxPlot = function() { | |
1923 "use strict"; | |
1924 | |
1925 //============================================================ | |
1926 // Public Variables with Default Settings | |
1927 //------------------------------------------------------------ | |
1928 | |
1929 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
1930 , width = 960 | |
1931 , height = 500 | |
1932 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
1933 , x = d3.scale.ordinal() | |
1934 , y = d3.scale.linear() | |
1935 , getX = function(d) { return d.x } | |
1936 , getY = function(d) { return d.y } | |
1937 , color = nv.utils.defaultColor() | |
1938 , container = null | |
1939 , xDomain | |
1940 , yDomain | |
1941 , xRange | |
1942 , yRange | |
1943 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') | |
1944 , duration = 250 | |
1945 , maxBoxWidth = null | |
1946 ; | |
1947 | |
1948 //============================================================ | |
1949 // Private Variables | |
1950 //------------------------------------------------------------ | |
1951 | |
1952 var x0, y0; | |
1953 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
1954 | |
1955 function chart(selection) { | |
1956 renderWatch.reset(); | |
1957 selection.each(function(data) { | |
1958 var availableWidth = width - margin.left - margin.right, | |
1959 availableHeight = height - margin.top - margin.bottom; | |
1960 | |
1961 container = d3.select(this); | |
1962 nv.utils.initSVG(container); | |
1963 | |
1964 // Setup Scales | |
1965 x .domain(xDomain || data.map(function(d,i) { return getX(d,i); })) | |
1966 .rangeBands(xRange || [0, availableWidth], .1); | |
1967 | |
1968 // if we know yDomain, no need to calculate | |
1969 var yData = [] | |
1970 if (!yDomain) { | |
1971 // (y-range is based on quartiles, whiskers and outliers) | |
1972 | |
1973 // lower values | |
1974 var yMin = d3.min(data.map(function(d) { | |
1975 var min_arr = []; | |
1976 | |
1977 min_arr.push(d.values.Q1); | |
1978 if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); } | |
1979 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); } | |
1980 | |
1981 return d3.min(min_arr); | |
1982 })); | |
1983 | |
1984 // upper values | |
1985 var yMax = d3.max(data.map(function(d) { | |
1986 var max_arr = []; | |
1987 | |
1988 max_arr.push(d.values.Q3); | |
1989 if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); } | |
1990 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); } | |
1991 | |
1992 return d3.max(max_arr); | |
1993 })); | |
1994 | |
1995 yData = [ yMin, yMax ] ; | |
1996 } | |
1997 | |
1998 y.domain(yDomain || yData); | |
1999 y.range(yRange || [availableHeight, 0]); | |
2000 | |
2001 //store old scales if they exist | |
2002 x0 = x0 || x; | |
2003 y0 = y0 || y.copy().range([y(0),y(0)]); | |
2004 | |
2005 // Setup containers and skeleton of chart | |
2006 var wrap = container.selectAll('g.nv-wrap').data([data]); | |
2007 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap'); | |
2008 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
2009 | |
2010 var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d }); | |
2011 var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6); | |
2012 boxplots | |
2013 .attr('class', 'nv-boxplot') | |
2014 .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; }) | |
2015 .classed('hover', function(d) { return d.hover }); | |
2016 boxplots | |
2017 .watchTransition(renderWatch, 'nv-boxplot: boxplots') | |
2018 .style('stroke-opacity', 1) | |
2019 .style('fill-opacity', .75) | |
2020 .delay(function(d,i) { return i * duration / data.length }) | |
2021 .attr('transform', function(d,i) { | |
2022 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; | |
2023 }); | |
2024 boxplots.exit().remove(); | |
2025 | |
2026 // ----- add the SVG elements for each boxPlot ----- | |
2027 | |
2028 // conditionally append whisker lines | |
2029 boxEnter.each(function(d,i) { | |
2030 var box = d3.select(this); | |
2031 | |
2032 ['low', 'high'].forEach(function(key) { | |
2033 if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) { | |
2034 box.append('line') | |
2035 .style('stroke', (d.color) ? d.color : color(d,i)) | |
2036 .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key); | |
2037 | |
2038 box.append('line') | |
2039 .style('stroke', (d.color) ? d.color : color(d,i)) | |
2040 .attr('class', 'nv-boxplot-tick nv-boxplot-' + key); | |
2041 } | |
2042 }); | |
2043 }); | |
2044 | |
2045 // outliers | |
2046 // TODO: support custom colors here | |
2047 var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) { | |
2048 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; } | |
2049 else { return []; } | |
2050 }); | |
2051 outliers.enter().append('circle') | |
2052 .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) }) | |
2053 .on('mouseover', function(d,i,j) { | |
2054 d3.select(this).classed('hover', true); | |
2055 dispatch.elementMouseover({ | |
2056 series: { key: d, color: color(d,j) }, | |
2057 e: d3.event | |
2058 }); | |
2059 }) | |
2060 .on('mouseout', function(d,i,j) { | |
2061 d3.select(this).classed('hover', false); | |
2062 dispatch.elementMouseout({ | |
2063 series: { key: d, color: color(d,j) }, | |
2064 e: d3.event | |
2065 }); | |
2066 }) | |
2067 .on('mousemove', function(d,i) { | |
2068 dispatch.elementMousemove({e: d3.event}); | |
2069 }); | |
2070 | |
2071 outliers.attr('class', 'nv-boxplot-outlier'); | |
2072 outliers | |
2073 .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier') | |
2074 .attr('cx', x.rangeBand() * .45) | |
2075 .attr('cy', function(d,i,j) { return y(d); }) | |
2076 .attr('r', '3'); | |
2077 outliers.exit().remove(); | |
2078 | |
2079 var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); }; | |
2080 var box_left = function() { return x.rangeBand() * .45 - box_width()/2; }; | |
2081 var box_right = function() { return x.rangeBand() * .45 + box_width()/2; }; | |
2082 | |
2083 // update whisker lines and ticks | |
2084 ['low', 'high'].forEach(function(key) { | |
2085 var endpoint = (key === 'low') ? 'Q1' : 'Q3'; | |
2086 | |
2087 boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key) | |
2088 .watchTransition(renderWatch, 'nv-boxplot: boxplots') | |
2089 .attr('x1', x.rangeBand() * .45 ) | |
2090 .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); }) | |
2091 .attr('x2', x.rangeBand() * .45 ) | |
2092 .attr('y2', function(d,i) { return y(d.values[endpoint]); }); | |
2093 | |
2094 boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key) | |
2095 .watchTransition(renderWatch, 'nv-boxplot: boxplots') | |
2096 .attr('x1', box_left ) | |
2097 .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); }) | |
2098 .attr('x2', box_right ) | |
2099 .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); }); | |
2100 }); | |
2101 | |
2102 ['low', 'high'].forEach(function(key) { | |
2103 boxEnter.selectAll('.nv-boxplot-' + key) | |
2104 .on('mouseover', function(d,i,j) { | |
2105 d3.select(this).classed('hover', true); | |
2106 dispatch.elementMouseover({ | |
2107 series: { key: d.values['whisker_' + key], color: color(d,j) }, | |
2108 e: d3.event | |
2109 }); | |
2110 }) | |
2111 .on('mouseout', function(d,i,j) { | |
2112 d3.select(this).classed('hover', false); | |
2113 dispatch.elementMouseout({ | |
2114 series: { key: d.values['whisker_' + key], color: color(d,j) }, | |
2115 e: d3.event | |
2116 }); | |
2117 }) | |
2118 .on('mousemove', function(d,i) { | |
2119 dispatch.elementMousemove({e: d3.event}); | |
2120 }); | |
2121 }); | |
2122 | |
2123 // boxes | |
2124 boxEnter.append('rect') | |
2125 .attr('class', 'nv-boxplot-box') | |
2126 // tooltip events | |
2127 .on('mouseover', function(d,i) { | |
2128 d3.select(this).classed('hover', true); | |
2129 dispatch.elementMouseover({ | |
2130 key: d.label, | |
2131 value: d.label, | |
2132 series: [ | |
2133 { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) }, | |
2134 { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) }, | |
2135 { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) } | |
2136 ], | |
2137 data: d, | |
2138 index: i, | |
2139 e: d3.event | |
2140 }); | |
2141 }) | |
2142 .on('mouseout', function(d,i) { | |
2143 d3.select(this).classed('hover', false); | |
2144 dispatch.elementMouseout({ | |
2145 key: d.label, | |
2146 value: d.label, | |
2147 series: [ | |
2148 { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) }, | |
2149 { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) }, | |
2150 { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) } | |
2151 ], | |
2152 data: d, | |
2153 index: i, | |
2154 e: d3.event | |
2155 }); | |
2156 }) | |
2157 .on('mousemove', function(d,i) { | |
2158 dispatch.elementMousemove({e: d3.event}); | |
2159 }); | |
2160 | |
2161 // box transitions | |
2162 boxplots.select('rect.nv-boxplot-box') | |
2163 .watchTransition(renderWatch, 'nv-boxplot: boxes') | |
2164 .attr('y', function(d,i) { return y(d.values.Q3); }) | |
2165 .attr('width', box_width) | |
2166 .attr('x', box_left ) | |
2167 | |
2168 .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 }) | |
2169 .style('fill', function(d,i) { return d.color || color(d,i) }) | |
2170 .style('stroke', function(d,i) { return d.color || color(d,i) }); | |
2171 | |
2172 // median line | |
2173 boxEnter.append('line').attr('class', 'nv-boxplot-median'); | |
2174 | |
2175 boxplots.select('line.nv-boxplot-median') | |
2176 .watchTransition(renderWatch, 'nv-boxplot: boxplots line') | |
2177 .attr('x1', box_left) | |
2178 .attr('y1', function(d,i) { return y(d.values.Q2); }) | |
2179 .attr('x2', box_right) | |
2180 .attr('y2', function(d,i) { return y(d.values.Q2); }); | |
2181 | |
2182 //store old scales for use in transitions on update | |
2183 x0 = x.copy(); | |
2184 y0 = y.copy(); | |
2185 }); | |
2186 | |
2187 renderWatch.renderEnd('nv-boxplot immediate'); | |
2188 return chart; | |
2189 } | |
2190 | |
2191 //============================================================ | |
2192 // Expose Public Variables | |
2193 //------------------------------------------------------------ | |
2194 | |
2195 chart.dispatch = dispatch; | |
2196 chart.options = nv.utils.optionsFunc.bind(chart); | |
2197 | |
2198 chart._options = Object.create({}, { | |
2199 // simple options, just get/set the necessary values | |
2200 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
2201 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
2202 maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}}, | |
2203 x: {get: function(){return getX;}, set: function(_){getX=_;}}, | |
2204 y: {get: function(){return getY;}, set: function(_){getY=_;}}, | |
2205 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
2206 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
2207 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
2208 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
2209 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
2210 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
2211 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
2212 // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, | |
2213 | |
2214 // options that require extra logic in the setter | |
2215 margin: {get: function(){return margin;}, set: function(_){ | |
2216 margin.top = _.top !== undefined ? _.top : margin.top; | |
2217 margin.right = _.right !== undefined ? _.right : margin.right; | |
2218 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
2219 margin.left = _.left !== undefined ? _.left : margin.left; | |
2220 }}, | |
2221 color: {get: function(){return color;}, set: function(_){ | |
2222 color = nv.utils.getColor(_); | |
2223 }}, | |
2224 duration: {get: function(){return duration;}, set: function(_){ | |
2225 duration = _; | |
2226 renderWatch.reset(duration); | |
2227 }} | |
2228 }); | |
2229 | |
2230 nv.utils.initOptions(chart); | |
2231 | |
2232 return chart; | |
2233 }; | |
2234 nv.models.boxPlotChart = function() { | |
2235 "use strict"; | |
2236 | |
2237 //============================================================ | |
2238 // Public Variables with Default Settings | |
2239 //------------------------------------------------------------ | |
2240 | |
2241 var boxplot = nv.models.boxPlot() | |
2242 , xAxis = nv.models.axis() | |
2243 , yAxis = nv.models.axis() | |
2244 ; | |
2245 | |
2246 var margin = {top: 15, right: 10, bottom: 50, left: 60} | |
2247 , width = null | |
2248 , height = null | |
2249 , color = nv.utils.getColor() | |
2250 , showXAxis = true | |
2251 , showYAxis = true | |
2252 , rightAlignYAxis = false | |
2253 , staggerLabels = false | |
2254 , tooltip = nv.models.tooltip() | |
2255 , x | |
2256 , y | |
2257 , noData = "No Data Available." | |
2258 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate', 'renderEnd') | |
2259 , duration = 250 | |
2260 ; | |
2261 | |
2262 xAxis | |
2263 .orient('bottom') | |
2264 .showMaxMin(false) | |
2265 .tickFormat(function(d) { return d }) | |
2266 ; | |
2267 yAxis | |
2268 .orient((rightAlignYAxis) ? 'right' : 'left') | |
2269 .tickFormat(d3.format(',.1f')) | |
2270 ; | |
2271 | |
2272 tooltip.duration(0); | |
2273 | |
2274 //============================================================ | |
2275 // Private Variables | |
2276 //------------------------------------------------------------ | |
2277 | |
2278 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
2279 | |
2280 function chart(selection) { | |
2281 renderWatch.reset(); | |
2282 renderWatch.models(boxplot); | |
2283 if (showXAxis) renderWatch.models(xAxis); | |
2284 if (showYAxis) renderWatch.models(yAxis); | |
2285 | |
2286 selection.each(function(data) { | |
2287 var container = d3.select(this), | |
2288 that = this; | |
2289 nv.utils.initSVG(container); | |
2290 var availableWidth = (width || parseInt(container.style('width')) || 960) | |
2291 - margin.left - margin.right, | |
2292 availableHeight = (height || parseInt(container.style('height')) || 400) | |
2293 - margin.top - margin.bottom; | |
2294 | |
2295 chart.update = function() { | |
2296 dispatch.beforeUpdate(); | |
2297 container.transition().duration(duration).call(chart); | |
2298 }; | |
2299 chart.container = this; | |
2300 | |
2301 // Display No Data message if there's nothing to show. (quartiles required at minimum) | |
2302 if (!data || !data.length || | |
2303 !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) { | |
2304 var noDataText = container.selectAll('.nv-noData').data([noData]); | |
2305 | |
2306 noDataText.enter().append('text') | |
2307 .attr('class', 'nvd3 nv-noData') | |
2308 .attr('dy', '-.7em') | |
2309 .style('text-anchor', 'middle'); | |
2310 | |
2311 noDataText | |
2312 .attr('x', margin.left + availableWidth / 2) | |
2313 .attr('y', margin.top + availableHeight / 2) | |
2314 .text(function(d) { return d }); | |
2315 | |
2316 return chart; | |
2317 } else { | |
2318 container.selectAll('.nv-noData').remove(); | |
2319 } | |
2320 | |
2321 // Setup Scales | |
2322 x = boxplot.xScale(); | |
2323 y = boxplot.yScale().clamp(true); | |
2324 | |
2325 // Setup containers and skeleton of chart | |
2326 var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]); | |
2327 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g'); | |
2328 var defsEnter = gEnter.append('defs'); | |
2329 var g = wrap.select('g'); | |
2330 | |
2331 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
2332 gEnter.append('g').attr('class', 'nv-y nv-axis') | |
2333 .append('g').attr('class', 'nv-zeroLine') | |
2334 .append('line'); | |
2335 | |
2336 gEnter.append('g').attr('class', 'nv-barsWrap'); | |
2337 | |
2338 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
2339 | |
2340 if (rightAlignYAxis) { | |
2341 g.select(".nv-y.nv-axis") | |
2342 .attr("transform", "translate(" + availableWidth + ",0)"); | |
2343 } | |
2344 | |
2345 // Main Chart Component(s) | |
2346 boxplot | |
2347 .width(availableWidth) | |
2348 .height(availableHeight); | |
2349 | |
2350 var barsWrap = g.select('.nv-barsWrap') | |
2351 .datum(data.filter(function(d) { return !d.disabled })) | |
2352 | |
2353 barsWrap.transition().call(boxplot); | |
2354 | |
2355 | |
2356 defsEnter.append('clipPath') | |
2357 .attr('id', 'nv-x-label-clip-' + boxplot.id()) | |
2358 .append('rect'); | |
2359 | |
2360 g.select('#nv-x-label-clip-' + boxplot.id() + ' rect') | |
2361 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) | |
2362 .attr('height', 16) | |
2363 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); | |
2364 | |
2365 // Setup Axes | |
2366 if (showXAxis) { | |
2367 xAxis | |
2368 .scale(x) | |
2369 .ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
2370 .tickSize(-availableHeight, 0); | |
2371 | |
2372 g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')'); | |
2373 g.select('.nv-x.nv-axis').call(xAxis); | |
2374 | |
2375 var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); | |
2376 if (staggerLabels) { | |
2377 xTicks | |
2378 .selectAll('text') | |
2379 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) | |
2380 } | |
2381 } | |
2382 | |
2383 if (showYAxis) { | |
2384 yAxis | |
2385 .scale(y) | |
2386 .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data | |
2387 .tickSize( -availableWidth, 0); | |
2388 | |
2389 g.select('.nv-y.nv-axis').call(yAxis); | |
2390 } | |
2391 | |
2392 // Zero line | |
2393 g.select(".nv-zeroLine line") | |
2394 .attr("x1",0) | |
2395 .attr("x2",availableWidth) | |
2396 .attr("y1", y(0)) | |
2397 .attr("y2", y(0)) | |
2398 ; | |
2399 | |
2400 //============================================================ | |
2401 // Event Handling/Dispatching (in chart's scope) | |
2402 //------------------------------------------------------------ | |
2403 }); | |
2404 | |
2405 renderWatch.renderEnd('nv-boxplot chart immediate'); | |
2406 return chart; | |
2407 } | |
2408 | |
2409 //============================================================ | |
2410 // Event Handling/Dispatching (out of chart's scope) | |
2411 //------------------------------------------------------------ | |
2412 | |
2413 boxplot.dispatch.on('elementMouseover.tooltip', function(evt) { | |
2414 tooltip.data(evt).hidden(false); | |
2415 }); | |
2416 | |
2417 boxplot.dispatch.on('elementMouseout.tooltip', function(evt) { | |
2418 tooltip.data(evt).hidden(true); | |
2419 }); | |
2420 | |
2421 boxplot.dispatch.on('elementMousemove.tooltip', function(evt) { | |
2422 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
2423 }); | |
2424 | |
2425 //============================================================ | |
2426 // Expose Public Variables | |
2427 //------------------------------------------------------------ | |
2428 | |
2429 chart.dispatch = dispatch; | |
2430 chart.boxplot = boxplot; | |
2431 chart.xAxis = xAxis; | |
2432 chart.yAxis = yAxis; | |
2433 chart.tooltip = tooltip; | |
2434 | |
2435 chart.options = nv.utils.optionsFunc.bind(chart); | |
2436 | |
2437 chart._options = Object.create({}, { | |
2438 // simple options, just get/set the necessary values | |
2439 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
2440 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
2441 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, | |
2442 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
2443 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
2444 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}}, | |
2445 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}}, | |
2446 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
2447 | |
2448 // options that require extra logic in the setter | |
2449 margin: {get: function(){return margin;}, set: function(_){ | |
2450 margin.top = _.top !== undefined ? _.top : margin.top; | |
2451 margin.right = _.right !== undefined ? _.right : margin.right; | |
2452 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
2453 margin.left = _.left !== undefined ? _.left : margin.left; | |
2454 }}, | |
2455 duration: {get: function(){return duration;}, set: function(_){ | |
2456 duration = _; | |
2457 renderWatch.reset(duration); | |
2458 boxplot.duration(duration); | |
2459 xAxis.duration(duration); | |
2460 yAxis.duration(duration); | |
2461 }}, | |
2462 color: {get: function(){return color;}, set: function(_){ | |
2463 color = nv.utils.getColor(_); | |
2464 boxplot.color(color); | |
2465 }}, | |
2466 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ | |
2467 rightAlignYAxis = _; | |
2468 yAxis.orient( (_) ? 'right' : 'left'); | |
2469 }} | |
2470 }); | |
2471 | |
2472 nv.utils.inheritOptions(chart, boxplot); | |
2473 nv.utils.initOptions(chart); | |
2474 | |
2475 return chart; | |
2476 } | |
2477 // Chart design based on the recommendations of Stephen Few. Implementation | |
2478 // based on the work of Clint Ivy, Jamie Love, and Jason Davies. | |
2479 // http://projects.instantcognition.com/protovis/bulletchart/ | |
2480 | |
2481 nv.models.bullet = function() { | |
2482 "use strict"; | |
2483 | |
2484 //============================================================ | |
2485 // Public Variables with Default Settings | |
2486 //------------------------------------------------------------ | |
2487 | |
2488 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
2489 , orient = 'left' // TODO top & bottom | |
2490 , reverse = false | |
2491 , ranges = function(d) { return d.ranges } | |
2492 , markers = function(d) { return d.markers ? d.markers : [0] } | |
2493 , measures = function(d) { return d.measures } | |
2494 , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] } | |
2495 , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] } | |
2496 , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] } | |
2497 , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) | |
2498 , width = 380 | |
2499 , height = 30 | |
2500 , container = null | |
2501 , tickFormat = null | |
2502 , color = nv.utils.getColor(['#1f77b4']) | |
2503 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove') | |
2504 ; | |
2505 | |
2506 function chart(selection) { | |
2507 selection.each(function(d, i) { | |
2508 var availableWidth = width - margin.left - margin.right, | |
2509 availableHeight = height - margin.top - margin.bottom; | |
2510 | |
2511 container = d3.select(this); | |
2512 nv.utils.initSVG(container); | |
2513 | |
2514 var rangez = ranges.call(this, d, i).slice().sort(d3.descending), | |
2515 markerz = markers.call(this, d, i).slice().sort(d3.descending), | |
2516 measurez = measures.call(this, d, i).slice().sort(d3.descending), | |
2517 rangeLabelz = rangeLabels.call(this, d, i).slice(), | |
2518 markerLabelz = markerLabels.call(this, d, i).slice(), | |
2519 measureLabelz = measureLabels.call(this, d, i).slice(); | |
2520 | |
2521 // Setup Scales | |
2522 // Compute the new x-scale. | |
2523 var x1 = d3.scale.linear() | |
2524 .domain( d3.extent(d3.merge([forceX, rangez])) ) | |
2525 .range(reverse ? [availableWidth, 0] : [0, availableWidth]); | |
2526 | |
2527 // Retrieve the old x-scale, if this is an update. | |
2528 var x0 = this.__chart__ || d3.scale.linear() | |
2529 .domain([0, Infinity]) | |
2530 .range(x1.range()); | |
2531 | |
2532 // Stash the new scale. | |
2533 this.__chart__ = x1; | |
2534 | |
2535 var rangeMin = d3.min(rangez), //rangez[2] | |
2536 rangeMax = d3.max(rangez), //rangez[0] | |
2537 rangeAvg = rangez[1]; | |
2538 | |
2539 // Setup containers and skeleton of chart | |
2540 var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]); | |
2541 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet'); | |
2542 var gEnter = wrapEnter.append('g'); | |
2543 var g = wrap.select('g'); | |
2544 | |
2545 gEnter.append('rect').attr('class', 'nv-range nv-rangeMax'); | |
2546 gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg'); | |
2547 gEnter.append('rect').attr('class', 'nv-range nv-rangeMin'); | |
2548 gEnter.append('rect').attr('class', 'nv-measure'); | |
2549 | |
2550 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
2551 | |
2552 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) | |
2553 w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; | |
2554 var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) }, | |
2555 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) }; | |
2556 | |
2557 g.select('rect.nv-rangeMax') | |
2558 .attr('height', availableHeight) | |
2559 .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin)) | |
2560 .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin)) | |
2561 .datum(rangeMax > 0 ? rangeMax : rangeMin) | |
2562 | |
2563 g.select('rect.nv-rangeAvg') | |
2564 .attr('height', availableHeight) | |
2565 .attr('width', w1(rangeAvg)) | |
2566 .attr('x', xp1(rangeAvg)) | |
2567 .datum(rangeAvg) | |
2568 | |
2569 g.select('rect.nv-rangeMin') | |
2570 .attr('height', availableHeight) | |
2571 .attr('width', w1(rangeMax)) | |
2572 .attr('x', xp1(rangeMax)) | |
2573 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax)) | |
2574 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax)) | |
2575 .datum(rangeMax > 0 ? rangeMin : rangeMax) | |
2576 | |
2577 g.select('rect.nv-measure') | |
2578 .style('fill', color) | |
2579 .attr('height', availableHeight / 3) | |
2580 .attr('y', availableHeight / 3) | |
2581 .attr('width', measurez < 0 ? | |
2582 x1(0) - x1(measurez[0]) | |
2583 : x1(measurez[0]) - x1(0)) | |
2584 .attr('x', xp1(measurez)) | |
2585 .on('mouseover', function() { | |
2586 dispatch.elementMouseover({ | |
2587 value: measurez[0], | |
2588 label: measureLabelz[0] || 'Current', | |
2589 color: d3.select(this).style("fill") | |
2590 }) | |
2591 }) | |
2592 .on('mousemove', function() { | |
2593 dispatch.elementMousemove({ | |
2594 value: measurez[0], | |
2595 label: measureLabelz[0] || 'Current', | |
2596 color: d3.select(this).style("fill") | |
2597 }) | |
2598 }) | |
2599 .on('mouseout', function() { | |
2600 dispatch.elementMouseout({ | |
2601 value: measurez[0], | |
2602 label: measureLabelz[0] || 'Current', | |
2603 color: d3.select(this).style("fill") | |
2604 }) | |
2605 }); | |
2606 | |
2607 var h3 = availableHeight / 6; | |
2608 | |
2609 var markerData = markerz.map( function(marker, index) { | |
2610 return {value: marker, label: markerLabelz[index]} | |
2611 }); | |
2612 gEnter | |
2613 .selectAll("path.nv-markerTriangle") | |
2614 .data(markerData) | |
2615 .enter() | |
2616 .append('path') | |
2617 .attr('class', 'nv-markerTriangle') | |
2618 .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' }) | |
2619 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z') | |
2620 .on('mouseover', function(d) { | |
2621 dispatch.elementMouseover({ | |
2622 value: d.value, | |
2623 label: d.label || 'Previous', | |
2624 color: d3.select(this).style("fill"), | |
2625 pos: [x1(d.value), availableHeight/2] | |
2626 }) | |
2627 | |
2628 }) | |
2629 .on('mousemove', function(d) { | |
2630 dispatch.elementMousemove({ | |
2631 value: d.value, | |
2632 label: d.label || 'Previous', | |
2633 color: d3.select(this).style("fill") | |
2634 }) | |
2635 }) | |
2636 .on('mouseout', function(d, i) { | |
2637 dispatch.elementMouseout({ | |
2638 value: d.value, | |
2639 label: d.label || 'Previous', | |
2640 color: d3.select(this).style("fill") | |
2641 }) | |
2642 }); | |
2643 | |
2644 wrap.selectAll('.nv-range') | |
2645 .on('mouseover', function(d,i) { | |
2646 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); | |
2647 dispatch.elementMouseover({ | |
2648 value: d, | |
2649 label: label, | |
2650 color: d3.select(this).style("fill") | |
2651 }) | |
2652 }) | |
2653 .on('mousemove', function() { | |
2654 dispatch.elementMousemove({ | |
2655 value: measurez[0], | |
2656 label: measureLabelz[0] || 'Previous', | |
2657 color: d3.select(this).style("fill") | |
2658 }) | |
2659 }) | |
2660 .on('mouseout', function(d,i) { | |
2661 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum"); | |
2662 dispatch.elementMouseout({ | |
2663 value: d, | |
2664 label: label, | |
2665 color: d3.select(this).style("fill") | |
2666 }) | |
2667 }); | |
2668 }); | |
2669 | |
2670 return chart; | |
2671 } | |
2672 | |
2673 //============================================================ | |
2674 // Expose Public Variables | |
2675 //------------------------------------------------------------ | |
2676 | |
2677 chart.dispatch = dispatch; | |
2678 chart.options = nv.utils.optionsFunc.bind(chart); | |
2679 | |
2680 chart._options = Object.create({}, { | |
2681 // simple options, just get/set the necessary values | |
2682 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) | |
2683 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) | |
2684 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) | |
2685 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, | |
2686 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
2687 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
2688 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, | |
2689 | |
2690 // options that require extra logic in the setter | |
2691 margin: {get: function(){return margin;}, set: function(_){ | |
2692 margin.top = _.top !== undefined ? _.top : margin.top; | |
2693 margin.right = _.right !== undefined ? _.right : margin.right; | |
2694 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
2695 margin.left = _.left !== undefined ? _.left : margin.left; | |
2696 }}, | |
2697 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom | |
2698 orient = _; | |
2699 reverse = orient == 'right' || orient == 'bottom'; | |
2700 }}, | |
2701 color: {get: function(){return color;}, set: function(_){ | |
2702 color = nv.utils.getColor(_); | |
2703 }} | |
2704 }); | |
2705 | |
2706 nv.utils.initOptions(chart); | |
2707 return chart; | |
2708 }; | |
2709 | |
2710 | |
2711 | |
2712 // Chart design based on the recommendations of Stephen Few. Implementation | |
2713 // based on the work of Clint Ivy, Jamie Love, and Jason Davies. | |
2714 // http://projects.instantcognition.com/protovis/bulletchart/ | |
2715 nv.models.bulletChart = function() { | |
2716 "use strict"; | |
2717 | |
2718 //============================================================ | |
2719 // Public Variables with Default Settings | |
2720 //------------------------------------------------------------ | |
2721 | |
2722 var bullet = nv.models.bullet(); | |
2723 var tooltip = nv.models.tooltip(); | |
2724 | |
2725 var orient = 'left' // TODO top & bottom | |
2726 , reverse = false | |
2727 , margin = {top: 5, right: 40, bottom: 20, left: 120} | |
2728 , ranges = function(d) { return d.ranges } | |
2729 , markers = function(d) { return d.markers ? d.markers : [0] } | |
2730 , measures = function(d) { return d.measures } | |
2731 , width = null | |
2732 , height = 55 | |
2733 , tickFormat = null | |
2734 , ticks = null | |
2735 , noData = null | |
2736 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide') | |
2737 ; | |
2738 | |
2739 tooltip.duration(0).headerEnabled(false); | |
2740 | |
2741 function chart(selection) { | |
2742 selection.each(function(d, i) { | |
2743 var container = d3.select(this); | |
2744 nv.utils.initSVG(container); | |
2745 | |
2746 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
2747 availableHeight = height - margin.top - margin.bottom, | |
2748 that = this; | |
2749 | |
2750 chart.update = function() { chart(selection) }; | |
2751 chart.container = this; | |
2752 | |
2753 // Display No Data message if there's nothing to show. | |
2754 if (!d || !ranges.call(this, d, i)) { | |
2755 nv.utils.noData(chart, container) | |
2756 return chart; | |
2757 } else { | |
2758 container.selectAll('.nv-noData').remove(); | |
2759 } | |
2760 | |
2761 var rangez = ranges.call(this, d, i).slice().sort(d3.descending), | |
2762 markerz = markers.call(this, d, i).slice().sort(d3.descending), | |
2763 measurez = measures.call(this, d, i).slice().sort(d3.descending); | |
2764 | |
2765 // Setup containers and skeleton of chart | |
2766 var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]); | |
2767 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart'); | |
2768 var gEnter = wrapEnter.append('g'); | |
2769 var g = wrap.select('g'); | |
2770 | |
2771 gEnter.append('g').attr('class', 'nv-bulletWrap'); | |
2772 gEnter.append('g').attr('class', 'nv-titles'); | |
2773 | |
2774 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
2775 | |
2776 // Compute the new x-scale. | |
2777 var x1 = d3.scale.linear() | |
2778 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain | |
2779 .range(reverse ? [availableWidth, 0] : [0, availableWidth]); | |
2780 | |
2781 // Retrieve the old x-scale, if this is an update. | |
2782 var x0 = this.__chart__ || d3.scale.linear() | |
2783 .domain([0, Infinity]) | |
2784 .range(x1.range()); | |
2785 | |
2786 // Stash the new scale. | |
2787 this.__chart__ = x1; | |
2788 | |
2789 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0) | |
2790 w1 = function(d) { return Math.abs(x1(d) - x1(0)) }; | |
2791 | |
2792 var title = gEnter.select('.nv-titles').append('g') | |
2793 .attr('text-anchor', 'end') | |
2794 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')'); | |
2795 title.append('text') | |
2796 .attr('class', 'nv-title') | |
2797 .text(function(d) { return d.title; }); | |
2798 | |
2799 title.append('text') | |
2800 .attr('class', 'nv-subtitle') | |
2801 .attr('dy', '1em') | |
2802 .text(function(d) { return d.subtitle; }); | |
2803 | |
2804 bullet | |
2805 .width(availableWidth) | |
2806 .height(availableHeight) | |
2807 | |
2808 var bulletWrap = g.select('.nv-bulletWrap'); | |
2809 d3.transition(bulletWrap).call(bullet); | |
2810 | |
2811 // Compute the tick format. | |
2812 var format = tickFormat || x1.tickFormat( availableWidth / 100 ); | |
2813 | |
2814 // Update the tick groups. | |
2815 var tick = g.selectAll('g.nv-tick') | |
2816 .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) { | |
2817 return this.textContent || format(d); | |
2818 }); | |
2819 | |
2820 // Initialize the ticks with the old scale, x0. | |
2821 var tickEnter = tick.enter().append('g') | |
2822 .attr('class', 'nv-tick') | |
2823 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' }) | |
2824 .style('opacity', 1e-6); | |
2825 | |
2826 tickEnter.append('line') | |
2827 .attr('y1', availableHeight) | |
2828 .attr('y2', availableHeight * 7 / 6); | |
2829 | |
2830 tickEnter.append('text') | |
2831 .attr('text-anchor', 'middle') | |
2832 .attr('dy', '1em') | |
2833 .attr('y', availableHeight * 7 / 6) | |
2834 .text(format); | |
2835 | |
2836 // Transition the updating ticks to the new scale, x1. | |
2837 var tickUpdate = d3.transition(tick) | |
2838 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) | |
2839 .style('opacity', 1); | |
2840 | |
2841 tickUpdate.select('line') | |
2842 .attr('y1', availableHeight) | |
2843 .attr('y2', availableHeight * 7 / 6); | |
2844 | |
2845 tickUpdate.select('text') | |
2846 .attr('y', availableHeight * 7 / 6); | |
2847 | |
2848 // Transition the exiting ticks to the new scale, x1. | |
2849 d3.transition(tick.exit()) | |
2850 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' }) | |
2851 .style('opacity', 1e-6) | |
2852 .remove(); | |
2853 }); | |
2854 | |
2855 d3.timer.flush(); | |
2856 return chart; | |
2857 } | |
2858 | |
2859 //============================================================ | |
2860 // Event Handling/Dispatching (out of chart's scope) | |
2861 //------------------------------------------------------------ | |
2862 | |
2863 bullet.dispatch.on('elementMouseover.tooltip', function(evt) { | |
2864 evt['series'] = { | |
2865 key: evt.label, | |
2866 value: evt.value, | |
2867 color: evt.color | |
2868 }; | |
2869 tooltip.data(evt).hidden(false); | |
2870 }); | |
2871 | |
2872 bullet.dispatch.on('elementMouseout.tooltip', function(evt) { | |
2873 tooltip.hidden(true); | |
2874 }); | |
2875 | |
2876 bullet.dispatch.on('elementMousemove.tooltip', function(evt) { | |
2877 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
2878 }); | |
2879 | |
2880 //============================================================ | |
2881 // Expose Public Variables | |
2882 //------------------------------------------------------------ | |
2883 | |
2884 chart.bullet = bullet; | |
2885 chart.dispatch = dispatch; | |
2886 chart.tooltip = tooltip; | |
2887 | |
2888 chart.options = nv.utils.optionsFunc.bind(chart); | |
2889 | |
2890 chart._options = Object.create({}, { | |
2891 // simple options, just get/set the necessary values | |
2892 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good) | |
2893 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal) | |
2894 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast) | |
2895 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
2896 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
2897 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}}, | |
2898 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}}, | |
2899 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
2900 | |
2901 // deprecated options | |
2902 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
2903 // deprecated after 1.7.1 | |
2904 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
2905 tooltip.enabled(!!_); | |
2906 }}, | |
2907 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
2908 // deprecated after 1.7.1 | |
2909 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
2910 tooltip.contentGenerator(_); | |
2911 }}, | |
2912 | |
2913 // options that require extra logic in the setter | |
2914 margin: {get: function(){return margin;}, set: function(_){ | |
2915 margin.top = _.top !== undefined ? _.top : margin.top; | |
2916 margin.right = _.right !== undefined ? _.right : margin.right; | |
2917 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
2918 margin.left = _.left !== undefined ? _.left : margin.left; | |
2919 }}, | |
2920 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom | |
2921 orient = _; | |
2922 reverse = orient == 'right' || orient == 'bottom'; | |
2923 }} | |
2924 }); | |
2925 | |
2926 nv.utils.inheritOptions(chart, bullet); | |
2927 nv.utils.initOptions(chart); | |
2928 | |
2929 return chart; | |
2930 }; | |
2931 | |
2932 | |
2933 | |
2934 nv.models.candlestickBar = function() { | |
2935 "use strict"; | |
2936 | |
2937 //============================================================ | |
2938 // Public Variables with Default Settings | |
2939 //------------------------------------------------------------ | |
2940 | |
2941 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
2942 , width = null | |
2943 , height = null | |
2944 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
2945 , container | |
2946 , x = d3.scale.linear() | |
2947 , y = d3.scale.linear() | |
2948 , getX = function(d) { return d.x } | |
2949 , getY = function(d) { return d.y } | |
2950 , getOpen = function(d) { return d.open } | |
2951 , getClose = function(d) { return d.close } | |
2952 , getHigh = function(d) { return d.high } | |
2953 , getLow = function(d) { return d.low } | |
2954 , forceX = [] | |
2955 , forceY = [] | |
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 | |
2957 , clipEdge = true | |
2958 , color = nv.utils.defaultColor() | |
2959 , interactive = false | |
2960 , xDomain | |
2961 , yDomain | |
2962 , xRange | |
2963 , yRange | |
2964 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') | |
2965 ; | |
2966 | |
2967 //============================================================ | |
2968 // Private Variables | |
2969 //------------------------------------------------------------ | |
2970 | |
2971 function chart(selection) { | |
2972 selection.each(function(data) { | |
2973 container = d3.select(this); | |
2974 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
2975 availableHeight = nv.utils.availableHeight(height, container, margin); | |
2976 | |
2977 nv.utils.initSVG(container); | |
2978 | |
2979 // Width of the candlestick bars. | |
2980 var barWidth = (availableWidth / data[0].values.length) * .45; | |
2981 | |
2982 // Setup Scales | |
2983 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); | |
2984 | |
2985 if (padData) | |
2986 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); | |
2987 else | |
2988 x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]); | |
2989 | |
2990 y.domain(yDomain || [ | |
2991 d3.min(data[0].values.map(getLow).concat(forceY)), | |
2992 d3.max(data[0].values.map(getHigh).concat(forceY)) | |
2993 ] | |
2994 ).range(yRange || [availableHeight, 0]); | |
2995 | |
2996 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point | |
2997 if (x.domain()[0] === x.domain()[1]) | |
2998 x.domain()[0] ? | |
2999 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) | |
3000 : x.domain([-1,1]); | |
3001 | |
3002 if (y.domain()[0] === y.domain()[1]) | |
3003 y.domain()[0] ? | |
3004 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) | |
3005 : y.domain([-1,1]); | |
3006 | |
3007 // Setup containers and skeleton of chart | |
3008 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]); | |
3009 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar'); | |
3010 var defsEnter = wrapEnter.append('defs'); | |
3011 var gEnter = wrapEnter.append('g'); | |
3012 var g = wrap.select('g'); | |
3013 | |
3014 gEnter.append('g').attr('class', 'nv-ticks'); | |
3015 | |
3016 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
3017 | |
3018 container | |
3019 .on('click', function(d,i) { | |
3020 dispatch.chartClick({ | |
3021 data: d, | |
3022 index: i, | |
3023 pos: d3.event, | |
3024 id: id | |
3025 }); | |
3026 }); | |
3027 | |
3028 defsEnter.append('clipPath') | |
3029 .attr('id', 'nv-chart-clip-path-' + id) | |
3030 .append('rect'); | |
3031 | |
3032 wrap.select('#nv-chart-clip-path-' + id + ' rect') | |
3033 .attr('width', availableWidth) | |
3034 .attr('height', availableHeight); | |
3035 | |
3036 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); | |
3037 | |
3038 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') | |
3039 .data(function(d) { return d }); | |
3040 ticks.exit().remove(); | |
3041 | |
3042 // The colors are currently controlled by CSS. | |
3043 var tickGroups = ticks.enter().append('g') | |
3044 .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i}); | |
3045 | |
3046 var lines = tickGroups.append('line') | |
3047 .attr('class', 'nv-candlestick-lines') | |
3048 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) | |
3049 .attr('x1', 0) | |
3050 .attr('y1', function(d, i) { return y(getHigh(d, i)); }) | |
3051 .attr('x2', 0) | |
3052 .attr('y2', function(d, i) { return y(getLow(d, i)); }); | |
3053 | |
3054 var rects = tickGroups.append('rect') | |
3055 .attr('class', 'nv-candlestick-rects nv-bars') | |
3056 .attr('transform', function(d, i) { | |
3057 return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' | |
3058 + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) | |
3059 + ')'; | |
3060 }) | |
3061 .attr('x', 0) | |
3062 .attr('y', 0) | |
3063 .attr('width', barWidth) | |
3064 .attr('height', function(d, i) { | |
3065 var open = getOpen(d, i); | |
3066 var close = getClose(d, i); | |
3067 return open > close ? y(close) - y(open) : y(open) - y(close); | |
3068 }); | |
3069 | |
3070 container.selectAll('.nv-candlestick-lines').transition() | |
3071 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; }) | |
3072 .attr('x1', 0) | |
3073 .attr('y1', function(d, i) { return y(getHigh(d, i)); }) | |
3074 .attr('x2', 0) | |
3075 .attr('y2', function(d, i) { return y(getLow(d, i)); }); | |
3076 | |
3077 container.selectAll('.nv-candlestick-rects').transition() | |
3078 .attr('transform', function(d, i) { | |
3079 return 'translate(' + (x(getX(d, i)) - barWidth/2) + ',' | |
3080 + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0)) | |
3081 + ')'; | |
3082 }) | |
3083 .attr('x', 0) | |
3084 .attr('y', 0) | |
3085 .attr('width', barWidth) | |
3086 .attr('height', function(d, i) { | |
3087 var open = getOpen(d, i); | |
3088 var close = getClose(d, i); | |
3089 return open > close ? y(close) - y(open) : y(open) - y(close); | |
3090 }); | |
3091 }); | |
3092 | |
3093 return chart; | |
3094 } | |
3095 | |
3096 | |
3097 //Create methods to allow outside functions to highlight a specific bar. | |
3098 chart.highlightPoint = function(pointIndex, isHoverOver) { | |
3099 chart.clearHighlights(); | |
3100 container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex) | |
3101 .classed("hover", isHoverOver) | |
3102 ; | |
3103 }; | |
3104 | |
3105 chart.clearHighlights = function() { | |
3106 container.select(".nv-candlestickBar .nv-tick.hover") | |
3107 .classed("hover", false) | |
3108 ; | |
3109 }; | |
3110 | |
3111 //============================================================ | |
3112 // Expose Public Variables | |
3113 //------------------------------------------------------------ | |
3114 | |
3115 chart.dispatch = dispatch; | |
3116 chart.options = nv.utils.optionsFunc.bind(chart); | |
3117 | |
3118 chart._options = Object.create({}, { | |
3119 // simple options, just get/set the necessary values | |
3120 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
3121 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
3122 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
3123 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
3124 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
3125 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
3126 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
3127 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
3128 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, | |
3129 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, | |
3130 padData: {get: function(){return padData;}, set: function(_){padData=_;}}, | |
3131 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, | |
3132 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
3133 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, | |
3134 | |
3135 x: {get: function(){return getX;}, set: function(_){getX=_;}}, | |
3136 y: {get: function(){return getY;}, set: function(_){getY=_;}}, | |
3137 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, | |
3138 close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, | |
3139 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, | |
3140 low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, | |
3141 | |
3142 // options that require extra logic in the setter | |
3143 margin: {get: function(){return margin;}, set: function(_){ | |
3144 margin.top = _.top != undefined ? _.top : margin.top; | |
3145 margin.right = _.right != undefined ? _.right : margin.right; | |
3146 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; | |
3147 margin.left = _.left != undefined ? _.left : margin.left; | |
3148 }}, | |
3149 color: {get: function(){return color;}, set: function(_){ | |
3150 color = nv.utils.getColor(_); | |
3151 }} | |
3152 }); | |
3153 | |
3154 nv.utils.initOptions(chart); | |
3155 return chart; | |
3156 }; | |
3157 | |
3158 nv.models.cumulativeLineChart = function() { | |
3159 "use strict"; | |
3160 | |
3161 //============================================================ | |
3162 // Public Variables with Default Settings | |
3163 //------------------------------------------------------------ | |
3164 | |
3165 var lines = nv.models.line() | |
3166 , xAxis = nv.models.axis() | |
3167 , yAxis = nv.models.axis() | |
3168 , legend = nv.models.legend() | |
3169 , controls = nv.models.legend() | |
3170 , interactiveLayer = nv.interactiveGuideline() | |
3171 , tooltip = nv.models.tooltip() | |
3172 ; | |
3173 | |
3174 var margin = {top: 30, right: 30, bottom: 50, left: 60} | |
3175 , color = nv.utils.defaultColor() | |
3176 , width = null | |
3177 , height = null | |
3178 , showLegend = true | |
3179 , showXAxis = true | |
3180 , showYAxis = true | |
3181 , rightAlignYAxis = false | |
3182 , showControls = true | |
3183 , useInteractiveGuideline = false | |
3184 , rescaleY = true | |
3185 , x //can be accessed via chart.xScale() | |
3186 , y //can be accessed via chart.yScale() | |
3187 , id = lines.id() | |
3188 , state = nv.utils.state() | |
3189 , defaultState = null | |
3190 , noData = null | |
3191 , average = function(d) { return d.average } | |
3192 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') | |
3193 , transitionDuration = 250 | |
3194 , duration = 250 | |
3195 , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function. | |
3196 ; | |
3197 | |
3198 state.index = 0; | |
3199 state.rescaleY = rescaleY; | |
3200 | |
3201 xAxis.orient('bottom').tickPadding(7); | |
3202 yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); | |
3203 | |
3204 tooltip.valueFormatter(function(d, i) { | |
3205 return yAxis.tickFormat()(d, i); | |
3206 }).headerFormatter(function(d, i) { | |
3207 return xAxis.tickFormat()(d, i); | |
3208 }); | |
3209 | |
3210 controls.updateState(false); | |
3211 | |
3212 //============================================================ | |
3213 // Private Variables | |
3214 //------------------------------------------------------------ | |
3215 | |
3216 var dx = d3.scale.linear() | |
3217 , index = {i: 0, x: 0} | |
3218 , renderWatch = nv.utils.renderWatch(dispatch, duration) | |
3219 ; | |
3220 | |
3221 var stateGetter = function(data) { | |
3222 return function(){ | |
3223 return { | |
3224 active: data.map(function(d) { return !d.disabled }), | |
3225 index: index.i, | |
3226 rescaleY: rescaleY | |
3227 }; | |
3228 } | |
3229 }; | |
3230 | |
3231 var stateSetter = function(data) { | |
3232 return function(state) { | |
3233 if (state.index !== undefined) | |
3234 index.i = state.index; | |
3235 if (state.rescaleY !== undefined) | |
3236 rescaleY = state.rescaleY; | |
3237 if (state.active !== undefined) | |
3238 data.forEach(function(series,i) { | |
3239 series.disabled = !state.active[i]; | |
3240 }); | |
3241 } | |
3242 }; | |
3243 | |
3244 function chart(selection) { | |
3245 renderWatch.reset(); | |
3246 renderWatch.models(lines); | |
3247 if (showXAxis) renderWatch.models(xAxis); | |
3248 if (showYAxis) renderWatch.models(yAxis); | |
3249 selection.each(function(data) { | |
3250 var container = d3.select(this); | |
3251 nv.utils.initSVG(container); | |
3252 container.classed('nv-chart-' + id, true); | |
3253 var that = this; | |
3254 | |
3255 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
3256 availableHeight = nv.utils.availableHeight(height, container, margin); | |
3257 | |
3258 chart.update = function() { | |
3259 if (duration === 0) | |
3260 container.call(chart); | |
3261 else | |
3262 container.transition().duration(duration).call(chart) | |
3263 }; | |
3264 chart.container = this; | |
3265 | |
3266 state | |
3267 .setter(stateSetter(data), chart.update) | |
3268 .getter(stateGetter(data)) | |
3269 .update(); | |
3270 | |
3271 // DEPRECATED set state.disableddisabled | |
3272 state.disabled = data.map(function(d) { return !!d.disabled }); | |
3273 | |
3274 if (!defaultState) { | |
3275 var key; | |
3276 defaultState = {}; | |
3277 for (key in state) { | |
3278 if (state[key] instanceof Array) | |
3279 defaultState[key] = state[key].slice(0); | |
3280 else | |
3281 defaultState[key] = state[key]; | |
3282 } | |
3283 } | |
3284 | |
3285 var indexDrag = d3.behavior.drag() | |
3286 .on('dragstart', dragStart) | |
3287 .on('drag', dragMove) | |
3288 .on('dragend', dragEnd); | |
3289 | |
3290 | |
3291 function dragStart(d,i) { | |
3292 d3.select(chart.container) | |
3293 .style('cursor', 'ew-resize'); | |
3294 } | |
3295 | |
3296 function dragMove(d,i) { | |
3297 index.x = d3.event.x; | |
3298 index.i = Math.round(dx.invert(index.x)); | |
3299 updateZero(); | |
3300 } | |
3301 | |
3302 function dragEnd(d,i) { | |
3303 d3.select(chart.container) | |
3304 .style('cursor', 'auto'); | |
3305 | |
3306 // update state and send stateChange with new index | |
3307 state.index = index.i; | |
3308 dispatch.stateChange(state); | |
3309 } | |
3310 | |
3311 // Display No Data message if there's nothing to show. | |
3312 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
3313 nv.utils.noData(chart, container) | |
3314 return chart; | |
3315 } else { | |
3316 container.selectAll('.nv-noData').remove(); | |
3317 } | |
3318 | |
3319 // Setup Scales | |
3320 x = lines.xScale(); | |
3321 y = lines.yScale(); | |
3322 | |
3323 if (!rescaleY) { | |
3324 var seriesDomains = data | |
3325 .filter(function(series) { return !series.disabled }) | |
3326 .map(function(series,i) { | |
3327 var initialDomain = d3.extent(series.values, lines.y()); | |
3328 | |
3329 //account for series being disabled when losing 95% or more | |
3330 if (initialDomain[0] < -.95) initialDomain[0] = -.95; | |
3331 | |
3332 return [ | |
3333 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]), | |
3334 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0]) | |
3335 ]; | |
3336 }); | |
3337 | |
3338 var completeDomain = [ | |
3339 d3.min(seriesDomains, function(d) { return d[0] }), | |
3340 d3.max(seriesDomains, function(d) { return d[1] }) | |
3341 ]; | |
3342 | |
3343 lines.yDomain(completeDomain); | |
3344 } else { | |
3345 lines.yDomain(null); | |
3346 } | |
3347 | |
3348 dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length | |
3349 .range([0, availableWidth]) | |
3350 .clamp(true); | |
3351 | |
3352 var data = indexify(index.i, data); | |
3353 | |
3354 // Setup containers and skeleton of chart | |
3355 var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all"; | |
3356 var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]); | |
3357 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g'); | |
3358 var g = wrap.select('g'); | |
3359 | |
3360 gEnter.append('g').attr('class', 'nv-interactive'); | |
3361 gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none"); | |
3362 gEnter.append('g').attr('class', 'nv-y nv-axis'); | |
3363 gEnter.append('g').attr('class', 'nv-background'); | |
3364 gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents); | |
3365 gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none"); | |
3366 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
3367 gEnter.append('g').attr('class', 'nv-controlsWrap'); | |
3368 | |
3369 // Legend | |
3370 if (showLegend) { | |
3371 legend.width(availableWidth); | |
3372 | |
3373 g.select('.nv-legendWrap') | |
3374 .datum(data) | |
3375 .call(legend); | |
3376 | |
3377 if ( margin.top != legend.height()) { | |
3378 margin.top = legend.height(); | |
3379 availableHeight = nv.utils.availableHeight(height, container, margin); | |
3380 } | |
3381 | |
3382 g.select('.nv-legendWrap') | |
3383 .attr('transform', 'translate(0,' + (-margin.top) +')') | |
3384 } | |
3385 | |
3386 // Controls | |
3387 if (showControls) { | |
3388 var controlsData = [ | |
3389 { key: 'Re-scale y-axis', disabled: !rescaleY } | |
3390 ]; | |
3391 | |
3392 controls | |
3393 .width(140) | |
3394 .color(['#444', '#444', '#444']) | |
3395 .rightAlign(false) | |
3396 .margin({top: 5, right: 0, bottom: 5, left: 20}) | |
3397 ; | |
3398 | |
3399 g.select('.nv-controlsWrap') | |
3400 .datum(controlsData) | |
3401 .attr('transform', 'translate(0,' + (-margin.top) +')') | |
3402 .call(controls); | |
3403 } | |
3404 | |
3405 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
3406 | |
3407 if (rightAlignYAxis) { | |
3408 g.select(".nv-y.nv-axis") | |
3409 .attr("transform", "translate(" + availableWidth + ",0)"); | |
3410 } | |
3411 | |
3412 // Show error if series goes below 100% | |
3413 var tempDisabled = data.filter(function(d) { return d.tempDisabled }); | |
3414 | |
3415 wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates | |
3416 if (tempDisabled.length) { | |
3417 wrap.append('text').attr('class', 'tempDisabled') | |
3418 .attr('x', availableWidth / 2) | |
3419 .attr('y', '-.71em') | |
3420 .style('text-anchor', 'end') | |
3421 .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.'); | |
3422 } | |
3423 | |
3424 //Set up interactive layer | |
3425 if (useInteractiveGuideline) { | |
3426 interactiveLayer | |
3427 .width(availableWidth) | |
3428 .height(availableHeight) | |
3429 .margin({left:margin.left,top:margin.top}) | |
3430 .svgContainer(container) | |
3431 .xScale(x); | |
3432 wrap.select(".nv-interactive").call(interactiveLayer); | |
3433 } | |
3434 | |
3435 gEnter.select('.nv-background') | |
3436 .append('rect'); | |
3437 | |
3438 g.select('.nv-background rect') | |
3439 .attr('width', availableWidth) | |
3440 .attr('height', availableHeight); | |
3441 | |
3442 lines | |
3443 //.x(function(d) { return d.x }) | |
3444 .y(function(d) { return d.display.y }) | |
3445 .width(availableWidth) | |
3446 .height(availableHeight) | |
3447 .color(data.map(function(d,i) { | |
3448 return d.color || color(d, i); | |
3449 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; })); | |
3450 | |
3451 var linesWrap = g.select('.nv-linesWrap') | |
3452 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled })); | |
3453 | |
3454 linesWrap.call(lines); | |
3455 | |
3456 //Store a series index number in the data array. | |
3457 data.forEach(function(d,i) { | |
3458 d.seriesIndex = i; | |
3459 }); | |
3460 | |
3461 var avgLineData = data.filter(function(d) { | |
3462 return !d.disabled && !!average(d); | |
3463 }); | |
3464 | |
3465 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line") | |
3466 .data(avgLineData, function(d) { return d.key; }); | |
3467 | |
3468 var getAvgLineY = function(d) { | |
3469 //If average lines go off the svg element, clamp them to the svg bounds. | |
3470 var yVal = y(average(d)); | |
3471 if (yVal < 0) return 0; | |
3472 if (yVal > availableHeight) return availableHeight; | |
3473 return yVal; | |
3474 }; | |
3475 | |
3476 avgLines.enter() | |
3477 .append('line') | |
3478 .style('stroke-width',2) | |
3479 .style('stroke-dasharray','10,10') | |
3480 .style('stroke',function (d,i) { | |
3481 return lines.color()(d,d.seriesIndex); | |
3482 }) | |
3483 .attr('x1',0) | |
3484 .attr('x2',availableWidth) | |
3485 .attr('y1', getAvgLineY) | |
3486 .attr('y2', getAvgLineY); | |
3487 | |
3488 avgLines | |
3489 .style('stroke-opacity',function(d){ | |
3490 //If average lines go offscreen, make them transparent | |
3491 var yVal = y(average(d)); | |
3492 if (yVal < 0 || yVal > availableHeight) return 0; | |
3493 return 1; | |
3494 }) | |
3495 .attr('x1',0) | |
3496 .attr('x2',availableWidth) | |
3497 .attr('y1', getAvgLineY) | |
3498 .attr('y2', getAvgLineY); | |
3499 | |
3500 avgLines.exit().remove(); | |
3501 | |
3502 //Create index line | |
3503 var indexLine = linesWrap.selectAll('.nv-indexLine') | |
3504 .data([index]); | |
3505 indexLine.enter().append('rect').attr('class', 'nv-indexLine') | |
3506 .attr('width', 3) | |
3507 .attr('x', -2) | |
3508 .attr('fill', 'red') | |
3509 .attr('fill-opacity', .5) | |
3510 .style("pointer-events","all") | |
3511 .call(indexDrag); | |
3512 | |
3513 indexLine | |
3514 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' }) | |
3515 .attr('height', availableHeight); | |
3516 | |
3517 // Setup Axes | |
3518 if (showXAxis) { | |
3519 xAxis | |
3520 .scale(x) | |
3521 ._ticks( nv.utils.calcTicksX(availableWidth/70, data) ) | |
3522 .tickSize(-availableHeight, 0); | |
3523 | |
3524 g.select('.nv-x.nv-axis') | |
3525 .attr('transform', 'translate(0,' + y.range()[0] + ')'); | |
3526 g.select('.nv-x.nv-axis') | |
3527 .call(xAxis); | |
3528 } | |
3529 | |
3530 if (showYAxis) { | |
3531 yAxis | |
3532 .scale(y) | |
3533 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) | |
3534 .tickSize( -availableWidth, 0); | |
3535 | |
3536 g.select('.nv-y.nv-axis') | |
3537 .call(yAxis); | |
3538 } | |
3539 | |
3540 //============================================================ | |
3541 // Event Handling/Dispatching (in chart's scope) | |
3542 //------------------------------------------------------------ | |
3543 | |
3544 function updateZero() { | |
3545 indexLine | |
3546 .data([index]); | |
3547 | |
3548 //When dragging the index line, turn off line transitions. | |
3549 // Then turn them back on when done dragging. | |
3550 var oldDuration = chart.duration(); | |
3551 chart.duration(0); | |
3552 chart.update(); | |
3553 chart.duration(oldDuration); | |
3554 } | |
3555 | |
3556 g.select('.nv-background rect') | |
3557 .on('click', function() { | |
3558 index.x = d3.mouse(this)[0]; | |
3559 index.i = Math.round(dx.invert(index.x)); | |
3560 | |
3561 // update state and send stateChange with new index | |
3562 state.index = index.i; | |
3563 dispatch.stateChange(state); | |
3564 | |
3565 updateZero(); | |
3566 }); | |
3567 | |
3568 lines.dispatch.on('elementClick', function(e) { | |
3569 index.i = e.pointIndex; | |
3570 index.x = dx(index.i); | |
3571 | |
3572 // update state and send stateChange with new index | |
3573 state.index = index.i; | |
3574 dispatch.stateChange(state); | |
3575 | |
3576 updateZero(); | |
3577 }); | |
3578 | |
3579 controls.dispatch.on('legendClick', function(d,i) { | |
3580 d.disabled = !d.disabled; | |
3581 rescaleY = !d.disabled; | |
3582 | |
3583 state.rescaleY = rescaleY; | |
3584 dispatch.stateChange(state); | |
3585 chart.update(); | |
3586 }); | |
3587 | |
3588 legend.dispatch.on('stateChange', function(newState) { | |
3589 for (var key in newState) | |
3590 state[key] = newState[key]; | |
3591 dispatch.stateChange(state); | |
3592 chart.update(); | |
3593 }); | |
3594 | |
3595 interactiveLayer.dispatch.on('elementMousemove', function(e) { | |
3596 lines.clearHighlights(); | |
3597 var singlePoint, pointIndex, pointXLocation, allData = []; | |
3598 | |
3599 data | |
3600 .filter(function(series, i) { | |
3601 series.seriesIndex = i; | |
3602 return !series.disabled; | |
3603 }) | |
3604 .forEach(function(series,i) { | |
3605 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); | |
3606 lines.highlightPoint(i, pointIndex, true); | |
3607 var point = series.values[pointIndex]; | |
3608 if (typeof point === 'undefined') return; | |
3609 if (typeof singlePoint === 'undefined') singlePoint = point; | |
3610 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); | |
3611 allData.push({ | |
3612 key: series.key, | |
3613 value: chart.y()(point, pointIndex), | |
3614 color: color(series,series.seriesIndex) | |
3615 }); | |
3616 }); | |
3617 | |
3618 //Highlight the tooltip entry based on which point the mouse is closest to. | |
3619 if (allData.length > 2) { | |
3620 var yValue = chart.yScale().invert(e.mouseY); | |
3621 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); | |
3622 var threshold = 0.03 * domainExtent; | |
3623 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); | |
3624 if (indexToHighlight !== null) | |
3625 allData[indexToHighlight].highlight = true; | |
3626 } | |
3627 | |
3628 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex); | |
3629 interactiveLayer.tooltip | |
3630 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) | |
3631 .chartContainer(that.parentNode) | |
3632 .valueFormatter(function(d,i) { | |
3633 return yAxis.tickFormat()(d); | |
3634 }) | |
3635 .data( | |
3636 { | |
3637 value: xValue, | |
3638 series: allData | |
3639 } | |
3640 )(); | |
3641 | |
3642 interactiveLayer.renderGuideLine(pointXLocation); | |
3643 }); | |
3644 | |
3645 interactiveLayer.dispatch.on("elementMouseout",function(e) { | |
3646 lines.clearHighlights(); | |
3647 }); | |
3648 | |
3649 // Update chart from a state object passed to event handler | |
3650 dispatch.on('changeState', function(e) { | |
3651 if (typeof e.disabled !== 'undefined') { | |
3652 data.forEach(function(series,i) { | |
3653 series.disabled = e.disabled[i]; | |
3654 }); | |
3655 | |
3656 state.disabled = e.disabled; | |
3657 } | |
3658 | |
3659 if (typeof e.index !== 'undefined') { | |
3660 index.i = e.index; | |
3661 index.x = dx(index.i); | |
3662 | |
3663 state.index = e.index; | |
3664 | |
3665 indexLine | |
3666 .data([index]); | |
3667 } | |
3668 | |
3669 if (typeof e.rescaleY !== 'undefined') { | |
3670 rescaleY = e.rescaleY; | |
3671 } | |
3672 | |
3673 chart.update(); | |
3674 }); | |
3675 | |
3676 }); | |
3677 | |
3678 renderWatch.renderEnd('cumulativeLineChart immediate'); | |
3679 | |
3680 return chart; | |
3681 } | |
3682 | |
3683 //============================================================ | |
3684 // Event Handling/Dispatching (out of chart's scope) | |
3685 //------------------------------------------------------------ | |
3686 | |
3687 lines.dispatch.on('elementMouseover.tooltip', function(evt) { | |
3688 var point = { | |
3689 x: chart.x()(evt.point), | |
3690 y: chart.y()(evt.point), | |
3691 color: evt.point.color | |
3692 }; | |
3693 evt.point = point; | |
3694 tooltip.data(evt).position(evt.pos).hidden(false); | |
3695 }); | |
3696 | |
3697 lines.dispatch.on('elementMouseout.tooltip', function(evt) { | |
3698 tooltip.hidden(true) | |
3699 }); | |
3700 | |
3701 //============================================================ | |
3702 // Functions | |
3703 //------------------------------------------------------------ | |
3704 | |
3705 var indexifyYGetter = null; | |
3706 /* Normalize the data according to an index point. */ | |
3707 function indexify(idx, data) { | |
3708 if (!indexifyYGetter) indexifyYGetter = lines.y(); | |
3709 return data.map(function(line, i) { | |
3710 if (!line.values) { | |
3711 return line; | |
3712 } | |
3713 var indexValue = line.values[idx]; | |
3714 if (indexValue == null) { | |
3715 return line; | |
3716 } | |
3717 var v = indexifyYGetter(indexValue, idx); | |
3718 | |
3719 //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue | |
3720 if (v < -.95 && !noErrorCheck) { | |
3721 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100) | |
3722 | |
3723 line.tempDisabled = true; | |
3724 return line; | |
3725 } | |
3726 | |
3727 line.tempDisabled = false; | |
3728 | |
3729 line.values = line.values.map(function(point, pointIndex) { | |
3730 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) }; | |
3731 return point; | |
3732 }); | |
3733 | |
3734 return line; | |
3735 }) | |
3736 } | |
3737 | |
3738 //============================================================ | |
3739 // Expose Public Variables | |
3740 //------------------------------------------------------------ | |
3741 | |
3742 // expose chart's sub-components | |
3743 chart.dispatch = dispatch; | |
3744 chart.lines = lines; | |
3745 chart.legend = legend; | |
3746 chart.controls = controls; | |
3747 chart.xAxis = xAxis; | |
3748 chart.yAxis = yAxis; | |
3749 chart.interactiveLayer = interactiveLayer; | |
3750 chart.state = state; | |
3751 chart.tooltip = tooltip; | |
3752 | |
3753 chart.options = nv.utils.optionsFunc.bind(chart); | |
3754 | |
3755 chart._options = Object.create({}, { | |
3756 // simple options, just get/set the necessary values | |
3757 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
3758 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
3759 rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}}, | |
3760 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, | |
3761 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
3762 average: {get: function(){return average;}, set: function(_){average=_;}}, | |
3763 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
3764 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
3765 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
3766 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
3767 noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}}, | |
3768 | |
3769 // deprecated options | |
3770 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
3771 // deprecated after 1.7.1 | |
3772 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
3773 tooltip.enabled(!!_); | |
3774 }}, | |
3775 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
3776 // deprecated after 1.7.1 | |
3777 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
3778 tooltip.contentGenerator(_); | |
3779 }}, | |
3780 | |
3781 // options that require extra logic in the setter | |
3782 margin: {get: function(){return margin;}, set: function(_){ | |
3783 margin.top = _.top !== undefined ? _.top : margin.top; | |
3784 margin.right = _.right !== undefined ? _.right : margin.right; | |
3785 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
3786 margin.left = _.left !== undefined ? _.left : margin.left; | |
3787 }}, | |
3788 color: {get: function(){return color;}, set: function(_){ | |
3789 color = nv.utils.getColor(_); | |
3790 legend.color(color); | |
3791 }}, | |
3792 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ | |
3793 useInteractiveGuideline = _; | |
3794 if (_ === true) { | |
3795 chart.interactive(false); | |
3796 chart.useVoronoi(false); | |
3797 } | |
3798 }}, | |
3799 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ | |
3800 rightAlignYAxis = _; | |
3801 yAxis.orient( (_) ? 'right' : 'left'); | |
3802 }}, | |
3803 duration: {get: function(){return duration;}, set: function(_){ | |
3804 duration = _; | |
3805 lines.duration(duration); | |
3806 xAxis.duration(duration); | |
3807 yAxis.duration(duration); | |
3808 renderWatch.reset(duration); | |
3809 }} | |
3810 }); | |
3811 | |
3812 nv.utils.inheritOptions(chart, lines); | |
3813 nv.utils.initOptions(chart); | |
3814 | |
3815 return chart; | |
3816 }; | |
3817 //TODO: consider deprecating by adding necessary features to multiBar model | |
3818 nv.models.discreteBar = function() { | |
3819 "use strict"; | |
3820 | |
3821 //============================================================ | |
3822 // Public Variables with Default Settings | |
3823 //------------------------------------------------------------ | |
3824 | |
3825 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
3826 , width = 960 | |
3827 , height = 500 | |
3828 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
3829 , container | |
3830 , x = d3.scale.ordinal() | |
3831 , y = d3.scale.linear() | |
3832 , getX = function(d) { return d.x } | |
3833 , getY = function(d) { return d.y } | |
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 | |
3835 , color = nv.utils.defaultColor() | |
3836 , showValues = false | |
3837 , valueFormat = d3.format(',.2f') | |
3838 , xDomain | |
3839 , yDomain | |
3840 , xRange | |
3841 , yRange | |
3842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') | |
3843 , rectClass = 'discreteBar' | |
3844 , duration = 250 | |
3845 ; | |
3846 | |
3847 //============================================================ | |
3848 // Private Variables | |
3849 //------------------------------------------------------------ | |
3850 | |
3851 var x0, y0; | |
3852 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
3853 | |
3854 function chart(selection) { | |
3855 renderWatch.reset(); | |
3856 selection.each(function(data) { | |
3857 var availableWidth = width - margin.left - margin.right, | |
3858 availableHeight = height - margin.top - margin.bottom; | |
3859 | |
3860 container = d3.select(this); | |
3861 nv.utils.initSVG(container); | |
3862 | |
3863 //add series index to each data point for reference | |
3864 data.forEach(function(series, i) { | |
3865 series.values.forEach(function(point) { | |
3866 point.series = i; | |
3867 }); | |
3868 }); | |
3869 | |
3870 // Setup Scales | |
3871 // remap and flatten the data for use in calculating the scales' domains | |
3872 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate | |
3873 data.map(function(d) { | |
3874 return d.values.map(function(d,i) { | |
3875 return { x: getX(d,i), y: getY(d,i), y0: d.y0 } | |
3876 }) | |
3877 }); | |
3878 | |
3879 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) | |
3880 .rangeBands(xRange || [0, availableWidth], .1); | |
3881 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY))); | |
3882 | |
3883 // If showValues, pad the Y axis range to account for label height | |
3884 if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]); | |
3885 else y.range(yRange || [availableHeight, 0]); | |
3886 | |
3887 //store old scales if they exist | |
3888 x0 = x0 || x; | |
3889 y0 = y0 || y.copy().range([y(0),y(0)]); | |
3890 | |
3891 // Setup containers and skeleton of chart | |
3892 var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]); | |
3893 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar'); | |
3894 var gEnter = wrapEnter.append('g'); | |
3895 var g = wrap.select('g'); | |
3896 | |
3897 gEnter.append('g').attr('class', 'nv-groups'); | |
3898 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
3899 | |
3900 //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later | |
3901 var groups = wrap.select('.nv-groups').selectAll('.nv-group') | |
3902 .data(function(d) { return d }, function(d) { return d.key }); | |
3903 groups.enter().append('g') | |
3904 .style('stroke-opacity', 1e-6) | |
3905 .style('fill-opacity', 1e-6); | |
3906 groups.exit() | |
3907 .watchTransition(renderWatch, 'discreteBar: exit groups') | |
3908 .style('stroke-opacity', 1e-6) | |
3909 .style('fill-opacity', 1e-6) | |
3910 .remove(); | |
3911 groups | |
3912 .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) | |
3913 .classed('hover', function(d) { return d.hover }); | |
3914 groups | |
3915 .watchTransition(renderWatch, 'discreteBar: groups') | |
3916 .style('stroke-opacity', 1) | |
3917 .style('fill-opacity', .75); | |
3918 | |
3919 var bars = groups.selectAll('g.nv-bar') | |
3920 .data(function(d) { return d.values }); | |
3921 bars.exit().remove(); | |
3922 | |
3923 var barsEnter = bars.enter().append('g') | |
3924 .attr('transform', function(d,i,j) { | |
3925 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')' | |
3926 }) | |
3927 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here | |
3928 d3.select(this).classed('hover', true); | |
3929 dispatch.elementMouseover({ | |
3930 data: d, | |
3931 index: i, | |
3932 color: d3.select(this).style("fill") | |
3933 }); | |
3934 }) | |
3935 .on('mouseout', function(d,i) { | |
3936 d3.select(this).classed('hover', false); | |
3937 dispatch.elementMouseout({ | |
3938 data: d, | |
3939 index: i, | |
3940 color: d3.select(this).style("fill") | |
3941 }); | |
3942 }) | |
3943 .on('mousemove', function(d,i) { | |
3944 dispatch.elementMousemove({ | |
3945 data: d, | |
3946 index: i, | |
3947 color: d3.select(this).style("fill") | |
3948 }); | |
3949 }) | |
3950 .on('click', function(d,i) { | |
3951 dispatch.elementClick({ | |
3952 data: d, | |
3953 index: i, | |
3954 color: d3.select(this).style("fill") | |
3955 }); | |
3956 d3.event.stopPropagation(); | |
3957 }) | |
3958 .on('dblclick', function(d,i) { | |
3959 dispatch.elementDblClick({ | |
3960 data: d, | |
3961 index: i, | |
3962 color: d3.select(this).style("fill") | |
3963 }); | |
3964 d3.event.stopPropagation(); | |
3965 }); | |
3966 | |
3967 barsEnter.append('rect') | |
3968 .attr('height', 0) | |
3969 .attr('width', x.rangeBand() * .9 / data.length ) | |
3970 | |
3971 if (showValues) { | |
3972 barsEnter.append('text') | |
3973 .attr('text-anchor', 'middle') | |
3974 ; | |
3975 | |
3976 bars.select('text') | |
3977 .text(function(d,i) { return valueFormat(getY(d,i)) }) | |
3978 .watchTransition(renderWatch, 'discreteBar: bars text') | |
3979 .attr('x', x.rangeBand() * .9 / 2) | |
3980 .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 }) | |
3981 | |
3982 ; | |
3983 } else { | |
3984 bars.selectAll('text').remove(); | |
3985 } | |
3986 | |
3987 bars | |
3988 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' }) | |
3989 .style('fill', function(d,i) { return d.color || color(d,i) }) | |
3990 .style('stroke', function(d,i) { return d.color || color(d,i) }) | |
3991 .select('rect') | |
3992 .attr('class', rectClass) | |
3993 .watchTransition(renderWatch, 'discreteBar: bars rect') | |
3994 .attr('width', x.rangeBand() * .9 / data.length); | |
3995 bars.watchTransition(renderWatch, 'discreteBar: bars') | |
3996 //.delay(function(d,i) { return i * 1200 / data[0].values.length }) | |
3997 .attr('transform', function(d,i) { | |
3998 var left = x(getX(d,i)) + x.rangeBand() * .05, | |
3999 top = getY(d,i) < 0 ? | |
4000 y(0) : | |
4001 y(0) - y(getY(d,i)) < 1 ? | |
4002 y(0) - 1 : //make 1 px positive bars show up above y=0 | |
4003 y(getY(d,i)); | |
4004 | |
4005 return 'translate(' + left + ', ' + top + ')' | |
4006 }) | |
4007 .select('rect') | |
4008 .attr('height', function(d,i) { | |
4009 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1) | |
4010 }); | |
4011 | |
4012 | |
4013 //store old scales for use in transitions on update | |
4014 x0 = x.copy(); | |
4015 y0 = y.copy(); | |
4016 | |
4017 }); | |
4018 | |
4019 renderWatch.renderEnd('discreteBar immediate'); | |
4020 return chart; | |
4021 } | |
4022 | |
4023 //============================================================ | |
4024 // Expose Public Variables | |
4025 //------------------------------------------------------------ | |
4026 | |
4027 chart.dispatch = dispatch; | |
4028 chart.options = nv.utils.optionsFunc.bind(chart); | |
4029 | |
4030 chart._options = Object.create({}, { | |
4031 // simple options, just get/set the necessary values | |
4032 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
4033 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
4034 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, | |
4035 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}}, | |
4036 x: {get: function(){return getX;}, set: function(_){getX=_;}}, | |
4037 y: {get: function(){return getY;}, set: function(_){getY=_;}}, | |
4038 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
4039 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
4040 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
4041 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
4042 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
4043 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
4044 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, | |
4045 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
4046 rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}}, | |
4047 | |
4048 // options that require extra logic in the setter | |
4049 margin: {get: function(){return margin;}, set: function(_){ | |
4050 margin.top = _.top !== undefined ? _.top : margin.top; | |
4051 margin.right = _.right !== undefined ? _.right : margin.right; | |
4052 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
4053 margin.left = _.left !== undefined ? _.left : margin.left; | |
4054 }}, | |
4055 color: {get: function(){return color;}, set: function(_){ | |
4056 color = nv.utils.getColor(_); | |
4057 }}, | |
4058 duration: {get: function(){return duration;}, set: function(_){ | |
4059 duration = _; | |
4060 renderWatch.reset(duration); | |
4061 }} | |
4062 }); | |
4063 | |
4064 nv.utils.initOptions(chart); | |
4065 | |
4066 return chart; | |
4067 }; | |
4068 | |
4069 nv.models.discreteBarChart = function() { | |
4070 "use strict"; | |
4071 | |
4072 //============================================================ | |
4073 // Public Variables with Default Settings | |
4074 //------------------------------------------------------------ | |
4075 | |
4076 var discretebar = nv.models.discreteBar() | |
4077 , xAxis = nv.models.axis() | |
4078 , yAxis = nv.models.axis() | |
4079 , tooltip = nv.models.tooltip() | |
4080 ; | |
4081 | |
4082 var margin = {top: 15, right: 10, bottom: 50, left: 60} | |
4083 , width = null | |
4084 , height = null | |
4085 , color = nv.utils.getColor() | |
4086 , showXAxis = true | |
4087 , showYAxis = true | |
4088 , rightAlignYAxis = false | |
4089 , staggerLabels = false | |
4090 , x | |
4091 , y | |
4092 , noData = null | |
4093 , dispatch = d3.dispatch('beforeUpdate','renderEnd') | |
4094 , duration = 250 | |
4095 ; | |
4096 | |
4097 xAxis | |
4098 .orient('bottom') | |
4099 .showMaxMin(false) | |
4100 .tickFormat(function(d) { return d }) | |
4101 ; | |
4102 yAxis | |
4103 .orient((rightAlignYAxis) ? 'right' : 'left') | |
4104 .tickFormat(d3.format(',.1f')) | |
4105 ; | |
4106 | |
4107 tooltip | |
4108 .duration(0) | |
4109 .headerEnabled(false) | |
4110 .valueFormatter(function(d, i) { | |
4111 return yAxis.tickFormat()(d, i); | |
4112 }) | |
4113 .keyFormatter(function(d, i) { | |
4114 return xAxis.tickFormat()(d, i); | |
4115 }); | |
4116 | |
4117 //============================================================ | |
4118 // Private Variables | |
4119 //------------------------------------------------------------ | |
4120 | |
4121 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
4122 | |
4123 function chart(selection) { | |
4124 renderWatch.reset(); | |
4125 renderWatch.models(discretebar); | |
4126 if (showXAxis) renderWatch.models(xAxis); | |
4127 if (showYAxis) renderWatch.models(yAxis); | |
4128 | |
4129 selection.each(function(data) { | |
4130 var container = d3.select(this), | |
4131 that = this; | |
4132 nv.utils.initSVG(container); | |
4133 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
4134 availableHeight = nv.utils.availableHeight(height, container, margin); | |
4135 | |
4136 chart.update = function() { | |
4137 dispatch.beforeUpdate(); | |
4138 container.transition().duration(duration).call(chart); | |
4139 }; | |
4140 chart.container = this; | |
4141 | |
4142 // Display No Data message if there's nothing to show. | |
4143 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
4144 nv.utils.noData(chart, container); | |
4145 return chart; | |
4146 } else { | |
4147 container.selectAll('.nv-noData').remove(); | |
4148 } | |
4149 | |
4150 // Setup Scales | |
4151 x = discretebar.xScale(); | |
4152 y = discretebar.yScale().clamp(true); | |
4153 | |
4154 // Setup containers and skeleton of chart | |
4155 var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]); | |
4156 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g'); | |
4157 var defsEnter = gEnter.append('defs'); | |
4158 var g = wrap.select('g'); | |
4159 | |
4160 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
4161 gEnter.append('g').attr('class', 'nv-y nv-axis') | |
4162 .append('g').attr('class', 'nv-zeroLine') | |
4163 .append('line'); | |
4164 | |
4165 gEnter.append('g').attr('class', 'nv-barsWrap'); | |
4166 | |
4167 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
4168 | |
4169 if (rightAlignYAxis) { | |
4170 g.select(".nv-y.nv-axis") | |
4171 .attr("transform", "translate(" + availableWidth + ",0)"); | |
4172 } | |
4173 | |
4174 // Main Chart Component(s) | |
4175 discretebar | |
4176 .width(availableWidth) | |
4177 .height(availableHeight); | |
4178 | |
4179 var barsWrap = g.select('.nv-barsWrap') | |
4180 .datum(data.filter(function(d) { return !d.disabled })); | |
4181 | |
4182 barsWrap.transition().call(discretebar); | |
4183 | |
4184 | |
4185 defsEnter.append('clipPath') | |
4186 .attr('id', 'nv-x-label-clip-' + discretebar.id()) | |
4187 .append('rect'); | |
4188 | |
4189 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect') | |
4190 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1)) | |
4191 .attr('height', 16) | |
4192 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 )); | |
4193 | |
4194 // Setup Axes | |
4195 if (showXAxis) { | |
4196 xAxis | |
4197 .scale(x) | |
4198 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
4199 .tickSize(-availableHeight, 0); | |
4200 | |
4201 g.select('.nv-x.nv-axis') | |
4202 .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')'); | |
4203 g.select('.nv-x.nv-axis').call(xAxis); | |
4204 | |
4205 var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); | |
4206 if (staggerLabels) { | |
4207 xTicks | |
4208 .selectAll('text') | |
4209 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' }) | |
4210 } | |
4211 } | |
4212 | |
4213 if (showYAxis) { | |
4214 yAxis | |
4215 .scale(y) | |
4216 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) | |
4217 .tickSize( -availableWidth, 0); | |
4218 | |
4219 g.select('.nv-y.nv-axis').call(yAxis); | |
4220 } | |
4221 | |
4222 // Zero line | |
4223 g.select(".nv-zeroLine line") | |
4224 .attr("x1",0) | |
4225 .attr("x2",availableWidth) | |
4226 .attr("y1", y(0)) | |
4227 .attr("y2", y(0)) | |
4228 ; | |
4229 }); | |
4230 | |
4231 renderWatch.renderEnd('discreteBar chart immediate'); | |
4232 return chart; | |
4233 } | |
4234 | |
4235 //============================================================ | |
4236 // Event Handling/Dispatching (out of chart's scope) | |
4237 //------------------------------------------------------------ | |
4238 | |
4239 discretebar.dispatch.on('elementMouseover.tooltip', function(evt) { | |
4240 evt['series'] = { | |
4241 key: chart.x()(evt.data), | |
4242 value: chart.y()(evt.data), | |
4243 color: evt.color | |
4244 }; | |
4245 tooltip.data(evt).hidden(false); | |
4246 }); | |
4247 | |
4248 discretebar.dispatch.on('elementMouseout.tooltip', function(evt) { | |
4249 tooltip.hidden(true); | |
4250 }); | |
4251 | |
4252 discretebar.dispatch.on('elementMousemove.tooltip', function(evt) { | |
4253 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
4254 }); | |
4255 | |
4256 //============================================================ | |
4257 // Expose Public Variables | |
4258 //------------------------------------------------------------ | |
4259 | |
4260 chart.dispatch = dispatch; | |
4261 chart.discretebar = discretebar; | |
4262 chart.xAxis = xAxis; | |
4263 chart.yAxis = yAxis; | |
4264 chart.tooltip = tooltip; | |
4265 | |
4266 chart.options = nv.utils.optionsFunc.bind(chart); | |
4267 | |
4268 chart._options = Object.create({}, { | |
4269 // simple options, just get/set the necessary values | |
4270 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
4271 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
4272 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, | |
4273 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
4274 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
4275 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
4276 | |
4277 // deprecated options | |
4278 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
4279 // deprecated after 1.7.1 | |
4280 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
4281 tooltip.enabled(!!_); | |
4282 }}, | |
4283 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
4284 // deprecated after 1.7.1 | |
4285 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
4286 tooltip.contentGenerator(_); | |
4287 }}, | |
4288 | |
4289 // options that require extra logic in the setter | |
4290 margin: {get: function(){return margin;}, set: function(_){ | |
4291 margin.top = _.top !== undefined ? _.top : margin.top; | |
4292 margin.right = _.right !== undefined ? _.right : margin.right; | |
4293 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
4294 margin.left = _.left !== undefined ? _.left : margin.left; | |
4295 }}, | |
4296 duration: {get: function(){return duration;}, set: function(_){ | |
4297 duration = _; | |
4298 renderWatch.reset(duration); | |
4299 discretebar.duration(duration); | |
4300 xAxis.duration(duration); | |
4301 yAxis.duration(duration); | |
4302 }}, | |
4303 color: {get: function(){return color;}, set: function(_){ | |
4304 color = nv.utils.getColor(_); | |
4305 discretebar.color(color); | |
4306 }}, | |
4307 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ | |
4308 rightAlignYAxis = _; | |
4309 yAxis.orient( (_) ? 'right' : 'left'); | |
4310 }} | |
4311 }); | |
4312 | |
4313 nv.utils.inheritOptions(chart, discretebar); | |
4314 nv.utils.initOptions(chart); | |
4315 | |
4316 return chart; | |
4317 } | |
4318 | |
4319 nv.models.distribution = function() { | |
4320 "use strict"; | |
4321 //============================================================ | |
4322 // Public Variables with Default Settings | |
4323 //------------------------------------------------------------ | |
4324 | |
4325 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
4326 , width = 400 //technically width or height depending on x or y.... | |
4327 , size = 8 | |
4328 , axis = 'x' // 'x' or 'y'... horizontal or vertical | |
4329 , getData = function(d) { return d[axis] } // defaults d.x or d.y | |
4330 , color = nv.utils.defaultColor() | |
4331 , scale = d3.scale.linear() | |
4332 , domain | |
4333 , duration = 250 | |
4334 , dispatch = d3.dispatch('renderEnd') | |
4335 ; | |
4336 | |
4337 //============================================================ | |
4338 | |
4339 | |
4340 //============================================================ | |
4341 // Private Variables | |
4342 //------------------------------------------------------------ | |
4343 | |
4344 var scale0; | |
4345 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
4346 | |
4347 //============================================================ | |
4348 | |
4349 | |
4350 function chart(selection) { | |
4351 renderWatch.reset(); | |
4352 selection.each(function(data) { | |
4353 var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom), | |
4354 naxis = axis == 'x' ? 'y' : 'x', | |
4355 container = d3.select(this); | |
4356 nv.utils.initSVG(container); | |
4357 | |
4358 //------------------------------------------------------------ | |
4359 // Setup Scales | |
4360 | |
4361 scale0 = scale0 || scale; | |
4362 | |
4363 //------------------------------------------------------------ | |
4364 | |
4365 | |
4366 //------------------------------------------------------------ | |
4367 // Setup containers and skeleton of chart | |
4368 | |
4369 var wrap = container.selectAll('g.nv-distribution').data([data]); | |
4370 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution'); | |
4371 var gEnter = wrapEnter.append('g'); | |
4372 var g = wrap.select('g'); | |
4373 | |
4374 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') | |
4375 | |
4376 //------------------------------------------------------------ | |
4377 | |
4378 | |
4379 var distWrap = g.selectAll('g.nv-dist') | |
4380 .data(function(d) { return d }, function(d) { return d.key }); | |
4381 | |
4382 distWrap.enter().append('g'); | |
4383 distWrap | |
4384 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i }) | |
4385 .style('stroke', function(d,i) { return color(d, i) }); | |
4386 | |
4387 var dist = distWrap.selectAll('line.nv-dist' + axis) | |
4388 .data(function(d) { return d.values }) | |
4389 dist.enter().append('line') | |
4390 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) }) | |
4391 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) }) | |
4392 renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit') | |
4393 // .transition() | |
4394 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) | |
4395 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) | |
4396 .style('stroke-opacity', 0) | |
4397 .remove(); | |
4398 dist | |
4399 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i }) | |
4400 .attr(naxis + '1', 0) | |
4401 .attr(naxis + '2', size); | |
4402 renderWatch.transition(dist, 'dist') | |
4403 // .transition() | |
4404 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) }) | |
4405 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) }) | |
4406 | |
4407 | |
4408 scale0 = scale.copy(); | |
4409 | |
4410 }); | |
4411 renderWatch.renderEnd('distribution immediate'); | |
4412 return chart; | |
4413 } | |
4414 | |
4415 | |
4416 //============================================================ | |
4417 // Expose Public Variables | |
4418 //------------------------------------------------------------ | |
4419 chart.options = nv.utils.optionsFunc.bind(chart); | |
4420 chart.dispatch = dispatch; | |
4421 | |
4422 chart.margin = function(_) { | |
4423 if (!arguments.length) return margin; | |
4424 margin.top = typeof _.top != 'undefined' ? _.top : margin.top; | |
4425 margin.right = typeof _.right != 'undefined' ? _.right : margin.right; | |
4426 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; | |
4427 margin.left = typeof _.left != 'undefined' ? _.left : margin.left; | |
4428 return chart; | |
4429 }; | |
4430 | |
4431 chart.width = function(_) { | |
4432 if (!arguments.length) return width; | |
4433 width = _; | |
4434 return chart; | |
4435 }; | |
4436 | |
4437 chart.axis = function(_) { | |
4438 if (!arguments.length) return axis; | |
4439 axis = _; | |
4440 return chart; | |
4441 }; | |
4442 | |
4443 chart.size = function(_) { | |
4444 if (!arguments.length) return size; | |
4445 size = _; | |
4446 return chart; | |
4447 }; | |
4448 | |
4449 chart.getData = function(_) { | |
4450 if (!arguments.length) return getData; | |
4451 getData = d3.functor(_); | |
4452 return chart; | |
4453 }; | |
4454 | |
4455 chart.scale = function(_) { | |
4456 if (!arguments.length) return scale; | |
4457 scale = _; | |
4458 return chart; | |
4459 }; | |
4460 | |
4461 chart.color = function(_) { | |
4462 if (!arguments.length) return color; | |
4463 color = nv.utils.getColor(_); | |
4464 return chart; | |
4465 }; | |
4466 | |
4467 chart.duration = function(_) { | |
4468 if (!arguments.length) return duration; | |
4469 duration = _; | |
4470 renderWatch.reset(duration); | |
4471 return chart; | |
4472 }; | |
4473 //============================================================ | |
4474 | |
4475 | |
4476 return chart; | |
4477 } | |
4478 nv.models.furiousLegend = function() { | |
4479 "use strict"; | |
4480 | |
4481 //============================================================ | |
4482 // Public Variables with Default Settings | |
4483 //------------------------------------------------------------ | |
4484 | |
4485 var margin = {top: 5, right: 0, bottom: 5, left: 0} | |
4486 , width = 400 | |
4487 , height = 20 | |
4488 , getKey = function(d) { return d.key } | |
4489 , color = nv.utils.getColor() | |
4490 , align = true | |
4491 , padding = 28 //define how much space between legend items. - recommend 32 for furious version | |
4492 , rightAlign = true | |
4493 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch. | |
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) | |
4495 , expanded = false | |
4496 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange') | |
4497 , vers = 'classic' //Options are "classic" and "furious" | |
4498 ; | |
4499 | |
4500 function chart(selection) { | |
4501 selection.each(function(data) { | |
4502 var availableWidth = width - margin.left - margin.right, | |
4503 container = d3.select(this); | |
4504 nv.utils.initSVG(container); | |
4505 | |
4506 // Setup containers and skeleton of chart | |
4507 var wrap = container.selectAll('g.nv-legend').data([data]); | |
4508 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); | |
4509 var g = wrap.select('g'); | |
4510 | |
4511 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
4512 | |
4513 var series = g.selectAll('.nv-series') | |
4514 .data(function(d) { | |
4515 if(vers != 'furious') return d; | |
4516 | |
4517 return d.filter(function(n) { | |
4518 return expanded ? true : !n.disengaged; | |
4519 }); | |
4520 }); | |
4521 var seriesEnter = series.enter().append('g').attr('class', 'nv-series') | |
4522 | |
4523 var seriesShape; | |
4524 | |
4525 if(vers == 'classic') { | |
4526 seriesEnter.append('circle') | |
4527 .style('stroke-width', 2) | |
4528 .attr('class','nv-legend-symbol') | |
4529 .attr('r', 5); | |
4530 | |
4531 seriesShape = series.select('circle'); | |
4532 } else if (vers == 'furious') { | |
4533 seriesEnter.append('rect') | |
4534 .style('stroke-width', 2) | |
4535 .attr('class','nv-legend-symbol') | |
4536 .attr('rx', 3) | |
4537 .attr('ry', 3); | |
4538 | |
4539 seriesShape = series.select('rect'); | |
4540 | |
4541 seriesEnter.append('g') | |
4542 .attr('class', 'nv-check-box') | |
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>') | |
4544 .attr('transform', 'translate(-10,-8)scale(0.5)'); | |
4545 | |
4546 var seriesCheckbox = series.select('.nv-check-box'); | |
4547 | |
4548 seriesCheckbox.each(function(d,i) { | |
4549 d3.select(this).selectAll('path') | |
4550 .attr('stroke', setTextColor(d,i)); | |
4551 }); | |
4552 } | |
4553 | |
4554 seriesEnter.append('text') | |
4555 .attr('text-anchor', 'start') | |
4556 .attr('class','nv-legend-text') | |
4557 .attr('dy', '.32em') | |
4558 .attr('dx', '8'); | |
4559 | |
4560 var seriesText = series.select('text.nv-legend-text'); | |
4561 | |
4562 series | |
4563 .on('mouseover', function(d,i) { | |
4564 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects | |
4565 }) | |
4566 .on('mouseout', function(d,i) { | |
4567 dispatch.legendMouseout(d,i); | |
4568 }) | |
4569 .on('click', function(d,i) { | |
4570 dispatch.legendClick(d,i); | |
4571 // make sure we re-get data in case it was modified | |
4572 var data = series.data(); | |
4573 if (updateState) { | |
4574 if(vers =='classic') { | |
4575 if (radioButtonMode) { | |
4576 //Radio button mode: set every series to disabled, | |
4577 // and enable the clicked series. | |
4578 data.forEach(function(series) { series.disabled = true}); | |
4579 d.disabled = false; | |
4580 } | |
4581 else { | |
4582 d.disabled = !d.disabled; | |
4583 if (data.every(function(series) { return series.disabled})) { | |
4584 //the default behavior of NVD3 legends is, if every single series | |
4585 // is disabled, turn all series' back on. | |
4586 data.forEach(function(series) { series.disabled = false}); | |
4587 } | |
4588 } | |
4589 } else if(vers == 'furious') { | |
4590 if(expanded) { | |
4591 d.disengaged = !d.disengaged; | |
4592 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled; | |
4593 d.disabled = d.disengaged || d.userDisabled; | |
4594 } else if (!expanded) { | |
4595 d.disabled = !d.disabled; | |
4596 d.userDisabled = d.disabled; | |
4597 var engaged = data.filter(function(d) { return !d.disengaged; }); | |
4598 if (engaged.every(function(series) { return series.userDisabled })) { | |
4599 //the default behavior of NVD3 legends is, if every single series | |
4600 // is disabled, turn all series' back on. | |
4601 data.forEach(function(series) { | |
4602 series.disabled = series.userDisabled = false; | |
4603 }); | |
4604 } | |
4605 } | |
4606 } | |
4607 dispatch.stateChange({ | |
4608 disabled: data.map(function(d) { return !!d.disabled }), | |
4609 disengaged: data.map(function(d) { return !!d.disengaged }) | |
4610 }); | |
4611 | |
4612 } | |
4613 }) | |
4614 .on('dblclick', function(d,i) { | |
4615 if(vers == 'furious' && expanded) return; | |
4616 dispatch.legendDblclick(d,i); | |
4617 if (updateState) { | |
4618 // make sure we re-get data in case it was modified | |
4619 var data = series.data(); | |
4620 //the default behavior of NVD3 legends, when double clicking one, | |
4621 // is to set all other series' to false, and make the double clicked series enabled. | |
4622 data.forEach(function(series) { | |
4623 series.disabled = true; | |
4624 if(vers == 'furious') series.userDisabled = series.disabled; | |
4625 }); | |
4626 d.disabled = false; | |
4627 if(vers == 'furious') d.userDisabled = d.disabled; | |
4628 dispatch.stateChange({ | |
4629 disabled: data.map(function(d) { return !!d.disabled }) | |
4630 }); | |
4631 } | |
4632 }); | |
4633 | |
4634 series.classed('nv-disabled', function(d) { return d.userDisabled }); | |
4635 series.exit().remove(); | |
4636 | |
4637 seriesText | |
4638 .attr('fill', setTextColor) | |
4639 .text(getKey); | |
4640 | |
4641 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) | |
4642 // NEW ALIGNING CODE, TODO: clean up | |
4643 | |
4644 var versPadding; | |
4645 switch(vers) { | |
4646 case 'furious' : | |
4647 versPadding = 23; | |
4648 break; | |
4649 case 'classic' : | |
4650 versPadding = 20; | |
4651 } | |
4652 | |
4653 if (align) { | |
4654 | |
4655 var seriesWidths = []; | |
4656 series.each(function(d,i) { | |
4657 var legendText = d3.select(this).select('text'); | |
4658 var nodeTextLength; | |
4659 try { | |
4660 nodeTextLength = legendText.node().getComputedTextLength(); | |
4661 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead | |
4662 if(nodeTextLength <= 0) throw Error(); | |
4663 } | |
4664 catch(e) { | |
4665 nodeTextLength = nv.utils.calcApproxTextWidth(legendText); | |
4666 } | |
4667 | |
4668 seriesWidths.push(nodeTextLength + padding); | |
4669 }); | |
4670 | |
4671 var seriesPerRow = 0; | |
4672 var legendWidth = 0; | |
4673 var columnWidths = []; | |
4674 | |
4675 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { | |
4676 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; | |
4677 legendWidth += seriesWidths[seriesPerRow++]; | |
4678 } | |
4679 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row | |
4680 | |
4681 while ( legendWidth > availableWidth && seriesPerRow > 1 ) { | |
4682 columnWidths = []; | |
4683 seriesPerRow--; | |
4684 | |
4685 for (var k = 0; k < seriesWidths.length; k++) { | |
4686 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) | |
4687 columnWidths[k % seriesPerRow] = seriesWidths[k]; | |
4688 } | |
4689 | |
4690 legendWidth = columnWidths.reduce(function(prev, cur, index, array) { | |
4691 return prev + cur; | |
4692 }); | |
4693 } | |
4694 | |
4695 var xPositions = []; | |
4696 for (var i = 0, curX = 0; i < seriesPerRow; i++) { | |
4697 xPositions[i] = curX; | |
4698 curX += columnWidths[i]; | |
4699 } | |
4700 | |
4701 series | |
4702 .attr('transform', function(d, i) { | |
4703 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')'; | |
4704 }); | |
4705 | |
4706 //position legend as far right as possible within the total width | |
4707 if (rightAlign) { | |
4708 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); | |
4709 } | |
4710 else { | |
4711 g.attr('transform', 'translate(0' + ',' + margin.top + ')'); | |
4712 } | |
4713 | |
4714 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding); | |
4715 | |
4716 } else { | |
4717 | |
4718 var ypos = 5, | |
4719 newxpos = 5, | |
4720 maxwidth = 0, | |
4721 xpos; | |
4722 series | |
4723 .attr('transform', function(d, i) { | |
4724 var length = d3.select(this).select('text').node().getComputedTextLength() + padding; | |
4725 xpos = newxpos; | |
4726 | |
4727 if (width < margin.left + margin.right + xpos + length) { | |
4728 newxpos = xpos = 5; | |
4729 ypos += versPadding; | |
4730 } | |
4731 | |
4732 newxpos += length; | |
4733 if (newxpos > maxwidth) maxwidth = newxpos; | |
4734 | |
4735 return 'translate(' + xpos + ',' + ypos + ')'; | |
4736 }); | |
4737 | |
4738 //position legend as far right as possible within the total width | |
4739 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); | |
4740 | |
4741 height = margin.top + margin.bottom + ypos + 15; | |
4742 } | |
4743 | |
4744 if(vers == 'furious') { | |
4745 // Size rectangles after text is placed | |
4746 seriesShape | |
4747 .attr('width', function(d,i) { | |
4748 return seriesText[0][i].getComputedTextLength() + 27; | |
4749 }) | |
4750 .attr('height', 18) | |
4751 .attr('y', -9) | |
4752 .attr('x', -15) | |
4753 } | |
4754 | |
4755 seriesShape | |
4756 .style('fill', setBGColor) | |
4757 .style('stroke', function(d,i) { return d.color || color(d, i) }); | |
4758 }); | |
4759 | |
4760 function setTextColor(d,i) { | |
4761 if(vers != 'furious') return '#000'; | |
4762 if(expanded) { | |
4763 return d.disengaged ? color(d,i) : '#fff'; | |
4764 } else if (!expanded) { | |
4765 return !!d.disabled ? color(d,i) : '#fff'; | |
4766 } | |
4767 } | |
4768 | |
4769 function setBGColor(d,i) { | |
4770 if(expanded && vers == 'furious') { | |
4771 return d.disengaged ? '#fff' : color(d,i); | |
4772 } else { | |
4773 return !!d.disabled ? '#fff' : color(d,i); | |
4774 } | |
4775 } | |
4776 | |
4777 return chart; | |
4778 } | |
4779 | |
4780 //============================================================ | |
4781 // Expose Public Variables | |
4782 //------------------------------------------------------------ | |
4783 | |
4784 chart.dispatch = dispatch; | |
4785 chart.options = nv.utils.optionsFunc.bind(chart); | |
4786 | |
4787 chart._options = Object.create({}, { | |
4788 // simple options, just get/set the necessary values | |
4789 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
4790 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
4791 key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, | |
4792 align: {get: function(){return align;}, set: function(_){align=_;}}, | |
4793 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, | |
4794 padding: {get: function(){return padding;}, set: function(_){padding=_;}}, | |
4795 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, | |
4796 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, | |
4797 expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, | |
4798 vers: {get: function(){return vers;}, set: function(_){vers=_;}}, | |
4799 | |
4800 // options that require extra logic in the setter | |
4801 margin: {get: function(){return margin;}, set: function(_){ | |
4802 margin.top = _.top !== undefined ? _.top : margin.top; | |
4803 margin.right = _.right !== undefined ? _.right : margin.right; | |
4804 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
4805 margin.left = _.left !== undefined ? _.left : margin.left; | |
4806 }}, | |
4807 color: {get: function(){return color;}, set: function(_){ | |
4808 color = nv.utils.getColor(_); | |
4809 }} | |
4810 }); | |
4811 | |
4812 nv.utils.initOptions(chart); | |
4813 | |
4814 return chart; | |
4815 }; | |
4816 //TODO: consider deprecating and using multibar with single series for this | |
4817 nv.models.historicalBar = function() { | |
4818 "use strict"; | |
4819 | |
4820 //============================================================ | |
4821 // Public Variables with Default Settings | |
4822 //------------------------------------------------------------ | |
4823 | |
4824 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
4825 , width = null | |
4826 , height = null | |
4827 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
4828 , container = null | |
4829 , x = d3.scale.linear() | |
4830 , y = d3.scale.linear() | |
4831 , getX = function(d) { return d.x } | |
4832 , getY = function(d) { return d.y } | |
4833 , forceX = [] | |
4834 , forceY = [0] | |
4835 , padData = false | |
4836 , clipEdge = true | |
4837 , color = nv.utils.defaultColor() | |
4838 , xDomain | |
4839 , yDomain | |
4840 , xRange | |
4841 , yRange | |
4842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') | |
4843 , interactive = true | |
4844 ; | |
4845 | |
4846 var renderWatch = nv.utils.renderWatch(dispatch, 0); | |
4847 | |
4848 function chart(selection) { | |
4849 selection.each(function(data) { | |
4850 renderWatch.reset(); | |
4851 | |
4852 container = d3.select(this); | |
4853 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
4854 availableHeight = nv.utils.availableHeight(height, container, margin); | |
4855 | |
4856 nv.utils.initSVG(container); | |
4857 | |
4858 // Setup Scales | |
4859 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); | |
4860 | |
4861 if (padData) | |
4862 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); | |
4863 else | |
4864 x.range(xRange || [0, availableWidth]); | |
4865 | |
4866 y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) )) | |
4867 .range(yRange || [availableHeight, 0]); | |
4868 | |
4869 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point | |
4870 if (x.domain()[0] === x.domain()[1]) | |
4871 x.domain()[0] ? | |
4872 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) | |
4873 : x.domain([-1,1]); | |
4874 | |
4875 if (y.domain()[0] === y.domain()[1]) | |
4876 y.domain()[0] ? | |
4877 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) | |
4878 : y.domain([-1,1]); | |
4879 | |
4880 // Setup containers and skeleton of chart | |
4881 var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]); | |
4882 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id); | |
4883 var defsEnter = wrapEnter.append('defs'); | |
4884 var gEnter = wrapEnter.append('g'); | |
4885 var g = wrap.select('g'); | |
4886 | |
4887 gEnter.append('g').attr('class', 'nv-bars'); | |
4888 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
4889 | |
4890 container | |
4891 .on('click', function(d,i) { | |
4892 dispatch.chartClick({ | |
4893 data: d, | |
4894 index: i, | |
4895 pos: d3.event, | |
4896 id: id | |
4897 }); | |
4898 }); | |
4899 | |
4900 defsEnter.append('clipPath') | |
4901 .attr('id', 'nv-chart-clip-path-' + id) | |
4902 .append('rect'); | |
4903 | |
4904 wrap.select('#nv-chart-clip-path-' + id + ' rect') | |
4905 .attr('width', availableWidth) | |
4906 .attr('height', availableHeight); | |
4907 | |
4908 g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); | |
4909 | |
4910 var bars = wrap.select('.nv-bars').selectAll('.nv-bar') | |
4911 .data(function(d) { return d }, function(d,i) {return getX(d,i)}); | |
4912 bars.exit().remove(); | |
4913 | |
4914 bars.enter().append('rect') | |
4915 .attr('x', 0 ) | |
4916 .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) }) | |
4917 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) }) | |
4918 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) | |
4919 .on('mouseover', function(d,i) { | |
4920 if (!interactive) return; | |
4921 d3.select(this).classed('hover', true); | |
4922 dispatch.elementMouseover({ | |
4923 data: d, | |
4924 index: i, | |
4925 color: d3.select(this).style("fill") | |
4926 }); | |
4927 | |
4928 }) | |
4929 .on('mouseout', function(d,i) { | |
4930 if (!interactive) return; | |
4931 d3.select(this).classed('hover', false); | |
4932 dispatch.elementMouseout({ | |
4933 data: d, | |
4934 index: i, | |
4935 color: d3.select(this).style("fill") | |
4936 }); | |
4937 }) | |
4938 .on('mousemove', function(d,i) { | |
4939 if (!interactive) return; | |
4940 dispatch.elementMousemove({ | |
4941 data: d, | |
4942 index: i, | |
4943 color: d3.select(this).style("fill") | |
4944 }); | |
4945 }) | |
4946 .on('click', function(d,i) { | |
4947 if (!interactive) return; | |
4948 dispatch.elementClick({ | |
4949 data: d, | |
4950 index: i, | |
4951 color: d3.select(this).style("fill") | |
4952 }); | |
4953 d3.event.stopPropagation(); | |
4954 }) | |
4955 .on('dblclick', function(d,i) { | |
4956 if (!interactive) return; | |
4957 dispatch.elementDblClick({ | |
4958 data: d, | |
4959 index: i, | |
4960 color: d3.select(this).style("fill") | |
4961 }); | |
4962 d3.event.stopPropagation(); | |
4963 }); | |
4964 | |
4965 bars | |
4966 .attr('fill', function(d,i) { return color(d, i); }) | |
4967 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i }) | |
4968 .watchTransition(renderWatch, 'bars') | |
4969 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) | |
4970 //TODO: better width calculations that don't assume always uniform data spacing;w | |
4971 .attr('width', (availableWidth / data[0].values.length) * .9 ); | |
4972 | |
4973 bars.watchTransition(renderWatch, 'bars') | |
4974 .attr('y', function(d,i) { | |
4975 var rval = getY(d,i) < 0 ? | |
4976 y(0) : | |
4977 y(0) - y(getY(d,i)) < 1 ? | |
4978 y(0) - 1 : | |
4979 y(getY(d,i)); | |
4980 return nv.utils.NaNtoZero(rval); | |
4981 }) | |
4982 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) }); | |
4983 | |
4984 }); | |
4985 | |
4986 renderWatch.renderEnd('historicalBar immediate'); | |
4987 return chart; | |
4988 } | |
4989 | |
4990 //Create methods to allow outside functions to highlight a specific bar. | |
4991 chart.highlightPoint = function(pointIndex, isHoverOver) { | |
4992 container | |
4993 .select(".nv-bars .nv-bar-0-" + pointIndex) | |
4994 .classed("hover", isHoverOver) | |
4995 ; | |
4996 }; | |
4997 | |
4998 chart.clearHighlights = function() { | |
4999 container | |
5000 .select(".nv-bars .nv-bar.hover") | |
5001 .classed("hover", false) | |
5002 ; | |
5003 }; | |
5004 | |
5005 //============================================================ | |
5006 // Expose Public Variables | |
5007 //------------------------------------------------------------ | |
5008 | |
5009 chart.dispatch = dispatch; | |
5010 chart.options = nv.utils.optionsFunc.bind(chart); | |
5011 | |
5012 chart._options = Object.create({}, { | |
5013 // simple options, just get/set the necessary values | |
5014 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
5015 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
5016 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, | |
5017 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, | |
5018 padData: {get: function(){return padData;}, set: function(_){padData=_;}}, | |
5019 x: {get: function(){return getX;}, set: function(_){getX=_;}}, | |
5020 y: {get: function(){return getY;}, set: function(_){getY=_;}}, | |
5021 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
5022 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
5023 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
5024 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
5025 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
5026 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
5027 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, | |
5028 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
5029 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, | |
5030 | |
5031 // options that require extra logic in the setter | |
5032 margin: {get: function(){return margin;}, set: function(_){ | |
5033 margin.top = _.top !== undefined ? _.top : margin.top; | |
5034 margin.right = _.right !== undefined ? _.right : margin.right; | |
5035 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
5036 margin.left = _.left !== undefined ? _.left : margin.left; | |
5037 }}, | |
5038 color: {get: function(){return color;}, set: function(_){ | |
5039 color = nv.utils.getColor(_); | |
5040 }} | |
5041 }); | |
5042 | |
5043 nv.utils.initOptions(chart); | |
5044 | |
5045 return chart; | |
5046 }; | |
5047 | |
5048 nv.models.historicalBarChart = function(bar_model) { | |
5049 "use strict"; | |
5050 | |
5051 //============================================================ | |
5052 // Public Variables with Default Settings | |
5053 //------------------------------------------------------------ | |
5054 | |
5055 var bars = bar_model || nv.models.historicalBar() | |
5056 , xAxis = nv.models.axis() | |
5057 , yAxis = nv.models.axis() | |
5058 , legend = nv.models.legend() | |
5059 , interactiveLayer = nv.interactiveGuideline() | |
5060 , tooltip = nv.models.tooltip() | |
5061 ; | |
5062 | |
5063 | |
5064 var margin = {top: 30, right: 90, bottom: 50, left: 90} | |
5065 , color = nv.utils.defaultColor() | |
5066 , width = null | |
5067 , height = null | |
5068 , showLegend = false | |
5069 , showXAxis = true | |
5070 , showYAxis = true | |
5071 , rightAlignYAxis = false | |
5072 , useInteractiveGuideline = false | |
5073 , x | |
5074 , y | |
5075 , state = {} | |
5076 , defaultState = null | |
5077 , noData = null | |
5078 , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd') | |
5079 , transitionDuration = 250 | |
5080 ; | |
5081 | |
5082 xAxis.orient('bottom').tickPadding(7); | |
5083 yAxis.orient( (rightAlignYAxis) ? 'right' : 'left'); | |
5084 tooltip | |
5085 .duration(0) | |
5086 .headerEnabled(false) | |
5087 .valueFormatter(function(d, i) { | |
5088 return yAxis.tickFormat()(d, i); | |
5089 }) | |
5090 .headerFormatter(function(d, i) { | |
5091 return xAxis.tickFormat()(d, i); | |
5092 }); | |
5093 | |
5094 | |
5095 //============================================================ | |
5096 // Private Variables | |
5097 //------------------------------------------------------------ | |
5098 | |
5099 var renderWatch = nv.utils.renderWatch(dispatch, 0); | |
5100 | |
5101 function chart(selection) { | |
5102 selection.each(function(data) { | |
5103 renderWatch.reset(); | |
5104 renderWatch.models(bars); | |
5105 if (showXAxis) renderWatch.models(xAxis); | |
5106 if (showYAxis) renderWatch.models(yAxis); | |
5107 | |
5108 var container = d3.select(this), | |
5109 that = this; | |
5110 nv.utils.initSVG(container); | |
5111 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
5112 availableHeight = nv.utils.availableHeight(height, container, margin); | |
5113 | |
5114 chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; | |
5115 chart.container = this; | |
5116 | |
5117 //set state.disabled | |
5118 state.disabled = data.map(function(d) { return !!d.disabled }); | |
5119 | |
5120 if (!defaultState) { | |
5121 var key; | |
5122 defaultState = {}; | |
5123 for (key in state) { | |
5124 if (state[key] instanceof Array) | |
5125 defaultState[key] = state[key].slice(0); | |
5126 else | |
5127 defaultState[key] = state[key]; | |
5128 } | |
5129 } | |
5130 | |
5131 // Display noData message if there's nothing to show. | |
5132 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
5133 nv.utils.noData(chart, container) | |
5134 return chart; | |
5135 } else { | |
5136 container.selectAll('.nv-noData').remove(); | |
5137 } | |
5138 | |
5139 // Setup Scales | |
5140 x = bars.xScale(); | |
5141 y = bars.yScale(); | |
5142 | |
5143 // Setup containers and skeleton of chart | |
5144 var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]); | |
5145 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g'); | |
5146 var g = wrap.select('g'); | |
5147 | |
5148 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
5149 gEnter.append('g').attr('class', 'nv-y nv-axis'); | |
5150 gEnter.append('g').attr('class', 'nv-barsWrap'); | |
5151 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
5152 gEnter.append('g').attr('class', 'nv-interactive'); | |
5153 | |
5154 // Legend | |
5155 if (showLegend) { | |
5156 legend.width(availableWidth); | |
5157 | |
5158 g.select('.nv-legendWrap') | |
5159 .datum(data) | |
5160 .call(legend); | |
5161 | |
5162 if ( margin.top != legend.height()) { | |
5163 margin.top = legend.height(); | |
5164 availableHeight = nv.utils.availableHeight(height, container, margin); | |
5165 } | |
5166 | |
5167 wrap.select('.nv-legendWrap') | |
5168 .attr('transform', 'translate(0,' + (-margin.top) +')') | |
5169 } | |
5170 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
5171 | |
5172 if (rightAlignYAxis) { | |
5173 g.select(".nv-y.nv-axis") | |
5174 .attr("transform", "translate(" + availableWidth + ",0)"); | |
5175 } | |
5176 | |
5177 //Set up interactive layer | |
5178 if (useInteractiveGuideline) { | |
5179 interactiveLayer | |
5180 .width(availableWidth) | |
5181 .height(availableHeight) | |
5182 .margin({left:margin.left, top:margin.top}) | |
5183 .svgContainer(container) | |
5184 .xScale(x); | |
5185 wrap.select(".nv-interactive").call(interactiveLayer); | |
5186 } | |
5187 bars | |
5188 .width(availableWidth) | |
5189 .height(availableHeight) | |
5190 .color(data.map(function(d,i) { | |
5191 return d.color || color(d, i); | |
5192 }).filter(function(d,i) { return !data[i].disabled })); | |
5193 | |
5194 var barsWrap = g.select('.nv-barsWrap') | |
5195 .datum(data.filter(function(d) { return !d.disabled })); | |
5196 barsWrap.transition().call(bars); | |
5197 | |
5198 // Setup Axes | |
5199 if (showXAxis) { | |
5200 xAxis | |
5201 .scale(x) | |
5202 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
5203 .tickSize(-availableHeight, 0); | |
5204 | |
5205 g.select('.nv-x.nv-axis') | |
5206 .attr('transform', 'translate(0,' + y.range()[0] + ')'); | |
5207 g.select('.nv-x.nv-axis') | |
5208 .transition() | |
5209 .call(xAxis); | |
5210 } | |
5211 | |
5212 if (showYAxis) { | |
5213 yAxis | |
5214 .scale(y) | |
5215 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) | |
5216 .tickSize( -availableWidth, 0); | |
5217 | |
5218 g.select('.nv-y.nv-axis') | |
5219 .transition() | |
5220 .call(yAxis); | |
5221 } | |
5222 | |
5223 //============================================================ | |
5224 // Event Handling/Dispatching (in chart's scope) | |
5225 //------------------------------------------------------------ | |
5226 | |
5227 interactiveLayer.dispatch.on('elementMousemove', function(e) { | |
5228 bars.clearHighlights(); | |
5229 | |
5230 var singlePoint, pointIndex, pointXLocation, allData = []; | |
5231 data | |
5232 .filter(function(series, i) { | |
5233 series.seriesIndex = i; | |
5234 return !series.disabled; | |
5235 }) | |
5236 .forEach(function(series,i) { | |
5237 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); | |
5238 bars.highlightPoint(pointIndex,true); | |
5239 var point = series.values[pointIndex]; | |
5240 if (point === undefined) return; | |
5241 if (singlePoint === undefined) singlePoint = point; | |
5242 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); | |
5243 allData.push({ | |
5244 key: series.key, | |
5245 value: chart.y()(point, pointIndex), | |
5246 color: color(series,series.seriesIndex), | |
5247 data: series.values[pointIndex] | |
5248 }); | |
5249 }); | |
5250 | |
5251 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); | |
5252 interactiveLayer.tooltip | |
5253 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) | |
5254 .chartContainer(that.parentNode) | |
5255 .valueFormatter(function(d,i) { | |
5256 return yAxis.tickFormat()(d); | |
5257 }) | |
5258 .data({ | |
5259 value: xValue, | |
5260 index: pointIndex, | |
5261 series: allData | |
5262 })(); | |
5263 | |
5264 interactiveLayer.renderGuideLine(pointXLocation); | |
5265 | |
5266 }); | |
5267 | |
5268 interactiveLayer.dispatch.on("elementMouseout",function(e) { | |
5269 dispatch.tooltipHide(); | |
5270 bars.clearHighlights(); | |
5271 }); | |
5272 | |
5273 legend.dispatch.on('legendClick', function(d,i) { | |
5274 d.disabled = !d.disabled; | |
5275 | |
5276 if (!data.filter(function(d) { return !d.disabled }).length) { | |
5277 data.map(function(d) { | |
5278 d.disabled = false; | |
5279 wrap.selectAll('.nv-series').classed('disabled', false); | |
5280 return d; | |
5281 }); | |
5282 } | |
5283 | |
5284 state.disabled = data.map(function(d) { return !!d.disabled }); | |
5285 dispatch.stateChange(state); | |
5286 | |
5287 selection.transition().call(chart); | |
5288 }); | |
5289 | |
5290 legend.dispatch.on('legendDblclick', function(d) { | |
5291 //Double clicking should always enable current series, and disabled all others. | |
5292 data.forEach(function(d) { | |
5293 d.disabled = true; | |
5294 }); | |
5295 d.disabled = false; | |
5296 | |
5297 state.disabled = data.map(function(d) { return !!d.disabled }); | |
5298 dispatch.stateChange(state); | |
5299 chart.update(); | |
5300 }); | |
5301 | |
5302 dispatch.on('changeState', function(e) { | |
5303 if (typeof e.disabled !== 'undefined') { | |
5304 data.forEach(function(series,i) { | |
5305 series.disabled = e.disabled[i]; | |
5306 }); | |
5307 | |
5308 state.disabled = e.disabled; | |
5309 } | |
5310 | |
5311 chart.update(); | |
5312 }); | |
5313 }); | |
5314 | |
5315 renderWatch.renderEnd('historicalBarChart immediate'); | |
5316 return chart; | |
5317 } | |
5318 | |
5319 //============================================================ | |
5320 // Event Handling/Dispatching (out of chart's scope) | |
5321 //------------------------------------------------------------ | |
5322 | |
5323 bars.dispatch.on('elementMouseover.tooltip', function(evt) { | |
5324 evt['series'] = { | |
5325 key: chart.x()(evt.data), | |
5326 value: chart.y()(evt.data), | |
5327 color: evt.color | |
5328 }; | |
5329 tooltip.data(evt).hidden(false); | |
5330 }); | |
5331 | |
5332 bars.dispatch.on('elementMouseout.tooltip', function(evt) { | |
5333 tooltip.hidden(true); | |
5334 }); | |
5335 | |
5336 bars.dispatch.on('elementMousemove.tooltip', function(evt) { | |
5337 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
5338 }); | |
5339 | |
5340 //============================================================ | |
5341 // Expose Public Variables | |
5342 //------------------------------------------------------------ | |
5343 | |
5344 // expose chart's sub-components | |
5345 chart.dispatch = dispatch; | |
5346 chart.bars = bars; | |
5347 chart.legend = legend; | |
5348 chart.xAxis = xAxis; | |
5349 chart.yAxis = yAxis; | |
5350 chart.interactiveLayer = interactiveLayer; | |
5351 chart.tooltip = tooltip; | |
5352 | |
5353 chart.options = nv.utils.optionsFunc.bind(chart); | |
5354 | |
5355 chart._options = Object.create({}, { | |
5356 // simple options, just get/set the necessary values | |
5357 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
5358 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
5359 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
5360 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
5361 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
5362 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
5363 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
5364 | |
5365 // deprecated options | |
5366 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
5367 // deprecated after 1.7.1 | |
5368 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
5369 tooltip.enabled(!!_); | |
5370 }}, | |
5371 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
5372 // deprecated after 1.7.1 | |
5373 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
5374 tooltip.contentGenerator(_); | |
5375 }}, | |
5376 | |
5377 // options that require extra logic in the setter | |
5378 margin: {get: function(){return margin;}, set: function(_){ | |
5379 margin.top = _.top !== undefined ? _.top : margin.top; | |
5380 margin.right = _.right !== undefined ? _.right : margin.right; | |
5381 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
5382 margin.left = _.left !== undefined ? _.left : margin.left; | |
5383 }}, | |
5384 color: {get: function(){return color;}, set: function(_){ | |
5385 color = nv.utils.getColor(_); | |
5386 legend.color(color); | |
5387 bars.color(color); | |
5388 }}, | |
5389 duration: {get: function(){return transitionDuration;}, set: function(_){ | |
5390 transitionDuration=_; | |
5391 renderWatch.reset(transitionDuration); | |
5392 yAxis.duration(transitionDuration); | |
5393 xAxis.duration(transitionDuration); | |
5394 }}, | |
5395 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ | |
5396 rightAlignYAxis = _; | |
5397 yAxis.orient( (_) ? 'right' : 'left'); | |
5398 }}, | |
5399 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ | |
5400 useInteractiveGuideline = _; | |
5401 if (_ === true) { | |
5402 chart.interactive(false); | |
5403 } | |
5404 }} | |
5405 }); | |
5406 | |
5407 nv.utils.inheritOptions(chart, bars); | |
5408 nv.utils.initOptions(chart); | |
5409 | |
5410 return chart; | |
5411 }; | |
5412 | |
5413 | |
5414 // ohlcChart is just a historical chart with ohlc bars and some tweaks | |
5415 nv.models.ohlcBarChart = function() { | |
5416 var chart = nv.models.historicalBarChart(nv.models.ohlcBar()); | |
5417 | |
5418 // special default tooltip since we show multiple values per x | |
5419 chart.useInteractiveGuideline(true); | |
5420 chart.interactiveLayer.tooltip.contentGenerator(function(data) { | |
5421 // we assume only one series exists for this chart | |
5422 var d = data.series[0].data; | |
5423 // match line colors as defined in nv.d3.css | |
5424 var color = d.open < d.close ? "2ca02c" : "d62728"; | |
5425 return '' + | |
5426 '<h3 style="color: #' + color + '">' + data.value + '</h3>' + | |
5427 '<table>' + | |
5428 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' + | |
5429 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' + | |
5430 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' + | |
5431 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' + | |
5432 '</table>'; | |
5433 }); | |
5434 return chart; | |
5435 }; | |
5436 | |
5437 // candlestickChart is just a historical chart with candlestick bars and some tweaks | |
5438 nv.models.candlestickBarChart = function() { | |
5439 var chart = nv.models.historicalBarChart(nv.models.candlestickBar()); | |
5440 | |
5441 // special default tooltip since we show multiple values per x | |
5442 chart.useInteractiveGuideline(true); | |
5443 chart.interactiveLayer.tooltip.contentGenerator(function(data) { | |
5444 // we assume only one series exists for this chart | |
5445 var d = data.series[0].data; | |
5446 // match line colors as defined in nv.d3.css | |
5447 var color = d.open < d.close ? "2ca02c" : "d62728"; | |
5448 return '' + | |
5449 '<h3 style="color: #' + color + '">' + data.value + '</h3>' + | |
5450 '<table>' + | |
5451 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' + | |
5452 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' + | |
5453 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' + | |
5454 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' + | |
5455 '</table>'; | |
5456 }); | |
5457 return chart; | |
5458 }; | |
5459 nv.models.legend = function() { | |
5460 "use strict"; | |
5461 | |
5462 //============================================================ | |
5463 // Public Variables with Default Settings | |
5464 //------------------------------------------------------------ | |
5465 | |
5466 var margin = {top: 5, right: 0, bottom: 5, left: 0} | |
5467 , width = 400 | |
5468 , height = 20 | |
5469 , getKey = function(d) { return d.key } | |
5470 , color = nv.utils.getColor() | |
5471 , align = true | |
5472 , padding = 32 //define how much space between legend items. - recommend 32 for furious version | |
5473 , rightAlign = true | |
5474 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch. | |
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) | |
5476 , expanded = false | |
5477 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange') | |
5478 , vers = 'classic' //Options are "classic" and "furious" | |
5479 ; | |
5480 | |
5481 function chart(selection) { | |
5482 selection.each(function(data) { | |
5483 var availableWidth = width - margin.left - margin.right, | |
5484 container = d3.select(this); | |
5485 nv.utils.initSVG(container); | |
5486 | |
5487 // Setup containers and skeleton of chart | |
5488 var wrap = container.selectAll('g.nv-legend').data([data]); | |
5489 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g'); | |
5490 var g = wrap.select('g'); | |
5491 | |
5492 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
5493 | |
5494 var series = g.selectAll('.nv-series') | |
5495 .data(function(d) { | |
5496 if(vers != 'furious') return d; | |
5497 | |
5498 return d.filter(function(n) { | |
5499 return expanded ? true : !n.disengaged; | |
5500 }); | |
5501 }); | |
5502 | |
5503 var seriesEnter = series.enter().append('g').attr('class', 'nv-series'); | |
5504 var seriesShape; | |
5505 | |
5506 var versPadding; | |
5507 switch(vers) { | |
5508 case 'furious' : | |
5509 versPadding = 23; | |
5510 break; | |
5511 case 'classic' : | |
5512 versPadding = 20; | |
5513 } | |
5514 | |
5515 if(vers == 'classic') { | |
5516 seriesEnter.append('circle') | |
5517 .style('stroke-width', 2) | |
5518 .attr('class','nv-legend-symbol') | |
5519 .attr('r', 5); | |
5520 | |
5521 seriesShape = series.select('circle'); | |
5522 } else if (vers == 'furious') { | |
5523 seriesEnter.append('rect') | |
5524 .style('stroke-width', 2) | |
5525 .attr('class','nv-legend-symbol') | |
5526 .attr('rx', 3) | |
5527 .attr('ry', 3); | |
5528 | |
5529 seriesShape = series.select('.nv-legend-symbol'); | |
5530 | |
5531 seriesEnter.append('g') | |
5532 .attr('class', 'nv-check-box') | |
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>') | |
5534 .attr('transform', 'translate(-10,-8)scale(0.5)'); | |
5535 | |
5536 var seriesCheckbox = series.select('.nv-check-box'); | |
5537 | |
5538 seriesCheckbox.each(function(d,i) { | |
5539 d3.select(this).selectAll('path') | |
5540 .attr('stroke', setTextColor(d,i)); | |
5541 }); | |
5542 } | |
5543 | |
5544 seriesEnter.append('text') | |
5545 .attr('text-anchor', 'start') | |
5546 .attr('class','nv-legend-text') | |
5547 .attr('dy', '.32em') | |
5548 .attr('dx', '8'); | |
5549 | |
5550 var seriesText = series.select('text.nv-legend-text'); | |
5551 | |
5552 series | |
5553 .on('mouseover', function(d,i) { | |
5554 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects | |
5555 }) | |
5556 .on('mouseout', function(d,i) { | |
5557 dispatch.legendMouseout(d,i); | |
5558 }) | |
5559 .on('click', function(d,i) { | |
5560 dispatch.legendClick(d,i); | |
5561 // make sure we re-get data in case it was modified | |
5562 var data = series.data(); | |
5563 if (updateState) { | |
5564 if(vers =='classic') { | |
5565 if (radioButtonMode) { | |
5566 //Radio button mode: set every series to disabled, | |
5567 // and enable the clicked series. | |
5568 data.forEach(function(series) { series.disabled = true}); | |
5569 d.disabled = false; | |
5570 } | |
5571 else { | |
5572 d.disabled = !d.disabled; | |
5573 if (data.every(function(series) { return series.disabled})) { | |
5574 //the default behavior of NVD3 legends is, if every single series | |
5575 // is disabled, turn all series' back on. | |
5576 data.forEach(function(series) { series.disabled = false}); | |
5577 } | |
5578 } | |
5579 } else if(vers == 'furious') { | |
5580 if(expanded) { | |
5581 d.disengaged = !d.disengaged; | |
5582 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled; | |
5583 d.disabled = d.disengaged || d.userDisabled; | |
5584 } else if (!expanded) { | |
5585 d.disabled = !d.disabled; | |
5586 d.userDisabled = d.disabled; | |
5587 var engaged = data.filter(function(d) { return !d.disengaged; }); | |
5588 if (engaged.every(function(series) { return series.userDisabled })) { | |
5589 //the default behavior of NVD3 legends is, if every single series | |
5590 // is disabled, turn all series' back on. | |
5591 data.forEach(function(series) { | |
5592 series.disabled = series.userDisabled = false; | |
5593 }); | |
5594 } | |
5595 } | |
5596 } | |
5597 dispatch.stateChange({ | |
5598 disabled: data.map(function(d) { return !!d.disabled }), | |
5599 disengaged: data.map(function(d) { return !!d.disengaged }) | |
5600 }); | |
5601 | |
5602 } | |
5603 }) | |
5604 .on('dblclick', function(d,i) { | |
5605 if(vers == 'furious' && expanded) return; | |
5606 dispatch.legendDblclick(d,i); | |
5607 if (updateState) { | |
5608 // make sure we re-get data in case it was modified | |
5609 var data = series.data(); | |
5610 //the default behavior of NVD3 legends, when double clicking one, | |
5611 // is to set all other series' to false, and make the double clicked series enabled. | |
5612 data.forEach(function(series) { | |
5613 series.disabled = true; | |
5614 if(vers == 'furious') series.userDisabled = series.disabled; | |
5615 }); | |
5616 d.disabled = false; | |
5617 if(vers == 'furious') d.userDisabled = d.disabled; | |
5618 dispatch.stateChange({ | |
5619 disabled: data.map(function(d) { return !!d.disabled }) | |
5620 }); | |
5621 } | |
5622 }); | |
5623 | |
5624 series.classed('nv-disabled', function(d) { return d.userDisabled }); | |
5625 series.exit().remove(); | |
5626 | |
5627 seriesText | |
5628 .attr('fill', setTextColor) | |
5629 .text(getKey); | |
5630 | |
5631 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option) | |
5632 // NEW ALIGNING CODE, TODO: clean up | |
5633 var legendWidth = 0; | |
5634 if (align) { | |
5635 | |
5636 var seriesWidths = []; | |
5637 series.each(function(d,i) { | |
5638 var legendText = d3.select(this).select('text'); | |
5639 var nodeTextLength; | |
5640 try { | |
5641 nodeTextLength = legendText.node().getComputedTextLength(); | |
5642 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead | |
5643 if(nodeTextLength <= 0) throw Error(); | |
5644 } | |
5645 catch(e) { | |
5646 nodeTextLength = nv.utils.calcApproxTextWidth(legendText); | |
5647 } | |
5648 | |
5649 seriesWidths.push(nodeTextLength + padding); | |
5650 }); | |
5651 | |
5652 var seriesPerRow = 0; | |
5653 var columnWidths = []; | |
5654 legendWidth = 0; | |
5655 | |
5656 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) { | |
5657 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow]; | |
5658 legendWidth += seriesWidths[seriesPerRow++]; | |
5659 } | |
5660 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row | |
5661 | |
5662 while ( legendWidth > availableWidth && seriesPerRow > 1 ) { | |
5663 columnWidths = []; | |
5664 seriesPerRow--; | |
5665 | |
5666 for (var k = 0; k < seriesWidths.length; k++) { | |
5667 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) ) | |
5668 columnWidths[k % seriesPerRow] = seriesWidths[k]; | |
5669 } | |
5670 | |
5671 legendWidth = columnWidths.reduce(function(prev, cur, index, array) { | |
5672 return prev + cur; | |
5673 }); | |
5674 } | |
5675 | |
5676 var xPositions = []; | |
5677 for (var i = 0, curX = 0; i < seriesPerRow; i++) { | |
5678 xPositions[i] = curX; | |
5679 curX += columnWidths[i]; | |
5680 } | |
5681 | |
5682 series | |
5683 .attr('transform', function(d, i) { | |
5684 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')'; | |
5685 }); | |
5686 | |
5687 //position legend as far right as possible within the total width | |
5688 if (rightAlign) { | |
5689 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')'); | |
5690 } | |
5691 else { | |
5692 g.attr('transform', 'translate(0' + ',' + margin.top + ')'); | |
5693 } | |
5694 | |
5695 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding); | |
5696 | |
5697 } else { | |
5698 | |
5699 var ypos = 5, | |
5700 newxpos = 5, | |
5701 maxwidth = 0, | |
5702 xpos; | |
5703 series | |
5704 .attr('transform', function(d, i) { | |
5705 var length = d3.select(this).select('text').node().getComputedTextLength() + padding; | |
5706 xpos = newxpos; | |
5707 | |
5708 if (width < margin.left + margin.right + xpos + length) { | |
5709 newxpos = xpos = 5; | |
5710 ypos += versPadding; | |
5711 } | |
5712 | |
5713 newxpos += length; | |
5714 if (newxpos > maxwidth) maxwidth = newxpos; | |
5715 | |
5716 if(legendWidth < xpos + maxwidth) { | |
5717 legendWidth = xpos + maxwidth; | |
5718 } | |
5719 return 'translate(' + xpos + ',' + ypos + ')'; | |
5720 }); | |
5721 | |
5722 //position legend as far right as possible within the total width | |
5723 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')'); | |
5724 | |
5725 height = margin.top + margin.bottom + ypos + 15; | |
5726 } | |
5727 | |
5728 if(vers == 'furious') { | |
5729 // Size rectangles after text is placed | |
5730 seriesShape | |
5731 .attr('width', function(d,i) { | |
5732 return seriesText[0][i].getComputedTextLength() + 27; | |
5733 }) | |
5734 .attr('height', 18) | |
5735 .attr('y', -9) | |
5736 .attr('x', -15); | |
5737 | |
5738 // The background for the expanded legend (UI) | |
5739 gEnter.insert('rect',':first-child') | |
5740 .attr('class', 'nv-legend-bg') | |
5741 .attr('fill', '#eee') | |
5742 // .attr('stroke', '#444') | |
5743 .attr('opacity',0); | |
5744 | |
5745 var seriesBG = g.select('.nv-legend-bg'); | |
5746 | |
5747 seriesBG | |
5748 .transition().duration(300) | |
5749 .attr('x', -versPadding ) | |
5750 .attr('width', legendWidth + versPadding - 12) | |
5751 .attr('height', height + 10) | |
5752 .attr('y', -margin.top - 10) | |
5753 .attr('opacity', expanded ? 1 : 0); | |
5754 | |
5755 | |
5756 } | |
5757 | |
5758 seriesShape | |
5759 .style('fill', setBGColor) | |
5760 .style('fill-opacity', setBGOpacity) | |
5761 .style('stroke', setBGColor); | |
5762 }); | |
5763 | |
5764 function setTextColor(d,i) { | |
5765 if(vers != 'furious') return '#000'; | |
5766 if(expanded) { | |
5767 return d.disengaged ? '#000' : '#fff'; | |
5768 } else if (!expanded) { | |
5769 if(!d.color) d.color = color(d,i); | |
5770 return !!d.disabled ? d.color : '#fff'; | |
5771 } | |
5772 } | |
5773 | |
5774 function setBGColor(d,i) { | |
5775 if(expanded && vers == 'furious') { | |
5776 return d.disengaged ? '#eee' : d.color || color(d,i); | |
5777 } else { | |
5778 return d.color || color(d,i); | |
5779 } | |
5780 } | |
5781 | |
5782 | |
5783 function setBGOpacity(d,i) { | |
5784 if(expanded && vers == 'furious') { | |
5785 return 1; | |
5786 } else { | |
5787 return !!d.disabled ? 0 : 1; | |
5788 } | |
5789 } | |
5790 | |
5791 return chart; | |
5792 } | |
5793 | |
5794 //============================================================ | |
5795 // Expose Public Variables | |
5796 //------------------------------------------------------------ | |
5797 | |
5798 chart.dispatch = dispatch; | |
5799 chart.options = nv.utils.optionsFunc.bind(chart); | |
5800 | |
5801 chart._options = Object.create({}, { | |
5802 // simple options, just get/set the necessary values | |
5803 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
5804 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
5805 key: {get: function(){return getKey;}, set: function(_){getKey=_;}}, | |
5806 align: {get: function(){return align;}, set: function(_){align=_;}}, | |
5807 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}}, | |
5808 padding: {get: function(){return padding;}, set: function(_){padding=_;}}, | |
5809 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}}, | |
5810 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}}, | |
5811 expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}}, | |
5812 vers: {get: function(){return vers;}, set: function(_){vers=_;}}, | |
5813 | |
5814 // options that require extra logic in the setter | |
5815 margin: {get: function(){return margin;}, set: function(_){ | |
5816 margin.top = _.top !== undefined ? _.top : margin.top; | |
5817 margin.right = _.right !== undefined ? _.right : margin.right; | |
5818 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
5819 margin.left = _.left !== undefined ? _.left : margin.left; | |
5820 }}, | |
5821 color: {get: function(){return color;}, set: function(_){ | |
5822 color = nv.utils.getColor(_); | |
5823 }} | |
5824 }); | |
5825 | |
5826 nv.utils.initOptions(chart); | |
5827 | |
5828 return chart; | |
5829 }; | |
5830 | |
5831 nv.models.line = function() { | |
5832 "use strict"; | |
5833 //============================================================ | |
5834 // Public Variables with Default Settings | |
5835 //------------------------------------------------------------ | |
5836 | |
5837 var scatter = nv.models.scatter() | |
5838 ; | |
5839 | |
5840 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
5841 , width = 960 | |
5842 , height = 500 | |
5843 , container = null | |
5844 , strokeWidth = 1.5 | |
5845 , color = nv.utils.defaultColor() // a function that returns a color | |
5846 , getX = function(d) { return d.x } // accessor to get the x value from a data point | |
5847 , getY = function(d) { return d.y } // accessor to get the y value from a data point | |
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 | |
5849 , isArea = function(d) { return d.area } // decides if a line is an area or just a line | |
5850 , clipEdge = false // if true, masks lines within x and y scale | |
5851 , x //can be accessed via chart.xScale() | |
5852 , y //can be accessed via chart.yScale() | |
5853 , interpolate = "linear" // controls the line interpolation | |
5854 , duration = 250 | |
5855 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd') | |
5856 ; | |
5857 | |
5858 scatter | |
5859 .pointSize(16) // default size | |
5860 .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor | |
5861 ; | |
5862 | |
5863 //============================================================ | |
5864 | |
5865 | |
5866 //============================================================ | |
5867 // Private Variables | |
5868 //------------------------------------------------------------ | |
5869 | |
5870 var x0, y0 //used to store previous scales | |
5871 , renderWatch = nv.utils.renderWatch(dispatch, duration) | |
5872 ; | |
5873 | |
5874 //============================================================ | |
5875 | |
5876 | |
5877 function chart(selection) { | |
5878 renderWatch.reset(); | |
5879 renderWatch.models(scatter); | |
5880 selection.each(function(data) { | |
5881 container = d3.select(this); | |
5882 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
5883 availableHeight = nv.utils.availableHeight(height, container, margin); | |
5884 nv.utils.initSVG(container); | |
5885 | |
5886 // Setup Scales | |
5887 x = scatter.xScale(); | |
5888 y = scatter.yScale(); | |
5889 | |
5890 x0 = x0 || x; | |
5891 y0 = y0 || y; | |
5892 | |
5893 // Setup containers and skeleton of chart | |
5894 var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]); | |
5895 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line'); | |
5896 var defsEnter = wrapEnter.append('defs'); | |
5897 var gEnter = wrapEnter.append('g'); | |
5898 var g = wrap.select('g'); | |
5899 | |
5900 gEnter.append('g').attr('class', 'nv-groups'); | |
5901 gEnter.append('g').attr('class', 'nv-scatterWrap'); | |
5902 | |
5903 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
5904 | |
5905 scatter | |
5906 .width(availableWidth) | |
5907 .height(availableHeight); | |
5908 | |
5909 var scatterWrap = wrap.select('.nv-scatterWrap'); | |
5910 scatterWrap.call(scatter); | |
5911 | |
5912 defsEnter.append('clipPath') | |
5913 .attr('id', 'nv-edge-clip-' + scatter.id()) | |
5914 .append('rect'); | |
5915 | |
5916 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect') | |
5917 .attr('width', availableWidth) | |
5918 .attr('height', (availableHeight > 0) ? availableHeight : 0); | |
5919 | |
5920 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); | |
5921 scatterWrap | |
5922 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : ''); | |
5923 | |
5924 var groups = wrap.select('.nv-groups').selectAll('.nv-group') | |
5925 .data(function(d) { return d }, function(d) { return d.key }); | |
5926 groups.enter().append('g') | |
5927 .style('stroke-opacity', 1e-6) | |
5928 .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth }) | |
5929 .style('fill-opacity', 1e-6); | |
5930 | |
5931 groups.exit().remove(); | |
5932 | |
5933 groups | |
5934 .attr('class', function(d,i) { | |
5935 return (d.classed || '') + ' nv-group nv-series-' + i; | |
5936 }) | |
5937 .classed('hover', function(d) { return d.hover }) | |
5938 .style('fill', function(d,i){ return color(d, i) }) | |
5939 .style('stroke', function(d,i){ return color(d, i)}); | |
5940 groups.watchTransition(renderWatch, 'line: groups') | |
5941 .style('stroke-opacity', 1) | |
5942 .style('fill-opacity', function(d) { return d.fillOpacity || .5}); | |
5943 | |
5944 var areaPaths = groups.selectAll('path.nv-area') | |
5945 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area | |
5946 areaPaths.enter().append('path') | |
5947 .attr('class', 'nv-area') | |
5948 .attr('d', function(d) { | |
5949 return d3.svg.area() | |
5950 .interpolate(interpolate) | |
5951 .defined(defined) | |
5952 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) | |
5953 .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) | |
5954 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) | |
5955 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this | |
5956 .apply(this, [d.values]) | |
5957 }); | |
5958 groups.exit().selectAll('path.nv-area') | |
5959 .remove(); | |
5960 | |
5961 areaPaths.watchTransition(renderWatch, 'line: areaPaths') | |
5962 .attr('d', function(d) { | |
5963 return d3.svg.area() | |
5964 .interpolate(interpolate) | |
5965 .defined(defined) | |
5966 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) | |
5967 .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) | |
5968 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) }) | |
5969 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this | |
5970 .apply(this, [d.values]) | |
5971 }); | |
5972 | |
5973 var linePaths = groups.selectAll('path.nv-line') | |
5974 .data(function(d) { return [d.values] }); | |
5975 | |
5976 linePaths.enter().append('path') | |
5977 .attr('class', 'nv-line') | |
5978 .attr('d', | |
5979 d3.svg.line() | |
5980 .interpolate(interpolate) | |
5981 .defined(defined) | |
5982 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) }) | |
5983 .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) }) | |
5984 ); | |
5985 | |
5986 linePaths.watchTransition(renderWatch, 'line: linePaths') | |
5987 .attr('d', | |
5988 d3.svg.line() | |
5989 .interpolate(interpolate) | |
5990 .defined(defined) | |
5991 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) }) | |
5992 .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) }) | |
5993 ); | |
5994 | |
5995 //store old scales for use in transitions on update | |
5996 x0 = x.copy(); | |
5997 y0 = y.copy(); | |
5998 }); | |
5999 renderWatch.renderEnd('line immediate'); | |
6000 return chart; | |
6001 } | |
6002 | |
6003 | |
6004 //============================================================ | |
6005 // Expose Public Variables | |
6006 //------------------------------------------------------------ | |
6007 | |
6008 chart.dispatch = dispatch; | |
6009 chart.scatter = scatter; | |
6010 // Pass through events | |
6011 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); }); | |
6012 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); }); | |
6013 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); }); | |
6014 | |
6015 chart.options = nv.utils.optionsFunc.bind(chart); | |
6016 | |
6017 chart._options = Object.create({}, { | |
6018 // simple options, just get/set the necessary values | |
6019 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
6020 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
6021 defined: {get: function(){return defined;}, set: function(_){defined=_;}}, | |
6022 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, | |
6023 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, | |
6024 | |
6025 // options that require extra logic in the setter | |
6026 margin: {get: function(){return margin;}, set: function(_){ | |
6027 margin.top = _.top !== undefined ? _.top : margin.top; | |
6028 margin.right = _.right !== undefined ? _.right : margin.right; | |
6029 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
6030 margin.left = _.left !== undefined ? _.left : margin.left; | |
6031 }}, | |
6032 duration: {get: function(){return duration;}, set: function(_){ | |
6033 duration = _; | |
6034 renderWatch.reset(duration); | |
6035 scatter.duration(duration); | |
6036 }}, | |
6037 isArea: {get: function(){return isArea;}, set: function(_){ | |
6038 isArea = d3.functor(_); | |
6039 }}, | |
6040 x: {get: function(){return getX;}, set: function(_){ | |
6041 getX = _; | |
6042 scatter.x(_); | |
6043 }}, | |
6044 y: {get: function(){return getY;}, set: function(_){ | |
6045 getY = _; | |
6046 scatter.y(_); | |
6047 }}, | |
6048 color: {get: function(){return color;}, set: function(_){ | |
6049 color = nv.utils.getColor(_); | |
6050 scatter.color(color); | |
6051 }} | |
6052 }); | |
6053 | |
6054 nv.utils.inheritOptions(chart, scatter); | |
6055 nv.utils.initOptions(chart); | |
6056 | |
6057 return chart; | |
6058 }; | |
6059 nv.models.lineChart = function() { | |
6060 "use strict"; | |
6061 | |
6062 //============================================================ | |
6063 // Public Variables with Default Settings | |
6064 //------------------------------------------------------------ | |
6065 | |
6066 var lines = nv.models.line() | |
6067 , xAxis = nv.models.axis() | |
6068 , yAxis = nv.models.axis() | |
6069 , legend = nv.models.legend() | |
6070 , interactiveLayer = nv.interactiveGuideline() | |
6071 , tooltip = nv.models.tooltip() | |
6072 ; | |
6073 | |
6074 var margin = {top: 30, right: 20, bottom: 50, left: 60} | |
6075 , color = nv.utils.defaultColor() | |
6076 , width = null | |
6077 , height = null | |
6078 , showLegend = true | |
6079 , showXAxis = true | |
6080 , showYAxis = true | |
6081 , rightAlignYAxis = false | |
6082 , useInteractiveGuideline = false | |
6083 , x | |
6084 , y | |
6085 , state = nv.utils.state() | |
6086 , defaultState = null | |
6087 , noData = null | |
6088 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd') | |
6089 , duration = 250 | |
6090 ; | |
6091 | |
6092 // set options on sub-objects for this chart | |
6093 xAxis.orient('bottom').tickPadding(7); | |
6094 yAxis.orient(rightAlignYAxis ? 'right' : 'left'); | |
6095 tooltip.valueFormatter(function(d, i) { | |
6096 return yAxis.tickFormat()(d, i); | |
6097 }).headerFormatter(function(d, i) { | |
6098 return xAxis.tickFormat()(d, i); | |
6099 }); | |
6100 | |
6101 | |
6102 //============================================================ | |
6103 // Private Variables | |
6104 //------------------------------------------------------------ | |
6105 | |
6106 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
6107 | |
6108 var stateGetter = function(data) { | |
6109 return function(){ | |
6110 return { | |
6111 active: data.map(function(d) { return !d.disabled }) | |
6112 }; | |
6113 } | |
6114 }; | |
6115 | |
6116 var stateSetter = function(data) { | |
6117 return function(state) { | |
6118 if (state.active !== undefined) | |
6119 data.forEach(function(series,i) { | |
6120 series.disabled = !state.active[i]; | |
6121 }); | |
6122 } | |
6123 }; | |
6124 | |
6125 function chart(selection) { | |
6126 renderWatch.reset(); | |
6127 renderWatch.models(lines); | |
6128 if (showXAxis) renderWatch.models(xAxis); | |
6129 if (showYAxis) renderWatch.models(yAxis); | |
6130 | |
6131 selection.each(function(data) { | |
6132 var container = d3.select(this), | |
6133 that = this; | |
6134 nv.utils.initSVG(container); | |
6135 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
6136 availableHeight = nv.utils.availableHeight(height, container, margin); | |
6137 | |
6138 chart.update = function() { | |
6139 if (duration === 0) | |
6140 container.call(chart); | |
6141 else | |
6142 container.transition().duration(duration).call(chart) | |
6143 }; | |
6144 chart.container = this; | |
6145 | |
6146 state | |
6147 .setter(stateSetter(data), chart.update) | |
6148 .getter(stateGetter(data)) | |
6149 .update(); | |
6150 | |
6151 // DEPRECATED set state.disableddisabled | |
6152 state.disabled = data.map(function(d) { return !!d.disabled }); | |
6153 | |
6154 if (!defaultState) { | |
6155 var key; | |
6156 defaultState = {}; | |
6157 for (key in state) { | |
6158 if (state[key] instanceof Array) | |
6159 defaultState[key] = state[key].slice(0); | |
6160 else | |
6161 defaultState[key] = state[key]; | |
6162 } | |
6163 } | |
6164 | |
6165 // Display noData message if there's nothing to show. | |
6166 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
6167 nv.utils.noData(chart, container) | |
6168 return chart; | |
6169 } else { | |
6170 container.selectAll('.nv-noData').remove(); | |
6171 } | |
6172 | |
6173 | |
6174 // Setup Scales | |
6175 x = lines.xScale(); | |
6176 y = lines.yScale(); | |
6177 | |
6178 // Setup containers and skeleton of chart | |
6179 var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]); | |
6180 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g'); | |
6181 var g = wrap.select('g'); | |
6182 | |
6183 gEnter.append("rect").style("opacity",0); | |
6184 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
6185 gEnter.append('g').attr('class', 'nv-y nv-axis'); | |
6186 gEnter.append('g').attr('class', 'nv-linesWrap'); | |
6187 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
6188 gEnter.append('g').attr('class', 'nv-interactive'); | |
6189 | |
6190 g.select("rect") | |
6191 .attr("width",availableWidth) | |
6192 .attr("height",(availableHeight > 0) ? availableHeight : 0); | |
6193 | |
6194 // Legend | |
6195 if (showLegend) { | |
6196 legend.width(availableWidth); | |
6197 | |
6198 g.select('.nv-legendWrap') | |
6199 .datum(data) | |
6200 .call(legend); | |
6201 | |
6202 if ( margin.top != legend.height()) { | |
6203 margin.top = legend.height(); | |
6204 availableHeight = nv.utils.availableHeight(height, container, margin); | |
6205 } | |
6206 | |
6207 wrap.select('.nv-legendWrap') | |
6208 .attr('transform', 'translate(0,' + (-margin.top) +')') | |
6209 } | |
6210 | |
6211 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
6212 | |
6213 if (rightAlignYAxis) { | |
6214 g.select(".nv-y.nv-axis") | |
6215 .attr("transform", "translate(" + availableWidth + ",0)"); | |
6216 } | |
6217 | |
6218 //Set up interactive layer | |
6219 if (useInteractiveGuideline) { | |
6220 interactiveLayer | |
6221 .width(availableWidth) | |
6222 .height(availableHeight) | |
6223 .margin({left:margin.left, top:margin.top}) | |
6224 .svgContainer(container) | |
6225 .xScale(x); | |
6226 wrap.select(".nv-interactive").call(interactiveLayer); | |
6227 } | |
6228 | |
6229 lines | |
6230 .width(availableWidth) | |
6231 .height(availableHeight) | |
6232 .color(data.map(function(d,i) { | |
6233 return d.color || color(d, i); | |
6234 }).filter(function(d,i) { return !data[i].disabled })); | |
6235 | |
6236 | |
6237 var linesWrap = g.select('.nv-linesWrap') | |
6238 .datum(data.filter(function(d) { return !d.disabled })); | |
6239 | |
6240 linesWrap.call(lines); | |
6241 | |
6242 // Setup Axes | |
6243 if (showXAxis) { | |
6244 xAxis | |
6245 .scale(x) | |
6246 ._ticks(nv.utils.calcTicksX(availableWidth/100, data) ) | |
6247 .tickSize(-availableHeight, 0); | |
6248 | |
6249 g.select('.nv-x.nv-axis') | |
6250 .attr('transform', 'translate(0,' + y.range()[0] + ')'); | |
6251 g.select('.nv-x.nv-axis') | |
6252 .call(xAxis); | |
6253 } | |
6254 | |
6255 if (showYAxis) { | |
6256 yAxis | |
6257 .scale(y) | |
6258 ._ticks(nv.utils.calcTicksY(availableHeight/36, data) ) | |
6259 .tickSize( -availableWidth, 0); | |
6260 | |
6261 g.select('.nv-y.nv-axis') | |
6262 .call(yAxis); | |
6263 } | |
6264 | |
6265 //============================================================ | |
6266 // Event Handling/Dispatching (in chart's scope) | |
6267 //------------------------------------------------------------ | |
6268 | |
6269 legend.dispatch.on('stateChange', function(newState) { | |
6270 for (var key in newState) | |
6271 state[key] = newState[key]; | |
6272 dispatch.stateChange(state); | |
6273 chart.update(); | |
6274 }); | |
6275 | |
6276 interactiveLayer.dispatch.on('elementMousemove', function(e) { | |
6277 lines.clearHighlights(); | |
6278 var singlePoint, pointIndex, pointXLocation, allData = []; | |
6279 data | |
6280 .filter(function(series, i) { | |
6281 series.seriesIndex = i; | |
6282 return !series.disabled; | |
6283 }) | |
6284 .forEach(function(series,i) { | |
6285 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); | |
6286 var point = series.values[pointIndex]; | |
6287 var pointYValue = chart.y()(point, pointIndex); | |
6288 if (pointYValue != null) { | |
6289 lines.highlightPoint(i, pointIndex, true); | |
6290 } | |
6291 if (point === undefined) return; | |
6292 if (singlePoint === undefined) singlePoint = point; | |
6293 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); | |
6294 allData.push({ | |
6295 key: series.key, | |
6296 value: pointYValue, | |
6297 color: color(series,series.seriesIndex) | |
6298 }); | |
6299 }); | |
6300 //Highlight the tooltip entry based on which point the mouse is closest to. | |
6301 if (allData.length > 2) { | |
6302 var yValue = chart.yScale().invert(e.mouseY); | |
6303 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); | |
6304 var threshold = 0.03 * domainExtent; | |
6305 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); | |
6306 if (indexToHighlight !== null) | |
6307 allData[indexToHighlight].highlight = true; | |
6308 } | |
6309 | |
6310 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); | |
6311 interactiveLayer.tooltip | |
6312 .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top}) | |
6313 .chartContainer(that.parentNode) | |
6314 .valueFormatter(function(d,i) { | |
6315 return d == null ? "N/A" : yAxis.tickFormat()(d); | |
6316 }) | |
6317 .data({ | |
6318 value: xValue, | |
6319 index: pointIndex, | |
6320 series: allData | |
6321 })(); | |
6322 | |
6323 interactiveLayer.renderGuideLine(pointXLocation); | |
6324 | |
6325 }); | |
6326 | |
6327 interactiveLayer.dispatch.on('elementClick', function(e) { | |
6328 var pointXLocation, allData = []; | |
6329 | |
6330 data.filter(function(series, i) { | |
6331 series.seriesIndex = i; | |
6332 return !series.disabled; | |
6333 }).forEach(function(series) { | |
6334 var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); | |
6335 var point = series.values[pointIndex]; | |
6336 if (typeof point === 'undefined') return; | |
6337 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); | |
6338 var yPos = chart.yScale()(chart.y()(point,pointIndex)); | |
6339 allData.push({ | |
6340 point: point, | |
6341 pointIndex: pointIndex, | |
6342 pos: [pointXLocation, yPos], | |
6343 seriesIndex: series.seriesIndex, | |
6344 series: series | |
6345 }); | |
6346 }); | |
6347 | |
6348 lines.dispatch.elementClick(allData); | |
6349 }); | |
6350 | |
6351 interactiveLayer.dispatch.on("elementMouseout",function(e) { | |
6352 lines.clearHighlights(); | |
6353 }); | |
6354 | |
6355 dispatch.on('changeState', function(e) { | |
6356 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) { | |
6357 data.forEach(function(series,i) { | |
6358 series.disabled = e.disabled[i]; | |
6359 }); | |
6360 | |
6361 state.disabled = e.disabled; | |
6362 } | |
6363 | |
6364 chart.update(); | |
6365 }); | |
6366 | |
6367 }); | |
6368 | |
6369 renderWatch.renderEnd('lineChart immediate'); | |
6370 return chart; | |
6371 } | |
6372 | |
6373 //============================================================ | |
6374 // Event Handling/Dispatching (out of chart's scope) | |
6375 //------------------------------------------------------------ | |
6376 | |
6377 lines.dispatch.on('elementMouseover.tooltip', function(evt) { | |
6378 tooltip.data(evt).position(evt.pos).hidden(false); | |
6379 }); | |
6380 | |
6381 lines.dispatch.on('elementMouseout.tooltip', function(evt) { | |
6382 tooltip.hidden(true) | |
6383 }); | |
6384 | |
6385 //============================================================ | |
6386 // Expose Public Variables | |
6387 //------------------------------------------------------------ | |
6388 | |
6389 // expose chart's sub-components | |
6390 chart.dispatch = dispatch; | |
6391 chart.lines = lines; | |
6392 chart.legend = legend; | |
6393 chart.xAxis = xAxis; | |
6394 chart.yAxis = yAxis; | |
6395 chart.interactiveLayer = interactiveLayer; | |
6396 chart.tooltip = tooltip; | |
6397 | |
6398 chart.dispatch = dispatch; | |
6399 chart.options = nv.utils.optionsFunc.bind(chart); | |
6400 | |
6401 chart._options = Object.create({}, { | |
6402 // simple options, just get/set the necessary values | |
6403 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
6404 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
6405 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
6406 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
6407 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
6408 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
6409 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
6410 | |
6411 // deprecated options | |
6412 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
6413 // deprecated after 1.7.1 | |
6414 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
6415 tooltip.enabled(!!_); | |
6416 }}, | |
6417 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
6418 // deprecated after 1.7.1 | |
6419 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
6420 tooltip.contentGenerator(_); | |
6421 }}, | |
6422 | |
6423 // options that require extra logic in the setter | |
6424 margin: {get: function(){return margin;}, set: function(_){ | |
6425 margin.top = _.top !== undefined ? _.top : margin.top; | |
6426 margin.right = _.right !== undefined ? _.right : margin.right; | |
6427 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
6428 margin.left = _.left !== undefined ? _.left : margin.left; | |
6429 }}, | |
6430 duration: {get: function(){return duration;}, set: function(_){ | |
6431 duration = _; | |
6432 renderWatch.reset(duration); | |
6433 lines.duration(duration); | |
6434 xAxis.duration(duration); | |
6435 yAxis.duration(duration); | |
6436 }}, | |
6437 color: {get: function(){return color;}, set: function(_){ | |
6438 color = nv.utils.getColor(_); | |
6439 legend.color(color); | |
6440 lines.color(color); | |
6441 }}, | |
6442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ | |
6443 rightAlignYAxis = _; | |
6444 yAxis.orient( rightAlignYAxis ? 'right' : 'left'); | |
6445 }}, | |
6446 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ | |
6447 useInteractiveGuideline = _; | |
6448 if (useInteractiveGuideline) { | |
6449 lines.interactive(false); | |
6450 lines.useVoronoi(false); | |
6451 } | |
6452 }} | |
6453 }); | |
6454 | |
6455 nv.utils.inheritOptions(chart, lines); | |
6456 nv.utils.initOptions(chart); | |
6457 | |
6458 return chart; | |
6459 }; | |
6460 nv.models.linePlusBarChart = function() { | |
6461 "use strict"; | |
6462 | |
6463 //============================================================ | |
6464 // Public Variables with Default Settings | |
6465 //------------------------------------------------------------ | |
6466 | |
6467 var lines = nv.models.line() | |
6468 , lines2 = nv.models.line() | |
6469 , bars = nv.models.historicalBar() | |
6470 , bars2 = nv.models.historicalBar() | |
6471 , xAxis = nv.models.axis() | |
6472 , x2Axis = nv.models.axis() | |
6473 , y1Axis = nv.models.axis() | |
6474 , y2Axis = nv.models.axis() | |
6475 , y3Axis = nv.models.axis() | |
6476 , y4Axis = nv.models.axis() | |
6477 , legend = nv.models.legend() | |
6478 , brush = d3.svg.brush() | |
6479 , tooltip = nv.models.tooltip() | |
6480 ; | |
6481 | |
6482 var margin = {top: 30, right: 30, bottom: 30, left: 60} | |
6483 , margin2 = {top: 0, right: 30, bottom: 20, left: 60} | |
6484 , width = null | |
6485 , height = null | |
6486 , getX = function(d) { return d.x } | |
6487 , getY = function(d) { return d.y } | |
6488 , color = nv.utils.defaultColor() | |
6489 , showLegend = true | |
6490 , focusEnable = true | |
6491 , focusShowAxisY = false | |
6492 , focusShowAxisX = true | |
6493 , focusHeight = 50 | |
6494 , extent | |
6495 , brushExtent = null | |
6496 , x | |
6497 , x2 | |
6498 , y1 | |
6499 , y2 | |
6500 , y3 | |
6501 , y4 | |
6502 , noData = null | |
6503 , dispatch = d3.dispatch('brush', 'stateChange', 'changeState') | |
6504 , transitionDuration = 0 | |
6505 , state = nv.utils.state() | |
6506 , defaultState = null | |
6507 , legendLeftAxisHint = ' (left axis)' | |
6508 , legendRightAxisHint = ' (right axis)' | |
6509 ; | |
6510 | |
6511 lines.clipEdge(true); | |
6512 lines2.interactive(false); | |
6513 xAxis.orient('bottom').tickPadding(5); | |
6514 y1Axis.orient('left'); | |
6515 y2Axis.orient('right'); | |
6516 x2Axis.orient('bottom').tickPadding(5); | |
6517 y3Axis.orient('left'); | |
6518 y4Axis.orient('right'); | |
6519 | |
6520 tooltip.headerEnabled(true).headerFormatter(function(d, i) { | |
6521 return xAxis.tickFormat()(d, i); | |
6522 }); | |
6523 | |
6524 //============================================================ | |
6525 // Private Variables | |
6526 //------------------------------------------------------------ | |
6527 | |
6528 var stateGetter = function(data) { | |
6529 return function(){ | |
6530 return { | |
6531 active: data.map(function(d) { return !d.disabled }) | |
6532 }; | |
6533 } | |
6534 }; | |
6535 | |
6536 var stateSetter = function(data) { | |
6537 return function(state) { | |
6538 if (state.active !== undefined) | |
6539 data.forEach(function(series,i) { | |
6540 series.disabled = !state.active[i]; | |
6541 }); | |
6542 } | |
6543 }; | |
6544 | |
6545 function chart(selection) { | |
6546 selection.each(function(data) { | |
6547 var container = d3.select(this), | |
6548 that = this; | |
6549 nv.utils.initSVG(container); | |
6550 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
6551 availableHeight1 = nv.utils.availableHeight(height, container, margin) | |
6552 - (focusEnable ? focusHeight : 0), | |
6553 availableHeight2 = focusHeight - margin2.top - margin2.bottom; | |
6554 | |
6555 chart.update = function() { container.transition().duration(transitionDuration).call(chart); }; | |
6556 chart.container = this; | |
6557 | |
6558 state | |
6559 .setter(stateSetter(data), chart.update) | |
6560 .getter(stateGetter(data)) | |
6561 .update(); | |
6562 | |
6563 // DEPRECATED set state.disableddisabled | |
6564 state.disabled = data.map(function(d) { return !!d.disabled }); | |
6565 | |
6566 if (!defaultState) { | |
6567 var key; | |
6568 defaultState = {}; | |
6569 for (key in state) { | |
6570 if (state[key] instanceof Array) | |
6571 defaultState[key] = state[key].slice(0); | |
6572 else | |
6573 defaultState[key] = state[key]; | |
6574 } | |
6575 } | |
6576 | |
6577 // Display No Data message if there's nothing to show. | |
6578 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
6579 nv.utils.noData(chart, container) | |
6580 return chart; | |
6581 } else { | |
6582 container.selectAll('.nv-noData').remove(); | |
6583 } | |
6584 | |
6585 // Setup Scales | |
6586 var dataBars = data.filter(function(d) { return !d.disabled && d.bar }); | |
6587 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240 | |
6588 | |
6589 x = bars.xScale(); | |
6590 x2 = x2Axis.scale(); | |
6591 y1 = bars.yScale(); | |
6592 y2 = lines.yScale(); | |
6593 y3 = bars2.yScale(); | |
6594 y4 = lines2.yScale(); | |
6595 | |
6596 var series1 = data | |
6597 .filter(function(d) { return !d.disabled && d.bar }) | |
6598 .map(function(d) { | |
6599 return d.values.map(function(d,i) { | |
6600 return { x: getX(d,i), y: getY(d,i) } | |
6601 }) | |
6602 }); | |
6603 | |
6604 var series2 = data | |
6605 .filter(function(d) { return !d.disabled && !d.bar }) | |
6606 .map(function(d) { | |
6607 return d.values.map(function(d,i) { | |
6608 return { x: getX(d,i), y: getY(d,i) } | |
6609 }) | |
6610 }); | |
6611 | |
6612 x.range([0, availableWidth]); | |
6613 | |
6614 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) | |
6615 .range([0, availableWidth]); | |
6616 | |
6617 // Setup containers and skeleton of chart | |
6618 var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]); | |
6619 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g'); | |
6620 var g = wrap.select('g'); | |
6621 | |
6622 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
6623 | |
6624 // this is the main chart | |
6625 var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); | |
6626 focusEnter.append('g').attr('class', 'nv-x nv-axis'); | |
6627 focusEnter.append('g').attr('class', 'nv-y1 nv-axis'); | |
6628 focusEnter.append('g').attr('class', 'nv-y2 nv-axis'); | |
6629 focusEnter.append('g').attr('class', 'nv-barsWrap'); | |
6630 focusEnter.append('g').attr('class', 'nv-linesWrap'); | |
6631 | |
6632 // context chart is where you can focus in | |
6633 var contextEnter = gEnter.append('g').attr('class', 'nv-context'); | |
6634 contextEnter.append('g').attr('class', 'nv-x nv-axis'); | |
6635 contextEnter.append('g').attr('class', 'nv-y1 nv-axis'); | |
6636 contextEnter.append('g').attr('class', 'nv-y2 nv-axis'); | |
6637 contextEnter.append('g').attr('class', 'nv-barsWrap'); | |
6638 contextEnter.append('g').attr('class', 'nv-linesWrap'); | |
6639 contextEnter.append('g').attr('class', 'nv-brushBackground'); | |
6640 contextEnter.append('g').attr('class', 'nv-x nv-brush'); | |
6641 | |
6642 //============================================================ | |
6643 // Legend | |
6644 //------------------------------------------------------------ | |
6645 | |
6646 if (showLegend) { | |
6647 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; | |
6648 var legendXPosition = legend.align() ? legendWidth : 0; | |
6649 | |
6650 legend.width(legendWidth); | |
6651 | |
6652 g.select('.nv-legendWrap') | |
6653 .datum(data.map(function(series) { | |
6654 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; | |
6655 series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint); | |
6656 return series; | |
6657 })) | |
6658 .call(legend); | |
6659 | |
6660 if ( margin.top != legend.height()) { | |
6661 margin.top = legend.height(); | |
6662 // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"? | |
6663 availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight; | |
6664 } | |
6665 | |
6666 g.select('.nv-legendWrap') | |
6667 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); | |
6668 } | |
6669 | |
6670 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
6671 | |
6672 //============================================================ | |
6673 // Context chart (focus chart) components | |
6674 //------------------------------------------------------------ | |
6675 | |
6676 // hide or show the focus context chart | |
6677 g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none'); | |
6678 | |
6679 bars2 | |
6680 .width(availableWidth) | |
6681 .height(availableHeight2) | |
6682 .color(data.map(function (d, i) { | |
6683 return d.color || color(d, i); | |
6684 }).filter(function (d, i) { | |
6685 return !data[i].disabled && data[i].bar | |
6686 })); | |
6687 lines2 | |
6688 .width(availableWidth) | |
6689 .height(availableHeight2) | |
6690 .color(data.map(function (d, i) { | |
6691 return d.color || color(d, i); | |
6692 }).filter(function (d, i) { | |
6693 return !data[i].disabled && !data[i].bar | |
6694 })); | |
6695 | |
6696 var bars2Wrap = g.select('.nv-context .nv-barsWrap') | |
6697 .datum(dataBars.length ? dataBars : [ | |
6698 {values: []} | |
6699 ]); | |
6700 var lines2Wrap = g.select('.nv-context .nv-linesWrap') | |
6701 .datum(!dataLines[0].disabled ? dataLines : [ | |
6702 {values: []} | |
6703 ]); | |
6704 | |
6705 g.select('.nv-context') | |
6706 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')'); | |
6707 | |
6708 bars2Wrap.transition().call(bars2); | |
6709 lines2Wrap.transition().call(lines2); | |
6710 | |
6711 // context (focus chart) axis controls | |
6712 if (focusShowAxisX) { | |
6713 x2Axis | |
6714 ._ticks( nv.utils.calcTicksX(availableWidth / 100, data)) | |
6715 .tickSize(-availableHeight2, 0); | |
6716 g.select('.nv-context .nv-x.nv-axis') | |
6717 .attr('transform', 'translate(0,' + y3.range()[0] + ')'); | |
6718 g.select('.nv-context .nv-x.nv-axis').transition() | |
6719 .call(x2Axis); | |
6720 } | |
6721 | |
6722 if (focusShowAxisY) { | |
6723 y3Axis | |
6724 .scale(y3) | |
6725 ._ticks( availableHeight2 / 36 ) | |
6726 .tickSize( -availableWidth, 0); | |
6727 y4Axis | |
6728 .scale(y4) | |
6729 ._ticks( availableHeight2 / 36 ) | |
6730 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none | |
6731 | |
6732 g.select('.nv-context .nv-y3.nv-axis') | |
6733 .style('opacity', dataBars.length ? 1 : 0) | |
6734 .attr('transform', 'translate(0,' + x2.range()[0] + ')'); | |
6735 g.select('.nv-context .nv-y2.nv-axis') | |
6736 .style('opacity', dataLines.length ? 1 : 0) | |
6737 .attr('transform', 'translate(' + x2.range()[1] + ',0)'); | |
6738 | |
6739 g.select('.nv-context .nv-y1.nv-axis').transition() | |
6740 .call(y3Axis); | |
6741 g.select('.nv-context .nv-y2.nv-axis').transition() | |
6742 .call(y4Axis); | |
6743 } | |
6744 | |
6745 // Setup Brush | |
6746 brush.x(x2).on('brush', onBrush); | |
6747 | |
6748 if (brushExtent) brush.extent(brushExtent); | |
6749 | |
6750 var brushBG = g.select('.nv-brushBackground').selectAll('g') | |
6751 .data([brushExtent || brush.extent()]); | |
6752 | |
6753 var brushBGenter = brushBG.enter() | |
6754 .append('g'); | |
6755 | |
6756 brushBGenter.append('rect') | |
6757 .attr('class', 'left') | |
6758 .attr('x', 0) | |
6759 .attr('y', 0) | |
6760 .attr('height', availableHeight2); | |
6761 | |
6762 brushBGenter.append('rect') | |
6763 .attr('class', 'right') | |
6764 .attr('x', 0) | |
6765 .attr('y', 0) | |
6766 .attr('height', availableHeight2); | |
6767 | |
6768 var gBrush = g.select('.nv-x.nv-brush') | |
6769 .call(brush); | |
6770 gBrush.selectAll('rect') | |
6771 //.attr('y', -5) | |
6772 .attr('height', availableHeight2); | |
6773 gBrush.selectAll('.resize').append('path').attr('d', resizePath); | |
6774 | |
6775 //============================================================ | |
6776 // Event Handling/Dispatching (in chart's scope) | |
6777 //------------------------------------------------------------ | |
6778 | |
6779 legend.dispatch.on('stateChange', function(newState) { | |
6780 for (var key in newState) | |
6781 state[key] = newState[key]; | |
6782 dispatch.stateChange(state); | |
6783 chart.update(); | |
6784 }); | |
6785 | |
6786 // Update chart from a state object passed to event handler | |
6787 dispatch.on('changeState', function(e) { | |
6788 if (typeof e.disabled !== 'undefined') { | |
6789 data.forEach(function(series,i) { | |
6790 series.disabled = e.disabled[i]; | |
6791 }); | |
6792 state.disabled = e.disabled; | |
6793 } | |
6794 chart.update(); | |
6795 }); | |
6796 | |
6797 //============================================================ | |
6798 // Functions | |
6799 //------------------------------------------------------------ | |
6800 | |
6801 // Taken from crossfilter (http://square.github.com/crossfilter/) | |
6802 function resizePath(d) { | |
6803 var e = +(d == 'e'), | |
6804 x = e ? 1 : -1, | |
6805 y = availableHeight2 / 3; | |
6806 return 'M' + (.5 * x) + ',' + y | |
6807 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) | |
6808 + 'V' + (2 * y - 6) | |
6809 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) | |
6810 + 'Z' | |
6811 + 'M' + (2.5 * x) + ',' + (y + 8) | |
6812 + 'V' + (2 * y - 8) | |
6813 + 'M' + (4.5 * x) + ',' + (y + 8) | |
6814 + 'V' + (2 * y - 8); | |
6815 } | |
6816 | |
6817 | |
6818 function updateBrushBG() { | |
6819 if (!brush.empty()) brush.extent(brushExtent); | |
6820 brushBG | |
6821 .data([brush.empty() ? x2.domain() : brushExtent]) | |
6822 .each(function(d,i) { | |
6823 var leftWidth = x2(d[0]) - x2.range()[0], | |
6824 rightWidth = x2.range()[1] - x2(d[1]); | |
6825 d3.select(this).select('.left') | |
6826 .attr('width', leftWidth < 0 ? 0 : leftWidth); | |
6827 | |
6828 d3.select(this).select('.right') | |
6829 .attr('x', x2(d[1])) | |
6830 .attr('width', rightWidth < 0 ? 0 : rightWidth); | |
6831 }); | |
6832 } | |
6833 | |
6834 function onBrush() { | |
6835 brushExtent = brush.empty() ? null : brush.extent(); | |
6836 extent = brush.empty() ? x2.domain() : brush.extent(); | |
6837 dispatch.brush({extent: extent, brush: brush}); | |
6838 updateBrushBG(); | |
6839 | |
6840 // Prepare Main (Focus) Bars and Lines | |
6841 bars | |
6842 .width(availableWidth) | |
6843 .height(availableHeight1) | |
6844 .color(data.map(function(d,i) { | |
6845 return d.color || color(d, i); | |
6846 }).filter(function(d,i) { return !data[i].disabled && data[i].bar })); | |
6847 | |
6848 lines | |
6849 .width(availableWidth) | |
6850 .height(availableHeight1) | |
6851 .color(data.map(function(d,i) { | |
6852 return d.color || color(d, i); | |
6853 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar })); | |
6854 | |
6855 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap') | |
6856 .datum(!dataBars.length ? [{values:[]}] : | |
6857 dataBars | |
6858 .map(function(d,i) { | |
6859 return { | |
6860 key: d.key, | |
6861 values: d.values.filter(function(d,i) { | |
6862 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1]; | |
6863 }) | |
6864 } | |
6865 }) | |
6866 ); | |
6867 | |
6868 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') | |
6869 .datum(dataLines[0].disabled ? [{values:[]}] : | |
6870 dataLines | |
6871 .map(function(d,i) { | |
6872 return { | |
6873 area: d.area, | |
6874 fillOpacity: d.fillOpacity, | |
6875 key: d.key, | |
6876 values: d.values.filter(function(d,i) { | |
6877 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; | |
6878 }) | |
6879 } | |
6880 }) | |
6881 ); | |
6882 | |
6883 // Update Main (Focus) X Axis | |
6884 if (dataBars.length) { | |
6885 x = bars.xScale(); | |
6886 } else { | |
6887 x = lines.xScale(); | |
6888 } | |
6889 | |
6890 xAxis | |
6891 .scale(x) | |
6892 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
6893 .tickSize(-availableHeight1, 0); | |
6894 | |
6895 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]); | |
6896 | |
6897 g.select('.nv-x.nv-axis').transition().duration(transitionDuration) | |
6898 .call(xAxis); | |
6899 | |
6900 // Update Main (Focus) Bars and Lines | |
6901 focusBarsWrap.transition().duration(transitionDuration).call(bars); | |
6902 focusLinesWrap.transition().duration(transitionDuration).call(lines); | |
6903 | |
6904 // Setup and Update Main (Focus) Y Axes | |
6905 g.select('.nv-focus .nv-x.nv-axis') | |
6906 .attr('transform', 'translate(0,' + y1.range()[0] + ')'); | |
6907 | |
6908 y1Axis | |
6909 .scale(y1) | |
6910 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) | |
6911 .tickSize(-availableWidth, 0); | |
6912 y2Axis | |
6913 .scale(y2) | |
6914 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) | |
6915 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none | |
6916 | |
6917 g.select('.nv-focus .nv-y1.nv-axis') | |
6918 .style('opacity', dataBars.length ? 1 : 0); | |
6919 g.select('.nv-focus .nv-y2.nv-axis') | |
6920 .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0) | |
6921 .attr('transform', 'translate(' + x.range()[1] + ',0)'); | |
6922 | |
6923 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration) | |
6924 .call(y1Axis); | |
6925 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration) | |
6926 .call(y2Axis); | |
6927 } | |
6928 | |
6929 onBrush(); | |
6930 | |
6931 }); | |
6932 | |
6933 return chart; | |
6934 } | |
6935 | |
6936 //============================================================ | |
6937 // Event Handling/Dispatching (out of chart's scope) | |
6938 //------------------------------------------------------------ | |
6939 | |
6940 lines.dispatch.on('elementMouseover.tooltip', function(evt) { | |
6941 tooltip | |
6942 .duration(100) | |
6943 .valueFormatter(function(d, i) { | |
6944 return y2Axis.tickFormat()(d, i); | |
6945 }) | |
6946 .data(evt) | |
6947 .position(evt.pos) | |
6948 .hidden(false); | |
6949 }); | |
6950 | |
6951 lines.dispatch.on('elementMouseout.tooltip', function(evt) { | |
6952 tooltip.hidden(true) | |
6953 }); | |
6954 | |
6955 bars.dispatch.on('elementMouseover.tooltip', function(evt) { | |
6956 evt.value = chart.x()(evt.data); | |
6957 evt['series'] = { | |
6958 value: chart.y()(evt.data), | |
6959 color: evt.color | |
6960 }; | |
6961 tooltip | |
6962 .duration(0) | |
6963 .valueFormatter(function(d, i) { | |
6964 return y1Axis.tickFormat()(d, i); | |
6965 }) | |
6966 .data(evt) | |
6967 .hidden(false); | |
6968 }); | |
6969 | |
6970 bars.dispatch.on('elementMouseout.tooltip', function(evt) { | |
6971 tooltip.hidden(true); | |
6972 }); | |
6973 | |
6974 bars.dispatch.on('elementMousemove.tooltip', function(evt) { | |
6975 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
6976 }); | |
6977 | |
6978 //============================================================ | |
6979 | |
6980 | |
6981 //============================================================ | |
6982 // Expose Public Variables | |
6983 //------------------------------------------------------------ | |
6984 | |
6985 // expose chart's sub-components | |
6986 chart.dispatch = dispatch; | |
6987 chart.legend = legend; | |
6988 chart.lines = lines; | |
6989 chart.lines2 = lines2; | |
6990 chart.bars = bars; | |
6991 chart.bars2 = bars2; | |
6992 chart.xAxis = xAxis; | |
6993 chart.x2Axis = x2Axis; | |
6994 chart.y1Axis = y1Axis; | |
6995 chart.y2Axis = y2Axis; | |
6996 chart.y3Axis = y3Axis; | |
6997 chart.y4Axis = y4Axis; | |
6998 chart.tooltip = tooltip; | |
6999 | |
7000 chart.options = nv.utils.optionsFunc.bind(chart); | |
7001 | |
7002 chart._options = Object.create({}, { | |
7003 // simple options, just get/set the necessary values | |
7004 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
7005 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
7006 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
7007 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, | |
7008 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
7009 focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}}, | |
7010 focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}}, | |
7011 focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}}, | |
7012 focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}}, | |
7013 legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}}, | |
7014 legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}}, | |
7015 | |
7016 // deprecated options | |
7017 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
7018 // deprecated after 1.7.1 | |
7019 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
7020 tooltip.enabled(!!_); | |
7021 }}, | |
7022 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
7023 // deprecated after 1.7.1 | |
7024 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
7025 tooltip.contentGenerator(_); | |
7026 }}, | |
7027 | |
7028 // options that require extra logic in the setter | |
7029 margin: {get: function(){return margin;}, set: function(_){ | |
7030 margin.top = _.top !== undefined ? _.top : margin.top; | |
7031 margin.right = _.right !== undefined ? _.right : margin.right; | |
7032 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
7033 margin.left = _.left !== undefined ? _.left : margin.left; | |
7034 }}, | |
7035 duration: {get: function(){return transitionDuration;}, set: function(_){ | |
7036 transitionDuration = _; | |
7037 }}, | |
7038 color: {get: function(){return color;}, set: function(_){ | |
7039 color = nv.utils.getColor(_); | |
7040 legend.color(color); | |
7041 }}, | |
7042 x: {get: function(){return getX;}, set: function(_){ | |
7043 getX = _; | |
7044 lines.x(_); | |
7045 lines2.x(_); | |
7046 bars.x(_); | |
7047 bars2.x(_); | |
7048 }}, | |
7049 y: {get: function(){return getY;}, set: function(_){ | |
7050 getY = _; | |
7051 lines.y(_); | |
7052 lines2.y(_); | |
7053 bars.y(_); | |
7054 bars2.y(_); | |
7055 }} | |
7056 }); | |
7057 | |
7058 nv.utils.inheritOptions(chart, lines); | |
7059 nv.utils.initOptions(chart); | |
7060 | |
7061 return chart; | |
7062 }; | |
7063 nv.models.lineWithFocusChart = function() { | |
7064 "use strict"; | |
7065 | |
7066 //============================================================ | |
7067 // Public Variables with Default Settings | |
7068 //------------------------------------------------------------ | |
7069 | |
7070 var lines = nv.models.line() | |
7071 , lines2 = nv.models.line() | |
7072 , xAxis = nv.models.axis() | |
7073 , yAxis = nv.models.axis() | |
7074 , x2Axis = nv.models.axis() | |
7075 , y2Axis = nv.models.axis() | |
7076 , legend = nv.models.legend() | |
7077 , brush = d3.svg.brush() | |
7078 , tooltip = nv.models.tooltip() | |
7079 , interactiveLayer = nv.interactiveGuideline() | |
7080 ; | |
7081 | |
7082 var margin = {top: 30, right: 30, bottom: 30, left: 60} | |
7083 , margin2 = {top: 0, right: 30, bottom: 20, left: 60} | |
7084 , color = nv.utils.defaultColor() | |
7085 , width = null | |
7086 , height = null | |
7087 , height2 = 50 | |
7088 , useInteractiveGuideline = false | |
7089 , x | |
7090 , y | |
7091 , x2 | |
7092 , y2 | |
7093 , showLegend = true | |
7094 , brushExtent = null | |
7095 , noData = null | |
7096 , dispatch = d3.dispatch('brush', 'stateChange', 'changeState') | |
7097 , transitionDuration = 250 | |
7098 , state = nv.utils.state() | |
7099 , defaultState = null | |
7100 ; | |
7101 | |
7102 lines.clipEdge(true).duration(0); | |
7103 lines2.interactive(false); | |
7104 xAxis.orient('bottom').tickPadding(5); | |
7105 yAxis.orient('left'); | |
7106 x2Axis.orient('bottom').tickPadding(5); | |
7107 y2Axis.orient('left'); | |
7108 | |
7109 tooltip.valueFormatter(function(d, i) { | |
7110 return yAxis.tickFormat()(d, i); | |
7111 }).headerFormatter(function(d, i) { | |
7112 return xAxis.tickFormat()(d, i); | |
7113 }); | |
7114 | |
7115 //============================================================ | |
7116 // Private Variables | |
7117 //------------------------------------------------------------ | |
7118 | |
7119 var stateGetter = function(data) { | |
7120 return function(){ | |
7121 return { | |
7122 active: data.map(function(d) { return !d.disabled }) | |
7123 }; | |
7124 } | |
7125 }; | |
7126 | |
7127 var stateSetter = function(data) { | |
7128 return function(state) { | |
7129 if (state.active !== undefined) | |
7130 data.forEach(function(series,i) { | |
7131 series.disabled = !state.active[i]; | |
7132 }); | |
7133 } | |
7134 }; | |
7135 | |
7136 function chart(selection) { | |
7137 selection.each(function(data) { | |
7138 var container = d3.select(this), | |
7139 that = this; | |
7140 nv.utils.initSVG(container); | |
7141 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
7142 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2, | |
7143 availableHeight2 = height2 - margin2.top - margin2.bottom; | |
7144 | |
7145 chart.update = function() { container.transition().duration(transitionDuration).call(chart) }; | |
7146 chart.container = this; | |
7147 | |
7148 state | |
7149 .setter(stateSetter(data), chart.update) | |
7150 .getter(stateGetter(data)) | |
7151 .update(); | |
7152 | |
7153 // DEPRECATED set state.disableddisabled | |
7154 state.disabled = data.map(function(d) { return !!d.disabled }); | |
7155 | |
7156 if (!defaultState) { | |
7157 var key; | |
7158 defaultState = {}; | |
7159 for (key in state) { | |
7160 if (state[key] instanceof Array) | |
7161 defaultState[key] = state[key].slice(0); | |
7162 else | |
7163 defaultState[key] = state[key]; | |
7164 } | |
7165 } | |
7166 | |
7167 // Display No Data message if there's nothing to show. | |
7168 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
7169 nv.utils.noData(chart, container) | |
7170 return chart; | |
7171 } else { | |
7172 container.selectAll('.nv-noData').remove(); | |
7173 } | |
7174 | |
7175 // Setup Scales | |
7176 x = lines.xScale(); | |
7177 y = lines.yScale(); | |
7178 x2 = lines2.xScale(); | |
7179 y2 = lines2.yScale(); | |
7180 | |
7181 // Setup containers and skeleton of chart | |
7182 var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]); | |
7183 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g'); | |
7184 var g = wrap.select('g'); | |
7185 | |
7186 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
7187 | |
7188 var focusEnter = gEnter.append('g').attr('class', 'nv-focus'); | |
7189 focusEnter.append('g').attr('class', 'nv-x nv-axis'); | |
7190 focusEnter.append('g').attr('class', 'nv-y nv-axis'); | |
7191 focusEnter.append('g').attr('class', 'nv-linesWrap'); | |
7192 focusEnter.append('g').attr('class', 'nv-interactive'); | |
7193 | |
7194 var contextEnter = gEnter.append('g').attr('class', 'nv-context'); | |
7195 contextEnter.append('g').attr('class', 'nv-x nv-axis'); | |
7196 contextEnter.append('g').attr('class', 'nv-y nv-axis'); | |
7197 contextEnter.append('g').attr('class', 'nv-linesWrap'); | |
7198 contextEnter.append('g').attr('class', 'nv-brushBackground'); | |
7199 contextEnter.append('g').attr('class', 'nv-x nv-brush'); | |
7200 | |
7201 // Legend | |
7202 if (showLegend) { | |
7203 legend.width(availableWidth); | |
7204 | |
7205 g.select('.nv-legendWrap') | |
7206 .datum(data) | |
7207 .call(legend); | |
7208 | |
7209 if ( margin.top != legend.height()) { | |
7210 margin.top = legend.height(); | |
7211 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2; | |
7212 } | |
7213 | |
7214 g.select('.nv-legendWrap') | |
7215 .attr('transform', 'translate(0,' + (-margin.top) +')') | |
7216 } | |
7217 | |
7218 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
7219 | |
7220 | |
7221 //Set up interactive layer | |
7222 if (useInteractiveGuideline) { | |
7223 interactiveLayer | |
7224 .width(availableWidth) | |
7225 .height(availableHeight1) | |
7226 .margin({left:margin.left, top:margin.top}) | |
7227 .svgContainer(container) | |
7228 .xScale(x); | |
7229 wrap.select(".nv-interactive").call(interactiveLayer); | |
7230 } | |
7231 | |
7232 // Main Chart Component(s) | |
7233 lines | |
7234 .width(availableWidth) | |
7235 .height(availableHeight1) | |
7236 .color( | |
7237 data | |
7238 .map(function(d,i) { | |
7239 return d.color || color(d, i); | |
7240 }) | |
7241 .filter(function(d,i) { | |
7242 return !data[i].disabled; | |
7243 }) | |
7244 ); | |
7245 | |
7246 lines2 | |
7247 .defined(lines.defined()) | |
7248 .width(availableWidth) | |
7249 .height(availableHeight2) | |
7250 .color( | |
7251 data | |
7252 .map(function(d,i) { | |
7253 return d.color || color(d, i); | |
7254 }) | |
7255 .filter(function(d,i) { | |
7256 return !data[i].disabled; | |
7257 }) | |
7258 ); | |
7259 | |
7260 g.select('.nv-context') | |
7261 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')') | |
7262 | |
7263 var contextLinesWrap = g.select('.nv-context .nv-linesWrap') | |
7264 .datum(data.filter(function(d) { return !d.disabled })) | |
7265 | |
7266 d3.transition(contextLinesWrap).call(lines2); | |
7267 | |
7268 // Setup Main (Focus) Axes | |
7269 xAxis | |
7270 .scale(x) | |
7271 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
7272 .tickSize(-availableHeight1, 0); | |
7273 | |
7274 yAxis | |
7275 .scale(y) | |
7276 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) ) | |
7277 .tickSize( -availableWidth, 0); | |
7278 | |
7279 g.select('.nv-focus .nv-x.nv-axis') | |
7280 .attr('transform', 'translate(0,' + availableHeight1 + ')'); | |
7281 | |
7282 // Setup Brush | |
7283 brush | |
7284 .x(x2) | |
7285 .on('brush', function() { | |
7286 onBrush(); | |
7287 }); | |
7288 | |
7289 if (brushExtent) brush.extent(brushExtent); | |
7290 | |
7291 var brushBG = g.select('.nv-brushBackground').selectAll('g') | |
7292 .data([brushExtent || brush.extent()]) | |
7293 | |
7294 var brushBGenter = brushBG.enter() | |
7295 .append('g'); | |
7296 | |
7297 brushBGenter.append('rect') | |
7298 .attr('class', 'left') | |
7299 .attr('x', 0) | |
7300 .attr('y', 0) | |
7301 .attr('height', availableHeight2); | |
7302 | |
7303 brushBGenter.append('rect') | |
7304 .attr('class', 'right') | |
7305 .attr('x', 0) | |
7306 .attr('y', 0) | |
7307 .attr('height', availableHeight2); | |
7308 | |
7309 var gBrush = g.select('.nv-x.nv-brush') | |
7310 .call(brush); | |
7311 gBrush.selectAll('rect') | |
7312 .attr('height', availableHeight2); | |
7313 gBrush.selectAll('.resize').append('path').attr('d', resizePath); | |
7314 | |
7315 onBrush(); | |
7316 | |
7317 // Setup Secondary (Context) Axes | |
7318 x2Axis | |
7319 .scale(x2) | |
7320 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
7321 .tickSize(-availableHeight2, 0); | |
7322 | |
7323 g.select('.nv-context .nv-x.nv-axis') | |
7324 .attr('transform', 'translate(0,' + y2.range()[0] + ')'); | |
7325 d3.transition(g.select('.nv-context .nv-x.nv-axis')) | |
7326 .call(x2Axis); | |
7327 | |
7328 y2Axis | |
7329 .scale(y2) | |
7330 ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) ) | |
7331 .tickSize( -availableWidth, 0); | |
7332 | |
7333 d3.transition(g.select('.nv-context .nv-y.nv-axis')) | |
7334 .call(y2Axis); | |
7335 | |
7336 g.select('.nv-context .nv-x.nv-axis') | |
7337 .attr('transform', 'translate(0,' + y2.range()[0] + ')'); | |
7338 | |
7339 //============================================================ | |
7340 // Event Handling/Dispatching (in chart's scope) | |
7341 //------------------------------------------------------------ | |
7342 | |
7343 legend.dispatch.on('stateChange', function(newState) { | |
7344 for (var key in newState) | |
7345 state[key] = newState[key]; | |
7346 dispatch.stateChange(state); | |
7347 chart.update(); | |
7348 }); | |
7349 | |
7350 interactiveLayer.dispatch.on('elementMousemove', function(e) { | |
7351 lines.clearHighlights(); | |
7352 var singlePoint, pointIndex, pointXLocation, allData = []; | |
7353 data | |
7354 .filter(function(series, i) { | |
7355 series.seriesIndex = i; | |
7356 return !series.disabled; | |
7357 }) | |
7358 .forEach(function(series,i) { | |
7359 var extent = brush.empty() ? x2.domain() : brush.extent(); | |
7360 var currentValues = series.values.filter(function(d,i) { | |
7361 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; | |
7362 }); | |
7363 | |
7364 pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x()); | |
7365 var point = currentValues[pointIndex]; | |
7366 var pointYValue = chart.y()(point, pointIndex); | |
7367 if (pointYValue != null) { | |
7368 lines.highlightPoint(i, pointIndex, true); | |
7369 } | |
7370 if (point === undefined) return; | |
7371 if (singlePoint === undefined) singlePoint = point; | |
7372 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); | |
7373 allData.push({ | |
7374 key: series.key, | |
7375 value: chart.y()(point, pointIndex), | |
7376 color: color(series,series.seriesIndex) | |
7377 }); | |
7378 }); | |
7379 //Highlight the tooltip entry based on which point the mouse is closest to. | |
7380 if (allData.length > 2) { | |
7381 var yValue = chart.yScale().invert(e.mouseY); | |
7382 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]); | |
7383 var threshold = 0.03 * domainExtent; | |
7384 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold); | |
7385 if (indexToHighlight !== null) | |
7386 allData[indexToHighlight].highlight = true; | |
7387 } | |
7388 | |
7389 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); | |
7390 interactiveLayer.tooltip | |
7391 .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top}) | |
7392 .chartContainer(that.parentNode) | |
7393 .valueFormatter(function(d,i) { | |
7394 return d == null ? "N/A" : yAxis.tickFormat()(d); | |
7395 }) | |
7396 .data({ | |
7397 value: xValue, | |
7398 index: pointIndex, | |
7399 series: allData | |
7400 })(); | |
7401 | |
7402 interactiveLayer.renderGuideLine(pointXLocation); | |
7403 | |
7404 }); | |
7405 | |
7406 interactiveLayer.dispatch.on("elementMouseout",function(e) { | |
7407 lines.clearHighlights(); | |
7408 }); | |
7409 | |
7410 dispatch.on('changeState', function(e) { | |
7411 if (typeof e.disabled !== 'undefined') { | |
7412 data.forEach(function(series,i) { | |
7413 series.disabled = e.disabled[i]; | |
7414 }); | |
7415 } | |
7416 chart.update(); | |
7417 }); | |
7418 | |
7419 //============================================================ | |
7420 // Functions | |
7421 //------------------------------------------------------------ | |
7422 | |
7423 // Taken from crossfilter (http://square.github.com/crossfilter/) | |
7424 function resizePath(d) { | |
7425 var e = +(d == 'e'), | |
7426 x = e ? 1 : -1, | |
7427 y = availableHeight2 / 3; | |
7428 return 'M' + (.5 * x) + ',' + y | |
7429 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) | |
7430 + 'V' + (2 * y - 6) | |
7431 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y) | |
7432 + 'Z' | |
7433 + 'M' + (2.5 * x) + ',' + (y + 8) | |
7434 + 'V' + (2 * y - 8) | |
7435 + 'M' + (4.5 * x) + ',' + (y + 8) | |
7436 + 'V' + (2 * y - 8); | |
7437 } | |
7438 | |
7439 | |
7440 function updateBrushBG() { | |
7441 if (!brush.empty()) brush.extent(brushExtent); | |
7442 brushBG | |
7443 .data([brush.empty() ? x2.domain() : brushExtent]) | |
7444 .each(function(d,i) { | |
7445 var leftWidth = x2(d[0]) - x.range()[0], | |
7446 rightWidth = availableWidth - x2(d[1]); | |
7447 d3.select(this).select('.left') | |
7448 .attr('width', leftWidth < 0 ? 0 : leftWidth); | |
7449 | |
7450 d3.select(this).select('.right') | |
7451 .attr('x', x2(d[1])) | |
7452 .attr('width', rightWidth < 0 ? 0 : rightWidth); | |
7453 }); | |
7454 } | |
7455 | |
7456 | |
7457 function onBrush() { | |
7458 brushExtent = brush.empty() ? null : brush.extent(); | |
7459 var extent = brush.empty() ? x2.domain() : brush.extent(); | |
7460 | |
7461 //The brush extent cannot be less than one. If it is, don't update the line chart. | |
7462 if (Math.abs(extent[0] - extent[1]) <= 1) { | |
7463 return; | |
7464 } | |
7465 | |
7466 dispatch.brush({extent: extent, brush: brush}); | |
7467 | |
7468 | |
7469 updateBrushBG(); | |
7470 | |
7471 // Update Main (Focus) | |
7472 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap') | |
7473 .datum( | |
7474 data | |
7475 .filter(function(d) { return !d.disabled }) | |
7476 .map(function(d,i) { | |
7477 return { | |
7478 key: d.key, | |
7479 area: d.area, | |
7480 values: d.values.filter(function(d,i) { | |
7481 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1]; | |
7482 }) | |
7483 } | |
7484 }) | |
7485 ); | |
7486 focusLinesWrap.transition().duration(transitionDuration).call(lines); | |
7487 | |
7488 | |
7489 // Update Main (Focus) Axes | |
7490 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration) | |
7491 .call(xAxis); | |
7492 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration) | |
7493 .call(yAxis); | |
7494 } | |
7495 }); | |
7496 | |
7497 return chart; | |
7498 } | |
7499 | |
7500 //============================================================ | |
7501 // Event Handling/Dispatching (out of chart's scope) | |
7502 //------------------------------------------------------------ | |
7503 | |
7504 lines.dispatch.on('elementMouseover.tooltip', function(evt) { | |
7505 tooltip.data(evt).position(evt.pos).hidden(false); | |
7506 }); | |
7507 | |
7508 lines.dispatch.on('elementMouseout.tooltip', function(evt) { | |
7509 tooltip.hidden(true) | |
7510 }); | |
7511 | |
7512 //============================================================ | |
7513 // Expose Public Variables | |
7514 //------------------------------------------------------------ | |
7515 | |
7516 // expose chart's sub-components | |
7517 chart.dispatch = dispatch; | |
7518 chart.legend = legend; | |
7519 chart.lines = lines; | |
7520 chart.lines2 = lines2; | |
7521 chart.xAxis = xAxis; | |
7522 chart.yAxis = yAxis; | |
7523 chart.x2Axis = x2Axis; | |
7524 chart.y2Axis = y2Axis; | |
7525 chart.interactiveLayer = interactiveLayer; | |
7526 chart.tooltip = tooltip; | |
7527 | |
7528 chart.options = nv.utils.optionsFunc.bind(chart); | |
7529 | |
7530 chart._options = Object.create({}, { | |
7531 // simple options, just get/set the necessary values | |
7532 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
7533 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
7534 focusHeight: {get: function(){return height2;}, set: function(_){height2=_;}}, | |
7535 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
7536 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}}, | |
7537 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
7538 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
7539 | |
7540 // deprecated options | |
7541 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
7542 // deprecated after 1.7.1 | |
7543 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
7544 tooltip.enabled(!!_); | |
7545 }}, | |
7546 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
7547 // deprecated after 1.7.1 | |
7548 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
7549 tooltip.contentGenerator(_); | |
7550 }}, | |
7551 | |
7552 // options that require extra logic in the setter | |
7553 margin: {get: function(){return margin;}, set: function(_){ | |
7554 margin.top = _.top !== undefined ? _.top : margin.top; | |
7555 margin.right = _.right !== undefined ? _.right : margin.right; | |
7556 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
7557 margin.left = _.left !== undefined ? _.left : margin.left; | |
7558 }}, | |
7559 color: {get: function(){return color;}, set: function(_){ | |
7560 color = nv.utils.getColor(_); | |
7561 legend.color(color); | |
7562 // line color is handled above? | |
7563 }}, | |
7564 interpolate: {get: function(){return lines.interpolate();}, set: function(_){ | |
7565 lines.interpolate(_); | |
7566 lines2.interpolate(_); | |
7567 }}, | |
7568 xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){ | |
7569 xAxis.tickFormat(_); | |
7570 x2Axis.tickFormat(_); | |
7571 }}, | |
7572 yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){ | |
7573 yAxis.tickFormat(_); | |
7574 y2Axis.tickFormat(_); | |
7575 }}, | |
7576 duration: {get: function(){return transitionDuration;}, set: function(_){ | |
7577 transitionDuration=_; | |
7578 yAxis.duration(transitionDuration); | |
7579 y2Axis.duration(transitionDuration); | |
7580 xAxis.duration(transitionDuration); | |
7581 x2Axis.duration(transitionDuration); | |
7582 }}, | |
7583 x: {get: function(){return lines.x();}, set: function(_){ | |
7584 lines.x(_); | |
7585 lines2.x(_); | |
7586 }}, | |
7587 y: {get: function(){return lines.y();}, set: function(_){ | |
7588 lines.y(_); | |
7589 lines2.y(_); | |
7590 }}, | |
7591 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ | |
7592 useInteractiveGuideline = _; | |
7593 if (useInteractiveGuideline) { | |
7594 lines.interactive(false); | |
7595 lines.useVoronoi(false); | |
7596 } | |
7597 }} | |
7598 }); | |
7599 | |
7600 nv.utils.inheritOptions(chart, lines); | |
7601 nv.utils.initOptions(chart); | |
7602 | |
7603 return chart; | |
7604 }; | |
7605 | |
7606 nv.models.multiBar = function() { | |
7607 "use strict"; | |
7608 | |
7609 //============================================================ | |
7610 // Public Variables with Default Settings | |
7611 //------------------------------------------------------------ | |
7612 | |
7613 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
7614 , width = 960 | |
7615 , height = 500 | |
7616 , x = d3.scale.ordinal() | |
7617 , y = d3.scale.linear() | |
7618 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
7619 , container = null | |
7620 , getX = function(d) { return d.x } | |
7621 , getY = function(d) { return d.y } | |
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 | |
7623 , clipEdge = true | |
7624 , stacked = false | |
7625 , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function | |
7626 , color = nv.utils.defaultColor() | |
7627 , hideable = false | |
7628 , barColor = null // adding the ability to set the color for each rather than the whole group | |
7629 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled | |
7630 , duration = 500 | |
7631 , xDomain | |
7632 , yDomain | |
7633 , xRange | |
7634 , yRange | |
7635 , groupSpacing = 0.1 | |
7636 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') | |
7637 ; | |
7638 | |
7639 //============================================================ | |
7640 // Private Variables | |
7641 //------------------------------------------------------------ | |
7642 | |
7643 var x0, y0 //used to store previous scales | |
7644 , renderWatch = nv.utils.renderWatch(dispatch, duration) | |
7645 ; | |
7646 | |
7647 var last_datalength = 0; | |
7648 | |
7649 function chart(selection) { | |
7650 renderWatch.reset(); | |
7651 selection.each(function(data) { | |
7652 var availableWidth = width - margin.left - margin.right, | |
7653 availableHeight = height - margin.top - margin.bottom; | |
7654 | |
7655 container = d3.select(this); | |
7656 nv.utils.initSVG(container); | |
7657 var nonStackableCount = 0; | |
7658 // This function defines the requirements for render complete | |
7659 var endFn = function(d, i) { | |
7660 if (d.series === data.length - 1 && i === data[0].values.length - 1) | |
7661 return true; | |
7662 return false; | |
7663 }; | |
7664 | |
7665 if(hideable && data.length) hideable = [{ | |
7666 values: data[0].values.map(function(d) { | |
7667 return { | |
7668 x: d.x, | |
7669 y: 0, | |
7670 series: d.series, | |
7671 size: 0.01 | |
7672 };} | |
7673 )}]; | |
7674 | |
7675 if (stacked) { | |
7676 var parsed = d3.layout.stack() | |
7677 .offset(stackOffset) | |
7678 .values(function(d){ return d.values }) | |
7679 .y(getY) | |
7680 (!data.length && hideable ? hideable : data); | |
7681 | |
7682 parsed.forEach(function(series, i){ | |
7683 // if series is non-stackable, use un-parsed data | |
7684 if (series.nonStackable) { | |
7685 data[i].nonStackableSeries = nonStackableCount++; | |
7686 parsed[i] = data[i]; | |
7687 } else { | |
7688 // don't stack this seires on top of the nonStackable seriees | |
7689 if (i > 0 && parsed[i - 1].nonStackable){ | |
7690 parsed[i].values.map(function(d,j){ | |
7691 d.y0 -= parsed[i - 1].values[j].y; | |
7692 d.y1 = d.y0 + d.y; | |
7693 }); | |
7694 } | |
7695 } | |
7696 }); | |
7697 data = parsed; | |
7698 } | |
7699 //add series index and key to each data point for reference | |
7700 data.forEach(function(series, i) { | |
7701 series.values.forEach(function(point) { | |
7702 point.series = i; | |
7703 point.key = series.key; | |
7704 }); | |
7705 }); | |
7706 | |
7707 // HACK for negative value stacking | |
7708 if (stacked) { | |
7709 data[0].values.map(function(d,i) { | |
7710 var posBase = 0, negBase = 0; | |
7711 data.map(function(d, idx) { | |
7712 if (!data[idx].nonStackable) { | |
7713 var f = d.values[i] | |
7714 f.size = Math.abs(f.y); | |
7715 if (f.y<0) { | |
7716 f.y1 = negBase; | |
7717 negBase = negBase - f.size; | |
7718 } else | |
7719 { | |
7720 f.y1 = f.size + posBase; | |
7721 posBase = posBase + f.size; | |
7722 } | |
7723 } | |
7724 | |
7725 }); | |
7726 }); | |
7727 } | |
7728 // Setup Scales | |
7729 // remap and flatten the data for use in calculating the scales' domains | |
7730 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate | |
7731 data.map(function(d, idx) { | |
7732 return d.values.map(function(d,i) { | |
7733 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx } | |
7734 }) | |
7735 }); | |
7736 | |
7737 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) | |
7738 .rangeBands(xRange || [0, availableWidth], groupSpacing); | |
7739 | |
7740 y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { | |
7741 var domain = d.y; | |
7742 // increase the domain range if this series is stackable | |
7743 if (stacked && !data[d.idx].nonStackable) { | |
7744 if (d.y > 0){ | |
7745 domain = d.y1 | |
7746 } else { | |
7747 domain = d.y1 + d.y | |
7748 } | |
7749 } | |
7750 return domain; | |
7751 }).concat(forceY))) | |
7752 .range(yRange || [availableHeight, 0]); | |
7753 | |
7754 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point | |
7755 if (x.domain()[0] === x.domain()[1]) | |
7756 x.domain()[0] ? | |
7757 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) | |
7758 : x.domain([-1,1]); | |
7759 | |
7760 if (y.domain()[0] === y.domain()[1]) | |
7761 y.domain()[0] ? | |
7762 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) | |
7763 : y.domain([-1,1]); | |
7764 | |
7765 x0 = x0 || x; | |
7766 y0 = y0 || y; | |
7767 | |
7768 // Setup containers and skeleton of chart | |
7769 var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]); | |
7770 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar'); | |
7771 var defsEnter = wrapEnter.append('defs'); | |
7772 var gEnter = wrapEnter.append('g'); | |
7773 var g = wrap.select('g'); | |
7774 | |
7775 gEnter.append('g').attr('class', 'nv-groups'); | |
7776 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
7777 | |
7778 defsEnter.append('clipPath') | |
7779 .attr('id', 'nv-edge-clip-' + id) | |
7780 .append('rect'); | |
7781 wrap.select('#nv-edge-clip-' + id + ' rect') | |
7782 .attr('width', availableWidth) | |
7783 .attr('height', availableHeight); | |
7784 | |
7785 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); | |
7786 | |
7787 var groups = wrap.select('.nv-groups').selectAll('.nv-group') | |
7788 .data(function(d) { return d }, function(d,i) { return i }); | |
7789 groups.enter().append('g') | |
7790 .style('stroke-opacity', 1e-6) | |
7791 .style('fill-opacity', 1e-6); | |
7792 | |
7793 var exitTransition = renderWatch | |
7794 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration)) | |
7795 .attr('y', function(d, i, j) { | |
7796 var yVal = y0(0) || 0; | |
7797 if (stacked) { | |
7798 if (data[d.series] && !data[d.series].nonStackable) { | |
7799 yVal = y0(d.y0); | |
7800 } | |
7801 } | |
7802 return yVal; | |
7803 }) | |
7804 .attr('height', 0) | |
7805 .remove(); | |
7806 if (exitTransition.delay) | |
7807 exitTransition.delay(function(d,i) { | |
7808 var delay = i * (duration / (last_datalength + 1)) - i; | |
7809 return delay; | |
7810 }); | |
7811 groups | |
7812 .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) | |
7813 .classed('hover', function(d) { return d.hover }) | |
7814 .style('fill', function(d,i){ return color(d, i) }) | |
7815 .style('stroke', function(d,i){ return color(d, i) }); | |
7816 groups | |
7817 .style('stroke-opacity', 1) | |
7818 .style('fill-opacity', 0.75); | |
7819 | |
7820 var bars = groups.selectAll('rect.nv-bar') | |
7821 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values }); | |
7822 bars.exit().remove(); | |
7823 | |
7824 var barsEnter = bars.enter().append('rect') | |
7825 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) | |
7826 .attr('x', function(d,i,j) { | |
7827 return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length ) | |
7828 }) | |
7829 .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 }) | |
7830 .attr('height', 0) | |
7831 .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) }) | |
7832 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; }) | |
7833 ; | |
7834 bars | |
7835 .style('fill', function(d,i,j){ return color(d, j, i); }) | |
7836 .style('stroke', function(d,i,j){ return color(d, j, i); }) | |
7837 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here | |
7838 d3.select(this).classed('hover', true); | |
7839 dispatch.elementMouseover({ | |
7840 data: d, | |
7841 index: i, | |
7842 color: d3.select(this).style("fill") | |
7843 }); | |
7844 }) | |
7845 .on('mouseout', function(d,i) { | |
7846 d3.select(this).classed('hover', false); | |
7847 dispatch.elementMouseout({ | |
7848 data: d, | |
7849 index: i, | |
7850 color: d3.select(this).style("fill") | |
7851 }); | |
7852 }) | |
7853 .on('mousemove', function(d,i) { | |
7854 dispatch.elementMousemove({ | |
7855 data: d, | |
7856 index: i, | |
7857 color: d3.select(this).style("fill") | |
7858 }); | |
7859 }) | |
7860 .on('click', function(d,i) { | |
7861 dispatch.elementClick({ | |
7862 data: d, | |
7863 index: i, | |
7864 color: d3.select(this).style("fill") | |
7865 }); | |
7866 d3.event.stopPropagation(); | |
7867 }) | |
7868 .on('dblclick', function(d,i) { | |
7869 dispatch.elementDblClick({ | |
7870 data: d, | |
7871 index: i, | |
7872 color: d3.select(this).style("fill") | |
7873 }); | |
7874 d3.event.stopPropagation(); | |
7875 }); | |
7876 bars | |
7877 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) | |
7878 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; }) | |
7879 | |
7880 if (barColor) { | |
7881 if (!disabled) disabled = data.map(function() { return true }); | |
7882 bars | |
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(); }) | |
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(); }); | |
7885 } | |
7886 | |
7887 var barSelection = | |
7888 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration)) | |
7889 .delay(function(d,i) { | |
7890 return i * duration / data[0].values.length; | |
7891 }); | |
7892 if (stacked){ | |
7893 barSelection | |
7894 .attr('y', function(d,i,j) { | |
7895 var yVal = 0; | |
7896 // if stackable, stack it on top of the previous series | |
7897 if (!data[j].nonStackable) { | |
7898 yVal = y(d.y1); | |
7899 } else { | |
7900 if (getY(d,i) < 0){ | |
7901 yVal = y(0); | |
7902 } else { | |
7903 if (y(0) - y(getY(d,i)) < -1){ | |
7904 yVal = y(0) - 1; | |
7905 } else { | |
7906 yVal = y(getY(d, i)) || 0; | |
7907 } | |
7908 } | |
7909 } | |
7910 return yVal; | |
7911 }) | |
7912 .attr('height', function(d,i,j) { | |
7913 if (!data[j].nonStackable) { | |
7914 return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 1); | |
7915 } else { | |
7916 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; | |
7917 } | |
7918 }) | |
7919 .attr('x', function(d,i,j) { | |
7920 var width = 0; | |
7921 if (data[j].nonStackable) { | |
7922 width = d.series * x.rangeBand() / data.length; | |
7923 if (data.length !== nonStackableCount){ | |
7924 width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2); | |
7925 } | |
7926 } | |
7927 return width; | |
7928 }) | |
7929 .attr('width', function(d,i,j){ | |
7930 if (!data[j].nonStackable) { | |
7931 return x.rangeBand(); | |
7932 } else { | |
7933 // if all series are nonStacable, take the full width | |
7934 var width = (x.rangeBand() / nonStackableCount); | |
7935 // otherwise, nonStackable graph will be only taking the half-width | |
7936 // of the x rangeBand | |
7937 if (data.length !== nonStackableCount) { | |
7938 width = x.rangeBand()/(nonStackableCount*2); | |
7939 } | |
7940 return width; | |
7941 } | |
7942 }); | |
7943 } | |
7944 else { | |
7945 barSelection | |
7946 .attr('x', function(d,i) { | |
7947 return d.series * x.rangeBand() / data.length; | |
7948 }) | |
7949 .attr('width', x.rangeBand() / data.length) | |
7950 .attr('y', function(d,i) { | |
7951 return getY(d,i) < 0 ? | |
7952 y(0) : | |
7953 y(0) - y(getY(d,i)) < 1 ? | |
7954 y(0) - 1 : | |
7955 y(getY(d,i)) || 0; | |
7956 }) | |
7957 .attr('height', function(d,i) { | |
7958 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0; | |
7959 }); | |
7960 } | |
7961 | |
7962 //store old scales for use in transitions on update | |
7963 x0 = x.copy(); | |
7964 y0 = y.copy(); | |
7965 | |
7966 // keep track of the last data value length for transition calculations | |
7967 if (data[0] && data[0].values) { | |
7968 last_datalength = data[0].values.length; | |
7969 } | |
7970 | |
7971 }); | |
7972 | |
7973 renderWatch.renderEnd('multibar immediate'); | |
7974 | |
7975 return chart; | |
7976 } | |
7977 | |
7978 //============================================================ | |
7979 // Expose Public Variables | |
7980 //------------------------------------------------------------ | |
7981 | |
7982 chart.dispatch = dispatch; | |
7983 | |
7984 chart.options = nv.utils.optionsFunc.bind(chart); | |
7985 | |
7986 chart._options = Object.create({}, { | |
7987 // simple options, just get/set the necessary values | |
7988 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
7989 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
7990 x: {get: function(){return getX;}, set: function(_){getX=_;}}, | |
7991 y: {get: function(){return getY;}, set: function(_){getY=_;}}, | |
7992 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
7993 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
7994 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
7995 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
7996 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
7997 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
7998 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, | |
7999 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}}, | |
8000 stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}}, | |
8001 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, | |
8002 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, | |
8003 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
8004 hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}}, | |
8005 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, | |
8006 | |
8007 // options that require extra logic in the setter | |
8008 margin: {get: function(){return margin;}, set: function(_){ | |
8009 margin.top = _.top !== undefined ? _.top : margin.top; | |
8010 margin.right = _.right !== undefined ? _.right : margin.right; | |
8011 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
8012 margin.left = _.left !== undefined ? _.left : margin.left; | |
8013 }}, | |
8014 duration: {get: function(){return duration;}, set: function(_){ | |
8015 duration = _; | |
8016 renderWatch.reset(duration); | |
8017 }}, | |
8018 color: {get: function(){return color;}, set: function(_){ | |
8019 color = nv.utils.getColor(_); | |
8020 }}, | |
8021 barColor: {get: function(){return barColor;}, set: function(_){ | |
8022 barColor = _ ? nv.utils.getColor(_) : null; | |
8023 }} | |
8024 }); | |
8025 | |
8026 nv.utils.initOptions(chart); | |
8027 | |
8028 return chart; | |
8029 }; | |
8030 nv.models.multiBarChart = function() { | |
8031 "use strict"; | |
8032 | |
8033 //============================================================ | |
8034 // Public Variables with Default Settings | |
8035 //------------------------------------------------------------ | |
8036 | |
8037 var multibar = nv.models.multiBar() | |
8038 , xAxis = nv.models.axis() | |
8039 , yAxis = nv.models.axis() | |
8040 , legend = nv.models.legend() | |
8041 , controls = nv.models.legend() | |
8042 , tooltip = nv.models.tooltip() | |
8043 ; | |
8044 | |
8045 var margin = {top: 30, right: 20, bottom: 50, left: 60} | |
8046 , width = null | |
8047 , height = null | |
8048 , color = nv.utils.defaultColor() | |
8049 , showControls = true | |
8050 , controlLabels = {} | |
8051 , showLegend = true | |
8052 , showXAxis = true | |
8053 , showYAxis = true | |
8054 , rightAlignYAxis = false | |
8055 , reduceXTicks = true // if false a tick will show for every data point | |
8056 , staggerLabels = false | |
8057 , rotateLabels = 0 | |
8058 , x //can be accessed via chart.xScale() | |
8059 , y //can be accessed via chart.yScale() | |
8060 , state = nv.utils.state() | |
8061 , defaultState = null | |
8062 , noData = null | |
8063 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') | |
8064 , controlWidth = function() { return showControls ? 180 : 0 } | |
8065 , duration = 250 | |
8066 ; | |
8067 | |
8068 state.stacked = false // DEPRECATED Maintained for backward compatibility | |
8069 | |
8070 multibar.stacked(false); | |
8071 xAxis | |
8072 .orient('bottom') | |
8073 .tickPadding(7) | |
8074 .showMaxMin(false) | |
8075 .tickFormat(function(d) { return d }) | |
8076 ; | |
8077 yAxis | |
8078 .orient((rightAlignYAxis) ? 'right' : 'left') | |
8079 .tickFormat(d3.format(',.1f')) | |
8080 ; | |
8081 | |
8082 tooltip | |
8083 .duration(0) | |
8084 .valueFormatter(function(d, i) { | |
8085 return yAxis.tickFormat()(d, i); | |
8086 }) | |
8087 .headerFormatter(function(d, i) { | |
8088 return xAxis.tickFormat()(d, i); | |
8089 }); | |
8090 | |
8091 controls.updateState(false); | |
8092 | |
8093 //============================================================ | |
8094 // Private Variables | |
8095 //------------------------------------------------------------ | |
8096 | |
8097 var renderWatch = nv.utils.renderWatch(dispatch); | |
8098 var stacked = false; | |
8099 | |
8100 var stateGetter = function(data) { | |
8101 return function(){ | |
8102 return { | |
8103 active: data.map(function(d) { return !d.disabled }), | |
8104 stacked: stacked | |
8105 }; | |
8106 } | |
8107 }; | |
8108 | |
8109 var stateSetter = function(data) { | |
8110 return function(state) { | |
8111 if (state.stacked !== undefined) | |
8112 stacked = state.stacked; | |
8113 if (state.active !== undefined) | |
8114 data.forEach(function(series,i) { | |
8115 series.disabled = !state.active[i]; | |
8116 }); | |
8117 } | |
8118 }; | |
8119 | |
8120 function chart(selection) { | |
8121 renderWatch.reset(); | |
8122 renderWatch.models(multibar); | |
8123 if (showXAxis) renderWatch.models(xAxis); | |
8124 if (showYAxis) renderWatch.models(yAxis); | |
8125 | |
8126 selection.each(function(data) { | |
8127 var container = d3.select(this), | |
8128 that = this; | |
8129 nv.utils.initSVG(container); | |
8130 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
8131 availableHeight = nv.utils.availableHeight(height, container, margin); | |
8132 | |
8133 chart.update = function() { | |
8134 if (duration === 0) | |
8135 container.call(chart); | |
8136 else | |
8137 container.transition() | |
8138 .duration(duration) | |
8139 .call(chart); | |
8140 }; | |
8141 chart.container = this; | |
8142 | |
8143 state | |
8144 .setter(stateSetter(data), chart.update) | |
8145 .getter(stateGetter(data)) | |
8146 .update(); | |
8147 | |
8148 // DEPRECATED set state.disableddisabled | |
8149 state.disabled = data.map(function(d) { return !!d.disabled }); | |
8150 | |
8151 if (!defaultState) { | |
8152 var key; | |
8153 defaultState = {}; | |
8154 for (key in state) { | |
8155 if (state[key] instanceof Array) | |
8156 defaultState[key] = state[key].slice(0); | |
8157 else | |
8158 defaultState[key] = state[key]; | |
8159 } | |
8160 } | |
8161 | |
8162 // Display noData message if there's nothing to show. | |
8163 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
8164 nv.utils.noData(chart, container) | |
8165 return chart; | |
8166 } else { | |
8167 container.selectAll('.nv-noData').remove(); | |
8168 } | |
8169 | |
8170 // Setup Scales | |
8171 x = multibar.xScale(); | |
8172 y = multibar.yScale(); | |
8173 | |
8174 // Setup containers and skeleton of chart | |
8175 var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]); | |
8176 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g'); | |
8177 var g = wrap.select('g'); | |
8178 | |
8179 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
8180 gEnter.append('g').attr('class', 'nv-y nv-axis'); | |
8181 gEnter.append('g').attr('class', 'nv-barsWrap'); | |
8182 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
8183 gEnter.append('g').attr('class', 'nv-controlsWrap'); | |
8184 | |
8185 // Legend | |
8186 if (showLegend) { | |
8187 legend.width(availableWidth - controlWidth()); | |
8188 | |
8189 g.select('.nv-legendWrap') | |
8190 .datum(data) | |
8191 .call(legend); | |
8192 | |
8193 if ( margin.top != legend.height()) { | |
8194 margin.top = legend.height(); | |
8195 availableHeight = nv.utils.availableHeight(height, container, margin); | |
8196 } | |
8197 | |
8198 g.select('.nv-legendWrap') | |
8199 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); | |
8200 } | |
8201 | |
8202 // Controls | |
8203 if (showControls) { | |
8204 var controlsData = [ | |
8205 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() }, | |
8206 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() } | |
8207 ]; | |
8208 | |
8209 controls.width(controlWidth()).color(['#444', '#444', '#444']); | |
8210 g.select('.nv-controlsWrap') | |
8211 .datum(controlsData) | |
8212 .attr('transform', 'translate(0,' + (-margin.top) +')') | |
8213 .call(controls); | |
8214 } | |
8215 | |
8216 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
8217 if (rightAlignYAxis) { | |
8218 g.select(".nv-y.nv-axis") | |
8219 .attr("transform", "translate(" + availableWidth + ",0)"); | |
8220 } | |
8221 | |
8222 // Main Chart Component(s) | |
8223 multibar | |
8224 .disabled(data.map(function(series) { return series.disabled })) | |
8225 .width(availableWidth) | |
8226 .height(availableHeight) | |
8227 .color(data.map(function(d,i) { | |
8228 return d.color || color(d, i); | |
8229 }).filter(function(d,i) { return !data[i].disabled })); | |
8230 | |
8231 | |
8232 var barsWrap = g.select('.nv-barsWrap') | |
8233 .datum(data.filter(function(d) { return !d.disabled })); | |
8234 | |
8235 barsWrap.call(multibar); | |
8236 | |
8237 // Setup Axes | |
8238 if (showXAxis) { | |
8239 xAxis | |
8240 .scale(x) | |
8241 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
8242 .tickSize(-availableHeight, 0); | |
8243 | |
8244 g.select('.nv-x.nv-axis') | |
8245 .attr('transform', 'translate(0,' + y.range()[0] + ')'); | |
8246 g.select('.nv-x.nv-axis') | |
8247 .call(xAxis); | |
8248 | |
8249 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g'); | |
8250 | |
8251 xTicks | |
8252 .selectAll('line, text') | |
8253 .style('opacity', 1) | |
8254 | |
8255 if (staggerLabels) { | |
8256 var getTranslate = function(x,y) { | |
8257 return "translate(" + x + "," + y + ")"; | |
8258 }; | |
8259 | |
8260 var staggerUp = 5, staggerDown = 17; //pixels to stagger by | |
8261 // Issue #140 | |
8262 xTicks | |
8263 .selectAll("text") | |
8264 .attr('transform', function(d,i,j) { | |
8265 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown)); | |
8266 }); | |
8267 | |
8268 var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length; | |
8269 g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text") | |
8270 .attr("transform", function(d,i) { | |
8271 return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp); | |
8272 }); | |
8273 } | |
8274 | |
8275 if (reduceXTicks) | |
8276 xTicks | |
8277 .filter(function(d,i) { | |
8278 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0; | |
8279 }) | |
8280 .selectAll('text, line') | |
8281 .style('opacity', 0); | |
8282 | |
8283 if(rotateLabels) | |
8284 xTicks | |
8285 .selectAll('.tick text') | |
8286 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)') | |
8287 .style('text-anchor', rotateLabels > 0 ? 'start' : 'end'); | |
8288 | |
8289 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text') | |
8290 .style('opacity', 1); | |
8291 } | |
8292 | |
8293 if (showYAxis) { | |
8294 yAxis | |
8295 .scale(y) | |
8296 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) | |
8297 .tickSize( -availableWidth, 0); | |
8298 | |
8299 g.select('.nv-y.nv-axis') | |
8300 .call(yAxis); | |
8301 } | |
8302 | |
8303 //============================================================ | |
8304 // Event Handling/Dispatching (in chart's scope) | |
8305 //------------------------------------------------------------ | |
8306 | |
8307 legend.dispatch.on('stateChange', function(newState) { | |
8308 for (var key in newState) | |
8309 state[key] = newState[key]; | |
8310 dispatch.stateChange(state); | |
8311 chart.update(); | |
8312 }); | |
8313 | |
8314 controls.dispatch.on('legendClick', function(d,i) { | |
8315 if (!d.disabled) return; | |
8316 controlsData = controlsData.map(function(s) { | |
8317 s.disabled = true; | |
8318 return s; | |
8319 }); | |
8320 d.disabled = false; | |
8321 | |
8322 switch (d.key) { | |
8323 case 'Grouped': | |
8324 case controlLabels.grouped: | |
8325 multibar.stacked(false); | |
8326 break; | |
8327 case 'Stacked': | |
8328 case controlLabels.stacked: | |
8329 multibar.stacked(true); | |
8330 break; | |
8331 } | |
8332 | |
8333 state.stacked = multibar.stacked(); | |
8334 dispatch.stateChange(state); | |
8335 chart.update(); | |
8336 }); | |
8337 | |
8338 // Update chart from a state object passed to event handler | |
8339 dispatch.on('changeState', function(e) { | |
8340 if (typeof e.disabled !== 'undefined') { | |
8341 data.forEach(function(series,i) { | |
8342 series.disabled = e.disabled[i]; | |
8343 }); | |
8344 state.disabled = e.disabled; | |
8345 } | |
8346 if (typeof e.stacked !== 'undefined') { | |
8347 multibar.stacked(e.stacked); | |
8348 state.stacked = e.stacked; | |
8349 stacked = e.stacked; | |
8350 } | |
8351 chart.update(); | |
8352 }); | |
8353 }); | |
8354 | |
8355 renderWatch.renderEnd('multibarchart immediate'); | |
8356 return chart; | |
8357 } | |
8358 | |
8359 //============================================================ | |
8360 // Event Handling/Dispatching (out of chart's scope) | |
8361 //------------------------------------------------------------ | |
8362 | |
8363 multibar.dispatch.on('elementMouseover.tooltip', function(evt) { | |
8364 evt.value = chart.x()(evt.data); | |
8365 evt['series'] = { | |
8366 key: evt.data.key, | |
8367 value: chart.y()(evt.data), | |
8368 color: evt.color | |
8369 }; | |
8370 tooltip.data(evt).hidden(false); | |
8371 }); | |
8372 | |
8373 multibar.dispatch.on('elementMouseout.tooltip', function(evt) { | |
8374 tooltip.hidden(true); | |
8375 }); | |
8376 | |
8377 multibar.dispatch.on('elementMousemove.tooltip', function(evt) { | |
8378 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
8379 }); | |
8380 | |
8381 //============================================================ | |
8382 // Expose Public Variables | |
8383 //------------------------------------------------------------ | |
8384 | |
8385 // expose chart's sub-components | |
8386 chart.dispatch = dispatch; | |
8387 chart.multibar = multibar; | |
8388 chart.legend = legend; | |
8389 chart.controls = controls; | |
8390 chart.xAxis = xAxis; | |
8391 chart.yAxis = yAxis; | |
8392 chart.state = state; | |
8393 chart.tooltip = tooltip; | |
8394 | |
8395 chart.options = nv.utils.optionsFunc.bind(chart); | |
8396 | |
8397 chart._options = Object.create({}, { | |
8398 // simple options, just get/set the necessary values | |
8399 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
8400 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
8401 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
8402 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, | |
8403 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, | |
8404 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
8405 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
8406 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
8407 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
8408 reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}}, | |
8409 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}}, | |
8410 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}}, | |
8411 | |
8412 // deprecated options | |
8413 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
8414 // deprecated after 1.7.1 | |
8415 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
8416 tooltip.enabled(!!_); | |
8417 }}, | |
8418 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
8419 // deprecated after 1.7.1 | |
8420 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
8421 tooltip.contentGenerator(_); | |
8422 }}, | |
8423 | |
8424 // options that require extra logic in the setter | |
8425 margin: {get: function(){return margin;}, set: function(_){ | |
8426 margin.top = _.top !== undefined ? _.top : margin.top; | |
8427 margin.right = _.right !== undefined ? _.right : margin.right; | |
8428 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
8429 margin.left = _.left !== undefined ? _.left : margin.left; | |
8430 }}, | |
8431 duration: {get: function(){return duration;}, set: function(_){ | |
8432 duration = _; | |
8433 multibar.duration(duration); | |
8434 xAxis.duration(duration); | |
8435 yAxis.duration(duration); | |
8436 renderWatch.reset(duration); | |
8437 }}, | |
8438 color: {get: function(){return color;}, set: function(_){ | |
8439 color = nv.utils.getColor(_); | |
8440 legend.color(color); | |
8441 }}, | |
8442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ | |
8443 rightAlignYAxis = _; | |
8444 yAxis.orient( rightAlignYAxis ? 'right' : 'left'); | |
8445 }}, | |
8446 barColor: {get: function(){return multibar.barColor;}, set: function(_){ | |
8447 multibar.barColor(_); | |
8448 legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();}) | |
8449 }} | |
8450 }); | |
8451 | |
8452 nv.utils.inheritOptions(chart, multibar); | |
8453 nv.utils.initOptions(chart); | |
8454 | |
8455 return chart; | |
8456 }; | |
8457 | |
8458 nv.models.multiBarHorizontal = function() { | |
8459 "use strict"; | |
8460 | |
8461 //============================================================ | |
8462 // Public Variables with Default Settings | |
8463 //------------------------------------------------------------ | |
8464 | |
8465 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
8466 , width = 960 | |
8467 , height = 500 | |
8468 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
8469 , container = null | |
8470 , x = d3.scale.ordinal() | |
8471 , y = d3.scale.linear() | |
8472 , getX = function(d) { return d.x } | |
8473 , getY = function(d) { return d.y } | |
8474 , getYerr = function(d) { return d.yErr } | |
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 | |
8476 , color = nv.utils.defaultColor() | |
8477 , barColor = null // adding the ability to set the color for each rather than the whole group | |
8478 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled | |
8479 , stacked = false | |
8480 , showValues = false | |
8481 , showBarLabels = false | |
8482 , valuePadding = 60 | |
8483 , groupSpacing = 0.1 | |
8484 , valueFormat = d3.format(',.2f') | |
8485 , delay = 1200 | |
8486 , xDomain | |
8487 , yDomain | |
8488 , xRange | |
8489 , yRange | |
8490 , duration = 250 | |
8491 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') | |
8492 ; | |
8493 | |
8494 //============================================================ | |
8495 // Private Variables | |
8496 //------------------------------------------------------------ | |
8497 | |
8498 var x0, y0; //used to store previous scales | |
8499 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
8500 | |
8501 function chart(selection) { | |
8502 renderWatch.reset(); | |
8503 selection.each(function(data) { | |
8504 var availableWidth = width - margin.left - margin.right, | |
8505 availableHeight = height - margin.top - margin.bottom; | |
8506 | |
8507 container = d3.select(this); | |
8508 nv.utils.initSVG(container); | |
8509 | |
8510 if (stacked) | |
8511 data = d3.layout.stack() | |
8512 .offset('zero') | |
8513 .values(function(d){ return d.values }) | |
8514 .y(getY) | |
8515 (data); | |
8516 | |
8517 //add series index and key to each data point for reference | |
8518 data.forEach(function(series, i) { | |
8519 series.values.forEach(function(point) { | |
8520 point.series = i; | |
8521 point.key = series.key; | |
8522 }); | |
8523 }); | |
8524 | |
8525 // HACK for negative value stacking | |
8526 if (stacked) | |
8527 data[0].values.map(function(d,i) { | |
8528 var posBase = 0, negBase = 0; | |
8529 data.map(function(d) { | |
8530 var f = d.values[i] | |
8531 f.size = Math.abs(f.y); | |
8532 if (f.y<0) { | |
8533 f.y1 = negBase - f.size; | |
8534 negBase = negBase - f.size; | |
8535 } else | |
8536 { | |
8537 f.y1 = posBase; | |
8538 posBase = posBase + f.size; | |
8539 } | |
8540 }); | |
8541 }); | |
8542 | |
8543 // Setup Scales | |
8544 // remap and flatten the data for use in calculating the scales' domains | |
8545 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate | |
8546 data.map(function(d) { | |
8547 return d.values.map(function(d,i) { | |
8548 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 } | |
8549 }) | |
8550 }); | |
8551 | |
8552 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x })) | |
8553 .rangeBands(xRange || [0, availableHeight], groupSpacing); | |
8554 | |
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))) | |
8556 | |
8557 if (showValues && !stacked) | |
8558 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]); | |
8559 else | |
8560 y.range(yRange || [0, availableWidth]); | |
8561 | |
8562 x0 = x0 || x; | |
8563 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]); | |
8564 | |
8565 // Setup containers and skeleton of chart | |
8566 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]); | |
8567 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal'); | |
8568 var defsEnter = wrapEnter.append('defs'); | |
8569 var gEnter = wrapEnter.append('g'); | |
8570 var g = wrap.select('g'); | |
8571 | |
8572 gEnter.append('g').attr('class', 'nv-groups'); | |
8573 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
8574 | |
8575 var groups = wrap.select('.nv-groups').selectAll('.nv-group') | |
8576 .data(function(d) { return d }, function(d,i) { return i }); | |
8577 groups.enter().append('g') | |
8578 .style('stroke-opacity', 1e-6) | |
8579 .style('fill-opacity', 1e-6); | |
8580 groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups') | |
8581 .style('stroke-opacity', 1e-6) | |
8582 .style('fill-opacity', 1e-6) | |
8583 .remove(); | |
8584 groups | |
8585 .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) | |
8586 .classed('hover', function(d) { return d.hover }) | |
8587 .style('fill', function(d,i){ return color(d, i) }) | |
8588 .style('stroke', function(d,i){ return color(d, i) }); | |
8589 groups.watchTransition(renderWatch, 'multibarhorizontal: groups') | |
8590 .style('stroke-opacity', 1) | |
8591 .style('fill-opacity', .75); | |
8592 | |
8593 var bars = groups.selectAll('g.nv-bar') | |
8594 .data(function(d) { return d.values }); | |
8595 bars.exit().remove(); | |
8596 | |
8597 var barsEnter = bars.enter().append('g') | |
8598 .attr('transform', function(d,i,j) { | |
8599 return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')' | |
8600 }); | |
8601 | |
8602 barsEnter.append('rect') | |
8603 .attr('width', 0) | |
8604 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) ) | |
8605 | |
8606 bars | |
8607 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here | |
8608 d3.select(this).classed('hover', true); | |
8609 dispatch.elementMouseover({ | |
8610 data: d, | |
8611 index: i, | |
8612 color: d3.select(this).style("fill") | |
8613 }); | |
8614 }) | |
8615 .on('mouseout', function(d,i) { | |
8616 d3.select(this).classed('hover', false); | |
8617 dispatch.elementMouseout({ | |
8618 data: d, | |
8619 index: i, | |
8620 color: d3.select(this).style("fill") | |
8621 }); | |
8622 }) | |
8623 .on('mouseout', function(d,i) { | |
8624 dispatch.elementMouseout({ | |
8625 data: d, | |
8626 index: i, | |
8627 color: d3.select(this).style("fill") | |
8628 }); | |
8629 }) | |
8630 .on('mousemove', function(d,i) { | |
8631 dispatch.elementMousemove({ | |
8632 data: d, | |
8633 index: i, | |
8634 color: d3.select(this).style("fill") | |
8635 }); | |
8636 }) | |
8637 .on('click', function(d,i) { | |
8638 dispatch.elementClick({ | |
8639 data: d, | |
8640 index: i, | |
8641 color: d3.select(this).style("fill") | |
8642 }); | |
8643 d3.event.stopPropagation(); | |
8644 }) | |
8645 .on('dblclick', function(d,i) { | |
8646 dispatch.elementDblClick({ | |
8647 data: d, | |
8648 index: i, | |
8649 color: d3.select(this).style("fill") | |
8650 }); | |
8651 d3.event.stopPropagation(); | |
8652 }); | |
8653 | |
8654 if (getYerr(data[0],0)) { | |
8655 barsEnter.append('polyline'); | |
8656 | |
8657 bars.select('polyline') | |
8658 .attr('fill', 'none') | |
8659 .attr('points', function(d,i) { | |
8660 var xerr = getYerr(d,i) | |
8661 , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2); | |
8662 xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)]; | |
8663 xerr = xerr.map(function(e) { return y(e) - y(0); }); | |
8664 var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]]; | |
8665 return a.map(function (path) { return path.join(',') }).join(' '); | |
8666 }) | |
8667 .attr('transform', function(d,i) { | |
8668 var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2); | |
8669 return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')' | |
8670 }); | |
8671 } | |
8672 | |
8673 barsEnter.append('text'); | |
8674 | |
8675 if (showValues && !stacked) { | |
8676 bars.select('text') | |
8677 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' }) | |
8678 .attr('y', x.rangeBand() / (data.length * 2)) | |
8679 .attr('dy', '.32em') | |
8680 .text(function(d,i) { | |
8681 var t = valueFormat(getY(d,i)) | |
8682 , yerr = getYerr(d,i); | |
8683 if (yerr === undefined) | |
8684 return t; | |
8685 if (!yerr.length) | |
8686 return t + '±' + valueFormat(Math.abs(yerr)); | |
8687 return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0])); | |
8688 }); | |
8689 bars.watchTransition(renderWatch, 'multibarhorizontal: bars') | |
8690 .select('text') | |
8691 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 }) | |
8692 } else { | |
8693 bars.selectAll('text').text(''); | |
8694 } | |
8695 | |
8696 if (showBarLabels && !stacked) { | |
8697 barsEnter.append('text').classed('nv-bar-label',true); | |
8698 bars.select('text.nv-bar-label') | |
8699 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' }) | |
8700 .attr('y', x.rangeBand() / (data.length * 2)) | |
8701 .attr('dy', '.32em') | |
8702 .text(function(d,i) { return getX(d,i) }); | |
8703 bars.watchTransition(renderWatch, 'multibarhorizontal: bars') | |
8704 .select('text.nv-bar-label') | |
8705 .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 }); | |
8706 } | |
8707 else { | |
8708 bars.selectAll('text.nv-bar-label').text(''); | |
8709 } | |
8710 | |
8711 bars | |
8712 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'}) | |
8713 | |
8714 if (barColor) { | |
8715 if (!disabled) disabled = data.map(function() { return true }); | |
8716 bars | |
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(); }) | |
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(); }); | |
8719 } | |
8720 | |
8721 if (stacked) | |
8722 bars.watchTransition(renderWatch, 'multibarhorizontal: bars') | |
8723 .attr('transform', function(d,i) { | |
8724 return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')' | |
8725 }) | |
8726 .select('rect') | |
8727 .attr('width', function(d,i) { | |
8728 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0)) | |
8729 }) | |
8730 .attr('height', x.rangeBand() ); | |
8731 else | |
8732 bars.watchTransition(renderWatch, 'multibarhorizontal: bars') | |
8733 .attr('transform', function(d,i) { | |
8734 //TODO: stacked must be all positive or all negative, not both? | |
8735 return 'translate(' + | |
8736 (getY(d,i) < 0 ? y(getY(d,i)) : y(0)) | |
8737 + ',' + | |
8738 (d.series * x.rangeBand() / data.length | |
8739 + | |
8740 x(getX(d,i)) ) | |
8741 + ')' | |
8742 }) | |
8743 .select('rect') | |
8744 .attr('height', x.rangeBand() / data.length ) | |
8745 .attr('width', function(d,i) { | |
8746 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) | |
8747 }); | |
8748 | |
8749 //store old scales for use in transitions on update | |
8750 x0 = x.copy(); | |
8751 y0 = y.copy(); | |
8752 | |
8753 }); | |
8754 | |
8755 renderWatch.renderEnd('multibarHorizontal immediate'); | |
8756 return chart; | |
8757 } | |
8758 | |
8759 //============================================================ | |
8760 // Expose Public Variables | |
8761 //------------------------------------------------------------ | |
8762 | |
8763 chart.dispatch = dispatch; | |
8764 | |
8765 chart.options = nv.utils.optionsFunc.bind(chart); | |
8766 | |
8767 chart._options = Object.create({}, { | |
8768 // simple options, just get/set the necessary values | |
8769 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
8770 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
8771 x: {get: function(){return getX;}, set: function(_){getX=_;}}, | |
8772 y: {get: function(){return getY;}, set: function(_){getY=_;}}, | |
8773 yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}}, | |
8774 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
8775 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
8776 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
8777 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
8778 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
8779 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
8780 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, | |
8781 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}}, | |
8782 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}}, | |
8783 // this shows the group name, seems pointless? | |
8784 //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}}, | |
8785 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}}, | |
8786 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
8787 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, | |
8788 valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}}, | |
8789 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}}, | |
8790 | |
8791 // options that require extra logic in the setter | |
8792 margin: {get: function(){return margin;}, set: function(_){ | |
8793 margin.top = _.top !== undefined ? _.top : margin.top; | |
8794 margin.right = _.right !== undefined ? _.right : margin.right; | |
8795 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
8796 margin.left = _.left !== undefined ? _.left : margin.left; | |
8797 }}, | |
8798 duration: {get: function(){return duration;}, set: function(_){ | |
8799 duration = _; | |
8800 renderWatch.reset(duration); | |
8801 }}, | |
8802 color: {get: function(){return color;}, set: function(_){ | |
8803 color = nv.utils.getColor(_); | |
8804 }}, | |
8805 barColor: {get: function(){return barColor;}, set: function(_){ | |
8806 barColor = _ ? nv.utils.getColor(_) : null; | |
8807 }} | |
8808 }); | |
8809 | |
8810 nv.utils.initOptions(chart); | |
8811 | |
8812 return chart; | |
8813 }; | |
8814 | |
8815 nv.models.multiBarHorizontalChart = function() { | |
8816 "use strict"; | |
8817 | |
8818 //============================================================ | |
8819 // Public Variables with Default Settings | |
8820 //------------------------------------------------------------ | |
8821 | |
8822 var multibar = nv.models.multiBarHorizontal() | |
8823 , xAxis = nv.models.axis() | |
8824 , yAxis = nv.models.axis() | |
8825 , legend = nv.models.legend().height(30) | |
8826 , controls = nv.models.legend().height(30) | |
8827 , tooltip = nv.models.tooltip() | |
8828 ; | |
8829 | |
8830 var margin = {top: 30, right: 20, bottom: 50, left: 60} | |
8831 , width = null | |
8832 , height = null | |
8833 , color = nv.utils.defaultColor() | |
8834 , showControls = true | |
8835 , controlLabels = {} | |
8836 , showLegend = true | |
8837 , showXAxis = true | |
8838 , showYAxis = true | |
8839 , stacked = false | |
8840 , x //can be accessed via chart.xScale() | |
8841 , y //can be accessed via chart.yScale() | |
8842 , state = nv.utils.state() | |
8843 , defaultState = null | |
8844 , noData = null | |
8845 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd') | |
8846 , controlWidth = function() { return showControls ? 180 : 0 } | |
8847 , duration = 250 | |
8848 ; | |
8849 | |
8850 state.stacked = false; // DEPRECATED Maintained for backward compatibility | |
8851 | |
8852 multibar.stacked(stacked); | |
8853 | |
8854 xAxis | |
8855 .orient('left') | |
8856 .tickPadding(5) | |
8857 .showMaxMin(false) | |
8858 .tickFormat(function(d) { return d }) | |
8859 ; | |
8860 yAxis | |
8861 .orient('bottom') | |
8862 .tickFormat(d3.format(',.1f')) | |
8863 ; | |
8864 | |
8865 tooltip | |
8866 .duration(0) | |
8867 .valueFormatter(function(d, i) { | |
8868 return yAxis.tickFormat()(d, i); | |
8869 }) | |
8870 .headerFormatter(function(d, i) { | |
8871 return xAxis.tickFormat()(d, i); | |
8872 }); | |
8873 | |
8874 controls.updateState(false); | |
8875 | |
8876 //============================================================ | |
8877 // Private Variables | |
8878 //------------------------------------------------------------ | |
8879 | |
8880 var stateGetter = function(data) { | |
8881 return function(){ | |
8882 return { | |
8883 active: data.map(function(d) { return !d.disabled }), | |
8884 stacked: stacked | |
8885 }; | |
8886 } | |
8887 }; | |
8888 | |
8889 var stateSetter = function(data) { | |
8890 return function(state) { | |
8891 if (state.stacked !== undefined) | |
8892 stacked = state.stacked; | |
8893 if (state.active !== undefined) | |
8894 data.forEach(function(series,i) { | |
8895 series.disabled = !state.active[i]; | |
8896 }); | |
8897 } | |
8898 }; | |
8899 | |
8900 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
8901 | |
8902 function chart(selection) { | |
8903 renderWatch.reset(); | |
8904 renderWatch.models(multibar); | |
8905 if (showXAxis) renderWatch.models(xAxis); | |
8906 if (showYAxis) renderWatch.models(yAxis); | |
8907 | |
8908 selection.each(function(data) { | |
8909 var container = d3.select(this), | |
8910 that = this; | |
8911 nv.utils.initSVG(container); | |
8912 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
8913 availableHeight = nv.utils.availableHeight(height, container, margin); | |
8914 | |
8915 chart.update = function() { container.transition().duration(duration).call(chart) }; | |
8916 chart.container = this; | |
8917 | |
8918 stacked = multibar.stacked(); | |
8919 | |
8920 state | |
8921 .setter(stateSetter(data), chart.update) | |
8922 .getter(stateGetter(data)) | |
8923 .update(); | |
8924 | |
8925 // DEPRECATED set state.disableddisabled | |
8926 state.disabled = data.map(function(d) { return !!d.disabled }); | |
8927 | |
8928 if (!defaultState) { | |
8929 var key; | |
8930 defaultState = {}; | |
8931 for (key in state) { | |
8932 if (state[key] instanceof Array) | |
8933 defaultState[key] = state[key].slice(0); | |
8934 else | |
8935 defaultState[key] = state[key]; | |
8936 } | |
8937 } | |
8938 | |
8939 // Display No Data message if there's nothing to show. | |
8940 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
8941 nv.utils.noData(chart, container) | |
8942 return chart; | |
8943 } else { | |
8944 container.selectAll('.nv-noData').remove(); | |
8945 } | |
8946 | |
8947 // Setup Scales | |
8948 x = multibar.xScale(); | |
8949 y = multibar.yScale(); | |
8950 | |
8951 // Setup containers and skeleton of chart | |
8952 var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]); | |
8953 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g'); | |
8954 var g = wrap.select('g'); | |
8955 | |
8956 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
8957 gEnter.append('g').attr('class', 'nv-y nv-axis') | |
8958 .append('g').attr('class', 'nv-zeroLine') | |
8959 .append('line'); | |
8960 gEnter.append('g').attr('class', 'nv-barsWrap'); | |
8961 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
8962 gEnter.append('g').attr('class', 'nv-controlsWrap'); | |
8963 | |
8964 // Legend | |
8965 if (showLegend) { | |
8966 legend.width(availableWidth - controlWidth()); | |
8967 | |
8968 g.select('.nv-legendWrap') | |
8969 .datum(data) | |
8970 .call(legend); | |
8971 | |
8972 if ( margin.top != legend.height()) { | |
8973 margin.top = legend.height(); | |
8974 availableHeight = nv.utils.availableHeight(height, container, margin); | |
8975 } | |
8976 | |
8977 g.select('.nv-legendWrap') | |
8978 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')'); | |
8979 } | |
8980 | |
8981 // Controls | |
8982 if (showControls) { | |
8983 var controlsData = [ | |
8984 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() }, | |
8985 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() } | |
8986 ]; | |
8987 | |
8988 controls.width(controlWidth()).color(['#444', '#444', '#444']); | |
8989 g.select('.nv-controlsWrap') | |
8990 .datum(controlsData) | |
8991 .attr('transform', 'translate(0,' + (-margin.top) +')') | |
8992 .call(controls); | |
8993 } | |
8994 | |
8995 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
8996 | |
8997 // Main Chart Component(s) | |
8998 multibar | |
8999 .disabled(data.map(function(series) { return series.disabled })) | |
9000 .width(availableWidth) | |
9001 .height(availableHeight) | |
9002 .color(data.map(function(d,i) { | |
9003 return d.color || color(d, i); | |
9004 }).filter(function(d,i) { return !data[i].disabled })); | |
9005 | |
9006 var barsWrap = g.select('.nv-barsWrap') | |
9007 .datum(data.filter(function(d) { return !d.disabled })); | |
9008 | |
9009 barsWrap.transition().call(multibar); | |
9010 | |
9011 // Setup Axes | |
9012 if (showXAxis) { | |
9013 xAxis | |
9014 .scale(x) | |
9015 ._ticks( nv.utils.calcTicksY(availableHeight/24, data) ) | |
9016 .tickSize(-availableWidth, 0); | |
9017 | |
9018 g.select('.nv-x.nv-axis').call(xAxis); | |
9019 | |
9020 var xTicks = g.select('.nv-x.nv-axis').selectAll('g'); | |
9021 | |
9022 xTicks | |
9023 .selectAll('line, text'); | |
9024 } | |
9025 | |
9026 if (showYAxis) { | |
9027 yAxis | |
9028 .scale(y) | |
9029 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
9030 .tickSize( -availableHeight, 0); | |
9031 | |
9032 g.select('.nv-y.nv-axis') | |
9033 .attr('transform', 'translate(0,' + availableHeight + ')'); | |
9034 g.select('.nv-y.nv-axis').call(yAxis); | |
9035 } | |
9036 | |
9037 // Zero line | |
9038 g.select(".nv-zeroLine line") | |
9039 .attr("x1", y(0)) | |
9040 .attr("x2", y(0)) | |
9041 .attr("y1", 0) | |
9042 .attr("y2", -availableHeight) | |
9043 ; | |
9044 | |
9045 //============================================================ | |
9046 // Event Handling/Dispatching (in chart's scope) | |
9047 //------------------------------------------------------------ | |
9048 | |
9049 legend.dispatch.on('stateChange', function(newState) { | |
9050 for (var key in newState) | |
9051 state[key] = newState[key]; | |
9052 dispatch.stateChange(state); | |
9053 chart.update(); | |
9054 }); | |
9055 | |
9056 controls.dispatch.on('legendClick', function(d,i) { | |
9057 if (!d.disabled) return; | |
9058 controlsData = controlsData.map(function(s) { | |
9059 s.disabled = true; | |
9060 return s; | |
9061 }); | |
9062 d.disabled = false; | |
9063 | |
9064 switch (d.key) { | |
9065 case 'Grouped': | |
9066 multibar.stacked(false); | |
9067 break; | |
9068 case 'Stacked': | |
9069 multibar.stacked(true); | |
9070 break; | |
9071 } | |
9072 | |
9073 state.stacked = multibar.stacked(); | |
9074 dispatch.stateChange(state); | |
9075 stacked = multibar.stacked(); | |
9076 | |
9077 chart.update(); | |
9078 }); | |
9079 | |
9080 // Update chart from a state object passed to event handler | |
9081 dispatch.on('changeState', function(e) { | |
9082 | |
9083 if (typeof e.disabled !== 'undefined') { | |
9084 data.forEach(function(series,i) { | |
9085 series.disabled = e.disabled[i]; | |
9086 }); | |
9087 | |
9088 state.disabled = e.disabled; | |
9089 } | |
9090 | |
9091 if (typeof e.stacked !== 'undefined') { | |
9092 multibar.stacked(e.stacked); | |
9093 state.stacked = e.stacked; | |
9094 stacked = e.stacked; | |
9095 } | |
9096 | |
9097 chart.update(); | |
9098 }); | |
9099 }); | |
9100 renderWatch.renderEnd('multibar horizontal chart immediate'); | |
9101 return chart; | |
9102 } | |
9103 | |
9104 //============================================================ | |
9105 // Event Handling/Dispatching (out of chart's scope) | |
9106 //------------------------------------------------------------ | |
9107 | |
9108 multibar.dispatch.on('elementMouseover.tooltip', function(evt) { | |
9109 evt.value = chart.x()(evt.data); | |
9110 evt['series'] = { | |
9111 key: evt.data.key, | |
9112 value: chart.y()(evt.data), | |
9113 color: evt.color | |
9114 }; | |
9115 tooltip.data(evt).hidden(false); | |
9116 }); | |
9117 | |
9118 multibar.dispatch.on('elementMouseout.tooltip', function(evt) { | |
9119 tooltip.hidden(true); | |
9120 }); | |
9121 | |
9122 multibar.dispatch.on('elementMousemove.tooltip', function(evt) { | |
9123 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
9124 }); | |
9125 | |
9126 //============================================================ | |
9127 // Expose Public Variables | |
9128 //------------------------------------------------------------ | |
9129 | |
9130 // expose chart's sub-components | |
9131 chart.dispatch = dispatch; | |
9132 chart.multibar = multibar; | |
9133 chart.legend = legend; | |
9134 chart.controls = controls; | |
9135 chart.xAxis = xAxis; | |
9136 chart.yAxis = yAxis; | |
9137 chart.state = state; | |
9138 chart.tooltip = tooltip; | |
9139 | |
9140 chart.options = nv.utils.optionsFunc.bind(chart); | |
9141 | |
9142 chart._options = Object.create({}, { | |
9143 // simple options, just get/set the necessary values | |
9144 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
9145 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
9146 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
9147 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, | |
9148 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, | |
9149 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
9150 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
9151 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
9152 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
9153 | |
9154 // deprecated options | |
9155 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
9156 // deprecated after 1.7.1 | |
9157 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
9158 tooltip.enabled(!!_); | |
9159 }}, | |
9160 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
9161 // deprecated after 1.7.1 | |
9162 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
9163 tooltip.contentGenerator(_); | |
9164 }}, | |
9165 | |
9166 // options that require extra logic in the setter | |
9167 margin: {get: function(){return margin;}, set: function(_){ | |
9168 margin.top = _.top !== undefined ? _.top : margin.top; | |
9169 margin.right = _.right !== undefined ? _.right : margin.right; | |
9170 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
9171 margin.left = _.left !== undefined ? _.left : margin.left; | |
9172 }}, | |
9173 duration: {get: function(){return duration;}, set: function(_){ | |
9174 duration = _; | |
9175 renderWatch.reset(duration); | |
9176 multibar.duration(duration); | |
9177 xAxis.duration(duration); | |
9178 yAxis.duration(duration); | |
9179 }}, | |
9180 color: {get: function(){return color;}, set: function(_){ | |
9181 color = nv.utils.getColor(_); | |
9182 legend.color(color); | |
9183 }}, | |
9184 barColor: {get: function(){return multibar.barColor;}, set: function(_){ | |
9185 multibar.barColor(_); | |
9186 legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();}) | |
9187 }} | |
9188 }); | |
9189 | |
9190 nv.utils.inheritOptions(chart, multibar); | |
9191 nv.utils.initOptions(chart); | |
9192 | |
9193 return chart; | |
9194 }; | |
9195 nv.models.multiChart = function() { | |
9196 "use strict"; | |
9197 | |
9198 //============================================================ | |
9199 // Public Variables with Default Settings | |
9200 //------------------------------------------------------------ | |
9201 | |
9202 var margin = {top: 30, right: 20, bottom: 50, left: 60}, | |
9203 color = nv.utils.defaultColor(), | |
9204 width = null, | |
9205 height = null, | |
9206 showLegend = true, | |
9207 noData = null, | |
9208 yDomain1, | |
9209 yDomain2, | |
9210 getX = function(d) { return d.x }, | |
9211 getY = function(d) { return d.y}, | |
9212 interpolate = 'monotone', | |
9213 useVoronoi = true | |
9214 ; | |
9215 | |
9216 //============================================================ | |
9217 // Private Variables | |
9218 //------------------------------------------------------------ | |
9219 | |
9220 var x = d3.scale.linear(), | |
9221 yScale1 = d3.scale.linear(), | |
9222 yScale2 = d3.scale.linear(), | |
9223 | |
9224 lines1 = nv.models.line().yScale(yScale1), | |
9225 lines2 = nv.models.line().yScale(yScale2), | |
9226 | |
9227 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1), | |
9228 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2), | |
9229 | |
9230 stack1 = nv.models.stackedArea().yScale(yScale1), | |
9231 stack2 = nv.models.stackedArea().yScale(yScale2), | |
9232 | |
9233 xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5), | |
9234 yAxis1 = nv.models.axis().scale(yScale1).orient('left'), | |
9235 yAxis2 = nv.models.axis().scale(yScale2).orient('right'), | |
9236 | |
9237 legend = nv.models.legend().height(30), | |
9238 tooltip = nv.models.tooltip(), | |
9239 dispatch = d3.dispatch(); | |
9240 | |
9241 function chart(selection) { | |
9242 selection.each(function(data) { | |
9243 var container = d3.select(this), | |
9244 that = this; | |
9245 nv.utils.initSVG(container); | |
9246 | |
9247 chart.update = function() { container.transition().call(chart); }; | |
9248 chart.container = this; | |
9249 | |
9250 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
9251 availableHeight = nv.utils.availableHeight(height, container, margin); | |
9252 | |
9253 var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1}); | |
9254 var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2}); | |
9255 var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1}); | |
9256 var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2}); | |
9257 var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1}); | |
9258 var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2}); | |
9259 | |
9260 // Display noData message if there's nothing to show. | |
9261 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
9262 nv.utils.noData(chart, container); | |
9263 return chart; | |
9264 } else { | |
9265 container.selectAll('.nv-noData').remove(); | |
9266 } | |
9267 | |
9268 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1}) | |
9269 .map(function(d) { | |
9270 return d.values.map(function(d,i) { | |
9271 return { x: d.x, y: d.y } | |
9272 }) | |
9273 }); | |
9274 | |
9275 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2}) | |
9276 .map(function(d) { | |
9277 return d.values.map(function(d,i) { | |
9278 return { x: d.x, y: d.y } | |
9279 }) | |
9280 }); | |
9281 | |
9282 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } )) | |
9283 .range([0, availableWidth]); | |
9284 | |
9285 var wrap = container.selectAll('g.wrap.multiChart').data([data]); | |
9286 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g'); | |
9287 | |
9288 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
9289 gEnter.append('g').attr('class', 'nv-y1 nv-axis'); | |
9290 gEnter.append('g').attr('class', 'nv-y2 nv-axis'); | |
9291 gEnter.append('g').attr('class', 'lines1Wrap'); | |
9292 gEnter.append('g').attr('class', 'lines2Wrap'); | |
9293 gEnter.append('g').attr('class', 'bars1Wrap'); | |
9294 gEnter.append('g').attr('class', 'bars2Wrap'); | |
9295 gEnter.append('g').attr('class', 'stack1Wrap'); | |
9296 gEnter.append('g').attr('class', 'stack2Wrap'); | |
9297 gEnter.append('g').attr('class', 'legendWrap'); | |
9298 | |
9299 var g = wrap.select('g'); | |
9300 | |
9301 var color_array = data.map(function(d,i) { | |
9302 return data[i].color || color(d, i); | |
9303 }); | |
9304 | |
9305 if (showLegend) { | |
9306 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth; | |
9307 var legendXPosition = legend.align() ? legendWidth : 0; | |
9308 | |
9309 legend.width(legendWidth); | |
9310 legend.color(color_array); | |
9311 | |
9312 g.select('.legendWrap') | |
9313 .datum(data.map(function(series) { | |
9314 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey; | |
9315 series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)'); | |
9316 return series; | |
9317 })) | |
9318 .call(legend); | |
9319 | |
9320 if ( margin.top != legend.height()) { | |
9321 margin.top = legend.height(); | |
9322 availableHeight = nv.utils.availableHeight(height, container, margin); | |
9323 } | |
9324 | |
9325 g.select('.legendWrap') | |
9326 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')'); | |
9327 } | |
9328 | |
9329 lines1 | |
9330 .width(availableWidth) | |
9331 .height(availableHeight) | |
9332 .interpolate(interpolate) | |
9333 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'})); | |
9334 lines2 | |
9335 .width(availableWidth) | |
9336 .height(availableHeight) | |
9337 .interpolate(interpolate) | |
9338 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'})); | |
9339 bars1 | |
9340 .width(availableWidth) | |
9341 .height(availableHeight) | |
9342 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'})); | |
9343 bars2 | |
9344 .width(availableWidth) | |
9345 .height(availableHeight) | |
9346 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'})); | |
9347 stack1 | |
9348 .width(availableWidth) | |
9349 .height(availableHeight) | |
9350 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'})); | |
9351 stack2 | |
9352 .width(availableWidth) | |
9353 .height(availableHeight) | |
9354 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'})); | |
9355 | |
9356 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
9357 | |
9358 var lines1Wrap = g.select('.lines1Wrap') | |
9359 .datum(dataLines1.filter(function(d){return !d.disabled})); | |
9360 var bars1Wrap = g.select('.bars1Wrap') | |
9361 .datum(dataBars1.filter(function(d){return !d.disabled})); | |
9362 var stack1Wrap = g.select('.stack1Wrap') | |
9363 .datum(dataStack1.filter(function(d){return !d.disabled})); | |
9364 var lines2Wrap = g.select('.lines2Wrap') | |
9365 .datum(dataLines2.filter(function(d){return !d.disabled})); | |
9366 var bars2Wrap = g.select('.bars2Wrap') | |
9367 .datum(dataBars2.filter(function(d){return !d.disabled})); | |
9368 var stack2Wrap = g.select('.stack2Wrap') | |
9369 .datum(dataStack2.filter(function(d){return !d.disabled})); | |
9370 | |
9371 var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){ | |
9372 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) | |
9373 }).concat([{x:0, y:0}]) : []; | |
9374 var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){ | |
9375 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}}) | |
9376 }).concat([{x:0, y:0}]) : []; | |
9377 | |
9378 yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } )) | |
9379 .range([0, availableHeight]); | |
9380 | |
9381 yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } )) | |
9382 .range([0, availableHeight]); | |
9383 | |
9384 lines1.yDomain(yScale1.domain()); | |
9385 bars1.yDomain(yScale1.domain()); | |
9386 stack1.yDomain(yScale1.domain()); | |
9387 | |
9388 lines2.yDomain(yScale2.domain()); | |
9389 bars2.yDomain(yScale2.domain()); | |
9390 stack2.yDomain(yScale2.domain()); | |
9391 | |
9392 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);} | |
9393 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);} | |
9394 | |
9395 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);} | |
9396 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);} | |
9397 | |
9398 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);} | |
9399 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);} | |
9400 | |
9401 xAxis | |
9402 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
9403 .tickSize(-availableHeight, 0); | |
9404 | |
9405 g.select('.nv-x.nv-axis') | |
9406 .attr('transform', 'translate(0,' + availableHeight + ')'); | |
9407 d3.transition(g.select('.nv-x.nv-axis')) | |
9408 .call(xAxis); | |
9409 | |
9410 yAxis1 | |
9411 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) | |
9412 .tickSize( -availableWidth, 0); | |
9413 | |
9414 | |
9415 d3.transition(g.select('.nv-y1.nv-axis')) | |
9416 .call(yAxis1); | |
9417 | |
9418 yAxis2 | |
9419 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) | |
9420 .tickSize( -availableWidth, 0); | |
9421 | |
9422 d3.transition(g.select('.nv-y2.nv-axis')) | |
9423 .call(yAxis2); | |
9424 | |
9425 g.select('.nv-y1.nv-axis') | |
9426 .classed('nv-disabled', series1.length ? false : true) | |
9427 .attr('transform', 'translate(' + x.range()[0] + ',0)'); | |
9428 | |
9429 g.select('.nv-y2.nv-axis') | |
9430 .classed('nv-disabled', series2.length ? false : true) | |
9431 .attr('transform', 'translate(' + x.range()[1] + ',0)'); | |
9432 | |
9433 legend.dispatch.on('stateChange', function(newState) { | |
9434 chart.update(); | |
9435 }); | |
9436 | |
9437 //============================================================ | |
9438 // Event Handling/Dispatching | |
9439 //------------------------------------------------------------ | |
9440 | |
9441 function mouseover_line(evt) { | |
9442 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; | |
9443 evt.value = evt.point.x; | |
9444 evt.series = { | |
9445 value: evt.point.y, | |
9446 color: evt.point.color | |
9447 }; | |
9448 tooltip | |
9449 .duration(100) | |
9450 .valueFormatter(function(d, i) { | |
9451 return yaxis.tickFormat()(d, i); | |
9452 }) | |
9453 .data(evt) | |
9454 .position(evt.pos) | |
9455 .hidden(false); | |
9456 } | |
9457 | |
9458 function mouseover_stack(evt) { | |
9459 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1; | |
9460 evt.point['x'] = stack1.x()(evt.point); | |
9461 evt.point['y'] = stack1.y()(evt.point); | |
9462 tooltip | |
9463 .duration(100) | |
9464 .valueFormatter(function(d, i) { | |
9465 return yaxis.tickFormat()(d, i); | |
9466 }) | |
9467 .data(evt) | |
9468 .position(evt.pos) | |
9469 .hidden(false); | |
9470 } | |
9471 | |
9472 function mouseover_bar(evt) { | |
9473 var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1; | |
9474 | |
9475 evt.value = bars1.x()(evt.data); | |
9476 evt['series'] = { | |
9477 value: bars1.y()(evt.data), | |
9478 color: evt.color | |
9479 }; | |
9480 tooltip | |
9481 .duration(0) | |
9482 .valueFormatter(function(d, i) { | |
9483 return yaxis.tickFormat()(d, i); | |
9484 }) | |
9485 .data(evt) | |
9486 .hidden(false); | |
9487 } | |
9488 | |
9489 lines1.dispatch.on('elementMouseover.tooltip', mouseover_line); | |
9490 lines2.dispatch.on('elementMouseover.tooltip', mouseover_line); | |
9491 lines1.dispatch.on('elementMouseout.tooltip', function(evt) { | |
9492 tooltip.hidden(true) | |
9493 }); | |
9494 lines2.dispatch.on('elementMouseout.tooltip', function(evt) { | |
9495 tooltip.hidden(true) | |
9496 }); | |
9497 | |
9498 stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack); | |
9499 stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack); | |
9500 stack1.dispatch.on('elementMouseout.tooltip', function(evt) { | |
9501 tooltip.hidden(true) | |
9502 }); | |
9503 stack2.dispatch.on('elementMouseout.tooltip', function(evt) { | |
9504 tooltip.hidden(true) | |
9505 }); | |
9506 | |
9507 bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar); | |
9508 bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar); | |
9509 | |
9510 bars1.dispatch.on('elementMouseout.tooltip', function(evt) { | |
9511 tooltip.hidden(true); | |
9512 }); | |
9513 bars2.dispatch.on('elementMouseout.tooltip', function(evt) { | |
9514 tooltip.hidden(true); | |
9515 }); | |
9516 bars1.dispatch.on('elementMousemove.tooltip', function(evt) { | |
9517 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
9518 }); | |
9519 bars2.dispatch.on('elementMousemove.tooltip', function(evt) { | |
9520 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
9521 }); | |
9522 | |
9523 }); | |
9524 | |
9525 return chart; | |
9526 } | |
9527 | |
9528 //============================================================ | |
9529 // Global getters and setters | |
9530 //------------------------------------------------------------ | |
9531 | |
9532 chart.dispatch = dispatch; | |
9533 chart.lines1 = lines1; | |
9534 chart.lines2 = lines2; | |
9535 chart.bars1 = bars1; | |
9536 chart.bars2 = bars2; | |
9537 chart.stack1 = stack1; | |
9538 chart.stack2 = stack2; | |
9539 chart.xAxis = xAxis; | |
9540 chart.yAxis1 = yAxis1; | |
9541 chart.yAxis2 = yAxis2; | |
9542 chart.tooltip = tooltip; | |
9543 | |
9544 chart.options = nv.utils.optionsFunc.bind(chart); | |
9545 | |
9546 chart._options = Object.create({}, { | |
9547 // simple options, just get/set the necessary values | |
9548 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
9549 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
9550 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
9551 yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}}, | |
9552 yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}}, | |
9553 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
9554 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, | |
9555 | |
9556 // deprecated options | |
9557 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
9558 // deprecated after 1.7.1 | |
9559 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
9560 tooltip.enabled(!!_); | |
9561 }}, | |
9562 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
9563 // deprecated after 1.7.1 | |
9564 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
9565 tooltip.contentGenerator(_); | |
9566 }}, | |
9567 | |
9568 // options that require extra logic in the setter | |
9569 margin: {get: function(){return margin;}, set: function(_){ | |
9570 margin.top = _.top !== undefined ? _.top : margin.top; | |
9571 margin.right = _.right !== undefined ? _.right : margin.right; | |
9572 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
9573 margin.left = _.left !== undefined ? _.left : margin.left; | |
9574 }}, | |
9575 color: {get: function(){return color;}, set: function(_){ | |
9576 color = nv.utils.getColor(_); | |
9577 }}, | |
9578 x: {get: function(){return getX;}, set: function(_){ | |
9579 getX = _; | |
9580 lines1.x(_); | |
9581 lines2.x(_); | |
9582 bars1.x(_); | |
9583 bars2.x(_); | |
9584 stack1.x(_); | |
9585 stack2.x(_); | |
9586 }}, | |
9587 y: {get: function(){return getY;}, set: function(_){ | |
9588 getY = _; | |
9589 lines1.y(_); | |
9590 lines2.y(_); | |
9591 stack1.y(_); | |
9592 stack2.y(_); | |
9593 bars1.y(_); | |
9594 bars2.y(_); | |
9595 }}, | |
9596 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){ | |
9597 useVoronoi=_; | |
9598 lines1.useVoronoi(_); | |
9599 lines2.useVoronoi(_); | |
9600 stack1.useVoronoi(_); | |
9601 stack2.useVoronoi(_); | |
9602 }} | |
9603 }); | |
9604 | |
9605 nv.utils.initOptions(chart); | |
9606 | |
9607 return chart; | |
9608 }; | |
9609 | |
9610 | |
9611 nv.models.ohlcBar = function() { | |
9612 "use strict"; | |
9613 | |
9614 //============================================================ | |
9615 // Public Variables with Default Settings | |
9616 //------------------------------------------------------------ | |
9617 | |
9618 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
9619 , width = null | |
9620 , height = null | |
9621 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
9622 , container = null | |
9623 , x = d3.scale.linear() | |
9624 , y = d3.scale.linear() | |
9625 , getX = function(d) { return d.x } | |
9626 , getY = function(d) { return d.y } | |
9627 , getOpen = function(d) { return d.open } | |
9628 , getClose = function(d) { return d.close } | |
9629 , getHigh = function(d) { return d.high } | |
9630 , getLow = function(d) { return d.low } | |
9631 , forceX = [] | |
9632 , forceY = [] | |
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 | |
9634 , clipEdge = true | |
9635 , color = nv.utils.defaultColor() | |
9636 , interactive = false | |
9637 , xDomain | |
9638 , yDomain | |
9639 , xRange | |
9640 , yRange | |
9641 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove') | |
9642 ; | |
9643 | |
9644 //============================================================ | |
9645 // Private Variables | |
9646 //------------------------------------------------------------ | |
9647 | |
9648 function chart(selection) { | |
9649 selection.each(function(data) { | |
9650 container = d3.select(this); | |
9651 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
9652 availableHeight = nv.utils.availableHeight(height, container, margin); | |
9653 | |
9654 nv.utils.initSVG(container); | |
9655 | |
9656 // ohlc bar width. | |
9657 var w = (availableWidth / data[0].values.length) * .9; | |
9658 | |
9659 // Setup Scales | |
9660 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) )); | |
9661 | |
9662 if (padData) | |
9663 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); | |
9664 else | |
9665 x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]); | |
9666 | |
9667 y.domain(yDomain || [ | |
9668 d3.min(data[0].values.map(getLow).concat(forceY)), | |
9669 d3.max(data[0].values.map(getHigh).concat(forceY)) | |
9670 ] | |
9671 ).range(yRange || [availableHeight, 0]); | |
9672 | |
9673 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point | |
9674 if (x.domain()[0] === x.domain()[1]) | |
9675 x.domain()[0] ? | |
9676 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) | |
9677 : x.domain([-1,1]); | |
9678 | |
9679 if (y.domain()[0] === y.domain()[1]) | |
9680 y.domain()[0] ? | |
9681 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01]) | |
9682 : y.domain([-1,1]); | |
9683 | |
9684 // Setup containers and skeleton of chart | |
9685 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]); | |
9686 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar'); | |
9687 var defsEnter = wrapEnter.append('defs'); | |
9688 var gEnter = wrapEnter.append('g'); | |
9689 var g = wrap.select('g'); | |
9690 | |
9691 gEnter.append('g').attr('class', 'nv-ticks'); | |
9692 | |
9693 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
9694 | |
9695 container | |
9696 .on('click', function(d,i) { | |
9697 dispatch.chartClick({ | |
9698 data: d, | |
9699 index: i, | |
9700 pos: d3.event, | |
9701 id: id | |
9702 }); | |
9703 }); | |
9704 | |
9705 defsEnter.append('clipPath') | |
9706 .attr('id', 'nv-chart-clip-path-' + id) | |
9707 .append('rect'); | |
9708 | |
9709 wrap.select('#nv-chart-clip-path-' + id + ' rect') | |
9710 .attr('width', availableWidth) | |
9711 .attr('height', availableHeight); | |
9712 | |
9713 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : ''); | |
9714 | |
9715 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick') | |
9716 .data(function(d) { return d }); | |
9717 ticks.exit().remove(); | |
9718 | |
9719 ticks.enter().append('path') | |
9720 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i }) | |
9721 .attr('d', function(d,i) { | |
9722 return 'm0,0l0,' | |
9723 + (y(getOpen(d,i)) | |
9724 - y(getHigh(d,i))) | |
9725 + 'l' | |
9726 + (-w/2) | |
9727 + ',0l' | |
9728 + (w/2) | |
9729 + ',0l0,' | |
9730 + (y(getLow(d,i)) - y(getOpen(d,i))) | |
9731 + 'l0,' | |
9732 + (y(getClose(d,i)) | |
9733 - y(getLow(d,i))) | |
9734 + 'l' | |
9735 + (w/2) | |
9736 + ',0l' | |
9737 + (-w/2) | |
9738 + ',0z'; | |
9739 }) | |
9740 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) | |
9741 .attr('fill', function(d,i) { return color[0]; }) | |
9742 .attr('stroke', function(d,i) { return color[0]; }) | |
9743 .attr('x', 0 ) | |
9744 .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) }) | |
9745 .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) }); | |
9746 | |
9747 // the bar colors are controlled by CSS currently | |
9748 ticks.attr('class', function(d,i,j) { | |
9749 return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i; | |
9750 }); | |
9751 | |
9752 d3.transition(ticks) | |
9753 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; }) | |
9754 .attr('d', function(d,i) { | |
9755 var w = (availableWidth / data[0].values.length) * .9; | |
9756 return 'm0,0l0,' | |
9757 + (y(getOpen(d,i)) | |
9758 - y(getHigh(d,i))) | |
9759 + 'l' | |
9760 + (-w/2) | |
9761 + ',0l' | |
9762 + (w/2) | |
9763 + ',0l0,' | |
9764 + (y(getLow(d,i)) | |
9765 - y(getOpen(d,i))) | |
9766 + 'l0,' | |
9767 + (y(getClose(d,i)) | |
9768 - y(getLow(d,i))) | |
9769 + 'l' | |
9770 + (w/2) | |
9771 + ',0l' | |
9772 + (-w/2) | |
9773 + ',0z'; | |
9774 }); | |
9775 }); | |
9776 | |
9777 return chart; | |
9778 } | |
9779 | |
9780 | |
9781 //Create methods to allow outside functions to highlight a specific bar. | |
9782 chart.highlightPoint = function(pointIndex, isHoverOver) { | |
9783 chart.clearHighlights(); | |
9784 container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex) | |
9785 .classed("hover", isHoverOver) | |
9786 ; | |
9787 }; | |
9788 | |
9789 chart.clearHighlights = function() { | |
9790 container.select(".nv-ohlcBar .nv-tick.hover") | |
9791 .classed("hover", false) | |
9792 ; | |
9793 }; | |
9794 | |
9795 //============================================================ | |
9796 // Expose Public Variables | |
9797 //------------------------------------------------------------ | |
9798 | |
9799 chart.dispatch = dispatch; | |
9800 chart.options = nv.utils.optionsFunc.bind(chart); | |
9801 | |
9802 chart._options = Object.create({}, { | |
9803 // simple options, just get/set the necessary values | |
9804 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
9805 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
9806 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
9807 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
9808 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
9809 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
9810 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
9811 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
9812 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, | |
9813 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, | |
9814 padData: {get: function(){return padData;}, set: function(_){padData=_;}}, | |
9815 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, | |
9816 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
9817 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, | |
9818 | |
9819 x: {get: function(){return getX;}, set: function(_){getX=_;}}, | |
9820 y: {get: function(){return getY;}, set: function(_){getY=_;}}, | |
9821 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}}, | |
9822 close: {get: function(){return getClose();}, set: function(_){getClose=_;}}, | |
9823 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}}, | |
9824 low: {get: function(){return getLow;}, set: function(_){getLow=_;}}, | |
9825 | |
9826 // options that require extra logic in the setter | |
9827 margin: {get: function(){return margin;}, set: function(_){ | |
9828 margin.top = _.top != undefined ? _.top : margin.top; | |
9829 margin.right = _.right != undefined ? _.right : margin.right; | |
9830 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; | |
9831 margin.left = _.left != undefined ? _.left : margin.left; | |
9832 }}, | |
9833 color: {get: function(){return color;}, set: function(_){ | |
9834 color = nv.utils.getColor(_); | |
9835 }} | |
9836 }); | |
9837 | |
9838 nv.utils.initOptions(chart); | |
9839 return chart; | |
9840 }; | |
9841 // Code adapted from Jason Davies' "Parallel Coordinates" | |
9842 // http://bl.ocks.org/jasondavies/1341281 | |
9843 nv.models.parallelCoordinates = function() { | |
9844 "use strict"; | |
9845 | |
9846 //============================================================ | |
9847 // Public Variables with Default Settings | |
9848 //------------------------------------------------------------ | |
9849 | |
9850 var margin = {top: 30, right: 0, bottom: 10, left: 0} | |
9851 , width = null | |
9852 , height = null | |
9853 , x = d3.scale.ordinal() | |
9854 , y = {} | |
9855 , dimensionNames = [] | |
9856 , dimensionFormats = [] | |
9857 , color = nv.utils.defaultColor() | |
9858 , filters = [] | |
9859 , active = [] | |
9860 , dragging = [] | |
9861 , lineTension = 1 | |
9862 , dispatch = d3.dispatch('brush', 'elementMouseover', 'elementMouseout') | |
9863 ; | |
9864 | |
9865 //============================================================ | |
9866 // Private Variables | |
9867 //------------------------------------------------------------ | |
9868 | |
9869 function chart(selection) { | |
9870 selection.each(function(data) { | |
9871 var container = d3.select(this); | |
9872 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
9873 availableHeight = nv.utils.availableHeight(height, container, margin); | |
9874 | |
9875 nv.utils.initSVG(container); | |
9876 | |
9877 active = data; //set all active before first brush call | |
9878 | |
9879 // Setup Scales | |
9880 x.rangePoints([0, availableWidth], 1).domain(dimensionNames); | |
9881 | |
9882 //Set as true if all values on an axis are missing. | |
9883 var onlyNanValues = {}; | |
9884 // Extract the list of dimensions and create a scale for each. | |
9885 dimensionNames.forEach(function(d) { | |
9886 var extent = d3.extent(data, function(p) { return +p[d]; }); | |
9887 onlyNanValues[d] = false; | |
9888 //If there is no values to display on an axis, set the extent to 0 | |
9889 if (extent[0] === undefined) { | |
9890 onlyNanValues[d] = true; | |
9891 extent[0] = 0; | |
9892 extent[1] = 0; | |
9893 } | |
9894 //Scale axis if there is only one value | |
9895 if (extent[0] === extent[1]) { | |
9896 extent[0] = extent[0] - 1; | |
9897 extent[1] = extent[1] + 1; | |
9898 } | |
9899 //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text. | |
9900 //The remaining 10% are used to display the missingValue line. | |
9901 y[d] = d3.scale.linear() | |
9902 .domain(extent) | |
9903 .range([(availableHeight - 12) * 0.9, 0]); | |
9904 | |
9905 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush); | |
9906 | |
9907 return d != 'name'; | |
9908 }); | |
9909 | |
9910 // Setup containers and skeleton of chart | |
9911 var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]); | |
9912 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates'); | |
9913 var gEnter = wrapEnter.append('g'); | |
9914 var g = wrap.select('g'); | |
9915 | |
9916 gEnter.append('g').attr('class', 'nv-parallelCoordinates background'); | |
9917 gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground'); | |
9918 gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline'); | |
9919 | |
9920 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
9921 | |
9922 var line = d3.svg.line().interpolate('cardinal').tension(lineTension), | |
9923 axis = d3.svg.axis().orient('left'), | |
9924 axisDrag = d3.behavior.drag() | |
9925 .on('dragstart', dragStart) | |
9926 .on('drag', dragMove) | |
9927 .on('dragend', dragEnd); | |
9928 | |
9929 //Add missing value line at the bottom of the chart | |
9930 var missingValuesline, missingValueslineText; | |
9931 var step = x.range()[1] - x.range()[0]; | |
9932 var axisWithMissingValues = []; | |
9933 var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12]; | |
9934 missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]); | |
9935 missingValuesline.enter().append('line'); | |
9936 missingValuesline.exit().remove(); | |
9937 missingValuesline.attr("x1", function(d) { return d[0]; }) | |
9938 .attr("y1", function(d) { return d[1]; }) | |
9939 .attr("x2", function(d) { return d[2]; }) | |
9940 .attr("y2", function(d) { return d[3]; }); | |
9941 | |
9942 //Add the text "undefined values" under the missing value line | |
9943 missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]); | |
9944 missingValueslineText.append('text').data(["undefined values"]); | |
9945 missingValueslineText.enter().append('text'); | |
9946 missingValueslineText.exit().remove(); | |
9947 missingValueslineText.attr("y", availableHeight) | |
9948 //To have the text right align with the missingValues line, substract 92 representing the text size. | |
9949 .attr("x", availableWidth - 92 - step / 2) | |
9950 .text(function(d) { return d; }); | |
9951 | |
9952 // Add grey background lines for context. | |
9953 var background = wrap.select('.background').selectAll('path').data(data); | |
9954 background.enter().append('path'); | |
9955 background.exit().remove(); | |
9956 background.attr('d', path); | |
9957 | |
9958 // Add blue foreground lines for focus. | |
9959 var foreground = wrap.select('.foreground').selectAll('path').data(data); | |
9960 foreground.enter().append('path') | |
9961 foreground.exit().remove(); | |
9962 foreground.attr('d', path).attr('stroke', color); | |
9963 foreground.on("mouseover", function (d, i) { | |
9964 d3.select(this).classed('hover', true); | |
9965 dispatch.elementMouseover({ | |
9966 label: d.name, | |
9967 data: d.data, | |
9968 index: i, | |
9969 pos: [d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]] | |
9970 }); | |
9971 | |
9972 }); | |
9973 foreground.on("mouseout", function (d, i) { | |
9974 d3.select(this).classed('hover', false); | |
9975 dispatch.elementMouseout({ | |
9976 label: d.name, | |
9977 data: d.data, | |
9978 index: i | |
9979 }); | |
9980 }); | |
9981 | |
9982 // Add a group element for each dimension. | |
9983 var dimensions = g.selectAll('.dimension').data(dimensionNames); | |
9984 var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension'); | |
9985 dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates nv-axis'); | |
9986 dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates-brush'); | |
9987 dimensionsEnter.append('text').attr('class', 'nv-parallelCoordinates nv-label'); | |
9988 | |
9989 dimensions.attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; }); | |
9990 dimensions.exit().remove(); | |
9991 | |
9992 // Add an axis and title. | |
9993 dimensions.select('.nv-label') | |
9994 .style("cursor", "move") | |
9995 .attr('dy', '-1em') | |
9996 .attr('text-anchor', 'middle') | |
9997 .text(String) | |
9998 .on("mouseover", function(d, i) { | |
9999 dispatch.elementMouseover({ | |
10000 dim: d, | |
10001 pos: [d3.mouse(this.parentNode.parentNode)[0], d3.mouse(this.parentNode.parentNode)[1]] | |
10002 }); | |
10003 }) | |
10004 .on("mouseout", function(d, i) { | |
10005 dispatch.elementMouseout({ | |
10006 dim: d | |
10007 }); | |
10008 }) | |
10009 .call(axisDrag); | |
10010 | |
10011 dimensions.select('.nv-axis') | |
10012 .each(function (d, i) { | |
10013 d3.select(this).call(axis.scale(y[d]).tickFormat(d3.format(dimensionFormats[i]))); | |
10014 }); | |
10015 | |
10016 dimensions.select('.nv-parallelCoordinates-brush') | |
10017 .each(function (d) { | |
10018 d3.select(this).call(y[d].brush); | |
10019 }) | |
10020 .selectAll('rect') | |
10021 .attr('x', -8) | |
10022 .attr('width', 16); | |
10023 | |
10024 // Returns the path for a given data point. | |
10025 function path(d) { | |
10026 return line(dimensionNames.map(function (p) { | |
10027 //If value if missing, put the value on the missing value line | |
10028 if(isNaN(d[p]) || isNaN(parseFloat(d[p]))) { | |
10029 var domain = y[p].domain(); | |
10030 var range = y[p].range(); | |
10031 var min = domain[0] - (domain[1] - domain[0]) / 9; | |
10032 | |
10033 //If it's not already the case, allow brush to select undefined values | |
10034 if(axisWithMissingValues.indexOf(p) < 0) { | |
10035 | |
10036 var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]); | |
10037 y[p].brush.y(newscale); | |
10038 axisWithMissingValues.push(p); | |
10039 } | |
10040 | |
10041 return [x(p), y[p](min)]; | |
10042 } | |
10043 | |
10044 //If parallelCoordinate contain missing values show the missing values line otherwise, hide it. | |
10045 if(axisWithMissingValues.length > 0) { | |
10046 missingValuesline.style("display", "inline"); | |
10047 missingValueslineText.style("display", "inline"); | |
10048 } else { | |
10049 missingValuesline.style("display", "none"); | |
10050 missingValueslineText.style("display", "none"); | |
10051 } | |
10052 | |
10053 return [x(p), y[p](d[p])]; | |
10054 })); | |
10055 } | |
10056 | |
10057 // Handles a brush event, toggling the display of foreground lines. | |
10058 function brush() { | |
10059 var actives = dimensionNames.filter(function(p) { return !y[p].brush.empty(); }), | |
10060 extents = actives.map(function(p) { return y[p].brush.extent(); }); | |
10061 | |
10062 filters = []; //erase current filters | |
10063 actives.forEach(function(d,i) { | |
10064 filters[i] = { | |
10065 dimension: d, | |
10066 extent: extents[i] | |
10067 } | |
10068 }); | |
10069 | |
10070 active = []; //erase current active list | |
10071 foreground.style('display', function(d) { | |
10072 var isActive = actives.every(function(p, i) { | |
10073 if(isNaN(d[p]) && extents[i][0] == y[p].brush.y().domain()[0]) return true; | |
10074 return extents[i][0] <= d[p] && d[p] <= extents[i][1]; | |
10075 }); | |
10076 if (isActive) active.push(d); | |
10077 return isActive ? null : 'none'; | |
10078 }); | |
10079 | |
10080 dispatch.brush({ | |
10081 filters: filters, | |
10082 active: active | |
10083 }); | |
10084 } | |
10085 | |
10086 function dragStart(d, i) { | |
10087 dragging[d] = this.parentNode.__origin__ = x(d); | |
10088 background.attr("visibility", "hidden"); | |
10089 | |
10090 } | |
10091 | |
10092 function dragMove(d, i) { | |
10093 dragging[d] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x)); | |
10094 foreground.attr("d", path); | |
10095 dimensionNames.sort(function (a, b) { return position(a) - position(b); }); | |
10096 x.domain(dimensionNames); | |
10097 dimensions.attr("transform", function(d) { return "translate(" + position(d) + ")"; }); | |
10098 } | |
10099 | |
10100 function dragEnd(d, i) { | |
10101 delete this.parentNode.__origin__; | |
10102 delete dragging[d]; | |
10103 d3.select(this.parentNode).attr("transform", "translate(" + x(d) + ")"); | |
10104 foreground | |
10105 .attr("d", path); | |
10106 background | |
10107 .attr("d", path) | |
10108 .attr("visibility", null); | |
10109 | |
10110 } | |
10111 | |
10112 function position(d) { | |
10113 var v = dragging[d]; | |
10114 return v == null ? x(d) : v; | |
10115 } | |
10116 }); | |
10117 | |
10118 return chart; | |
10119 } | |
10120 | |
10121 //============================================================ | |
10122 // Expose Public Variables | |
10123 //------------------------------------------------------------ | |
10124 | |
10125 chart.dispatch = dispatch; | |
10126 chart.options = nv.utils.optionsFunc.bind(chart); | |
10127 | |
10128 chart._options = Object.create({}, { | |
10129 // simple options, just get/set the necessary values | |
10130 width: {get: function(){return width;}, set: function(_){width= _;}}, | |
10131 height: {get: function(){return height;}, set: function(_){height= _;}}, | |
10132 dimensionNames: {get: function() { return dimensionNames;}, set: function(_){dimensionNames= _;}}, | |
10133 dimensionFormats : {get: function(){return dimensionFormats;}, set: function (_){dimensionFormats=_;}}, | |
10134 lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}}, | |
10135 | |
10136 // deprecated options | |
10137 dimensions: {get: function (){return dimensionNames;}, set: function(_){ | |
10138 // deprecated after 1.8.1 | |
10139 nv.deprecated('dimensions', 'use dimensionNames instead'); | |
10140 dimensionNames = _; | |
10141 }}, | |
10142 | |
10143 // options that require extra logic in the setter | |
10144 margin: {get: function(){return margin;}, set: function(_){ | |
10145 margin.top = _.top !== undefined ? _.top : margin.top; | |
10146 margin.right = _.right !== undefined ? _.right : margin.right; | |
10147 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
10148 margin.left = _.left !== undefined ? _.left : margin.left; | |
10149 }}, | |
10150 color: {get: function(){return color;}, set: function(_){ | |
10151 color = nv.utils.getColor(_); | |
10152 }} | |
10153 }); | |
10154 | |
10155 nv.utils.initOptions(chart); | |
10156 return chart; | |
10157 }; | |
10158 nv.models.pie = function() { | |
10159 "use strict"; | |
10160 | |
10161 //============================================================ | |
10162 // Public Variables with Default Settings | |
10163 //------------------------------------------------------------ | |
10164 | |
10165 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
10166 , width = 500 | |
10167 , height = 500 | |
10168 , getX = function(d) { return d.x } | |
10169 , getY = function(d) { return d.y } | |
10170 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
10171 , container = null | |
10172 , color = nv.utils.defaultColor() | |
10173 , valueFormat = d3.format(',.2f') | |
10174 , showLabels = true | |
10175 , labelsOutside = false | |
10176 , labelType = "key" | |
10177 , labelThreshold = .02 //if slice percentage is under this, don't show label | |
10178 , donut = false | |
10179 , title = false | |
10180 , growOnHover = true | |
10181 , titleOffset = 0 | |
10182 , labelSunbeamLayout = false | |
10183 , startAngle = false | |
10184 , padAngle = false | |
10185 , endAngle = false | |
10186 , cornerRadius = 0 | |
10187 , donutRatio = 0.5 | |
10188 , arcsRadius = [] | |
10189 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd') | |
10190 ; | |
10191 | |
10192 var arcs = []; | |
10193 var arcsOver = []; | |
10194 | |
10195 //============================================================ | |
10196 // chart function | |
10197 //------------------------------------------------------------ | |
10198 | |
10199 var renderWatch = nv.utils.renderWatch(dispatch); | |
10200 | |
10201 function chart(selection) { | |
10202 renderWatch.reset(); | |
10203 selection.each(function(data) { | |
10204 var availableWidth = width - margin.left - margin.right | |
10205 , availableHeight = height - margin.top - margin.bottom | |
10206 , radius = Math.min(availableWidth, availableHeight) / 2 | |
10207 , arcsRadiusOuter = [] | |
10208 , arcsRadiusInner = [] | |
10209 ; | |
10210 | |
10211 container = d3.select(this) | |
10212 if (arcsRadius.length === 0) { | |
10213 var outer = radius - radius / 5; | |
10214 var inner = donutRatio * radius; | |
10215 for (var i = 0; i < data[0].length; i++) { | |
10216 arcsRadiusOuter.push(outer); | |
10217 arcsRadiusInner.push(inner); | |
10218 } | |
10219 } else { | |
10220 arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; }); | |
10221 arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; }); | |
10222 donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); })); | |
10223 } | |
10224 nv.utils.initSVG(container); | |
10225 | |
10226 // Setup containers and skeleton of chart | |
10227 var wrap = container.selectAll('.nv-wrap.nv-pie').data(data); | |
10228 var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id); | |
10229 var gEnter = wrapEnter.append('g'); | |
10230 var g = wrap.select('g'); | |
10231 var g_pie = gEnter.append('g').attr('class', 'nv-pie'); | |
10232 gEnter.append('g').attr('class', 'nv-pieLabels'); | |
10233 | |
10234 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
10235 g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); | |
10236 g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); | |
10237 | |
10238 // | |
10239 container.on('click', function(d,i) { | |
10240 dispatch.chartClick({ | |
10241 data: d, | |
10242 index: i, | |
10243 pos: d3.event, | |
10244 id: id | |
10245 }); | |
10246 }); | |
10247 | |
10248 arcs = []; | |
10249 arcsOver = []; | |
10250 for (var i = 0; i < data[0].length; i++) { | |
10251 | |
10252 var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]); | |
10253 var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5); | |
10254 | |
10255 if (startAngle !== false) { | |
10256 arc.startAngle(startAngle); | |
10257 arcOver.startAngle(startAngle); | |
10258 } | |
10259 if (endAngle !== false) { | |
10260 arc.endAngle(endAngle); | |
10261 arcOver.endAngle(endAngle); | |
10262 } | |
10263 if (donut) { | |
10264 arc.innerRadius(arcsRadiusInner[i]); | |
10265 arcOver.innerRadius(arcsRadiusInner[i]); | |
10266 } | |
10267 | |
10268 if (arc.cornerRadius && cornerRadius) { | |
10269 arc.cornerRadius(cornerRadius); | |
10270 arcOver.cornerRadius(cornerRadius); | |
10271 } | |
10272 | |
10273 arcs.push(arc); | |
10274 arcsOver.push(arcOver); | |
10275 } | |
10276 | |
10277 // Setup the Pie chart and choose the data element | |
10278 var pie = d3.layout.pie() | |
10279 .sort(null) | |
10280 .value(function(d) { return d.disabled ? 0 : getY(d) }); | |
10281 | |
10282 // padAngle added in d3 3.5 | |
10283 if (pie.padAngle && padAngle) { | |
10284 pie.padAngle(padAngle); | |
10285 } | |
10286 | |
10287 // if title is specified and donut, put it in the middle | |
10288 if (donut && title) { | |
10289 g_pie.append("text").attr('class', 'nv-pie-title'); | |
10290 | |
10291 wrap.select('.nv-pie-title') | |
10292 .style("text-anchor", "middle") | |
10293 .text(function (d) { | |
10294 return title; | |
10295 }) | |
10296 .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px") | |
10297 .attr("dy", "0.35em") // trick to vertically center text | |
10298 .attr('transform', function(d, i) { | |
10299 return 'translate(0, '+ titleOffset + ')'; | |
10300 }); | |
10301 } | |
10302 | |
10303 var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie); | |
10304 var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie); | |
10305 | |
10306 slices.exit().remove(); | |
10307 pieLabels.exit().remove(); | |
10308 | |
10309 var ae = slices.enter().append('g'); | |
10310 ae.attr('class', 'nv-slice'); | |
10311 ae.on('mouseover', function(d, i) { | |
10312 d3.select(this).classed('hover', true); | |
10313 if (growOnHover) { | |
10314 d3.select(this).select("path").transition() | |
10315 .duration(70) | |
10316 .attr("d", arcsOver[i]); | |
10317 } | |
10318 dispatch.elementMouseover({ | |
10319 data: d.data, | |
10320 index: i, | |
10321 color: d3.select(this).style("fill") | |
10322 }); | |
10323 }); | |
10324 ae.on('mouseout', function(d, i) { | |
10325 d3.select(this).classed('hover', false); | |
10326 if (growOnHover) { | |
10327 d3.select(this).select("path").transition() | |
10328 .duration(50) | |
10329 .attr("d", arcs[i]); | |
10330 } | |
10331 dispatch.elementMouseout({data: d.data, index: i}); | |
10332 }); | |
10333 ae.on('mousemove', function(d, i) { | |
10334 dispatch.elementMousemove({data: d.data, index: i}); | |
10335 }); | |
10336 ae.on('click', function(d, i) { | |
10337 dispatch.elementClick({ | |
10338 data: d.data, | |
10339 index: i, | |
10340 color: d3.select(this).style("fill") | |
10341 }); | |
10342 }); | |
10343 ae.on('dblclick', function(d, i) { | |
10344 dispatch.elementDblClick({ | |
10345 data: d.data, | |
10346 index: i, | |
10347 color: d3.select(this).style("fill") | |
10348 }); | |
10349 }); | |
10350 | |
10351 slices.attr('fill', function(d,i) { return color(d.data, i); }); | |
10352 slices.attr('stroke', function(d,i) { return color(d.data, i); }); | |
10353 | |
10354 var paths = ae.append('path').each(function(d) { | |
10355 this._current = d; | |
10356 }); | |
10357 | |
10358 slices.select('path') | |
10359 .transition() | |
10360 .attr('d', function (d, i) { return arcs[i](d); }) | |
10361 .attrTween('d', arcTween); | |
10362 | |
10363 if (showLabels) { | |
10364 // This does the normal label | |
10365 var labelsArc = []; | |
10366 for (var i = 0; i < data[0].length; i++) { | |
10367 labelsArc.push(arcs[i]); | |
10368 | |
10369 if (labelsOutside) { | |
10370 if (donut) { | |
10371 labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius()); | |
10372 if (startAngle !== false) labelsArc[i].startAngle(startAngle); | |
10373 if (endAngle !== false) labelsArc[i].endAngle(endAngle); | |
10374 } | |
10375 } else if (!donut) { | |
10376 labelsArc[i].innerRadius(0); | |
10377 } | |
10378 } | |
10379 | |
10380 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) { | |
10381 var group = d3.select(this); | |
10382 | |
10383 group.attr('transform', function (d, i) { | |
10384 if (labelSunbeamLayout) { | |
10385 d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate | |
10386 d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate | |
10387 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); | |
10388 if ((d.startAngle + d.endAngle) / 2 < Math.PI) { | |
10389 rotateAngle -= 90; | |
10390 } else { | |
10391 rotateAngle += 90; | |
10392 } | |
10393 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')'; | |
10394 } else { | |
10395 d.outerRadius = radius + 10; // Set Outer Coordinate | |
10396 d.innerRadius = radius + 15; // Set Inner Coordinate | |
10397 return 'translate(' + labelsArc[i].centroid(d) + ')' | |
10398 } | |
10399 }); | |
10400 | |
10401 group.append('rect') | |
10402 .style('stroke', '#fff') | |
10403 .style('fill', '#fff') | |
10404 .attr("rx", 3) | |
10405 .attr("ry", 3); | |
10406 | |
10407 group.append('text') | |
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 | |
10409 .style('fill', '#000') | |
10410 }); | |
10411 | |
10412 var labelLocationHash = {}; | |
10413 var avgHeight = 14; | |
10414 var avgWidth = 140; | |
10415 var createHashKey = function(coordinates) { | |
10416 return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight; | |
10417 }; | |
10418 | |
10419 pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) { | |
10420 if (labelSunbeamLayout) { | |
10421 d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate | |
10422 d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate | |
10423 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI); | |
10424 if ((d.startAngle + d.endAngle) / 2 < Math.PI) { | |
10425 rotateAngle -= 90; | |
10426 } else { | |
10427 rotateAngle += 90; | |
10428 } | |
10429 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')'; | |
10430 } else { | |
10431 d.outerRadius = radius + 10; // Set Outer Coordinate | |
10432 d.innerRadius = radius + 15; // Set Inner Coordinate | |
10433 | |
10434 /* | |
10435 Overlapping pie labels are not good. What this attempts to do is, prevent overlapping. | |
10436 Each label location is hashed, and if a hash collision occurs, we assume an overlap. | |
10437 Adjust the label's y-position to remove the overlap. | |
10438 */ | |
10439 var center = labelsArc[i].centroid(d); | |
10440 if (d.value) { | |
10441 var hashKey = createHashKey(center); | |
10442 if (labelLocationHash[hashKey]) { | |
10443 center[1] -= avgHeight; | |
10444 } | |
10445 labelLocationHash[createHashKey(center)] = true; | |
10446 } | |
10447 return 'translate(' + center + ')' | |
10448 } | |
10449 }); | |
10450 | |
10451 pieLabels.select(".nv-label text") | |
10452 .style('text-anchor', function(d,i) { | |
10453 //center the text on it's origin or begin/end if orthogonal aligned | |
10454 return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle'; | |
10455 }) | |
10456 .text(function(d, i) { | |
10457 var percent = (d.endAngle - d.startAngle) / (2 * Math.PI); | |
10458 var label = ''; | |
10459 if (!d.value || percent < labelThreshold) return ''; | |
10460 | |
10461 if(typeof labelType === 'function') { | |
10462 label = labelType(d, i, { | |
10463 'key': getX(d.data), | |
10464 'value': getY(d.data), | |
10465 'percent': valueFormat(percent) | |
10466 }); | |
10467 } else { | |
10468 switch (labelType) { | |
10469 case 'key': | |
10470 label = getX(d.data); | |
10471 break; | |
10472 case 'value': | |
10473 label = valueFormat(getY(d.data)); | |
10474 break; | |
10475 case 'percent': | |
10476 label = d3.format('%')(percent); | |
10477 break; | |
10478 } | |
10479 } | |
10480 return label; | |
10481 }) | |
10482 ; | |
10483 } | |
10484 | |
10485 | |
10486 // Computes the angle of an arc, converting from radians to degrees. | |
10487 function angle(d) { | |
10488 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90; | |
10489 return a > 90 ? a - 180 : a; | |
10490 } | |
10491 | |
10492 function arcTween(a, idx) { | |
10493 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle; | |
10494 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle; | |
10495 if (!donut) a.innerRadius = 0; | |
10496 var i = d3.interpolate(this._current, a); | |
10497 this._current = i(0); | |
10498 return function (t) { | |
10499 return arcs[idx](i(t)); | |
10500 }; | |
10501 } | |
10502 }); | |
10503 | |
10504 renderWatch.renderEnd('pie immediate'); | |
10505 return chart; | |
10506 } | |
10507 | |
10508 //============================================================ | |
10509 // Expose Public Variables | |
10510 //------------------------------------------------------------ | |
10511 | |
10512 chart.dispatch = dispatch; | |
10513 chart.options = nv.utils.optionsFunc.bind(chart); | |
10514 | |
10515 chart._options = Object.create({}, { | |
10516 // simple options, just get/set the necessary values | |
10517 arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } }, | |
10518 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
10519 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
10520 showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}}, | |
10521 title: {get: function(){return title;}, set: function(_){title=_;}}, | |
10522 titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}}, | |
10523 labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}}, | |
10524 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}}, | |
10525 x: {get: function(){return getX;}, set: function(_){getX=_;}}, | |
10526 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
10527 endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}}, | |
10528 startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}}, | |
10529 padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}}, | |
10530 cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}}, | |
10531 donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}}, | |
10532 labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}}, | |
10533 labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}}, | |
10534 donut: {get: function(){return donut;}, set: function(_){donut=_;}}, | |
10535 growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}}, | |
10536 | |
10537 // depreciated after 1.7.1 | |
10538 pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){ | |
10539 labelsOutside=_; | |
10540 nv.deprecated('pieLabelsOutside', 'use labelsOutside instead'); | |
10541 }}, | |
10542 // depreciated after 1.7.1 | |
10543 donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){ | |
10544 labelsOutside=_; | |
10545 nv.deprecated('donutLabelsOutside', 'use labelsOutside instead'); | |
10546 }}, | |
10547 // deprecated after 1.7.1 | |
10548 labelFormat: {get: function(){ return valueFormat;}, set: function(_) { | |
10549 valueFormat=_; | |
10550 nv.deprecated('labelFormat','use valueFormat instead'); | |
10551 }}, | |
10552 | |
10553 // options that require extra logic in the setter | |
10554 margin: {get: function(){return margin;}, set: function(_){ | |
10555 margin.top = typeof _.top != 'undefined' ? _.top : margin.top; | |
10556 margin.right = typeof _.right != 'undefined' ? _.right : margin.right; | |
10557 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom; | |
10558 margin.left = typeof _.left != 'undefined' ? _.left : margin.left; | |
10559 }}, | |
10560 y: {get: function(){return getY;}, set: function(_){ | |
10561 getY=d3.functor(_); | |
10562 }}, | |
10563 color: {get: function(){return color;}, set: function(_){ | |
10564 color=nv.utils.getColor(_); | |
10565 }}, | |
10566 labelType: {get: function(){return labelType;}, set: function(_){ | |
10567 labelType= _ || 'key'; | |
10568 }} | |
10569 }); | |
10570 | |
10571 nv.utils.initOptions(chart); | |
10572 return chart; | |
10573 }; | |
10574 nv.models.pieChart = function() { | |
10575 "use strict"; | |
10576 | |
10577 //============================================================ | |
10578 // Public Variables with Default Settings | |
10579 //------------------------------------------------------------ | |
10580 | |
10581 var pie = nv.models.pie(); | |
10582 var legend = nv.models.legend(); | |
10583 var tooltip = nv.models.tooltip(); | |
10584 | |
10585 var margin = {top: 30, right: 20, bottom: 20, left: 20} | |
10586 , width = null | |
10587 , height = null | |
10588 , showLegend = true | |
10589 , legendPosition = "top" | |
10590 , color = nv.utils.defaultColor() | |
10591 , state = nv.utils.state() | |
10592 , defaultState = null | |
10593 , noData = null | |
10594 , duration = 250 | |
10595 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd') | |
10596 ; | |
10597 | |
10598 tooltip | |
10599 .headerEnabled(false) | |
10600 .duration(0) | |
10601 .valueFormatter(function(d, i) { | |
10602 return pie.valueFormat()(d, i); | |
10603 }); | |
10604 | |
10605 //============================================================ | |
10606 // Private Variables | |
10607 //------------------------------------------------------------ | |
10608 | |
10609 var renderWatch = nv.utils.renderWatch(dispatch); | |
10610 | |
10611 var stateGetter = function(data) { | |
10612 return function(){ | |
10613 return { | |
10614 active: data.map(function(d) { return !d.disabled }) | |
10615 }; | |
10616 } | |
10617 }; | |
10618 | |
10619 var stateSetter = function(data) { | |
10620 return function(state) { | |
10621 if (state.active !== undefined) { | |
10622 data.forEach(function (series, i) { | |
10623 series.disabled = !state.active[i]; | |
10624 }); | |
10625 } | |
10626 } | |
10627 }; | |
10628 | |
10629 //============================================================ | |
10630 // Chart function | |
10631 //------------------------------------------------------------ | |
10632 | |
10633 function chart(selection) { | |
10634 renderWatch.reset(); | |
10635 renderWatch.models(pie); | |
10636 | |
10637 selection.each(function(data) { | |
10638 var container = d3.select(this); | |
10639 nv.utils.initSVG(container); | |
10640 | |
10641 var that = this; | |
10642 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
10643 availableHeight = nv.utils.availableHeight(height, container, margin); | |
10644 | |
10645 chart.update = function() { container.transition().call(chart); }; | |
10646 chart.container = this; | |
10647 | |
10648 state.setter(stateSetter(data), chart.update) | |
10649 .getter(stateGetter(data)) | |
10650 .update(); | |
10651 | |
10652 //set state.disabled | |
10653 state.disabled = data.map(function(d) { return !!d.disabled }); | |
10654 | |
10655 if (!defaultState) { | |
10656 var key; | |
10657 defaultState = {}; | |
10658 for (key in state) { | |
10659 if (state[key] instanceof Array) | |
10660 defaultState[key] = state[key].slice(0); | |
10661 else | |
10662 defaultState[key] = state[key]; | |
10663 } | |
10664 } | |
10665 | |
10666 // Display No Data message if there's nothing to show. | |
10667 if (!data || !data.length) { | |
10668 nv.utils.noData(chart, container); | |
10669 return chart; | |
10670 } else { | |
10671 container.selectAll('.nv-noData').remove(); | |
10672 } | |
10673 | |
10674 // Setup containers and skeleton of chart | |
10675 var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]); | |
10676 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g'); | |
10677 var g = wrap.select('g'); | |
10678 | |
10679 gEnter.append('g').attr('class', 'nv-pieWrap'); | |
10680 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
10681 | |
10682 // Legend | |
10683 if (showLegend) { | |
10684 if (legendPosition === "top") { | |
10685 legend.width( availableWidth ).key(pie.x()); | |
10686 | |
10687 wrap.select('.nv-legendWrap') | |
10688 .datum(data) | |
10689 .call(legend); | |
10690 | |
10691 if ( margin.top != legend.height()) { | |
10692 margin.top = legend.height(); | |
10693 availableHeight = nv.utils.availableHeight(height, container, margin); | |
10694 } | |
10695 | |
10696 wrap.select('.nv-legendWrap') | |
10697 .attr('transform', 'translate(0,' + (-margin.top) +')'); | |
10698 } else if (legendPosition === "right") { | |
10699 var legendWidth = nv.models.legend().width(); | |
10700 if (availableWidth / 2 < legendWidth) { | |
10701 legendWidth = (availableWidth / 2) | |
10702 } | |
10703 legend.height(availableHeight).key(pie.x()); | |
10704 legend.width(legendWidth); | |
10705 availableWidth -= legend.width(); | |
10706 | |
10707 wrap.select('.nv-legendWrap') | |
10708 .datum(data) | |
10709 .call(legend) | |
10710 .attr('transform', 'translate(' + (availableWidth) +',0)'); | |
10711 } | |
10712 } | |
10713 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
10714 | |
10715 // Main Chart Component(s) | |
10716 pie.width(availableWidth).height(availableHeight); | |
10717 var pieWrap = g.select('.nv-pieWrap').datum([data]); | |
10718 d3.transition(pieWrap).call(pie); | |
10719 | |
10720 //============================================================ | |
10721 // Event Handling/Dispatching (in chart's scope) | |
10722 //------------------------------------------------------------ | |
10723 | |
10724 legend.dispatch.on('stateChange', function(newState) { | |
10725 for (var key in newState) { | |
10726 state[key] = newState[key]; | |
10727 } | |
10728 dispatch.stateChange(state); | |
10729 chart.update(); | |
10730 }); | |
10731 | |
10732 // Update chart from a state object passed to event handler | |
10733 dispatch.on('changeState', function(e) { | |
10734 if (typeof e.disabled !== 'undefined') { | |
10735 data.forEach(function(series,i) { | |
10736 series.disabled = e.disabled[i]; | |
10737 }); | |
10738 state.disabled = e.disabled; | |
10739 } | |
10740 chart.update(); | |
10741 }); | |
10742 }); | |
10743 | |
10744 renderWatch.renderEnd('pieChart immediate'); | |
10745 return chart; | |
10746 } | |
10747 | |
10748 //============================================================ | |
10749 // Event Handling/Dispatching (out of chart's scope) | |
10750 //------------------------------------------------------------ | |
10751 | |
10752 pie.dispatch.on('elementMouseover.tooltip', function(evt) { | |
10753 evt['series'] = { | |
10754 key: chart.x()(evt.data), | |
10755 value: chart.y()(evt.data), | |
10756 color: evt.color | |
10757 }; | |
10758 tooltip.data(evt).hidden(false); | |
10759 }); | |
10760 | |
10761 pie.dispatch.on('elementMouseout.tooltip', function(evt) { | |
10762 tooltip.hidden(true); | |
10763 }); | |
10764 | |
10765 pie.dispatch.on('elementMousemove.tooltip', function(evt) { | |
10766 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
10767 }); | |
10768 | |
10769 //============================================================ | |
10770 // Expose Public Variables | |
10771 //------------------------------------------------------------ | |
10772 | |
10773 // expose chart's sub-components | |
10774 chart.legend = legend; | |
10775 chart.dispatch = dispatch; | |
10776 chart.pie = pie; | |
10777 chart.tooltip = tooltip; | |
10778 chart.options = nv.utils.optionsFunc.bind(chart); | |
10779 | |
10780 // use Object get/set functionality to map between vars and chart functions | |
10781 chart._options = Object.create({}, { | |
10782 // simple options, just get/set the necessary values | |
10783 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
10784 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
10785 legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}}, | |
10786 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
10787 | |
10788 // deprecated options | |
10789 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
10790 // deprecated after 1.7.1 | |
10791 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
10792 tooltip.enabled(!!_); | |
10793 }}, | |
10794 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
10795 // deprecated after 1.7.1 | |
10796 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
10797 tooltip.contentGenerator(_); | |
10798 }}, | |
10799 | |
10800 // options that require extra logic in the setter | |
10801 color: {get: function(){return color;}, set: function(_){ | |
10802 color = _; | |
10803 legend.color(color); | |
10804 pie.color(color); | |
10805 }}, | |
10806 duration: {get: function(){return duration;}, set: function(_){ | |
10807 duration = _; | |
10808 renderWatch.reset(duration); | |
10809 }}, | |
10810 margin: {get: function(){return margin;}, set: function(_){ | |
10811 margin.top = _.top !== undefined ? _.top : margin.top; | |
10812 margin.right = _.right !== undefined ? _.right : margin.right; | |
10813 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
10814 margin.left = _.left !== undefined ? _.left : margin.left; | |
10815 }} | |
10816 }); | |
10817 nv.utils.inheritOptions(chart, pie); | |
10818 nv.utils.initOptions(chart); | |
10819 return chart; | |
10820 }; | |
10821 | |
10822 nv.models.scatter = function() { | |
10823 "use strict"; | |
10824 | |
10825 //============================================================ | |
10826 // Public Variables with Default Settings | |
10827 //------------------------------------------------------------ | |
10828 | |
10829 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
10830 , width = null | |
10831 , height = null | |
10832 , color = nv.utils.defaultColor() // chooses color | |
10833 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one | |
10834 , container = null | |
10835 , x = d3.scale.linear() | |
10836 , y = d3.scale.linear() | |
10837 , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area | |
10838 , getX = function(d) { return d.x } // accessor to get the x value | |
10839 , getY = function(d) { return d.y } // accessor to get the y value | |
10840 , getSize = function(d) { return d.size || 1} // accessor to get the point size | |
10841 , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape | |
10842 , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.) | |
10843 , forceY = [] // List of numbers to Force into the Y scale | |
10844 , forceSize = [] // List of numbers to Force into the Size scale | |
10845 , interactive = true // If true, plots a voronoi overlay for advanced point intersection | |
10846 , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out | |
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 | |
10848 , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding | |
10849 , clipEdge = false // if true, masks points within x and y scale | |
10850 , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance | |
10851 , showVoronoi = false // display the voronoi areas | |
10852 , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips | |
10853 , xDomain = null // Override x domain (skips the calculation from data) | |
10854 , yDomain = null // Override y domain | |
10855 , xRange = null // Override x range | |
10856 , yRange = null // Override y range | |
10857 , sizeDomain = null // Override point size domain | |
10858 , sizeRange = null | |
10859 , singlePoint = false | |
10860 , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd') | |
10861 , useVoronoi = true | |
10862 , duration = 250 | |
10863 ; | |
10864 | |
10865 | |
10866 //============================================================ | |
10867 // Private Variables | |
10868 //------------------------------------------------------------ | |
10869 | |
10870 var x0, y0, z0 // used to store previous scales | |
10871 , timeoutID | |
10872 , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips | |
10873 , renderWatch = nv.utils.renderWatch(dispatch, duration) | |
10874 , _sizeRange_def = [16, 256] | |
10875 ; | |
10876 | |
10877 function chart(selection) { | |
10878 renderWatch.reset(); | |
10879 selection.each(function(data) { | |
10880 container = d3.select(this); | |
10881 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
10882 availableHeight = nv.utils.availableHeight(height, container, margin); | |
10883 | |
10884 nv.utils.initSVG(container); | |
10885 | |
10886 //add series index to each data point for reference | |
10887 data.forEach(function(series, i) { | |
10888 series.values.forEach(function(point) { | |
10889 point.series = i; | |
10890 }); | |
10891 }); | |
10892 | |
10893 // Setup Scales | |
10894 // remap and flatten the data for use in calculating the scales' domains | |
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 | |
10896 d3.merge( | |
10897 data.map(function(d) { | |
10898 return d.values.map(function(d,i) { | |
10899 return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) } | |
10900 }) | |
10901 }) | |
10902 ); | |
10903 | |
10904 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX))) | |
10905 | |
10906 if (padData && data[0]) | |
10907 x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]); | |
10908 //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]); | |
10909 else | |
10910 x.range(xRange || [0, availableWidth]); | |
10911 | |
10912 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY))) | |
10913 .range(yRange || [availableHeight, 0]); | |
10914 | |
10915 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize))) | |
10916 .range(sizeRange || _sizeRange_def); | |
10917 | |
10918 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point | |
10919 singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]; | |
10920 | |
10921 if (x.domain()[0] === x.domain()[1]) | |
10922 x.domain()[0] ? | |
10923 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01]) | |
10924 : x.domain([-1,1]); | |
10925 | |
10926 if (y.domain()[0] === y.domain()[1]) | |
10927 y.domain()[0] ? | |
10928 y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01]) | |
10929 : y.domain([-1,1]); | |
10930 | |
10931 if ( isNaN(x.domain()[0])) { | |
10932 x.domain([-1,1]); | |
10933 } | |
10934 | |
10935 if ( isNaN(y.domain()[0])) { | |
10936 y.domain([-1,1]); | |
10937 } | |
10938 | |
10939 x0 = x0 || x; | |
10940 y0 = y0 || y; | |
10941 z0 = z0 || z; | |
10942 | |
10943 // Setup containers and skeleton of chart | |
10944 var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]); | |
10945 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id); | |
10946 var defsEnter = wrapEnter.append('defs'); | |
10947 var gEnter = wrapEnter.append('g'); | |
10948 var g = wrap.select('g'); | |
10949 | |
10950 wrap.classed('nv-single-point', singlePoint); | |
10951 gEnter.append('g').attr('class', 'nv-groups'); | |
10952 gEnter.append('g').attr('class', 'nv-point-paths'); | |
10953 wrapEnter.append('g').attr('class', 'nv-point-clips'); | |
10954 | |
10955 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
10956 | |
10957 defsEnter.append('clipPath') | |
10958 .attr('id', 'nv-edge-clip-' + id) | |
10959 .append('rect'); | |
10960 | |
10961 wrap.select('#nv-edge-clip-' + id + ' rect') | |
10962 .attr('width', availableWidth) | |
10963 .attr('height', (availableHeight > 0) ? availableHeight : 0); | |
10964 | |
10965 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); | |
10966 | |
10967 function updateInteractiveLayer() { | |
10968 // Always clear needs-update flag regardless of whether or not | |
10969 // we will actually do anything (avoids needless invocations). | |
10970 needsUpdate = false; | |
10971 | |
10972 if (!interactive) return false; | |
10973 | |
10974 // inject series and point index for reference into voronoi | |
10975 if (useVoronoi === true) { | |
10976 var vertices = d3.merge(data.map(function(group, groupIndex) { | |
10977 return group.values | |
10978 .map(function(point, pointIndex) { | |
10979 // *Adding noise to make duplicates very unlikely | |
10980 // *Injecting series and point index for reference | |
10981 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi. | |
10982 */ | |
10983 var pX = getX(point,pointIndex); | |
10984 var pY = getY(point,pointIndex); | |
10985 | |
10986 return [x(pX)+ Math.random() * 1e-4, | |
10987 y(pY)+ Math.random() * 1e-4, | |
10988 groupIndex, | |
10989 pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates | |
10990 }) | |
10991 .filter(function(pointArray, pointIndex) { | |
10992 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct! | |
10993 }) | |
10994 }) | |
10995 ); | |
10996 | |
10997 if (vertices.length == 0) return false; // No active points, we're done | |
10998 if (vertices.length < 3) { | |
10999 // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work | |
11000 vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]); | |
11001 vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]); | |
11002 vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]); | |
11003 vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]); | |
11004 } | |
11005 | |
11006 // keep voronoi sections from going more than 10 outside of graph | |
11007 // to avoid overlap with other things like legend etc | |
11008 var bounds = d3.geom.polygon([ | |
11009 [-10,-10], | |
11010 [-10,height + 10], | |
11011 [width + 10,height + 10], | |
11012 [width + 10,-10] | |
11013 ]); | |
11014 | |
11015 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) { | |
11016 return { | |
11017 'data': bounds.clip(d), | |
11018 'series': vertices[i][2], | |
11019 'point': vertices[i][3] | |
11020 } | |
11021 }); | |
11022 | |
11023 // nuke all voronoi paths on reload and recreate them | |
11024 wrap.select('.nv-point-paths').selectAll('path').remove(); | |
11025 var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi); | |
11026 var vPointPaths = pointPaths | |
11027 .enter().append("svg:path") | |
11028 .attr("d", function(d) { | |
11029 if (!d || !d.data || d.data.length === 0) | |
11030 return 'M 0 0'; | |
11031 else | |
11032 return "M" + d.data.join(",") + "Z"; | |
11033 }) | |
11034 .attr("id", function(d,i) { | |
11035 return "nv-path-"+i; }) | |
11036 .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; }) | |
11037 ; | |
11038 | |
11039 // good for debugging point hover issues | |
11040 if (showVoronoi) { | |
11041 vPointPaths.style("fill", d3.rgb(230, 230, 230)) | |
11042 .style('fill-opacity', 0.4) | |
11043 .style('stroke-opacity', 1) | |
11044 .style("stroke", d3.rgb(200,200,200)); | |
11045 } | |
11046 | |
11047 if (clipVoronoi) { | |
11048 // voronoi sections are already set to clip, | |
11049 // just create the circles with the IDs they expect | |
11050 wrap.select('.nv-point-clips').selectAll('clipPath').remove(); | |
11051 wrap.select('.nv-point-clips').selectAll("clipPath") | |
11052 .data(vertices) | |
11053 .enter().append("svg:clipPath") | |
11054 .attr("id", function(d, i) { return "nv-clip-"+i;}) | |
11055 .append("svg:circle") | |
11056 .attr('cx', function(d) { return d[0]; }) | |
11057 .attr('cy', function(d) { return d[1]; }) | |
11058 .attr('r', clipRadius); | |
11059 } | |
11060 | |
11061 var mouseEventCallback = function(d, mDispatch) { | |
11062 if (needsUpdate) return 0; | |
11063 var series = data[d.series]; | |
11064 if (series === undefined) return; | |
11065 var point = series.values[d.point]; | |
11066 point['color'] = color(series, d.series); | |
11067 | |
11068 // standardize attributes for tooltip. | |
11069 point['x'] = getX(point); | |
11070 point['y'] = getY(point); | |
11071 | |
11072 // can't just get box of event node since it's actually a voronoi polygon | |
11073 var box = container.node().getBoundingClientRect(); | |
11074 var scrollTop = window.pageYOffset || document.documentElement.scrollTop; | |
11075 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; | |
11076 | |
11077 var pos = { | |
11078 left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10, | |
11079 top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10 | |
11080 }; | |
11081 | |
11082 mDispatch({ | |
11083 point: point, | |
11084 series: series, | |
11085 pos: pos, | |
11086 seriesIndex: d.series, | |
11087 pointIndex: d.point | |
11088 }); | |
11089 }; | |
11090 | |
11091 pointPaths | |
11092 .on('click', function(d) { | |
11093 mouseEventCallback(d, dispatch.elementClick); | |
11094 }) | |
11095 .on('dblclick', function(d) { | |
11096 mouseEventCallback(d, dispatch.elementDblClick); | |
11097 }) | |
11098 .on('mouseover', function(d) { | |
11099 mouseEventCallback(d, dispatch.elementMouseover); | |
11100 }) | |
11101 .on('mouseout', function(d, i) { | |
11102 mouseEventCallback(d, dispatch.elementMouseout); | |
11103 }); | |
11104 | |
11105 } else { | |
11106 // add event handlers to points instead voronoi paths | |
11107 wrap.select('.nv-groups').selectAll('.nv-group') | |
11108 .selectAll('.nv-point') | |
11109 //.data(dataWithPoints) | |
11110 //.style('pointer-events', 'auto') // recativate events, disabled by css | |
11111 .on('click', function(d,i) { | |
11112 //nv.log('test', d, i); | |
11113 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point | |
11114 var series = data[d.series], | |
11115 point = series.values[i]; | |
11116 | |
11117 dispatch.elementClick({ | |
11118 point: point, | |
11119 series: series, | |
11120 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], | |
11121 seriesIndex: d.series, | |
11122 pointIndex: i | |
11123 }); | |
11124 }) | |
11125 .on('dblclick', function(d,i) { | |
11126 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point | |
11127 var series = data[d.series], | |
11128 point = series.values[i]; | |
11129 | |
11130 dispatch.elementDblClick({ | |
11131 point: point, | |
11132 series: series, | |
11133 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], | |
11134 seriesIndex: d.series, | |
11135 pointIndex: i | |
11136 }); | |
11137 }) | |
11138 .on('mouseover', function(d,i) { | |
11139 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point | |
11140 var series = data[d.series], | |
11141 point = series.values[i]; | |
11142 | |
11143 dispatch.elementMouseover({ | |
11144 point: point, | |
11145 series: series, | |
11146 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top], | |
11147 seriesIndex: d.series, | |
11148 pointIndex: i, | |
11149 color: color(d, i) | |
11150 }); | |
11151 }) | |
11152 .on('mouseout', function(d,i) { | |
11153 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point | |
11154 var series = data[d.series], | |
11155 point = series.values[i]; | |
11156 | |
11157 dispatch.elementMouseout({ | |
11158 point: point, | |
11159 series: series, | |
11160 seriesIndex: d.series, | |
11161 pointIndex: i, | |
11162 color: color(d, i) | |
11163 }); | |
11164 }); | |
11165 } | |
11166 } | |
11167 | |
11168 needsUpdate = true; | |
11169 var groups = wrap.select('.nv-groups').selectAll('.nv-group') | |
11170 .data(function(d) { return d }, function(d) { return d.key }); | |
11171 groups.enter().append('g') | |
11172 .style('stroke-opacity', 1e-6) | |
11173 .style('fill-opacity', 1e-6); | |
11174 groups.exit() | |
11175 .remove(); | |
11176 groups | |
11177 .attr('class', function(d,i) { return 'nv-group nv-series-' + i }) | |
11178 .classed('hover', function(d) { return d.hover }); | |
11179 groups.watchTransition(renderWatch, 'scatter: groups') | |
11180 .style('fill', function(d,i) { return color(d, i) }) | |
11181 .style('stroke', function(d,i) { return color(d, i) }) | |
11182 .style('stroke-opacity', 1) | |
11183 .style('fill-opacity', .5); | |
11184 | |
11185 // create the points, maintaining their IDs from the original data set | |
11186 var points = groups.selectAll('path.nv-point') | |
11187 .data(function(d) { | |
11188 return d.values.map( | |
11189 function (point, pointIndex) { | |
11190 return [point, pointIndex] | |
11191 }).filter( | |
11192 function(pointArray, pointIndex) { | |
11193 return pointActive(pointArray[0], pointIndex) | |
11194 }) | |
11195 }); | |
11196 points.enter().append('path') | |
11197 .style('fill', function (d) { return d.color }) | |
11198 .style('stroke', function (d) { return d.color }) | |
11199 .attr('transform', function(d) { | |
11200 return 'translate(' + x0(getX(d[0],d[1])) + ',' + y0(getY(d[0],d[1])) + ')' | |
11201 }) | |
11202 .attr('d', | |
11203 nv.utils.symbol() | |
11204 .type(function(d) { return getShape(d[0]); }) | |
11205 .size(function(d) { return z(getSize(d[0],d[1])) }) | |
11206 ); | |
11207 points.exit().remove(); | |
11208 groups.exit().selectAll('path.nv-point') | |
11209 .watchTransition(renderWatch, 'scatter exit') | |
11210 .attr('transform', function(d) { | |
11211 return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')' | |
11212 }) | |
11213 .remove(); | |
11214 points.each(function(d) { | |
11215 d3.select(this) | |
11216 .classed('nv-point', true) | |
11217 .classed('nv-point-' + d[1], true) | |
11218 .classed('nv-noninteractive', !interactive) | |
11219 .classed('hover',false) | |
11220 ; | |
11221 }); | |
11222 points | |
11223 .watchTransition(renderWatch, 'scatter points') | |
11224 .attr('transform', function(d) { | |
11225 //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1]))); | |
11226 return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')' | |
11227 }) | |
11228 .attr('d', | |
11229 nv.utils.symbol() | |
11230 .type(function(d) { return getShape(d[0]); }) | |
11231 .size(function(d) { return z(getSize(d[0],d[1])) }) | |
11232 ); | |
11233 | |
11234 // Delay updating the invisible interactive layer for smoother animation | |
11235 clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer | |
11236 timeoutID = setTimeout(updateInteractiveLayer, 300); | |
11237 //updateInteractiveLayer(); | |
11238 | |
11239 //store old scales for use in transitions on update | |
11240 x0 = x.copy(); | |
11241 y0 = y.copy(); | |
11242 z0 = z.copy(); | |
11243 | |
11244 }); | |
11245 renderWatch.renderEnd('scatter immediate'); | |
11246 return chart; | |
11247 } | |
11248 | |
11249 //============================================================ | |
11250 // Expose Public Variables | |
11251 //------------------------------------------------------------ | |
11252 | |
11253 chart.dispatch = dispatch; | |
11254 chart.options = nv.utils.optionsFunc.bind(chart); | |
11255 | |
11256 // utility function calls provided by this chart | |
11257 chart._calls = new function() { | |
11258 this.clearHighlights = function () { | |
11259 nv.dom.write(function() { | |
11260 container.selectAll(".nv-point.hover").classed("hover", false); | |
11261 }); | |
11262 return null; | |
11263 }; | |
11264 this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) { | |
11265 nv.dom.write(function() { | |
11266 container.select(" .nv-series-" + seriesIndex + " .nv-point-" + pointIndex) | |
11267 .classed("hover", isHoverOver); | |
11268 }); | |
11269 }; | |
11270 }; | |
11271 | |
11272 // trigger calls from events too | |
11273 dispatch.on('elementMouseover.point', function(d) { | |
11274 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true); | |
11275 }); | |
11276 | |
11277 dispatch.on('elementMouseout.point', function(d) { | |
11278 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false); | |
11279 }); | |
11280 | |
11281 chart._options = Object.create({}, { | |
11282 // simple options, just get/set the necessary values | |
11283 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
11284 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
11285 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
11286 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
11287 pointScale: {get: function(){return z;}, set: function(_){z=_;}}, | |
11288 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
11289 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
11290 pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}}, | |
11291 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
11292 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
11293 pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}}, | |
11294 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}}, | |
11295 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}}, | |
11296 forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}}, | |
11297 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}}, | |
11298 pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}}, | |
11299 padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}}, | |
11300 padData: {get: function(){return padData;}, set: function(_){padData=_;}}, | |
11301 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, | |
11302 clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}}, | |
11303 clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}}, | |
11304 showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}}, | |
11305 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
11306 | |
11307 | |
11308 // simple functor options | |
11309 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, | |
11310 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}}, | |
11311 pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}}, | |
11312 pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}}, | |
11313 | |
11314 // options that require extra logic in the setter | |
11315 margin: {get: function(){return margin;}, set: function(_){ | |
11316 margin.top = _.top !== undefined ? _.top : margin.top; | |
11317 margin.right = _.right !== undefined ? _.right : margin.right; | |
11318 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
11319 margin.left = _.left !== undefined ? _.left : margin.left; | |
11320 }}, | |
11321 duration: {get: function(){return duration;}, set: function(_){ | |
11322 duration = _; | |
11323 renderWatch.reset(duration); | |
11324 }}, | |
11325 color: {get: function(){return color;}, set: function(_){ | |
11326 color = nv.utils.getColor(_); | |
11327 }}, | |
11328 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){ | |
11329 useVoronoi = _; | |
11330 if (useVoronoi === false) { | |
11331 clipVoronoi = false; | |
11332 } | |
11333 }} | |
11334 }); | |
11335 | |
11336 nv.utils.initOptions(chart); | |
11337 return chart; | |
11338 }; | |
11339 | |
11340 nv.models.scatterChart = function() { | |
11341 "use strict"; | |
11342 | |
11343 //============================================================ | |
11344 // Public Variables with Default Settings | |
11345 //------------------------------------------------------------ | |
11346 | |
11347 var scatter = nv.models.scatter() | |
11348 , xAxis = nv.models.axis() | |
11349 , yAxis = nv.models.axis() | |
11350 , legend = nv.models.legend() | |
11351 , distX = nv.models.distribution() | |
11352 , distY = nv.models.distribution() | |
11353 , tooltip = nv.models.tooltip() | |
11354 ; | |
11355 | |
11356 var margin = {top: 30, right: 20, bottom: 50, left: 75} | |
11357 , width = null | |
11358 , height = null | |
11359 , container = null | |
11360 , color = nv.utils.defaultColor() | |
11361 , x = scatter.xScale() | |
11362 , y = scatter.yScale() | |
11363 , showDistX = false | |
11364 , showDistY = false | |
11365 , showLegend = true | |
11366 , showXAxis = true | |
11367 , showYAxis = true | |
11368 , rightAlignYAxis = false | |
11369 , state = nv.utils.state() | |
11370 , defaultState = null | |
11371 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd') | |
11372 , noData = null | |
11373 , duration = 250 | |
11374 ; | |
11375 | |
11376 scatter.xScale(x).yScale(y); | |
11377 xAxis.orient('bottom').tickPadding(10); | |
11378 yAxis | |
11379 .orient((rightAlignYAxis) ? 'right' : 'left') | |
11380 .tickPadding(10) | |
11381 ; | |
11382 distX.axis('x'); | |
11383 distY.axis('y'); | |
11384 tooltip | |
11385 .headerFormatter(function(d, i) { | |
11386 return xAxis.tickFormat()(d, i); | |
11387 }) | |
11388 .valueFormatter(function(d, i) { | |
11389 return yAxis.tickFormat()(d, i); | |
11390 }); | |
11391 | |
11392 //============================================================ | |
11393 // Private Variables | |
11394 //------------------------------------------------------------ | |
11395 | |
11396 var x0, y0 | |
11397 , renderWatch = nv.utils.renderWatch(dispatch, duration); | |
11398 | |
11399 var stateGetter = function(data) { | |
11400 return function(){ | |
11401 return { | |
11402 active: data.map(function(d) { return !d.disabled }) | |
11403 }; | |
11404 } | |
11405 }; | |
11406 | |
11407 var stateSetter = function(data) { | |
11408 return function(state) { | |
11409 if (state.active !== undefined) | |
11410 data.forEach(function(series,i) { | |
11411 series.disabled = !state.active[i]; | |
11412 }); | |
11413 } | |
11414 }; | |
11415 | |
11416 function chart(selection) { | |
11417 renderWatch.reset(); | |
11418 renderWatch.models(scatter); | |
11419 if (showXAxis) renderWatch.models(xAxis); | |
11420 if (showYAxis) renderWatch.models(yAxis); | |
11421 if (showDistX) renderWatch.models(distX); | |
11422 if (showDistY) renderWatch.models(distY); | |
11423 | |
11424 selection.each(function(data) { | |
11425 var that = this; | |
11426 | |
11427 container = d3.select(this); | |
11428 nv.utils.initSVG(container); | |
11429 | |
11430 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
11431 availableHeight = nv.utils.availableHeight(height, container, margin); | |
11432 | |
11433 chart.update = function() { | |
11434 if (duration === 0) | |
11435 container.call(chart); | |
11436 else | |
11437 container.transition().duration(duration).call(chart); | |
11438 }; | |
11439 chart.container = this; | |
11440 | |
11441 state | |
11442 .setter(stateSetter(data), chart.update) | |
11443 .getter(stateGetter(data)) | |
11444 .update(); | |
11445 | |
11446 // DEPRECATED set state.disableddisabled | |
11447 state.disabled = data.map(function(d) { return !!d.disabled }); | |
11448 | |
11449 if (!defaultState) { | |
11450 var key; | |
11451 defaultState = {}; | |
11452 for (key in state) { | |
11453 if (state[key] instanceof Array) | |
11454 defaultState[key] = state[key].slice(0); | |
11455 else | |
11456 defaultState[key] = state[key]; | |
11457 } | |
11458 } | |
11459 | |
11460 // Display noData message if there's nothing to show. | |
11461 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
11462 nv.utils.noData(chart, container); | |
11463 renderWatch.renderEnd('scatter immediate'); | |
11464 return chart; | |
11465 } else { | |
11466 container.selectAll('.nv-noData').remove(); | |
11467 } | |
11468 | |
11469 // Setup Scales | |
11470 x = scatter.xScale(); | |
11471 y = scatter.yScale(); | |
11472 | |
11473 // Setup containers and skeleton of chart | |
11474 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]); | |
11475 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id()); | |
11476 var gEnter = wrapEnter.append('g'); | |
11477 var g = wrap.select('g'); | |
11478 | |
11479 // background for pointer events | |
11480 gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none"); | |
11481 | |
11482 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
11483 gEnter.append('g').attr('class', 'nv-y nv-axis'); | |
11484 gEnter.append('g').attr('class', 'nv-scatterWrap'); | |
11485 gEnter.append('g').attr('class', 'nv-regressionLinesWrap'); | |
11486 gEnter.append('g').attr('class', 'nv-distWrap'); | |
11487 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
11488 | |
11489 if (rightAlignYAxis) { | |
11490 g.select(".nv-y.nv-axis") | |
11491 .attr("transform", "translate(" + availableWidth + ",0)"); | |
11492 } | |
11493 | |
11494 // Legend | |
11495 if (showLegend) { | |
11496 var legendWidth = availableWidth; | |
11497 legend.width(legendWidth); | |
11498 | |
11499 wrap.select('.nv-legendWrap') | |
11500 .datum(data) | |
11501 .call(legend); | |
11502 | |
11503 if ( margin.top != legend.height()) { | |
11504 margin.top = legend.height(); | |
11505 availableHeight = nv.utils.availableHeight(height, container, margin); | |
11506 } | |
11507 | |
11508 wrap.select('.nv-legendWrap') | |
11509 .attr('transform', 'translate(0' + ',' + (-margin.top) +')'); | |
11510 } | |
11511 | |
11512 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
11513 | |
11514 // Main Chart Component(s) | |
11515 scatter | |
11516 .width(availableWidth) | |
11517 .height(availableHeight) | |
11518 .color(data.map(function(d,i) { | |
11519 d.color = d.color || color(d, i); | |
11520 return d.color; | |
11521 }).filter(function(d,i) { return !data[i].disabled })); | |
11522 | |
11523 wrap.select('.nv-scatterWrap') | |
11524 .datum(data.filter(function(d) { return !d.disabled })) | |
11525 .call(scatter); | |
11526 | |
11527 | |
11528 wrap.select('.nv-regressionLinesWrap') | |
11529 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')'); | |
11530 | |
11531 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines') | |
11532 .data(function (d) { | |
11533 return d; | |
11534 }); | |
11535 | |
11536 regWrap.enter().append('g').attr('class', 'nv-regLines'); | |
11537 | |
11538 var regLine = regWrap.selectAll('.nv-regLine') | |
11539 .data(function (d) { | |
11540 return [d] | |
11541 }); | |
11542 | |
11543 regLine.enter() | |
11544 .append('line').attr('class', 'nv-regLine') | |
11545 .style('stroke-opacity', 0); | |
11546 | |
11547 // don't add lines unless we have slope and intercept to use | |
11548 regLine.filter(function(d) { | |
11549 return d.intercept && d.slope; | |
11550 }) | |
11551 .watchTransition(renderWatch, 'scatterPlusLineChart: regline') | |
11552 .attr('x1', x.range()[0]) | |
11553 .attr('x2', x.range()[1]) | |
11554 .attr('y1', function (d, i) { | |
11555 return y(x.domain()[0] * d.slope + d.intercept) | |
11556 }) | |
11557 .attr('y2', function (d, i) { | |
11558 return y(x.domain()[1] * d.slope + d.intercept) | |
11559 }) | |
11560 .style('stroke', function (d, i, j) { | |
11561 return color(d, j) | |
11562 }) | |
11563 .style('stroke-opacity', function (d, i) { | |
11564 return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1 | |
11565 }); | |
11566 | |
11567 // Setup Axes | |
11568 if (showXAxis) { | |
11569 xAxis | |
11570 .scale(x) | |
11571 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
11572 .tickSize( -availableHeight , 0); | |
11573 | |
11574 g.select('.nv-x.nv-axis') | |
11575 .attr('transform', 'translate(0,' + y.range()[0] + ')') | |
11576 .call(xAxis); | |
11577 } | |
11578 | |
11579 if (showYAxis) { | |
11580 yAxis | |
11581 .scale(y) | |
11582 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) ) | |
11583 .tickSize( -availableWidth, 0); | |
11584 | |
11585 g.select('.nv-y.nv-axis') | |
11586 .call(yAxis); | |
11587 } | |
11588 | |
11589 | |
11590 if (showDistX) { | |
11591 distX | |
11592 .getData(scatter.x()) | |
11593 .scale(x) | |
11594 .width(availableWidth) | |
11595 .color(data.map(function(d,i) { | |
11596 return d.color || color(d, i); | |
11597 }).filter(function(d,i) { return !data[i].disabled })); | |
11598 gEnter.select('.nv-distWrap').append('g') | |
11599 .attr('class', 'nv-distributionX'); | |
11600 g.select('.nv-distributionX') | |
11601 .attr('transform', 'translate(0,' + y.range()[0] + ')') | |
11602 .datum(data.filter(function(d) { return !d.disabled })) | |
11603 .call(distX); | |
11604 } | |
11605 | |
11606 if (showDistY) { | |
11607 distY | |
11608 .getData(scatter.y()) | |
11609 .scale(y) | |
11610 .width(availableHeight) | |
11611 .color(data.map(function(d,i) { | |
11612 return d.color || color(d, i); | |
11613 }).filter(function(d,i) { return !data[i].disabled })); | |
11614 gEnter.select('.nv-distWrap').append('g') | |
11615 .attr('class', 'nv-distributionY'); | |
11616 g.select('.nv-distributionY') | |
11617 .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)') | |
11618 .datum(data.filter(function(d) { return !d.disabled })) | |
11619 .call(distY); | |
11620 } | |
11621 | |
11622 //============================================================ | |
11623 // Event Handling/Dispatching (in chart's scope) | |
11624 //------------------------------------------------------------ | |
11625 | |
11626 legend.dispatch.on('stateChange', function(newState) { | |
11627 for (var key in newState) | |
11628 state[key] = newState[key]; | |
11629 dispatch.stateChange(state); | |
11630 chart.update(); | |
11631 }); | |
11632 | |
11633 // Update chart from a state object passed to event handler | |
11634 dispatch.on('changeState', function(e) { | |
11635 if (typeof e.disabled !== 'undefined') { | |
11636 data.forEach(function(series,i) { | |
11637 series.disabled = e.disabled[i]; | |
11638 }); | |
11639 state.disabled = e.disabled; | |
11640 } | |
11641 chart.update(); | |
11642 }); | |
11643 | |
11644 // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block | |
11645 scatter.dispatch.on('elementMouseout.tooltip', function(evt) { | |
11646 tooltip.hidden(true); | |
11647 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) | |
11648 .attr('y1', 0); | |
11649 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) | |
11650 .attr('x2', distY.size()); | |
11651 }); | |
11652 | |
11653 scatter.dispatch.on('elementMouseover.tooltip', function(evt) { | |
11654 container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex) | |
11655 .attr('y1', evt.pos.top - availableHeight - margin.top); | |
11656 container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex) | |
11657 .attr('x2', evt.pos.left + distX.size() - margin.left); | |
11658 tooltip.position(evt.pos).data(evt).hidden(false); | |
11659 }); | |
11660 | |
11661 //store old scales for use in transitions on update | |
11662 x0 = x.copy(); | |
11663 y0 = y.copy(); | |
11664 | |
11665 }); | |
11666 | |
11667 renderWatch.renderEnd('scatter with line immediate'); | |
11668 return chart; | |
11669 } | |
11670 | |
11671 //============================================================ | |
11672 // Expose Public Variables | |
11673 //------------------------------------------------------------ | |
11674 | |
11675 // expose chart's sub-components | |
11676 chart.dispatch = dispatch; | |
11677 chart.scatter = scatter; | |
11678 chart.legend = legend; | |
11679 chart.xAxis = xAxis; | |
11680 chart.yAxis = yAxis; | |
11681 chart.distX = distX; | |
11682 chart.distY = distY; | |
11683 chart.tooltip = tooltip; | |
11684 | |
11685 chart.options = nv.utils.optionsFunc.bind(chart); | |
11686 chart._options = Object.create({}, { | |
11687 // simple options, just get/set the necessary values | |
11688 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
11689 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
11690 container: {get: function(){return container;}, set: function(_){container=_;}}, | |
11691 showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}}, | |
11692 showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}}, | |
11693 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
11694 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
11695 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
11696 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
11697 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
11698 duration: {get: function(){return duration;}, set: function(_){duration=_;}}, | |
11699 | |
11700 // deprecated options | |
11701 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
11702 // deprecated after 1.7.1 | |
11703 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
11704 tooltip.enabled(!!_); | |
11705 }}, | |
11706 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
11707 // deprecated after 1.7.1 | |
11708 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
11709 tooltip.contentGenerator(_); | |
11710 }}, | |
11711 tooltipXContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
11712 // deprecated after 1.7.1 | |
11713 nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.'); | |
11714 }}, | |
11715 tooltipYContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
11716 // deprecated after 1.7.1 | |
11717 nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.'); | |
11718 }}, | |
11719 | |
11720 // options that require extra logic in the setter | |
11721 margin: {get: function(){return margin;}, set: function(_){ | |
11722 margin.top = _.top !== undefined ? _.top : margin.top; | |
11723 margin.right = _.right !== undefined ? _.right : margin.right; | |
11724 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
11725 margin.left = _.left !== undefined ? _.left : margin.left; | |
11726 }}, | |
11727 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ | |
11728 rightAlignYAxis = _; | |
11729 yAxis.orient( (_) ? 'right' : 'left'); | |
11730 }}, | |
11731 color: {get: function(){return color;}, set: function(_){ | |
11732 color = nv.utils.getColor(_); | |
11733 legend.color(color); | |
11734 distX.color(color); | |
11735 distY.color(color); | |
11736 }} | |
11737 }); | |
11738 | |
11739 nv.utils.inheritOptions(chart, scatter); | |
11740 nv.utils.initOptions(chart); | |
11741 return chart; | |
11742 }; | |
11743 | |
11744 nv.models.sparkline = function() { | |
11745 "use strict"; | |
11746 | |
11747 //============================================================ | |
11748 // Public Variables with Default Settings | |
11749 //------------------------------------------------------------ | |
11750 | |
11751 var margin = {top: 2, right: 0, bottom: 2, left: 0} | |
11752 , width = 400 | |
11753 , height = 32 | |
11754 , container = null | |
11755 , animate = true | |
11756 , x = d3.scale.linear() | |
11757 , y = d3.scale.linear() | |
11758 , getX = function(d) { return d.x } | |
11759 , getY = function(d) { return d.y } | |
11760 , color = nv.utils.getColor(['#000']) | |
11761 , xDomain | |
11762 , yDomain | |
11763 , xRange | |
11764 , yRange | |
11765 ; | |
11766 | |
11767 function chart(selection) { | |
11768 selection.each(function(data) { | |
11769 var availableWidth = width - margin.left - margin.right, | |
11770 availableHeight = height - margin.top - margin.bottom; | |
11771 | |
11772 container = d3.select(this); | |
11773 nv.utils.initSVG(container); | |
11774 | |
11775 // Setup Scales | |
11776 x .domain(xDomain || d3.extent(data, getX )) | |
11777 .range(xRange || [0, availableWidth]); | |
11778 | |
11779 y .domain(yDomain || d3.extent(data, getY )) | |
11780 .range(yRange || [availableHeight, 0]); | |
11781 | |
11782 // Setup containers and skeleton of chart | |
11783 var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]); | |
11784 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline'); | |
11785 var gEnter = wrapEnter.append('g'); | |
11786 var g = wrap.select('g'); | |
11787 | |
11788 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') | |
11789 | |
11790 var paths = wrap.selectAll('path') | |
11791 .data(function(d) { return [d] }); | |
11792 paths.enter().append('path'); | |
11793 paths.exit().remove(); | |
11794 paths | |
11795 .style('stroke', function(d,i) { return d.color || color(d, i) }) | |
11796 .attr('d', d3.svg.line() | |
11797 .x(function(d,i) { return x(getX(d,i)) }) | |
11798 .y(function(d,i) { return y(getY(d,i)) }) | |
11799 ); | |
11800 | |
11801 // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent) | |
11802 var points = wrap.selectAll('circle.nv-point') | |
11803 .data(function(data) { | |
11804 var yValues = data.map(function(d, i) { return getY(d,i); }); | |
11805 function pointIndex(index) { | |
11806 if (index != -1) { | |
11807 var result = data[index]; | |
11808 result.pointIndex = index; | |
11809 return result; | |
11810 } else { | |
11811 return null; | |
11812 } | |
11813 } | |
11814 var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])), | |
11815 minPoint = pointIndex(yValues.indexOf(y.domain()[0])), | |
11816 currentPoint = pointIndex(yValues.length - 1); | |
11817 return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;}); | |
11818 }); | |
11819 points.enter().append('circle'); | |
11820 points.exit().remove(); | |
11821 points | |
11822 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) }) | |
11823 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) }) | |
11824 .attr('r', 2) | |
11825 .attr('class', function(d,i) { | |
11826 return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' : | |
11827 getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue' | |
11828 }); | |
11829 }); | |
11830 | |
11831 return chart; | |
11832 } | |
11833 | |
11834 //============================================================ | |
11835 // Expose Public Variables | |
11836 //------------------------------------------------------------ | |
11837 | |
11838 chart.options = nv.utils.optionsFunc.bind(chart); | |
11839 | |
11840 chart._options = Object.create({}, { | |
11841 // simple options, just get/set the necessary values | |
11842 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
11843 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
11844 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}}, | |
11845 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}}, | |
11846 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}}, | |
11847 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}}, | |
11848 xScale: {get: function(){return x;}, set: function(_){x=_;}}, | |
11849 yScale: {get: function(){return y;}, set: function(_){y=_;}}, | |
11850 animate: {get: function(){return animate;}, set: function(_){animate=_;}}, | |
11851 | |
11852 //functor options | |
11853 x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}}, | |
11854 y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}}, | |
11855 | |
11856 // options that require extra logic in the setter | |
11857 margin: {get: function(){return margin;}, set: function(_){ | |
11858 margin.top = _.top !== undefined ? _.top : margin.top; | |
11859 margin.right = _.right !== undefined ? _.right : margin.right; | |
11860 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
11861 margin.left = _.left !== undefined ? _.left : margin.left; | |
11862 }}, | |
11863 color: {get: function(){return color;}, set: function(_){ | |
11864 color = nv.utils.getColor(_); | |
11865 }} | |
11866 }); | |
11867 | |
11868 nv.utils.initOptions(chart); | |
11869 return chart; | |
11870 }; | |
11871 | |
11872 nv.models.sparklinePlus = function() { | |
11873 "use strict"; | |
11874 | |
11875 //============================================================ | |
11876 // Public Variables with Default Settings | |
11877 //------------------------------------------------------------ | |
11878 | |
11879 var sparkline = nv.models.sparkline(); | |
11880 | |
11881 var margin = {top: 15, right: 100, bottom: 10, left: 50} | |
11882 , width = null | |
11883 , height = null | |
11884 , x | |
11885 , y | |
11886 , index = [] | |
11887 , paused = false | |
11888 , xTickFormat = d3.format(',r') | |
11889 , yTickFormat = d3.format(',.2f') | |
11890 , showLastValue = true | |
11891 , alignValue = true | |
11892 , rightAlignValue = false | |
11893 , noData = null | |
11894 ; | |
11895 | |
11896 function chart(selection) { | |
11897 selection.each(function(data) { | |
11898 var container = d3.select(this); | |
11899 nv.utils.initSVG(container); | |
11900 | |
11901 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
11902 availableHeight = nv.utils.availableHeight(height, container, margin); | |
11903 | |
11904 chart.update = function() { container.call(chart); }; | |
11905 chart.container = this; | |
11906 | |
11907 // Display No Data message if there's nothing to show. | |
11908 if (!data || !data.length) { | |
11909 nv.utils.noData(chart, container) | |
11910 return chart; | |
11911 } else { | |
11912 container.selectAll('.nv-noData').remove(); | |
11913 } | |
11914 | |
11915 var currentValue = sparkline.y()(data[data.length-1], data.length-1); | |
11916 | |
11917 // Setup Scales | |
11918 x = sparkline.xScale(); | |
11919 y = sparkline.yScale(); | |
11920 | |
11921 // Setup containers and skeleton of chart | |
11922 var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]); | |
11923 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus'); | |
11924 var gEnter = wrapEnter.append('g'); | |
11925 var g = wrap.select('g'); | |
11926 | |
11927 gEnter.append('g').attr('class', 'nv-sparklineWrap'); | |
11928 gEnter.append('g').attr('class', 'nv-valueWrap'); | |
11929 gEnter.append('g').attr('class', 'nv-hoverArea'); | |
11930 | |
11931 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
11932 | |
11933 // Main Chart Component(s) | |
11934 var sparklineWrap = g.select('.nv-sparklineWrap'); | |
11935 | |
11936 sparkline.width(availableWidth).height(availableHeight); | |
11937 sparklineWrap.call(sparkline); | |
11938 | |
11939 if (showLastValue) { | |
11940 var valueWrap = g.select('.nv-valueWrap'); | |
11941 var value = valueWrap.selectAll('.nv-currentValue') | |
11942 .data([currentValue]); | |
11943 | |
11944 value.enter().append('text').attr('class', 'nv-currentValue') | |
11945 .attr('dx', rightAlignValue ? -8 : 8) | |
11946 .attr('dy', '.9em') | |
11947 .style('text-anchor', rightAlignValue ? 'end' : 'start'); | |
11948 | |
11949 value | |
11950 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0)) | |
11951 .attr('y', alignValue ? function (d) { | |
11952 return y(d) | |
11953 } : 0) | |
11954 .style('fill', sparkline.color()(data[data.length - 1], data.length - 1)) | |
11955 .text(yTickFormat(currentValue)); | |
11956 } | |
11957 | |
11958 gEnter.select('.nv-hoverArea').append('rect') | |
11959 .on('mousemove', sparklineHover) | |
11960 .on('click', function() { paused = !paused }) | |
11961 .on('mouseout', function() { index = []; updateValueLine(); }); | |
11962 | |
11963 g.select('.nv-hoverArea rect') | |
11964 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' }) | |
11965 .attr('width', availableWidth + margin.left + margin.right) | |
11966 .attr('height', availableHeight + margin.top); | |
11967 | |
11968 //index is currently global (within the chart), may or may not keep it that way | |
11969 function updateValueLine() { | |
11970 if (paused) return; | |
11971 | |
11972 var hoverValue = g.selectAll('.nv-hoverValue').data(index); | |
11973 | |
11974 var hoverEnter = hoverValue.enter() | |
11975 .append('g').attr('class', 'nv-hoverValue') | |
11976 .style('stroke-opacity', 0) | |
11977 .style('fill-opacity', 0); | |
11978 | |
11979 hoverValue.exit() | |
11980 .transition().duration(250) | |
11981 .style('stroke-opacity', 0) | |
11982 .style('fill-opacity', 0) | |
11983 .remove(); | |
11984 | |
11985 hoverValue | |
11986 .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' }) | |
11987 .transition().duration(250) | |
11988 .style('stroke-opacity', 1) | |
11989 .style('fill-opacity', 1); | |
11990 | |
11991 if (!index.length) return; | |
11992 | |
11993 hoverEnter.append('line') | |
11994 .attr('x1', 0) | |
11995 .attr('y1', -margin.top) | |
11996 .attr('x2', 0) | |
11997 .attr('y2', availableHeight); | |
11998 | |
11999 hoverEnter.append('text').attr('class', 'nv-xValue') | |
12000 .attr('x', -6) | |
12001 .attr('y', -margin.top) | |
12002 .attr('text-anchor', 'end') | |
12003 .attr('dy', '.9em'); | |
12004 | |
12005 g.select('.nv-hoverValue .nv-xValue') | |
12006 .text(xTickFormat(sparkline.x()(data[index[0]], index[0]))); | |
12007 | |
12008 hoverEnter.append('text').attr('class', 'nv-yValue') | |
12009 .attr('x', 6) | |
12010 .attr('y', -margin.top) | |
12011 .attr('text-anchor', 'start') | |
12012 .attr('dy', '.9em'); | |
12013 | |
12014 g.select('.nv-hoverValue .nv-yValue') | |
12015 .text(yTickFormat(sparkline.y()(data[index[0]], index[0]))); | |
12016 } | |
12017 | |
12018 function sparklineHover() { | |
12019 if (paused) return; | |
12020 | |
12021 var pos = d3.mouse(this)[0] - margin.left; | |
12022 | |
12023 function getClosestIndex(data, x) { | |
12024 var distance = Math.abs(sparkline.x()(data[0], 0) - x); | |
12025 var closestIndex = 0; | |
12026 for (var i = 0; i < data.length; i++){ | |
12027 if (Math.abs(sparkline.x()(data[i], i) - x) < distance) { | |
12028 distance = Math.abs(sparkline.x()(data[i], i) - x); | |
12029 closestIndex = i; | |
12030 } | |
12031 } | |
12032 return closestIndex; | |
12033 } | |
12034 | |
12035 index = [getClosestIndex(data, Math.round(x.invert(pos)))]; | |
12036 updateValueLine(); | |
12037 } | |
12038 | |
12039 }); | |
12040 | |
12041 return chart; | |
12042 } | |
12043 | |
12044 //============================================================ | |
12045 // Expose Public Variables | |
12046 //------------------------------------------------------------ | |
12047 | |
12048 // expose chart's sub-components | |
12049 chart.sparkline = sparkline; | |
12050 | |
12051 chart.options = nv.utils.optionsFunc.bind(chart); | |
12052 | |
12053 chart._options = Object.create({}, { | |
12054 // simple options, just get/set the necessary values | |
12055 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
12056 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
12057 xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}}, | |
12058 yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}}, | |
12059 showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}}, | |
12060 alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}}, | |
12061 rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}}, | |
12062 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
12063 | |
12064 // options that require extra logic in the setter | |
12065 margin: {get: function(){return margin;}, set: function(_){ | |
12066 margin.top = _.top !== undefined ? _.top : margin.top; | |
12067 margin.right = _.right !== undefined ? _.right : margin.right; | |
12068 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
12069 margin.left = _.left !== undefined ? _.left : margin.left; | |
12070 }} | |
12071 }); | |
12072 | |
12073 nv.utils.inheritOptions(chart, sparkline); | |
12074 nv.utils.initOptions(chart); | |
12075 | |
12076 return chart; | |
12077 }; | |
12078 | |
12079 nv.models.stackedArea = function() { | |
12080 "use strict"; | |
12081 | |
12082 //============================================================ | |
12083 // Public Variables with Default Settings | |
12084 //------------------------------------------------------------ | |
12085 | |
12086 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
12087 , width = 960 | |
12088 , height = 500 | |
12089 , color = nv.utils.defaultColor() // a function that computes the color | |
12090 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one | |
12091 , container = null | |
12092 , getX = function(d) { return d.x } // accessor to get the x value from a data point | |
12093 , getY = function(d) { return d.y } // accessor to get the y value from a data point | |
12094 , style = 'stack' | |
12095 , offset = 'zero' | |
12096 , order = 'default' | |
12097 , interpolate = 'linear' // controls the line interpolation | |
12098 , clipEdge = false // if true, masks lines within x and y scale | |
12099 , x //can be accessed via chart.xScale() | |
12100 , y //can be accessed via chart.yScale() | |
12101 , scatter = nv.models.scatter() | |
12102 , duration = 250 | |
12103 , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout') | |
12104 ; | |
12105 | |
12106 scatter | |
12107 .pointSize(2.2) // default size | |
12108 .pointDomain([2.2, 2.2]) // all the same size by default | |
12109 ; | |
12110 | |
12111 /************************************ | |
12112 * offset: | |
12113 * 'wiggle' (stream) | |
12114 * 'zero' (stacked) | |
12115 * 'expand' (normalize to 100%) | |
12116 * 'silhouette' (simple centered) | |
12117 * | |
12118 * order: | |
12119 * 'inside-out' (stream) | |
12120 * 'default' (input order) | |
12121 ************************************/ | |
12122 | |
12123 var renderWatch = nv.utils.renderWatch(dispatch, duration); | |
12124 | |
12125 function chart(selection) { | |
12126 renderWatch.reset(); | |
12127 renderWatch.models(scatter); | |
12128 selection.each(function(data) { | |
12129 var availableWidth = width - margin.left - margin.right, | |
12130 availableHeight = height - margin.top - margin.bottom; | |
12131 | |
12132 container = d3.select(this); | |
12133 nv.utils.initSVG(container); | |
12134 | |
12135 // Setup Scales | |
12136 x = scatter.xScale(); | |
12137 y = scatter.yScale(); | |
12138 | |
12139 var dataRaw = data; | |
12140 // Injecting point index into each point because d3.layout.stack().out does not give index | |
12141 data.forEach(function(aseries, i) { | |
12142 aseries.seriesIndex = i; | |
12143 aseries.values = aseries.values.map(function(d, j) { | |
12144 d.index = j; | |
12145 d.seriesIndex = i; | |
12146 return d; | |
12147 }); | |
12148 }); | |
12149 | |
12150 var dataFiltered = data.filter(function(series) { | |
12151 return !series.disabled; | |
12152 }); | |
12153 | |
12154 data = d3.layout.stack() | |
12155 .order(order) | |
12156 .offset(offset) | |
12157 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion | |
12158 .x(getX) | |
12159 .y(getY) | |
12160 .out(function(d, y0, y) { | |
12161 d.display = { | |
12162 y: y, | |
12163 y0: y0 | |
12164 }; | |
12165 }) | |
12166 (dataFiltered); | |
12167 | |
12168 // Setup containers and skeleton of chart | |
12169 var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]); | |
12170 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea'); | |
12171 var defsEnter = wrapEnter.append('defs'); | |
12172 var gEnter = wrapEnter.append('g'); | |
12173 var g = wrap.select('g'); | |
12174 | |
12175 gEnter.append('g').attr('class', 'nv-areaWrap'); | |
12176 gEnter.append('g').attr('class', 'nv-scatterWrap'); | |
12177 | |
12178 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
12179 | |
12180 // If the user has not specified forceY, make sure 0 is included in the domain | |
12181 // Otherwise, use user-specified values for forceY | |
12182 if (scatter.forceY().length == 0) { | |
12183 scatter.forceY().push(0); | |
12184 } | |
12185 | |
12186 scatter | |
12187 .width(availableWidth) | |
12188 .height(availableHeight) | |
12189 .x(getX) | |
12190 .y(function(d) { return d.display.y + d.display.y0 }) | |
12191 .forceY([0]) | |
12192 .color(data.map(function(d,i) { | |
12193 return d.color || color(d, d.seriesIndex); | |
12194 })); | |
12195 | |
12196 var scatterWrap = g.select('.nv-scatterWrap') | |
12197 .datum(data); | |
12198 | |
12199 scatterWrap.call(scatter); | |
12200 | |
12201 defsEnter.append('clipPath') | |
12202 .attr('id', 'nv-edge-clip-' + id) | |
12203 .append('rect'); | |
12204 | |
12205 wrap.select('#nv-edge-clip-' + id + ' rect') | |
12206 .attr('width', availableWidth) | |
12207 .attr('height', availableHeight); | |
12208 | |
12209 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : ''); | |
12210 | |
12211 var area = d3.svg.area() | |
12212 .x(function(d,i) { return x(getX(d,i)) }) | |
12213 .y0(function(d) { | |
12214 return y(d.display.y0) | |
12215 }) | |
12216 .y1(function(d) { | |
12217 return y(d.display.y + d.display.y0) | |
12218 }) | |
12219 .interpolate(interpolate); | |
12220 | |
12221 var zeroArea = d3.svg.area() | |
12222 .x(function(d,i) { return x(getX(d,i)) }) | |
12223 .y0(function(d) { return y(d.display.y0) }) | |
12224 .y1(function(d) { return y(d.display.y0) }); | |
12225 | |
12226 var path = g.select('.nv-areaWrap').selectAll('path.nv-area') | |
12227 .data(function(d) { return d }); | |
12228 | |
12229 path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i }) | |
12230 .attr('d', function(d,i){ | |
12231 return zeroArea(d.values, d.seriesIndex); | |
12232 }) | |
12233 .on('mouseover', function(d,i) { | |
12234 d3.select(this).classed('hover', true); | |
12235 dispatch.areaMouseover({ | |
12236 point: d, | |
12237 series: d.key, | |
12238 pos: [d3.event.pageX, d3.event.pageY], | |
12239 seriesIndex: d.seriesIndex | |
12240 }); | |
12241 }) | |
12242 .on('mouseout', function(d,i) { | |
12243 d3.select(this).classed('hover', false); | |
12244 dispatch.areaMouseout({ | |
12245 point: d, | |
12246 series: d.key, | |
12247 pos: [d3.event.pageX, d3.event.pageY], | |
12248 seriesIndex: d.seriesIndex | |
12249 }); | |
12250 }) | |
12251 .on('click', function(d,i) { | |
12252 d3.select(this).classed('hover', false); | |
12253 dispatch.areaClick({ | |
12254 point: d, | |
12255 series: d.key, | |
12256 pos: [d3.event.pageX, d3.event.pageY], | |
12257 seriesIndex: d.seriesIndex | |
12258 }); | |
12259 }); | |
12260 | |
12261 path.exit().remove(); | |
12262 path.style('fill', function(d,i){ | |
12263 return d.color || color(d, d.seriesIndex) | |
12264 }) | |
12265 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) }); | |
12266 path.watchTransition(renderWatch,'stackedArea path') | |
12267 .attr('d', function(d,i) { | |
12268 return area(d.values,i) | |
12269 }); | |
12270 | |
12271 //============================================================ | |
12272 // Event Handling/Dispatching (in chart's scope) | |
12273 //------------------------------------------------------------ | |
12274 | |
12275 scatter.dispatch.on('elementMouseover.area', function(e) { | |
12276 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true); | |
12277 }); | |
12278 scatter.dispatch.on('elementMouseout.area', function(e) { | |
12279 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false); | |
12280 }); | |
12281 | |
12282 //Special offset functions | |
12283 chart.d3_stackedOffset_stackPercent = function(stackData) { | |
12284 var n = stackData.length, //How many series | |
12285 m = stackData[0].length, //how many points per series | |
12286 i, | |
12287 j, | |
12288 o, | |
12289 y0 = []; | |
12290 | |
12291 for (j = 0; j < m; ++j) { //Looping through all points | |
12292 for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series | |
12293 o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time. | |
12294 } | |
12295 | |
12296 if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0 | |
12297 stackData[i][j][1] /= o; | |
12298 } else { //(total y value of all series at point in time i) == 0 | |
12299 for (i = 0; i < n; i++) { | |
12300 stackData[i][j][1] = 0; | |
12301 } | |
12302 } | |
12303 } | |
12304 for (j = 0; j < m; ++j) y0[j] = 0; | |
12305 return y0; | |
12306 }; | |
12307 | |
12308 }); | |
12309 | |
12310 renderWatch.renderEnd('stackedArea immediate'); | |
12311 return chart; | |
12312 } | |
12313 | |
12314 //============================================================ | |
12315 // Global getters and setters | |
12316 //------------------------------------------------------------ | |
12317 | |
12318 chart.dispatch = dispatch; | |
12319 chart.scatter = scatter; | |
12320 | |
12321 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); }); | |
12322 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); }); | |
12323 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); }); | |
12324 | |
12325 chart.interpolate = function(_) { | |
12326 if (!arguments.length) return interpolate; | |
12327 interpolate = _; | |
12328 return chart; | |
12329 }; | |
12330 | |
12331 chart.duration = function(_) { | |
12332 if (!arguments.length) return duration; | |
12333 duration = _; | |
12334 renderWatch.reset(duration); | |
12335 scatter.duration(duration); | |
12336 return chart; | |
12337 }; | |
12338 | |
12339 chart.dispatch = dispatch; | |
12340 chart.scatter = scatter; | |
12341 chart.options = nv.utils.optionsFunc.bind(chart); | |
12342 | |
12343 chart._options = Object.create({}, { | |
12344 // simple options, just get/set the necessary values | |
12345 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
12346 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
12347 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}}, | |
12348 offset: {get: function(){return offset;}, set: function(_){offset=_;}}, | |
12349 order: {get: function(){return order;}, set: function(_){order=_;}}, | |
12350 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}}, | |
12351 | |
12352 // simple functor options | |
12353 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}}, | |
12354 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}}, | |
12355 | |
12356 // options that require extra logic in the setter | |
12357 margin: {get: function(){return margin;}, set: function(_){ | |
12358 margin.top = _.top !== undefined ? _.top : margin.top; | |
12359 margin.right = _.right !== undefined ? _.right : margin.right; | |
12360 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
12361 margin.left = _.left !== undefined ? _.left : margin.left; | |
12362 }}, | |
12363 color: {get: function(){return color;}, set: function(_){ | |
12364 color = nv.utils.getColor(_); | |
12365 }}, | |
12366 style: {get: function(){return style;}, set: function(_){ | |
12367 style = _; | |
12368 switch (style) { | |
12369 case 'stack': | |
12370 chart.offset('zero'); | |
12371 chart.order('default'); | |
12372 break; | |
12373 case 'stream': | |
12374 chart.offset('wiggle'); | |
12375 chart.order('inside-out'); | |
12376 break; | |
12377 case 'stream-center': | |
12378 chart.offset('silhouette'); | |
12379 chart.order('inside-out'); | |
12380 break; | |
12381 case 'expand': | |
12382 chart.offset('expand'); | |
12383 chart.order('default'); | |
12384 break; | |
12385 case 'stack_percent': | |
12386 chart.offset(chart.d3_stackedOffset_stackPercent); | |
12387 chart.order('default'); | |
12388 break; | |
12389 } | |
12390 }}, | |
12391 duration: {get: function(){return duration;}, set: function(_){ | |
12392 duration = _; | |
12393 renderWatch.reset(duration); | |
12394 scatter.duration(duration); | |
12395 }} | |
12396 }); | |
12397 | |
12398 nv.utils.inheritOptions(chart, scatter); | |
12399 nv.utils.initOptions(chart); | |
12400 | |
12401 return chart; | |
12402 }; | |
12403 | |
12404 nv.models.stackedAreaChart = function() { | |
12405 "use strict"; | |
12406 | |
12407 //============================================================ | |
12408 // Public Variables with Default Settings | |
12409 //------------------------------------------------------------ | |
12410 | |
12411 var stacked = nv.models.stackedArea() | |
12412 , xAxis = nv.models.axis() | |
12413 , yAxis = nv.models.axis() | |
12414 , legend = nv.models.legend() | |
12415 , controls = nv.models.legend() | |
12416 , interactiveLayer = nv.interactiveGuideline() | |
12417 , tooltip = nv.models.tooltip() | |
12418 ; | |
12419 | |
12420 var margin = {top: 30, right: 25, bottom: 50, left: 60} | |
12421 , width = null | |
12422 , height = null | |
12423 , color = nv.utils.defaultColor() | |
12424 , showControls = true | |
12425 , showLegend = true | |
12426 , showXAxis = true | |
12427 , showYAxis = true | |
12428 , rightAlignYAxis = false | |
12429 , useInteractiveGuideline = false | |
12430 , x //can be accessed via chart.xScale() | |
12431 , y //can be accessed via chart.yScale() | |
12432 , state = nv.utils.state() | |
12433 , defaultState = null | |
12434 , noData = null | |
12435 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd') | |
12436 , controlWidth = 250 | |
12437 , controlOptions = ['Stacked','Stream','Expanded'] | |
12438 , controlLabels = {} | |
12439 , duration = 250 | |
12440 ; | |
12441 | |
12442 state.style = stacked.style(); | |
12443 xAxis.orient('bottom').tickPadding(7); | |
12444 yAxis.orient((rightAlignYAxis) ? 'right' : 'left'); | |
12445 | |
12446 tooltip | |
12447 .headerFormatter(function(d, i) { | |
12448 return xAxis.tickFormat()(d, i); | |
12449 }) | |
12450 .valueFormatter(function(d, i) { | |
12451 return yAxis.tickFormat()(d, i); | |
12452 }); | |
12453 | |
12454 interactiveLayer.tooltip | |
12455 .headerFormatter(function(d, i) { | |
12456 return xAxis.tickFormat()(d, i); | |
12457 }) | |
12458 .valueFormatter(function(d, i) { | |
12459 return yAxis.tickFormat()(d, i); | |
12460 }); | |
12461 | |
12462 var oldYTickFormat = null, | |
12463 oldValueFormatter = null; | |
12464 | |
12465 controls.updateState(false); | |
12466 | |
12467 //============================================================ | |
12468 // Private Variables | |
12469 //------------------------------------------------------------ | |
12470 | |
12471 var renderWatch = nv.utils.renderWatch(dispatch); | |
12472 var style = stacked.style(); | |
12473 | |
12474 var stateGetter = function(data) { | |
12475 return function(){ | |
12476 return { | |
12477 active: data.map(function(d) { return !d.disabled }), | |
12478 style: stacked.style() | |
12479 }; | |
12480 } | |
12481 }; | |
12482 | |
12483 var stateSetter = function(data) { | |
12484 return function(state) { | |
12485 if (state.style !== undefined) | |
12486 style = state.style; | |
12487 if (state.active !== undefined) | |
12488 data.forEach(function(series,i) { | |
12489 series.disabled = !state.active[i]; | |
12490 }); | |
12491 } | |
12492 }; | |
12493 | |
12494 var percentFormatter = d3.format('%'); | |
12495 | |
12496 function chart(selection) { | |
12497 renderWatch.reset(); | |
12498 renderWatch.models(stacked); | |
12499 if (showXAxis) renderWatch.models(xAxis); | |
12500 if (showYAxis) renderWatch.models(yAxis); | |
12501 | |
12502 selection.each(function(data) { | |
12503 var container = d3.select(this), | |
12504 that = this; | |
12505 nv.utils.initSVG(container); | |
12506 | |
12507 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
12508 availableHeight = nv.utils.availableHeight(height, container, margin); | |
12509 | |
12510 chart.update = function() { container.transition().duration(duration).call(chart); }; | |
12511 chart.container = this; | |
12512 | |
12513 state | |
12514 .setter(stateSetter(data), chart.update) | |
12515 .getter(stateGetter(data)) | |
12516 .update(); | |
12517 | |
12518 // DEPRECATED set state.disabled | |
12519 state.disabled = data.map(function(d) { return !!d.disabled }); | |
12520 | |
12521 if (!defaultState) { | |
12522 var key; | |
12523 defaultState = {}; | |
12524 for (key in state) { | |
12525 if (state[key] instanceof Array) | |
12526 defaultState[key] = state[key].slice(0); | |
12527 else | |
12528 defaultState[key] = state[key]; | |
12529 } | |
12530 } | |
12531 | |
12532 // Display No Data message if there's nothing to show. | |
12533 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) { | |
12534 nv.utils.noData(chart, container) | |
12535 return chart; | |
12536 } else { | |
12537 container.selectAll('.nv-noData').remove(); | |
12538 } | |
12539 | |
12540 // Setup Scales | |
12541 x = stacked.xScale(); | |
12542 y = stacked.yScale(); | |
12543 | |
12544 // Setup containers and skeleton of chart | |
12545 var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]); | |
12546 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g'); | |
12547 var g = wrap.select('g'); | |
12548 | |
12549 gEnter.append("rect").style("opacity",0); | |
12550 gEnter.append('g').attr('class', 'nv-x nv-axis'); | |
12551 gEnter.append('g').attr('class', 'nv-y nv-axis'); | |
12552 gEnter.append('g').attr('class', 'nv-stackedWrap'); | |
12553 gEnter.append('g').attr('class', 'nv-legendWrap'); | |
12554 gEnter.append('g').attr('class', 'nv-controlsWrap'); | |
12555 gEnter.append('g').attr('class', 'nv-interactive'); | |
12556 | |
12557 g.select("rect").attr("width",availableWidth).attr("height",availableHeight); | |
12558 | |
12559 // Legend | |
12560 if (showLegend) { | |
12561 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth; | |
12562 | |
12563 legend.width(legendWidth); | |
12564 g.select('.nv-legendWrap').datum(data).call(legend); | |
12565 | |
12566 if ( margin.top != legend.height()) { | |
12567 margin.top = legend.height(); | |
12568 availableHeight = nv.utils.availableHeight(height, container, margin); | |
12569 } | |
12570 | |
12571 g.select('.nv-legendWrap') | |
12572 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')'); | |
12573 } | |
12574 | |
12575 // Controls | |
12576 if (showControls) { | |
12577 var controlsData = [ | |
12578 { | |
12579 key: controlLabels.stacked || 'Stacked', | |
12580 metaKey: 'Stacked', | |
12581 disabled: stacked.style() != 'stack', | |
12582 style: 'stack' | |
12583 }, | |
12584 { | |
12585 key: controlLabels.stream || 'Stream', | |
12586 metaKey: 'Stream', | |
12587 disabled: stacked.style() != 'stream', | |
12588 style: 'stream' | |
12589 }, | |
12590 { | |
12591 key: controlLabels.expanded || 'Expanded', | |
12592 metaKey: 'Expanded', | |
12593 disabled: stacked.style() != 'expand', | |
12594 style: 'expand' | |
12595 }, | |
12596 { | |
12597 key: controlLabels.stack_percent || 'Stack %', | |
12598 metaKey: 'Stack_Percent', | |
12599 disabled: stacked.style() != 'stack_percent', | |
12600 style: 'stack_percent' | |
12601 } | |
12602 ]; | |
12603 | |
12604 controlWidth = (controlOptions.length/3) * 260; | |
12605 controlsData = controlsData.filter(function(d) { | |
12606 return controlOptions.indexOf(d.metaKey) !== -1; | |
12607 }); | |
12608 | |
12609 controls | |
12610 .width( controlWidth ) | |
12611 .color(['#444', '#444', '#444']); | |
12612 | |
12613 g.select('.nv-controlsWrap') | |
12614 .datum(controlsData) | |
12615 .call(controls); | |
12616 | |
12617 if ( margin.top != Math.max(controls.height(), legend.height()) ) { | |
12618 margin.top = Math.max(controls.height(), legend.height()); | |
12619 availableHeight = nv.utils.availableHeight(height, container, margin); | |
12620 } | |
12621 | |
12622 g.select('.nv-controlsWrap') | |
12623 .attr('transform', 'translate(0,' + (-margin.top) +')'); | |
12624 } | |
12625 | |
12626 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
12627 | |
12628 if (rightAlignYAxis) { | |
12629 g.select(".nv-y.nv-axis") | |
12630 .attr("transform", "translate(" + availableWidth + ",0)"); | |
12631 } | |
12632 | |
12633 //Set up interactive layer | |
12634 if (useInteractiveGuideline) { | |
12635 interactiveLayer | |
12636 .width(availableWidth) | |
12637 .height(availableHeight) | |
12638 .margin({left: margin.left, top: margin.top}) | |
12639 .svgContainer(container) | |
12640 .xScale(x); | |
12641 wrap.select(".nv-interactive").call(interactiveLayer); | |
12642 } | |
12643 | |
12644 stacked | |
12645 .width(availableWidth) | |
12646 .height(availableHeight); | |
12647 | |
12648 var stackedWrap = g.select('.nv-stackedWrap') | |
12649 .datum(data); | |
12650 | |
12651 stackedWrap.transition().call(stacked); | |
12652 | |
12653 // Setup Axes | |
12654 if (showXAxis) { | |
12655 xAxis.scale(x) | |
12656 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) ) | |
12657 .tickSize( -availableHeight, 0); | |
12658 | |
12659 g.select('.nv-x.nv-axis') | |
12660 .attr('transform', 'translate(0,' + availableHeight + ')'); | |
12661 | |
12662 g.select('.nv-x.nv-axis') | |
12663 .transition().duration(0) | |
12664 .call(xAxis); | |
12665 } | |
12666 | |
12667 if (showYAxis) { | |
12668 var ticks; | |
12669 if (stacked.offset() === 'wiggle') { | |
12670 ticks = 0; | |
12671 } | |
12672 else { | |
12673 ticks = nv.utils.calcTicksY(availableHeight/36, data); | |
12674 } | |
12675 yAxis.scale(y) | |
12676 ._ticks(ticks) | |
12677 .tickSize(-availableWidth, 0); | |
12678 | |
12679 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { | |
12680 var currentFormat = yAxis.tickFormat(); | |
12681 | |
12682 if ( !oldYTickFormat || currentFormat !== percentFormatter ) | |
12683 oldYTickFormat = currentFormat; | |
12684 | |
12685 //Forces the yAxis to use percentage in 'expand' mode. | |
12686 yAxis.tickFormat(percentFormatter); | |
12687 } | |
12688 else { | |
12689 if (oldYTickFormat) { | |
12690 yAxis.tickFormat(oldYTickFormat); | |
12691 oldYTickFormat = null; | |
12692 } | |
12693 } | |
12694 | |
12695 g.select('.nv-y.nv-axis') | |
12696 .transition().duration(0) | |
12697 .call(yAxis); | |
12698 } | |
12699 | |
12700 //============================================================ | |
12701 // Event Handling/Dispatching (in chart's scope) | |
12702 //------------------------------------------------------------ | |
12703 | |
12704 stacked.dispatch.on('areaClick.toggle', function(e) { | |
12705 if (data.filter(function(d) { return !d.disabled }).length === 1) | |
12706 data.forEach(function(d) { | |
12707 d.disabled = false; | |
12708 }); | |
12709 else | |
12710 data.forEach(function(d,i) { | |
12711 d.disabled = (i != e.seriesIndex); | |
12712 }); | |
12713 | |
12714 state.disabled = data.map(function(d) { return !!d.disabled }); | |
12715 dispatch.stateChange(state); | |
12716 | |
12717 chart.update(); | |
12718 }); | |
12719 | |
12720 legend.dispatch.on('stateChange', function(newState) { | |
12721 for (var key in newState) | |
12722 state[key] = newState[key]; | |
12723 dispatch.stateChange(state); | |
12724 chart.update(); | |
12725 }); | |
12726 | |
12727 controls.dispatch.on('legendClick', function(d,i) { | |
12728 if (!d.disabled) return; | |
12729 | |
12730 controlsData = controlsData.map(function(s) { | |
12731 s.disabled = true; | |
12732 return s; | |
12733 }); | |
12734 d.disabled = false; | |
12735 | |
12736 stacked.style(d.style); | |
12737 | |
12738 | |
12739 state.style = stacked.style(); | |
12740 dispatch.stateChange(state); | |
12741 | |
12742 chart.update(); | |
12743 }); | |
12744 | |
12745 interactiveLayer.dispatch.on('elementMousemove', function(e) { | |
12746 stacked.clearHighlights(); | |
12747 var singlePoint, pointIndex, pointXLocation, allData = []; | |
12748 data | |
12749 .filter(function(series, i) { | |
12750 series.seriesIndex = i; | |
12751 return !series.disabled; | |
12752 }) | |
12753 .forEach(function(series,i) { | |
12754 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x()); | |
12755 var point = series.values[pointIndex]; | |
12756 var pointYValue = chart.y()(point, pointIndex); | |
12757 if (pointYValue != null) { | |
12758 stacked.highlightPoint(i, pointIndex, true); | |
12759 } | |
12760 if (typeof point === 'undefined') return; | |
12761 if (typeof singlePoint === 'undefined') singlePoint = point; | |
12762 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex)); | |
12763 | |
12764 //If we are in 'expand' mode, use the stacked percent value instead of raw value. | |
12765 var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex); | |
12766 allData.push({ | |
12767 key: series.key, | |
12768 value: tooltipValue, | |
12769 color: color(series,series.seriesIndex), | |
12770 stackedValue: point.display | |
12771 }); | |
12772 }); | |
12773 | |
12774 allData.reverse(); | |
12775 | |
12776 //Highlight the tooltip entry based on which stack the mouse is closest to. | |
12777 if (allData.length > 2) { | |
12778 var yValue = chart.yScale().invert(e.mouseY); | |
12779 var yDistMax = Infinity, indexToHighlight = null; | |
12780 allData.forEach(function(series,i) { | |
12781 | |
12782 //To handle situation where the stacked area chart is negative, we need to use absolute values | |
12783 //when checking if the mouse Y value is within the stack area. | |
12784 yValue = Math.abs(yValue); | |
12785 var stackedY0 = Math.abs(series.stackedValue.y0); | |
12786 var stackedY = Math.abs(series.stackedValue.y); | |
12787 if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0)) | |
12788 { | |
12789 indexToHighlight = i; | |
12790 return; | |
12791 } | |
12792 }); | |
12793 if (indexToHighlight != null) | |
12794 allData[indexToHighlight].highlight = true; | |
12795 } | |
12796 | |
12797 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex)); | |
12798 | |
12799 var valueFormatter = interactiveLayer.tooltip.valueFormatter(); | |
12800 // Keeps track of the tooltip valueFormatter if the chart changes to expanded view | |
12801 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') { | |
12802 if ( !oldValueFormatter ) { | |
12803 oldValueFormatter = valueFormatter; | |
12804 } | |
12805 //Forces the tooltip to use percentage in 'expand' mode. | |
12806 valueFormatter = d3.format(".1%"); | |
12807 } | |
12808 else { | |
12809 if (oldValueFormatter) { | |
12810 valueFormatter = oldValueFormatter; | |
12811 oldValueFormatter = null; | |
12812 } | |
12813 } | |
12814 | |
12815 interactiveLayer.tooltip | |
12816 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top}) | |
12817 .chartContainer(that.parentNode) | |
12818 .valueFormatter(valueFormatter) | |
12819 .data( | |
12820 { | |
12821 value: xValue, | |
12822 series: allData | |
12823 } | |
12824 )(); | |
12825 | |
12826 interactiveLayer.renderGuideLine(pointXLocation); | |
12827 | |
12828 }); | |
12829 | |
12830 interactiveLayer.dispatch.on("elementMouseout",function(e) { | |
12831 stacked.clearHighlights(); | |
12832 }); | |
12833 | |
12834 // Update chart from a state object passed to event handler | |
12835 dispatch.on('changeState', function(e) { | |
12836 | |
12837 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) { | |
12838 data.forEach(function(series,i) { | |
12839 series.disabled = e.disabled[i]; | |
12840 }); | |
12841 | |
12842 state.disabled = e.disabled; | |
12843 } | |
12844 | |
12845 if (typeof e.style !== 'undefined') { | |
12846 stacked.style(e.style); | |
12847 style = e.style; | |
12848 } | |
12849 | |
12850 chart.update(); | |
12851 }); | |
12852 | |
12853 }); | |
12854 | |
12855 renderWatch.renderEnd('stacked Area chart immediate'); | |
12856 return chart; | |
12857 } | |
12858 | |
12859 //============================================================ | |
12860 // Event Handling/Dispatching (out of chart's scope) | |
12861 //------------------------------------------------------------ | |
12862 | |
12863 stacked.dispatch.on('elementMouseover.tooltip', function(evt) { | |
12864 evt.point['x'] = stacked.x()(evt.point); | |
12865 evt.point['y'] = stacked.y()(evt.point); | |
12866 tooltip.data(evt).position(evt.pos).hidden(false); | |
12867 }); | |
12868 | |
12869 stacked.dispatch.on('elementMouseout.tooltip', function(evt) { | |
12870 tooltip.hidden(true) | |
12871 }); | |
12872 | |
12873 //============================================================ | |
12874 // Expose Public Variables | |
12875 //------------------------------------------------------------ | |
12876 | |
12877 // expose chart's sub-components | |
12878 chart.dispatch = dispatch; | |
12879 chart.stacked = stacked; | |
12880 chart.legend = legend; | |
12881 chart.controls = controls; | |
12882 chart.xAxis = xAxis; | |
12883 chart.yAxis = yAxis; | |
12884 chart.interactiveLayer = interactiveLayer; | |
12885 chart.tooltip = tooltip; | |
12886 | |
12887 chart.dispatch = dispatch; | |
12888 chart.options = nv.utils.optionsFunc.bind(chart); | |
12889 | |
12890 chart._options = Object.create({}, { | |
12891 // simple options, just get/set the necessary values | |
12892 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
12893 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
12894 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}}, | |
12895 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}}, | |
12896 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}}, | |
12897 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
12898 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
12899 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}}, | |
12900 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}}, | |
12901 controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}}, | |
12902 | |
12903 // deprecated options | |
12904 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){ | |
12905 // deprecated after 1.7.1 | |
12906 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead'); | |
12907 tooltip.enabled(!!_); | |
12908 }}, | |
12909 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){ | |
12910 // deprecated after 1.7.1 | |
12911 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead'); | |
12912 tooltip.contentGenerator(_); | |
12913 }}, | |
12914 | |
12915 // options that require extra logic in the setter | |
12916 margin: {get: function(){return margin;}, set: function(_){ | |
12917 margin.top = _.top !== undefined ? _.top : margin.top; | |
12918 margin.right = _.right !== undefined ? _.right : margin.right; | |
12919 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
12920 margin.left = _.left !== undefined ? _.left : margin.left; | |
12921 }}, | |
12922 duration: {get: function(){return duration;}, set: function(_){ | |
12923 duration = _; | |
12924 renderWatch.reset(duration); | |
12925 stacked.duration(duration); | |
12926 xAxis.duration(duration); | |
12927 yAxis.duration(duration); | |
12928 }}, | |
12929 color: {get: function(){return color;}, set: function(_){ | |
12930 color = nv.utils.getColor(_); | |
12931 legend.color(color); | |
12932 stacked.color(color); | |
12933 }}, | |
12934 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){ | |
12935 rightAlignYAxis = _; | |
12936 yAxis.orient( rightAlignYAxis ? 'right' : 'left'); | |
12937 }}, | |
12938 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){ | |
12939 useInteractiveGuideline = !!_; | |
12940 chart.interactive(!_); | |
12941 chart.useVoronoi(!_); | |
12942 stacked.scatter.interactive(!_); | |
12943 }} | |
12944 }); | |
12945 | |
12946 nv.utils.inheritOptions(chart, stacked); | |
12947 nv.utils.initOptions(chart); | |
12948 | |
12949 return chart; | |
12950 }; | |
12951 // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad | |
12952 nv.models.sunburst = function() { | |
12953 "use strict"; | |
12954 | |
12955 //============================================================ | |
12956 // Public Variables with Default Settings | |
12957 //------------------------------------------------------------ | |
12958 | |
12959 var margin = {top: 0, right: 0, bottom: 0, left: 0} | |
12960 , width = null | |
12961 , height = null | |
12962 , mode = "count" | |
12963 , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }} | |
12964 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one | |
12965 , container = null | |
12966 , color = nv.utils.defaultColor() | |
12967 , duration = 500 | |
12968 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd') | |
12969 ; | |
12970 | |
12971 var x = d3.scale.linear().range([0, 2 * Math.PI]); | |
12972 var y = d3.scale.sqrt(); | |
12973 | |
12974 var partition = d3.layout.partition() | |
12975 .sort(null) | |
12976 .value(function(d) { return 1; }); | |
12977 | |
12978 var arc = d3.svg.arc() | |
12979 .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); }) | |
12980 .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); }) | |
12981 .innerRadius(function(d) { return Math.max(0, y(d.y)); }) | |
12982 .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); }); | |
12983 | |
12984 // Keep track of the current and previous node being displayed as the root. | |
12985 var node, prevNode; | |
12986 // Keep track of the root node | |
12987 var rootNode; | |
12988 | |
12989 //============================================================ | |
12990 // chart function | |
12991 //------------------------------------------------------------ | |
12992 | |
12993 var renderWatch = nv.utils.renderWatch(dispatch); | |
12994 | |
12995 function chart(selection) { | |
12996 renderWatch.reset(); | |
12997 selection.each(function(data) { | |
12998 container = d3.select(this); | |
12999 var availableWidth = nv.utils.availableWidth(width, container, margin); | |
13000 var availableHeight = nv.utils.availableHeight(height, container, margin); | |
13001 var radius = Math.min(availableWidth, availableHeight) / 2; | |
13002 var path; | |
13003 | |
13004 nv.utils.initSVG(container); | |
13005 | |
13006 // Setup containers and skeleton of chart | |
13007 var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data); | |
13008 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id); | |
13009 | |
13010 var g = wrapEnter.selectAll('nv-sunburst'); | |
13011 | |
13012 wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')'); | |
13013 | |
13014 container.on('click', function (d, i) { | |
13015 dispatch.chartClick({ | |
13016 data: d, | |
13017 index: i, | |
13018 pos: d3.event, | |
13019 id: id | |
13020 }); | |
13021 }); | |
13022 | |
13023 y.range([0, radius]); | |
13024 | |
13025 node = node || data; | |
13026 rootNode = data[0]; | |
13027 partition.value(modes[mode] || modes["count"]); | |
13028 path = g.data(partition.nodes).enter() | |
13029 .append("path") | |
13030 .attr("d", arc) | |
13031 .style("fill", function (d) { | |
13032 return color((d.children ? d : d.parent).name); | |
13033 }) | |
13034 .style("stroke", "#FFF") | |
13035 .on("click", function(d) { | |
13036 if (prevNode !== node && node !== d) prevNode = node; | |
13037 node = d; | |
13038 path.transition() | |
13039 .duration(duration) | |
13040 .attrTween("d", arcTweenZoom(d)); | |
13041 }) | |
13042 .each(stash) | |
13043 .on("dblclick", function(d) { | |
13044 if (prevNode.parent == d) { | |
13045 path.transition() | |
13046 .duration(duration) | |
13047 .attrTween("d", arcTweenZoom(rootNode)); | |
13048 } | |
13049 }) | |
13050 .each(stash) | |
13051 .on('mouseover', function(d,i){ | |
13052 d3.select(this).classed('hover', true).style('opacity', 0.8); | |
13053 dispatch.elementMouseover({ | |
13054 data: d, | |
13055 color: d3.select(this).style("fill") | |
13056 }); | |
13057 }) | |
13058 .on('mouseout', function(d,i){ | |
13059 d3.select(this).classed('hover', false).style('opacity', 1); | |
13060 dispatch.elementMouseout({ | |
13061 data: d | |
13062 }); | |
13063 }) | |
13064 .on('mousemove', function(d,i){ | |
13065 dispatch.elementMousemove({ | |
13066 data: d | |
13067 }); | |
13068 }); | |
13069 | |
13070 | |
13071 | |
13072 // Setup for switching data: stash the old values for transition. | |
13073 function stash(d) { | |
13074 d.x0 = d.x; | |
13075 d.dx0 = d.dx; | |
13076 } | |
13077 | |
13078 // When switching data: interpolate the arcs in data space. | |
13079 function arcTweenData(a, i) { | |
13080 var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a); | |
13081 | |
13082 function tween(t) { | |
13083 var b = oi(t); | |
13084 a.x0 = b.x; | |
13085 a.dx0 = b.dx; | |
13086 return arc(b); | |
13087 } | |
13088 | |
13089 if (i == 0) { | |
13090 // If we are on the first arc, adjust the x domain to match the root node | |
13091 // at the current zoom level. (We only need to do this once.) | |
13092 var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]); | |
13093 return function (t) { | |
13094 x.domain(xd(t)); | |
13095 return tween(t); | |
13096 }; | |
13097 } else { | |
13098 return tween; | |
13099 } | |
13100 } | |
13101 | |
13102 // When zooming: interpolate the scales. | |
13103 function arcTweenZoom(d) { | |
13104 var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]), | |
13105 yd = d3.interpolate(y.domain(), [d.y, 1]), | |
13106 yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]); | |
13107 return function (d, i) { | |
13108 return i | |
13109 ? function (t) { | |
13110 return arc(d); | |
13111 } | |
13112 : function (t) { | |
13113 x.domain(xd(t)); | |
13114 y.domain(yd(t)).range(yr(t)); | |
13115 return arc(d); | |
13116 }; | |
13117 }; | |
13118 } | |
13119 | |
13120 }); | |
13121 | |
13122 renderWatch.renderEnd('sunburst immediate'); | |
13123 return chart; | |
13124 } | |
13125 | |
13126 //============================================================ | |
13127 // Expose Public Variables | |
13128 //------------------------------------------------------------ | |
13129 | |
13130 chart.dispatch = dispatch; | |
13131 chart.options = nv.utils.optionsFunc.bind(chart); | |
13132 | |
13133 chart._options = Object.create({}, { | |
13134 // simple options, just get/set the necessary values | |
13135 width: {get: function(){return width;}, set: function(_){width=_;}}, | |
13136 height: {get: function(){return height;}, set: function(_){height=_;}}, | |
13137 mode: {get: function(){return mode;}, set: function(_){mode=_;}}, | |
13138 id: {get: function(){return id;}, set: function(_){id=_;}}, | |
13139 duration: {get: function(){return duration;}, set: function(_){duration=_;}}, | |
13140 | |
13141 // options that require extra logic in the setter | |
13142 margin: {get: function(){return margin;}, set: function(_){ | |
13143 margin.top = _.top != undefined ? _.top : margin.top; | |
13144 margin.right = _.right != undefined ? _.right : margin.right; | |
13145 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom; | |
13146 margin.left = _.left != undefined ? _.left : margin.left; | |
13147 }}, | |
13148 color: {get: function(){return color;}, set: function(_){ | |
13149 color=nv.utils.getColor(_); | |
13150 }} | |
13151 }); | |
13152 | |
13153 nv.utils.initOptions(chart); | |
13154 return chart; | |
13155 }; | |
13156 nv.models.sunburstChart = function() { | |
13157 "use strict"; | |
13158 | |
13159 //============================================================ | |
13160 // Public Variables with Default Settings | |
13161 //------------------------------------------------------------ | |
13162 | |
13163 var sunburst = nv.models.sunburst(); | |
13164 var tooltip = nv.models.tooltip(); | |
13165 | |
13166 var margin = {top: 30, right: 20, bottom: 20, left: 20} | |
13167 , width = null | |
13168 , height = null | |
13169 , color = nv.utils.defaultColor() | |
13170 , id = Math.round(Math.random() * 100000) | |
13171 , defaultState = null | |
13172 , noData = null | |
13173 , duration = 250 | |
13174 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd') | |
13175 ; | |
13176 | |
13177 //============================================================ | |
13178 // Private Variables | |
13179 //------------------------------------------------------------ | |
13180 | |
13181 var renderWatch = nv.utils.renderWatch(dispatch); | |
13182 tooltip.headerEnabled(false).duration(0).valueFormatter(function(d, i) { | |
13183 return d; | |
13184 }); | |
13185 | |
13186 //============================================================ | |
13187 // Chart function | |
13188 //------------------------------------------------------------ | |
13189 | |
13190 function chart(selection) { | |
13191 renderWatch.reset(); | |
13192 renderWatch.models(sunburst); | |
13193 | |
13194 selection.each(function(data) { | |
13195 var container = d3.select(this); | |
13196 nv.utils.initSVG(container); | |
13197 | |
13198 var that = this; | |
13199 var availableWidth = nv.utils.availableWidth(width, container, margin), | |
13200 availableHeight = nv.utils.availableHeight(height, container, margin); | |
13201 | |
13202 chart.update = function() { | |
13203 if (duration === 0) | |
13204 container.call(chart); | |
13205 else | |
13206 container.transition().duration(duration).call(chart) | |
13207 }; | |
13208 chart.container = this; | |
13209 | |
13210 // Display No Data message if there's nothing to show. | |
13211 if (!data || !data.length) { | |
13212 nv.utils.noData(chart, container); | |
13213 return chart; | |
13214 } else { | |
13215 container.selectAll('.nv-noData').remove(); | |
13216 } | |
13217 | |
13218 // Setup containers and skeleton of chart | |
13219 var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data); | |
13220 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g'); | |
13221 var g = wrap.select('g'); | |
13222 | |
13223 gEnter.append('g').attr('class', 'nv-sunburstWrap'); | |
13224 | |
13225 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); | |
13226 | |
13227 // Main Chart Component(s) | |
13228 sunburst.width(availableWidth).height(availableHeight); | |
13229 var sunWrap = g.select('.nv-sunburstWrap').datum(data); | |
13230 d3.transition(sunWrap).call(sunburst); | |
13231 | |
13232 }); | |
13233 | |
13234 renderWatch.renderEnd('sunburstChart immediate'); | |
13235 return chart; | |
13236 } | |
13237 | |
13238 //============================================================ | |
13239 // Event Handling/Dispatching (out of chart's scope) | |
13240 //------------------------------------------------------------ | |
13241 | |
13242 sunburst.dispatch.on('elementMouseover.tooltip', function(evt) { | |
13243 evt['series'] = { | |
13244 key: evt.data.name, | |
13245 value: evt.data.size, | |
13246 color: evt.color | |
13247 }; | |
13248 tooltip.data(evt).hidden(false); | |
13249 }); | |
13250 | |
13251 sunburst.dispatch.on('elementMouseout.tooltip', function(evt) { | |
13252 tooltip.hidden(true); | |
13253 }); | |
13254 | |
13255 sunburst.dispatch.on('elementMousemove.tooltip', function(evt) { | |
13256 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})(); | |
13257 }); | |
13258 | |
13259 //============================================================ | |
13260 // Expose Public Variables | |
13261 //------------------------------------------------------------ | |
13262 | |
13263 // expose chart's sub-components | |
13264 chart.dispatch = dispatch; | |
13265 chart.sunburst = sunburst; | |
13266 chart.tooltip = tooltip; | |
13267 chart.options = nv.utils.optionsFunc.bind(chart); | |
13268 | |
13269 // use Object get/set functionality to map between vars and chart functions | |
13270 chart._options = Object.create({}, { | |
13271 // simple options, just get/set the necessary values | |
13272 noData: {get: function(){return noData;}, set: function(_){noData=_;}}, | |
13273 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}}, | |
13274 | |
13275 // options that require extra logic in the setter | |
13276 color: {get: function(){return color;}, set: function(_){ | |
13277 color = _; | |
13278 sunburst.color(color); | |
13279 }}, | |
13280 duration: {get: function(){return duration;}, set: function(_){ | |
13281 duration = _; | |
13282 renderWatch.reset(duration); | |
13283 sunburst.duration(duration); | |
13284 }}, | |
13285 margin: {get: function(){return margin;}, set: function(_){ | |
13286 margin.top = _.top !== undefined ? _.top : margin.top; | |
13287 margin.right = _.right !== undefined ? _.right : margin.right; | |
13288 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom; | |
13289 margin.left = _.left !== undefined ? _.left : margin.left; | |
13290 }} | |
13291 }); | |
13292 nv.utils.inheritOptions(chart, sunburst); | |
13293 nv.utils.initOptions(chart); | |
13294 return chart; | |
13295 }; | |
13296 | |
13297 nv.version = "1.8.1"; | |
13298 })(); |