annotate 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
rev   line source
paulo@89 1 /* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */
paulo@89 2 (function(){
paulo@89 3
paulo@89 4 // set up main nv object
paulo@89 5 var nv = {};
paulo@89 6
paulo@89 7 // the major global objects under the nv namespace
paulo@89 8 nv.dev = false; //set false when in production
paulo@89 9 nv.tooltip = nv.tooltip || {}; // For the tooltip system
paulo@89 10 nv.utils = nv.utils || {}; // Utility subsystem
paulo@89 11 nv.models = nv.models || {}; //stores all the possible models/components
paulo@89 12 nv.charts = {}; //stores all the ready to use charts
paulo@89 13 nv.logs = {}; //stores some statistics and potential error messages
paulo@89 14 nv.dom = {}; //DOM manipulation functions
paulo@89 15
paulo@89 16 nv.dispatch = d3.dispatch('render_start', 'render_end');
paulo@89 17
paulo@89 18 // Function bind polyfill
paulo@89 19 // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
paulo@89 20 // https://github.com/ariya/phantomjs/issues/10522
paulo@89 21 // http://kangax.github.io/compat-table/es5/#Function.prototype.bind
paulo@89 22 // phantomJS is used for running the test suite
paulo@89 23 if (!Function.prototype.bind) {
paulo@89 24 Function.prototype.bind = function (oThis) {
paulo@89 25 if (typeof this !== "function") {
paulo@89 26 // closest thing possible to the ECMAScript 5 internal IsCallable function
paulo@89 27 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
paulo@89 28 }
paulo@89 29
paulo@89 30 var aArgs = Array.prototype.slice.call(arguments, 1),
paulo@89 31 fToBind = this,
paulo@89 32 fNOP = function () {},
paulo@89 33 fBound = function () {
paulo@89 34 return fToBind.apply(this instanceof fNOP && oThis
paulo@89 35 ? this
paulo@89 36 : oThis,
paulo@89 37 aArgs.concat(Array.prototype.slice.call(arguments)));
paulo@89 38 };
paulo@89 39
paulo@89 40 fNOP.prototype = this.prototype;
paulo@89 41 fBound.prototype = new fNOP();
paulo@89 42 return fBound;
paulo@89 43 };
paulo@89 44 }
paulo@89 45
paulo@89 46 // Development render timers - disabled if dev = false
paulo@89 47 if (nv.dev) {
paulo@89 48 nv.dispatch.on('render_start', function(e) {
paulo@89 49 nv.logs.startTime = +new Date();
paulo@89 50 });
paulo@89 51
paulo@89 52 nv.dispatch.on('render_end', function(e) {
paulo@89 53 nv.logs.endTime = +new Date();
paulo@89 54 nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
paulo@89 55 nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
paulo@89 56 });
paulo@89 57 }
paulo@89 58
paulo@89 59 // Logs all arguments, and returns the last so you can test things in place
paulo@89 60 // Note: in IE8 console.log is an object not a function, and if modernizr is used
paulo@89 61 // then calling Function.prototype.bind with with anything other than a function
paulo@89 62 // causes a TypeError to be thrown.
paulo@89 63 nv.log = function() {
paulo@89 64 if (nv.dev && window.console && console.log && console.log.apply)
paulo@89 65 console.log.apply(console, arguments);
paulo@89 66 else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
paulo@89 67 var log = Function.prototype.bind.call(console.log, console);
paulo@89 68 log.apply(console, arguments);
paulo@89 69 }
paulo@89 70 return arguments[arguments.length - 1];
paulo@89 71 };
paulo@89 72
paulo@89 73 // print console warning, should be used by deprecated functions
paulo@89 74 nv.deprecated = function(name, info) {
paulo@89 75 if (console && console.warn) {
paulo@89 76 console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || '');
paulo@89 77 }
paulo@89 78 };
paulo@89 79
paulo@89 80 // The nv.render function is used to queue up chart rendering
paulo@89 81 // in non-blocking async functions.
paulo@89 82 // When all queued charts are done rendering, nv.dispatch.render_end is invoked.
paulo@89 83 nv.render = function render(step) {
paulo@89 84 // number of graphs to generate in each timeout loop
paulo@89 85 step = step || 1;
paulo@89 86
paulo@89 87 nv.render.active = true;
paulo@89 88 nv.dispatch.render_start();
paulo@89 89
paulo@89 90 var renderLoop = function() {
paulo@89 91 var chart, graph;
paulo@89 92
paulo@89 93 for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
paulo@89 94 chart = graph.generate();
paulo@89 95 if (typeof graph.callback == typeof(Function)) graph.callback(chart);
paulo@89 96 }
paulo@89 97
paulo@89 98 nv.render.queue.splice(0, i);
paulo@89 99
paulo@89 100 if (nv.render.queue.length) {
paulo@89 101 setTimeout(renderLoop);
paulo@89 102 }
paulo@89 103 else {
paulo@89 104 nv.dispatch.render_end();
paulo@89 105 nv.render.active = false;
paulo@89 106 }
paulo@89 107 };
paulo@89 108
paulo@89 109 setTimeout(renderLoop);
paulo@89 110 };
paulo@89 111
paulo@89 112 nv.render.active = false;
paulo@89 113 nv.render.queue = [];
paulo@89 114
paulo@89 115 /*
paulo@89 116 Adds a chart to the async rendering queue. This method can take arguments in two forms:
paulo@89 117 nv.addGraph({
paulo@89 118 generate: <Function>
paulo@89 119 callback: <Function>
paulo@89 120 })
paulo@89 121
paulo@89 122 or
paulo@89 123
paulo@89 124 nv.addGraph(<generate Function>, <callback Function>)
paulo@89 125
paulo@89 126 The generate function should contain code that creates the NVD3 model, sets options
paulo@89 127 on it, adds data to an SVG element, and invokes the chart model. The generate function
paulo@89 128 should return the chart model. See examples/lineChart.html for a usage example.
paulo@89 129
paulo@89 130 The callback function is optional, and it is called when the generate function completes.
paulo@89 131 */
paulo@89 132 nv.addGraph = function(obj) {
paulo@89 133 if (typeof arguments[0] === typeof(Function)) {
paulo@89 134 obj = {generate: arguments[0], callback: arguments[1]};
paulo@89 135 }
paulo@89 136
paulo@89 137 nv.render.queue.push(obj);
paulo@89 138
paulo@89 139 if (!nv.render.active) {
paulo@89 140 nv.render();
paulo@89 141 }
paulo@89 142 };
paulo@89 143
paulo@89 144 // Node/CommonJS exports
paulo@89 145 if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
paulo@89 146 module.exports = nv;
paulo@89 147 }
paulo@89 148
paulo@89 149 if (typeof(window) !== 'undefined') {
paulo@89 150 window.nv = nv;
paulo@89 151 }
paulo@89 152 /* Facade for queueing DOM write operations
paulo@89 153 * with Fastdom (https://github.com/wilsonpage/fastdom)
paulo@89 154 * if available.
paulo@89 155 * This could easily be extended to support alternate
paulo@89 156 * implementations in the future.
paulo@89 157 */
paulo@89 158 nv.dom.write = function(callback) {
paulo@89 159 if (window.fastdom !== undefined) {
paulo@89 160 return fastdom.write(callback);
paulo@89 161 }
paulo@89 162 return callback();
paulo@89 163 };
paulo@89 164
paulo@89 165 /* Facade for queueing DOM read operations
paulo@89 166 * with Fastdom (https://github.com/wilsonpage/fastdom)
paulo@89 167 * if available.
paulo@89 168 * This could easily be extended to support alternate
paulo@89 169 * implementations in the future.
paulo@89 170 */
paulo@89 171 nv.dom.read = function(callback) {
paulo@89 172 if (window.fastdom !== undefined) {
paulo@89 173 return fastdom.read(callback);
paulo@89 174 }
paulo@89 175 return callback();
paulo@89 176 };/* Utility class to handle creation of an interactive layer.
paulo@89 177 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
paulo@89 178 containing the X-coordinate. It can also render a vertical line where the mouse is located.
paulo@89 179
paulo@89 180 dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
paulo@89 181 the rectangle. The dispatch is given one object which contains the mouseX/Y location.
paulo@89 182 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
paulo@89 183 */
paulo@89 184 nv.interactiveGuideline = function() {
paulo@89 185 "use strict";
paulo@89 186
paulo@89 187 var tooltip = nv.models.tooltip();
paulo@89 188 tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false);
paulo@89 189
paulo@89 190 //Public settings
paulo@89 191 var width = null;
paulo@89 192 var height = null;
paulo@89 193
paulo@89 194 //Please pass in the bounding chart's top and left margins
paulo@89 195 //This is important for calculating the correct mouseX/Y positions.
paulo@89 196 var margin = {left: 0, top: 0}
paulo@89 197 , xScale = d3.scale.linear()
paulo@89 198 , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick')
paulo@89 199 , showGuideLine = true;
paulo@89 200 //Must pass in the bounding chart's <svg> container.
paulo@89 201 //The mousemove event is attached to this container.
paulo@89 202 var svgContainer = null;
paulo@89 203
paulo@89 204 // check if IE by looking for activeX
paulo@89 205 var isMSIE = "ActiveXObject" in window;
paulo@89 206
paulo@89 207
paulo@89 208 function layer(selection) {
paulo@89 209 selection.each(function(data) {
paulo@89 210 var container = d3.select(this);
paulo@89 211 var availableWidth = (width || 960), availableHeight = (height || 400);
paulo@89 212 var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
paulo@89 213 .data([data]);
paulo@89 214 var wrapEnter = wrap.enter()
paulo@89 215 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
paulo@89 216 wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
paulo@89 217
paulo@89 218 if (!svgContainer) {
paulo@89 219 return;
paulo@89 220 }
paulo@89 221
paulo@89 222 function mouseHandler() {
paulo@89 223 var d3mouse = d3.mouse(this);
paulo@89 224 var mouseX = d3mouse[0];
paulo@89 225 var mouseY = d3mouse[1];
paulo@89 226 var subtractMargin = true;
paulo@89 227 var mouseOutAnyReason = false;
paulo@89 228 if (isMSIE) {
paulo@89 229 /*
paulo@89 230 D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
paulo@89 231 d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
paulo@89 232 over a rect in IE 10.
paulo@89 233 However, d3.event.offsetX/Y also returns the mouse coordinates
paulo@89 234 relative to the triggering <rect>. So we use offsetX/Y on IE.
paulo@89 235 */
paulo@89 236 mouseX = d3.event.offsetX;
paulo@89 237 mouseY = d3.event.offsetY;
paulo@89 238
paulo@89 239 /*
paulo@89 240 On IE, if you attach a mouse event listener to the <svg> container,
paulo@89 241 it will actually trigger it for all the child elements (like <path>, <circle>, etc).
paulo@89 242 When this happens on IE, the offsetX/Y is set to where ever the child element
paulo@89 243 is located.
paulo@89 244 As a result, we do NOT need to subtract margins to figure out the mouse X/Y
paulo@89 245 position under this scenario. Removing the line below *will* cause
paulo@89 246 the interactive layer to not work right on IE.
paulo@89 247 */
paulo@89 248 if(d3.event.target.tagName !== "svg") {
paulo@89 249 subtractMargin = false;
paulo@89 250 }
paulo@89 251
paulo@89 252 if (d3.event.target.className.baseVal.match("nv-legend")) {
paulo@89 253 mouseOutAnyReason = true;
paulo@89 254 }
paulo@89 255
paulo@89 256 }
paulo@89 257
paulo@89 258 if(subtractMargin) {
paulo@89 259 mouseX -= margin.left;
paulo@89 260 mouseY -= margin.top;
paulo@89 261 }
paulo@89 262
paulo@89 263 /* If mouseX/Y is outside of the chart's bounds,
paulo@89 264 trigger a mouseOut event.
paulo@89 265 */
paulo@89 266 if (mouseX < 0 || mouseY < 0
paulo@89 267 || mouseX > availableWidth || mouseY > availableHeight
paulo@89 268 || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
paulo@89 269 || mouseOutAnyReason
paulo@89 270 ) {
paulo@89 271
paulo@89 272 if (isMSIE) {
paulo@89 273 if (d3.event.relatedTarget
paulo@89 274 && d3.event.relatedTarget.ownerSVGElement === undefined
paulo@89 275 && (d3.event.relatedTarget.className === undefined
paulo@89 276 || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) {
paulo@89 277
paulo@89 278 return;
paulo@89 279 }
paulo@89 280 }
paulo@89 281 dispatch.elementMouseout({
paulo@89 282 mouseX: mouseX,
paulo@89 283 mouseY: mouseY
paulo@89 284 });
paulo@89 285 layer.renderGuideLine(null); //hide the guideline
paulo@89 286 tooltip.hidden(true);
paulo@89 287 return;
paulo@89 288 } else {
paulo@89 289 tooltip.hidden(false);
paulo@89 290 }
paulo@89 291
paulo@89 292 var pointXValue = xScale.invert(mouseX);
paulo@89 293 dispatch.elementMousemove({
paulo@89 294 mouseX: mouseX,
paulo@89 295 mouseY: mouseY,
paulo@89 296 pointXValue: pointXValue
paulo@89 297 });
paulo@89 298
paulo@89 299 //If user double clicks the layer, fire a elementDblclick
paulo@89 300 if (d3.event.type === "dblclick") {
paulo@89 301 dispatch.elementDblclick({
paulo@89 302 mouseX: mouseX,
paulo@89 303 mouseY: mouseY,
paulo@89 304 pointXValue: pointXValue
paulo@89 305 });
paulo@89 306 }
paulo@89 307
paulo@89 308 // if user single clicks the layer, fire elementClick
paulo@89 309 if (d3.event.type === 'click') {
paulo@89 310 dispatch.elementClick({
paulo@89 311 mouseX: mouseX,
paulo@89 312 mouseY: mouseY,
paulo@89 313 pointXValue: pointXValue
paulo@89 314 });
paulo@89 315 }
paulo@89 316 }
paulo@89 317
paulo@89 318 svgContainer
paulo@89 319 .on("touchmove",mouseHandler)
paulo@89 320 .on("mousemove",mouseHandler, true)
paulo@89 321 .on("mouseout" ,mouseHandler,true)
paulo@89 322 .on("dblclick" ,mouseHandler)
paulo@89 323 .on("click", mouseHandler)
paulo@89 324 ;
paulo@89 325
paulo@89 326 layer.guideLine = null;
paulo@89 327 //Draws a vertical guideline at the given X postion.
paulo@89 328 layer.renderGuideLine = function(x) {
paulo@89 329 if (!showGuideLine) return;
paulo@89 330 if (layer.guideLine && layer.guideLine.attr("x1") === x) return;
paulo@89 331 nv.dom.write(function() {
paulo@89 332 var line = wrap.select(".nv-interactiveGuideLine")
paulo@89 333 .selectAll("line")
paulo@89 334 .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
paulo@89 335 line.enter()
paulo@89 336 .append("line")
paulo@89 337 .attr("class", "nv-guideline")
paulo@89 338 .attr("x1", function(d) { return d;})
paulo@89 339 .attr("x2", function(d) { return d;})
paulo@89 340 .attr("y1", availableHeight)
paulo@89 341 .attr("y2",0);
paulo@89 342 line.exit().remove();
paulo@89 343 });
paulo@89 344 }
paulo@89 345 });
paulo@89 346 }
paulo@89 347
paulo@89 348 layer.dispatch = dispatch;
paulo@89 349 layer.tooltip = tooltip;
paulo@89 350
paulo@89 351 layer.margin = function(_) {
paulo@89 352 if (!arguments.length) return margin;
paulo@89 353 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
paulo@89 354 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
paulo@89 355 return layer;
paulo@89 356 };
paulo@89 357
paulo@89 358 layer.width = function(_) {
paulo@89 359 if (!arguments.length) return width;
paulo@89 360 width = _;
paulo@89 361 return layer;
paulo@89 362 };
paulo@89 363
paulo@89 364 layer.height = function(_) {
paulo@89 365 if (!arguments.length) return height;
paulo@89 366 height = _;
paulo@89 367 return layer;
paulo@89 368 };
paulo@89 369
paulo@89 370 layer.xScale = function(_) {
paulo@89 371 if (!arguments.length) return xScale;
paulo@89 372 xScale = _;
paulo@89 373 return layer;
paulo@89 374 };
paulo@89 375
paulo@89 376 layer.showGuideLine = function(_) {
paulo@89 377 if (!arguments.length) return showGuideLine;
paulo@89 378 showGuideLine = _;
paulo@89 379 return layer;
paulo@89 380 };
paulo@89 381
paulo@89 382 layer.svgContainer = function(_) {
paulo@89 383 if (!arguments.length) return svgContainer;
paulo@89 384 svgContainer = _;
paulo@89 385 return layer;
paulo@89 386 };
paulo@89 387
paulo@89 388 return layer;
paulo@89 389 };
paulo@89 390
paulo@89 391 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
paulo@89 392 This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
paulo@89 393
paulo@89 394 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
paulo@89 395 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
paulo@89 396 because 28 is closer to 30 than 10.
paulo@89 397
paulo@89 398 Unit tests can be found in: interactiveBisectTest.html
paulo@89 399
paulo@89 400 Has the following known issues:
paulo@89 401 * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
paulo@89 402 * Won't work if there are duplicate x coordinate values.
paulo@89 403 */
paulo@89 404 nv.interactiveBisect = function (values, searchVal, xAccessor) {
paulo@89 405 "use strict";
paulo@89 406 if (! (values instanceof Array)) {
paulo@89 407 return null;
paulo@89 408 }
paulo@89 409 var _xAccessor;
paulo@89 410 if (typeof xAccessor !== 'function') {
paulo@89 411 _xAccessor = function(d) {
paulo@89 412 return d.x;
paulo@89 413 }
paulo@89 414 } else {
paulo@89 415 _xAccessor = xAccessor;
paulo@89 416 }
paulo@89 417 var _cmp = function(d, v) {
paulo@89 418 // Accessors are no longer passed the index of the element along with
paulo@89 419 // the element itself when invoked by d3.bisector.
paulo@89 420 //
paulo@89 421 // Starting at D3 v3.4.4, d3.bisector() started inspecting the
paulo@89 422 // function passed to determine if it should consider it an accessor
paulo@89 423 // or a comparator. This meant that accessors that take two arguments
paulo@89 424 // (expecting an index as the second parameter) are treated as
paulo@89 425 // comparators where the second argument is the search value against
paulo@89 426 // which the first argument is compared.
paulo@89 427 return _xAccessor(d) - v;
paulo@89 428 };
paulo@89 429
paulo@89 430 var bisect = d3.bisector(_cmp).left;
paulo@89 431 var index = d3.max([0, bisect(values,searchVal) - 1]);
paulo@89 432 var currentValue = _xAccessor(values[index]);
paulo@89 433
paulo@89 434 if (typeof currentValue === 'undefined') {
paulo@89 435 currentValue = index;
paulo@89 436 }
paulo@89 437
paulo@89 438 if (currentValue === searchVal) {
paulo@89 439 return index; //found exact match
paulo@89 440 }
paulo@89 441
paulo@89 442 var nextIndex = d3.min([index+1, values.length - 1]);
paulo@89 443 var nextValue = _xAccessor(values[nextIndex]);
paulo@89 444
paulo@89 445 if (typeof nextValue === 'undefined') {
paulo@89 446 nextValue = nextIndex;
paulo@89 447 }
paulo@89 448
paulo@89 449 if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
paulo@89 450 return index;
paulo@89 451 } else {
paulo@89 452 return nextIndex
paulo@89 453 }
paulo@89 454 };
paulo@89 455
paulo@89 456 /*
paulo@89 457 Returns the index in the array "values" that is closest to searchVal.
paulo@89 458 Only returns an index if searchVal is within some "threshold".
paulo@89 459 Otherwise, returns null.
paulo@89 460 */
paulo@89 461 nv.nearestValueIndex = function (values, searchVal, threshold) {
paulo@89 462 "use strict";
paulo@89 463 var yDistMax = Infinity, indexToHighlight = null;
paulo@89 464 values.forEach(function(d,i) {
paulo@89 465 var delta = Math.abs(searchVal - d);
paulo@89 466 if ( d != null && delta <= yDistMax && delta < threshold) {
paulo@89 467 yDistMax = delta;
paulo@89 468 indexToHighlight = i;
paulo@89 469 }
paulo@89 470 });
paulo@89 471 return indexToHighlight;
paulo@89 472 };
paulo@89 473 /* Tooltip rendering model for nvd3 charts.
paulo@89 474 window.nv.models.tooltip is the updated,new way to render tooltips.
paulo@89 475
paulo@89 476 window.nv.tooltip.show is the old tooltip code.
paulo@89 477 window.nv.tooltip.* also has various helper methods.
paulo@89 478 */
paulo@89 479 (function() {
paulo@89 480 "use strict";
paulo@89 481
paulo@89 482 /* Model which can be instantiated to handle tooltip rendering.
paulo@89 483 Example usage:
paulo@89 484 var tip = nv.models.tooltip().gravity('w').distance(23)
paulo@89 485 .data(myDataObject);
paulo@89 486
paulo@89 487 tip(); //just invoke the returned function to render tooltip.
paulo@89 488 */
paulo@89 489 nv.models.tooltip = function() {
paulo@89 490
paulo@89 491 /*
paulo@89 492 Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
paulo@89 493 Example Format of data:
paulo@89 494 {
paulo@89 495 key: "Date",
paulo@89 496 value: "August 2009",
paulo@89 497 series: [
paulo@89 498 {key: "Series 1", value: "Value 1", color: "#000"},
paulo@89 499 {key: "Series 2", value: "Value 2", color: "#00f"}
paulo@89 500 ]
paulo@89 501 }
paulo@89 502 */
paulo@89 503 var data = null;
paulo@89 504 var gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned.
paulo@89 505 , distance = 25 //Distance to offset tooltip from the mouse location.
paulo@89 506 , snapDistance = 0 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
paulo@89 507 , fixedTop = null //If not null, this fixes the top position of the tooltip.
paulo@89 508 , classes = null //Attaches additional CSS classes to the tooltip DIV that is created.
paulo@89 509 , chartContainer = null //Parent dom element of the SVG that holds the chart.
paulo@89 510 , hidden = true // start off hidden, toggle with hide/show functions below
paulo@89 511 , hideDelay = 400 // delay before the tooltip hides after calling hide()
paulo@89 512 , tooltip = null // d3 select of tooltipElem below
paulo@89 513 , tooltipElem = null //actual DOM element representing the tooltip.
paulo@89 514 , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
paulo@89 515 , offset = {left: 0, top: 0} //Offset of tooltip against the pointer
paulo@89 516 , enabled = true //True -> tooltips are rendered. False -> don't render tooltips.
paulo@89 517 , duration = 100 // duration for tooltip movement
paulo@89 518 , headerEnabled = true
paulo@89 519 ;
paulo@89 520
paulo@89 521 // set to true by interactive layer to adjust tooltip positions
paulo@89 522 // eventually we should probably fix interactive layer to get the position better.
paulo@89 523 // for now this is needed if you want to set chartContainer for normal tooltips, else it "fixes" it to broken
paulo@89 524 var isInteractiveLayer = false;
paulo@89 525
paulo@89 526 //Generates a unique id when you create a new tooltip() object
paulo@89 527 var id = "nvtooltip-" + Math.floor(Math.random() * 100000);
paulo@89 528
paulo@89 529 //CSS class to specify whether element should not have mouse events.
paulo@89 530 var nvPointerEventsClass = "nv-pointer-events-none";
paulo@89 531
paulo@89 532 //Format function for the tooltip values column
paulo@89 533 var valueFormatter = function(d,i) {
paulo@89 534 return d;
paulo@89 535 };
paulo@89 536
paulo@89 537 //Format function for the tooltip header value.
paulo@89 538 var headerFormatter = function(d) {
paulo@89 539 return d;
paulo@89 540 };
paulo@89 541
paulo@89 542 var keyFormatter = function(d, i) {
paulo@89 543 return d;
paulo@89 544 };
paulo@89 545
paulo@89 546 //By default, the tooltip model renders a beautiful table inside a DIV.
paulo@89 547 //You can override this function if a custom tooltip is desired.
paulo@89 548 var contentGenerator = function(d) {
paulo@89 549 if (d === null) {
paulo@89 550 return '';
paulo@89 551 }
paulo@89 552
paulo@89 553 var table = d3.select(document.createElement("table"));
paulo@89 554 if (headerEnabled) {
paulo@89 555 var theadEnter = table.selectAll("thead")
paulo@89 556 .data([d])
paulo@89 557 .enter().append("thead");
paulo@89 558
paulo@89 559 theadEnter.append("tr")
paulo@89 560 .append("td")
paulo@89 561 .attr("colspan", 3)
paulo@89 562 .append("strong")
paulo@89 563 .classed("x-value", true)
paulo@89 564 .html(headerFormatter(d.value));
paulo@89 565 }
paulo@89 566
paulo@89 567 var tbodyEnter = table.selectAll("tbody")
paulo@89 568 .data([d])
paulo@89 569 .enter().append("tbody");
paulo@89 570
paulo@89 571 var trowEnter = tbodyEnter.selectAll("tr")
paulo@89 572 .data(function(p) { return p.series})
paulo@89 573 .enter()
paulo@89 574 .append("tr")
paulo@89 575 .classed("highlight", function(p) { return p.highlight});
paulo@89 576
paulo@89 577 trowEnter.append("td")
paulo@89 578 .classed("legend-color-guide",true)
paulo@89 579 .append("div")
paulo@89 580 .style("background-color", function(p) { return p.color});
paulo@89 581
paulo@89 582 trowEnter.append("td")
paulo@89 583 .classed("key",true)
paulo@89 584 .html(function(p, i) {return keyFormatter(p.key, i)});
paulo@89 585
paulo@89 586 trowEnter.append("td")
paulo@89 587 .classed("value",true)
paulo@89 588 .html(function(p, i) { return valueFormatter(p.value, i) });
paulo@89 589
paulo@89 590
paulo@89 591 trowEnter.selectAll("td").each(function(p) {
paulo@89 592 if (p.highlight) {
paulo@89 593 var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
paulo@89 594 var opacity = 0.6;
paulo@89 595 d3.select(this)
paulo@89 596 .style("border-bottom-color", opacityScale(opacity))
paulo@89 597 .style("border-top-color", opacityScale(opacity))
paulo@89 598 ;
paulo@89 599 }
paulo@89 600 });
paulo@89 601
paulo@89 602 var html = table.node().outerHTML;
paulo@89 603 if (d.footer !== undefined)
paulo@89 604 html += "<div class='footer'>" + d.footer + "</div>";
paulo@89 605 return html;
paulo@89 606
paulo@89 607 };
paulo@89 608
paulo@89 609 var dataSeriesExists = function(d) {
paulo@89 610 if (d && d.series) {
paulo@89 611 if (d.series instanceof Array) {
paulo@89 612 return !!d.series.length;
paulo@89 613 }
paulo@89 614 // if object, it's okay just convert to array of the object
paulo@89 615 if (d.series instanceof Object) {
paulo@89 616 d.series = [d.series];
paulo@89 617 return true;
paulo@89 618 }
paulo@89 619 }
paulo@89 620 return false;
paulo@89 621 };
paulo@89 622
paulo@89 623 var calcTooltipPosition = function(pos) {
paulo@89 624 if (!tooltipElem) return;
paulo@89 625
paulo@89 626 nv.dom.read(function() {
paulo@89 627 var height = parseInt(tooltipElem.offsetHeight, 10),
paulo@89 628 width = parseInt(tooltipElem.offsetWidth, 10),
paulo@89 629 windowWidth = nv.utils.windowSize().width,
paulo@89 630 windowHeight = nv.utils.windowSize().height,
paulo@89 631 scrollTop = window.pageYOffset,
paulo@89 632 scrollLeft = window.pageXOffset,
paulo@89 633 left, top;
paulo@89 634
paulo@89 635 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
paulo@89 636 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
paulo@89 637
paulo@89 638
paulo@89 639 //Helper functions to find the total offsets of a given DOM element.
paulo@89 640 //Looks up the entire ancestry of an element, up to the first relatively positioned element.
paulo@89 641 var tooltipTop = function ( Elem ) {
paulo@89 642 var offsetTop = top;
paulo@89 643 do {
paulo@89 644 if( !isNaN( Elem.offsetTop ) ) {
paulo@89 645 offsetTop += (Elem.offsetTop);
paulo@89 646 }
paulo@89 647 Elem = Elem.offsetParent;
paulo@89 648 } while( Elem );
paulo@89 649 return offsetTop;
paulo@89 650 };
paulo@89 651 var tooltipLeft = function ( Elem ) {
paulo@89 652 var offsetLeft = left;
paulo@89 653 do {
paulo@89 654 if( !isNaN( Elem.offsetLeft ) ) {
paulo@89 655 offsetLeft += (Elem.offsetLeft);
paulo@89 656 }
paulo@89 657 Elem = Elem.offsetParent;
paulo@89 658 } while( Elem );
paulo@89 659 return offsetLeft;
paulo@89 660 };
paulo@89 661
paulo@89 662 // calculate position based on gravity
paulo@89 663 var tLeft, tTop;
paulo@89 664 switch (gravity) {
paulo@89 665 case 'e':
paulo@89 666 left = pos[0] - width - distance;
paulo@89 667 top = pos[1] - (height / 2);
paulo@89 668 tLeft = tooltipLeft(tooltipElem);
paulo@89 669 tTop = tooltipTop(tooltipElem);
paulo@89 670 if (tLeft < scrollLeft) left = pos[0] + distance > scrollLeft ? pos[0] + distance : scrollLeft - tLeft + left;
paulo@89 671 if (tTop < scrollTop) top = scrollTop - tTop + top;
paulo@89 672 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
paulo@89 673 break;
paulo@89 674 case 'w':
paulo@89 675 left = pos[0] + distance;
paulo@89 676 top = pos[1] - (height / 2);
paulo@89 677 tLeft = tooltipLeft(tooltipElem);
paulo@89 678 tTop = tooltipTop(tooltipElem);
paulo@89 679 if (tLeft + width > windowWidth) left = pos[0] - width - distance;
paulo@89 680 if (tTop < scrollTop) top = scrollTop + 5;
paulo@89 681 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
paulo@89 682 break;
paulo@89 683 case 'n':
paulo@89 684 left = pos[0] - (width / 2) - 5;
paulo@89 685 top = pos[1] + distance;
paulo@89 686 tLeft = tooltipLeft(tooltipElem);
paulo@89 687 tTop = tooltipTop(tooltipElem);
paulo@89 688 if (tLeft < scrollLeft) left = scrollLeft + 5;
paulo@89 689 if (tLeft + width > windowWidth) left = left - width/2 + 5;
paulo@89 690 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
paulo@89 691 break;
paulo@89 692 case 's':
paulo@89 693 left = pos[0] - (width / 2);
paulo@89 694 top = pos[1] - height - distance;
paulo@89 695 tLeft = tooltipLeft(tooltipElem);
paulo@89 696 tTop = tooltipTop(tooltipElem);
paulo@89 697 if (tLeft < scrollLeft) left = scrollLeft + 5;
paulo@89 698 if (tLeft + width > windowWidth) left = left - width/2 + 5;
paulo@89 699 if (scrollTop > tTop) top = scrollTop;
paulo@89 700 break;
paulo@89 701 case 'none':
paulo@89 702 left = pos[0];
paulo@89 703 top = pos[1] - distance;
paulo@89 704 tLeft = tooltipLeft(tooltipElem);
paulo@89 705 tTop = tooltipTop(tooltipElem);
paulo@89 706 break;
paulo@89 707 }
paulo@89 708
paulo@89 709 // adjust tooltip offsets
paulo@89 710 left -= offset.left;
paulo@89 711 top -= offset.top;
paulo@89 712
paulo@89 713 // using tooltip.style('transform') returns values un-usable for tween
paulo@89 714 var box = tooltipElem.getBoundingClientRect();
paulo@89 715 var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
paulo@89 716 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
paulo@89 717 var old_translate = 'translate(' + (box.left + scrollLeft) + 'px, ' + (box.top + scrollTop) + 'px)';
paulo@89 718 var new_translate = 'translate(' + left + 'px, ' + top + 'px)';
paulo@89 719 var translateInterpolator = d3.interpolateString(old_translate, new_translate);
paulo@89 720
paulo@89 721 var is_hidden = tooltip.style('opacity') < 0.1;
paulo@89 722
paulo@89 723 // delay hiding a bit to avoid flickering
paulo@89 724 if (hidden) {
paulo@89 725 tooltip
paulo@89 726 .transition()
paulo@89 727 .delay(hideDelay)
paulo@89 728 .duration(0)
paulo@89 729 .style('opacity', 0);
paulo@89 730 } else {
paulo@89 731 tooltip
paulo@89 732 .interrupt() // cancel running transitions
paulo@89 733 .transition()
paulo@89 734 .duration(is_hidden ? 0 : duration)
paulo@89 735 // using tween since some versions of d3 can't auto-tween a translate on a div
paulo@89 736 .styleTween('transform', function (d) {
paulo@89 737 return translateInterpolator;
paulo@89 738 }, 'important')
paulo@89 739 // Safari has its own `-webkit-transform` and does not support `transform`
paulo@89 740 // transform tooltip without transition only in Safari
paulo@89 741 .style('-webkit-transform', new_translate)
paulo@89 742 .style('opacity', 1);
paulo@89 743 }
paulo@89 744
paulo@89 745
paulo@89 746
paulo@89 747 });
paulo@89 748 };
paulo@89 749
paulo@89 750 //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
paulo@89 751 function convertViewBoxRatio() {
paulo@89 752 if (chartContainer) {
paulo@89 753 var svg = d3.select(chartContainer);
paulo@89 754 if (svg.node().tagName !== "svg") {
paulo@89 755 svg = svg.select("svg");
paulo@89 756 }
paulo@89 757 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
paulo@89 758 if (viewBox) {
paulo@89 759 viewBox = viewBox.split(' ');
paulo@89 760 var ratio = parseInt(svg.style('width'), 10) / viewBox[2];
paulo@89 761
paulo@89 762 position.left = position.left * ratio;
paulo@89 763 position.top = position.top * ratio;
paulo@89 764 }
paulo@89 765 }
paulo@89 766 }
paulo@89 767
paulo@89 768 //Creates new tooltip container, or uses existing one on DOM.
paulo@89 769 function initTooltip() {
paulo@89 770 if (!tooltip) {
paulo@89 771 var body;
paulo@89 772 if (chartContainer) {
paulo@89 773 body = chartContainer;
paulo@89 774 } else {
paulo@89 775 body = document.body;
paulo@89 776 }
paulo@89 777 //Create new tooltip div if it doesn't exist on DOM.
paulo@89 778 tooltip = d3.select(body).append("div")
paulo@89 779 .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
paulo@89 780 .attr("id", id);
paulo@89 781 tooltip.style("top", 0).style("left", 0);
paulo@89 782 tooltip.style('opacity', 0);
paulo@89 783 tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true);
paulo@89 784 tooltip.classed(nvPointerEventsClass, true);
paulo@89 785 tooltipElem = tooltip.node();
paulo@89 786 }
paulo@89 787 }
paulo@89 788
paulo@89 789 //Draw the tooltip onto the DOM.
paulo@89 790 function nvtooltip() {
paulo@89 791 if (!enabled) return;
paulo@89 792 if (!dataSeriesExists(data)) return;
paulo@89 793
paulo@89 794 convertViewBoxRatio();
paulo@89 795
paulo@89 796 var left = position.left;
paulo@89 797 var top = (fixedTop !== null) ? fixedTop : position.top;
paulo@89 798
paulo@89 799 nv.dom.write(function () {
paulo@89 800 initTooltip();
paulo@89 801 // generate data and set it into tooltip
paulo@89 802 // Bonus - If you override contentGenerator and return falsey you can use something like
paulo@89 803 // React or Knockout to bind the data for your tooltip
paulo@89 804 var newContent = contentGenerator(data);
paulo@89 805 if (newContent) {
paulo@89 806 tooltipElem.innerHTML = newContent;
paulo@89 807 }
paulo@89 808
paulo@89 809 if (chartContainer && isInteractiveLayer) {
paulo@89 810 nv.dom.read(function() {
paulo@89 811 var svgComp = chartContainer.getElementsByTagName("svg")[0];
paulo@89 812 var svgOffset = {left:0,top:0};
paulo@89 813 if (svgComp) {
paulo@89 814 var svgBound = svgComp.getBoundingClientRect();
paulo@89 815 var chartBound = chartContainer.getBoundingClientRect();
paulo@89 816 var svgBoundTop = svgBound.top;
paulo@89 817
paulo@89 818 //Defensive code. Sometimes, svgBoundTop can be a really negative
paulo@89 819 // number, like -134254. That's a bug.
paulo@89 820 // If such a number is found, use zero instead. FireFox bug only
paulo@89 821 if (svgBoundTop < 0) {
paulo@89 822 var containerBound = chartContainer.getBoundingClientRect();
paulo@89 823 svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
paulo@89 824 }
paulo@89 825 svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
paulo@89 826 svgOffset.left = Math.abs(svgBound.left - chartBound.left);
paulo@89 827 }
paulo@89 828 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
paulo@89 829 //You need to also add any offset between the <svg> element and its containing <div>
paulo@89 830 //Finally, add any offset of the containing <div> on the whole page.
paulo@89 831 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
paulo@89 832 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
paulo@89 833
paulo@89 834 if (snapDistance && snapDistance > 0) {
paulo@89 835 top = Math.floor(top/snapDistance) * snapDistance;
paulo@89 836 }
paulo@89 837 calcTooltipPosition([left,top]);
paulo@89 838 });
paulo@89 839 } else {
paulo@89 840 calcTooltipPosition([left,top]);
paulo@89 841 }
paulo@89 842 });
paulo@89 843
paulo@89 844 return nvtooltip;
paulo@89 845 }
paulo@89 846
paulo@89 847 nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
paulo@89 848 nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip);
paulo@89 849
paulo@89 850 nvtooltip._options = Object.create({}, {
paulo@89 851 // simple read/write options
paulo@89 852 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
paulo@89 853 gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
paulo@89 854 distance: {get: function(){return distance;}, set: function(_){distance=_;}},
paulo@89 855 snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}},
paulo@89 856 classes: {get: function(){return classes;}, set: function(_){classes=_;}},
paulo@89 857 chartContainer: {get: function(){return chartContainer;}, set: function(_){chartContainer=_;}},
paulo@89 858 fixedTop: {get: function(){return fixedTop;}, set: function(_){fixedTop=_;}},
paulo@89 859 enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}},
paulo@89 860 hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}},
paulo@89 861 contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}},
paulo@89 862 valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}},
paulo@89 863 headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}},
paulo@89 864 keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
paulo@89 865 headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}},
paulo@89 866
paulo@89 867 // internal use only, set by interactive layer to adjust position.
paulo@89 868 _isInteractiveLayer: {get: function(){return isInteractiveLayer;}, set: function(_){isInteractiveLayer=!!_;}},
paulo@89 869
paulo@89 870 // options with extra logic
paulo@89 871 position: {get: function(){return position;}, set: function(_){
paulo@89 872 position.left = _.left !== undefined ? _.left : position.left;
paulo@89 873 position.top = _.top !== undefined ? _.top : position.top;
paulo@89 874 }},
paulo@89 875 offset: {get: function(){return offset;}, set: function(_){
paulo@89 876 offset.left = _.left !== undefined ? _.left : offset.left;
paulo@89 877 offset.top = _.top !== undefined ? _.top : offset.top;
paulo@89 878 }},
paulo@89 879 hidden: {get: function(){return hidden;}, set: function(_){
paulo@89 880 if (hidden != _) {
paulo@89 881 hidden = !!_;
paulo@89 882 nvtooltip();
paulo@89 883 }
paulo@89 884 }},
paulo@89 885 data: {get: function(){return data;}, set: function(_){
paulo@89 886 // if showing a single data point, adjust data format with that
paulo@89 887 if (_.point) {
paulo@89 888 _.value = _.point.x;
paulo@89 889 _.series = _.series || {};
paulo@89 890 _.series.value = _.point.y;
paulo@89 891 _.series.color = _.point.color || _.series.color;
paulo@89 892 }
paulo@89 893 data = _;
paulo@89 894 }},
paulo@89 895
paulo@89 896 // read only properties
paulo@89 897 tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}},
paulo@89 898 id: {get: function(){return id;}, set: function(_){}}
paulo@89 899 });
paulo@89 900
paulo@89 901 nv.utils.initOptions(nvtooltip);
paulo@89 902 return nvtooltip;
paulo@89 903 };
paulo@89 904
paulo@89 905 })();
paulo@89 906
paulo@89 907
paulo@89 908 /*
paulo@89 909 Gets the browser window size
paulo@89 910
paulo@89 911 Returns object with height and width properties
paulo@89 912 */
paulo@89 913 nv.utils.windowSize = function() {
paulo@89 914 // Sane defaults
paulo@89 915 var size = {width: 640, height: 480};
paulo@89 916
paulo@89 917 // Most recent browsers use
paulo@89 918 if (window.innerWidth && window.innerHeight) {
paulo@89 919 size.width = window.innerWidth;
paulo@89 920 size.height = window.innerHeight;
paulo@89 921 return (size);
paulo@89 922 }
paulo@89 923
paulo@89 924 // IE can use depending on mode it is in
paulo@89 925 if (document.compatMode=='CSS1Compat' &&
paulo@89 926 document.documentElement &&
paulo@89 927 document.documentElement.offsetWidth ) {
paulo@89 928
paulo@89 929 size.width = document.documentElement.offsetWidth;
paulo@89 930 size.height = document.documentElement.offsetHeight;
paulo@89 931 return (size);
paulo@89 932 }
paulo@89 933
paulo@89 934 // Earlier IE uses Doc.body
paulo@89 935 if (document.body && document.body.offsetWidth) {
paulo@89 936 size.width = document.body.offsetWidth;
paulo@89 937 size.height = document.body.offsetHeight;
paulo@89 938 return (size);
paulo@89 939 }
paulo@89 940
paulo@89 941 return (size);
paulo@89 942 };
paulo@89 943
paulo@89 944 /*
paulo@89 945 Binds callback function to run when window is resized
paulo@89 946 */
paulo@89 947 nv.utils.windowResize = function(handler) {
paulo@89 948 if (window.addEventListener) {
paulo@89 949 window.addEventListener('resize', handler);
paulo@89 950 } else {
paulo@89 951 nv.log("ERROR: Failed to bind to window.resize with: ", handler);
paulo@89 952 }
paulo@89 953 // return object with clear function to remove the single added callback.
paulo@89 954 return {
paulo@89 955 callback: handler,
paulo@89 956 clear: function() {
paulo@89 957 window.removeEventListener('resize', handler);
paulo@89 958 }
paulo@89 959 }
paulo@89 960 };
paulo@89 961
paulo@89 962
paulo@89 963 /*
paulo@89 964 Backwards compatible way to implement more d3-like coloring of graphs.
paulo@89 965 Can take in nothing, an array, or a function/scale
paulo@89 966 To use a normal scale, get the range and pass that because we must be able
paulo@89 967 to take two arguments and use the index to keep backward compatibility
paulo@89 968 */
paulo@89 969 nv.utils.getColor = function(color) {
paulo@89 970 //if you pass in nothing, get default colors back
paulo@89 971 if (color === undefined) {
paulo@89 972 return nv.utils.defaultColor();
paulo@89 973
paulo@89 974 //if passed an array, turn it into a color scale
paulo@89 975 // use isArray, instanceof fails if d3 range is created in an iframe
paulo@89 976 } else if(Array.isArray(color)) {
paulo@89 977 var color_scale = d3.scale.ordinal().range(color);
paulo@89 978 return function(d, i) {
paulo@89 979 var key = i === undefined ? d : i;
paulo@89 980 return d.color || color_scale(key);
paulo@89 981 };
paulo@89 982
paulo@89 983 //if passed a function or scale, return it, or whatever it may be
paulo@89 984 //external libs, such as angularjs-nvd3-directives use this
paulo@89 985 } else {
paulo@89 986 //can't really help it if someone passes rubbish as color
paulo@89 987 return color;
paulo@89 988 }
paulo@89 989 };
paulo@89 990
paulo@89 991
paulo@89 992 /*
paulo@89 993 Default color chooser uses a color scale of 20 colors from D3
paulo@89 994 https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
paulo@89 995 */
paulo@89 996 nv.utils.defaultColor = function() {
paulo@89 997 // get range of the scale so we'll turn it into our own function.
paulo@89 998 return nv.utils.getColor(d3.scale.category20().range());
paulo@89 999 };
paulo@89 1000
paulo@89 1001
paulo@89 1002 /*
paulo@89 1003 Returns a color function that takes the result of 'getKey' for each series and
paulo@89 1004 looks for a corresponding color from the dictionary
paulo@89 1005 */
paulo@89 1006 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
paulo@89 1007 // use default series.key if getKey is undefined
paulo@89 1008 getKey = getKey || function(series) { return series.key };
paulo@89 1009 defaultColors = defaultColors || d3.scale.category20().range();
paulo@89 1010
paulo@89 1011 // start at end of default color list and walk back to index 0
paulo@89 1012 var defIndex = defaultColors.length;
paulo@89 1013
paulo@89 1014 return function(series, index) {
paulo@89 1015 var key = getKey(series);
paulo@89 1016 if (typeof dictionary[key] === 'function') {
paulo@89 1017 return dictionary[key]();
paulo@89 1018 } else if (dictionary[key] !== undefined) {
paulo@89 1019 return dictionary[key];
paulo@89 1020 } else {
paulo@89 1021 // no match in dictionary, use a default color
paulo@89 1022 if (!defIndex) {
paulo@89 1023 // used all the default colors, start over
paulo@89 1024 defIndex = defaultColors.length;
paulo@89 1025 }
paulo@89 1026 defIndex = defIndex - 1;
paulo@89 1027 return defaultColors[defIndex];
paulo@89 1028 }
paulo@89 1029 };
paulo@89 1030 };
paulo@89 1031
paulo@89 1032
paulo@89 1033 /*
paulo@89 1034 From the PJAX example on d3js.org, while this is not really directly needed
paulo@89 1035 it's a very cool method for doing pjax, I may expand upon it a little bit,
paulo@89 1036 open to suggestions on anything that may be useful
paulo@89 1037 */
paulo@89 1038 nv.utils.pjax = function(links, content) {
paulo@89 1039
paulo@89 1040 var load = function(href) {
paulo@89 1041 d3.html(href, function(fragment) {
paulo@89 1042 var target = d3.select(content).node();
paulo@89 1043 target.parentNode.replaceChild(
paulo@89 1044 d3.select(fragment).select(content).node(),
paulo@89 1045 target);
paulo@89 1046 nv.utils.pjax(links, content);
paulo@89 1047 });
paulo@89 1048 };
paulo@89 1049
paulo@89 1050 d3.selectAll(links).on("click", function() {
paulo@89 1051 history.pushState(this.href, this.textContent, this.href);
paulo@89 1052 load(this.href);
paulo@89 1053 d3.event.preventDefault();
paulo@89 1054 });
paulo@89 1055
paulo@89 1056 d3.select(window).on("popstate", function() {
paulo@89 1057 if (d3.event.state) {
paulo@89 1058 load(d3.event.state);
paulo@89 1059 }
paulo@89 1060 });
paulo@89 1061 };
paulo@89 1062
paulo@89 1063
paulo@89 1064 /*
paulo@89 1065 For when we want to approximate the width in pixels for an SVG:text element.
paulo@89 1066 Most common instance is when the element is in a display:none; container.
paulo@89 1067 Forumla is : text.length * font-size * constant_factor
paulo@89 1068 */
paulo@89 1069 nv.utils.calcApproxTextWidth = function (svgTextElem) {
paulo@89 1070 if (typeof svgTextElem.style === 'function'
paulo@89 1071 && typeof svgTextElem.text === 'function') {
paulo@89 1072
paulo@89 1073 var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
paulo@89 1074 var textLength = svgTextElem.text().length;
paulo@89 1075 return textLength * fontSize * 0.5;
paulo@89 1076 }
paulo@89 1077 return 0;
paulo@89 1078 };
paulo@89 1079
paulo@89 1080
paulo@89 1081 /*
paulo@89 1082 Numbers that are undefined, null or NaN, convert them to zeros.
paulo@89 1083 */
paulo@89 1084 nv.utils.NaNtoZero = function(n) {
paulo@89 1085 if (typeof n !== 'number'
paulo@89 1086 || isNaN(n)
paulo@89 1087 || n === null
paulo@89 1088 || n === Infinity
paulo@89 1089 || n === -Infinity) {
paulo@89 1090
paulo@89 1091 return 0;
paulo@89 1092 }
paulo@89 1093 return n;
paulo@89 1094 };
paulo@89 1095
paulo@89 1096 /*
paulo@89 1097 Add a way to watch for d3 transition ends to d3
paulo@89 1098 */
paulo@89 1099 d3.selection.prototype.watchTransition = function(renderWatch){
paulo@89 1100 var args = [this].concat([].slice.call(arguments, 1));
paulo@89 1101 return renderWatch.transition.apply(renderWatch, args);
paulo@89 1102 };
paulo@89 1103
paulo@89 1104
paulo@89 1105 /*
paulo@89 1106 Helper object to watch when d3 has rendered something
paulo@89 1107 */
paulo@89 1108 nv.utils.renderWatch = function(dispatch, duration) {
paulo@89 1109 if (!(this instanceof nv.utils.renderWatch)) {
paulo@89 1110 return new nv.utils.renderWatch(dispatch, duration);
paulo@89 1111 }
paulo@89 1112
paulo@89 1113 var _duration = duration !== undefined ? duration : 250;
paulo@89 1114 var renderStack = [];
paulo@89 1115 var self = this;
paulo@89 1116
paulo@89 1117 this.models = function(models) {
paulo@89 1118 models = [].slice.call(arguments, 0);
paulo@89 1119 models.forEach(function(model){
paulo@89 1120 model.__rendered = false;
paulo@89 1121 (function(m){
paulo@89 1122 m.dispatch.on('renderEnd', function(arg){
paulo@89 1123 m.__rendered = true;
paulo@89 1124 self.renderEnd('model');
paulo@89 1125 });
paulo@89 1126 })(model);
paulo@89 1127
paulo@89 1128 if (renderStack.indexOf(model) < 0) {
paulo@89 1129 renderStack.push(model);
paulo@89 1130 }
paulo@89 1131 });
paulo@89 1132 return this;
paulo@89 1133 };
paulo@89 1134
paulo@89 1135 this.reset = function(duration) {
paulo@89 1136 if (duration !== undefined) {
paulo@89 1137 _duration = duration;
paulo@89 1138 }
paulo@89 1139 renderStack = [];
paulo@89 1140 };
paulo@89 1141
paulo@89 1142 this.transition = function(selection, args, duration) {
paulo@89 1143 args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
paulo@89 1144
paulo@89 1145 if (args.length > 1) {
paulo@89 1146 duration = args.pop();
paulo@89 1147 } else {
paulo@89 1148 duration = _duration !== undefined ? _duration : 250;
paulo@89 1149 }
paulo@89 1150 selection.__rendered = false;
paulo@89 1151
paulo@89 1152 if (renderStack.indexOf(selection) < 0) {
paulo@89 1153 renderStack.push(selection);
paulo@89 1154 }
paulo@89 1155
paulo@89 1156 if (duration === 0) {
paulo@89 1157 selection.__rendered = true;
paulo@89 1158 selection.delay = function() { return this; };
paulo@89 1159 selection.duration = function() { return this; };
paulo@89 1160 return selection;
paulo@89 1161 } else {
paulo@89 1162 if (selection.length === 0) {
paulo@89 1163 selection.__rendered = true;
paulo@89 1164 } else if (selection.every( function(d){ return !d.length; } )) {
paulo@89 1165 selection.__rendered = true;
paulo@89 1166 } else {
paulo@89 1167 selection.__rendered = false;
paulo@89 1168 }
paulo@89 1169
paulo@89 1170 var n = 0;
paulo@89 1171 return selection
paulo@89 1172 .transition()
paulo@89 1173 .duration(duration)
paulo@89 1174 .each(function(){ ++n; })
paulo@89 1175 .each('end', function(d, i) {
paulo@89 1176 if (--n === 0) {
paulo@89 1177 selection.__rendered = true;
paulo@89 1178 self.renderEnd.apply(this, args);
paulo@89 1179 }
paulo@89 1180 });
paulo@89 1181 }
paulo@89 1182 };
paulo@89 1183
paulo@89 1184 this.renderEnd = function() {
paulo@89 1185 if (renderStack.every( function(d){ return d.__rendered; } )) {
paulo@89 1186 renderStack.forEach( function(d){ d.__rendered = false; });
paulo@89 1187 dispatch.renderEnd.apply(this, arguments);
paulo@89 1188 }
paulo@89 1189 }
paulo@89 1190
paulo@89 1191 };
paulo@89 1192
paulo@89 1193
paulo@89 1194 /*
paulo@89 1195 Takes multiple objects and combines them into the first one (dst)
paulo@89 1196 example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
paulo@89 1197 gives: {a: 2, b: 3, c: 4}
paulo@89 1198 */
paulo@89 1199 nv.utils.deepExtend = function(dst){
paulo@89 1200 var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
paulo@89 1201 sources.forEach(function(source) {
paulo@89 1202 for (var key in source) {
paulo@89 1203 var isArray = dst[key] instanceof Array;
paulo@89 1204 var isObject = typeof dst[key] === 'object';
paulo@89 1205 var srcObj = typeof source[key] === 'object';
paulo@89 1206
paulo@89 1207 if (isObject && !isArray && srcObj) {
paulo@89 1208 nv.utils.deepExtend(dst[key], source[key]);
paulo@89 1209 } else {
paulo@89 1210 dst[key] = source[key];
paulo@89 1211 }
paulo@89 1212 }
paulo@89 1213 });
paulo@89 1214 };
paulo@89 1215
paulo@89 1216
paulo@89 1217 /*
paulo@89 1218 state utility object, used to track d3 states in the models
paulo@89 1219 */
paulo@89 1220 nv.utils.state = function(){
paulo@89 1221 if (!(this instanceof nv.utils.state)) {
paulo@89 1222 return new nv.utils.state();
paulo@89 1223 }
paulo@89 1224 var state = {};
paulo@89 1225 var _self = this;
paulo@89 1226 var _setState = function(){};
paulo@89 1227 var _getState = function(){ return {}; };
paulo@89 1228 var init = null;
paulo@89 1229 var changed = null;
paulo@89 1230
paulo@89 1231 this.dispatch = d3.dispatch('change', 'set');
paulo@89 1232
paulo@89 1233 this.dispatch.on('set', function(state){
paulo@89 1234 _setState(state, true);
paulo@89 1235 });
paulo@89 1236
paulo@89 1237 this.getter = function(fn){
paulo@89 1238 _getState = fn;
paulo@89 1239 return this;
paulo@89 1240 };
paulo@89 1241
paulo@89 1242 this.setter = function(fn, callback) {
paulo@89 1243 if (!callback) {
paulo@89 1244 callback = function(){};
paulo@89 1245 }
paulo@89 1246 _setState = function(state, update){
paulo@89 1247 fn(state);
paulo@89 1248 if (update) {
paulo@89 1249 callback();
paulo@89 1250 }
paulo@89 1251 };
paulo@89 1252 return this;
paulo@89 1253 };
paulo@89 1254
paulo@89 1255 this.init = function(state){
paulo@89 1256 init = init || {};
paulo@89 1257 nv.utils.deepExtend(init, state);
paulo@89 1258 };
paulo@89 1259
paulo@89 1260 var _set = function(){
paulo@89 1261 var settings = _getState();
paulo@89 1262
paulo@89 1263 if (JSON.stringify(settings) === JSON.stringify(state)) {
paulo@89 1264 return false;
paulo@89 1265 }
paulo@89 1266
paulo@89 1267 for (var key in settings) {
paulo@89 1268 if (state[key] === undefined) {
paulo@89 1269 state[key] = {};
paulo@89 1270 }
paulo@89 1271 state[key] = settings[key];
paulo@89 1272 changed = true;
paulo@89 1273 }
paulo@89 1274 return true;
paulo@89 1275 };
paulo@89 1276
paulo@89 1277 this.update = function(){
paulo@89 1278 if (init) {
paulo@89 1279 _setState(init, false);
paulo@89 1280 init = null;
paulo@89 1281 }
paulo@89 1282 if (_set.call(this)) {
paulo@89 1283 this.dispatch.change(state);
paulo@89 1284 }
paulo@89 1285 };
paulo@89 1286
paulo@89 1287 };
paulo@89 1288
paulo@89 1289
paulo@89 1290 /*
paulo@89 1291 Snippet of code you can insert into each nv.models.* to give you the ability to
paulo@89 1292 do things like:
paulo@89 1293 chart.options({
paulo@89 1294 showXAxis: true,
paulo@89 1295 tooltips: true
paulo@89 1296 });
paulo@89 1297
paulo@89 1298 To enable in the chart:
paulo@89 1299 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 1300 */
paulo@89 1301 nv.utils.optionsFunc = function(args) {
paulo@89 1302 if (args) {
paulo@89 1303 d3.map(args).forEach((function(key,value) {
paulo@89 1304 if (typeof this[key] === "function") {
paulo@89 1305 this[key](value);
paulo@89 1306 }
paulo@89 1307 }).bind(this));
paulo@89 1308 }
paulo@89 1309 return this;
paulo@89 1310 };
paulo@89 1311
paulo@89 1312
paulo@89 1313 /*
paulo@89 1314 numTicks: requested number of ticks
paulo@89 1315 data: the chart data
paulo@89 1316
paulo@89 1317 returns the number of ticks to actually use on X axis, based on chart data
paulo@89 1318 to avoid duplicate ticks with the same value
paulo@89 1319 */
paulo@89 1320 nv.utils.calcTicksX = function(numTicks, data) {
paulo@89 1321 // find max number of values from all data streams
paulo@89 1322 var numValues = 1;
paulo@89 1323 var i = 0;
paulo@89 1324 for (i; i < data.length; i += 1) {
paulo@89 1325 var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
paulo@89 1326 numValues = stream_len > numValues ? stream_len : numValues;
paulo@89 1327 }
paulo@89 1328 nv.log("Requested number of ticks: ", numTicks);
paulo@89 1329 nv.log("Calculated max values to be: ", numValues);
paulo@89 1330 // make sure we don't have more ticks than values to avoid duplicates
paulo@89 1331 numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
paulo@89 1332 // make sure we have at least one tick
paulo@89 1333 numTicks = numTicks < 1 ? 1 : numTicks;
paulo@89 1334 // make sure it's an integer
paulo@89 1335 numTicks = Math.floor(numTicks);
paulo@89 1336 nv.log("Calculating tick count as: ", numTicks);
paulo@89 1337 return numTicks;
paulo@89 1338 };
paulo@89 1339
paulo@89 1340
paulo@89 1341 /*
paulo@89 1342 returns number of ticks to actually use on Y axis, based on chart data
paulo@89 1343 */
paulo@89 1344 nv.utils.calcTicksY = function(numTicks, data) {
paulo@89 1345 // currently uses the same logic but we can adjust here if needed later
paulo@89 1346 return nv.utils.calcTicksX(numTicks, data);
paulo@89 1347 };
paulo@89 1348
paulo@89 1349
paulo@89 1350 /*
paulo@89 1351 Add a particular option from an options object onto chart
paulo@89 1352 Options exposed on a chart are a getter/setter function that returns chart
paulo@89 1353 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
paulo@89 1354
paulo@89 1355 option objects should be generated via Object.create() to provide
paulo@89 1356 the option of manipulating data via get/set functions.
paulo@89 1357 */
paulo@89 1358 nv.utils.initOption = function(chart, name) {
paulo@89 1359 // if it's a call option, just call it directly, otherwise do get/set
paulo@89 1360 if (chart._calls && chart._calls[name]) {
paulo@89 1361 chart[name] = chart._calls[name];
paulo@89 1362 } else {
paulo@89 1363 chart[name] = function (_) {
paulo@89 1364 if (!arguments.length) return chart._options[name];
paulo@89 1365 chart._overrides[name] = true;
paulo@89 1366 chart._options[name] = _;
paulo@89 1367 return chart;
paulo@89 1368 };
paulo@89 1369 // calling the option as _option will ignore if set by option already
paulo@89 1370 // so nvd3 can set options internally but the stop if set manually
paulo@89 1371 chart['_' + name] = function(_) {
paulo@89 1372 if (!arguments.length) return chart._options[name];
paulo@89 1373 if (!chart._overrides[name]) {
paulo@89 1374 chart._options[name] = _;
paulo@89 1375 }
paulo@89 1376 return chart;
paulo@89 1377 }
paulo@89 1378 }
paulo@89 1379 };
paulo@89 1380
paulo@89 1381
paulo@89 1382 /*
paulo@89 1383 Add all options in an options object to the chart
paulo@89 1384 */
paulo@89 1385 nv.utils.initOptions = function(chart) {
paulo@89 1386 chart._overrides = chart._overrides || {};
paulo@89 1387 var ops = Object.getOwnPropertyNames(chart._options || {});
paulo@89 1388 var calls = Object.getOwnPropertyNames(chart._calls || {});
paulo@89 1389 ops = ops.concat(calls);
paulo@89 1390 for (var i in ops) {
paulo@89 1391 nv.utils.initOption(chart, ops[i]);
paulo@89 1392 }
paulo@89 1393 };
paulo@89 1394
paulo@89 1395
paulo@89 1396 /*
paulo@89 1397 Inherit options from a D3 object
paulo@89 1398 d3.rebind makes calling the function on target actually call it on source
paulo@89 1399 Also use _d3options so we can track what we inherit for documentation and chained inheritance
paulo@89 1400 */
paulo@89 1401 nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
paulo@89 1402 target._d3options = oplist.concat(target._d3options || []);
paulo@89 1403 oplist.unshift(d3_source);
paulo@89 1404 oplist.unshift(target);
paulo@89 1405 d3.rebind.apply(this, oplist);
paulo@89 1406 };
paulo@89 1407
paulo@89 1408
paulo@89 1409 /*
paulo@89 1410 Remove duplicates from an array
paulo@89 1411 */
paulo@89 1412 nv.utils.arrayUnique = function(a) {
paulo@89 1413 return a.sort().filter(function(item, pos) {
paulo@89 1414 return !pos || item != a[pos - 1];
paulo@89 1415 });
paulo@89 1416 };
paulo@89 1417
paulo@89 1418
paulo@89 1419 /*
paulo@89 1420 Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
paulo@89 1421 Necessary since d3 doesn't let you extend its list -_-
paulo@89 1422 Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
paulo@89 1423 */
paulo@89 1424 nv.utils.symbolMap = d3.map();
paulo@89 1425
paulo@89 1426
paulo@89 1427 /*
paulo@89 1428 Replaces d3.svg.symbol so that we can look both there and our own map
paulo@89 1429 */
paulo@89 1430 nv.utils.symbol = function() {
paulo@89 1431 var type,
paulo@89 1432 size = 64;
paulo@89 1433 function symbol(d,i) {
paulo@89 1434 var t = type.call(this,d,i);
paulo@89 1435 var s = size.call(this,d,i);
paulo@89 1436 if (d3.svg.symbolTypes.indexOf(t) !== -1) {
paulo@89 1437 return d3.svg.symbol().type(t).size(s)();
paulo@89 1438 } else {
paulo@89 1439 return nv.utils.symbolMap.get(t)(s);
paulo@89 1440 }
paulo@89 1441 }
paulo@89 1442 symbol.type = function(_) {
paulo@89 1443 if (!arguments.length) return type;
paulo@89 1444 type = d3.functor(_);
paulo@89 1445 return symbol;
paulo@89 1446 };
paulo@89 1447 symbol.size = function(_) {
paulo@89 1448 if (!arguments.length) return size;
paulo@89 1449 size = d3.functor(_);
paulo@89 1450 return symbol;
paulo@89 1451 };
paulo@89 1452 return symbol;
paulo@89 1453 };
paulo@89 1454
paulo@89 1455
paulo@89 1456 /*
paulo@89 1457 Inherit option getter/setter functions from source to target
paulo@89 1458 d3.rebind makes calling the function on target actually call it on source
paulo@89 1459 Also track via _inherited and _d3options so we can track what we inherit
paulo@89 1460 for documentation generation purposes and chained inheritance
paulo@89 1461 */
paulo@89 1462 nv.utils.inheritOptions = function(target, source) {
paulo@89 1463 // inherit all the things
paulo@89 1464 var ops = Object.getOwnPropertyNames(source._options || {});
paulo@89 1465 var calls = Object.getOwnPropertyNames(source._calls || {});
paulo@89 1466 var inherited = source._inherited || [];
paulo@89 1467 var d3ops = source._d3options || [];
paulo@89 1468 var args = ops.concat(calls).concat(inherited).concat(d3ops);
paulo@89 1469 args.unshift(source);
paulo@89 1470 args.unshift(target);
paulo@89 1471 d3.rebind.apply(this, args);
paulo@89 1472 // pass along the lists to keep track of them, don't allow duplicates
paulo@89 1473 target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
paulo@89 1474 target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
paulo@89 1475 };
paulo@89 1476
paulo@89 1477
paulo@89 1478 /*
paulo@89 1479 Runs common initialize code on the svg before the chart builds
paulo@89 1480 */
paulo@89 1481 nv.utils.initSVG = function(svg) {
paulo@89 1482 svg.classed({'nvd3-svg':true});
paulo@89 1483 };
paulo@89 1484
paulo@89 1485
paulo@89 1486 /*
paulo@89 1487 Sanitize and provide default for the container height.
paulo@89 1488 */
paulo@89 1489 nv.utils.sanitizeHeight = function(height, container) {
paulo@89 1490 return (height || parseInt(container.style('height'), 10) || 400);
paulo@89 1491 };
paulo@89 1492
paulo@89 1493
paulo@89 1494 /*
paulo@89 1495 Sanitize and provide default for the container width.
paulo@89 1496 */
paulo@89 1497 nv.utils.sanitizeWidth = function(width, container) {
paulo@89 1498 return (width || parseInt(container.style('width'), 10) || 960);
paulo@89 1499 };
paulo@89 1500
paulo@89 1501
paulo@89 1502 /*
paulo@89 1503 Calculate the available height for a chart.
paulo@89 1504 */
paulo@89 1505 nv.utils.availableHeight = function(height, container, margin) {
paulo@89 1506 return nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom;
paulo@89 1507 };
paulo@89 1508
paulo@89 1509 /*
paulo@89 1510 Calculate the available width for a chart.
paulo@89 1511 */
paulo@89 1512 nv.utils.availableWidth = function(width, container, margin) {
paulo@89 1513 return nv.utils.sanitizeWidth(width, container) - margin.left - margin.right;
paulo@89 1514 };
paulo@89 1515
paulo@89 1516 /*
paulo@89 1517 Clear any rendered chart components and display a chart's 'noData' message
paulo@89 1518 */
paulo@89 1519 nv.utils.noData = function(chart, container) {
paulo@89 1520 var opt = chart.options(),
paulo@89 1521 margin = opt.margin(),
paulo@89 1522 noData = opt.noData(),
paulo@89 1523 data = (noData == null) ? ["No Data Available."] : [noData],
paulo@89 1524 height = nv.utils.availableHeight(opt.height(), container, margin),
paulo@89 1525 width = nv.utils.availableWidth(opt.width(), container, margin),
paulo@89 1526 x = margin.left + width/2,
paulo@89 1527 y = margin.top + height/2;
paulo@89 1528
paulo@89 1529 //Remove any previously created chart components
paulo@89 1530 container.selectAll('g').remove();
paulo@89 1531
paulo@89 1532 var noDataText = container.selectAll('.nv-noData').data(data);
paulo@89 1533
paulo@89 1534 noDataText.enter().append('text')
paulo@89 1535 .attr('class', 'nvd3 nv-noData')
paulo@89 1536 .attr('dy', '-.7em')
paulo@89 1537 .style('text-anchor', 'middle');
paulo@89 1538
paulo@89 1539 noDataText
paulo@89 1540 .attr('x', x)
paulo@89 1541 .attr('y', y)
paulo@89 1542 .text(function(t){ return t; });
paulo@89 1543 };
paulo@89 1544
paulo@89 1545 nv.models.axis = function() {
paulo@89 1546 "use strict";
paulo@89 1547
paulo@89 1548 //============================================================
paulo@89 1549 // Public Variables with Default Settings
paulo@89 1550 //------------------------------------------------------------
paulo@89 1551
paulo@89 1552 var axis = d3.svg.axis();
paulo@89 1553 var scale = d3.scale.linear();
paulo@89 1554
paulo@89 1555 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 1556 , width = 75 //only used for tickLabel currently
paulo@89 1557 , height = 60 //only used for tickLabel currently
paulo@89 1558 , axisLabelText = null
paulo@89 1559 , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
paulo@89 1560 , rotateLabels = 0
paulo@89 1561 , rotateYLabel = true
paulo@89 1562 , staggerLabels = false
paulo@89 1563 , isOrdinal = false
paulo@89 1564 , ticks = null
paulo@89 1565 , axisLabelDistance = 0
paulo@89 1566 , duration = 250
paulo@89 1567 , dispatch = d3.dispatch('renderEnd')
paulo@89 1568 ;
paulo@89 1569 axis
paulo@89 1570 .scale(scale)
paulo@89 1571 .orient('bottom')
paulo@89 1572 .tickFormat(function(d) { return d })
paulo@89 1573 ;
paulo@89 1574
paulo@89 1575 //============================================================
paulo@89 1576 // Private Variables
paulo@89 1577 //------------------------------------------------------------
paulo@89 1578
paulo@89 1579 var scale0;
paulo@89 1580 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 1581
paulo@89 1582 function chart(selection) {
paulo@89 1583 renderWatch.reset();
paulo@89 1584 selection.each(function(data) {
paulo@89 1585 var container = d3.select(this);
paulo@89 1586 nv.utils.initSVG(container);
paulo@89 1587
paulo@89 1588 // Setup containers and skeleton of chart
paulo@89 1589 var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
paulo@89 1590 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
paulo@89 1591 var gEnter = wrapEnter.append('g');
paulo@89 1592 var g = wrap.select('g');
paulo@89 1593
paulo@89 1594 if (ticks !== null)
paulo@89 1595 axis.ticks(ticks);
paulo@89 1596 else if (axis.orient() == 'top' || axis.orient() == 'bottom')
paulo@89 1597 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
paulo@89 1598
paulo@89 1599 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
paulo@89 1600 g.watchTransition(renderWatch, 'axis').call(axis);
paulo@89 1601
paulo@89 1602 scale0 = scale0 || axis.scale();
paulo@89 1603
paulo@89 1604 var fmt = axis.tickFormat();
paulo@89 1605 if (fmt == null) {
paulo@89 1606 fmt = scale0.tickFormat();
paulo@89 1607 }
paulo@89 1608
paulo@89 1609 var axisLabel = g.selectAll('text.nv-axislabel')
paulo@89 1610 .data([axisLabelText || null]);
paulo@89 1611 axisLabel.exit().remove();
paulo@89 1612
paulo@89 1613 var xLabelMargin;
paulo@89 1614 var axisMaxMin;
paulo@89 1615 var w;
paulo@89 1616 switch (axis.orient()) {
paulo@89 1617 case 'top':
paulo@89 1618 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
paulo@89 1619 if (scale.range().length < 2) {
paulo@89 1620 w = 0;
paulo@89 1621 } else if (scale.range().length === 2) {
paulo@89 1622 w = scale.range()[1];
paulo@89 1623 } else {
paulo@89 1624 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
paulo@89 1625 }
paulo@89 1626 axisLabel
paulo@89 1627 .attr('text-anchor', 'middle')
paulo@89 1628 .attr('y', 0)
paulo@89 1629 .attr('x', w/2);
paulo@89 1630 if (showMaxMin) {
paulo@89 1631 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
paulo@89 1632 .data(scale.domain());
paulo@89 1633 axisMaxMin.enter().append('g').attr('class',function(d,i){
paulo@89 1634 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
paulo@89 1635 }).append('text');
paulo@89 1636 axisMaxMin.exit().remove();
paulo@89 1637 axisMaxMin
paulo@89 1638 .attr('transform', function(d,i) {
paulo@89 1639 return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
paulo@89 1640 })
paulo@89 1641 .select('text')
paulo@89 1642 .attr('dy', '-0.5em')
paulo@89 1643 .attr('y', -axis.tickPadding())
paulo@89 1644 .attr('text-anchor', 'middle')
paulo@89 1645 .text(function(d,i) {
paulo@89 1646 var v = fmt(d);
paulo@89 1647 return ('' + v).match('NaN') ? '' : v;
paulo@89 1648 });
paulo@89 1649 axisMaxMin.watchTransition(renderWatch, 'min-max top')
paulo@89 1650 .attr('transform', function(d,i) {
paulo@89 1651 return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
paulo@89 1652 });
paulo@89 1653 }
paulo@89 1654 break;
paulo@89 1655 case 'bottom':
paulo@89 1656 xLabelMargin = axisLabelDistance + 36;
paulo@89 1657 var maxTextWidth = 30;
paulo@89 1658 var textHeight = 0;
paulo@89 1659 var xTicks = g.selectAll('g').select("text");
paulo@89 1660 var rotateLabelsRule = '';
paulo@89 1661 if (rotateLabels%360) {
paulo@89 1662 //Calculate the longest xTick width
paulo@89 1663 xTicks.each(function(d,i){
paulo@89 1664 var box = this.getBoundingClientRect();
paulo@89 1665 var width = box.width;
paulo@89 1666 textHeight = box.height;
paulo@89 1667 if(width > maxTextWidth) maxTextWidth = width;
paulo@89 1668 });
paulo@89 1669 rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')';
paulo@89 1670 //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
paulo@89 1671 var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
paulo@89 1672 xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
paulo@89 1673 //Rotate all xTicks
paulo@89 1674 xTicks
paulo@89 1675 .attr('transform', rotateLabelsRule)
paulo@89 1676 .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
paulo@89 1677 }
paulo@89 1678 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
paulo@89 1679 if (scale.range().length < 2) {
paulo@89 1680 w = 0;
paulo@89 1681 } else if (scale.range().length === 2) {
paulo@89 1682 w = scale.range()[1];
paulo@89 1683 } else {
paulo@89 1684 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
paulo@89 1685 }
paulo@89 1686 axisLabel
paulo@89 1687 .attr('text-anchor', 'middle')
paulo@89 1688 .attr('y', xLabelMargin)
paulo@89 1689 .attr('x', w/2);
paulo@89 1690 if (showMaxMin) {
paulo@89 1691 //if (showMaxMin && !isOrdinal) {
paulo@89 1692 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
paulo@89 1693 //.data(scale.domain())
paulo@89 1694 .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
paulo@89 1695 axisMaxMin.enter().append('g').attr('class',function(d,i){
paulo@89 1696 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
paulo@89 1697 }).append('text');
paulo@89 1698 axisMaxMin.exit().remove();
paulo@89 1699 axisMaxMin
paulo@89 1700 .attr('transform', function(d,i) {
paulo@89 1701 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
paulo@89 1702 })
paulo@89 1703 .select('text')
paulo@89 1704 .attr('dy', '.71em')
paulo@89 1705 .attr('y', axis.tickPadding())
paulo@89 1706 .attr('transform', rotateLabelsRule)
paulo@89 1707 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
paulo@89 1708 .text(function(d,i) {
paulo@89 1709 var v = fmt(d);
paulo@89 1710 return ('' + v).match('NaN') ? '' : v;
paulo@89 1711 });
paulo@89 1712 axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
paulo@89 1713 .attr('transform', function(d,i) {
paulo@89 1714 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
paulo@89 1715 });
paulo@89 1716 }
paulo@89 1717 if (staggerLabels)
paulo@89 1718 xTicks
paulo@89 1719 .attr('transform', function(d,i) {
paulo@89 1720 return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
paulo@89 1721 });
paulo@89 1722
paulo@89 1723 break;
paulo@89 1724 case 'right':
paulo@89 1725 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
paulo@89 1726 axisLabel
paulo@89 1727 .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
paulo@89 1728 .attr('transform', rotateYLabel ? 'rotate(90)' : '')
paulo@89 1729 .attr('y', rotateYLabel ? (-Math.max(margin.right, width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
paulo@89 1730 .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding());
paulo@89 1731 if (showMaxMin) {
paulo@89 1732 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
paulo@89 1733 .data(scale.domain());
paulo@89 1734 axisMaxMin.enter().append('g').attr('class',function(d,i){
paulo@89 1735 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
paulo@89 1736 }).append('text')
paulo@89 1737 .style('opacity', 0);
paulo@89 1738 axisMaxMin.exit().remove();
paulo@89 1739 axisMaxMin
paulo@89 1740 .attr('transform', function(d,i) {
paulo@89 1741 return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
paulo@89 1742 })
paulo@89 1743 .select('text')
paulo@89 1744 .attr('dy', '.32em')
paulo@89 1745 .attr('y', 0)
paulo@89 1746 .attr('x', axis.tickPadding())
paulo@89 1747 .style('text-anchor', 'start')
paulo@89 1748 .text(function(d, i) {
paulo@89 1749 var v = fmt(d);
paulo@89 1750 return ('' + v).match('NaN') ? '' : v;
paulo@89 1751 });
paulo@89 1752 axisMaxMin.watchTransition(renderWatch, 'min-max right')
paulo@89 1753 .attr('transform', function(d,i) {
paulo@89 1754 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
paulo@89 1755 })
paulo@89 1756 .select('text')
paulo@89 1757 .style('opacity', 1);
paulo@89 1758 }
paulo@89 1759 break;
paulo@89 1760 case 'left':
paulo@89 1761 /*
paulo@89 1762 //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
paulo@89 1763 var yTicks = g.selectAll('g').select("text");
paulo@89 1764 yTicks.each(function(d,i){
paulo@89 1765 var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
paulo@89 1766 if(labelPadding > width) width = labelPadding;
paulo@89 1767 });
paulo@89 1768 */
paulo@89 1769 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
paulo@89 1770 axisLabel
paulo@89 1771 .style('text-anchor', rotateYLabel ? 'middle' : 'end')
paulo@89 1772 .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
paulo@89 1773 .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10)
paulo@89 1774 .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding());
paulo@89 1775 if (showMaxMin) {
paulo@89 1776 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
paulo@89 1777 .data(scale.domain());
paulo@89 1778 axisMaxMin.enter().append('g').attr('class',function(d,i){
paulo@89 1779 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
paulo@89 1780 }).append('text')
paulo@89 1781 .style('opacity', 0);
paulo@89 1782 axisMaxMin.exit().remove();
paulo@89 1783 axisMaxMin
paulo@89 1784 .attr('transform', function(d,i) {
paulo@89 1785 return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
paulo@89 1786 })
paulo@89 1787 .select('text')
paulo@89 1788 .attr('dy', '.32em')
paulo@89 1789 .attr('y', 0)
paulo@89 1790 .attr('x', -axis.tickPadding())
paulo@89 1791 .attr('text-anchor', 'end')
paulo@89 1792 .text(function(d,i) {
paulo@89 1793 var v = fmt(d);
paulo@89 1794 return ('' + v).match('NaN') ? '' : v;
paulo@89 1795 });
paulo@89 1796 axisMaxMin.watchTransition(renderWatch, 'min-max right')
paulo@89 1797 .attr('transform', function(d,i) {
paulo@89 1798 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
paulo@89 1799 })
paulo@89 1800 .select('text')
paulo@89 1801 .style('opacity', 1);
paulo@89 1802 }
paulo@89 1803 break;
paulo@89 1804 }
paulo@89 1805 axisLabel.text(function(d) { return d });
paulo@89 1806
paulo@89 1807 if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
paulo@89 1808 //check if max and min overlap other values, if so, hide the values that overlap
paulo@89 1809 g.selectAll('g') // the g's wrapping each tick
paulo@89 1810 .each(function(d,i) {
paulo@89 1811 d3.select(this).select('text').attr('opacity', 1);
paulo@89 1812 if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it!
paulo@89 1813 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
paulo@89 1814 d3.select(this).attr('opacity', 0);
paulo@89 1815
paulo@89 1816 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
paulo@89 1817 }
paulo@89 1818 });
paulo@89 1819
paulo@89 1820 //if Max and Min = 0 only show min, Issue #281
paulo@89 1821 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
paulo@89 1822 wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
paulo@89 1823 return !i ? 1 : 0
paulo@89 1824 });
paulo@89 1825 }
paulo@89 1826 }
paulo@89 1827
paulo@89 1828 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
paulo@89 1829 var maxMinRange = [];
paulo@89 1830 wrap.selectAll('g.nv-axisMaxMin')
paulo@89 1831 .each(function(d,i) {
paulo@89 1832 try {
paulo@89 1833 if (i) // i== 1, max position
paulo@89 1834 maxMinRange.push(scale(d) - this.getBoundingClientRect().width - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
paulo@89 1835 else // i==0, min position
paulo@89 1836 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
paulo@89 1837 }catch (err) {
paulo@89 1838 if (i) // i== 1, max position
paulo@89 1839 maxMinRange.push(scale(d) - 4); //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
paulo@89 1840 else // i==0, min position
paulo@89 1841 maxMinRange.push(scale(d) + 4);
paulo@89 1842 }
paulo@89 1843 });
paulo@89 1844 // the g's wrapping each tick
paulo@89 1845 g.selectAll('g').each(function(d, i) {
paulo@89 1846 if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
paulo@89 1847 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
paulo@89 1848 d3.select(this).remove();
paulo@89 1849 else
paulo@89 1850 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
paulo@89 1851 }
paulo@89 1852 });
paulo@89 1853 }
paulo@89 1854
paulo@89 1855 //Highlight zero tick line
paulo@89 1856 g.selectAll('.tick')
paulo@89 1857 .filter(function (d) {
paulo@89 1858 /*
paulo@89 1859 The filter needs to return only ticks at or near zero.
paulo@89 1860 Numbers like 0.00001 need to count as zero as well,
paulo@89 1861 and the arithmetic trick below solves that.
paulo@89 1862 */
paulo@89 1863 return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined)
paulo@89 1864 })
paulo@89 1865 .classed('zero', true);
paulo@89 1866
paulo@89 1867 //store old scales for use in transitions on update
paulo@89 1868 scale0 = scale.copy();
paulo@89 1869
paulo@89 1870 });
paulo@89 1871
paulo@89 1872 renderWatch.renderEnd('axis immediate');
paulo@89 1873 return chart;
paulo@89 1874 }
paulo@89 1875
paulo@89 1876 //============================================================
paulo@89 1877 // Expose Public Variables
paulo@89 1878 //------------------------------------------------------------
paulo@89 1879
paulo@89 1880 // expose chart's sub-components
paulo@89 1881 chart.axis = axis;
paulo@89 1882 chart.dispatch = dispatch;
paulo@89 1883
paulo@89 1884 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 1885 chart._options = Object.create({}, {
paulo@89 1886 // simple options, just get/set the necessary values
paulo@89 1887 axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
paulo@89 1888 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
paulo@89 1889 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
paulo@89 1890 rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
paulo@89 1891 showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
paulo@89 1892 axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
paulo@89 1893 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 1894 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
paulo@89 1895 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 1896
paulo@89 1897 // options that require extra logic in the setter
paulo@89 1898 margin: {get: function(){return margin;}, set: function(_){
paulo@89 1899 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 1900 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 1901 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 1902 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 1903 }},
paulo@89 1904 duration: {get: function(){return duration;}, set: function(_){
paulo@89 1905 duration=_;
paulo@89 1906 renderWatch.reset(duration);
paulo@89 1907 }},
paulo@89 1908 scale: {get: function(){return scale;}, set: function(_){
paulo@89 1909 scale = _;
paulo@89 1910 axis.scale(scale);
paulo@89 1911 isOrdinal = typeof scale.rangeBands === 'function';
paulo@89 1912 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
paulo@89 1913 }}
paulo@89 1914 });
paulo@89 1915
paulo@89 1916 nv.utils.initOptions(chart);
paulo@89 1917 nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
paulo@89 1918 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
paulo@89 1919
paulo@89 1920 return chart;
paulo@89 1921 };
paulo@89 1922 nv.models.boxPlot = function() {
paulo@89 1923 "use strict";
paulo@89 1924
paulo@89 1925 //============================================================
paulo@89 1926 // Public Variables with Default Settings
paulo@89 1927 //------------------------------------------------------------
paulo@89 1928
paulo@89 1929 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 1930 , width = 960
paulo@89 1931 , height = 500
paulo@89 1932 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 1933 , x = d3.scale.ordinal()
paulo@89 1934 , y = d3.scale.linear()
paulo@89 1935 , getX = function(d) { return d.x }
paulo@89 1936 , getY = function(d) { return d.y }
paulo@89 1937 , color = nv.utils.defaultColor()
paulo@89 1938 , container = null
paulo@89 1939 , xDomain
paulo@89 1940 , yDomain
paulo@89 1941 , xRange
paulo@89 1942 , yRange
paulo@89 1943 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
paulo@89 1944 , duration = 250
paulo@89 1945 , maxBoxWidth = null
paulo@89 1946 ;
paulo@89 1947
paulo@89 1948 //============================================================
paulo@89 1949 // Private Variables
paulo@89 1950 //------------------------------------------------------------
paulo@89 1951
paulo@89 1952 var x0, y0;
paulo@89 1953 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 1954
paulo@89 1955 function chart(selection) {
paulo@89 1956 renderWatch.reset();
paulo@89 1957 selection.each(function(data) {
paulo@89 1958 var availableWidth = width - margin.left - margin.right,
paulo@89 1959 availableHeight = height - margin.top - margin.bottom;
paulo@89 1960
paulo@89 1961 container = d3.select(this);
paulo@89 1962 nv.utils.initSVG(container);
paulo@89 1963
paulo@89 1964 // Setup Scales
paulo@89 1965 x .domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
paulo@89 1966 .rangeBands(xRange || [0, availableWidth], .1);
paulo@89 1967
paulo@89 1968 // if we know yDomain, no need to calculate
paulo@89 1969 var yData = []
paulo@89 1970 if (!yDomain) {
paulo@89 1971 // (y-range is based on quartiles, whiskers and outliers)
paulo@89 1972
paulo@89 1973 // lower values
paulo@89 1974 var yMin = d3.min(data.map(function(d) {
paulo@89 1975 var min_arr = [];
paulo@89 1976
paulo@89 1977 min_arr.push(d.values.Q1);
paulo@89 1978 if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); }
paulo@89 1979 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); }
paulo@89 1980
paulo@89 1981 return d3.min(min_arr);
paulo@89 1982 }));
paulo@89 1983
paulo@89 1984 // upper values
paulo@89 1985 var yMax = d3.max(data.map(function(d) {
paulo@89 1986 var max_arr = [];
paulo@89 1987
paulo@89 1988 max_arr.push(d.values.Q3);
paulo@89 1989 if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); }
paulo@89 1990 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); }
paulo@89 1991
paulo@89 1992 return d3.max(max_arr);
paulo@89 1993 }));
paulo@89 1994
paulo@89 1995 yData = [ yMin, yMax ] ;
paulo@89 1996 }
paulo@89 1997
paulo@89 1998 y.domain(yDomain || yData);
paulo@89 1999 y.range(yRange || [availableHeight, 0]);
paulo@89 2000
paulo@89 2001 //store old scales if they exist
paulo@89 2002 x0 = x0 || x;
paulo@89 2003 y0 = y0 || y.copy().range([y(0),y(0)]);
paulo@89 2004
paulo@89 2005 // Setup containers and skeleton of chart
paulo@89 2006 var wrap = container.selectAll('g.nv-wrap').data([data]);
paulo@89 2007 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap');
paulo@89 2008 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 2009
paulo@89 2010 var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d });
paulo@89 2011 var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6);
paulo@89 2012 boxplots
paulo@89 2013 .attr('class', 'nv-boxplot')
paulo@89 2014 .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; })
paulo@89 2015 .classed('hover', function(d) { return d.hover });
paulo@89 2016 boxplots
paulo@89 2017 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
paulo@89 2018 .style('stroke-opacity', 1)
paulo@89 2019 .style('fill-opacity', .75)
paulo@89 2020 .delay(function(d,i) { return i * duration / data.length })
paulo@89 2021 .attr('transform', function(d,i) {
paulo@89 2022 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)';
paulo@89 2023 });
paulo@89 2024 boxplots.exit().remove();
paulo@89 2025
paulo@89 2026 // ----- add the SVG elements for each boxPlot -----
paulo@89 2027
paulo@89 2028 // conditionally append whisker lines
paulo@89 2029 boxEnter.each(function(d,i) {
paulo@89 2030 var box = d3.select(this);
paulo@89 2031
paulo@89 2032 ['low', 'high'].forEach(function(key) {
paulo@89 2033 if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) {
paulo@89 2034 box.append('line')
paulo@89 2035 .style('stroke', (d.color) ? d.color : color(d,i))
paulo@89 2036 .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
paulo@89 2037
paulo@89 2038 box.append('line')
paulo@89 2039 .style('stroke', (d.color) ? d.color : color(d,i))
paulo@89 2040 .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
paulo@89 2041 }
paulo@89 2042 });
paulo@89 2043 });
paulo@89 2044
paulo@89 2045 // outliers
paulo@89 2046 // TODO: support custom colors here
paulo@89 2047 var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
paulo@89 2048 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; }
paulo@89 2049 else { return []; }
paulo@89 2050 });
paulo@89 2051 outliers.enter().append('circle')
paulo@89 2052 .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) })
paulo@89 2053 .on('mouseover', function(d,i,j) {
paulo@89 2054 d3.select(this).classed('hover', true);
paulo@89 2055 dispatch.elementMouseover({
paulo@89 2056 series: { key: d, color: color(d,j) },
paulo@89 2057 e: d3.event
paulo@89 2058 });
paulo@89 2059 })
paulo@89 2060 .on('mouseout', function(d,i,j) {
paulo@89 2061 d3.select(this).classed('hover', false);
paulo@89 2062 dispatch.elementMouseout({
paulo@89 2063 series: { key: d, color: color(d,j) },
paulo@89 2064 e: d3.event
paulo@89 2065 });
paulo@89 2066 })
paulo@89 2067 .on('mousemove', function(d,i) {
paulo@89 2068 dispatch.elementMousemove({e: d3.event});
paulo@89 2069 });
paulo@89 2070
paulo@89 2071 outliers.attr('class', 'nv-boxplot-outlier');
paulo@89 2072 outliers
paulo@89 2073 .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
paulo@89 2074 .attr('cx', x.rangeBand() * .45)
paulo@89 2075 .attr('cy', function(d,i,j) { return y(d); })
paulo@89 2076 .attr('r', '3');
paulo@89 2077 outliers.exit().remove();
paulo@89 2078
paulo@89 2079 var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); };
paulo@89 2080 var box_left = function() { return x.rangeBand() * .45 - box_width()/2; };
paulo@89 2081 var box_right = function() { return x.rangeBand() * .45 + box_width()/2; };
paulo@89 2082
paulo@89 2083 // update whisker lines and ticks
paulo@89 2084 ['low', 'high'].forEach(function(key) {
paulo@89 2085 var endpoint = (key === 'low') ? 'Q1' : 'Q3';
paulo@89 2086
paulo@89 2087 boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
paulo@89 2088 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
paulo@89 2089 .attr('x1', x.rangeBand() * .45 )
paulo@89 2090 .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
paulo@89 2091 .attr('x2', x.rangeBand() * .45 )
paulo@89 2092 .attr('y2', function(d,i) { return y(d.values[endpoint]); });
paulo@89 2093
paulo@89 2094 boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
paulo@89 2095 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
paulo@89 2096 .attr('x1', box_left )
paulo@89 2097 .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
paulo@89 2098 .attr('x2', box_right )
paulo@89 2099 .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); });
paulo@89 2100 });
paulo@89 2101
paulo@89 2102 ['low', 'high'].forEach(function(key) {
paulo@89 2103 boxEnter.selectAll('.nv-boxplot-' + key)
paulo@89 2104 .on('mouseover', function(d,i,j) {
paulo@89 2105 d3.select(this).classed('hover', true);
paulo@89 2106 dispatch.elementMouseover({
paulo@89 2107 series: { key: d.values['whisker_' + key], color: color(d,j) },
paulo@89 2108 e: d3.event
paulo@89 2109 });
paulo@89 2110 })
paulo@89 2111 .on('mouseout', function(d,i,j) {
paulo@89 2112 d3.select(this).classed('hover', false);
paulo@89 2113 dispatch.elementMouseout({
paulo@89 2114 series: { key: d.values['whisker_' + key], color: color(d,j) },
paulo@89 2115 e: d3.event
paulo@89 2116 });
paulo@89 2117 })
paulo@89 2118 .on('mousemove', function(d,i) {
paulo@89 2119 dispatch.elementMousemove({e: d3.event});
paulo@89 2120 });
paulo@89 2121 });
paulo@89 2122
paulo@89 2123 // boxes
paulo@89 2124 boxEnter.append('rect')
paulo@89 2125 .attr('class', 'nv-boxplot-box')
paulo@89 2126 // tooltip events
paulo@89 2127 .on('mouseover', function(d,i) {
paulo@89 2128 d3.select(this).classed('hover', true);
paulo@89 2129 dispatch.elementMouseover({
paulo@89 2130 key: d.label,
paulo@89 2131 value: d.label,
paulo@89 2132 series: [
paulo@89 2133 { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
paulo@89 2134 { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
paulo@89 2135 { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
paulo@89 2136 ],
paulo@89 2137 data: d,
paulo@89 2138 index: i,
paulo@89 2139 e: d3.event
paulo@89 2140 });
paulo@89 2141 })
paulo@89 2142 .on('mouseout', function(d,i) {
paulo@89 2143 d3.select(this).classed('hover', false);
paulo@89 2144 dispatch.elementMouseout({
paulo@89 2145 key: d.label,
paulo@89 2146 value: d.label,
paulo@89 2147 series: [
paulo@89 2148 { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
paulo@89 2149 { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
paulo@89 2150 { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
paulo@89 2151 ],
paulo@89 2152 data: d,
paulo@89 2153 index: i,
paulo@89 2154 e: d3.event
paulo@89 2155 });
paulo@89 2156 })
paulo@89 2157 .on('mousemove', function(d,i) {
paulo@89 2158 dispatch.elementMousemove({e: d3.event});
paulo@89 2159 });
paulo@89 2160
paulo@89 2161 // box transitions
paulo@89 2162 boxplots.select('rect.nv-boxplot-box')
paulo@89 2163 .watchTransition(renderWatch, 'nv-boxplot: boxes')
paulo@89 2164 .attr('y', function(d,i) { return y(d.values.Q3); })
paulo@89 2165 .attr('width', box_width)
paulo@89 2166 .attr('x', box_left )
paulo@89 2167
paulo@89 2168 .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 })
paulo@89 2169 .style('fill', function(d,i) { return d.color || color(d,i) })
paulo@89 2170 .style('stroke', function(d,i) { return d.color || color(d,i) });
paulo@89 2171
paulo@89 2172 // median line
paulo@89 2173 boxEnter.append('line').attr('class', 'nv-boxplot-median');
paulo@89 2174
paulo@89 2175 boxplots.select('line.nv-boxplot-median')
paulo@89 2176 .watchTransition(renderWatch, 'nv-boxplot: boxplots line')
paulo@89 2177 .attr('x1', box_left)
paulo@89 2178 .attr('y1', function(d,i) { return y(d.values.Q2); })
paulo@89 2179 .attr('x2', box_right)
paulo@89 2180 .attr('y2', function(d,i) { return y(d.values.Q2); });
paulo@89 2181
paulo@89 2182 //store old scales for use in transitions on update
paulo@89 2183 x0 = x.copy();
paulo@89 2184 y0 = y.copy();
paulo@89 2185 });
paulo@89 2186
paulo@89 2187 renderWatch.renderEnd('nv-boxplot immediate');
paulo@89 2188 return chart;
paulo@89 2189 }
paulo@89 2190
paulo@89 2191 //============================================================
paulo@89 2192 // Expose Public Variables
paulo@89 2193 //------------------------------------------------------------
paulo@89 2194
paulo@89 2195 chart.dispatch = dispatch;
paulo@89 2196 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 2197
paulo@89 2198 chart._options = Object.create({}, {
paulo@89 2199 // simple options, just get/set the necessary values
paulo@89 2200 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 2201 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 2202 maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}},
paulo@89 2203 x: {get: function(){return getX;}, set: function(_){getX=_;}},
paulo@89 2204 y: {get: function(){return getY;}, set: function(_){getY=_;}},
paulo@89 2205 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 2206 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 2207 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 2208 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 2209 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 2210 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 2211 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 2212 // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
paulo@89 2213
paulo@89 2214 // options that require extra logic in the setter
paulo@89 2215 margin: {get: function(){return margin;}, set: function(_){
paulo@89 2216 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 2217 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 2218 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 2219 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 2220 }},
paulo@89 2221 color: {get: function(){return color;}, set: function(_){
paulo@89 2222 color = nv.utils.getColor(_);
paulo@89 2223 }},
paulo@89 2224 duration: {get: function(){return duration;}, set: function(_){
paulo@89 2225 duration = _;
paulo@89 2226 renderWatch.reset(duration);
paulo@89 2227 }}
paulo@89 2228 });
paulo@89 2229
paulo@89 2230 nv.utils.initOptions(chart);
paulo@89 2231
paulo@89 2232 return chart;
paulo@89 2233 };
paulo@89 2234 nv.models.boxPlotChart = function() {
paulo@89 2235 "use strict";
paulo@89 2236
paulo@89 2237 //============================================================
paulo@89 2238 // Public Variables with Default Settings
paulo@89 2239 //------------------------------------------------------------
paulo@89 2240
paulo@89 2241 var boxplot = nv.models.boxPlot()
paulo@89 2242 , xAxis = nv.models.axis()
paulo@89 2243 , yAxis = nv.models.axis()
paulo@89 2244 ;
paulo@89 2245
paulo@89 2246 var margin = {top: 15, right: 10, bottom: 50, left: 60}
paulo@89 2247 , width = null
paulo@89 2248 , height = null
paulo@89 2249 , color = nv.utils.getColor()
paulo@89 2250 , showXAxis = true
paulo@89 2251 , showYAxis = true
paulo@89 2252 , rightAlignYAxis = false
paulo@89 2253 , staggerLabels = false
paulo@89 2254 , tooltip = nv.models.tooltip()
paulo@89 2255 , x
paulo@89 2256 , y
paulo@89 2257 , noData = "No Data Available."
paulo@89 2258 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate', 'renderEnd')
paulo@89 2259 , duration = 250
paulo@89 2260 ;
paulo@89 2261
paulo@89 2262 xAxis
paulo@89 2263 .orient('bottom')
paulo@89 2264 .showMaxMin(false)
paulo@89 2265 .tickFormat(function(d) { return d })
paulo@89 2266 ;
paulo@89 2267 yAxis
paulo@89 2268 .orient((rightAlignYAxis) ? 'right' : 'left')
paulo@89 2269 .tickFormat(d3.format(',.1f'))
paulo@89 2270 ;
paulo@89 2271
paulo@89 2272 tooltip.duration(0);
paulo@89 2273
paulo@89 2274 //============================================================
paulo@89 2275 // Private Variables
paulo@89 2276 //------------------------------------------------------------
paulo@89 2277
paulo@89 2278 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 2279
paulo@89 2280 function chart(selection) {
paulo@89 2281 renderWatch.reset();
paulo@89 2282 renderWatch.models(boxplot);
paulo@89 2283 if (showXAxis) renderWatch.models(xAxis);
paulo@89 2284 if (showYAxis) renderWatch.models(yAxis);
paulo@89 2285
paulo@89 2286 selection.each(function(data) {
paulo@89 2287 var container = d3.select(this),
paulo@89 2288 that = this;
paulo@89 2289 nv.utils.initSVG(container);
paulo@89 2290 var availableWidth = (width || parseInt(container.style('width')) || 960)
paulo@89 2291 - margin.left - margin.right,
paulo@89 2292 availableHeight = (height || parseInt(container.style('height')) || 400)
paulo@89 2293 - margin.top - margin.bottom;
paulo@89 2294
paulo@89 2295 chart.update = function() {
paulo@89 2296 dispatch.beforeUpdate();
paulo@89 2297 container.transition().duration(duration).call(chart);
paulo@89 2298 };
paulo@89 2299 chart.container = this;
paulo@89 2300
paulo@89 2301 // Display No Data message if there's nothing to show. (quartiles required at minimum)
paulo@89 2302 if (!data || !data.length ||
paulo@89 2303 !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) {
paulo@89 2304 var noDataText = container.selectAll('.nv-noData').data([noData]);
paulo@89 2305
paulo@89 2306 noDataText.enter().append('text')
paulo@89 2307 .attr('class', 'nvd3 nv-noData')
paulo@89 2308 .attr('dy', '-.7em')
paulo@89 2309 .style('text-anchor', 'middle');
paulo@89 2310
paulo@89 2311 noDataText
paulo@89 2312 .attr('x', margin.left + availableWidth / 2)
paulo@89 2313 .attr('y', margin.top + availableHeight / 2)
paulo@89 2314 .text(function(d) { return d });
paulo@89 2315
paulo@89 2316 return chart;
paulo@89 2317 } else {
paulo@89 2318 container.selectAll('.nv-noData').remove();
paulo@89 2319 }
paulo@89 2320
paulo@89 2321 // Setup Scales
paulo@89 2322 x = boxplot.xScale();
paulo@89 2323 y = boxplot.yScale().clamp(true);
paulo@89 2324
paulo@89 2325 // Setup containers and skeleton of chart
paulo@89 2326 var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]);
paulo@89 2327 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g');
paulo@89 2328 var defsEnter = gEnter.append('defs');
paulo@89 2329 var g = wrap.select('g');
paulo@89 2330
paulo@89 2331 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 2332 gEnter.append('g').attr('class', 'nv-y nv-axis')
paulo@89 2333 .append('g').attr('class', 'nv-zeroLine')
paulo@89 2334 .append('line');
paulo@89 2335
paulo@89 2336 gEnter.append('g').attr('class', 'nv-barsWrap');
paulo@89 2337
paulo@89 2338 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 2339
paulo@89 2340 if (rightAlignYAxis) {
paulo@89 2341 g.select(".nv-y.nv-axis")
paulo@89 2342 .attr("transform", "translate(" + availableWidth + ",0)");
paulo@89 2343 }
paulo@89 2344
paulo@89 2345 // Main Chart Component(s)
paulo@89 2346 boxplot
paulo@89 2347 .width(availableWidth)
paulo@89 2348 .height(availableHeight);
paulo@89 2349
paulo@89 2350 var barsWrap = g.select('.nv-barsWrap')
paulo@89 2351 .datum(data.filter(function(d) { return !d.disabled }))
paulo@89 2352
paulo@89 2353 barsWrap.transition().call(boxplot);
paulo@89 2354
paulo@89 2355
paulo@89 2356 defsEnter.append('clipPath')
paulo@89 2357 .attr('id', 'nv-x-label-clip-' + boxplot.id())
paulo@89 2358 .append('rect');
paulo@89 2359
paulo@89 2360 g.select('#nv-x-label-clip-' + boxplot.id() + ' rect')
paulo@89 2361 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
paulo@89 2362 .attr('height', 16)
paulo@89 2363 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
paulo@89 2364
paulo@89 2365 // Setup Axes
paulo@89 2366 if (showXAxis) {
paulo@89 2367 xAxis
paulo@89 2368 .scale(x)
paulo@89 2369 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 2370 .tickSize(-availableHeight, 0);
paulo@89 2371
paulo@89 2372 g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')');
paulo@89 2373 g.select('.nv-x.nv-axis').call(xAxis);
paulo@89 2374
paulo@89 2375 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
paulo@89 2376 if (staggerLabels) {
paulo@89 2377 xTicks
paulo@89 2378 .selectAll('text')
paulo@89 2379 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
paulo@89 2380 }
paulo@89 2381 }
paulo@89 2382
paulo@89 2383 if (showYAxis) {
paulo@89 2384 yAxis
paulo@89 2385 .scale(y)
paulo@89 2386 .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data
paulo@89 2387 .tickSize( -availableWidth, 0);
paulo@89 2388
paulo@89 2389 g.select('.nv-y.nv-axis').call(yAxis);
paulo@89 2390 }
paulo@89 2391
paulo@89 2392 // Zero line
paulo@89 2393 g.select(".nv-zeroLine line")
paulo@89 2394 .attr("x1",0)
paulo@89 2395 .attr("x2",availableWidth)
paulo@89 2396 .attr("y1", y(0))
paulo@89 2397 .attr("y2", y(0))
paulo@89 2398 ;
paulo@89 2399
paulo@89 2400 //============================================================
paulo@89 2401 // Event Handling/Dispatching (in chart's scope)
paulo@89 2402 //------------------------------------------------------------
paulo@89 2403 });
paulo@89 2404
paulo@89 2405 renderWatch.renderEnd('nv-boxplot chart immediate');
paulo@89 2406 return chart;
paulo@89 2407 }
paulo@89 2408
paulo@89 2409 //============================================================
paulo@89 2410 // Event Handling/Dispatching (out of chart's scope)
paulo@89 2411 //------------------------------------------------------------
paulo@89 2412
paulo@89 2413 boxplot.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 2414 tooltip.data(evt).hidden(false);
paulo@89 2415 });
paulo@89 2416
paulo@89 2417 boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 2418 tooltip.data(evt).hidden(true);
paulo@89 2419 });
paulo@89 2420
paulo@89 2421 boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 2422 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 2423 });
paulo@89 2424
paulo@89 2425 //============================================================
paulo@89 2426 // Expose Public Variables
paulo@89 2427 //------------------------------------------------------------
paulo@89 2428
paulo@89 2429 chart.dispatch = dispatch;
paulo@89 2430 chart.boxplot = boxplot;
paulo@89 2431 chart.xAxis = xAxis;
paulo@89 2432 chart.yAxis = yAxis;
paulo@89 2433 chart.tooltip = tooltip;
paulo@89 2434
paulo@89 2435 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 2436
paulo@89 2437 chart._options = Object.create({}, {
paulo@89 2438 // simple options, just get/set the necessary values
paulo@89 2439 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 2440 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 2441 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
paulo@89 2442 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 2443 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 2444 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
paulo@89 2445 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
paulo@89 2446 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 2447
paulo@89 2448 // options that require extra logic in the setter
paulo@89 2449 margin: {get: function(){return margin;}, set: function(_){
paulo@89 2450 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 2451 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 2452 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 2453 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 2454 }},
paulo@89 2455 duration: {get: function(){return duration;}, set: function(_){
paulo@89 2456 duration = _;
paulo@89 2457 renderWatch.reset(duration);
paulo@89 2458 boxplot.duration(duration);
paulo@89 2459 xAxis.duration(duration);
paulo@89 2460 yAxis.duration(duration);
paulo@89 2461 }},
paulo@89 2462 color: {get: function(){return color;}, set: function(_){
paulo@89 2463 color = nv.utils.getColor(_);
paulo@89 2464 boxplot.color(color);
paulo@89 2465 }},
paulo@89 2466 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
paulo@89 2467 rightAlignYAxis = _;
paulo@89 2468 yAxis.orient( (_) ? 'right' : 'left');
paulo@89 2469 }}
paulo@89 2470 });
paulo@89 2471
paulo@89 2472 nv.utils.inheritOptions(chart, boxplot);
paulo@89 2473 nv.utils.initOptions(chart);
paulo@89 2474
paulo@89 2475 return chart;
paulo@89 2476 }
paulo@89 2477 // Chart design based on the recommendations of Stephen Few. Implementation
paulo@89 2478 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
paulo@89 2479 // http://projects.instantcognition.com/protovis/bulletchart/
paulo@89 2480
paulo@89 2481 nv.models.bullet = function() {
paulo@89 2482 "use strict";
paulo@89 2483
paulo@89 2484 //============================================================
paulo@89 2485 // Public Variables with Default Settings
paulo@89 2486 //------------------------------------------------------------
paulo@89 2487
paulo@89 2488 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 2489 , orient = 'left' // TODO top & bottom
paulo@89 2490 , reverse = false
paulo@89 2491 , ranges = function(d) { return d.ranges }
paulo@89 2492 , markers = function(d) { return d.markers ? d.markers : [0] }
paulo@89 2493 , measures = function(d) { return d.measures }
paulo@89 2494 , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
paulo@89 2495 , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
paulo@89 2496 , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
paulo@89 2497 , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
paulo@89 2498 , width = 380
paulo@89 2499 , height = 30
paulo@89 2500 , container = null
paulo@89 2501 , tickFormat = null
paulo@89 2502 , color = nv.utils.getColor(['#1f77b4'])
paulo@89 2503 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove')
paulo@89 2504 ;
paulo@89 2505
paulo@89 2506 function chart(selection) {
paulo@89 2507 selection.each(function(d, i) {
paulo@89 2508 var availableWidth = width - margin.left - margin.right,
paulo@89 2509 availableHeight = height - margin.top - margin.bottom;
paulo@89 2510
paulo@89 2511 container = d3.select(this);
paulo@89 2512 nv.utils.initSVG(container);
paulo@89 2513
paulo@89 2514 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
paulo@89 2515 markerz = markers.call(this, d, i).slice().sort(d3.descending),
paulo@89 2516 measurez = measures.call(this, d, i).slice().sort(d3.descending),
paulo@89 2517 rangeLabelz = rangeLabels.call(this, d, i).slice(),
paulo@89 2518 markerLabelz = markerLabels.call(this, d, i).slice(),
paulo@89 2519 measureLabelz = measureLabels.call(this, d, i).slice();
paulo@89 2520
paulo@89 2521 // Setup Scales
paulo@89 2522 // Compute the new x-scale.
paulo@89 2523 var x1 = d3.scale.linear()
paulo@89 2524 .domain( d3.extent(d3.merge([forceX, rangez])) )
paulo@89 2525 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
paulo@89 2526
paulo@89 2527 // Retrieve the old x-scale, if this is an update.
paulo@89 2528 var x0 = this.__chart__ || d3.scale.linear()
paulo@89 2529 .domain([0, Infinity])
paulo@89 2530 .range(x1.range());
paulo@89 2531
paulo@89 2532 // Stash the new scale.
paulo@89 2533 this.__chart__ = x1;
paulo@89 2534
paulo@89 2535 var rangeMin = d3.min(rangez), //rangez[2]
paulo@89 2536 rangeMax = d3.max(rangez), //rangez[0]
paulo@89 2537 rangeAvg = rangez[1];
paulo@89 2538
paulo@89 2539 // Setup containers and skeleton of chart
paulo@89 2540 var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
paulo@89 2541 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
paulo@89 2542 var gEnter = wrapEnter.append('g');
paulo@89 2543 var g = wrap.select('g');
paulo@89 2544
paulo@89 2545 gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
paulo@89 2546 gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
paulo@89 2547 gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
paulo@89 2548 gEnter.append('rect').attr('class', 'nv-measure');
paulo@89 2549
paulo@89 2550 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 2551
paulo@89 2552 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
paulo@89 2553 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
paulo@89 2554 var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
paulo@89 2555 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
paulo@89 2556
paulo@89 2557 g.select('rect.nv-rangeMax')
paulo@89 2558 .attr('height', availableHeight)
paulo@89 2559 .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
paulo@89 2560 .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
paulo@89 2561 .datum(rangeMax > 0 ? rangeMax : rangeMin)
paulo@89 2562
paulo@89 2563 g.select('rect.nv-rangeAvg')
paulo@89 2564 .attr('height', availableHeight)
paulo@89 2565 .attr('width', w1(rangeAvg))
paulo@89 2566 .attr('x', xp1(rangeAvg))
paulo@89 2567 .datum(rangeAvg)
paulo@89 2568
paulo@89 2569 g.select('rect.nv-rangeMin')
paulo@89 2570 .attr('height', availableHeight)
paulo@89 2571 .attr('width', w1(rangeMax))
paulo@89 2572 .attr('x', xp1(rangeMax))
paulo@89 2573 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
paulo@89 2574 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
paulo@89 2575 .datum(rangeMax > 0 ? rangeMin : rangeMax)
paulo@89 2576
paulo@89 2577 g.select('rect.nv-measure')
paulo@89 2578 .style('fill', color)
paulo@89 2579 .attr('height', availableHeight / 3)
paulo@89 2580 .attr('y', availableHeight / 3)
paulo@89 2581 .attr('width', measurez < 0 ?
paulo@89 2582 x1(0) - x1(measurez[0])
paulo@89 2583 : x1(measurez[0]) - x1(0))
paulo@89 2584 .attr('x', xp1(measurez))
paulo@89 2585 .on('mouseover', function() {
paulo@89 2586 dispatch.elementMouseover({
paulo@89 2587 value: measurez[0],
paulo@89 2588 label: measureLabelz[0] || 'Current',
paulo@89 2589 color: d3.select(this).style("fill")
paulo@89 2590 })
paulo@89 2591 })
paulo@89 2592 .on('mousemove', function() {
paulo@89 2593 dispatch.elementMousemove({
paulo@89 2594 value: measurez[0],
paulo@89 2595 label: measureLabelz[0] || 'Current',
paulo@89 2596 color: d3.select(this).style("fill")
paulo@89 2597 })
paulo@89 2598 })
paulo@89 2599 .on('mouseout', function() {
paulo@89 2600 dispatch.elementMouseout({
paulo@89 2601 value: measurez[0],
paulo@89 2602 label: measureLabelz[0] || 'Current',
paulo@89 2603 color: d3.select(this).style("fill")
paulo@89 2604 })
paulo@89 2605 });
paulo@89 2606
paulo@89 2607 var h3 = availableHeight / 6;
paulo@89 2608
paulo@89 2609 var markerData = markerz.map( function(marker, index) {
paulo@89 2610 return {value: marker, label: markerLabelz[index]}
paulo@89 2611 });
paulo@89 2612 gEnter
paulo@89 2613 .selectAll("path.nv-markerTriangle")
paulo@89 2614 .data(markerData)
paulo@89 2615 .enter()
paulo@89 2616 .append('path')
paulo@89 2617 .attr('class', 'nv-markerTriangle')
paulo@89 2618 .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' })
paulo@89 2619 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
paulo@89 2620 .on('mouseover', function(d) {
paulo@89 2621 dispatch.elementMouseover({
paulo@89 2622 value: d.value,
paulo@89 2623 label: d.label || 'Previous',
paulo@89 2624 color: d3.select(this).style("fill"),
paulo@89 2625 pos: [x1(d.value), availableHeight/2]
paulo@89 2626 })
paulo@89 2627
paulo@89 2628 })
paulo@89 2629 .on('mousemove', function(d) {
paulo@89 2630 dispatch.elementMousemove({
paulo@89 2631 value: d.value,
paulo@89 2632 label: d.label || 'Previous',
paulo@89 2633 color: d3.select(this).style("fill")
paulo@89 2634 })
paulo@89 2635 })
paulo@89 2636 .on('mouseout', function(d, i) {
paulo@89 2637 dispatch.elementMouseout({
paulo@89 2638 value: d.value,
paulo@89 2639 label: d.label || 'Previous',
paulo@89 2640 color: d3.select(this).style("fill")
paulo@89 2641 })
paulo@89 2642 });
paulo@89 2643
paulo@89 2644 wrap.selectAll('.nv-range')
paulo@89 2645 .on('mouseover', function(d,i) {
paulo@89 2646 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
paulo@89 2647 dispatch.elementMouseover({
paulo@89 2648 value: d,
paulo@89 2649 label: label,
paulo@89 2650 color: d3.select(this).style("fill")
paulo@89 2651 })
paulo@89 2652 })
paulo@89 2653 .on('mousemove', function() {
paulo@89 2654 dispatch.elementMousemove({
paulo@89 2655 value: measurez[0],
paulo@89 2656 label: measureLabelz[0] || 'Previous',
paulo@89 2657 color: d3.select(this).style("fill")
paulo@89 2658 })
paulo@89 2659 })
paulo@89 2660 .on('mouseout', function(d,i) {
paulo@89 2661 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
paulo@89 2662 dispatch.elementMouseout({
paulo@89 2663 value: d,
paulo@89 2664 label: label,
paulo@89 2665 color: d3.select(this).style("fill")
paulo@89 2666 })
paulo@89 2667 });
paulo@89 2668 });
paulo@89 2669
paulo@89 2670 return chart;
paulo@89 2671 }
paulo@89 2672
paulo@89 2673 //============================================================
paulo@89 2674 // Expose Public Variables
paulo@89 2675 //------------------------------------------------------------
paulo@89 2676
paulo@89 2677 chart.dispatch = dispatch;
paulo@89 2678 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 2679
paulo@89 2680 chart._options = Object.create({}, {
paulo@89 2681 // simple options, just get/set the necessary values
paulo@89 2682 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
paulo@89 2683 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
paulo@89 2684 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
paulo@89 2685 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
paulo@89 2686 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 2687 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 2688 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
paulo@89 2689
paulo@89 2690 // options that require extra logic in the setter
paulo@89 2691 margin: {get: function(){return margin;}, set: function(_){
paulo@89 2692 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 2693 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 2694 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 2695 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 2696 }},
paulo@89 2697 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
paulo@89 2698 orient = _;
paulo@89 2699 reverse = orient == 'right' || orient == 'bottom';
paulo@89 2700 }},
paulo@89 2701 color: {get: function(){return color;}, set: function(_){
paulo@89 2702 color = nv.utils.getColor(_);
paulo@89 2703 }}
paulo@89 2704 });
paulo@89 2705
paulo@89 2706 nv.utils.initOptions(chart);
paulo@89 2707 return chart;
paulo@89 2708 };
paulo@89 2709
paulo@89 2710
paulo@89 2711
paulo@89 2712 // Chart design based on the recommendations of Stephen Few. Implementation
paulo@89 2713 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
paulo@89 2714 // http://projects.instantcognition.com/protovis/bulletchart/
paulo@89 2715 nv.models.bulletChart = function() {
paulo@89 2716 "use strict";
paulo@89 2717
paulo@89 2718 //============================================================
paulo@89 2719 // Public Variables with Default Settings
paulo@89 2720 //------------------------------------------------------------
paulo@89 2721
paulo@89 2722 var bullet = nv.models.bullet();
paulo@89 2723 var tooltip = nv.models.tooltip();
paulo@89 2724
paulo@89 2725 var orient = 'left' // TODO top & bottom
paulo@89 2726 , reverse = false
paulo@89 2727 , margin = {top: 5, right: 40, bottom: 20, left: 120}
paulo@89 2728 , ranges = function(d) { return d.ranges }
paulo@89 2729 , markers = function(d) { return d.markers ? d.markers : [0] }
paulo@89 2730 , measures = function(d) { return d.measures }
paulo@89 2731 , width = null
paulo@89 2732 , height = 55
paulo@89 2733 , tickFormat = null
paulo@89 2734 , ticks = null
paulo@89 2735 , noData = null
paulo@89 2736 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
paulo@89 2737 ;
paulo@89 2738
paulo@89 2739 tooltip.duration(0).headerEnabled(false);
paulo@89 2740
paulo@89 2741 function chart(selection) {
paulo@89 2742 selection.each(function(d, i) {
paulo@89 2743 var container = d3.select(this);
paulo@89 2744 nv.utils.initSVG(container);
paulo@89 2745
paulo@89 2746 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 2747 availableHeight = height - margin.top - margin.bottom,
paulo@89 2748 that = this;
paulo@89 2749
paulo@89 2750 chart.update = function() { chart(selection) };
paulo@89 2751 chart.container = this;
paulo@89 2752
paulo@89 2753 // Display No Data message if there's nothing to show.
paulo@89 2754 if (!d || !ranges.call(this, d, i)) {
paulo@89 2755 nv.utils.noData(chart, container)
paulo@89 2756 return chart;
paulo@89 2757 } else {
paulo@89 2758 container.selectAll('.nv-noData').remove();
paulo@89 2759 }
paulo@89 2760
paulo@89 2761 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
paulo@89 2762 markerz = markers.call(this, d, i).slice().sort(d3.descending),
paulo@89 2763 measurez = measures.call(this, d, i).slice().sort(d3.descending);
paulo@89 2764
paulo@89 2765 // Setup containers and skeleton of chart
paulo@89 2766 var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
paulo@89 2767 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
paulo@89 2768 var gEnter = wrapEnter.append('g');
paulo@89 2769 var g = wrap.select('g');
paulo@89 2770
paulo@89 2771 gEnter.append('g').attr('class', 'nv-bulletWrap');
paulo@89 2772 gEnter.append('g').attr('class', 'nv-titles');
paulo@89 2773
paulo@89 2774 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 2775
paulo@89 2776 // Compute the new x-scale.
paulo@89 2777 var x1 = d3.scale.linear()
paulo@89 2778 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
paulo@89 2779 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
paulo@89 2780
paulo@89 2781 // Retrieve the old x-scale, if this is an update.
paulo@89 2782 var x0 = this.__chart__ || d3.scale.linear()
paulo@89 2783 .domain([0, Infinity])
paulo@89 2784 .range(x1.range());
paulo@89 2785
paulo@89 2786 // Stash the new scale.
paulo@89 2787 this.__chart__ = x1;
paulo@89 2788
paulo@89 2789 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
paulo@89 2790 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
paulo@89 2791
paulo@89 2792 var title = gEnter.select('.nv-titles').append('g')
paulo@89 2793 .attr('text-anchor', 'end')
paulo@89 2794 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
paulo@89 2795 title.append('text')
paulo@89 2796 .attr('class', 'nv-title')
paulo@89 2797 .text(function(d) { return d.title; });
paulo@89 2798
paulo@89 2799 title.append('text')
paulo@89 2800 .attr('class', 'nv-subtitle')
paulo@89 2801 .attr('dy', '1em')
paulo@89 2802 .text(function(d) { return d.subtitle; });
paulo@89 2803
paulo@89 2804 bullet
paulo@89 2805 .width(availableWidth)
paulo@89 2806 .height(availableHeight)
paulo@89 2807
paulo@89 2808 var bulletWrap = g.select('.nv-bulletWrap');
paulo@89 2809 d3.transition(bulletWrap).call(bullet);
paulo@89 2810
paulo@89 2811 // Compute the tick format.
paulo@89 2812 var format = tickFormat || x1.tickFormat( availableWidth / 100 );
paulo@89 2813
paulo@89 2814 // Update the tick groups.
paulo@89 2815 var tick = g.selectAll('g.nv-tick')
paulo@89 2816 .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) {
paulo@89 2817 return this.textContent || format(d);
paulo@89 2818 });
paulo@89 2819
paulo@89 2820 // Initialize the ticks with the old scale, x0.
paulo@89 2821 var tickEnter = tick.enter().append('g')
paulo@89 2822 .attr('class', 'nv-tick')
paulo@89 2823 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
paulo@89 2824 .style('opacity', 1e-6);
paulo@89 2825
paulo@89 2826 tickEnter.append('line')
paulo@89 2827 .attr('y1', availableHeight)
paulo@89 2828 .attr('y2', availableHeight * 7 / 6);
paulo@89 2829
paulo@89 2830 tickEnter.append('text')
paulo@89 2831 .attr('text-anchor', 'middle')
paulo@89 2832 .attr('dy', '1em')
paulo@89 2833 .attr('y', availableHeight * 7 / 6)
paulo@89 2834 .text(format);
paulo@89 2835
paulo@89 2836 // Transition the updating ticks to the new scale, x1.
paulo@89 2837 var tickUpdate = d3.transition(tick)
paulo@89 2838 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
paulo@89 2839 .style('opacity', 1);
paulo@89 2840
paulo@89 2841 tickUpdate.select('line')
paulo@89 2842 .attr('y1', availableHeight)
paulo@89 2843 .attr('y2', availableHeight * 7 / 6);
paulo@89 2844
paulo@89 2845 tickUpdate.select('text')
paulo@89 2846 .attr('y', availableHeight * 7 / 6);
paulo@89 2847
paulo@89 2848 // Transition the exiting ticks to the new scale, x1.
paulo@89 2849 d3.transition(tick.exit())
paulo@89 2850 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
paulo@89 2851 .style('opacity', 1e-6)
paulo@89 2852 .remove();
paulo@89 2853 });
paulo@89 2854
paulo@89 2855 d3.timer.flush();
paulo@89 2856 return chart;
paulo@89 2857 }
paulo@89 2858
paulo@89 2859 //============================================================
paulo@89 2860 // Event Handling/Dispatching (out of chart's scope)
paulo@89 2861 //------------------------------------------------------------
paulo@89 2862
paulo@89 2863 bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 2864 evt['series'] = {
paulo@89 2865 key: evt.label,
paulo@89 2866 value: evt.value,
paulo@89 2867 color: evt.color
paulo@89 2868 };
paulo@89 2869 tooltip.data(evt).hidden(false);
paulo@89 2870 });
paulo@89 2871
paulo@89 2872 bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 2873 tooltip.hidden(true);
paulo@89 2874 });
paulo@89 2875
paulo@89 2876 bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 2877 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 2878 });
paulo@89 2879
paulo@89 2880 //============================================================
paulo@89 2881 // Expose Public Variables
paulo@89 2882 //------------------------------------------------------------
paulo@89 2883
paulo@89 2884 chart.bullet = bullet;
paulo@89 2885 chart.dispatch = dispatch;
paulo@89 2886 chart.tooltip = tooltip;
paulo@89 2887
paulo@89 2888 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 2889
paulo@89 2890 chart._options = Object.create({}, {
paulo@89 2891 // simple options, just get/set the necessary values
paulo@89 2892 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
paulo@89 2893 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
paulo@89 2894 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
paulo@89 2895 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 2896 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 2897 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
paulo@89 2898 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
paulo@89 2899 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 2900
paulo@89 2901 // deprecated options
paulo@89 2902 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 2903 // deprecated after 1.7.1
paulo@89 2904 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 2905 tooltip.enabled(!!_);
paulo@89 2906 }},
paulo@89 2907 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 2908 // deprecated after 1.7.1
paulo@89 2909 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 2910 tooltip.contentGenerator(_);
paulo@89 2911 }},
paulo@89 2912
paulo@89 2913 // options that require extra logic in the setter
paulo@89 2914 margin: {get: function(){return margin;}, set: function(_){
paulo@89 2915 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 2916 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 2917 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 2918 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 2919 }},
paulo@89 2920 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
paulo@89 2921 orient = _;
paulo@89 2922 reverse = orient == 'right' || orient == 'bottom';
paulo@89 2923 }}
paulo@89 2924 });
paulo@89 2925
paulo@89 2926 nv.utils.inheritOptions(chart, bullet);
paulo@89 2927 nv.utils.initOptions(chart);
paulo@89 2928
paulo@89 2929 return chart;
paulo@89 2930 };
paulo@89 2931
paulo@89 2932
paulo@89 2933
paulo@89 2934 nv.models.candlestickBar = function() {
paulo@89 2935 "use strict";
paulo@89 2936
paulo@89 2937 //============================================================
paulo@89 2938 // Public Variables with Default Settings
paulo@89 2939 //------------------------------------------------------------
paulo@89 2940
paulo@89 2941 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 2942 , width = null
paulo@89 2943 , height = null
paulo@89 2944 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 2945 , container
paulo@89 2946 , x = d3.scale.linear()
paulo@89 2947 , y = d3.scale.linear()
paulo@89 2948 , getX = function(d) { return d.x }
paulo@89 2949 , getY = function(d) { return d.y }
paulo@89 2950 , getOpen = function(d) { return d.open }
paulo@89 2951 , getClose = function(d) { return d.close }
paulo@89 2952 , getHigh = function(d) { return d.high }
paulo@89 2953 , getLow = function(d) { return d.low }
paulo@89 2954 , forceX = []
paulo@89 2955 , forceY = []
paulo@89 2956 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
paulo@89 2957 , clipEdge = true
paulo@89 2958 , color = nv.utils.defaultColor()
paulo@89 2959 , interactive = false
paulo@89 2960 , xDomain
paulo@89 2961 , yDomain
paulo@89 2962 , xRange
paulo@89 2963 , yRange
paulo@89 2964 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
paulo@89 2965 ;
paulo@89 2966
paulo@89 2967 //============================================================
paulo@89 2968 // Private Variables
paulo@89 2969 //------------------------------------------------------------
paulo@89 2970
paulo@89 2971 function chart(selection) {
paulo@89 2972 selection.each(function(data) {
paulo@89 2973 container = d3.select(this);
paulo@89 2974 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 2975 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 2976
paulo@89 2977 nv.utils.initSVG(container);
paulo@89 2978
paulo@89 2979 // Width of the candlestick bars.
paulo@89 2980 var barWidth = (availableWidth / data[0].values.length) * .45;
paulo@89 2981
paulo@89 2982 // Setup Scales
paulo@89 2983 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
paulo@89 2984
paulo@89 2985 if (padData)
paulo@89 2986 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
paulo@89 2987 else
paulo@89 2988 x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]);
paulo@89 2989
paulo@89 2990 y.domain(yDomain || [
paulo@89 2991 d3.min(data[0].values.map(getLow).concat(forceY)),
paulo@89 2992 d3.max(data[0].values.map(getHigh).concat(forceY))
paulo@89 2993 ]
paulo@89 2994 ).range(yRange || [availableHeight, 0]);
paulo@89 2995
paulo@89 2996 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
paulo@89 2997 if (x.domain()[0] === x.domain()[1])
paulo@89 2998 x.domain()[0] ?
paulo@89 2999 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
paulo@89 3000 : x.domain([-1,1]);
paulo@89 3001
paulo@89 3002 if (y.domain()[0] === y.domain()[1])
paulo@89 3003 y.domain()[0] ?
paulo@89 3004 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
paulo@89 3005 : y.domain([-1,1]);
paulo@89 3006
paulo@89 3007 // Setup containers and skeleton of chart
paulo@89 3008 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]);
paulo@89 3009 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar');
paulo@89 3010 var defsEnter = wrapEnter.append('defs');
paulo@89 3011 var gEnter = wrapEnter.append('g');
paulo@89 3012 var g = wrap.select('g');
paulo@89 3013
paulo@89 3014 gEnter.append('g').attr('class', 'nv-ticks');
paulo@89 3015
paulo@89 3016 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 3017
paulo@89 3018 container
paulo@89 3019 .on('click', function(d,i) {
paulo@89 3020 dispatch.chartClick({
paulo@89 3021 data: d,
paulo@89 3022 index: i,
paulo@89 3023 pos: d3.event,
paulo@89 3024 id: id
paulo@89 3025 });
paulo@89 3026 });
paulo@89 3027
paulo@89 3028 defsEnter.append('clipPath')
paulo@89 3029 .attr('id', 'nv-chart-clip-path-' + id)
paulo@89 3030 .append('rect');
paulo@89 3031
paulo@89 3032 wrap.select('#nv-chart-clip-path-' + id + ' rect')
paulo@89 3033 .attr('width', availableWidth)
paulo@89 3034 .attr('height', availableHeight);
paulo@89 3035
paulo@89 3036 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
paulo@89 3037
paulo@89 3038 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
paulo@89 3039 .data(function(d) { return d });
paulo@89 3040 ticks.exit().remove();
paulo@89 3041
paulo@89 3042 // The colors are currently controlled by CSS.
paulo@89 3043 var tickGroups = ticks.enter().append('g')
paulo@89 3044 .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i});
paulo@89 3045
paulo@89 3046 var lines = tickGroups.append('line')
paulo@89 3047 .attr('class', 'nv-candlestick-lines')
paulo@89 3048 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
paulo@89 3049 .attr('x1', 0)
paulo@89 3050 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
paulo@89 3051 .attr('x2', 0)
paulo@89 3052 .attr('y2', function(d, i) { return y(getLow(d, i)); });
paulo@89 3053
paulo@89 3054 var rects = tickGroups.append('rect')
paulo@89 3055 .attr('class', 'nv-candlestick-rects nv-bars')
paulo@89 3056 .attr('transform', function(d, i) {
paulo@89 3057 return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
paulo@89 3058 + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
paulo@89 3059 + ')';
paulo@89 3060 })
paulo@89 3061 .attr('x', 0)
paulo@89 3062 .attr('y', 0)
paulo@89 3063 .attr('width', barWidth)
paulo@89 3064 .attr('height', function(d, i) {
paulo@89 3065 var open = getOpen(d, i);
paulo@89 3066 var close = getClose(d, i);
paulo@89 3067 return open > close ? y(close) - y(open) : y(open) - y(close);
paulo@89 3068 });
paulo@89 3069
paulo@89 3070 container.selectAll('.nv-candlestick-lines').transition()
paulo@89 3071 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
paulo@89 3072 .attr('x1', 0)
paulo@89 3073 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
paulo@89 3074 .attr('x2', 0)
paulo@89 3075 .attr('y2', function(d, i) { return y(getLow(d, i)); });
paulo@89 3076
paulo@89 3077 container.selectAll('.nv-candlestick-rects').transition()
paulo@89 3078 .attr('transform', function(d, i) {
paulo@89 3079 return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
paulo@89 3080 + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
paulo@89 3081 + ')';
paulo@89 3082 })
paulo@89 3083 .attr('x', 0)
paulo@89 3084 .attr('y', 0)
paulo@89 3085 .attr('width', barWidth)
paulo@89 3086 .attr('height', function(d, i) {
paulo@89 3087 var open = getOpen(d, i);
paulo@89 3088 var close = getClose(d, i);
paulo@89 3089 return open > close ? y(close) - y(open) : y(open) - y(close);
paulo@89 3090 });
paulo@89 3091 });
paulo@89 3092
paulo@89 3093 return chart;
paulo@89 3094 }
paulo@89 3095
paulo@89 3096
paulo@89 3097 //Create methods to allow outside functions to highlight a specific bar.
paulo@89 3098 chart.highlightPoint = function(pointIndex, isHoverOver) {
paulo@89 3099 chart.clearHighlights();
paulo@89 3100 container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex)
paulo@89 3101 .classed("hover", isHoverOver)
paulo@89 3102 ;
paulo@89 3103 };
paulo@89 3104
paulo@89 3105 chart.clearHighlights = function() {
paulo@89 3106 container.select(".nv-candlestickBar .nv-tick.hover")
paulo@89 3107 .classed("hover", false)
paulo@89 3108 ;
paulo@89 3109 };
paulo@89 3110
paulo@89 3111 //============================================================
paulo@89 3112 // Expose Public Variables
paulo@89 3113 //------------------------------------------------------------
paulo@89 3114
paulo@89 3115 chart.dispatch = dispatch;
paulo@89 3116 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 3117
paulo@89 3118 chart._options = Object.create({}, {
paulo@89 3119 // simple options, just get/set the necessary values
paulo@89 3120 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 3121 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 3122 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 3123 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 3124 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 3125 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 3126 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 3127 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 3128 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
paulo@89 3129 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
paulo@89 3130 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
paulo@89 3131 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
paulo@89 3132 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 3133 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
paulo@89 3134
paulo@89 3135 x: {get: function(){return getX;}, set: function(_){getX=_;}},
paulo@89 3136 y: {get: function(){return getY;}, set: function(_){getY=_;}},
paulo@89 3137 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
paulo@89 3138 close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
paulo@89 3139 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
paulo@89 3140 low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
paulo@89 3141
paulo@89 3142 // options that require extra logic in the setter
paulo@89 3143 margin: {get: function(){return margin;}, set: function(_){
paulo@89 3144 margin.top = _.top != undefined ? _.top : margin.top;
paulo@89 3145 margin.right = _.right != undefined ? _.right : margin.right;
paulo@89 3146 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
paulo@89 3147 margin.left = _.left != undefined ? _.left : margin.left;
paulo@89 3148 }},
paulo@89 3149 color: {get: function(){return color;}, set: function(_){
paulo@89 3150 color = nv.utils.getColor(_);
paulo@89 3151 }}
paulo@89 3152 });
paulo@89 3153
paulo@89 3154 nv.utils.initOptions(chart);
paulo@89 3155 return chart;
paulo@89 3156 };
paulo@89 3157
paulo@89 3158 nv.models.cumulativeLineChart = function() {
paulo@89 3159 "use strict";
paulo@89 3160
paulo@89 3161 //============================================================
paulo@89 3162 // Public Variables with Default Settings
paulo@89 3163 //------------------------------------------------------------
paulo@89 3164
paulo@89 3165 var lines = nv.models.line()
paulo@89 3166 , xAxis = nv.models.axis()
paulo@89 3167 , yAxis = nv.models.axis()
paulo@89 3168 , legend = nv.models.legend()
paulo@89 3169 , controls = nv.models.legend()
paulo@89 3170 , interactiveLayer = nv.interactiveGuideline()
paulo@89 3171 , tooltip = nv.models.tooltip()
paulo@89 3172 ;
paulo@89 3173
paulo@89 3174 var margin = {top: 30, right: 30, bottom: 50, left: 60}
paulo@89 3175 , color = nv.utils.defaultColor()
paulo@89 3176 , width = null
paulo@89 3177 , height = null
paulo@89 3178 , showLegend = true
paulo@89 3179 , showXAxis = true
paulo@89 3180 , showYAxis = true
paulo@89 3181 , rightAlignYAxis = false
paulo@89 3182 , showControls = true
paulo@89 3183 , useInteractiveGuideline = false
paulo@89 3184 , rescaleY = true
paulo@89 3185 , x //can be accessed via chart.xScale()
paulo@89 3186 , y //can be accessed via chart.yScale()
paulo@89 3187 , id = lines.id()
paulo@89 3188 , state = nv.utils.state()
paulo@89 3189 , defaultState = null
paulo@89 3190 , noData = null
paulo@89 3191 , average = function(d) { return d.average }
paulo@89 3192 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
paulo@89 3193 , transitionDuration = 250
paulo@89 3194 , duration = 250
paulo@89 3195 , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
paulo@89 3196 ;
paulo@89 3197
paulo@89 3198 state.index = 0;
paulo@89 3199 state.rescaleY = rescaleY;
paulo@89 3200
paulo@89 3201 xAxis.orient('bottom').tickPadding(7);
paulo@89 3202 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
paulo@89 3203
paulo@89 3204 tooltip.valueFormatter(function(d, i) {
paulo@89 3205 return yAxis.tickFormat()(d, i);
paulo@89 3206 }).headerFormatter(function(d, i) {
paulo@89 3207 return xAxis.tickFormat()(d, i);
paulo@89 3208 });
paulo@89 3209
paulo@89 3210 controls.updateState(false);
paulo@89 3211
paulo@89 3212 //============================================================
paulo@89 3213 // Private Variables
paulo@89 3214 //------------------------------------------------------------
paulo@89 3215
paulo@89 3216 var dx = d3.scale.linear()
paulo@89 3217 , index = {i: 0, x: 0}
paulo@89 3218 , renderWatch = nv.utils.renderWatch(dispatch, duration)
paulo@89 3219 ;
paulo@89 3220
paulo@89 3221 var stateGetter = function(data) {
paulo@89 3222 return function(){
paulo@89 3223 return {
paulo@89 3224 active: data.map(function(d) { return !d.disabled }),
paulo@89 3225 index: index.i,
paulo@89 3226 rescaleY: rescaleY
paulo@89 3227 };
paulo@89 3228 }
paulo@89 3229 };
paulo@89 3230
paulo@89 3231 var stateSetter = function(data) {
paulo@89 3232 return function(state) {
paulo@89 3233 if (state.index !== undefined)
paulo@89 3234 index.i = state.index;
paulo@89 3235 if (state.rescaleY !== undefined)
paulo@89 3236 rescaleY = state.rescaleY;
paulo@89 3237 if (state.active !== undefined)
paulo@89 3238 data.forEach(function(series,i) {
paulo@89 3239 series.disabled = !state.active[i];
paulo@89 3240 });
paulo@89 3241 }
paulo@89 3242 };
paulo@89 3243
paulo@89 3244 function chart(selection) {
paulo@89 3245 renderWatch.reset();
paulo@89 3246 renderWatch.models(lines);
paulo@89 3247 if (showXAxis) renderWatch.models(xAxis);
paulo@89 3248 if (showYAxis) renderWatch.models(yAxis);
paulo@89 3249 selection.each(function(data) {
paulo@89 3250 var container = d3.select(this);
paulo@89 3251 nv.utils.initSVG(container);
paulo@89 3252 container.classed('nv-chart-' + id, true);
paulo@89 3253 var that = this;
paulo@89 3254
paulo@89 3255 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 3256 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 3257
paulo@89 3258 chart.update = function() {
paulo@89 3259 if (duration === 0)
paulo@89 3260 container.call(chart);
paulo@89 3261 else
paulo@89 3262 container.transition().duration(duration).call(chart)
paulo@89 3263 };
paulo@89 3264 chart.container = this;
paulo@89 3265
paulo@89 3266 state
paulo@89 3267 .setter(stateSetter(data), chart.update)
paulo@89 3268 .getter(stateGetter(data))
paulo@89 3269 .update();
paulo@89 3270
paulo@89 3271 // DEPRECATED set state.disableddisabled
paulo@89 3272 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 3273
paulo@89 3274 if (!defaultState) {
paulo@89 3275 var key;
paulo@89 3276 defaultState = {};
paulo@89 3277 for (key in state) {
paulo@89 3278 if (state[key] instanceof Array)
paulo@89 3279 defaultState[key] = state[key].slice(0);
paulo@89 3280 else
paulo@89 3281 defaultState[key] = state[key];
paulo@89 3282 }
paulo@89 3283 }
paulo@89 3284
paulo@89 3285 var indexDrag = d3.behavior.drag()
paulo@89 3286 .on('dragstart', dragStart)
paulo@89 3287 .on('drag', dragMove)
paulo@89 3288 .on('dragend', dragEnd);
paulo@89 3289
paulo@89 3290
paulo@89 3291 function dragStart(d,i) {
paulo@89 3292 d3.select(chart.container)
paulo@89 3293 .style('cursor', 'ew-resize');
paulo@89 3294 }
paulo@89 3295
paulo@89 3296 function dragMove(d,i) {
paulo@89 3297 index.x = d3.event.x;
paulo@89 3298 index.i = Math.round(dx.invert(index.x));
paulo@89 3299 updateZero();
paulo@89 3300 }
paulo@89 3301
paulo@89 3302 function dragEnd(d,i) {
paulo@89 3303 d3.select(chart.container)
paulo@89 3304 .style('cursor', 'auto');
paulo@89 3305
paulo@89 3306 // update state and send stateChange with new index
paulo@89 3307 state.index = index.i;
paulo@89 3308 dispatch.stateChange(state);
paulo@89 3309 }
paulo@89 3310
paulo@89 3311 // Display No Data message if there's nothing to show.
paulo@89 3312 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 3313 nv.utils.noData(chart, container)
paulo@89 3314 return chart;
paulo@89 3315 } else {
paulo@89 3316 container.selectAll('.nv-noData').remove();
paulo@89 3317 }
paulo@89 3318
paulo@89 3319 // Setup Scales
paulo@89 3320 x = lines.xScale();
paulo@89 3321 y = lines.yScale();
paulo@89 3322
paulo@89 3323 if (!rescaleY) {
paulo@89 3324 var seriesDomains = data
paulo@89 3325 .filter(function(series) { return !series.disabled })
paulo@89 3326 .map(function(series,i) {
paulo@89 3327 var initialDomain = d3.extent(series.values, lines.y());
paulo@89 3328
paulo@89 3329 //account for series being disabled when losing 95% or more
paulo@89 3330 if (initialDomain[0] < -.95) initialDomain[0] = -.95;
paulo@89 3331
paulo@89 3332 return [
paulo@89 3333 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
paulo@89 3334 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
paulo@89 3335 ];
paulo@89 3336 });
paulo@89 3337
paulo@89 3338 var completeDomain = [
paulo@89 3339 d3.min(seriesDomains, function(d) { return d[0] }),
paulo@89 3340 d3.max(seriesDomains, function(d) { return d[1] })
paulo@89 3341 ];
paulo@89 3342
paulo@89 3343 lines.yDomain(completeDomain);
paulo@89 3344 } else {
paulo@89 3345 lines.yDomain(null);
paulo@89 3346 }
paulo@89 3347
paulo@89 3348 dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
paulo@89 3349 .range([0, availableWidth])
paulo@89 3350 .clamp(true);
paulo@89 3351
paulo@89 3352 var data = indexify(index.i, data);
paulo@89 3353
paulo@89 3354 // Setup containers and skeleton of chart
paulo@89 3355 var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
paulo@89 3356 var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
paulo@89 3357 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
paulo@89 3358 var g = wrap.select('g');
paulo@89 3359
paulo@89 3360 gEnter.append('g').attr('class', 'nv-interactive');
paulo@89 3361 gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
paulo@89 3362 gEnter.append('g').attr('class', 'nv-y nv-axis');
paulo@89 3363 gEnter.append('g').attr('class', 'nv-background');
paulo@89 3364 gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
paulo@89 3365 gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
paulo@89 3366 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 3367 gEnter.append('g').attr('class', 'nv-controlsWrap');
paulo@89 3368
paulo@89 3369 // Legend
paulo@89 3370 if (showLegend) {
paulo@89 3371 legend.width(availableWidth);
paulo@89 3372
paulo@89 3373 g.select('.nv-legendWrap')
paulo@89 3374 .datum(data)
paulo@89 3375 .call(legend);
paulo@89 3376
paulo@89 3377 if ( margin.top != legend.height()) {
paulo@89 3378 margin.top = legend.height();
paulo@89 3379 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 3380 }
paulo@89 3381
paulo@89 3382 g.select('.nv-legendWrap')
paulo@89 3383 .attr('transform', 'translate(0,' + (-margin.top) +')')
paulo@89 3384 }
paulo@89 3385
paulo@89 3386 // Controls
paulo@89 3387 if (showControls) {
paulo@89 3388 var controlsData = [
paulo@89 3389 { key: 'Re-scale y-axis', disabled: !rescaleY }
paulo@89 3390 ];
paulo@89 3391
paulo@89 3392 controls
paulo@89 3393 .width(140)
paulo@89 3394 .color(['#444', '#444', '#444'])
paulo@89 3395 .rightAlign(false)
paulo@89 3396 .margin({top: 5, right: 0, bottom: 5, left: 20})
paulo@89 3397 ;
paulo@89 3398
paulo@89 3399 g.select('.nv-controlsWrap')
paulo@89 3400 .datum(controlsData)
paulo@89 3401 .attr('transform', 'translate(0,' + (-margin.top) +')')
paulo@89 3402 .call(controls);
paulo@89 3403 }
paulo@89 3404
paulo@89 3405 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 3406
paulo@89 3407 if (rightAlignYAxis) {
paulo@89 3408 g.select(".nv-y.nv-axis")
paulo@89 3409 .attr("transform", "translate(" + availableWidth + ",0)");
paulo@89 3410 }
paulo@89 3411
paulo@89 3412 // Show error if series goes below 100%
paulo@89 3413 var tempDisabled = data.filter(function(d) { return d.tempDisabled });
paulo@89 3414
paulo@89 3415 wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
paulo@89 3416 if (tempDisabled.length) {
paulo@89 3417 wrap.append('text').attr('class', 'tempDisabled')
paulo@89 3418 .attr('x', availableWidth / 2)
paulo@89 3419 .attr('y', '-.71em')
paulo@89 3420 .style('text-anchor', 'end')
paulo@89 3421 .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
paulo@89 3422 }
paulo@89 3423
paulo@89 3424 //Set up interactive layer
paulo@89 3425 if (useInteractiveGuideline) {
paulo@89 3426 interactiveLayer
paulo@89 3427 .width(availableWidth)
paulo@89 3428 .height(availableHeight)
paulo@89 3429 .margin({left:margin.left,top:margin.top})
paulo@89 3430 .svgContainer(container)
paulo@89 3431 .xScale(x);
paulo@89 3432 wrap.select(".nv-interactive").call(interactiveLayer);
paulo@89 3433 }
paulo@89 3434
paulo@89 3435 gEnter.select('.nv-background')
paulo@89 3436 .append('rect');
paulo@89 3437
paulo@89 3438 g.select('.nv-background rect')
paulo@89 3439 .attr('width', availableWidth)
paulo@89 3440 .attr('height', availableHeight);
paulo@89 3441
paulo@89 3442 lines
paulo@89 3443 //.x(function(d) { return d.x })
paulo@89 3444 .y(function(d) { return d.display.y })
paulo@89 3445 .width(availableWidth)
paulo@89 3446 .height(availableHeight)
paulo@89 3447 .color(data.map(function(d,i) {
paulo@89 3448 return d.color || color(d, i);
paulo@89 3449 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
paulo@89 3450
paulo@89 3451 var linesWrap = g.select('.nv-linesWrap')
paulo@89 3452 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
paulo@89 3453
paulo@89 3454 linesWrap.call(lines);
paulo@89 3455
paulo@89 3456 //Store a series index number in the data array.
paulo@89 3457 data.forEach(function(d,i) {
paulo@89 3458 d.seriesIndex = i;
paulo@89 3459 });
paulo@89 3460
paulo@89 3461 var avgLineData = data.filter(function(d) {
paulo@89 3462 return !d.disabled && !!average(d);
paulo@89 3463 });
paulo@89 3464
paulo@89 3465 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
paulo@89 3466 .data(avgLineData, function(d) { return d.key; });
paulo@89 3467
paulo@89 3468 var getAvgLineY = function(d) {
paulo@89 3469 //If average lines go off the svg element, clamp them to the svg bounds.
paulo@89 3470 var yVal = y(average(d));
paulo@89 3471 if (yVal < 0) return 0;
paulo@89 3472 if (yVal > availableHeight) return availableHeight;
paulo@89 3473 return yVal;
paulo@89 3474 };
paulo@89 3475
paulo@89 3476 avgLines.enter()
paulo@89 3477 .append('line')
paulo@89 3478 .style('stroke-width',2)
paulo@89 3479 .style('stroke-dasharray','10,10')
paulo@89 3480 .style('stroke',function (d,i) {
paulo@89 3481 return lines.color()(d,d.seriesIndex);
paulo@89 3482 })
paulo@89 3483 .attr('x1',0)
paulo@89 3484 .attr('x2',availableWidth)
paulo@89 3485 .attr('y1', getAvgLineY)
paulo@89 3486 .attr('y2', getAvgLineY);
paulo@89 3487
paulo@89 3488 avgLines
paulo@89 3489 .style('stroke-opacity',function(d){
paulo@89 3490 //If average lines go offscreen, make them transparent
paulo@89 3491 var yVal = y(average(d));
paulo@89 3492 if (yVal < 0 || yVal > availableHeight) return 0;
paulo@89 3493 return 1;
paulo@89 3494 })
paulo@89 3495 .attr('x1',0)
paulo@89 3496 .attr('x2',availableWidth)
paulo@89 3497 .attr('y1', getAvgLineY)
paulo@89 3498 .attr('y2', getAvgLineY);
paulo@89 3499
paulo@89 3500 avgLines.exit().remove();
paulo@89 3501
paulo@89 3502 //Create index line
paulo@89 3503 var indexLine = linesWrap.selectAll('.nv-indexLine')
paulo@89 3504 .data([index]);
paulo@89 3505 indexLine.enter().append('rect').attr('class', 'nv-indexLine')
paulo@89 3506 .attr('width', 3)
paulo@89 3507 .attr('x', -2)
paulo@89 3508 .attr('fill', 'red')
paulo@89 3509 .attr('fill-opacity', .5)
paulo@89 3510 .style("pointer-events","all")
paulo@89 3511 .call(indexDrag);
paulo@89 3512
paulo@89 3513 indexLine
paulo@89 3514 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
paulo@89 3515 .attr('height', availableHeight);
paulo@89 3516
paulo@89 3517 // Setup Axes
paulo@89 3518 if (showXAxis) {
paulo@89 3519 xAxis
paulo@89 3520 .scale(x)
paulo@89 3521 ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
paulo@89 3522 .tickSize(-availableHeight, 0);
paulo@89 3523
paulo@89 3524 g.select('.nv-x.nv-axis')
paulo@89 3525 .attr('transform', 'translate(0,' + y.range()[0] + ')');
paulo@89 3526 g.select('.nv-x.nv-axis')
paulo@89 3527 .call(xAxis);
paulo@89 3528 }
paulo@89 3529
paulo@89 3530 if (showYAxis) {
paulo@89 3531 yAxis
paulo@89 3532 .scale(y)
paulo@89 3533 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
paulo@89 3534 .tickSize( -availableWidth, 0);
paulo@89 3535
paulo@89 3536 g.select('.nv-y.nv-axis')
paulo@89 3537 .call(yAxis);
paulo@89 3538 }
paulo@89 3539
paulo@89 3540 //============================================================
paulo@89 3541 // Event Handling/Dispatching (in chart's scope)
paulo@89 3542 //------------------------------------------------------------
paulo@89 3543
paulo@89 3544 function updateZero() {
paulo@89 3545 indexLine
paulo@89 3546 .data([index]);
paulo@89 3547
paulo@89 3548 //When dragging the index line, turn off line transitions.
paulo@89 3549 // Then turn them back on when done dragging.
paulo@89 3550 var oldDuration = chart.duration();
paulo@89 3551 chart.duration(0);
paulo@89 3552 chart.update();
paulo@89 3553 chart.duration(oldDuration);
paulo@89 3554 }
paulo@89 3555
paulo@89 3556 g.select('.nv-background rect')
paulo@89 3557 .on('click', function() {
paulo@89 3558 index.x = d3.mouse(this)[0];
paulo@89 3559 index.i = Math.round(dx.invert(index.x));
paulo@89 3560
paulo@89 3561 // update state and send stateChange with new index
paulo@89 3562 state.index = index.i;
paulo@89 3563 dispatch.stateChange(state);
paulo@89 3564
paulo@89 3565 updateZero();
paulo@89 3566 });
paulo@89 3567
paulo@89 3568 lines.dispatch.on('elementClick', function(e) {
paulo@89 3569 index.i = e.pointIndex;
paulo@89 3570 index.x = dx(index.i);
paulo@89 3571
paulo@89 3572 // update state and send stateChange with new index
paulo@89 3573 state.index = index.i;
paulo@89 3574 dispatch.stateChange(state);
paulo@89 3575
paulo@89 3576 updateZero();
paulo@89 3577 });
paulo@89 3578
paulo@89 3579 controls.dispatch.on('legendClick', function(d,i) {
paulo@89 3580 d.disabled = !d.disabled;
paulo@89 3581 rescaleY = !d.disabled;
paulo@89 3582
paulo@89 3583 state.rescaleY = rescaleY;
paulo@89 3584 dispatch.stateChange(state);
paulo@89 3585 chart.update();
paulo@89 3586 });
paulo@89 3587
paulo@89 3588 legend.dispatch.on('stateChange', function(newState) {
paulo@89 3589 for (var key in newState)
paulo@89 3590 state[key] = newState[key];
paulo@89 3591 dispatch.stateChange(state);
paulo@89 3592 chart.update();
paulo@89 3593 });
paulo@89 3594
paulo@89 3595 interactiveLayer.dispatch.on('elementMousemove', function(e) {
paulo@89 3596 lines.clearHighlights();
paulo@89 3597 var singlePoint, pointIndex, pointXLocation, allData = [];
paulo@89 3598
paulo@89 3599 data
paulo@89 3600 .filter(function(series, i) {
paulo@89 3601 series.seriesIndex = i;
paulo@89 3602 return !series.disabled;
paulo@89 3603 })
paulo@89 3604 .forEach(function(series,i) {
paulo@89 3605 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
paulo@89 3606 lines.highlightPoint(i, pointIndex, true);
paulo@89 3607 var point = series.values[pointIndex];
paulo@89 3608 if (typeof point === 'undefined') return;
paulo@89 3609 if (typeof singlePoint === 'undefined') singlePoint = point;
paulo@89 3610 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
paulo@89 3611 allData.push({
paulo@89 3612 key: series.key,
paulo@89 3613 value: chart.y()(point, pointIndex),
paulo@89 3614 color: color(series,series.seriesIndex)
paulo@89 3615 });
paulo@89 3616 });
paulo@89 3617
paulo@89 3618 //Highlight the tooltip entry based on which point the mouse is closest to.
paulo@89 3619 if (allData.length > 2) {
paulo@89 3620 var yValue = chart.yScale().invert(e.mouseY);
paulo@89 3621 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
paulo@89 3622 var threshold = 0.03 * domainExtent;
paulo@89 3623 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
paulo@89 3624 if (indexToHighlight !== null)
paulo@89 3625 allData[indexToHighlight].highlight = true;
paulo@89 3626 }
paulo@89 3627
paulo@89 3628 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
paulo@89 3629 interactiveLayer.tooltip
paulo@89 3630 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
paulo@89 3631 .chartContainer(that.parentNode)
paulo@89 3632 .valueFormatter(function(d,i) {
paulo@89 3633 return yAxis.tickFormat()(d);
paulo@89 3634 })
paulo@89 3635 .data(
paulo@89 3636 {
paulo@89 3637 value: xValue,
paulo@89 3638 series: allData
paulo@89 3639 }
paulo@89 3640 )();
paulo@89 3641
paulo@89 3642 interactiveLayer.renderGuideLine(pointXLocation);
paulo@89 3643 });
paulo@89 3644
paulo@89 3645 interactiveLayer.dispatch.on("elementMouseout",function(e) {
paulo@89 3646 lines.clearHighlights();
paulo@89 3647 });
paulo@89 3648
paulo@89 3649 // Update chart from a state object passed to event handler
paulo@89 3650 dispatch.on('changeState', function(e) {
paulo@89 3651 if (typeof e.disabled !== 'undefined') {
paulo@89 3652 data.forEach(function(series,i) {
paulo@89 3653 series.disabled = e.disabled[i];
paulo@89 3654 });
paulo@89 3655
paulo@89 3656 state.disabled = e.disabled;
paulo@89 3657 }
paulo@89 3658
paulo@89 3659 if (typeof e.index !== 'undefined') {
paulo@89 3660 index.i = e.index;
paulo@89 3661 index.x = dx(index.i);
paulo@89 3662
paulo@89 3663 state.index = e.index;
paulo@89 3664
paulo@89 3665 indexLine
paulo@89 3666 .data([index]);
paulo@89 3667 }
paulo@89 3668
paulo@89 3669 if (typeof e.rescaleY !== 'undefined') {
paulo@89 3670 rescaleY = e.rescaleY;
paulo@89 3671 }
paulo@89 3672
paulo@89 3673 chart.update();
paulo@89 3674 });
paulo@89 3675
paulo@89 3676 });
paulo@89 3677
paulo@89 3678 renderWatch.renderEnd('cumulativeLineChart immediate');
paulo@89 3679
paulo@89 3680 return chart;
paulo@89 3681 }
paulo@89 3682
paulo@89 3683 //============================================================
paulo@89 3684 // Event Handling/Dispatching (out of chart's scope)
paulo@89 3685 //------------------------------------------------------------
paulo@89 3686
paulo@89 3687 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 3688 var point = {
paulo@89 3689 x: chart.x()(evt.point),
paulo@89 3690 y: chart.y()(evt.point),
paulo@89 3691 color: evt.point.color
paulo@89 3692 };
paulo@89 3693 evt.point = point;
paulo@89 3694 tooltip.data(evt).position(evt.pos).hidden(false);
paulo@89 3695 });
paulo@89 3696
paulo@89 3697 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 3698 tooltip.hidden(true)
paulo@89 3699 });
paulo@89 3700
paulo@89 3701 //============================================================
paulo@89 3702 // Functions
paulo@89 3703 //------------------------------------------------------------
paulo@89 3704
paulo@89 3705 var indexifyYGetter = null;
paulo@89 3706 /* Normalize the data according to an index point. */
paulo@89 3707 function indexify(idx, data) {
paulo@89 3708 if (!indexifyYGetter) indexifyYGetter = lines.y();
paulo@89 3709 return data.map(function(line, i) {
paulo@89 3710 if (!line.values) {
paulo@89 3711 return line;
paulo@89 3712 }
paulo@89 3713 var indexValue = line.values[idx];
paulo@89 3714 if (indexValue == null) {
paulo@89 3715 return line;
paulo@89 3716 }
paulo@89 3717 var v = indexifyYGetter(indexValue, idx);
paulo@89 3718
paulo@89 3719 //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
paulo@89 3720 if (v < -.95 && !noErrorCheck) {
paulo@89 3721 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
paulo@89 3722
paulo@89 3723 line.tempDisabled = true;
paulo@89 3724 return line;
paulo@89 3725 }
paulo@89 3726
paulo@89 3727 line.tempDisabled = false;
paulo@89 3728
paulo@89 3729 line.values = line.values.map(function(point, pointIndex) {
paulo@89 3730 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
paulo@89 3731 return point;
paulo@89 3732 });
paulo@89 3733
paulo@89 3734 return line;
paulo@89 3735 })
paulo@89 3736 }
paulo@89 3737
paulo@89 3738 //============================================================
paulo@89 3739 // Expose Public Variables
paulo@89 3740 //------------------------------------------------------------
paulo@89 3741
paulo@89 3742 // expose chart's sub-components
paulo@89 3743 chart.dispatch = dispatch;
paulo@89 3744 chart.lines = lines;
paulo@89 3745 chart.legend = legend;
paulo@89 3746 chart.controls = controls;
paulo@89 3747 chart.xAxis = xAxis;
paulo@89 3748 chart.yAxis = yAxis;
paulo@89 3749 chart.interactiveLayer = interactiveLayer;
paulo@89 3750 chart.state = state;
paulo@89 3751 chart.tooltip = tooltip;
paulo@89 3752
paulo@89 3753 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 3754
paulo@89 3755 chart._options = Object.create({}, {
paulo@89 3756 // simple options, just get/set the necessary values
paulo@89 3757 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 3758 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 3759 rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
paulo@89 3760 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
paulo@89 3761 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 3762 average: {get: function(){return average;}, set: function(_){average=_;}},
paulo@89 3763 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 3764 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 3765 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 3766 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 3767 noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
paulo@89 3768
paulo@89 3769 // deprecated options
paulo@89 3770 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 3771 // deprecated after 1.7.1
paulo@89 3772 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 3773 tooltip.enabled(!!_);
paulo@89 3774 }},
paulo@89 3775 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 3776 // deprecated after 1.7.1
paulo@89 3777 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 3778 tooltip.contentGenerator(_);
paulo@89 3779 }},
paulo@89 3780
paulo@89 3781 // options that require extra logic in the setter
paulo@89 3782 margin: {get: function(){return margin;}, set: function(_){
paulo@89 3783 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 3784 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 3785 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 3786 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 3787 }},
paulo@89 3788 color: {get: function(){return color;}, set: function(_){
paulo@89 3789 color = nv.utils.getColor(_);
paulo@89 3790 legend.color(color);
paulo@89 3791 }},
paulo@89 3792 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
paulo@89 3793 useInteractiveGuideline = _;
paulo@89 3794 if (_ === true) {
paulo@89 3795 chart.interactive(false);
paulo@89 3796 chart.useVoronoi(false);
paulo@89 3797 }
paulo@89 3798 }},
paulo@89 3799 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
paulo@89 3800 rightAlignYAxis = _;
paulo@89 3801 yAxis.orient( (_) ? 'right' : 'left');
paulo@89 3802 }},
paulo@89 3803 duration: {get: function(){return duration;}, set: function(_){
paulo@89 3804 duration = _;
paulo@89 3805 lines.duration(duration);
paulo@89 3806 xAxis.duration(duration);
paulo@89 3807 yAxis.duration(duration);
paulo@89 3808 renderWatch.reset(duration);
paulo@89 3809 }}
paulo@89 3810 });
paulo@89 3811
paulo@89 3812 nv.utils.inheritOptions(chart, lines);
paulo@89 3813 nv.utils.initOptions(chart);
paulo@89 3814
paulo@89 3815 return chart;
paulo@89 3816 };
paulo@89 3817 //TODO: consider deprecating by adding necessary features to multiBar model
paulo@89 3818 nv.models.discreteBar = function() {
paulo@89 3819 "use strict";
paulo@89 3820
paulo@89 3821 //============================================================
paulo@89 3822 // Public Variables with Default Settings
paulo@89 3823 //------------------------------------------------------------
paulo@89 3824
paulo@89 3825 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 3826 , width = 960
paulo@89 3827 , height = 500
paulo@89 3828 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 3829 , container
paulo@89 3830 , x = d3.scale.ordinal()
paulo@89 3831 , y = d3.scale.linear()
paulo@89 3832 , getX = function(d) { return d.x }
paulo@89 3833 , getY = function(d) { return d.y }
paulo@89 3834 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
paulo@89 3835 , color = nv.utils.defaultColor()
paulo@89 3836 , showValues = false
paulo@89 3837 , valueFormat = d3.format(',.2f')
paulo@89 3838 , xDomain
paulo@89 3839 , yDomain
paulo@89 3840 , xRange
paulo@89 3841 , yRange
paulo@89 3842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
paulo@89 3843 , rectClass = 'discreteBar'
paulo@89 3844 , duration = 250
paulo@89 3845 ;
paulo@89 3846
paulo@89 3847 //============================================================
paulo@89 3848 // Private Variables
paulo@89 3849 //------------------------------------------------------------
paulo@89 3850
paulo@89 3851 var x0, y0;
paulo@89 3852 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 3853
paulo@89 3854 function chart(selection) {
paulo@89 3855 renderWatch.reset();
paulo@89 3856 selection.each(function(data) {
paulo@89 3857 var availableWidth = width - margin.left - margin.right,
paulo@89 3858 availableHeight = height - margin.top - margin.bottom;
paulo@89 3859
paulo@89 3860 container = d3.select(this);
paulo@89 3861 nv.utils.initSVG(container);
paulo@89 3862
paulo@89 3863 //add series index to each data point for reference
paulo@89 3864 data.forEach(function(series, i) {
paulo@89 3865 series.values.forEach(function(point) {
paulo@89 3866 point.series = i;
paulo@89 3867 });
paulo@89 3868 });
paulo@89 3869
paulo@89 3870 // Setup Scales
paulo@89 3871 // remap and flatten the data for use in calculating the scales' domains
paulo@89 3872 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
paulo@89 3873 data.map(function(d) {
paulo@89 3874 return d.values.map(function(d,i) {
paulo@89 3875 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
paulo@89 3876 })
paulo@89 3877 });
paulo@89 3878
paulo@89 3879 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
paulo@89 3880 .rangeBands(xRange || [0, availableWidth], .1);
paulo@89 3881 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
paulo@89 3882
paulo@89 3883 // If showValues, pad the Y axis range to account for label height
paulo@89 3884 if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
paulo@89 3885 else y.range(yRange || [availableHeight, 0]);
paulo@89 3886
paulo@89 3887 //store old scales if they exist
paulo@89 3888 x0 = x0 || x;
paulo@89 3889 y0 = y0 || y.copy().range([y(0),y(0)]);
paulo@89 3890
paulo@89 3891 // Setup containers and skeleton of chart
paulo@89 3892 var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
paulo@89 3893 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
paulo@89 3894 var gEnter = wrapEnter.append('g');
paulo@89 3895 var g = wrap.select('g');
paulo@89 3896
paulo@89 3897 gEnter.append('g').attr('class', 'nv-groups');
paulo@89 3898 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 3899
paulo@89 3900 //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
paulo@89 3901 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
paulo@89 3902 .data(function(d) { return d }, function(d) { return d.key });
paulo@89 3903 groups.enter().append('g')
paulo@89 3904 .style('stroke-opacity', 1e-6)
paulo@89 3905 .style('fill-opacity', 1e-6);
paulo@89 3906 groups.exit()
paulo@89 3907 .watchTransition(renderWatch, 'discreteBar: exit groups')
paulo@89 3908 .style('stroke-opacity', 1e-6)
paulo@89 3909 .style('fill-opacity', 1e-6)
paulo@89 3910 .remove();
paulo@89 3911 groups
paulo@89 3912 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
paulo@89 3913 .classed('hover', function(d) { return d.hover });
paulo@89 3914 groups
paulo@89 3915 .watchTransition(renderWatch, 'discreteBar: groups')
paulo@89 3916 .style('stroke-opacity', 1)
paulo@89 3917 .style('fill-opacity', .75);
paulo@89 3918
paulo@89 3919 var bars = groups.selectAll('g.nv-bar')
paulo@89 3920 .data(function(d) { return d.values });
paulo@89 3921 bars.exit().remove();
paulo@89 3922
paulo@89 3923 var barsEnter = bars.enter().append('g')
paulo@89 3924 .attr('transform', function(d,i,j) {
paulo@89 3925 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
paulo@89 3926 })
paulo@89 3927 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
paulo@89 3928 d3.select(this).classed('hover', true);
paulo@89 3929 dispatch.elementMouseover({
paulo@89 3930 data: d,
paulo@89 3931 index: i,
paulo@89 3932 color: d3.select(this).style("fill")
paulo@89 3933 });
paulo@89 3934 })
paulo@89 3935 .on('mouseout', function(d,i) {
paulo@89 3936 d3.select(this).classed('hover', false);
paulo@89 3937 dispatch.elementMouseout({
paulo@89 3938 data: d,
paulo@89 3939 index: i,
paulo@89 3940 color: d3.select(this).style("fill")
paulo@89 3941 });
paulo@89 3942 })
paulo@89 3943 .on('mousemove', function(d,i) {
paulo@89 3944 dispatch.elementMousemove({
paulo@89 3945 data: d,
paulo@89 3946 index: i,
paulo@89 3947 color: d3.select(this).style("fill")
paulo@89 3948 });
paulo@89 3949 })
paulo@89 3950 .on('click', function(d,i) {
paulo@89 3951 dispatch.elementClick({
paulo@89 3952 data: d,
paulo@89 3953 index: i,
paulo@89 3954 color: d3.select(this).style("fill")
paulo@89 3955 });
paulo@89 3956 d3.event.stopPropagation();
paulo@89 3957 })
paulo@89 3958 .on('dblclick', function(d,i) {
paulo@89 3959 dispatch.elementDblClick({
paulo@89 3960 data: d,
paulo@89 3961 index: i,
paulo@89 3962 color: d3.select(this).style("fill")
paulo@89 3963 });
paulo@89 3964 d3.event.stopPropagation();
paulo@89 3965 });
paulo@89 3966
paulo@89 3967 barsEnter.append('rect')
paulo@89 3968 .attr('height', 0)
paulo@89 3969 .attr('width', x.rangeBand() * .9 / data.length )
paulo@89 3970
paulo@89 3971 if (showValues) {
paulo@89 3972 barsEnter.append('text')
paulo@89 3973 .attr('text-anchor', 'middle')
paulo@89 3974 ;
paulo@89 3975
paulo@89 3976 bars.select('text')
paulo@89 3977 .text(function(d,i) { return valueFormat(getY(d,i)) })
paulo@89 3978 .watchTransition(renderWatch, 'discreteBar: bars text')
paulo@89 3979 .attr('x', x.rangeBand() * .9 / 2)
paulo@89 3980 .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
paulo@89 3981
paulo@89 3982 ;
paulo@89 3983 } else {
paulo@89 3984 bars.selectAll('text').remove();
paulo@89 3985 }
paulo@89 3986
paulo@89 3987 bars
paulo@89 3988 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
paulo@89 3989 .style('fill', function(d,i) { return d.color || color(d,i) })
paulo@89 3990 .style('stroke', function(d,i) { return d.color || color(d,i) })
paulo@89 3991 .select('rect')
paulo@89 3992 .attr('class', rectClass)
paulo@89 3993 .watchTransition(renderWatch, 'discreteBar: bars rect')
paulo@89 3994 .attr('width', x.rangeBand() * .9 / data.length);
paulo@89 3995 bars.watchTransition(renderWatch, 'discreteBar: bars')
paulo@89 3996 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
paulo@89 3997 .attr('transform', function(d,i) {
paulo@89 3998 var left = x(getX(d,i)) + x.rangeBand() * .05,
paulo@89 3999 top = getY(d,i) < 0 ?
paulo@89 4000 y(0) :
paulo@89 4001 y(0) - y(getY(d,i)) < 1 ?
paulo@89 4002 y(0) - 1 : //make 1 px positive bars show up above y=0
paulo@89 4003 y(getY(d,i));
paulo@89 4004
paulo@89 4005 return 'translate(' + left + ', ' + top + ')'
paulo@89 4006 })
paulo@89 4007 .select('rect')
paulo@89 4008 .attr('height', function(d,i) {
paulo@89 4009 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
paulo@89 4010 });
paulo@89 4011
paulo@89 4012
paulo@89 4013 //store old scales for use in transitions on update
paulo@89 4014 x0 = x.copy();
paulo@89 4015 y0 = y.copy();
paulo@89 4016
paulo@89 4017 });
paulo@89 4018
paulo@89 4019 renderWatch.renderEnd('discreteBar immediate');
paulo@89 4020 return chart;
paulo@89 4021 }
paulo@89 4022
paulo@89 4023 //============================================================
paulo@89 4024 // Expose Public Variables
paulo@89 4025 //------------------------------------------------------------
paulo@89 4026
paulo@89 4027 chart.dispatch = dispatch;
paulo@89 4028 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 4029
paulo@89 4030 chart._options = Object.create({}, {
paulo@89 4031 // simple options, just get/set the necessary values
paulo@89 4032 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 4033 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 4034 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
paulo@89 4035 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
paulo@89 4036 x: {get: function(){return getX;}, set: function(_){getX=_;}},
paulo@89 4037 y: {get: function(){return getY;}, set: function(_){getY=_;}},
paulo@89 4038 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 4039 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 4040 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 4041 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 4042 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 4043 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 4044 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
paulo@89 4045 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 4046 rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
paulo@89 4047
paulo@89 4048 // options that require extra logic in the setter
paulo@89 4049 margin: {get: function(){return margin;}, set: function(_){
paulo@89 4050 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 4051 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 4052 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 4053 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 4054 }},
paulo@89 4055 color: {get: function(){return color;}, set: function(_){
paulo@89 4056 color = nv.utils.getColor(_);
paulo@89 4057 }},
paulo@89 4058 duration: {get: function(){return duration;}, set: function(_){
paulo@89 4059 duration = _;
paulo@89 4060 renderWatch.reset(duration);
paulo@89 4061 }}
paulo@89 4062 });
paulo@89 4063
paulo@89 4064 nv.utils.initOptions(chart);
paulo@89 4065
paulo@89 4066 return chart;
paulo@89 4067 };
paulo@89 4068
paulo@89 4069 nv.models.discreteBarChart = function() {
paulo@89 4070 "use strict";
paulo@89 4071
paulo@89 4072 //============================================================
paulo@89 4073 // Public Variables with Default Settings
paulo@89 4074 //------------------------------------------------------------
paulo@89 4075
paulo@89 4076 var discretebar = nv.models.discreteBar()
paulo@89 4077 , xAxis = nv.models.axis()
paulo@89 4078 , yAxis = nv.models.axis()
paulo@89 4079 , tooltip = nv.models.tooltip()
paulo@89 4080 ;
paulo@89 4081
paulo@89 4082 var margin = {top: 15, right: 10, bottom: 50, left: 60}
paulo@89 4083 , width = null
paulo@89 4084 , height = null
paulo@89 4085 , color = nv.utils.getColor()
paulo@89 4086 , showXAxis = true
paulo@89 4087 , showYAxis = true
paulo@89 4088 , rightAlignYAxis = false
paulo@89 4089 , staggerLabels = false
paulo@89 4090 , x
paulo@89 4091 , y
paulo@89 4092 , noData = null
paulo@89 4093 , dispatch = d3.dispatch('beforeUpdate','renderEnd')
paulo@89 4094 , duration = 250
paulo@89 4095 ;
paulo@89 4096
paulo@89 4097 xAxis
paulo@89 4098 .orient('bottom')
paulo@89 4099 .showMaxMin(false)
paulo@89 4100 .tickFormat(function(d) { return d })
paulo@89 4101 ;
paulo@89 4102 yAxis
paulo@89 4103 .orient((rightAlignYAxis) ? 'right' : 'left')
paulo@89 4104 .tickFormat(d3.format(',.1f'))
paulo@89 4105 ;
paulo@89 4106
paulo@89 4107 tooltip
paulo@89 4108 .duration(0)
paulo@89 4109 .headerEnabled(false)
paulo@89 4110 .valueFormatter(function(d, i) {
paulo@89 4111 return yAxis.tickFormat()(d, i);
paulo@89 4112 })
paulo@89 4113 .keyFormatter(function(d, i) {
paulo@89 4114 return xAxis.tickFormat()(d, i);
paulo@89 4115 });
paulo@89 4116
paulo@89 4117 //============================================================
paulo@89 4118 // Private Variables
paulo@89 4119 //------------------------------------------------------------
paulo@89 4120
paulo@89 4121 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 4122
paulo@89 4123 function chart(selection) {
paulo@89 4124 renderWatch.reset();
paulo@89 4125 renderWatch.models(discretebar);
paulo@89 4126 if (showXAxis) renderWatch.models(xAxis);
paulo@89 4127 if (showYAxis) renderWatch.models(yAxis);
paulo@89 4128
paulo@89 4129 selection.each(function(data) {
paulo@89 4130 var container = d3.select(this),
paulo@89 4131 that = this;
paulo@89 4132 nv.utils.initSVG(container);
paulo@89 4133 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 4134 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 4135
paulo@89 4136 chart.update = function() {
paulo@89 4137 dispatch.beforeUpdate();
paulo@89 4138 container.transition().duration(duration).call(chart);
paulo@89 4139 };
paulo@89 4140 chart.container = this;
paulo@89 4141
paulo@89 4142 // Display No Data message if there's nothing to show.
paulo@89 4143 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 4144 nv.utils.noData(chart, container);
paulo@89 4145 return chart;
paulo@89 4146 } else {
paulo@89 4147 container.selectAll('.nv-noData').remove();
paulo@89 4148 }
paulo@89 4149
paulo@89 4150 // Setup Scales
paulo@89 4151 x = discretebar.xScale();
paulo@89 4152 y = discretebar.yScale().clamp(true);
paulo@89 4153
paulo@89 4154 // Setup containers and skeleton of chart
paulo@89 4155 var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
paulo@89 4156 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
paulo@89 4157 var defsEnter = gEnter.append('defs');
paulo@89 4158 var g = wrap.select('g');
paulo@89 4159
paulo@89 4160 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 4161 gEnter.append('g').attr('class', 'nv-y nv-axis')
paulo@89 4162 .append('g').attr('class', 'nv-zeroLine')
paulo@89 4163 .append('line');
paulo@89 4164
paulo@89 4165 gEnter.append('g').attr('class', 'nv-barsWrap');
paulo@89 4166
paulo@89 4167 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 4168
paulo@89 4169 if (rightAlignYAxis) {
paulo@89 4170 g.select(".nv-y.nv-axis")
paulo@89 4171 .attr("transform", "translate(" + availableWidth + ",0)");
paulo@89 4172 }
paulo@89 4173
paulo@89 4174 // Main Chart Component(s)
paulo@89 4175 discretebar
paulo@89 4176 .width(availableWidth)
paulo@89 4177 .height(availableHeight);
paulo@89 4178
paulo@89 4179 var barsWrap = g.select('.nv-barsWrap')
paulo@89 4180 .datum(data.filter(function(d) { return !d.disabled }));
paulo@89 4181
paulo@89 4182 barsWrap.transition().call(discretebar);
paulo@89 4183
paulo@89 4184
paulo@89 4185 defsEnter.append('clipPath')
paulo@89 4186 .attr('id', 'nv-x-label-clip-' + discretebar.id())
paulo@89 4187 .append('rect');
paulo@89 4188
paulo@89 4189 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
paulo@89 4190 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
paulo@89 4191 .attr('height', 16)
paulo@89 4192 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
paulo@89 4193
paulo@89 4194 // Setup Axes
paulo@89 4195 if (showXAxis) {
paulo@89 4196 xAxis
paulo@89 4197 .scale(x)
paulo@89 4198 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 4199 .tickSize(-availableHeight, 0);
paulo@89 4200
paulo@89 4201 g.select('.nv-x.nv-axis')
paulo@89 4202 .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
paulo@89 4203 g.select('.nv-x.nv-axis').call(xAxis);
paulo@89 4204
paulo@89 4205 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
paulo@89 4206 if (staggerLabels) {
paulo@89 4207 xTicks
paulo@89 4208 .selectAll('text')
paulo@89 4209 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
paulo@89 4210 }
paulo@89 4211 }
paulo@89 4212
paulo@89 4213 if (showYAxis) {
paulo@89 4214 yAxis
paulo@89 4215 .scale(y)
paulo@89 4216 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
paulo@89 4217 .tickSize( -availableWidth, 0);
paulo@89 4218
paulo@89 4219 g.select('.nv-y.nv-axis').call(yAxis);
paulo@89 4220 }
paulo@89 4221
paulo@89 4222 // Zero line
paulo@89 4223 g.select(".nv-zeroLine line")
paulo@89 4224 .attr("x1",0)
paulo@89 4225 .attr("x2",availableWidth)
paulo@89 4226 .attr("y1", y(0))
paulo@89 4227 .attr("y2", y(0))
paulo@89 4228 ;
paulo@89 4229 });
paulo@89 4230
paulo@89 4231 renderWatch.renderEnd('discreteBar chart immediate');
paulo@89 4232 return chart;
paulo@89 4233 }
paulo@89 4234
paulo@89 4235 //============================================================
paulo@89 4236 // Event Handling/Dispatching (out of chart's scope)
paulo@89 4237 //------------------------------------------------------------
paulo@89 4238
paulo@89 4239 discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 4240 evt['series'] = {
paulo@89 4241 key: chart.x()(evt.data),
paulo@89 4242 value: chart.y()(evt.data),
paulo@89 4243 color: evt.color
paulo@89 4244 };
paulo@89 4245 tooltip.data(evt).hidden(false);
paulo@89 4246 });
paulo@89 4247
paulo@89 4248 discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 4249 tooltip.hidden(true);
paulo@89 4250 });
paulo@89 4251
paulo@89 4252 discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 4253 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 4254 });
paulo@89 4255
paulo@89 4256 //============================================================
paulo@89 4257 // Expose Public Variables
paulo@89 4258 //------------------------------------------------------------
paulo@89 4259
paulo@89 4260 chart.dispatch = dispatch;
paulo@89 4261 chart.discretebar = discretebar;
paulo@89 4262 chart.xAxis = xAxis;
paulo@89 4263 chart.yAxis = yAxis;
paulo@89 4264 chart.tooltip = tooltip;
paulo@89 4265
paulo@89 4266 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 4267
paulo@89 4268 chart._options = Object.create({}, {
paulo@89 4269 // simple options, just get/set the necessary values
paulo@89 4270 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 4271 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 4272 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
paulo@89 4273 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 4274 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 4275 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 4276
paulo@89 4277 // deprecated options
paulo@89 4278 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 4279 // deprecated after 1.7.1
paulo@89 4280 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 4281 tooltip.enabled(!!_);
paulo@89 4282 }},
paulo@89 4283 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 4284 // deprecated after 1.7.1
paulo@89 4285 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 4286 tooltip.contentGenerator(_);
paulo@89 4287 }},
paulo@89 4288
paulo@89 4289 // options that require extra logic in the setter
paulo@89 4290 margin: {get: function(){return margin;}, set: function(_){
paulo@89 4291 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 4292 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 4293 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 4294 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 4295 }},
paulo@89 4296 duration: {get: function(){return duration;}, set: function(_){
paulo@89 4297 duration = _;
paulo@89 4298 renderWatch.reset(duration);
paulo@89 4299 discretebar.duration(duration);
paulo@89 4300 xAxis.duration(duration);
paulo@89 4301 yAxis.duration(duration);
paulo@89 4302 }},
paulo@89 4303 color: {get: function(){return color;}, set: function(_){
paulo@89 4304 color = nv.utils.getColor(_);
paulo@89 4305 discretebar.color(color);
paulo@89 4306 }},
paulo@89 4307 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
paulo@89 4308 rightAlignYAxis = _;
paulo@89 4309 yAxis.orient( (_) ? 'right' : 'left');
paulo@89 4310 }}
paulo@89 4311 });
paulo@89 4312
paulo@89 4313 nv.utils.inheritOptions(chart, discretebar);
paulo@89 4314 nv.utils.initOptions(chart);
paulo@89 4315
paulo@89 4316 return chart;
paulo@89 4317 }
paulo@89 4318
paulo@89 4319 nv.models.distribution = function() {
paulo@89 4320 "use strict";
paulo@89 4321 //============================================================
paulo@89 4322 // Public Variables with Default Settings
paulo@89 4323 //------------------------------------------------------------
paulo@89 4324
paulo@89 4325 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 4326 , width = 400 //technically width or height depending on x or y....
paulo@89 4327 , size = 8
paulo@89 4328 , axis = 'x' // 'x' or 'y'... horizontal or vertical
paulo@89 4329 , getData = function(d) { return d[axis] } // defaults d.x or d.y
paulo@89 4330 , color = nv.utils.defaultColor()
paulo@89 4331 , scale = d3.scale.linear()
paulo@89 4332 , domain
paulo@89 4333 , duration = 250
paulo@89 4334 , dispatch = d3.dispatch('renderEnd')
paulo@89 4335 ;
paulo@89 4336
paulo@89 4337 //============================================================
paulo@89 4338
paulo@89 4339
paulo@89 4340 //============================================================
paulo@89 4341 // Private Variables
paulo@89 4342 //------------------------------------------------------------
paulo@89 4343
paulo@89 4344 var scale0;
paulo@89 4345 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 4346
paulo@89 4347 //============================================================
paulo@89 4348
paulo@89 4349
paulo@89 4350 function chart(selection) {
paulo@89 4351 renderWatch.reset();
paulo@89 4352 selection.each(function(data) {
paulo@89 4353 var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
paulo@89 4354 naxis = axis == 'x' ? 'y' : 'x',
paulo@89 4355 container = d3.select(this);
paulo@89 4356 nv.utils.initSVG(container);
paulo@89 4357
paulo@89 4358 //------------------------------------------------------------
paulo@89 4359 // Setup Scales
paulo@89 4360
paulo@89 4361 scale0 = scale0 || scale;
paulo@89 4362
paulo@89 4363 //------------------------------------------------------------
paulo@89 4364
paulo@89 4365
paulo@89 4366 //------------------------------------------------------------
paulo@89 4367 // Setup containers and skeleton of chart
paulo@89 4368
paulo@89 4369 var wrap = container.selectAll('g.nv-distribution').data([data]);
paulo@89 4370 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
paulo@89 4371 var gEnter = wrapEnter.append('g');
paulo@89 4372 var g = wrap.select('g');
paulo@89 4373
paulo@89 4374 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
paulo@89 4375
paulo@89 4376 //------------------------------------------------------------
paulo@89 4377
paulo@89 4378
paulo@89 4379 var distWrap = g.selectAll('g.nv-dist')
paulo@89 4380 .data(function(d) { return d }, function(d) { return d.key });
paulo@89 4381
paulo@89 4382 distWrap.enter().append('g');
paulo@89 4383 distWrap
paulo@89 4384 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
paulo@89 4385 .style('stroke', function(d,i) { return color(d, i) });
paulo@89 4386
paulo@89 4387 var dist = distWrap.selectAll('line.nv-dist' + axis)
paulo@89 4388 .data(function(d) { return d.values })
paulo@89 4389 dist.enter().append('line')
paulo@89 4390 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
paulo@89 4391 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
paulo@89 4392 renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
paulo@89 4393 // .transition()
paulo@89 4394 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
paulo@89 4395 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
paulo@89 4396 .style('stroke-opacity', 0)
paulo@89 4397 .remove();
paulo@89 4398 dist
paulo@89 4399 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
paulo@89 4400 .attr(naxis + '1', 0)
paulo@89 4401 .attr(naxis + '2', size);
paulo@89 4402 renderWatch.transition(dist, 'dist')
paulo@89 4403 // .transition()
paulo@89 4404 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
paulo@89 4405 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
paulo@89 4406
paulo@89 4407
paulo@89 4408 scale0 = scale.copy();
paulo@89 4409
paulo@89 4410 });
paulo@89 4411 renderWatch.renderEnd('distribution immediate');
paulo@89 4412 return chart;
paulo@89 4413 }
paulo@89 4414
paulo@89 4415
paulo@89 4416 //============================================================
paulo@89 4417 // Expose Public Variables
paulo@89 4418 //------------------------------------------------------------
paulo@89 4419 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 4420 chart.dispatch = dispatch;
paulo@89 4421
paulo@89 4422 chart.margin = function(_) {
paulo@89 4423 if (!arguments.length) return margin;
paulo@89 4424 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
paulo@89 4425 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
paulo@89 4426 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
paulo@89 4427 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
paulo@89 4428 return chart;
paulo@89 4429 };
paulo@89 4430
paulo@89 4431 chart.width = function(_) {
paulo@89 4432 if (!arguments.length) return width;
paulo@89 4433 width = _;
paulo@89 4434 return chart;
paulo@89 4435 };
paulo@89 4436
paulo@89 4437 chart.axis = function(_) {
paulo@89 4438 if (!arguments.length) return axis;
paulo@89 4439 axis = _;
paulo@89 4440 return chart;
paulo@89 4441 };
paulo@89 4442
paulo@89 4443 chart.size = function(_) {
paulo@89 4444 if (!arguments.length) return size;
paulo@89 4445 size = _;
paulo@89 4446 return chart;
paulo@89 4447 };
paulo@89 4448
paulo@89 4449 chart.getData = function(_) {
paulo@89 4450 if (!arguments.length) return getData;
paulo@89 4451 getData = d3.functor(_);
paulo@89 4452 return chart;
paulo@89 4453 };
paulo@89 4454
paulo@89 4455 chart.scale = function(_) {
paulo@89 4456 if (!arguments.length) return scale;
paulo@89 4457 scale = _;
paulo@89 4458 return chart;
paulo@89 4459 };
paulo@89 4460
paulo@89 4461 chart.color = function(_) {
paulo@89 4462 if (!arguments.length) return color;
paulo@89 4463 color = nv.utils.getColor(_);
paulo@89 4464 return chart;
paulo@89 4465 };
paulo@89 4466
paulo@89 4467 chart.duration = function(_) {
paulo@89 4468 if (!arguments.length) return duration;
paulo@89 4469 duration = _;
paulo@89 4470 renderWatch.reset(duration);
paulo@89 4471 return chart;
paulo@89 4472 };
paulo@89 4473 //============================================================
paulo@89 4474
paulo@89 4475
paulo@89 4476 return chart;
paulo@89 4477 }
paulo@89 4478 nv.models.furiousLegend = function() {
paulo@89 4479 "use strict";
paulo@89 4480
paulo@89 4481 //============================================================
paulo@89 4482 // Public Variables with Default Settings
paulo@89 4483 //------------------------------------------------------------
paulo@89 4484
paulo@89 4485 var margin = {top: 5, right: 0, bottom: 5, left: 0}
paulo@89 4486 , width = 400
paulo@89 4487 , height = 20
paulo@89 4488 , getKey = function(d) { return d.key }
paulo@89 4489 , color = nv.utils.getColor()
paulo@89 4490 , align = true
paulo@89 4491 , padding = 28 //define how much space between legend items. - recommend 32 for furious version
paulo@89 4492 , rightAlign = true
paulo@89 4493 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
paulo@89 4494 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
paulo@89 4495 , expanded = false
paulo@89 4496 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
paulo@89 4497 , vers = 'classic' //Options are "classic" and "furious"
paulo@89 4498 ;
paulo@89 4499
paulo@89 4500 function chart(selection) {
paulo@89 4501 selection.each(function(data) {
paulo@89 4502 var availableWidth = width - margin.left - margin.right,
paulo@89 4503 container = d3.select(this);
paulo@89 4504 nv.utils.initSVG(container);
paulo@89 4505
paulo@89 4506 // Setup containers and skeleton of chart
paulo@89 4507 var wrap = container.selectAll('g.nv-legend').data([data]);
paulo@89 4508 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
paulo@89 4509 var g = wrap.select('g');
paulo@89 4510
paulo@89 4511 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 4512
paulo@89 4513 var series = g.selectAll('.nv-series')
paulo@89 4514 .data(function(d) {
paulo@89 4515 if(vers != 'furious') return d;
paulo@89 4516
paulo@89 4517 return d.filter(function(n) {
paulo@89 4518 return expanded ? true : !n.disengaged;
paulo@89 4519 });
paulo@89 4520 });
paulo@89 4521 var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
paulo@89 4522
paulo@89 4523 var seriesShape;
paulo@89 4524
paulo@89 4525 if(vers == 'classic') {
paulo@89 4526 seriesEnter.append('circle')
paulo@89 4527 .style('stroke-width', 2)
paulo@89 4528 .attr('class','nv-legend-symbol')
paulo@89 4529 .attr('r', 5);
paulo@89 4530
paulo@89 4531 seriesShape = series.select('circle');
paulo@89 4532 } else if (vers == 'furious') {
paulo@89 4533 seriesEnter.append('rect')
paulo@89 4534 .style('stroke-width', 2)
paulo@89 4535 .attr('class','nv-legend-symbol')
paulo@89 4536 .attr('rx', 3)
paulo@89 4537 .attr('ry', 3);
paulo@89 4538
paulo@89 4539 seriesShape = series.select('rect');
paulo@89 4540
paulo@89 4541 seriesEnter.append('g')
paulo@89 4542 .attr('class', 'nv-check-box')
paulo@89 4543 .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
paulo@89 4544 .attr('transform', 'translate(-10,-8)scale(0.5)');
paulo@89 4545
paulo@89 4546 var seriesCheckbox = series.select('.nv-check-box');
paulo@89 4547
paulo@89 4548 seriesCheckbox.each(function(d,i) {
paulo@89 4549 d3.select(this).selectAll('path')
paulo@89 4550 .attr('stroke', setTextColor(d,i));
paulo@89 4551 });
paulo@89 4552 }
paulo@89 4553
paulo@89 4554 seriesEnter.append('text')
paulo@89 4555 .attr('text-anchor', 'start')
paulo@89 4556 .attr('class','nv-legend-text')
paulo@89 4557 .attr('dy', '.32em')
paulo@89 4558 .attr('dx', '8');
paulo@89 4559
paulo@89 4560 var seriesText = series.select('text.nv-legend-text');
paulo@89 4561
paulo@89 4562 series
paulo@89 4563 .on('mouseover', function(d,i) {
paulo@89 4564 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
paulo@89 4565 })
paulo@89 4566 .on('mouseout', function(d,i) {
paulo@89 4567 dispatch.legendMouseout(d,i);
paulo@89 4568 })
paulo@89 4569 .on('click', function(d,i) {
paulo@89 4570 dispatch.legendClick(d,i);
paulo@89 4571 // make sure we re-get data in case it was modified
paulo@89 4572 var data = series.data();
paulo@89 4573 if (updateState) {
paulo@89 4574 if(vers =='classic') {
paulo@89 4575 if (radioButtonMode) {
paulo@89 4576 //Radio button mode: set every series to disabled,
paulo@89 4577 // and enable the clicked series.
paulo@89 4578 data.forEach(function(series) { series.disabled = true});
paulo@89 4579 d.disabled = false;
paulo@89 4580 }
paulo@89 4581 else {
paulo@89 4582 d.disabled = !d.disabled;
paulo@89 4583 if (data.every(function(series) { return series.disabled})) {
paulo@89 4584 //the default behavior of NVD3 legends is, if every single series
paulo@89 4585 // is disabled, turn all series' back on.
paulo@89 4586 data.forEach(function(series) { series.disabled = false});
paulo@89 4587 }
paulo@89 4588 }
paulo@89 4589 } else if(vers == 'furious') {
paulo@89 4590 if(expanded) {
paulo@89 4591 d.disengaged = !d.disengaged;
paulo@89 4592 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
paulo@89 4593 d.disabled = d.disengaged || d.userDisabled;
paulo@89 4594 } else if (!expanded) {
paulo@89 4595 d.disabled = !d.disabled;
paulo@89 4596 d.userDisabled = d.disabled;
paulo@89 4597 var engaged = data.filter(function(d) { return !d.disengaged; });
paulo@89 4598 if (engaged.every(function(series) { return series.userDisabled })) {
paulo@89 4599 //the default behavior of NVD3 legends is, if every single series
paulo@89 4600 // is disabled, turn all series' back on.
paulo@89 4601 data.forEach(function(series) {
paulo@89 4602 series.disabled = series.userDisabled = false;
paulo@89 4603 });
paulo@89 4604 }
paulo@89 4605 }
paulo@89 4606 }
paulo@89 4607 dispatch.stateChange({
paulo@89 4608 disabled: data.map(function(d) { return !!d.disabled }),
paulo@89 4609 disengaged: data.map(function(d) { return !!d.disengaged })
paulo@89 4610 });
paulo@89 4611
paulo@89 4612 }
paulo@89 4613 })
paulo@89 4614 .on('dblclick', function(d,i) {
paulo@89 4615 if(vers == 'furious' && expanded) return;
paulo@89 4616 dispatch.legendDblclick(d,i);
paulo@89 4617 if (updateState) {
paulo@89 4618 // make sure we re-get data in case it was modified
paulo@89 4619 var data = series.data();
paulo@89 4620 //the default behavior of NVD3 legends, when double clicking one,
paulo@89 4621 // is to set all other series' to false, and make the double clicked series enabled.
paulo@89 4622 data.forEach(function(series) {
paulo@89 4623 series.disabled = true;
paulo@89 4624 if(vers == 'furious') series.userDisabled = series.disabled;
paulo@89 4625 });
paulo@89 4626 d.disabled = false;
paulo@89 4627 if(vers == 'furious') d.userDisabled = d.disabled;
paulo@89 4628 dispatch.stateChange({
paulo@89 4629 disabled: data.map(function(d) { return !!d.disabled })
paulo@89 4630 });
paulo@89 4631 }
paulo@89 4632 });
paulo@89 4633
paulo@89 4634 series.classed('nv-disabled', function(d) { return d.userDisabled });
paulo@89 4635 series.exit().remove();
paulo@89 4636
paulo@89 4637 seriesText
paulo@89 4638 .attr('fill', setTextColor)
paulo@89 4639 .text(getKey);
paulo@89 4640
paulo@89 4641 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
paulo@89 4642 // NEW ALIGNING CODE, TODO: clean up
paulo@89 4643
paulo@89 4644 var versPadding;
paulo@89 4645 switch(vers) {
paulo@89 4646 case 'furious' :
paulo@89 4647 versPadding = 23;
paulo@89 4648 break;
paulo@89 4649 case 'classic' :
paulo@89 4650 versPadding = 20;
paulo@89 4651 }
paulo@89 4652
paulo@89 4653 if (align) {
paulo@89 4654
paulo@89 4655 var seriesWidths = [];
paulo@89 4656 series.each(function(d,i) {
paulo@89 4657 var legendText = d3.select(this).select('text');
paulo@89 4658 var nodeTextLength;
paulo@89 4659 try {
paulo@89 4660 nodeTextLength = legendText.node().getComputedTextLength();
paulo@89 4661 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
paulo@89 4662 if(nodeTextLength <= 0) throw Error();
paulo@89 4663 }
paulo@89 4664 catch(e) {
paulo@89 4665 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
paulo@89 4666 }
paulo@89 4667
paulo@89 4668 seriesWidths.push(nodeTextLength + padding);
paulo@89 4669 });
paulo@89 4670
paulo@89 4671 var seriesPerRow = 0;
paulo@89 4672 var legendWidth = 0;
paulo@89 4673 var columnWidths = [];
paulo@89 4674
paulo@89 4675 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
paulo@89 4676 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
paulo@89 4677 legendWidth += seriesWidths[seriesPerRow++];
paulo@89 4678 }
paulo@89 4679 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
paulo@89 4680
paulo@89 4681 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
paulo@89 4682 columnWidths = [];
paulo@89 4683 seriesPerRow--;
paulo@89 4684
paulo@89 4685 for (var k = 0; k < seriesWidths.length; k++) {
paulo@89 4686 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
paulo@89 4687 columnWidths[k % seriesPerRow] = seriesWidths[k];
paulo@89 4688 }
paulo@89 4689
paulo@89 4690 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
paulo@89 4691 return prev + cur;
paulo@89 4692 });
paulo@89 4693 }
paulo@89 4694
paulo@89 4695 var xPositions = [];
paulo@89 4696 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
paulo@89 4697 xPositions[i] = curX;
paulo@89 4698 curX += columnWidths[i];
paulo@89 4699 }
paulo@89 4700
paulo@89 4701 series
paulo@89 4702 .attr('transform', function(d, i) {
paulo@89 4703 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
paulo@89 4704 });
paulo@89 4705
paulo@89 4706 //position legend as far right as possible within the total width
paulo@89 4707 if (rightAlign) {
paulo@89 4708 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
paulo@89 4709 }
paulo@89 4710 else {
paulo@89 4711 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
paulo@89 4712 }
paulo@89 4713
paulo@89 4714 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
paulo@89 4715
paulo@89 4716 } else {
paulo@89 4717
paulo@89 4718 var ypos = 5,
paulo@89 4719 newxpos = 5,
paulo@89 4720 maxwidth = 0,
paulo@89 4721 xpos;
paulo@89 4722 series
paulo@89 4723 .attr('transform', function(d, i) {
paulo@89 4724 var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
paulo@89 4725 xpos = newxpos;
paulo@89 4726
paulo@89 4727 if (width < margin.left + margin.right + xpos + length) {
paulo@89 4728 newxpos = xpos = 5;
paulo@89 4729 ypos += versPadding;
paulo@89 4730 }
paulo@89 4731
paulo@89 4732 newxpos += length;
paulo@89 4733 if (newxpos > maxwidth) maxwidth = newxpos;
paulo@89 4734
paulo@89 4735 return 'translate(' + xpos + ',' + ypos + ')';
paulo@89 4736 });
paulo@89 4737
paulo@89 4738 //position legend as far right as possible within the total width
paulo@89 4739 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
paulo@89 4740
paulo@89 4741 height = margin.top + margin.bottom + ypos + 15;
paulo@89 4742 }
paulo@89 4743
paulo@89 4744 if(vers == 'furious') {
paulo@89 4745 // Size rectangles after text is placed
paulo@89 4746 seriesShape
paulo@89 4747 .attr('width', function(d,i) {
paulo@89 4748 return seriesText[0][i].getComputedTextLength() + 27;
paulo@89 4749 })
paulo@89 4750 .attr('height', 18)
paulo@89 4751 .attr('y', -9)
paulo@89 4752 .attr('x', -15)
paulo@89 4753 }
paulo@89 4754
paulo@89 4755 seriesShape
paulo@89 4756 .style('fill', setBGColor)
paulo@89 4757 .style('stroke', function(d,i) { return d.color || color(d, i) });
paulo@89 4758 });
paulo@89 4759
paulo@89 4760 function setTextColor(d,i) {
paulo@89 4761 if(vers != 'furious') return '#000';
paulo@89 4762 if(expanded) {
paulo@89 4763 return d.disengaged ? color(d,i) : '#fff';
paulo@89 4764 } else if (!expanded) {
paulo@89 4765 return !!d.disabled ? color(d,i) : '#fff';
paulo@89 4766 }
paulo@89 4767 }
paulo@89 4768
paulo@89 4769 function setBGColor(d,i) {
paulo@89 4770 if(expanded && vers == 'furious') {
paulo@89 4771 return d.disengaged ? '#fff' : color(d,i);
paulo@89 4772 } else {
paulo@89 4773 return !!d.disabled ? '#fff' : color(d,i);
paulo@89 4774 }
paulo@89 4775 }
paulo@89 4776
paulo@89 4777 return chart;
paulo@89 4778 }
paulo@89 4779
paulo@89 4780 //============================================================
paulo@89 4781 // Expose Public Variables
paulo@89 4782 //------------------------------------------------------------
paulo@89 4783
paulo@89 4784 chart.dispatch = dispatch;
paulo@89 4785 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 4786
paulo@89 4787 chart._options = Object.create({}, {
paulo@89 4788 // simple options, just get/set the necessary values
paulo@89 4789 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 4790 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 4791 key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
paulo@89 4792 align: {get: function(){return align;}, set: function(_){align=_;}},
paulo@89 4793 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
paulo@89 4794 padding: {get: function(){return padding;}, set: function(_){padding=_;}},
paulo@89 4795 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
paulo@89 4796 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
paulo@89 4797 expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
paulo@89 4798 vers: {get: function(){return vers;}, set: function(_){vers=_;}},
paulo@89 4799
paulo@89 4800 // options that require extra logic in the setter
paulo@89 4801 margin: {get: function(){return margin;}, set: function(_){
paulo@89 4802 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 4803 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 4804 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 4805 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 4806 }},
paulo@89 4807 color: {get: function(){return color;}, set: function(_){
paulo@89 4808 color = nv.utils.getColor(_);
paulo@89 4809 }}
paulo@89 4810 });
paulo@89 4811
paulo@89 4812 nv.utils.initOptions(chart);
paulo@89 4813
paulo@89 4814 return chart;
paulo@89 4815 };
paulo@89 4816 //TODO: consider deprecating and using multibar with single series for this
paulo@89 4817 nv.models.historicalBar = function() {
paulo@89 4818 "use strict";
paulo@89 4819
paulo@89 4820 //============================================================
paulo@89 4821 // Public Variables with Default Settings
paulo@89 4822 //------------------------------------------------------------
paulo@89 4823
paulo@89 4824 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 4825 , width = null
paulo@89 4826 , height = null
paulo@89 4827 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 4828 , container = null
paulo@89 4829 , x = d3.scale.linear()
paulo@89 4830 , y = d3.scale.linear()
paulo@89 4831 , getX = function(d) { return d.x }
paulo@89 4832 , getY = function(d) { return d.y }
paulo@89 4833 , forceX = []
paulo@89 4834 , forceY = [0]
paulo@89 4835 , padData = false
paulo@89 4836 , clipEdge = true
paulo@89 4837 , color = nv.utils.defaultColor()
paulo@89 4838 , xDomain
paulo@89 4839 , yDomain
paulo@89 4840 , xRange
paulo@89 4841 , yRange
paulo@89 4842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
paulo@89 4843 , interactive = true
paulo@89 4844 ;
paulo@89 4845
paulo@89 4846 var renderWatch = nv.utils.renderWatch(dispatch, 0);
paulo@89 4847
paulo@89 4848 function chart(selection) {
paulo@89 4849 selection.each(function(data) {
paulo@89 4850 renderWatch.reset();
paulo@89 4851
paulo@89 4852 container = d3.select(this);
paulo@89 4853 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 4854 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 4855
paulo@89 4856 nv.utils.initSVG(container);
paulo@89 4857
paulo@89 4858 // Setup Scales
paulo@89 4859 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
paulo@89 4860
paulo@89 4861 if (padData)
paulo@89 4862 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
paulo@89 4863 else
paulo@89 4864 x.range(xRange || [0, availableWidth]);
paulo@89 4865
paulo@89 4866 y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
paulo@89 4867 .range(yRange || [availableHeight, 0]);
paulo@89 4868
paulo@89 4869 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
paulo@89 4870 if (x.domain()[0] === x.domain()[1])
paulo@89 4871 x.domain()[0] ?
paulo@89 4872 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
paulo@89 4873 : x.domain([-1,1]);
paulo@89 4874
paulo@89 4875 if (y.domain()[0] === y.domain()[1])
paulo@89 4876 y.domain()[0] ?
paulo@89 4877 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
paulo@89 4878 : y.domain([-1,1]);
paulo@89 4879
paulo@89 4880 // Setup containers and skeleton of chart
paulo@89 4881 var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
paulo@89 4882 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
paulo@89 4883 var defsEnter = wrapEnter.append('defs');
paulo@89 4884 var gEnter = wrapEnter.append('g');
paulo@89 4885 var g = wrap.select('g');
paulo@89 4886
paulo@89 4887 gEnter.append('g').attr('class', 'nv-bars');
paulo@89 4888 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 4889
paulo@89 4890 container
paulo@89 4891 .on('click', function(d,i) {
paulo@89 4892 dispatch.chartClick({
paulo@89 4893 data: d,
paulo@89 4894 index: i,
paulo@89 4895 pos: d3.event,
paulo@89 4896 id: id
paulo@89 4897 });
paulo@89 4898 });
paulo@89 4899
paulo@89 4900 defsEnter.append('clipPath')
paulo@89 4901 .attr('id', 'nv-chart-clip-path-' + id)
paulo@89 4902 .append('rect');
paulo@89 4903
paulo@89 4904 wrap.select('#nv-chart-clip-path-' + id + ' rect')
paulo@89 4905 .attr('width', availableWidth)
paulo@89 4906 .attr('height', availableHeight);
paulo@89 4907
paulo@89 4908 g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
paulo@89 4909
paulo@89 4910 var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
paulo@89 4911 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
paulo@89 4912 bars.exit().remove();
paulo@89 4913
paulo@89 4914 bars.enter().append('rect')
paulo@89 4915 .attr('x', 0 )
paulo@89 4916 .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
paulo@89 4917 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
paulo@89 4918 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
paulo@89 4919 .on('mouseover', function(d,i) {
paulo@89 4920 if (!interactive) return;
paulo@89 4921 d3.select(this).classed('hover', true);
paulo@89 4922 dispatch.elementMouseover({
paulo@89 4923 data: d,
paulo@89 4924 index: i,
paulo@89 4925 color: d3.select(this).style("fill")
paulo@89 4926 });
paulo@89 4927
paulo@89 4928 })
paulo@89 4929 .on('mouseout', function(d,i) {
paulo@89 4930 if (!interactive) return;
paulo@89 4931 d3.select(this).classed('hover', false);
paulo@89 4932 dispatch.elementMouseout({
paulo@89 4933 data: d,
paulo@89 4934 index: i,
paulo@89 4935 color: d3.select(this).style("fill")
paulo@89 4936 });
paulo@89 4937 })
paulo@89 4938 .on('mousemove', function(d,i) {
paulo@89 4939 if (!interactive) return;
paulo@89 4940 dispatch.elementMousemove({
paulo@89 4941 data: d,
paulo@89 4942 index: i,
paulo@89 4943 color: d3.select(this).style("fill")
paulo@89 4944 });
paulo@89 4945 })
paulo@89 4946 .on('click', function(d,i) {
paulo@89 4947 if (!interactive) return;
paulo@89 4948 dispatch.elementClick({
paulo@89 4949 data: d,
paulo@89 4950 index: i,
paulo@89 4951 color: d3.select(this).style("fill")
paulo@89 4952 });
paulo@89 4953 d3.event.stopPropagation();
paulo@89 4954 })
paulo@89 4955 .on('dblclick', function(d,i) {
paulo@89 4956 if (!interactive) return;
paulo@89 4957 dispatch.elementDblClick({
paulo@89 4958 data: d,
paulo@89 4959 index: i,
paulo@89 4960 color: d3.select(this).style("fill")
paulo@89 4961 });
paulo@89 4962 d3.event.stopPropagation();
paulo@89 4963 });
paulo@89 4964
paulo@89 4965 bars
paulo@89 4966 .attr('fill', function(d,i) { return color(d, i); })
paulo@89 4967 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
paulo@89 4968 .watchTransition(renderWatch, 'bars')
paulo@89 4969 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
paulo@89 4970 //TODO: better width calculations that don't assume always uniform data spacing;w
paulo@89 4971 .attr('width', (availableWidth / data[0].values.length) * .9 );
paulo@89 4972
paulo@89 4973 bars.watchTransition(renderWatch, 'bars')
paulo@89 4974 .attr('y', function(d,i) {
paulo@89 4975 var rval = getY(d,i) < 0 ?
paulo@89 4976 y(0) :
paulo@89 4977 y(0) - y(getY(d,i)) < 1 ?
paulo@89 4978 y(0) - 1 :
paulo@89 4979 y(getY(d,i));
paulo@89 4980 return nv.utils.NaNtoZero(rval);
paulo@89 4981 })
paulo@89 4982 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
paulo@89 4983
paulo@89 4984 });
paulo@89 4985
paulo@89 4986 renderWatch.renderEnd('historicalBar immediate');
paulo@89 4987 return chart;
paulo@89 4988 }
paulo@89 4989
paulo@89 4990 //Create methods to allow outside functions to highlight a specific bar.
paulo@89 4991 chart.highlightPoint = function(pointIndex, isHoverOver) {
paulo@89 4992 container
paulo@89 4993 .select(".nv-bars .nv-bar-0-" + pointIndex)
paulo@89 4994 .classed("hover", isHoverOver)
paulo@89 4995 ;
paulo@89 4996 };
paulo@89 4997
paulo@89 4998 chart.clearHighlights = function() {
paulo@89 4999 container
paulo@89 5000 .select(".nv-bars .nv-bar.hover")
paulo@89 5001 .classed("hover", false)
paulo@89 5002 ;
paulo@89 5003 };
paulo@89 5004
paulo@89 5005 //============================================================
paulo@89 5006 // Expose Public Variables
paulo@89 5007 //------------------------------------------------------------
paulo@89 5008
paulo@89 5009 chart.dispatch = dispatch;
paulo@89 5010 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 5011
paulo@89 5012 chart._options = Object.create({}, {
paulo@89 5013 // simple options, just get/set the necessary values
paulo@89 5014 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 5015 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 5016 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
paulo@89 5017 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
paulo@89 5018 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
paulo@89 5019 x: {get: function(){return getX;}, set: function(_){getX=_;}},
paulo@89 5020 y: {get: function(){return getY;}, set: function(_){getY=_;}},
paulo@89 5021 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 5022 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 5023 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 5024 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 5025 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 5026 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 5027 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
paulo@89 5028 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 5029 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
paulo@89 5030
paulo@89 5031 // options that require extra logic in the setter
paulo@89 5032 margin: {get: function(){return margin;}, set: function(_){
paulo@89 5033 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 5034 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 5035 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 5036 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 5037 }},
paulo@89 5038 color: {get: function(){return color;}, set: function(_){
paulo@89 5039 color = nv.utils.getColor(_);
paulo@89 5040 }}
paulo@89 5041 });
paulo@89 5042
paulo@89 5043 nv.utils.initOptions(chart);
paulo@89 5044
paulo@89 5045 return chart;
paulo@89 5046 };
paulo@89 5047
paulo@89 5048 nv.models.historicalBarChart = function(bar_model) {
paulo@89 5049 "use strict";
paulo@89 5050
paulo@89 5051 //============================================================
paulo@89 5052 // Public Variables with Default Settings
paulo@89 5053 //------------------------------------------------------------
paulo@89 5054
paulo@89 5055 var bars = bar_model || nv.models.historicalBar()
paulo@89 5056 , xAxis = nv.models.axis()
paulo@89 5057 , yAxis = nv.models.axis()
paulo@89 5058 , legend = nv.models.legend()
paulo@89 5059 , interactiveLayer = nv.interactiveGuideline()
paulo@89 5060 , tooltip = nv.models.tooltip()
paulo@89 5061 ;
paulo@89 5062
paulo@89 5063
paulo@89 5064 var margin = {top: 30, right: 90, bottom: 50, left: 90}
paulo@89 5065 , color = nv.utils.defaultColor()
paulo@89 5066 , width = null
paulo@89 5067 , height = null
paulo@89 5068 , showLegend = false
paulo@89 5069 , showXAxis = true
paulo@89 5070 , showYAxis = true
paulo@89 5071 , rightAlignYAxis = false
paulo@89 5072 , useInteractiveGuideline = false
paulo@89 5073 , x
paulo@89 5074 , y
paulo@89 5075 , state = {}
paulo@89 5076 , defaultState = null
paulo@89 5077 , noData = null
paulo@89 5078 , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
paulo@89 5079 , transitionDuration = 250
paulo@89 5080 ;
paulo@89 5081
paulo@89 5082 xAxis.orient('bottom').tickPadding(7);
paulo@89 5083 yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
paulo@89 5084 tooltip
paulo@89 5085 .duration(0)
paulo@89 5086 .headerEnabled(false)
paulo@89 5087 .valueFormatter(function(d, i) {
paulo@89 5088 return yAxis.tickFormat()(d, i);
paulo@89 5089 })
paulo@89 5090 .headerFormatter(function(d, i) {
paulo@89 5091 return xAxis.tickFormat()(d, i);
paulo@89 5092 });
paulo@89 5093
paulo@89 5094
paulo@89 5095 //============================================================
paulo@89 5096 // Private Variables
paulo@89 5097 //------------------------------------------------------------
paulo@89 5098
paulo@89 5099 var renderWatch = nv.utils.renderWatch(dispatch, 0);
paulo@89 5100
paulo@89 5101 function chart(selection) {
paulo@89 5102 selection.each(function(data) {
paulo@89 5103 renderWatch.reset();
paulo@89 5104 renderWatch.models(bars);
paulo@89 5105 if (showXAxis) renderWatch.models(xAxis);
paulo@89 5106 if (showYAxis) renderWatch.models(yAxis);
paulo@89 5107
paulo@89 5108 var container = d3.select(this),
paulo@89 5109 that = this;
paulo@89 5110 nv.utils.initSVG(container);
paulo@89 5111 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 5112 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 5113
paulo@89 5114 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
paulo@89 5115 chart.container = this;
paulo@89 5116
paulo@89 5117 //set state.disabled
paulo@89 5118 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 5119
paulo@89 5120 if (!defaultState) {
paulo@89 5121 var key;
paulo@89 5122 defaultState = {};
paulo@89 5123 for (key in state) {
paulo@89 5124 if (state[key] instanceof Array)
paulo@89 5125 defaultState[key] = state[key].slice(0);
paulo@89 5126 else
paulo@89 5127 defaultState[key] = state[key];
paulo@89 5128 }
paulo@89 5129 }
paulo@89 5130
paulo@89 5131 // Display noData message if there's nothing to show.
paulo@89 5132 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 5133 nv.utils.noData(chart, container)
paulo@89 5134 return chart;
paulo@89 5135 } else {
paulo@89 5136 container.selectAll('.nv-noData').remove();
paulo@89 5137 }
paulo@89 5138
paulo@89 5139 // Setup Scales
paulo@89 5140 x = bars.xScale();
paulo@89 5141 y = bars.yScale();
paulo@89 5142
paulo@89 5143 // Setup containers and skeleton of chart
paulo@89 5144 var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
paulo@89 5145 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
paulo@89 5146 var g = wrap.select('g');
paulo@89 5147
paulo@89 5148 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 5149 gEnter.append('g').attr('class', 'nv-y nv-axis');
paulo@89 5150 gEnter.append('g').attr('class', 'nv-barsWrap');
paulo@89 5151 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 5152 gEnter.append('g').attr('class', 'nv-interactive');
paulo@89 5153
paulo@89 5154 // Legend
paulo@89 5155 if (showLegend) {
paulo@89 5156 legend.width(availableWidth);
paulo@89 5157
paulo@89 5158 g.select('.nv-legendWrap')
paulo@89 5159 .datum(data)
paulo@89 5160 .call(legend);
paulo@89 5161
paulo@89 5162 if ( margin.top != legend.height()) {
paulo@89 5163 margin.top = legend.height();
paulo@89 5164 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 5165 }
paulo@89 5166
paulo@89 5167 wrap.select('.nv-legendWrap')
paulo@89 5168 .attr('transform', 'translate(0,' + (-margin.top) +')')
paulo@89 5169 }
paulo@89 5170 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 5171
paulo@89 5172 if (rightAlignYAxis) {
paulo@89 5173 g.select(".nv-y.nv-axis")
paulo@89 5174 .attr("transform", "translate(" + availableWidth + ",0)");
paulo@89 5175 }
paulo@89 5176
paulo@89 5177 //Set up interactive layer
paulo@89 5178 if (useInteractiveGuideline) {
paulo@89 5179 interactiveLayer
paulo@89 5180 .width(availableWidth)
paulo@89 5181 .height(availableHeight)
paulo@89 5182 .margin({left:margin.left, top:margin.top})
paulo@89 5183 .svgContainer(container)
paulo@89 5184 .xScale(x);
paulo@89 5185 wrap.select(".nv-interactive").call(interactiveLayer);
paulo@89 5186 }
paulo@89 5187 bars
paulo@89 5188 .width(availableWidth)
paulo@89 5189 .height(availableHeight)
paulo@89 5190 .color(data.map(function(d,i) {
paulo@89 5191 return d.color || color(d, i);
paulo@89 5192 }).filter(function(d,i) { return !data[i].disabled }));
paulo@89 5193
paulo@89 5194 var barsWrap = g.select('.nv-barsWrap')
paulo@89 5195 .datum(data.filter(function(d) { return !d.disabled }));
paulo@89 5196 barsWrap.transition().call(bars);
paulo@89 5197
paulo@89 5198 // Setup Axes
paulo@89 5199 if (showXAxis) {
paulo@89 5200 xAxis
paulo@89 5201 .scale(x)
paulo@89 5202 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 5203 .tickSize(-availableHeight, 0);
paulo@89 5204
paulo@89 5205 g.select('.nv-x.nv-axis')
paulo@89 5206 .attr('transform', 'translate(0,' + y.range()[0] + ')');
paulo@89 5207 g.select('.nv-x.nv-axis')
paulo@89 5208 .transition()
paulo@89 5209 .call(xAxis);
paulo@89 5210 }
paulo@89 5211
paulo@89 5212 if (showYAxis) {
paulo@89 5213 yAxis
paulo@89 5214 .scale(y)
paulo@89 5215 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
paulo@89 5216 .tickSize( -availableWidth, 0);
paulo@89 5217
paulo@89 5218 g.select('.nv-y.nv-axis')
paulo@89 5219 .transition()
paulo@89 5220 .call(yAxis);
paulo@89 5221 }
paulo@89 5222
paulo@89 5223 //============================================================
paulo@89 5224 // Event Handling/Dispatching (in chart's scope)
paulo@89 5225 //------------------------------------------------------------
paulo@89 5226
paulo@89 5227 interactiveLayer.dispatch.on('elementMousemove', function(e) {
paulo@89 5228 bars.clearHighlights();
paulo@89 5229
paulo@89 5230 var singlePoint, pointIndex, pointXLocation, allData = [];
paulo@89 5231 data
paulo@89 5232 .filter(function(series, i) {
paulo@89 5233 series.seriesIndex = i;
paulo@89 5234 return !series.disabled;
paulo@89 5235 })
paulo@89 5236 .forEach(function(series,i) {
paulo@89 5237 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
paulo@89 5238 bars.highlightPoint(pointIndex,true);
paulo@89 5239 var point = series.values[pointIndex];
paulo@89 5240 if (point === undefined) return;
paulo@89 5241 if (singlePoint === undefined) singlePoint = point;
paulo@89 5242 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
paulo@89 5243 allData.push({
paulo@89 5244 key: series.key,
paulo@89 5245 value: chart.y()(point, pointIndex),
paulo@89 5246 color: color(series,series.seriesIndex),
paulo@89 5247 data: series.values[pointIndex]
paulo@89 5248 });
paulo@89 5249 });
paulo@89 5250
paulo@89 5251 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
paulo@89 5252 interactiveLayer.tooltip
paulo@89 5253 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
paulo@89 5254 .chartContainer(that.parentNode)
paulo@89 5255 .valueFormatter(function(d,i) {
paulo@89 5256 return yAxis.tickFormat()(d);
paulo@89 5257 })
paulo@89 5258 .data({
paulo@89 5259 value: xValue,
paulo@89 5260 index: pointIndex,
paulo@89 5261 series: allData
paulo@89 5262 })();
paulo@89 5263
paulo@89 5264 interactiveLayer.renderGuideLine(pointXLocation);
paulo@89 5265
paulo@89 5266 });
paulo@89 5267
paulo@89 5268 interactiveLayer.dispatch.on("elementMouseout",function(e) {
paulo@89 5269 dispatch.tooltipHide();
paulo@89 5270 bars.clearHighlights();
paulo@89 5271 });
paulo@89 5272
paulo@89 5273 legend.dispatch.on('legendClick', function(d,i) {
paulo@89 5274 d.disabled = !d.disabled;
paulo@89 5275
paulo@89 5276 if (!data.filter(function(d) { return !d.disabled }).length) {
paulo@89 5277 data.map(function(d) {
paulo@89 5278 d.disabled = false;
paulo@89 5279 wrap.selectAll('.nv-series').classed('disabled', false);
paulo@89 5280 return d;
paulo@89 5281 });
paulo@89 5282 }
paulo@89 5283
paulo@89 5284 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 5285 dispatch.stateChange(state);
paulo@89 5286
paulo@89 5287 selection.transition().call(chart);
paulo@89 5288 });
paulo@89 5289
paulo@89 5290 legend.dispatch.on('legendDblclick', function(d) {
paulo@89 5291 //Double clicking should always enable current series, and disabled all others.
paulo@89 5292 data.forEach(function(d) {
paulo@89 5293 d.disabled = true;
paulo@89 5294 });
paulo@89 5295 d.disabled = false;
paulo@89 5296
paulo@89 5297 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 5298 dispatch.stateChange(state);
paulo@89 5299 chart.update();
paulo@89 5300 });
paulo@89 5301
paulo@89 5302 dispatch.on('changeState', function(e) {
paulo@89 5303 if (typeof e.disabled !== 'undefined') {
paulo@89 5304 data.forEach(function(series,i) {
paulo@89 5305 series.disabled = e.disabled[i];
paulo@89 5306 });
paulo@89 5307
paulo@89 5308 state.disabled = e.disabled;
paulo@89 5309 }
paulo@89 5310
paulo@89 5311 chart.update();
paulo@89 5312 });
paulo@89 5313 });
paulo@89 5314
paulo@89 5315 renderWatch.renderEnd('historicalBarChart immediate');
paulo@89 5316 return chart;
paulo@89 5317 }
paulo@89 5318
paulo@89 5319 //============================================================
paulo@89 5320 // Event Handling/Dispatching (out of chart's scope)
paulo@89 5321 //------------------------------------------------------------
paulo@89 5322
paulo@89 5323 bars.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 5324 evt['series'] = {
paulo@89 5325 key: chart.x()(evt.data),
paulo@89 5326 value: chart.y()(evt.data),
paulo@89 5327 color: evt.color
paulo@89 5328 };
paulo@89 5329 tooltip.data(evt).hidden(false);
paulo@89 5330 });
paulo@89 5331
paulo@89 5332 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 5333 tooltip.hidden(true);
paulo@89 5334 });
paulo@89 5335
paulo@89 5336 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 5337 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 5338 });
paulo@89 5339
paulo@89 5340 //============================================================
paulo@89 5341 // Expose Public Variables
paulo@89 5342 //------------------------------------------------------------
paulo@89 5343
paulo@89 5344 // expose chart's sub-components
paulo@89 5345 chart.dispatch = dispatch;
paulo@89 5346 chart.bars = bars;
paulo@89 5347 chart.legend = legend;
paulo@89 5348 chart.xAxis = xAxis;
paulo@89 5349 chart.yAxis = yAxis;
paulo@89 5350 chart.interactiveLayer = interactiveLayer;
paulo@89 5351 chart.tooltip = tooltip;
paulo@89 5352
paulo@89 5353 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 5354
paulo@89 5355 chart._options = Object.create({}, {
paulo@89 5356 // simple options, just get/set the necessary values
paulo@89 5357 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 5358 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 5359 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 5360 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 5361 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 5362 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 5363 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 5364
paulo@89 5365 // deprecated options
paulo@89 5366 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 5367 // deprecated after 1.7.1
paulo@89 5368 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 5369 tooltip.enabled(!!_);
paulo@89 5370 }},
paulo@89 5371 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 5372 // deprecated after 1.7.1
paulo@89 5373 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 5374 tooltip.contentGenerator(_);
paulo@89 5375 }},
paulo@89 5376
paulo@89 5377 // options that require extra logic in the setter
paulo@89 5378 margin: {get: function(){return margin;}, set: function(_){
paulo@89 5379 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 5380 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 5381 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 5382 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 5383 }},
paulo@89 5384 color: {get: function(){return color;}, set: function(_){
paulo@89 5385 color = nv.utils.getColor(_);
paulo@89 5386 legend.color(color);
paulo@89 5387 bars.color(color);
paulo@89 5388 }},
paulo@89 5389 duration: {get: function(){return transitionDuration;}, set: function(_){
paulo@89 5390 transitionDuration=_;
paulo@89 5391 renderWatch.reset(transitionDuration);
paulo@89 5392 yAxis.duration(transitionDuration);
paulo@89 5393 xAxis.duration(transitionDuration);
paulo@89 5394 }},
paulo@89 5395 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
paulo@89 5396 rightAlignYAxis = _;
paulo@89 5397 yAxis.orient( (_) ? 'right' : 'left');
paulo@89 5398 }},
paulo@89 5399 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
paulo@89 5400 useInteractiveGuideline = _;
paulo@89 5401 if (_ === true) {
paulo@89 5402 chart.interactive(false);
paulo@89 5403 }
paulo@89 5404 }}
paulo@89 5405 });
paulo@89 5406
paulo@89 5407 nv.utils.inheritOptions(chart, bars);
paulo@89 5408 nv.utils.initOptions(chart);
paulo@89 5409
paulo@89 5410 return chart;
paulo@89 5411 };
paulo@89 5412
paulo@89 5413
paulo@89 5414 // ohlcChart is just a historical chart with ohlc bars and some tweaks
paulo@89 5415 nv.models.ohlcBarChart = function() {
paulo@89 5416 var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
paulo@89 5417
paulo@89 5418 // special default tooltip since we show multiple values per x
paulo@89 5419 chart.useInteractiveGuideline(true);
paulo@89 5420 chart.interactiveLayer.tooltip.contentGenerator(function(data) {
paulo@89 5421 // we assume only one series exists for this chart
paulo@89 5422 var d = data.series[0].data;
paulo@89 5423 // match line colors as defined in nv.d3.css
paulo@89 5424 var color = d.open < d.close ? "2ca02c" : "d62728";
paulo@89 5425 return '' +
paulo@89 5426 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
paulo@89 5427 '<table>' +
paulo@89 5428 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
paulo@89 5429 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
paulo@89 5430 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
paulo@89 5431 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
paulo@89 5432 '</table>';
paulo@89 5433 });
paulo@89 5434 return chart;
paulo@89 5435 };
paulo@89 5436
paulo@89 5437 // candlestickChart is just a historical chart with candlestick bars and some tweaks
paulo@89 5438 nv.models.candlestickBarChart = function() {
paulo@89 5439 var chart = nv.models.historicalBarChart(nv.models.candlestickBar());
paulo@89 5440
paulo@89 5441 // special default tooltip since we show multiple values per x
paulo@89 5442 chart.useInteractiveGuideline(true);
paulo@89 5443 chart.interactiveLayer.tooltip.contentGenerator(function(data) {
paulo@89 5444 // we assume only one series exists for this chart
paulo@89 5445 var d = data.series[0].data;
paulo@89 5446 // match line colors as defined in nv.d3.css
paulo@89 5447 var color = d.open < d.close ? "2ca02c" : "d62728";
paulo@89 5448 return '' +
paulo@89 5449 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
paulo@89 5450 '<table>' +
paulo@89 5451 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
paulo@89 5452 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
paulo@89 5453 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
paulo@89 5454 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
paulo@89 5455 '</table>';
paulo@89 5456 });
paulo@89 5457 return chart;
paulo@89 5458 };
paulo@89 5459 nv.models.legend = function() {
paulo@89 5460 "use strict";
paulo@89 5461
paulo@89 5462 //============================================================
paulo@89 5463 // Public Variables with Default Settings
paulo@89 5464 //------------------------------------------------------------
paulo@89 5465
paulo@89 5466 var margin = {top: 5, right: 0, bottom: 5, left: 0}
paulo@89 5467 , width = 400
paulo@89 5468 , height = 20
paulo@89 5469 , getKey = function(d) { return d.key }
paulo@89 5470 , color = nv.utils.getColor()
paulo@89 5471 , align = true
paulo@89 5472 , padding = 32 //define how much space between legend items. - recommend 32 for furious version
paulo@89 5473 , rightAlign = true
paulo@89 5474 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
paulo@89 5475 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
paulo@89 5476 , expanded = false
paulo@89 5477 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
paulo@89 5478 , vers = 'classic' //Options are "classic" and "furious"
paulo@89 5479 ;
paulo@89 5480
paulo@89 5481 function chart(selection) {
paulo@89 5482 selection.each(function(data) {
paulo@89 5483 var availableWidth = width - margin.left - margin.right,
paulo@89 5484 container = d3.select(this);
paulo@89 5485 nv.utils.initSVG(container);
paulo@89 5486
paulo@89 5487 // Setup containers and skeleton of chart
paulo@89 5488 var wrap = container.selectAll('g.nv-legend').data([data]);
paulo@89 5489 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
paulo@89 5490 var g = wrap.select('g');
paulo@89 5491
paulo@89 5492 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 5493
paulo@89 5494 var series = g.selectAll('.nv-series')
paulo@89 5495 .data(function(d) {
paulo@89 5496 if(vers != 'furious') return d;
paulo@89 5497
paulo@89 5498 return d.filter(function(n) {
paulo@89 5499 return expanded ? true : !n.disengaged;
paulo@89 5500 });
paulo@89 5501 });
paulo@89 5502
paulo@89 5503 var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
paulo@89 5504 var seriesShape;
paulo@89 5505
paulo@89 5506 var versPadding;
paulo@89 5507 switch(vers) {
paulo@89 5508 case 'furious' :
paulo@89 5509 versPadding = 23;
paulo@89 5510 break;
paulo@89 5511 case 'classic' :
paulo@89 5512 versPadding = 20;
paulo@89 5513 }
paulo@89 5514
paulo@89 5515 if(vers == 'classic') {
paulo@89 5516 seriesEnter.append('circle')
paulo@89 5517 .style('stroke-width', 2)
paulo@89 5518 .attr('class','nv-legend-symbol')
paulo@89 5519 .attr('r', 5);
paulo@89 5520
paulo@89 5521 seriesShape = series.select('circle');
paulo@89 5522 } else if (vers == 'furious') {
paulo@89 5523 seriesEnter.append('rect')
paulo@89 5524 .style('stroke-width', 2)
paulo@89 5525 .attr('class','nv-legend-symbol')
paulo@89 5526 .attr('rx', 3)
paulo@89 5527 .attr('ry', 3);
paulo@89 5528
paulo@89 5529 seriesShape = series.select('.nv-legend-symbol');
paulo@89 5530
paulo@89 5531 seriesEnter.append('g')
paulo@89 5532 .attr('class', 'nv-check-box')
paulo@89 5533 .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
paulo@89 5534 .attr('transform', 'translate(-10,-8)scale(0.5)');
paulo@89 5535
paulo@89 5536 var seriesCheckbox = series.select('.nv-check-box');
paulo@89 5537
paulo@89 5538 seriesCheckbox.each(function(d,i) {
paulo@89 5539 d3.select(this).selectAll('path')
paulo@89 5540 .attr('stroke', setTextColor(d,i));
paulo@89 5541 });
paulo@89 5542 }
paulo@89 5543
paulo@89 5544 seriesEnter.append('text')
paulo@89 5545 .attr('text-anchor', 'start')
paulo@89 5546 .attr('class','nv-legend-text')
paulo@89 5547 .attr('dy', '.32em')
paulo@89 5548 .attr('dx', '8');
paulo@89 5549
paulo@89 5550 var seriesText = series.select('text.nv-legend-text');
paulo@89 5551
paulo@89 5552 series
paulo@89 5553 .on('mouseover', function(d,i) {
paulo@89 5554 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
paulo@89 5555 })
paulo@89 5556 .on('mouseout', function(d,i) {
paulo@89 5557 dispatch.legendMouseout(d,i);
paulo@89 5558 })
paulo@89 5559 .on('click', function(d,i) {
paulo@89 5560 dispatch.legendClick(d,i);
paulo@89 5561 // make sure we re-get data in case it was modified
paulo@89 5562 var data = series.data();
paulo@89 5563 if (updateState) {
paulo@89 5564 if(vers =='classic') {
paulo@89 5565 if (radioButtonMode) {
paulo@89 5566 //Radio button mode: set every series to disabled,
paulo@89 5567 // and enable the clicked series.
paulo@89 5568 data.forEach(function(series) { series.disabled = true});
paulo@89 5569 d.disabled = false;
paulo@89 5570 }
paulo@89 5571 else {
paulo@89 5572 d.disabled = !d.disabled;
paulo@89 5573 if (data.every(function(series) { return series.disabled})) {
paulo@89 5574 //the default behavior of NVD3 legends is, if every single series
paulo@89 5575 // is disabled, turn all series' back on.
paulo@89 5576 data.forEach(function(series) { series.disabled = false});
paulo@89 5577 }
paulo@89 5578 }
paulo@89 5579 } else if(vers == 'furious') {
paulo@89 5580 if(expanded) {
paulo@89 5581 d.disengaged = !d.disengaged;
paulo@89 5582 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
paulo@89 5583 d.disabled = d.disengaged || d.userDisabled;
paulo@89 5584 } else if (!expanded) {
paulo@89 5585 d.disabled = !d.disabled;
paulo@89 5586 d.userDisabled = d.disabled;
paulo@89 5587 var engaged = data.filter(function(d) { return !d.disengaged; });
paulo@89 5588 if (engaged.every(function(series) { return series.userDisabled })) {
paulo@89 5589 //the default behavior of NVD3 legends is, if every single series
paulo@89 5590 // is disabled, turn all series' back on.
paulo@89 5591 data.forEach(function(series) {
paulo@89 5592 series.disabled = series.userDisabled = false;
paulo@89 5593 });
paulo@89 5594 }
paulo@89 5595 }
paulo@89 5596 }
paulo@89 5597 dispatch.stateChange({
paulo@89 5598 disabled: data.map(function(d) { return !!d.disabled }),
paulo@89 5599 disengaged: data.map(function(d) { return !!d.disengaged })
paulo@89 5600 });
paulo@89 5601
paulo@89 5602 }
paulo@89 5603 })
paulo@89 5604 .on('dblclick', function(d,i) {
paulo@89 5605 if(vers == 'furious' && expanded) return;
paulo@89 5606 dispatch.legendDblclick(d,i);
paulo@89 5607 if (updateState) {
paulo@89 5608 // make sure we re-get data in case it was modified
paulo@89 5609 var data = series.data();
paulo@89 5610 //the default behavior of NVD3 legends, when double clicking one,
paulo@89 5611 // is to set all other series' to false, and make the double clicked series enabled.
paulo@89 5612 data.forEach(function(series) {
paulo@89 5613 series.disabled = true;
paulo@89 5614 if(vers == 'furious') series.userDisabled = series.disabled;
paulo@89 5615 });
paulo@89 5616 d.disabled = false;
paulo@89 5617 if(vers == 'furious') d.userDisabled = d.disabled;
paulo@89 5618 dispatch.stateChange({
paulo@89 5619 disabled: data.map(function(d) { return !!d.disabled })
paulo@89 5620 });
paulo@89 5621 }
paulo@89 5622 });
paulo@89 5623
paulo@89 5624 series.classed('nv-disabled', function(d) { return d.userDisabled });
paulo@89 5625 series.exit().remove();
paulo@89 5626
paulo@89 5627 seriesText
paulo@89 5628 .attr('fill', setTextColor)
paulo@89 5629 .text(getKey);
paulo@89 5630
paulo@89 5631 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
paulo@89 5632 // NEW ALIGNING CODE, TODO: clean up
paulo@89 5633 var legendWidth = 0;
paulo@89 5634 if (align) {
paulo@89 5635
paulo@89 5636 var seriesWidths = [];
paulo@89 5637 series.each(function(d,i) {
paulo@89 5638 var legendText = d3.select(this).select('text');
paulo@89 5639 var nodeTextLength;
paulo@89 5640 try {
paulo@89 5641 nodeTextLength = legendText.node().getComputedTextLength();
paulo@89 5642 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
paulo@89 5643 if(nodeTextLength <= 0) throw Error();
paulo@89 5644 }
paulo@89 5645 catch(e) {
paulo@89 5646 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
paulo@89 5647 }
paulo@89 5648
paulo@89 5649 seriesWidths.push(nodeTextLength + padding);
paulo@89 5650 });
paulo@89 5651
paulo@89 5652 var seriesPerRow = 0;
paulo@89 5653 var columnWidths = [];
paulo@89 5654 legendWidth = 0;
paulo@89 5655
paulo@89 5656 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
paulo@89 5657 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
paulo@89 5658 legendWidth += seriesWidths[seriesPerRow++];
paulo@89 5659 }
paulo@89 5660 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
paulo@89 5661
paulo@89 5662 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
paulo@89 5663 columnWidths = [];
paulo@89 5664 seriesPerRow--;
paulo@89 5665
paulo@89 5666 for (var k = 0; k < seriesWidths.length; k++) {
paulo@89 5667 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
paulo@89 5668 columnWidths[k % seriesPerRow] = seriesWidths[k];
paulo@89 5669 }
paulo@89 5670
paulo@89 5671 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
paulo@89 5672 return prev + cur;
paulo@89 5673 });
paulo@89 5674 }
paulo@89 5675
paulo@89 5676 var xPositions = [];
paulo@89 5677 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
paulo@89 5678 xPositions[i] = curX;
paulo@89 5679 curX += columnWidths[i];
paulo@89 5680 }
paulo@89 5681
paulo@89 5682 series
paulo@89 5683 .attr('transform', function(d, i) {
paulo@89 5684 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
paulo@89 5685 });
paulo@89 5686
paulo@89 5687 //position legend as far right as possible within the total width
paulo@89 5688 if (rightAlign) {
paulo@89 5689 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
paulo@89 5690 }
paulo@89 5691 else {
paulo@89 5692 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
paulo@89 5693 }
paulo@89 5694
paulo@89 5695 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
paulo@89 5696
paulo@89 5697 } else {
paulo@89 5698
paulo@89 5699 var ypos = 5,
paulo@89 5700 newxpos = 5,
paulo@89 5701 maxwidth = 0,
paulo@89 5702 xpos;
paulo@89 5703 series
paulo@89 5704 .attr('transform', function(d, i) {
paulo@89 5705 var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
paulo@89 5706 xpos = newxpos;
paulo@89 5707
paulo@89 5708 if (width < margin.left + margin.right + xpos + length) {
paulo@89 5709 newxpos = xpos = 5;
paulo@89 5710 ypos += versPadding;
paulo@89 5711 }
paulo@89 5712
paulo@89 5713 newxpos += length;
paulo@89 5714 if (newxpos > maxwidth) maxwidth = newxpos;
paulo@89 5715
paulo@89 5716 if(legendWidth < xpos + maxwidth) {
paulo@89 5717 legendWidth = xpos + maxwidth;
paulo@89 5718 }
paulo@89 5719 return 'translate(' + xpos + ',' + ypos + ')';
paulo@89 5720 });
paulo@89 5721
paulo@89 5722 //position legend as far right as possible within the total width
paulo@89 5723 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
paulo@89 5724
paulo@89 5725 height = margin.top + margin.bottom + ypos + 15;
paulo@89 5726 }
paulo@89 5727
paulo@89 5728 if(vers == 'furious') {
paulo@89 5729 // Size rectangles after text is placed
paulo@89 5730 seriesShape
paulo@89 5731 .attr('width', function(d,i) {
paulo@89 5732 return seriesText[0][i].getComputedTextLength() + 27;
paulo@89 5733 })
paulo@89 5734 .attr('height', 18)
paulo@89 5735 .attr('y', -9)
paulo@89 5736 .attr('x', -15);
paulo@89 5737
paulo@89 5738 // The background for the expanded legend (UI)
paulo@89 5739 gEnter.insert('rect',':first-child')
paulo@89 5740 .attr('class', 'nv-legend-bg')
paulo@89 5741 .attr('fill', '#eee')
paulo@89 5742 // .attr('stroke', '#444')
paulo@89 5743 .attr('opacity',0);
paulo@89 5744
paulo@89 5745 var seriesBG = g.select('.nv-legend-bg');
paulo@89 5746
paulo@89 5747 seriesBG
paulo@89 5748 .transition().duration(300)
paulo@89 5749 .attr('x', -versPadding )
paulo@89 5750 .attr('width', legendWidth + versPadding - 12)
paulo@89 5751 .attr('height', height + 10)
paulo@89 5752 .attr('y', -margin.top - 10)
paulo@89 5753 .attr('opacity', expanded ? 1 : 0);
paulo@89 5754
paulo@89 5755
paulo@89 5756 }
paulo@89 5757
paulo@89 5758 seriesShape
paulo@89 5759 .style('fill', setBGColor)
paulo@89 5760 .style('fill-opacity', setBGOpacity)
paulo@89 5761 .style('stroke', setBGColor);
paulo@89 5762 });
paulo@89 5763
paulo@89 5764 function setTextColor(d,i) {
paulo@89 5765 if(vers != 'furious') return '#000';
paulo@89 5766 if(expanded) {
paulo@89 5767 return d.disengaged ? '#000' : '#fff';
paulo@89 5768 } else if (!expanded) {
paulo@89 5769 if(!d.color) d.color = color(d,i);
paulo@89 5770 return !!d.disabled ? d.color : '#fff';
paulo@89 5771 }
paulo@89 5772 }
paulo@89 5773
paulo@89 5774 function setBGColor(d,i) {
paulo@89 5775 if(expanded && vers == 'furious') {
paulo@89 5776 return d.disengaged ? '#eee' : d.color || color(d,i);
paulo@89 5777 } else {
paulo@89 5778 return d.color || color(d,i);
paulo@89 5779 }
paulo@89 5780 }
paulo@89 5781
paulo@89 5782
paulo@89 5783 function setBGOpacity(d,i) {
paulo@89 5784 if(expanded && vers == 'furious') {
paulo@89 5785 return 1;
paulo@89 5786 } else {
paulo@89 5787 return !!d.disabled ? 0 : 1;
paulo@89 5788 }
paulo@89 5789 }
paulo@89 5790
paulo@89 5791 return chart;
paulo@89 5792 }
paulo@89 5793
paulo@89 5794 //============================================================
paulo@89 5795 // Expose Public Variables
paulo@89 5796 //------------------------------------------------------------
paulo@89 5797
paulo@89 5798 chart.dispatch = dispatch;
paulo@89 5799 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 5800
paulo@89 5801 chart._options = Object.create({}, {
paulo@89 5802 // simple options, just get/set the necessary values
paulo@89 5803 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 5804 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 5805 key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
paulo@89 5806 align: {get: function(){return align;}, set: function(_){align=_;}},
paulo@89 5807 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
paulo@89 5808 padding: {get: function(){return padding;}, set: function(_){padding=_;}},
paulo@89 5809 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
paulo@89 5810 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
paulo@89 5811 expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
paulo@89 5812 vers: {get: function(){return vers;}, set: function(_){vers=_;}},
paulo@89 5813
paulo@89 5814 // options that require extra logic in the setter
paulo@89 5815 margin: {get: function(){return margin;}, set: function(_){
paulo@89 5816 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 5817 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 5818 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 5819 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 5820 }},
paulo@89 5821 color: {get: function(){return color;}, set: function(_){
paulo@89 5822 color = nv.utils.getColor(_);
paulo@89 5823 }}
paulo@89 5824 });
paulo@89 5825
paulo@89 5826 nv.utils.initOptions(chart);
paulo@89 5827
paulo@89 5828 return chart;
paulo@89 5829 };
paulo@89 5830
paulo@89 5831 nv.models.line = function() {
paulo@89 5832 "use strict";
paulo@89 5833 //============================================================
paulo@89 5834 // Public Variables with Default Settings
paulo@89 5835 //------------------------------------------------------------
paulo@89 5836
paulo@89 5837 var scatter = nv.models.scatter()
paulo@89 5838 ;
paulo@89 5839
paulo@89 5840 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 5841 , width = 960
paulo@89 5842 , height = 500
paulo@89 5843 , container = null
paulo@89 5844 , strokeWidth = 1.5
paulo@89 5845 , color = nv.utils.defaultColor() // a function that returns a color
paulo@89 5846 , getX = function(d) { return d.x } // accessor to get the x value from a data point
paulo@89 5847 , getY = function(d) { return d.y } // accessor to get the y value from a data point
paulo@89 5848 , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continuous when it is not defined
paulo@89 5849 , isArea = function(d) { return d.area } // decides if a line is an area or just a line
paulo@89 5850 , clipEdge = false // if true, masks lines within x and y scale
paulo@89 5851 , x //can be accessed via chart.xScale()
paulo@89 5852 , y //can be accessed via chart.yScale()
paulo@89 5853 , interpolate = "linear" // controls the line interpolation
paulo@89 5854 , duration = 250
paulo@89 5855 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
paulo@89 5856 ;
paulo@89 5857
paulo@89 5858 scatter
paulo@89 5859 .pointSize(16) // default size
paulo@89 5860 .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
paulo@89 5861 ;
paulo@89 5862
paulo@89 5863 //============================================================
paulo@89 5864
paulo@89 5865
paulo@89 5866 //============================================================
paulo@89 5867 // Private Variables
paulo@89 5868 //------------------------------------------------------------
paulo@89 5869
paulo@89 5870 var x0, y0 //used to store previous scales
paulo@89 5871 , renderWatch = nv.utils.renderWatch(dispatch, duration)
paulo@89 5872 ;
paulo@89 5873
paulo@89 5874 //============================================================
paulo@89 5875
paulo@89 5876
paulo@89 5877 function chart(selection) {
paulo@89 5878 renderWatch.reset();
paulo@89 5879 renderWatch.models(scatter);
paulo@89 5880 selection.each(function(data) {
paulo@89 5881 container = d3.select(this);
paulo@89 5882 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 5883 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 5884 nv.utils.initSVG(container);
paulo@89 5885
paulo@89 5886 // Setup Scales
paulo@89 5887 x = scatter.xScale();
paulo@89 5888 y = scatter.yScale();
paulo@89 5889
paulo@89 5890 x0 = x0 || x;
paulo@89 5891 y0 = y0 || y;
paulo@89 5892
paulo@89 5893 // Setup containers and skeleton of chart
paulo@89 5894 var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
paulo@89 5895 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
paulo@89 5896 var defsEnter = wrapEnter.append('defs');
paulo@89 5897 var gEnter = wrapEnter.append('g');
paulo@89 5898 var g = wrap.select('g');
paulo@89 5899
paulo@89 5900 gEnter.append('g').attr('class', 'nv-groups');
paulo@89 5901 gEnter.append('g').attr('class', 'nv-scatterWrap');
paulo@89 5902
paulo@89 5903 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 5904
paulo@89 5905 scatter
paulo@89 5906 .width(availableWidth)
paulo@89 5907 .height(availableHeight);
paulo@89 5908
paulo@89 5909 var scatterWrap = wrap.select('.nv-scatterWrap');
paulo@89 5910 scatterWrap.call(scatter);
paulo@89 5911
paulo@89 5912 defsEnter.append('clipPath')
paulo@89 5913 .attr('id', 'nv-edge-clip-' + scatter.id())
paulo@89 5914 .append('rect');
paulo@89 5915
paulo@89 5916 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
paulo@89 5917 .attr('width', availableWidth)
paulo@89 5918 .attr('height', (availableHeight > 0) ? availableHeight : 0);
paulo@89 5919
paulo@89 5920 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
paulo@89 5921 scatterWrap
paulo@89 5922 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
paulo@89 5923
paulo@89 5924 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
paulo@89 5925 .data(function(d) { return d }, function(d) { return d.key });
paulo@89 5926 groups.enter().append('g')
paulo@89 5927 .style('stroke-opacity', 1e-6)
paulo@89 5928 .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth })
paulo@89 5929 .style('fill-opacity', 1e-6);
paulo@89 5930
paulo@89 5931 groups.exit().remove();
paulo@89 5932
paulo@89 5933 groups
paulo@89 5934 .attr('class', function(d,i) {
paulo@89 5935 return (d.classed || '') + ' nv-group nv-series-' + i;
paulo@89 5936 })
paulo@89 5937 .classed('hover', function(d) { return d.hover })
paulo@89 5938 .style('fill', function(d,i){ return color(d, i) })
paulo@89 5939 .style('stroke', function(d,i){ return color(d, i)});
paulo@89 5940 groups.watchTransition(renderWatch, 'line: groups')
paulo@89 5941 .style('stroke-opacity', 1)
paulo@89 5942 .style('fill-opacity', function(d) { return d.fillOpacity || .5});
paulo@89 5943
paulo@89 5944 var areaPaths = groups.selectAll('path.nv-area')
paulo@89 5945 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
paulo@89 5946 areaPaths.enter().append('path')
paulo@89 5947 .attr('class', 'nv-area')
paulo@89 5948 .attr('d', function(d) {
paulo@89 5949 return d3.svg.area()
paulo@89 5950 .interpolate(interpolate)
paulo@89 5951 .defined(defined)
paulo@89 5952 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
paulo@89 5953 .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
paulo@89 5954 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
paulo@89 5955 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
paulo@89 5956 .apply(this, [d.values])
paulo@89 5957 });
paulo@89 5958 groups.exit().selectAll('path.nv-area')
paulo@89 5959 .remove();
paulo@89 5960
paulo@89 5961 areaPaths.watchTransition(renderWatch, 'line: areaPaths')
paulo@89 5962 .attr('d', function(d) {
paulo@89 5963 return d3.svg.area()
paulo@89 5964 .interpolate(interpolate)
paulo@89 5965 .defined(defined)
paulo@89 5966 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
paulo@89 5967 .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
paulo@89 5968 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
paulo@89 5969 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
paulo@89 5970 .apply(this, [d.values])
paulo@89 5971 });
paulo@89 5972
paulo@89 5973 var linePaths = groups.selectAll('path.nv-line')
paulo@89 5974 .data(function(d) { return [d.values] });
paulo@89 5975
paulo@89 5976 linePaths.enter().append('path')
paulo@89 5977 .attr('class', 'nv-line')
paulo@89 5978 .attr('d',
paulo@89 5979 d3.svg.line()
paulo@89 5980 .interpolate(interpolate)
paulo@89 5981 .defined(defined)
paulo@89 5982 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
paulo@89 5983 .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
paulo@89 5984 );
paulo@89 5985
paulo@89 5986 linePaths.watchTransition(renderWatch, 'line: linePaths')
paulo@89 5987 .attr('d',
paulo@89 5988 d3.svg.line()
paulo@89 5989 .interpolate(interpolate)
paulo@89 5990 .defined(defined)
paulo@89 5991 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
paulo@89 5992 .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
paulo@89 5993 );
paulo@89 5994
paulo@89 5995 //store old scales for use in transitions on update
paulo@89 5996 x0 = x.copy();
paulo@89 5997 y0 = y.copy();
paulo@89 5998 });
paulo@89 5999 renderWatch.renderEnd('line immediate');
paulo@89 6000 return chart;
paulo@89 6001 }
paulo@89 6002
paulo@89 6003
paulo@89 6004 //============================================================
paulo@89 6005 // Expose Public Variables
paulo@89 6006 //------------------------------------------------------------
paulo@89 6007
paulo@89 6008 chart.dispatch = dispatch;
paulo@89 6009 chart.scatter = scatter;
paulo@89 6010 // Pass through events
paulo@89 6011 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
paulo@89 6012 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
paulo@89 6013 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
paulo@89 6014
paulo@89 6015 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 6016
paulo@89 6017 chart._options = Object.create({}, {
paulo@89 6018 // simple options, just get/set the necessary values
paulo@89 6019 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 6020 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 6021 defined: {get: function(){return defined;}, set: function(_){defined=_;}},
paulo@89 6022 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
paulo@89 6023 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
paulo@89 6024
paulo@89 6025 // options that require extra logic in the setter
paulo@89 6026 margin: {get: function(){return margin;}, set: function(_){
paulo@89 6027 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 6028 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 6029 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 6030 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 6031 }},
paulo@89 6032 duration: {get: function(){return duration;}, set: function(_){
paulo@89 6033 duration = _;
paulo@89 6034 renderWatch.reset(duration);
paulo@89 6035 scatter.duration(duration);
paulo@89 6036 }},
paulo@89 6037 isArea: {get: function(){return isArea;}, set: function(_){
paulo@89 6038 isArea = d3.functor(_);
paulo@89 6039 }},
paulo@89 6040 x: {get: function(){return getX;}, set: function(_){
paulo@89 6041 getX = _;
paulo@89 6042 scatter.x(_);
paulo@89 6043 }},
paulo@89 6044 y: {get: function(){return getY;}, set: function(_){
paulo@89 6045 getY = _;
paulo@89 6046 scatter.y(_);
paulo@89 6047 }},
paulo@89 6048 color: {get: function(){return color;}, set: function(_){
paulo@89 6049 color = nv.utils.getColor(_);
paulo@89 6050 scatter.color(color);
paulo@89 6051 }}
paulo@89 6052 });
paulo@89 6053
paulo@89 6054 nv.utils.inheritOptions(chart, scatter);
paulo@89 6055 nv.utils.initOptions(chart);
paulo@89 6056
paulo@89 6057 return chart;
paulo@89 6058 };
paulo@89 6059 nv.models.lineChart = function() {
paulo@89 6060 "use strict";
paulo@89 6061
paulo@89 6062 //============================================================
paulo@89 6063 // Public Variables with Default Settings
paulo@89 6064 //------------------------------------------------------------
paulo@89 6065
paulo@89 6066 var lines = nv.models.line()
paulo@89 6067 , xAxis = nv.models.axis()
paulo@89 6068 , yAxis = nv.models.axis()
paulo@89 6069 , legend = nv.models.legend()
paulo@89 6070 , interactiveLayer = nv.interactiveGuideline()
paulo@89 6071 , tooltip = nv.models.tooltip()
paulo@89 6072 ;
paulo@89 6073
paulo@89 6074 var margin = {top: 30, right: 20, bottom: 50, left: 60}
paulo@89 6075 , color = nv.utils.defaultColor()
paulo@89 6076 , width = null
paulo@89 6077 , height = null
paulo@89 6078 , showLegend = true
paulo@89 6079 , showXAxis = true
paulo@89 6080 , showYAxis = true
paulo@89 6081 , rightAlignYAxis = false
paulo@89 6082 , useInteractiveGuideline = false
paulo@89 6083 , x
paulo@89 6084 , y
paulo@89 6085 , state = nv.utils.state()
paulo@89 6086 , defaultState = null
paulo@89 6087 , noData = null
paulo@89 6088 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
paulo@89 6089 , duration = 250
paulo@89 6090 ;
paulo@89 6091
paulo@89 6092 // set options on sub-objects for this chart
paulo@89 6093 xAxis.orient('bottom').tickPadding(7);
paulo@89 6094 yAxis.orient(rightAlignYAxis ? 'right' : 'left');
paulo@89 6095 tooltip.valueFormatter(function(d, i) {
paulo@89 6096 return yAxis.tickFormat()(d, i);
paulo@89 6097 }).headerFormatter(function(d, i) {
paulo@89 6098 return xAxis.tickFormat()(d, i);
paulo@89 6099 });
paulo@89 6100
paulo@89 6101
paulo@89 6102 //============================================================
paulo@89 6103 // Private Variables
paulo@89 6104 //------------------------------------------------------------
paulo@89 6105
paulo@89 6106 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 6107
paulo@89 6108 var stateGetter = function(data) {
paulo@89 6109 return function(){
paulo@89 6110 return {
paulo@89 6111 active: data.map(function(d) { return !d.disabled })
paulo@89 6112 };
paulo@89 6113 }
paulo@89 6114 };
paulo@89 6115
paulo@89 6116 var stateSetter = function(data) {
paulo@89 6117 return function(state) {
paulo@89 6118 if (state.active !== undefined)
paulo@89 6119 data.forEach(function(series,i) {
paulo@89 6120 series.disabled = !state.active[i];
paulo@89 6121 });
paulo@89 6122 }
paulo@89 6123 };
paulo@89 6124
paulo@89 6125 function chart(selection) {
paulo@89 6126 renderWatch.reset();
paulo@89 6127 renderWatch.models(lines);
paulo@89 6128 if (showXAxis) renderWatch.models(xAxis);
paulo@89 6129 if (showYAxis) renderWatch.models(yAxis);
paulo@89 6130
paulo@89 6131 selection.each(function(data) {
paulo@89 6132 var container = d3.select(this),
paulo@89 6133 that = this;
paulo@89 6134 nv.utils.initSVG(container);
paulo@89 6135 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 6136 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 6137
paulo@89 6138 chart.update = function() {
paulo@89 6139 if (duration === 0)
paulo@89 6140 container.call(chart);
paulo@89 6141 else
paulo@89 6142 container.transition().duration(duration).call(chart)
paulo@89 6143 };
paulo@89 6144 chart.container = this;
paulo@89 6145
paulo@89 6146 state
paulo@89 6147 .setter(stateSetter(data), chart.update)
paulo@89 6148 .getter(stateGetter(data))
paulo@89 6149 .update();
paulo@89 6150
paulo@89 6151 // DEPRECATED set state.disableddisabled
paulo@89 6152 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 6153
paulo@89 6154 if (!defaultState) {
paulo@89 6155 var key;
paulo@89 6156 defaultState = {};
paulo@89 6157 for (key in state) {
paulo@89 6158 if (state[key] instanceof Array)
paulo@89 6159 defaultState[key] = state[key].slice(0);
paulo@89 6160 else
paulo@89 6161 defaultState[key] = state[key];
paulo@89 6162 }
paulo@89 6163 }
paulo@89 6164
paulo@89 6165 // Display noData message if there's nothing to show.
paulo@89 6166 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 6167 nv.utils.noData(chart, container)
paulo@89 6168 return chart;
paulo@89 6169 } else {
paulo@89 6170 container.selectAll('.nv-noData').remove();
paulo@89 6171 }
paulo@89 6172
paulo@89 6173
paulo@89 6174 // Setup Scales
paulo@89 6175 x = lines.xScale();
paulo@89 6176 y = lines.yScale();
paulo@89 6177
paulo@89 6178 // Setup containers and skeleton of chart
paulo@89 6179 var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
paulo@89 6180 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
paulo@89 6181 var g = wrap.select('g');
paulo@89 6182
paulo@89 6183 gEnter.append("rect").style("opacity",0);
paulo@89 6184 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 6185 gEnter.append('g').attr('class', 'nv-y nv-axis');
paulo@89 6186 gEnter.append('g').attr('class', 'nv-linesWrap');
paulo@89 6187 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 6188 gEnter.append('g').attr('class', 'nv-interactive');
paulo@89 6189
paulo@89 6190 g.select("rect")
paulo@89 6191 .attr("width",availableWidth)
paulo@89 6192 .attr("height",(availableHeight > 0) ? availableHeight : 0);
paulo@89 6193
paulo@89 6194 // Legend
paulo@89 6195 if (showLegend) {
paulo@89 6196 legend.width(availableWidth);
paulo@89 6197
paulo@89 6198 g.select('.nv-legendWrap')
paulo@89 6199 .datum(data)
paulo@89 6200 .call(legend);
paulo@89 6201
paulo@89 6202 if ( margin.top != legend.height()) {
paulo@89 6203 margin.top = legend.height();
paulo@89 6204 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 6205 }
paulo@89 6206
paulo@89 6207 wrap.select('.nv-legendWrap')
paulo@89 6208 .attr('transform', 'translate(0,' + (-margin.top) +')')
paulo@89 6209 }
paulo@89 6210
paulo@89 6211 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 6212
paulo@89 6213 if (rightAlignYAxis) {
paulo@89 6214 g.select(".nv-y.nv-axis")
paulo@89 6215 .attr("transform", "translate(" + availableWidth + ",0)");
paulo@89 6216 }
paulo@89 6217
paulo@89 6218 //Set up interactive layer
paulo@89 6219 if (useInteractiveGuideline) {
paulo@89 6220 interactiveLayer
paulo@89 6221 .width(availableWidth)
paulo@89 6222 .height(availableHeight)
paulo@89 6223 .margin({left:margin.left, top:margin.top})
paulo@89 6224 .svgContainer(container)
paulo@89 6225 .xScale(x);
paulo@89 6226 wrap.select(".nv-interactive").call(interactiveLayer);
paulo@89 6227 }
paulo@89 6228
paulo@89 6229 lines
paulo@89 6230 .width(availableWidth)
paulo@89 6231 .height(availableHeight)
paulo@89 6232 .color(data.map(function(d,i) {
paulo@89 6233 return d.color || color(d, i);
paulo@89 6234 }).filter(function(d,i) { return !data[i].disabled }));
paulo@89 6235
paulo@89 6236
paulo@89 6237 var linesWrap = g.select('.nv-linesWrap')
paulo@89 6238 .datum(data.filter(function(d) { return !d.disabled }));
paulo@89 6239
paulo@89 6240 linesWrap.call(lines);
paulo@89 6241
paulo@89 6242 // Setup Axes
paulo@89 6243 if (showXAxis) {
paulo@89 6244 xAxis
paulo@89 6245 .scale(x)
paulo@89 6246 ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 6247 .tickSize(-availableHeight, 0);
paulo@89 6248
paulo@89 6249 g.select('.nv-x.nv-axis')
paulo@89 6250 .attr('transform', 'translate(0,' + y.range()[0] + ')');
paulo@89 6251 g.select('.nv-x.nv-axis')
paulo@89 6252 .call(xAxis);
paulo@89 6253 }
paulo@89 6254
paulo@89 6255 if (showYAxis) {
paulo@89 6256 yAxis
paulo@89 6257 .scale(y)
paulo@89 6258 ._ticks(nv.utils.calcTicksY(availableHeight/36, data) )
paulo@89 6259 .tickSize( -availableWidth, 0);
paulo@89 6260
paulo@89 6261 g.select('.nv-y.nv-axis')
paulo@89 6262 .call(yAxis);
paulo@89 6263 }
paulo@89 6264
paulo@89 6265 //============================================================
paulo@89 6266 // Event Handling/Dispatching (in chart's scope)
paulo@89 6267 //------------------------------------------------------------
paulo@89 6268
paulo@89 6269 legend.dispatch.on('stateChange', function(newState) {
paulo@89 6270 for (var key in newState)
paulo@89 6271 state[key] = newState[key];
paulo@89 6272 dispatch.stateChange(state);
paulo@89 6273 chart.update();
paulo@89 6274 });
paulo@89 6275
paulo@89 6276 interactiveLayer.dispatch.on('elementMousemove', function(e) {
paulo@89 6277 lines.clearHighlights();
paulo@89 6278 var singlePoint, pointIndex, pointXLocation, allData = [];
paulo@89 6279 data
paulo@89 6280 .filter(function(series, i) {
paulo@89 6281 series.seriesIndex = i;
paulo@89 6282 return !series.disabled;
paulo@89 6283 })
paulo@89 6284 .forEach(function(series,i) {
paulo@89 6285 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
paulo@89 6286 var point = series.values[pointIndex];
paulo@89 6287 var pointYValue = chart.y()(point, pointIndex);
paulo@89 6288 if (pointYValue != null) {
paulo@89 6289 lines.highlightPoint(i, pointIndex, true);
paulo@89 6290 }
paulo@89 6291 if (point === undefined) return;
paulo@89 6292 if (singlePoint === undefined) singlePoint = point;
paulo@89 6293 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
paulo@89 6294 allData.push({
paulo@89 6295 key: series.key,
paulo@89 6296 value: pointYValue,
paulo@89 6297 color: color(series,series.seriesIndex)
paulo@89 6298 });
paulo@89 6299 });
paulo@89 6300 //Highlight the tooltip entry based on which point the mouse is closest to.
paulo@89 6301 if (allData.length > 2) {
paulo@89 6302 var yValue = chart.yScale().invert(e.mouseY);
paulo@89 6303 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
paulo@89 6304 var threshold = 0.03 * domainExtent;
paulo@89 6305 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
paulo@89 6306 if (indexToHighlight !== null)
paulo@89 6307 allData[indexToHighlight].highlight = true;
paulo@89 6308 }
paulo@89 6309
paulo@89 6310 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
paulo@89 6311 interactiveLayer.tooltip
paulo@89 6312 .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top})
paulo@89 6313 .chartContainer(that.parentNode)
paulo@89 6314 .valueFormatter(function(d,i) {
paulo@89 6315 return d == null ? "N/A" : yAxis.tickFormat()(d);
paulo@89 6316 })
paulo@89 6317 .data({
paulo@89 6318 value: xValue,
paulo@89 6319 index: pointIndex,
paulo@89 6320 series: allData
paulo@89 6321 })();
paulo@89 6322
paulo@89 6323 interactiveLayer.renderGuideLine(pointXLocation);
paulo@89 6324
paulo@89 6325 });
paulo@89 6326
paulo@89 6327 interactiveLayer.dispatch.on('elementClick', function(e) {
paulo@89 6328 var pointXLocation, allData = [];
paulo@89 6329
paulo@89 6330 data.filter(function(series, i) {
paulo@89 6331 series.seriesIndex = i;
paulo@89 6332 return !series.disabled;
paulo@89 6333 }).forEach(function(series) {
paulo@89 6334 var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
paulo@89 6335 var point = series.values[pointIndex];
paulo@89 6336 if (typeof point === 'undefined') return;
paulo@89 6337 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
paulo@89 6338 var yPos = chart.yScale()(chart.y()(point,pointIndex));
paulo@89 6339 allData.push({
paulo@89 6340 point: point,
paulo@89 6341 pointIndex: pointIndex,
paulo@89 6342 pos: [pointXLocation, yPos],
paulo@89 6343 seriesIndex: series.seriesIndex,
paulo@89 6344 series: series
paulo@89 6345 });
paulo@89 6346 });
paulo@89 6347
paulo@89 6348 lines.dispatch.elementClick(allData);
paulo@89 6349 });
paulo@89 6350
paulo@89 6351 interactiveLayer.dispatch.on("elementMouseout",function(e) {
paulo@89 6352 lines.clearHighlights();
paulo@89 6353 });
paulo@89 6354
paulo@89 6355 dispatch.on('changeState', function(e) {
paulo@89 6356 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
paulo@89 6357 data.forEach(function(series,i) {
paulo@89 6358 series.disabled = e.disabled[i];
paulo@89 6359 });
paulo@89 6360
paulo@89 6361 state.disabled = e.disabled;
paulo@89 6362 }
paulo@89 6363
paulo@89 6364 chart.update();
paulo@89 6365 });
paulo@89 6366
paulo@89 6367 });
paulo@89 6368
paulo@89 6369 renderWatch.renderEnd('lineChart immediate');
paulo@89 6370 return chart;
paulo@89 6371 }
paulo@89 6372
paulo@89 6373 //============================================================
paulo@89 6374 // Event Handling/Dispatching (out of chart's scope)
paulo@89 6375 //------------------------------------------------------------
paulo@89 6376
paulo@89 6377 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 6378 tooltip.data(evt).position(evt.pos).hidden(false);
paulo@89 6379 });
paulo@89 6380
paulo@89 6381 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 6382 tooltip.hidden(true)
paulo@89 6383 });
paulo@89 6384
paulo@89 6385 //============================================================
paulo@89 6386 // Expose Public Variables
paulo@89 6387 //------------------------------------------------------------
paulo@89 6388
paulo@89 6389 // expose chart's sub-components
paulo@89 6390 chart.dispatch = dispatch;
paulo@89 6391 chart.lines = lines;
paulo@89 6392 chart.legend = legend;
paulo@89 6393 chart.xAxis = xAxis;
paulo@89 6394 chart.yAxis = yAxis;
paulo@89 6395 chart.interactiveLayer = interactiveLayer;
paulo@89 6396 chart.tooltip = tooltip;
paulo@89 6397
paulo@89 6398 chart.dispatch = dispatch;
paulo@89 6399 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 6400
paulo@89 6401 chart._options = Object.create({}, {
paulo@89 6402 // simple options, just get/set the necessary values
paulo@89 6403 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 6404 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 6405 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 6406 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 6407 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 6408 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 6409 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 6410
paulo@89 6411 // deprecated options
paulo@89 6412 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 6413 // deprecated after 1.7.1
paulo@89 6414 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 6415 tooltip.enabled(!!_);
paulo@89 6416 }},
paulo@89 6417 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 6418 // deprecated after 1.7.1
paulo@89 6419 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 6420 tooltip.contentGenerator(_);
paulo@89 6421 }},
paulo@89 6422
paulo@89 6423 // options that require extra logic in the setter
paulo@89 6424 margin: {get: function(){return margin;}, set: function(_){
paulo@89 6425 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 6426 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 6427 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 6428 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 6429 }},
paulo@89 6430 duration: {get: function(){return duration;}, set: function(_){
paulo@89 6431 duration = _;
paulo@89 6432 renderWatch.reset(duration);
paulo@89 6433 lines.duration(duration);
paulo@89 6434 xAxis.duration(duration);
paulo@89 6435 yAxis.duration(duration);
paulo@89 6436 }},
paulo@89 6437 color: {get: function(){return color;}, set: function(_){
paulo@89 6438 color = nv.utils.getColor(_);
paulo@89 6439 legend.color(color);
paulo@89 6440 lines.color(color);
paulo@89 6441 }},
paulo@89 6442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
paulo@89 6443 rightAlignYAxis = _;
paulo@89 6444 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
paulo@89 6445 }},
paulo@89 6446 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
paulo@89 6447 useInteractiveGuideline = _;
paulo@89 6448 if (useInteractiveGuideline) {
paulo@89 6449 lines.interactive(false);
paulo@89 6450 lines.useVoronoi(false);
paulo@89 6451 }
paulo@89 6452 }}
paulo@89 6453 });
paulo@89 6454
paulo@89 6455 nv.utils.inheritOptions(chart, lines);
paulo@89 6456 nv.utils.initOptions(chart);
paulo@89 6457
paulo@89 6458 return chart;
paulo@89 6459 };
paulo@89 6460 nv.models.linePlusBarChart = function() {
paulo@89 6461 "use strict";
paulo@89 6462
paulo@89 6463 //============================================================
paulo@89 6464 // Public Variables with Default Settings
paulo@89 6465 //------------------------------------------------------------
paulo@89 6466
paulo@89 6467 var lines = nv.models.line()
paulo@89 6468 , lines2 = nv.models.line()
paulo@89 6469 , bars = nv.models.historicalBar()
paulo@89 6470 , bars2 = nv.models.historicalBar()
paulo@89 6471 , xAxis = nv.models.axis()
paulo@89 6472 , x2Axis = nv.models.axis()
paulo@89 6473 , y1Axis = nv.models.axis()
paulo@89 6474 , y2Axis = nv.models.axis()
paulo@89 6475 , y3Axis = nv.models.axis()
paulo@89 6476 , y4Axis = nv.models.axis()
paulo@89 6477 , legend = nv.models.legend()
paulo@89 6478 , brush = d3.svg.brush()
paulo@89 6479 , tooltip = nv.models.tooltip()
paulo@89 6480 ;
paulo@89 6481
paulo@89 6482 var margin = {top: 30, right: 30, bottom: 30, left: 60}
paulo@89 6483 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
paulo@89 6484 , width = null
paulo@89 6485 , height = null
paulo@89 6486 , getX = function(d) { return d.x }
paulo@89 6487 , getY = function(d) { return d.y }
paulo@89 6488 , color = nv.utils.defaultColor()
paulo@89 6489 , showLegend = true
paulo@89 6490 , focusEnable = true
paulo@89 6491 , focusShowAxisY = false
paulo@89 6492 , focusShowAxisX = true
paulo@89 6493 , focusHeight = 50
paulo@89 6494 , extent
paulo@89 6495 , brushExtent = null
paulo@89 6496 , x
paulo@89 6497 , x2
paulo@89 6498 , y1
paulo@89 6499 , y2
paulo@89 6500 , y3
paulo@89 6501 , y4
paulo@89 6502 , noData = null
paulo@89 6503 , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
paulo@89 6504 , transitionDuration = 0
paulo@89 6505 , state = nv.utils.state()
paulo@89 6506 , defaultState = null
paulo@89 6507 , legendLeftAxisHint = ' (left axis)'
paulo@89 6508 , legendRightAxisHint = ' (right axis)'
paulo@89 6509 ;
paulo@89 6510
paulo@89 6511 lines.clipEdge(true);
paulo@89 6512 lines2.interactive(false);
paulo@89 6513 xAxis.orient('bottom').tickPadding(5);
paulo@89 6514 y1Axis.orient('left');
paulo@89 6515 y2Axis.orient('right');
paulo@89 6516 x2Axis.orient('bottom').tickPadding(5);
paulo@89 6517 y3Axis.orient('left');
paulo@89 6518 y4Axis.orient('right');
paulo@89 6519
paulo@89 6520 tooltip.headerEnabled(true).headerFormatter(function(d, i) {
paulo@89 6521 return xAxis.tickFormat()(d, i);
paulo@89 6522 });
paulo@89 6523
paulo@89 6524 //============================================================
paulo@89 6525 // Private Variables
paulo@89 6526 //------------------------------------------------------------
paulo@89 6527
paulo@89 6528 var stateGetter = function(data) {
paulo@89 6529 return function(){
paulo@89 6530 return {
paulo@89 6531 active: data.map(function(d) { return !d.disabled })
paulo@89 6532 };
paulo@89 6533 }
paulo@89 6534 };
paulo@89 6535
paulo@89 6536 var stateSetter = function(data) {
paulo@89 6537 return function(state) {
paulo@89 6538 if (state.active !== undefined)
paulo@89 6539 data.forEach(function(series,i) {
paulo@89 6540 series.disabled = !state.active[i];
paulo@89 6541 });
paulo@89 6542 }
paulo@89 6543 };
paulo@89 6544
paulo@89 6545 function chart(selection) {
paulo@89 6546 selection.each(function(data) {
paulo@89 6547 var container = d3.select(this),
paulo@89 6548 that = this;
paulo@89 6549 nv.utils.initSVG(container);
paulo@89 6550 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 6551 availableHeight1 = nv.utils.availableHeight(height, container, margin)
paulo@89 6552 - (focusEnable ? focusHeight : 0),
paulo@89 6553 availableHeight2 = focusHeight - margin2.top - margin2.bottom;
paulo@89 6554
paulo@89 6555 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
paulo@89 6556 chart.container = this;
paulo@89 6557
paulo@89 6558 state
paulo@89 6559 .setter(stateSetter(data), chart.update)
paulo@89 6560 .getter(stateGetter(data))
paulo@89 6561 .update();
paulo@89 6562
paulo@89 6563 // DEPRECATED set state.disableddisabled
paulo@89 6564 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 6565
paulo@89 6566 if (!defaultState) {
paulo@89 6567 var key;
paulo@89 6568 defaultState = {};
paulo@89 6569 for (key in state) {
paulo@89 6570 if (state[key] instanceof Array)
paulo@89 6571 defaultState[key] = state[key].slice(0);
paulo@89 6572 else
paulo@89 6573 defaultState[key] = state[key];
paulo@89 6574 }
paulo@89 6575 }
paulo@89 6576
paulo@89 6577 // Display No Data message if there's nothing to show.
paulo@89 6578 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 6579 nv.utils.noData(chart, container)
paulo@89 6580 return chart;
paulo@89 6581 } else {
paulo@89 6582 container.selectAll('.nv-noData').remove();
paulo@89 6583 }
paulo@89 6584
paulo@89 6585 // Setup Scales
paulo@89 6586 var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
paulo@89 6587 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
paulo@89 6588
paulo@89 6589 x = bars.xScale();
paulo@89 6590 x2 = x2Axis.scale();
paulo@89 6591 y1 = bars.yScale();
paulo@89 6592 y2 = lines.yScale();
paulo@89 6593 y3 = bars2.yScale();
paulo@89 6594 y4 = lines2.yScale();
paulo@89 6595
paulo@89 6596 var series1 = data
paulo@89 6597 .filter(function(d) { return !d.disabled && d.bar })
paulo@89 6598 .map(function(d) {
paulo@89 6599 return d.values.map(function(d,i) {
paulo@89 6600 return { x: getX(d,i), y: getY(d,i) }
paulo@89 6601 })
paulo@89 6602 });
paulo@89 6603
paulo@89 6604 var series2 = data
paulo@89 6605 .filter(function(d) { return !d.disabled && !d.bar })
paulo@89 6606 .map(function(d) {
paulo@89 6607 return d.values.map(function(d,i) {
paulo@89 6608 return { x: getX(d,i), y: getY(d,i) }
paulo@89 6609 })
paulo@89 6610 });
paulo@89 6611
paulo@89 6612 x.range([0, availableWidth]);
paulo@89 6613
paulo@89 6614 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
paulo@89 6615 .range([0, availableWidth]);
paulo@89 6616
paulo@89 6617 // Setup containers and skeleton of chart
paulo@89 6618 var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
paulo@89 6619 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
paulo@89 6620 var g = wrap.select('g');
paulo@89 6621
paulo@89 6622 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 6623
paulo@89 6624 // this is the main chart
paulo@89 6625 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
paulo@89 6626 focusEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 6627 focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
paulo@89 6628 focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
paulo@89 6629 focusEnter.append('g').attr('class', 'nv-barsWrap');
paulo@89 6630 focusEnter.append('g').attr('class', 'nv-linesWrap');
paulo@89 6631
paulo@89 6632 // context chart is where you can focus in
paulo@89 6633 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
paulo@89 6634 contextEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 6635 contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
paulo@89 6636 contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
paulo@89 6637 contextEnter.append('g').attr('class', 'nv-barsWrap');
paulo@89 6638 contextEnter.append('g').attr('class', 'nv-linesWrap');
paulo@89 6639 contextEnter.append('g').attr('class', 'nv-brushBackground');
paulo@89 6640 contextEnter.append('g').attr('class', 'nv-x nv-brush');
paulo@89 6641
paulo@89 6642 //============================================================
paulo@89 6643 // Legend
paulo@89 6644 //------------------------------------------------------------
paulo@89 6645
paulo@89 6646 if (showLegend) {
paulo@89 6647 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
paulo@89 6648 var legendXPosition = legend.align() ? legendWidth : 0;
paulo@89 6649
paulo@89 6650 legend.width(legendWidth);
paulo@89 6651
paulo@89 6652 g.select('.nv-legendWrap')
paulo@89 6653 .datum(data.map(function(series) {
paulo@89 6654 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
paulo@89 6655 series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint);
paulo@89 6656 return series;
paulo@89 6657 }))
paulo@89 6658 .call(legend);
paulo@89 6659
paulo@89 6660 if ( margin.top != legend.height()) {
paulo@89 6661 margin.top = legend.height();
paulo@89 6662 // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"?
paulo@89 6663 availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight;
paulo@89 6664 }
paulo@89 6665
paulo@89 6666 g.select('.nv-legendWrap')
paulo@89 6667 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
paulo@89 6668 }
paulo@89 6669
paulo@89 6670 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 6671
paulo@89 6672 //============================================================
paulo@89 6673 // Context chart (focus chart) components
paulo@89 6674 //------------------------------------------------------------
paulo@89 6675
paulo@89 6676 // hide or show the focus context chart
paulo@89 6677 g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
paulo@89 6678
paulo@89 6679 bars2
paulo@89 6680 .width(availableWidth)
paulo@89 6681 .height(availableHeight2)
paulo@89 6682 .color(data.map(function (d, i) {
paulo@89 6683 return d.color || color(d, i);
paulo@89 6684 }).filter(function (d, i) {
paulo@89 6685 return !data[i].disabled && data[i].bar
paulo@89 6686 }));
paulo@89 6687 lines2
paulo@89 6688 .width(availableWidth)
paulo@89 6689 .height(availableHeight2)
paulo@89 6690 .color(data.map(function (d, i) {
paulo@89 6691 return d.color || color(d, i);
paulo@89 6692 }).filter(function (d, i) {
paulo@89 6693 return !data[i].disabled && !data[i].bar
paulo@89 6694 }));
paulo@89 6695
paulo@89 6696 var bars2Wrap = g.select('.nv-context .nv-barsWrap')
paulo@89 6697 .datum(dataBars.length ? dataBars : [
paulo@89 6698 {values: []}
paulo@89 6699 ]);
paulo@89 6700 var lines2Wrap = g.select('.nv-context .nv-linesWrap')
paulo@89 6701 .datum(!dataLines[0].disabled ? dataLines : [
paulo@89 6702 {values: []}
paulo@89 6703 ]);
paulo@89 6704
paulo@89 6705 g.select('.nv-context')
paulo@89 6706 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
paulo@89 6707
paulo@89 6708 bars2Wrap.transition().call(bars2);
paulo@89 6709 lines2Wrap.transition().call(lines2);
paulo@89 6710
paulo@89 6711 // context (focus chart) axis controls
paulo@89 6712 if (focusShowAxisX) {
paulo@89 6713 x2Axis
paulo@89 6714 ._ticks( nv.utils.calcTicksX(availableWidth / 100, data))
paulo@89 6715 .tickSize(-availableHeight2, 0);
paulo@89 6716 g.select('.nv-context .nv-x.nv-axis')
paulo@89 6717 .attr('transform', 'translate(0,' + y3.range()[0] + ')');
paulo@89 6718 g.select('.nv-context .nv-x.nv-axis').transition()
paulo@89 6719 .call(x2Axis);
paulo@89 6720 }
paulo@89 6721
paulo@89 6722 if (focusShowAxisY) {
paulo@89 6723 y3Axis
paulo@89 6724 .scale(y3)
paulo@89 6725 ._ticks( availableHeight2 / 36 )
paulo@89 6726 .tickSize( -availableWidth, 0);
paulo@89 6727 y4Axis
paulo@89 6728 .scale(y4)
paulo@89 6729 ._ticks( availableHeight2 / 36 )
paulo@89 6730 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
paulo@89 6731
paulo@89 6732 g.select('.nv-context .nv-y3.nv-axis')
paulo@89 6733 .style('opacity', dataBars.length ? 1 : 0)
paulo@89 6734 .attr('transform', 'translate(0,' + x2.range()[0] + ')');
paulo@89 6735 g.select('.nv-context .nv-y2.nv-axis')
paulo@89 6736 .style('opacity', dataLines.length ? 1 : 0)
paulo@89 6737 .attr('transform', 'translate(' + x2.range()[1] + ',0)');
paulo@89 6738
paulo@89 6739 g.select('.nv-context .nv-y1.nv-axis').transition()
paulo@89 6740 .call(y3Axis);
paulo@89 6741 g.select('.nv-context .nv-y2.nv-axis').transition()
paulo@89 6742 .call(y4Axis);
paulo@89 6743 }
paulo@89 6744
paulo@89 6745 // Setup Brush
paulo@89 6746 brush.x(x2).on('brush', onBrush);
paulo@89 6747
paulo@89 6748 if (brushExtent) brush.extent(brushExtent);
paulo@89 6749
paulo@89 6750 var brushBG = g.select('.nv-brushBackground').selectAll('g')
paulo@89 6751 .data([brushExtent || brush.extent()]);
paulo@89 6752
paulo@89 6753 var brushBGenter = brushBG.enter()
paulo@89 6754 .append('g');
paulo@89 6755
paulo@89 6756 brushBGenter.append('rect')
paulo@89 6757 .attr('class', 'left')
paulo@89 6758 .attr('x', 0)
paulo@89 6759 .attr('y', 0)
paulo@89 6760 .attr('height', availableHeight2);
paulo@89 6761
paulo@89 6762 brushBGenter.append('rect')
paulo@89 6763 .attr('class', 'right')
paulo@89 6764 .attr('x', 0)
paulo@89 6765 .attr('y', 0)
paulo@89 6766 .attr('height', availableHeight2);
paulo@89 6767
paulo@89 6768 var gBrush = g.select('.nv-x.nv-brush')
paulo@89 6769 .call(brush);
paulo@89 6770 gBrush.selectAll('rect')
paulo@89 6771 //.attr('y', -5)
paulo@89 6772 .attr('height', availableHeight2);
paulo@89 6773 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
paulo@89 6774
paulo@89 6775 //============================================================
paulo@89 6776 // Event Handling/Dispatching (in chart's scope)
paulo@89 6777 //------------------------------------------------------------
paulo@89 6778
paulo@89 6779 legend.dispatch.on('stateChange', function(newState) {
paulo@89 6780 for (var key in newState)
paulo@89 6781 state[key] = newState[key];
paulo@89 6782 dispatch.stateChange(state);
paulo@89 6783 chart.update();
paulo@89 6784 });
paulo@89 6785
paulo@89 6786 // Update chart from a state object passed to event handler
paulo@89 6787 dispatch.on('changeState', function(e) {
paulo@89 6788 if (typeof e.disabled !== 'undefined') {
paulo@89 6789 data.forEach(function(series,i) {
paulo@89 6790 series.disabled = e.disabled[i];
paulo@89 6791 });
paulo@89 6792 state.disabled = e.disabled;
paulo@89 6793 }
paulo@89 6794 chart.update();
paulo@89 6795 });
paulo@89 6796
paulo@89 6797 //============================================================
paulo@89 6798 // Functions
paulo@89 6799 //------------------------------------------------------------
paulo@89 6800
paulo@89 6801 // Taken from crossfilter (http://square.github.com/crossfilter/)
paulo@89 6802 function resizePath(d) {
paulo@89 6803 var e = +(d == 'e'),
paulo@89 6804 x = e ? 1 : -1,
paulo@89 6805 y = availableHeight2 / 3;
paulo@89 6806 return 'M' + (.5 * x) + ',' + y
paulo@89 6807 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
paulo@89 6808 + 'V' + (2 * y - 6)
paulo@89 6809 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
paulo@89 6810 + 'Z'
paulo@89 6811 + 'M' + (2.5 * x) + ',' + (y + 8)
paulo@89 6812 + 'V' + (2 * y - 8)
paulo@89 6813 + 'M' + (4.5 * x) + ',' + (y + 8)
paulo@89 6814 + 'V' + (2 * y - 8);
paulo@89 6815 }
paulo@89 6816
paulo@89 6817
paulo@89 6818 function updateBrushBG() {
paulo@89 6819 if (!brush.empty()) brush.extent(brushExtent);
paulo@89 6820 brushBG
paulo@89 6821 .data([brush.empty() ? x2.domain() : brushExtent])
paulo@89 6822 .each(function(d,i) {
paulo@89 6823 var leftWidth = x2(d[0]) - x2.range()[0],
paulo@89 6824 rightWidth = x2.range()[1] - x2(d[1]);
paulo@89 6825 d3.select(this).select('.left')
paulo@89 6826 .attr('width', leftWidth < 0 ? 0 : leftWidth);
paulo@89 6827
paulo@89 6828 d3.select(this).select('.right')
paulo@89 6829 .attr('x', x2(d[1]))
paulo@89 6830 .attr('width', rightWidth < 0 ? 0 : rightWidth);
paulo@89 6831 });
paulo@89 6832 }
paulo@89 6833
paulo@89 6834 function onBrush() {
paulo@89 6835 brushExtent = brush.empty() ? null : brush.extent();
paulo@89 6836 extent = brush.empty() ? x2.domain() : brush.extent();
paulo@89 6837 dispatch.brush({extent: extent, brush: brush});
paulo@89 6838 updateBrushBG();
paulo@89 6839
paulo@89 6840 // Prepare Main (Focus) Bars and Lines
paulo@89 6841 bars
paulo@89 6842 .width(availableWidth)
paulo@89 6843 .height(availableHeight1)
paulo@89 6844 .color(data.map(function(d,i) {
paulo@89 6845 return d.color || color(d, i);
paulo@89 6846 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
paulo@89 6847
paulo@89 6848 lines
paulo@89 6849 .width(availableWidth)
paulo@89 6850 .height(availableHeight1)
paulo@89 6851 .color(data.map(function(d,i) {
paulo@89 6852 return d.color || color(d, i);
paulo@89 6853 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
paulo@89 6854
paulo@89 6855 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
paulo@89 6856 .datum(!dataBars.length ? [{values:[]}] :
paulo@89 6857 dataBars
paulo@89 6858 .map(function(d,i) {
paulo@89 6859 return {
paulo@89 6860 key: d.key,
paulo@89 6861 values: d.values.filter(function(d,i) {
paulo@89 6862 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
paulo@89 6863 })
paulo@89 6864 }
paulo@89 6865 })
paulo@89 6866 );
paulo@89 6867
paulo@89 6868 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
paulo@89 6869 .datum(dataLines[0].disabled ? [{values:[]}] :
paulo@89 6870 dataLines
paulo@89 6871 .map(function(d,i) {
paulo@89 6872 return {
paulo@89 6873 area: d.area,
paulo@89 6874 fillOpacity: d.fillOpacity,
paulo@89 6875 key: d.key,
paulo@89 6876 values: d.values.filter(function(d,i) {
paulo@89 6877 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
paulo@89 6878 })
paulo@89 6879 }
paulo@89 6880 })
paulo@89 6881 );
paulo@89 6882
paulo@89 6883 // Update Main (Focus) X Axis
paulo@89 6884 if (dataBars.length) {
paulo@89 6885 x = bars.xScale();
paulo@89 6886 } else {
paulo@89 6887 x = lines.xScale();
paulo@89 6888 }
paulo@89 6889
paulo@89 6890 xAxis
paulo@89 6891 .scale(x)
paulo@89 6892 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 6893 .tickSize(-availableHeight1, 0);
paulo@89 6894
paulo@89 6895 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
paulo@89 6896
paulo@89 6897 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
paulo@89 6898 .call(xAxis);
paulo@89 6899
paulo@89 6900 // Update Main (Focus) Bars and Lines
paulo@89 6901 focusBarsWrap.transition().duration(transitionDuration).call(bars);
paulo@89 6902 focusLinesWrap.transition().duration(transitionDuration).call(lines);
paulo@89 6903
paulo@89 6904 // Setup and Update Main (Focus) Y Axes
paulo@89 6905 g.select('.nv-focus .nv-x.nv-axis')
paulo@89 6906 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
paulo@89 6907
paulo@89 6908 y1Axis
paulo@89 6909 .scale(y1)
paulo@89 6910 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
paulo@89 6911 .tickSize(-availableWidth, 0);
paulo@89 6912 y2Axis
paulo@89 6913 .scale(y2)
paulo@89 6914 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
paulo@89 6915 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
paulo@89 6916
paulo@89 6917 g.select('.nv-focus .nv-y1.nv-axis')
paulo@89 6918 .style('opacity', dataBars.length ? 1 : 0);
paulo@89 6919 g.select('.nv-focus .nv-y2.nv-axis')
paulo@89 6920 .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0)
paulo@89 6921 .attr('transform', 'translate(' + x.range()[1] + ',0)');
paulo@89 6922
paulo@89 6923 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
paulo@89 6924 .call(y1Axis);
paulo@89 6925 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
paulo@89 6926 .call(y2Axis);
paulo@89 6927 }
paulo@89 6928
paulo@89 6929 onBrush();
paulo@89 6930
paulo@89 6931 });
paulo@89 6932
paulo@89 6933 return chart;
paulo@89 6934 }
paulo@89 6935
paulo@89 6936 //============================================================
paulo@89 6937 // Event Handling/Dispatching (out of chart's scope)
paulo@89 6938 //------------------------------------------------------------
paulo@89 6939
paulo@89 6940 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 6941 tooltip
paulo@89 6942 .duration(100)
paulo@89 6943 .valueFormatter(function(d, i) {
paulo@89 6944 return y2Axis.tickFormat()(d, i);
paulo@89 6945 })
paulo@89 6946 .data(evt)
paulo@89 6947 .position(evt.pos)
paulo@89 6948 .hidden(false);
paulo@89 6949 });
paulo@89 6950
paulo@89 6951 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 6952 tooltip.hidden(true)
paulo@89 6953 });
paulo@89 6954
paulo@89 6955 bars.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 6956 evt.value = chart.x()(evt.data);
paulo@89 6957 evt['series'] = {
paulo@89 6958 value: chart.y()(evt.data),
paulo@89 6959 color: evt.color
paulo@89 6960 };
paulo@89 6961 tooltip
paulo@89 6962 .duration(0)
paulo@89 6963 .valueFormatter(function(d, i) {
paulo@89 6964 return y1Axis.tickFormat()(d, i);
paulo@89 6965 })
paulo@89 6966 .data(evt)
paulo@89 6967 .hidden(false);
paulo@89 6968 });
paulo@89 6969
paulo@89 6970 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 6971 tooltip.hidden(true);
paulo@89 6972 });
paulo@89 6973
paulo@89 6974 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 6975 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 6976 });
paulo@89 6977
paulo@89 6978 //============================================================
paulo@89 6979
paulo@89 6980
paulo@89 6981 //============================================================
paulo@89 6982 // Expose Public Variables
paulo@89 6983 //------------------------------------------------------------
paulo@89 6984
paulo@89 6985 // expose chart's sub-components
paulo@89 6986 chart.dispatch = dispatch;
paulo@89 6987 chart.legend = legend;
paulo@89 6988 chart.lines = lines;
paulo@89 6989 chart.lines2 = lines2;
paulo@89 6990 chart.bars = bars;
paulo@89 6991 chart.bars2 = bars2;
paulo@89 6992 chart.xAxis = xAxis;
paulo@89 6993 chart.x2Axis = x2Axis;
paulo@89 6994 chart.y1Axis = y1Axis;
paulo@89 6995 chart.y2Axis = y2Axis;
paulo@89 6996 chart.y3Axis = y3Axis;
paulo@89 6997 chart.y4Axis = y4Axis;
paulo@89 6998 chart.tooltip = tooltip;
paulo@89 6999
paulo@89 7000 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 7001
paulo@89 7002 chart._options = Object.create({}, {
paulo@89 7003 // simple options, just get/set the necessary values
paulo@89 7004 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 7005 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 7006 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 7007 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
paulo@89 7008 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 7009 focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
paulo@89 7010 focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
paulo@89 7011 focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
paulo@89 7012 focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
paulo@89 7013 legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}},
paulo@89 7014 legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
paulo@89 7015
paulo@89 7016 // deprecated options
paulo@89 7017 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 7018 // deprecated after 1.7.1
paulo@89 7019 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 7020 tooltip.enabled(!!_);
paulo@89 7021 }},
paulo@89 7022 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 7023 // deprecated after 1.7.1
paulo@89 7024 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 7025 tooltip.contentGenerator(_);
paulo@89 7026 }},
paulo@89 7027
paulo@89 7028 // options that require extra logic in the setter
paulo@89 7029 margin: {get: function(){return margin;}, set: function(_){
paulo@89 7030 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 7031 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 7032 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 7033 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 7034 }},
paulo@89 7035 duration: {get: function(){return transitionDuration;}, set: function(_){
paulo@89 7036 transitionDuration = _;
paulo@89 7037 }},
paulo@89 7038 color: {get: function(){return color;}, set: function(_){
paulo@89 7039 color = nv.utils.getColor(_);
paulo@89 7040 legend.color(color);
paulo@89 7041 }},
paulo@89 7042 x: {get: function(){return getX;}, set: function(_){
paulo@89 7043 getX = _;
paulo@89 7044 lines.x(_);
paulo@89 7045 lines2.x(_);
paulo@89 7046 bars.x(_);
paulo@89 7047 bars2.x(_);
paulo@89 7048 }},
paulo@89 7049 y: {get: function(){return getY;}, set: function(_){
paulo@89 7050 getY = _;
paulo@89 7051 lines.y(_);
paulo@89 7052 lines2.y(_);
paulo@89 7053 bars.y(_);
paulo@89 7054 bars2.y(_);
paulo@89 7055 }}
paulo@89 7056 });
paulo@89 7057
paulo@89 7058 nv.utils.inheritOptions(chart, lines);
paulo@89 7059 nv.utils.initOptions(chart);
paulo@89 7060
paulo@89 7061 return chart;
paulo@89 7062 };
paulo@89 7063 nv.models.lineWithFocusChart = function() {
paulo@89 7064 "use strict";
paulo@89 7065
paulo@89 7066 //============================================================
paulo@89 7067 // Public Variables with Default Settings
paulo@89 7068 //------------------------------------------------------------
paulo@89 7069
paulo@89 7070 var lines = nv.models.line()
paulo@89 7071 , lines2 = nv.models.line()
paulo@89 7072 , xAxis = nv.models.axis()
paulo@89 7073 , yAxis = nv.models.axis()
paulo@89 7074 , x2Axis = nv.models.axis()
paulo@89 7075 , y2Axis = nv.models.axis()
paulo@89 7076 , legend = nv.models.legend()
paulo@89 7077 , brush = d3.svg.brush()
paulo@89 7078 , tooltip = nv.models.tooltip()
paulo@89 7079 , interactiveLayer = nv.interactiveGuideline()
paulo@89 7080 ;
paulo@89 7081
paulo@89 7082 var margin = {top: 30, right: 30, bottom: 30, left: 60}
paulo@89 7083 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
paulo@89 7084 , color = nv.utils.defaultColor()
paulo@89 7085 , width = null
paulo@89 7086 , height = null
paulo@89 7087 , height2 = 50
paulo@89 7088 , useInteractiveGuideline = false
paulo@89 7089 , x
paulo@89 7090 , y
paulo@89 7091 , x2
paulo@89 7092 , y2
paulo@89 7093 , showLegend = true
paulo@89 7094 , brushExtent = null
paulo@89 7095 , noData = null
paulo@89 7096 , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
paulo@89 7097 , transitionDuration = 250
paulo@89 7098 , state = nv.utils.state()
paulo@89 7099 , defaultState = null
paulo@89 7100 ;
paulo@89 7101
paulo@89 7102 lines.clipEdge(true).duration(0);
paulo@89 7103 lines2.interactive(false);
paulo@89 7104 xAxis.orient('bottom').tickPadding(5);
paulo@89 7105 yAxis.orient('left');
paulo@89 7106 x2Axis.orient('bottom').tickPadding(5);
paulo@89 7107 y2Axis.orient('left');
paulo@89 7108
paulo@89 7109 tooltip.valueFormatter(function(d, i) {
paulo@89 7110 return yAxis.tickFormat()(d, i);
paulo@89 7111 }).headerFormatter(function(d, i) {
paulo@89 7112 return xAxis.tickFormat()(d, i);
paulo@89 7113 });
paulo@89 7114
paulo@89 7115 //============================================================
paulo@89 7116 // Private Variables
paulo@89 7117 //------------------------------------------------------------
paulo@89 7118
paulo@89 7119 var stateGetter = function(data) {
paulo@89 7120 return function(){
paulo@89 7121 return {
paulo@89 7122 active: data.map(function(d) { return !d.disabled })
paulo@89 7123 };
paulo@89 7124 }
paulo@89 7125 };
paulo@89 7126
paulo@89 7127 var stateSetter = function(data) {
paulo@89 7128 return function(state) {
paulo@89 7129 if (state.active !== undefined)
paulo@89 7130 data.forEach(function(series,i) {
paulo@89 7131 series.disabled = !state.active[i];
paulo@89 7132 });
paulo@89 7133 }
paulo@89 7134 };
paulo@89 7135
paulo@89 7136 function chart(selection) {
paulo@89 7137 selection.each(function(data) {
paulo@89 7138 var container = d3.select(this),
paulo@89 7139 that = this;
paulo@89 7140 nv.utils.initSVG(container);
paulo@89 7141 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 7142 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2,
paulo@89 7143 availableHeight2 = height2 - margin2.top - margin2.bottom;
paulo@89 7144
paulo@89 7145 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
paulo@89 7146 chart.container = this;
paulo@89 7147
paulo@89 7148 state
paulo@89 7149 .setter(stateSetter(data), chart.update)
paulo@89 7150 .getter(stateGetter(data))
paulo@89 7151 .update();
paulo@89 7152
paulo@89 7153 // DEPRECATED set state.disableddisabled
paulo@89 7154 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 7155
paulo@89 7156 if (!defaultState) {
paulo@89 7157 var key;
paulo@89 7158 defaultState = {};
paulo@89 7159 for (key in state) {
paulo@89 7160 if (state[key] instanceof Array)
paulo@89 7161 defaultState[key] = state[key].slice(0);
paulo@89 7162 else
paulo@89 7163 defaultState[key] = state[key];
paulo@89 7164 }
paulo@89 7165 }
paulo@89 7166
paulo@89 7167 // Display No Data message if there's nothing to show.
paulo@89 7168 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 7169 nv.utils.noData(chart, container)
paulo@89 7170 return chart;
paulo@89 7171 } else {
paulo@89 7172 container.selectAll('.nv-noData').remove();
paulo@89 7173 }
paulo@89 7174
paulo@89 7175 // Setup Scales
paulo@89 7176 x = lines.xScale();
paulo@89 7177 y = lines.yScale();
paulo@89 7178 x2 = lines2.xScale();
paulo@89 7179 y2 = lines2.yScale();
paulo@89 7180
paulo@89 7181 // Setup containers and skeleton of chart
paulo@89 7182 var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
paulo@89 7183 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
paulo@89 7184 var g = wrap.select('g');
paulo@89 7185
paulo@89 7186 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 7187
paulo@89 7188 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
paulo@89 7189 focusEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 7190 focusEnter.append('g').attr('class', 'nv-y nv-axis');
paulo@89 7191 focusEnter.append('g').attr('class', 'nv-linesWrap');
paulo@89 7192 focusEnter.append('g').attr('class', 'nv-interactive');
paulo@89 7193
paulo@89 7194 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
paulo@89 7195 contextEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 7196 contextEnter.append('g').attr('class', 'nv-y nv-axis');
paulo@89 7197 contextEnter.append('g').attr('class', 'nv-linesWrap');
paulo@89 7198 contextEnter.append('g').attr('class', 'nv-brushBackground');
paulo@89 7199 contextEnter.append('g').attr('class', 'nv-x nv-brush');
paulo@89 7200
paulo@89 7201 // Legend
paulo@89 7202 if (showLegend) {
paulo@89 7203 legend.width(availableWidth);
paulo@89 7204
paulo@89 7205 g.select('.nv-legendWrap')
paulo@89 7206 .datum(data)
paulo@89 7207 .call(legend);
paulo@89 7208
paulo@89 7209 if ( margin.top != legend.height()) {
paulo@89 7210 margin.top = legend.height();
paulo@89 7211 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2;
paulo@89 7212 }
paulo@89 7213
paulo@89 7214 g.select('.nv-legendWrap')
paulo@89 7215 .attr('transform', 'translate(0,' + (-margin.top) +')')
paulo@89 7216 }
paulo@89 7217
paulo@89 7218 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 7219
paulo@89 7220
paulo@89 7221 //Set up interactive layer
paulo@89 7222 if (useInteractiveGuideline) {
paulo@89 7223 interactiveLayer
paulo@89 7224 .width(availableWidth)
paulo@89 7225 .height(availableHeight1)
paulo@89 7226 .margin({left:margin.left, top:margin.top})
paulo@89 7227 .svgContainer(container)
paulo@89 7228 .xScale(x);
paulo@89 7229 wrap.select(".nv-interactive").call(interactiveLayer);
paulo@89 7230 }
paulo@89 7231
paulo@89 7232 // Main Chart Component(s)
paulo@89 7233 lines
paulo@89 7234 .width(availableWidth)
paulo@89 7235 .height(availableHeight1)
paulo@89 7236 .color(
paulo@89 7237 data
paulo@89 7238 .map(function(d,i) {
paulo@89 7239 return d.color || color(d, i);
paulo@89 7240 })
paulo@89 7241 .filter(function(d,i) {
paulo@89 7242 return !data[i].disabled;
paulo@89 7243 })
paulo@89 7244 );
paulo@89 7245
paulo@89 7246 lines2
paulo@89 7247 .defined(lines.defined())
paulo@89 7248 .width(availableWidth)
paulo@89 7249 .height(availableHeight2)
paulo@89 7250 .color(
paulo@89 7251 data
paulo@89 7252 .map(function(d,i) {
paulo@89 7253 return d.color || color(d, i);
paulo@89 7254 })
paulo@89 7255 .filter(function(d,i) {
paulo@89 7256 return !data[i].disabled;
paulo@89 7257 })
paulo@89 7258 );
paulo@89 7259
paulo@89 7260 g.select('.nv-context')
paulo@89 7261 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
paulo@89 7262
paulo@89 7263 var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
paulo@89 7264 .datum(data.filter(function(d) { return !d.disabled }))
paulo@89 7265
paulo@89 7266 d3.transition(contextLinesWrap).call(lines2);
paulo@89 7267
paulo@89 7268 // Setup Main (Focus) Axes
paulo@89 7269 xAxis
paulo@89 7270 .scale(x)
paulo@89 7271 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 7272 .tickSize(-availableHeight1, 0);
paulo@89 7273
paulo@89 7274 yAxis
paulo@89 7275 .scale(y)
paulo@89 7276 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
paulo@89 7277 .tickSize( -availableWidth, 0);
paulo@89 7278
paulo@89 7279 g.select('.nv-focus .nv-x.nv-axis')
paulo@89 7280 .attr('transform', 'translate(0,' + availableHeight1 + ')');
paulo@89 7281
paulo@89 7282 // Setup Brush
paulo@89 7283 brush
paulo@89 7284 .x(x2)
paulo@89 7285 .on('brush', function() {
paulo@89 7286 onBrush();
paulo@89 7287 });
paulo@89 7288
paulo@89 7289 if (brushExtent) brush.extent(brushExtent);
paulo@89 7290
paulo@89 7291 var brushBG = g.select('.nv-brushBackground').selectAll('g')
paulo@89 7292 .data([brushExtent || brush.extent()])
paulo@89 7293
paulo@89 7294 var brushBGenter = brushBG.enter()
paulo@89 7295 .append('g');
paulo@89 7296
paulo@89 7297 brushBGenter.append('rect')
paulo@89 7298 .attr('class', 'left')
paulo@89 7299 .attr('x', 0)
paulo@89 7300 .attr('y', 0)
paulo@89 7301 .attr('height', availableHeight2);
paulo@89 7302
paulo@89 7303 brushBGenter.append('rect')
paulo@89 7304 .attr('class', 'right')
paulo@89 7305 .attr('x', 0)
paulo@89 7306 .attr('y', 0)
paulo@89 7307 .attr('height', availableHeight2);
paulo@89 7308
paulo@89 7309 var gBrush = g.select('.nv-x.nv-brush')
paulo@89 7310 .call(brush);
paulo@89 7311 gBrush.selectAll('rect')
paulo@89 7312 .attr('height', availableHeight2);
paulo@89 7313 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
paulo@89 7314
paulo@89 7315 onBrush();
paulo@89 7316
paulo@89 7317 // Setup Secondary (Context) Axes
paulo@89 7318 x2Axis
paulo@89 7319 .scale(x2)
paulo@89 7320 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 7321 .tickSize(-availableHeight2, 0);
paulo@89 7322
paulo@89 7323 g.select('.nv-context .nv-x.nv-axis')
paulo@89 7324 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
paulo@89 7325 d3.transition(g.select('.nv-context .nv-x.nv-axis'))
paulo@89 7326 .call(x2Axis);
paulo@89 7327
paulo@89 7328 y2Axis
paulo@89 7329 .scale(y2)
paulo@89 7330 ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
paulo@89 7331 .tickSize( -availableWidth, 0);
paulo@89 7332
paulo@89 7333 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
paulo@89 7334 .call(y2Axis);
paulo@89 7335
paulo@89 7336 g.select('.nv-context .nv-x.nv-axis')
paulo@89 7337 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
paulo@89 7338
paulo@89 7339 //============================================================
paulo@89 7340 // Event Handling/Dispatching (in chart's scope)
paulo@89 7341 //------------------------------------------------------------
paulo@89 7342
paulo@89 7343 legend.dispatch.on('stateChange', function(newState) {
paulo@89 7344 for (var key in newState)
paulo@89 7345 state[key] = newState[key];
paulo@89 7346 dispatch.stateChange(state);
paulo@89 7347 chart.update();
paulo@89 7348 });
paulo@89 7349
paulo@89 7350 interactiveLayer.dispatch.on('elementMousemove', function(e) {
paulo@89 7351 lines.clearHighlights();
paulo@89 7352 var singlePoint, pointIndex, pointXLocation, allData = [];
paulo@89 7353 data
paulo@89 7354 .filter(function(series, i) {
paulo@89 7355 series.seriesIndex = i;
paulo@89 7356 return !series.disabled;
paulo@89 7357 })
paulo@89 7358 .forEach(function(series,i) {
paulo@89 7359 var extent = brush.empty() ? x2.domain() : brush.extent();
paulo@89 7360 var currentValues = series.values.filter(function(d,i) {
paulo@89 7361 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
paulo@89 7362 });
paulo@89 7363
paulo@89 7364 pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
paulo@89 7365 var point = currentValues[pointIndex];
paulo@89 7366 var pointYValue = chart.y()(point, pointIndex);
paulo@89 7367 if (pointYValue != null) {
paulo@89 7368 lines.highlightPoint(i, pointIndex, true);
paulo@89 7369 }
paulo@89 7370 if (point === undefined) return;
paulo@89 7371 if (singlePoint === undefined) singlePoint = point;
paulo@89 7372 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
paulo@89 7373 allData.push({
paulo@89 7374 key: series.key,
paulo@89 7375 value: chart.y()(point, pointIndex),
paulo@89 7376 color: color(series,series.seriesIndex)
paulo@89 7377 });
paulo@89 7378 });
paulo@89 7379 //Highlight the tooltip entry based on which point the mouse is closest to.
paulo@89 7380 if (allData.length > 2) {
paulo@89 7381 var yValue = chart.yScale().invert(e.mouseY);
paulo@89 7382 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
paulo@89 7383 var threshold = 0.03 * domainExtent;
paulo@89 7384 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
paulo@89 7385 if (indexToHighlight !== null)
paulo@89 7386 allData[indexToHighlight].highlight = true;
paulo@89 7387 }
paulo@89 7388
paulo@89 7389 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
paulo@89 7390 interactiveLayer.tooltip
paulo@89 7391 .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top})
paulo@89 7392 .chartContainer(that.parentNode)
paulo@89 7393 .valueFormatter(function(d,i) {
paulo@89 7394 return d == null ? "N/A" : yAxis.tickFormat()(d);
paulo@89 7395 })
paulo@89 7396 .data({
paulo@89 7397 value: xValue,
paulo@89 7398 index: pointIndex,
paulo@89 7399 series: allData
paulo@89 7400 })();
paulo@89 7401
paulo@89 7402 interactiveLayer.renderGuideLine(pointXLocation);
paulo@89 7403
paulo@89 7404 });
paulo@89 7405
paulo@89 7406 interactiveLayer.dispatch.on("elementMouseout",function(e) {
paulo@89 7407 lines.clearHighlights();
paulo@89 7408 });
paulo@89 7409
paulo@89 7410 dispatch.on('changeState', function(e) {
paulo@89 7411 if (typeof e.disabled !== 'undefined') {
paulo@89 7412 data.forEach(function(series,i) {
paulo@89 7413 series.disabled = e.disabled[i];
paulo@89 7414 });
paulo@89 7415 }
paulo@89 7416 chart.update();
paulo@89 7417 });
paulo@89 7418
paulo@89 7419 //============================================================
paulo@89 7420 // Functions
paulo@89 7421 //------------------------------------------------------------
paulo@89 7422
paulo@89 7423 // Taken from crossfilter (http://square.github.com/crossfilter/)
paulo@89 7424 function resizePath(d) {
paulo@89 7425 var e = +(d == 'e'),
paulo@89 7426 x = e ? 1 : -1,
paulo@89 7427 y = availableHeight2 / 3;
paulo@89 7428 return 'M' + (.5 * x) + ',' + y
paulo@89 7429 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
paulo@89 7430 + 'V' + (2 * y - 6)
paulo@89 7431 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
paulo@89 7432 + 'Z'
paulo@89 7433 + 'M' + (2.5 * x) + ',' + (y + 8)
paulo@89 7434 + 'V' + (2 * y - 8)
paulo@89 7435 + 'M' + (4.5 * x) + ',' + (y + 8)
paulo@89 7436 + 'V' + (2 * y - 8);
paulo@89 7437 }
paulo@89 7438
paulo@89 7439
paulo@89 7440 function updateBrushBG() {
paulo@89 7441 if (!brush.empty()) brush.extent(brushExtent);
paulo@89 7442 brushBG
paulo@89 7443 .data([brush.empty() ? x2.domain() : brushExtent])
paulo@89 7444 .each(function(d,i) {
paulo@89 7445 var leftWidth = x2(d[0]) - x.range()[0],
paulo@89 7446 rightWidth = availableWidth - x2(d[1]);
paulo@89 7447 d3.select(this).select('.left')
paulo@89 7448 .attr('width', leftWidth < 0 ? 0 : leftWidth);
paulo@89 7449
paulo@89 7450 d3.select(this).select('.right')
paulo@89 7451 .attr('x', x2(d[1]))
paulo@89 7452 .attr('width', rightWidth < 0 ? 0 : rightWidth);
paulo@89 7453 });
paulo@89 7454 }
paulo@89 7455
paulo@89 7456
paulo@89 7457 function onBrush() {
paulo@89 7458 brushExtent = brush.empty() ? null : brush.extent();
paulo@89 7459 var extent = brush.empty() ? x2.domain() : brush.extent();
paulo@89 7460
paulo@89 7461 //The brush extent cannot be less than one. If it is, don't update the line chart.
paulo@89 7462 if (Math.abs(extent[0] - extent[1]) <= 1) {
paulo@89 7463 return;
paulo@89 7464 }
paulo@89 7465
paulo@89 7466 dispatch.brush({extent: extent, brush: brush});
paulo@89 7467
paulo@89 7468
paulo@89 7469 updateBrushBG();
paulo@89 7470
paulo@89 7471 // Update Main (Focus)
paulo@89 7472 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
paulo@89 7473 .datum(
paulo@89 7474 data
paulo@89 7475 .filter(function(d) { return !d.disabled })
paulo@89 7476 .map(function(d,i) {
paulo@89 7477 return {
paulo@89 7478 key: d.key,
paulo@89 7479 area: d.area,
paulo@89 7480 values: d.values.filter(function(d,i) {
paulo@89 7481 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
paulo@89 7482 })
paulo@89 7483 }
paulo@89 7484 })
paulo@89 7485 );
paulo@89 7486 focusLinesWrap.transition().duration(transitionDuration).call(lines);
paulo@89 7487
paulo@89 7488
paulo@89 7489 // Update Main (Focus) Axes
paulo@89 7490 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
paulo@89 7491 .call(xAxis);
paulo@89 7492 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
paulo@89 7493 .call(yAxis);
paulo@89 7494 }
paulo@89 7495 });
paulo@89 7496
paulo@89 7497 return chart;
paulo@89 7498 }
paulo@89 7499
paulo@89 7500 //============================================================
paulo@89 7501 // Event Handling/Dispatching (out of chart's scope)
paulo@89 7502 //------------------------------------------------------------
paulo@89 7503
paulo@89 7504 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 7505 tooltip.data(evt).position(evt.pos).hidden(false);
paulo@89 7506 });
paulo@89 7507
paulo@89 7508 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 7509 tooltip.hidden(true)
paulo@89 7510 });
paulo@89 7511
paulo@89 7512 //============================================================
paulo@89 7513 // Expose Public Variables
paulo@89 7514 //------------------------------------------------------------
paulo@89 7515
paulo@89 7516 // expose chart's sub-components
paulo@89 7517 chart.dispatch = dispatch;
paulo@89 7518 chart.legend = legend;
paulo@89 7519 chart.lines = lines;
paulo@89 7520 chart.lines2 = lines2;
paulo@89 7521 chart.xAxis = xAxis;
paulo@89 7522 chart.yAxis = yAxis;
paulo@89 7523 chart.x2Axis = x2Axis;
paulo@89 7524 chart.y2Axis = y2Axis;
paulo@89 7525 chart.interactiveLayer = interactiveLayer;
paulo@89 7526 chart.tooltip = tooltip;
paulo@89 7527
paulo@89 7528 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 7529
paulo@89 7530 chart._options = Object.create({}, {
paulo@89 7531 // simple options, just get/set the necessary values
paulo@89 7532 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 7533 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 7534 focusHeight: {get: function(){return height2;}, set: function(_){height2=_;}},
paulo@89 7535 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 7536 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
paulo@89 7537 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 7538 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 7539
paulo@89 7540 // deprecated options
paulo@89 7541 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 7542 // deprecated after 1.7.1
paulo@89 7543 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 7544 tooltip.enabled(!!_);
paulo@89 7545 }},
paulo@89 7546 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 7547 // deprecated after 1.7.1
paulo@89 7548 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 7549 tooltip.contentGenerator(_);
paulo@89 7550 }},
paulo@89 7551
paulo@89 7552 // options that require extra logic in the setter
paulo@89 7553 margin: {get: function(){return margin;}, set: function(_){
paulo@89 7554 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 7555 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 7556 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 7557 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 7558 }},
paulo@89 7559 color: {get: function(){return color;}, set: function(_){
paulo@89 7560 color = nv.utils.getColor(_);
paulo@89 7561 legend.color(color);
paulo@89 7562 // line color is handled above?
paulo@89 7563 }},
paulo@89 7564 interpolate: {get: function(){return lines.interpolate();}, set: function(_){
paulo@89 7565 lines.interpolate(_);
paulo@89 7566 lines2.interpolate(_);
paulo@89 7567 }},
paulo@89 7568 xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
paulo@89 7569 xAxis.tickFormat(_);
paulo@89 7570 x2Axis.tickFormat(_);
paulo@89 7571 }},
paulo@89 7572 yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
paulo@89 7573 yAxis.tickFormat(_);
paulo@89 7574 y2Axis.tickFormat(_);
paulo@89 7575 }},
paulo@89 7576 duration: {get: function(){return transitionDuration;}, set: function(_){
paulo@89 7577 transitionDuration=_;
paulo@89 7578 yAxis.duration(transitionDuration);
paulo@89 7579 y2Axis.duration(transitionDuration);
paulo@89 7580 xAxis.duration(transitionDuration);
paulo@89 7581 x2Axis.duration(transitionDuration);
paulo@89 7582 }},
paulo@89 7583 x: {get: function(){return lines.x();}, set: function(_){
paulo@89 7584 lines.x(_);
paulo@89 7585 lines2.x(_);
paulo@89 7586 }},
paulo@89 7587 y: {get: function(){return lines.y();}, set: function(_){
paulo@89 7588 lines.y(_);
paulo@89 7589 lines2.y(_);
paulo@89 7590 }},
paulo@89 7591 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
paulo@89 7592 useInteractiveGuideline = _;
paulo@89 7593 if (useInteractiveGuideline) {
paulo@89 7594 lines.interactive(false);
paulo@89 7595 lines.useVoronoi(false);
paulo@89 7596 }
paulo@89 7597 }}
paulo@89 7598 });
paulo@89 7599
paulo@89 7600 nv.utils.inheritOptions(chart, lines);
paulo@89 7601 nv.utils.initOptions(chart);
paulo@89 7602
paulo@89 7603 return chart;
paulo@89 7604 };
paulo@89 7605
paulo@89 7606 nv.models.multiBar = function() {
paulo@89 7607 "use strict";
paulo@89 7608
paulo@89 7609 //============================================================
paulo@89 7610 // Public Variables with Default Settings
paulo@89 7611 //------------------------------------------------------------
paulo@89 7612
paulo@89 7613 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 7614 , width = 960
paulo@89 7615 , height = 500
paulo@89 7616 , x = d3.scale.ordinal()
paulo@89 7617 , y = d3.scale.linear()
paulo@89 7618 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 7619 , container = null
paulo@89 7620 , getX = function(d) { return d.x }
paulo@89 7621 , getY = function(d) { return d.y }
paulo@89 7622 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
paulo@89 7623 , clipEdge = true
paulo@89 7624 , stacked = false
paulo@89 7625 , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
paulo@89 7626 , color = nv.utils.defaultColor()
paulo@89 7627 , hideable = false
paulo@89 7628 , barColor = null // adding the ability to set the color for each rather than the whole group
paulo@89 7629 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
paulo@89 7630 , duration = 500
paulo@89 7631 , xDomain
paulo@89 7632 , yDomain
paulo@89 7633 , xRange
paulo@89 7634 , yRange
paulo@89 7635 , groupSpacing = 0.1
paulo@89 7636 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
paulo@89 7637 ;
paulo@89 7638
paulo@89 7639 //============================================================
paulo@89 7640 // Private Variables
paulo@89 7641 //------------------------------------------------------------
paulo@89 7642
paulo@89 7643 var x0, y0 //used to store previous scales
paulo@89 7644 , renderWatch = nv.utils.renderWatch(dispatch, duration)
paulo@89 7645 ;
paulo@89 7646
paulo@89 7647 var last_datalength = 0;
paulo@89 7648
paulo@89 7649 function chart(selection) {
paulo@89 7650 renderWatch.reset();
paulo@89 7651 selection.each(function(data) {
paulo@89 7652 var availableWidth = width - margin.left - margin.right,
paulo@89 7653 availableHeight = height - margin.top - margin.bottom;
paulo@89 7654
paulo@89 7655 container = d3.select(this);
paulo@89 7656 nv.utils.initSVG(container);
paulo@89 7657 var nonStackableCount = 0;
paulo@89 7658 // This function defines the requirements for render complete
paulo@89 7659 var endFn = function(d, i) {
paulo@89 7660 if (d.series === data.length - 1 && i === data[0].values.length - 1)
paulo@89 7661 return true;
paulo@89 7662 return false;
paulo@89 7663 };
paulo@89 7664
paulo@89 7665 if(hideable && data.length) hideable = [{
paulo@89 7666 values: data[0].values.map(function(d) {
paulo@89 7667 return {
paulo@89 7668 x: d.x,
paulo@89 7669 y: 0,
paulo@89 7670 series: d.series,
paulo@89 7671 size: 0.01
paulo@89 7672 };}
paulo@89 7673 )}];
paulo@89 7674
paulo@89 7675 if (stacked) {
paulo@89 7676 var parsed = d3.layout.stack()
paulo@89 7677 .offset(stackOffset)
paulo@89 7678 .values(function(d){ return d.values })
paulo@89 7679 .y(getY)
paulo@89 7680 (!data.length && hideable ? hideable : data);
paulo@89 7681
paulo@89 7682 parsed.forEach(function(series, i){
paulo@89 7683 // if series is non-stackable, use un-parsed data
paulo@89 7684 if (series.nonStackable) {
paulo@89 7685 data[i].nonStackableSeries = nonStackableCount++;
paulo@89 7686 parsed[i] = data[i];
paulo@89 7687 } else {
paulo@89 7688 // don't stack this seires on top of the nonStackable seriees
paulo@89 7689 if (i > 0 && parsed[i - 1].nonStackable){
paulo@89 7690 parsed[i].values.map(function(d,j){
paulo@89 7691 d.y0 -= parsed[i - 1].values[j].y;
paulo@89 7692 d.y1 = d.y0 + d.y;
paulo@89 7693 });
paulo@89 7694 }
paulo@89 7695 }
paulo@89 7696 });
paulo@89 7697 data = parsed;
paulo@89 7698 }
paulo@89 7699 //add series index and key to each data point for reference
paulo@89 7700 data.forEach(function(series, i) {
paulo@89 7701 series.values.forEach(function(point) {
paulo@89 7702 point.series = i;
paulo@89 7703 point.key = series.key;
paulo@89 7704 });
paulo@89 7705 });
paulo@89 7706
paulo@89 7707 // HACK for negative value stacking
paulo@89 7708 if (stacked) {
paulo@89 7709 data[0].values.map(function(d,i) {
paulo@89 7710 var posBase = 0, negBase = 0;
paulo@89 7711 data.map(function(d, idx) {
paulo@89 7712 if (!data[idx].nonStackable) {
paulo@89 7713 var f = d.values[i]
paulo@89 7714 f.size = Math.abs(f.y);
paulo@89 7715 if (f.y<0) {
paulo@89 7716 f.y1 = negBase;
paulo@89 7717 negBase = negBase - f.size;
paulo@89 7718 } else
paulo@89 7719 {
paulo@89 7720 f.y1 = f.size + posBase;
paulo@89 7721 posBase = posBase + f.size;
paulo@89 7722 }
paulo@89 7723 }
paulo@89 7724
paulo@89 7725 });
paulo@89 7726 });
paulo@89 7727 }
paulo@89 7728 // Setup Scales
paulo@89 7729 // remap and flatten the data for use in calculating the scales' domains
paulo@89 7730 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
paulo@89 7731 data.map(function(d, idx) {
paulo@89 7732 return d.values.map(function(d,i) {
paulo@89 7733 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx }
paulo@89 7734 })
paulo@89 7735 });
paulo@89 7736
paulo@89 7737 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
paulo@89 7738 .rangeBands(xRange || [0, availableWidth], groupSpacing);
paulo@89 7739
paulo@89 7740 y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) {
paulo@89 7741 var domain = d.y;
paulo@89 7742 // increase the domain range if this series is stackable
paulo@89 7743 if (stacked && !data[d.idx].nonStackable) {
paulo@89 7744 if (d.y > 0){
paulo@89 7745 domain = d.y1
paulo@89 7746 } else {
paulo@89 7747 domain = d.y1 + d.y
paulo@89 7748 }
paulo@89 7749 }
paulo@89 7750 return domain;
paulo@89 7751 }).concat(forceY)))
paulo@89 7752 .range(yRange || [availableHeight, 0]);
paulo@89 7753
paulo@89 7754 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
paulo@89 7755 if (x.domain()[0] === x.domain()[1])
paulo@89 7756 x.domain()[0] ?
paulo@89 7757 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
paulo@89 7758 : x.domain([-1,1]);
paulo@89 7759
paulo@89 7760 if (y.domain()[0] === y.domain()[1])
paulo@89 7761 y.domain()[0] ?
paulo@89 7762 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
paulo@89 7763 : y.domain([-1,1]);
paulo@89 7764
paulo@89 7765 x0 = x0 || x;
paulo@89 7766 y0 = y0 || y;
paulo@89 7767
paulo@89 7768 // Setup containers and skeleton of chart
paulo@89 7769 var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
paulo@89 7770 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
paulo@89 7771 var defsEnter = wrapEnter.append('defs');
paulo@89 7772 var gEnter = wrapEnter.append('g');
paulo@89 7773 var g = wrap.select('g');
paulo@89 7774
paulo@89 7775 gEnter.append('g').attr('class', 'nv-groups');
paulo@89 7776 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 7777
paulo@89 7778 defsEnter.append('clipPath')
paulo@89 7779 .attr('id', 'nv-edge-clip-' + id)
paulo@89 7780 .append('rect');
paulo@89 7781 wrap.select('#nv-edge-clip-' + id + ' rect')
paulo@89 7782 .attr('width', availableWidth)
paulo@89 7783 .attr('height', availableHeight);
paulo@89 7784
paulo@89 7785 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
paulo@89 7786
paulo@89 7787 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
paulo@89 7788 .data(function(d) { return d }, function(d,i) { return i });
paulo@89 7789 groups.enter().append('g')
paulo@89 7790 .style('stroke-opacity', 1e-6)
paulo@89 7791 .style('fill-opacity', 1e-6);
paulo@89 7792
paulo@89 7793 var exitTransition = renderWatch
paulo@89 7794 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
paulo@89 7795 .attr('y', function(d, i, j) {
paulo@89 7796 var yVal = y0(0) || 0;
paulo@89 7797 if (stacked) {
paulo@89 7798 if (data[d.series] && !data[d.series].nonStackable) {
paulo@89 7799 yVal = y0(d.y0);
paulo@89 7800 }
paulo@89 7801 }
paulo@89 7802 return yVal;
paulo@89 7803 })
paulo@89 7804 .attr('height', 0)
paulo@89 7805 .remove();
paulo@89 7806 if (exitTransition.delay)
paulo@89 7807 exitTransition.delay(function(d,i) {
paulo@89 7808 var delay = i * (duration / (last_datalength + 1)) - i;
paulo@89 7809 return delay;
paulo@89 7810 });
paulo@89 7811 groups
paulo@89 7812 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
paulo@89 7813 .classed('hover', function(d) { return d.hover })
paulo@89 7814 .style('fill', function(d,i){ return color(d, i) })
paulo@89 7815 .style('stroke', function(d,i){ return color(d, i) });
paulo@89 7816 groups
paulo@89 7817 .style('stroke-opacity', 1)
paulo@89 7818 .style('fill-opacity', 0.75);
paulo@89 7819
paulo@89 7820 var bars = groups.selectAll('rect.nv-bar')
paulo@89 7821 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
paulo@89 7822 bars.exit().remove();
paulo@89 7823
paulo@89 7824 var barsEnter = bars.enter().append('rect')
paulo@89 7825 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
paulo@89 7826 .attr('x', function(d,i,j) {
paulo@89 7827 return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length )
paulo@89 7828 })
paulo@89 7829 .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
paulo@89 7830 .attr('height', 0)
paulo@89 7831 .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) })
paulo@89 7832 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
paulo@89 7833 ;
paulo@89 7834 bars
paulo@89 7835 .style('fill', function(d,i,j){ return color(d, j, i); })
paulo@89 7836 .style('stroke', function(d,i,j){ return color(d, j, i); })
paulo@89 7837 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
paulo@89 7838 d3.select(this).classed('hover', true);
paulo@89 7839 dispatch.elementMouseover({
paulo@89 7840 data: d,
paulo@89 7841 index: i,
paulo@89 7842 color: d3.select(this).style("fill")
paulo@89 7843 });
paulo@89 7844 })
paulo@89 7845 .on('mouseout', function(d,i) {
paulo@89 7846 d3.select(this).classed('hover', false);
paulo@89 7847 dispatch.elementMouseout({
paulo@89 7848 data: d,
paulo@89 7849 index: i,
paulo@89 7850 color: d3.select(this).style("fill")
paulo@89 7851 });
paulo@89 7852 })
paulo@89 7853 .on('mousemove', function(d,i) {
paulo@89 7854 dispatch.elementMousemove({
paulo@89 7855 data: d,
paulo@89 7856 index: i,
paulo@89 7857 color: d3.select(this).style("fill")
paulo@89 7858 });
paulo@89 7859 })
paulo@89 7860 .on('click', function(d,i) {
paulo@89 7861 dispatch.elementClick({
paulo@89 7862 data: d,
paulo@89 7863 index: i,
paulo@89 7864 color: d3.select(this).style("fill")
paulo@89 7865 });
paulo@89 7866 d3.event.stopPropagation();
paulo@89 7867 })
paulo@89 7868 .on('dblclick', function(d,i) {
paulo@89 7869 dispatch.elementDblClick({
paulo@89 7870 data: d,
paulo@89 7871 index: i,
paulo@89 7872 color: d3.select(this).style("fill")
paulo@89 7873 });
paulo@89 7874 d3.event.stopPropagation();
paulo@89 7875 });
paulo@89 7876 bars
paulo@89 7877 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
paulo@89 7878 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
paulo@89 7879
paulo@89 7880 if (barColor) {
paulo@89 7881 if (!disabled) disabled = data.map(function() { return true });
paulo@89 7882 bars
paulo@89 7883 .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
paulo@89 7884 .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
paulo@89 7885 }
paulo@89 7886
paulo@89 7887 var barSelection =
paulo@89 7888 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
paulo@89 7889 .delay(function(d,i) {
paulo@89 7890 return i * duration / data[0].values.length;
paulo@89 7891 });
paulo@89 7892 if (stacked){
paulo@89 7893 barSelection
paulo@89 7894 .attr('y', function(d,i,j) {
paulo@89 7895 var yVal = 0;
paulo@89 7896 // if stackable, stack it on top of the previous series
paulo@89 7897 if (!data[j].nonStackable) {
paulo@89 7898 yVal = y(d.y1);
paulo@89 7899 } else {
paulo@89 7900 if (getY(d,i) < 0){
paulo@89 7901 yVal = y(0);
paulo@89 7902 } else {
paulo@89 7903 if (y(0) - y(getY(d,i)) < -1){
paulo@89 7904 yVal = y(0) - 1;
paulo@89 7905 } else {
paulo@89 7906 yVal = y(getY(d, i)) || 0;
paulo@89 7907 }
paulo@89 7908 }
paulo@89 7909 }
paulo@89 7910 return yVal;
paulo@89 7911 })
paulo@89 7912 .attr('height', function(d,i,j) {
paulo@89 7913 if (!data[j].nonStackable) {
paulo@89 7914 return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 1);
paulo@89 7915 } else {
paulo@89 7916 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
paulo@89 7917 }
paulo@89 7918 })
paulo@89 7919 .attr('x', function(d,i,j) {
paulo@89 7920 var width = 0;
paulo@89 7921 if (data[j].nonStackable) {
paulo@89 7922 width = d.series * x.rangeBand() / data.length;
paulo@89 7923 if (data.length !== nonStackableCount){
paulo@89 7924 width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2);
paulo@89 7925 }
paulo@89 7926 }
paulo@89 7927 return width;
paulo@89 7928 })
paulo@89 7929 .attr('width', function(d,i,j){
paulo@89 7930 if (!data[j].nonStackable) {
paulo@89 7931 return x.rangeBand();
paulo@89 7932 } else {
paulo@89 7933 // if all series are nonStacable, take the full width
paulo@89 7934 var width = (x.rangeBand() / nonStackableCount);
paulo@89 7935 // otherwise, nonStackable graph will be only taking the half-width
paulo@89 7936 // of the x rangeBand
paulo@89 7937 if (data.length !== nonStackableCount) {
paulo@89 7938 width = x.rangeBand()/(nonStackableCount*2);
paulo@89 7939 }
paulo@89 7940 return width;
paulo@89 7941 }
paulo@89 7942 });
paulo@89 7943 }
paulo@89 7944 else {
paulo@89 7945 barSelection
paulo@89 7946 .attr('x', function(d,i) {
paulo@89 7947 return d.series * x.rangeBand() / data.length;
paulo@89 7948 })
paulo@89 7949 .attr('width', x.rangeBand() / data.length)
paulo@89 7950 .attr('y', function(d,i) {
paulo@89 7951 return getY(d,i) < 0 ?
paulo@89 7952 y(0) :
paulo@89 7953 y(0) - y(getY(d,i)) < 1 ?
paulo@89 7954 y(0) - 1 :
paulo@89 7955 y(getY(d,i)) || 0;
paulo@89 7956 })
paulo@89 7957 .attr('height', function(d,i) {
paulo@89 7958 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
paulo@89 7959 });
paulo@89 7960 }
paulo@89 7961
paulo@89 7962 //store old scales for use in transitions on update
paulo@89 7963 x0 = x.copy();
paulo@89 7964 y0 = y.copy();
paulo@89 7965
paulo@89 7966 // keep track of the last data value length for transition calculations
paulo@89 7967 if (data[0] && data[0].values) {
paulo@89 7968 last_datalength = data[0].values.length;
paulo@89 7969 }
paulo@89 7970
paulo@89 7971 });
paulo@89 7972
paulo@89 7973 renderWatch.renderEnd('multibar immediate');
paulo@89 7974
paulo@89 7975 return chart;
paulo@89 7976 }
paulo@89 7977
paulo@89 7978 //============================================================
paulo@89 7979 // Expose Public Variables
paulo@89 7980 //------------------------------------------------------------
paulo@89 7981
paulo@89 7982 chart.dispatch = dispatch;
paulo@89 7983
paulo@89 7984 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 7985
paulo@89 7986 chart._options = Object.create({}, {
paulo@89 7987 // simple options, just get/set the necessary values
paulo@89 7988 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 7989 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 7990 x: {get: function(){return getX;}, set: function(_){getX=_;}},
paulo@89 7991 y: {get: function(){return getY;}, set: function(_){getY=_;}},
paulo@89 7992 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 7993 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 7994 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 7995 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 7996 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 7997 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 7998 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
paulo@89 7999 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
paulo@89 8000 stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
paulo@89 8001 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
paulo@89 8002 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
paulo@89 8003 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 8004 hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}},
paulo@89 8005 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
paulo@89 8006
paulo@89 8007 // options that require extra logic in the setter
paulo@89 8008 margin: {get: function(){return margin;}, set: function(_){
paulo@89 8009 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 8010 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 8011 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 8012 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 8013 }},
paulo@89 8014 duration: {get: function(){return duration;}, set: function(_){
paulo@89 8015 duration = _;
paulo@89 8016 renderWatch.reset(duration);
paulo@89 8017 }},
paulo@89 8018 color: {get: function(){return color;}, set: function(_){
paulo@89 8019 color = nv.utils.getColor(_);
paulo@89 8020 }},
paulo@89 8021 barColor: {get: function(){return barColor;}, set: function(_){
paulo@89 8022 barColor = _ ? nv.utils.getColor(_) : null;
paulo@89 8023 }}
paulo@89 8024 });
paulo@89 8025
paulo@89 8026 nv.utils.initOptions(chart);
paulo@89 8027
paulo@89 8028 return chart;
paulo@89 8029 };
paulo@89 8030 nv.models.multiBarChart = function() {
paulo@89 8031 "use strict";
paulo@89 8032
paulo@89 8033 //============================================================
paulo@89 8034 // Public Variables with Default Settings
paulo@89 8035 //------------------------------------------------------------
paulo@89 8036
paulo@89 8037 var multibar = nv.models.multiBar()
paulo@89 8038 , xAxis = nv.models.axis()
paulo@89 8039 , yAxis = nv.models.axis()
paulo@89 8040 , legend = nv.models.legend()
paulo@89 8041 , controls = nv.models.legend()
paulo@89 8042 , tooltip = nv.models.tooltip()
paulo@89 8043 ;
paulo@89 8044
paulo@89 8045 var margin = {top: 30, right: 20, bottom: 50, left: 60}
paulo@89 8046 , width = null
paulo@89 8047 , height = null
paulo@89 8048 , color = nv.utils.defaultColor()
paulo@89 8049 , showControls = true
paulo@89 8050 , controlLabels = {}
paulo@89 8051 , showLegend = true
paulo@89 8052 , showXAxis = true
paulo@89 8053 , showYAxis = true
paulo@89 8054 , rightAlignYAxis = false
paulo@89 8055 , reduceXTicks = true // if false a tick will show for every data point
paulo@89 8056 , staggerLabels = false
paulo@89 8057 , rotateLabels = 0
paulo@89 8058 , x //can be accessed via chart.xScale()
paulo@89 8059 , y //can be accessed via chart.yScale()
paulo@89 8060 , state = nv.utils.state()
paulo@89 8061 , defaultState = null
paulo@89 8062 , noData = null
paulo@89 8063 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
paulo@89 8064 , controlWidth = function() { return showControls ? 180 : 0 }
paulo@89 8065 , duration = 250
paulo@89 8066 ;
paulo@89 8067
paulo@89 8068 state.stacked = false // DEPRECATED Maintained for backward compatibility
paulo@89 8069
paulo@89 8070 multibar.stacked(false);
paulo@89 8071 xAxis
paulo@89 8072 .orient('bottom')
paulo@89 8073 .tickPadding(7)
paulo@89 8074 .showMaxMin(false)
paulo@89 8075 .tickFormat(function(d) { return d })
paulo@89 8076 ;
paulo@89 8077 yAxis
paulo@89 8078 .orient((rightAlignYAxis) ? 'right' : 'left')
paulo@89 8079 .tickFormat(d3.format(',.1f'))
paulo@89 8080 ;
paulo@89 8081
paulo@89 8082 tooltip
paulo@89 8083 .duration(0)
paulo@89 8084 .valueFormatter(function(d, i) {
paulo@89 8085 return yAxis.tickFormat()(d, i);
paulo@89 8086 })
paulo@89 8087 .headerFormatter(function(d, i) {
paulo@89 8088 return xAxis.tickFormat()(d, i);
paulo@89 8089 });
paulo@89 8090
paulo@89 8091 controls.updateState(false);
paulo@89 8092
paulo@89 8093 //============================================================
paulo@89 8094 // Private Variables
paulo@89 8095 //------------------------------------------------------------
paulo@89 8096
paulo@89 8097 var renderWatch = nv.utils.renderWatch(dispatch);
paulo@89 8098 var stacked = false;
paulo@89 8099
paulo@89 8100 var stateGetter = function(data) {
paulo@89 8101 return function(){
paulo@89 8102 return {
paulo@89 8103 active: data.map(function(d) { return !d.disabled }),
paulo@89 8104 stacked: stacked
paulo@89 8105 };
paulo@89 8106 }
paulo@89 8107 };
paulo@89 8108
paulo@89 8109 var stateSetter = function(data) {
paulo@89 8110 return function(state) {
paulo@89 8111 if (state.stacked !== undefined)
paulo@89 8112 stacked = state.stacked;
paulo@89 8113 if (state.active !== undefined)
paulo@89 8114 data.forEach(function(series,i) {
paulo@89 8115 series.disabled = !state.active[i];
paulo@89 8116 });
paulo@89 8117 }
paulo@89 8118 };
paulo@89 8119
paulo@89 8120 function chart(selection) {
paulo@89 8121 renderWatch.reset();
paulo@89 8122 renderWatch.models(multibar);
paulo@89 8123 if (showXAxis) renderWatch.models(xAxis);
paulo@89 8124 if (showYAxis) renderWatch.models(yAxis);
paulo@89 8125
paulo@89 8126 selection.each(function(data) {
paulo@89 8127 var container = d3.select(this),
paulo@89 8128 that = this;
paulo@89 8129 nv.utils.initSVG(container);
paulo@89 8130 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 8131 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 8132
paulo@89 8133 chart.update = function() {
paulo@89 8134 if (duration === 0)
paulo@89 8135 container.call(chart);
paulo@89 8136 else
paulo@89 8137 container.transition()
paulo@89 8138 .duration(duration)
paulo@89 8139 .call(chart);
paulo@89 8140 };
paulo@89 8141 chart.container = this;
paulo@89 8142
paulo@89 8143 state
paulo@89 8144 .setter(stateSetter(data), chart.update)
paulo@89 8145 .getter(stateGetter(data))
paulo@89 8146 .update();
paulo@89 8147
paulo@89 8148 // DEPRECATED set state.disableddisabled
paulo@89 8149 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 8150
paulo@89 8151 if (!defaultState) {
paulo@89 8152 var key;
paulo@89 8153 defaultState = {};
paulo@89 8154 for (key in state) {
paulo@89 8155 if (state[key] instanceof Array)
paulo@89 8156 defaultState[key] = state[key].slice(0);
paulo@89 8157 else
paulo@89 8158 defaultState[key] = state[key];
paulo@89 8159 }
paulo@89 8160 }
paulo@89 8161
paulo@89 8162 // Display noData message if there's nothing to show.
paulo@89 8163 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 8164 nv.utils.noData(chart, container)
paulo@89 8165 return chart;
paulo@89 8166 } else {
paulo@89 8167 container.selectAll('.nv-noData').remove();
paulo@89 8168 }
paulo@89 8169
paulo@89 8170 // Setup Scales
paulo@89 8171 x = multibar.xScale();
paulo@89 8172 y = multibar.yScale();
paulo@89 8173
paulo@89 8174 // Setup containers and skeleton of chart
paulo@89 8175 var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
paulo@89 8176 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
paulo@89 8177 var g = wrap.select('g');
paulo@89 8178
paulo@89 8179 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 8180 gEnter.append('g').attr('class', 'nv-y nv-axis');
paulo@89 8181 gEnter.append('g').attr('class', 'nv-barsWrap');
paulo@89 8182 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 8183 gEnter.append('g').attr('class', 'nv-controlsWrap');
paulo@89 8184
paulo@89 8185 // Legend
paulo@89 8186 if (showLegend) {
paulo@89 8187 legend.width(availableWidth - controlWidth());
paulo@89 8188
paulo@89 8189 g.select('.nv-legendWrap')
paulo@89 8190 .datum(data)
paulo@89 8191 .call(legend);
paulo@89 8192
paulo@89 8193 if ( margin.top != legend.height()) {
paulo@89 8194 margin.top = legend.height();
paulo@89 8195 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 8196 }
paulo@89 8197
paulo@89 8198 g.select('.nv-legendWrap')
paulo@89 8199 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
paulo@89 8200 }
paulo@89 8201
paulo@89 8202 // Controls
paulo@89 8203 if (showControls) {
paulo@89 8204 var controlsData = [
paulo@89 8205 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
paulo@89 8206 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
paulo@89 8207 ];
paulo@89 8208
paulo@89 8209 controls.width(controlWidth()).color(['#444', '#444', '#444']);
paulo@89 8210 g.select('.nv-controlsWrap')
paulo@89 8211 .datum(controlsData)
paulo@89 8212 .attr('transform', 'translate(0,' + (-margin.top) +')')
paulo@89 8213 .call(controls);
paulo@89 8214 }
paulo@89 8215
paulo@89 8216 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 8217 if (rightAlignYAxis) {
paulo@89 8218 g.select(".nv-y.nv-axis")
paulo@89 8219 .attr("transform", "translate(" + availableWidth + ",0)");
paulo@89 8220 }
paulo@89 8221
paulo@89 8222 // Main Chart Component(s)
paulo@89 8223 multibar
paulo@89 8224 .disabled(data.map(function(series) { return series.disabled }))
paulo@89 8225 .width(availableWidth)
paulo@89 8226 .height(availableHeight)
paulo@89 8227 .color(data.map(function(d,i) {
paulo@89 8228 return d.color || color(d, i);
paulo@89 8229 }).filter(function(d,i) { return !data[i].disabled }));
paulo@89 8230
paulo@89 8231
paulo@89 8232 var barsWrap = g.select('.nv-barsWrap')
paulo@89 8233 .datum(data.filter(function(d) { return !d.disabled }));
paulo@89 8234
paulo@89 8235 barsWrap.call(multibar);
paulo@89 8236
paulo@89 8237 // Setup Axes
paulo@89 8238 if (showXAxis) {
paulo@89 8239 xAxis
paulo@89 8240 .scale(x)
paulo@89 8241 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 8242 .tickSize(-availableHeight, 0);
paulo@89 8243
paulo@89 8244 g.select('.nv-x.nv-axis')
paulo@89 8245 .attr('transform', 'translate(0,' + y.range()[0] + ')');
paulo@89 8246 g.select('.nv-x.nv-axis')
paulo@89 8247 .call(xAxis);
paulo@89 8248
paulo@89 8249 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
paulo@89 8250
paulo@89 8251 xTicks
paulo@89 8252 .selectAll('line, text')
paulo@89 8253 .style('opacity', 1)
paulo@89 8254
paulo@89 8255 if (staggerLabels) {
paulo@89 8256 var getTranslate = function(x,y) {
paulo@89 8257 return "translate(" + x + "," + y + ")";
paulo@89 8258 };
paulo@89 8259
paulo@89 8260 var staggerUp = 5, staggerDown = 17; //pixels to stagger by
paulo@89 8261 // Issue #140
paulo@89 8262 xTicks
paulo@89 8263 .selectAll("text")
paulo@89 8264 .attr('transform', function(d,i,j) {
paulo@89 8265 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
paulo@89 8266 });
paulo@89 8267
paulo@89 8268 var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
paulo@89 8269 g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
paulo@89 8270 .attr("transform", function(d,i) {
paulo@89 8271 return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
paulo@89 8272 });
paulo@89 8273 }
paulo@89 8274
paulo@89 8275 if (reduceXTicks)
paulo@89 8276 xTicks
paulo@89 8277 .filter(function(d,i) {
paulo@89 8278 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
paulo@89 8279 })
paulo@89 8280 .selectAll('text, line')
paulo@89 8281 .style('opacity', 0);
paulo@89 8282
paulo@89 8283 if(rotateLabels)
paulo@89 8284 xTicks
paulo@89 8285 .selectAll('.tick text')
paulo@89 8286 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
paulo@89 8287 .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
paulo@89 8288
paulo@89 8289 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
paulo@89 8290 .style('opacity', 1);
paulo@89 8291 }
paulo@89 8292
paulo@89 8293 if (showYAxis) {
paulo@89 8294 yAxis
paulo@89 8295 .scale(y)
paulo@89 8296 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
paulo@89 8297 .tickSize( -availableWidth, 0);
paulo@89 8298
paulo@89 8299 g.select('.nv-y.nv-axis')
paulo@89 8300 .call(yAxis);
paulo@89 8301 }
paulo@89 8302
paulo@89 8303 //============================================================
paulo@89 8304 // Event Handling/Dispatching (in chart's scope)
paulo@89 8305 //------------------------------------------------------------
paulo@89 8306
paulo@89 8307 legend.dispatch.on('stateChange', function(newState) {
paulo@89 8308 for (var key in newState)
paulo@89 8309 state[key] = newState[key];
paulo@89 8310 dispatch.stateChange(state);
paulo@89 8311 chart.update();
paulo@89 8312 });
paulo@89 8313
paulo@89 8314 controls.dispatch.on('legendClick', function(d,i) {
paulo@89 8315 if (!d.disabled) return;
paulo@89 8316 controlsData = controlsData.map(function(s) {
paulo@89 8317 s.disabled = true;
paulo@89 8318 return s;
paulo@89 8319 });
paulo@89 8320 d.disabled = false;
paulo@89 8321
paulo@89 8322 switch (d.key) {
paulo@89 8323 case 'Grouped':
paulo@89 8324 case controlLabels.grouped:
paulo@89 8325 multibar.stacked(false);
paulo@89 8326 break;
paulo@89 8327 case 'Stacked':
paulo@89 8328 case controlLabels.stacked:
paulo@89 8329 multibar.stacked(true);
paulo@89 8330 break;
paulo@89 8331 }
paulo@89 8332
paulo@89 8333 state.stacked = multibar.stacked();
paulo@89 8334 dispatch.stateChange(state);
paulo@89 8335 chart.update();
paulo@89 8336 });
paulo@89 8337
paulo@89 8338 // Update chart from a state object passed to event handler
paulo@89 8339 dispatch.on('changeState', function(e) {
paulo@89 8340 if (typeof e.disabled !== 'undefined') {
paulo@89 8341 data.forEach(function(series,i) {
paulo@89 8342 series.disabled = e.disabled[i];
paulo@89 8343 });
paulo@89 8344 state.disabled = e.disabled;
paulo@89 8345 }
paulo@89 8346 if (typeof e.stacked !== 'undefined') {
paulo@89 8347 multibar.stacked(e.stacked);
paulo@89 8348 state.stacked = e.stacked;
paulo@89 8349 stacked = e.stacked;
paulo@89 8350 }
paulo@89 8351 chart.update();
paulo@89 8352 });
paulo@89 8353 });
paulo@89 8354
paulo@89 8355 renderWatch.renderEnd('multibarchart immediate');
paulo@89 8356 return chart;
paulo@89 8357 }
paulo@89 8358
paulo@89 8359 //============================================================
paulo@89 8360 // Event Handling/Dispatching (out of chart's scope)
paulo@89 8361 //------------------------------------------------------------
paulo@89 8362
paulo@89 8363 multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 8364 evt.value = chart.x()(evt.data);
paulo@89 8365 evt['series'] = {
paulo@89 8366 key: evt.data.key,
paulo@89 8367 value: chart.y()(evt.data),
paulo@89 8368 color: evt.color
paulo@89 8369 };
paulo@89 8370 tooltip.data(evt).hidden(false);
paulo@89 8371 });
paulo@89 8372
paulo@89 8373 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 8374 tooltip.hidden(true);
paulo@89 8375 });
paulo@89 8376
paulo@89 8377 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 8378 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 8379 });
paulo@89 8380
paulo@89 8381 //============================================================
paulo@89 8382 // Expose Public Variables
paulo@89 8383 //------------------------------------------------------------
paulo@89 8384
paulo@89 8385 // expose chart's sub-components
paulo@89 8386 chart.dispatch = dispatch;
paulo@89 8387 chart.multibar = multibar;
paulo@89 8388 chart.legend = legend;
paulo@89 8389 chart.controls = controls;
paulo@89 8390 chart.xAxis = xAxis;
paulo@89 8391 chart.yAxis = yAxis;
paulo@89 8392 chart.state = state;
paulo@89 8393 chart.tooltip = tooltip;
paulo@89 8394
paulo@89 8395 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 8396
paulo@89 8397 chart._options = Object.create({}, {
paulo@89 8398 // simple options, just get/set the necessary values
paulo@89 8399 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 8400 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 8401 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 8402 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
paulo@89 8403 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
paulo@89 8404 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 8405 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 8406 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 8407 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 8408 reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
paulo@89 8409 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
paulo@89 8410 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
paulo@89 8411
paulo@89 8412 // deprecated options
paulo@89 8413 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 8414 // deprecated after 1.7.1
paulo@89 8415 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 8416 tooltip.enabled(!!_);
paulo@89 8417 }},
paulo@89 8418 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 8419 // deprecated after 1.7.1
paulo@89 8420 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 8421 tooltip.contentGenerator(_);
paulo@89 8422 }},
paulo@89 8423
paulo@89 8424 // options that require extra logic in the setter
paulo@89 8425 margin: {get: function(){return margin;}, set: function(_){
paulo@89 8426 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 8427 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 8428 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 8429 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 8430 }},
paulo@89 8431 duration: {get: function(){return duration;}, set: function(_){
paulo@89 8432 duration = _;
paulo@89 8433 multibar.duration(duration);
paulo@89 8434 xAxis.duration(duration);
paulo@89 8435 yAxis.duration(duration);
paulo@89 8436 renderWatch.reset(duration);
paulo@89 8437 }},
paulo@89 8438 color: {get: function(){return color;}, set: function(_){
paulo@89 8439 color = nv.utils.getColor(_);
paulo@89 8440 legend.color(color);
paulo@89 8441 }},
paulo@89 8442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
paulo@89 8443 rightAlignYAxis = _;
paulo@89 8444 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
paulo@89 8445 }},
paulo@89 8446 barColor: {get: function(){return multibar.barColor;}, set: function(_){
paulo@89 8447 multibar.barColor(_);
paulo@89 8448 legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
paulo@89 8449 }}
paulo@89 8450 });
paulo@89 8451
paulo@89 8452 nv.utils.inheritOptions(chart, multibar);
paulo@89 8453 nv.utils.initOptions(chart);
paulo@89 8454
paulo@89 8455 return chart;
paulo@89 8456 };
paulo@89 8457
paulo@89 8458 nv.models.multiBarHorizontal = function() {
paulo@89 8459 "use strict";
paulo@89 8460
paulo@89 8461 //============================================================
paulo@89 8462 // Public Variables with Default Settings
paulo@89 8463 //------------------------------------------------------------
paulo@89 8464
paulo@89 8465 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 8466 , width = 960
paulo@89 8467 , height = 500
paulo@89 8468 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 8469 , container = null
paulo@89 8470 , x = d3.scale.ordinal()
paulo@89 8471 , y = d3.scale.linear()
paulo@89 8472 , getX = function(d) { return d.x }
paulo@89 8473 , getY = function(d) { return d.y }
paulo@89 8474 , getYerr = function(d) { return d.yErr }
paulo@89 8475 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
paulo@89 8476 , color = nv.utils.defaultColor()
paulo@89 8477 , barColor = null // adding the ability to set the color for each rather than the whole group
paulo@89 8478 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
paulo@89 8479 , stacked = false
paulo@89 8480 , showValues = false
paulo@89 8481 , showBarLabels = false
paulo@89 8482 , valuePadding = 60
paulo@89 8483 , groupSpacing = 0.1
paulo@89 8484 , valueFormat = d3.format(',.2f')
paulo@89 8485 , delay = 1200
paulo@89 8486 , xDomain
paulo@89 8487 , yDomain
paulo@89 8488 , xRange
paulo@89 8489 , yRange
paulo@89 8490 , duration = 250
paulo@89 8491 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
paulo@89 8492 ;
paulo@89 8493
paulo@89 8494 //============================================================
paulo@89 8495 // Private Variables
paulo@89 8496 //------------------------------------------------------------
paulo@89 8497
paulo@89 8498 var x0, y0; //used to store previous scales
paulo@89 8499 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 8500
paulo@89 8501 function chart(selection) {
paulo@89 8502 renderWatch.reset();
paulo@89 8503 selection.each(function(data) {
paulo@89 8504 var availableWidth = width - margin.left - margin.right,
paulo@89 8505 availableHeight = height - margin.top - margin.bottom;
paulo@89 8506
paulo@89 8507 container = d3.select(this);
paulo@89 8508 nv.utils.initSVG(container);
paulo@89 8509
paulo@89 8510 if (stacked)
paulo@89 8511 data = d3.layout.stack()
paulo@89 8512 .offset('zero')
paulo@89 8513 .values(function(d){ return d.values })
paulo@89 8514 .y(getY)
paulo@89 8515 (data);
paulo@89 8516
paulo@89 8517 //add series index and key to each data point for reference
paulo@89 8518 data.forEach(function(series, i) {
paulo@89 8519 series.values.forEach(function(point) {
paulo@89 8520 point.series = i;
paulo@89 8521 point.key = series.key;
paulo@89 8522 });
paulo@89 8523 });
paulo@89 8524
paulo@89 8525 // HACK for negative value stacking
paulo@89 8526 if (stacked)
paulo@89 8527 data[0].values.map(function(d,i) {
paulo@89 8528 var posBase = 0, negBase = 0;
paulo@89 8529 data.map(function(d) {
paulo@89 8530 var f = d.values[i]
paulo@89 8531 f.size = Math.abs(f.y);
paulo@89 8532 if (f.y<0) {
paulo@89 8533 f.y1 = negBase - f.size;
paulo@89 8534 negBase = negBase - f.size;
paulo@89 8535 } else
paulo@89 8536 {
paulo@89 8537 f.y1 = posBase;
paulo@89 8538 posBase = posBase + f.size;
paulo@89 8539 }
paulo@89 8540 });
paulo@89 8541 });
paulo@89 8542
paulo@89 8543 // Setup Scales
paulo@89 8544 // remap and flatten the data for use in calculating the scales' domains
paulo@89 8545 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
paulo@89 8546 data.map(function(d) {
paulo@89 8547 return d.values.map(function(d,i) {
paulo@89 8548 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
paulo@89 8549 })
paulo@89 8550 });
paulo@89 8551
paulo@89 8552 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
paulo@89 8553 .rangeBands(xRange || [0, availableHeight], groupSpacing);
paulo@89 8554
paulo@89 8555 y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 + d.y : d.y1 ) : d.y }).concat(forceY)))
paulo@89 8556
paulo@89 8557 if (showValues && !stacked)
paulo@89 8558 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
paulo@89 8559 else
paulo@89 8560 y.range(yRange || [0, availableWidth]);
paulo@89 8561
paulo@89 8562 x0 = x0 || x;
paulo@89 8563 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
paulo@89 8564
paulo@89 8565 // Setup containers and skeleton of chart
paulo@89 8566 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
paulo@89 8567 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
paulo@89 8568 var defsEnter = wrapEnter.append('defs');
paulo@89 8569 var gEnter = wrapEnter.append('g');
paulo@89 8570 var g = wrap.select('g');
paulo@89 8571
paulo@89 8572 gEnter.append('g').attr('class', 'nv-groups');
paulo@89 8573 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 8574
paulo@89 8575 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
paulo@89 8576 .data(function(d) { return d }, function(d,i) { return i });
paulo@89 8577 groups.enter().append('g')
paulo@89 8578 .style('stroke-opacity', 1e-6)
paulo@89 8579 .style('fill-opacity', 1e-6);
paulo@89 8580 groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
paulo@89 8581 .style('stroke-opacity', 1e-6)
paulo@89 8582 .style('fill-opacity', 1e-6)
paulo@89 8583 .remove();
paulo@89 8584 groups
paulo@89 8585 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
paulo@89 8586 .classed('hover', function(d) { return d.hover })
paulo@89 8587 .style('fill', function(d,i){ return color(d, i) })
paulo@89 8588 .style('stroke', function(d,i){ return color(d, i) });
paulo@89 8589 groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
paulo@89 8590 .style('stroke-opacity', 1)
paulo@89 8591 .style('fill-opacity', .75);
paulo@89 8592
paulo@89 8593 var bars = groups.selectAll('g.nv-bar')
paulo@89 8594 .data(function(d) { return d.values });
paulo@89 8595 bars.exit().remove();
paulo@89 8596
paulo@89 8597 var barsEnter = bars.enter().append('g')
paulo@89 8598 .attr('transform', function(d,i,j) {
paulo@89 8599 return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
paulo@89 8600 });
paulo@89 8601
paulo@89 8602 barsEnter.append('rect')
paulo@89 8603 .attr('width', 0)
paulo@89 8604 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
paulo@89 8605
paulo@89 8606 bars
paulo@89 8607 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
paulo@89 8608 d3.select(this).classed('hover', true);
paulo@89 8609 dispatch.elementMouseover({
paulo@89 8610 data: d,
paulo@89 8611 index: i,
paulo@89 8612 color: d3.select(this).style("fill")
paulo@89 8613 });
paulo@89 8614 })
paulo@89 8615 .on('mouseout', function(d,i) {
paulo@89 8616 d3.select(this).classed('hover', false);
paulo@89 8617 dispatch.elementMouseout({
paulo@89 8618 data: d,
paulo@89 8619 index: i,
paulo@89 8620 color: d3.select(this).style("fill")
paulo@89 8621 });
paulo@89 8622 })
paulo@89 8623 .on('mouseout', function(d,i) {
paulo@89 8624 dispatch.elementMouseout({
paulo@89 8625 data: d,
paulo@89 8626 index: i,
paulo@89 8627 color: d3.select(this).style("fill")
paulo@89 8628 });
paulo@89 8629 })
paulo@89 8630 .on('mousemove', function(d,i) {
paulo@89 8631 dispatch.elementMousemove({
paulo@89 8632 data: d,
paulo@89 8633 index: i,
paulo@89 8634 color: d3.select(this).style("fill")
paulo@89 8635 });
paulo@89 8636 })
paulo@89 8637 .on('click', function(d,i) {
paulo@89 8638 dispatch.elementClick({
paulo@89 8639 data: d,
paulo@89 8640 index: i,
paulo@89 8641 color: d3.select(this).style("fill")
paulo@89 8642 });
paulo@89 8643 d3.event.stopPropagation();
paulo@89 8644 })
paulo@89 8645 .on('dblclick', function(d,i) {
paulo@89 8646 dispatch.elementDblClick({
paulo@89 8647 data: d,
paulo@89 8648 index: i,
paulo@89 8649 color: d3.select(this).style("fill")
paulo@89 8650 });
paulo@89 8651 d3.event.stopPropagation();
paulo@89 8652 });
paulo@89 8653
paulo@89 8654 if (getYerr(data[0],0)) {
paulo@89 8655 barsEnter.append('polyline');
paulo@89 8656
paulo@89 8657 bars.select('polyline')
paulo@89 8658 .attr('fill', 'none')
paulo@89 8659 .attr('points', function(d,i) {
paulo@89 8660 var xerr = getYerr(d,i)
paulo@89 8661 , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
paulo@89 8662 xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
paulo@89 8663 xerr = xerr.map(function(e) { return y(e) - y(0); });
paulo@89 8664 var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
paulo@89 8665 return a.map(function (path) { return path.join(',') }).join(' ');
paulo@89 8666 })
paulo@89 8667 .attr('transform', function(d,i) {
paulo@89 8668 var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
paulo@89 8669 return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
paulo@89 8670 });
paulo@89 8671 }
paulo@89 8672
paulo@89 8673 barsEnter.append('text');
paulo@89 8674
paulo@89 8675 if (showValues && !stacked) {
paulo@89 8676 bars.select('text')
paulo@89 8677 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
paulo@89 8678 .attr('y', x.rangeBand() / (data.length * 2))
paulo@89 8679 .attr('dy', '.32em')
paulo@89 8680 .text(function(d,i) {
paulo@89 8681 var t = valueFormat(getY(d,i))
paulo@89 8682 , yerr = getYerr(d,i);
paulo@89 8683 if (yerr === undefined)
paulo@89 8684 return t;
paulo@89 8685 if (!yerr.length)
paulo@89 8686 return t + '±' + valueFormat(Math.abs(yerr));
paulo@89 8687 return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
paulo@89 8688 });
paulo@89 8689 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
paulo@89 8690 .select('text')
paulo@89 8691 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
paulo@89 8692 } else {
paulo@89 8693 bars.selectAll('text').text('');
paulo@89 8694 }
paulo@89 8695
paulo@89 8696 if (showBarLabels && !stacked) {
paulo@89 8697 barsEnter.append('text').classed('nv-bar-label',true);
paulo@89 8698 bars.select('text.nv-bar-label')
paulo@89 8699 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
paulo@89 8700 .attr('y', x.rangeBand() / (data.length * 2))
paulo@89 8701 .attr('dy', '.32em')
paulo@89 8702 .text(function(d,i) { return getX(d,i) });
paulo@89 8703 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
paulo@89 8704 .select('text.nv-bar-label')
paulo@89 8705 .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
paulo@89 8706 }
paulo@89 8707 else {
paulo@89 8708 bars.selectAll('text.nv-bar-label').text('');
paulo@89 8709 }
paulo@89 8710
paulo@89 8711 bars
paulo@89 8712 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
paulo@89 8713
paulo@89 8714 if (barColor) {
paulo@89 8715 if (!disabled) disabled = data.map(function() { return true });
paulo@89 8716 bars
paulo@89 8717 .style('fill', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); })
paulo@89 8718 .style('stroke', function(d,i,j) { return d3.rgb(barColor(d,i)).darker( disabled.map(function(d,i) { return i }).filter(function(d,i){ return !disabled[i] })[j] ).toString(); });
paulo@89 8719 }
paulo@89 8720
paulo@89 8721 if (stacked)
paulo@89 8722 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
paulo@89 8723 .attr('transform', function(d,i) {
paulo@89 8724 return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
paulo@89 8725 })
paulo@89 8726 .select('rect')
paulo@89 8727 .attr('width', function(d,i) {
paulo@89 8728 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
paulo@89 8729 })
paulo@89 8730 .attr('height', x.rangeBand() );
paulo@89 8731 else
paulo@89 8732 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
paulo@89 8733 .attr('transform', function(d,i) {
paulo@89 8734 //TODO: stacked must be all positive or all negative, not both?
paulo@89 8735 return 'translate(' +
paulo@89 8736 (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
paulo@89 8737 + ',' +
paulo@89 8738 (d.series * x.rangeBand() / data.length
paulo@89 8739 +
paulo@89 8740 x(getX(d,i)) )
paulo@89 8741 + ')'
paulo@89 8742 })
paulo@89 8743 .select('rect')
paulo@89 8744 .attr('height', x.rangeBand() / data.length )
paulo@89 8745 .attr('width', function(d,i) {
paulo@89 8746 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
paulo@89 8747 });
paulo@89 8748
paulo@89 8749 //store old scales for use in transitions on update
paulo@89 8750 x0 = x.copy();
paulo@89 8751 y0 = y.copy();
paulo@89 8752
paulo@89 8753 });
paulo@89 8754
paulo@89 8755 renderWatch.renderEnd('multibarHorizontal immediate');
paulo@89 8756 return chart;
paulo@89 8757 }
paulo@89 8758
paulo@89 8759 //============================================================
paulo@89 8760 // Expose Public Variables
paulo@89 8761 //------------------------------------------------------------
paulo@89 8762
paulo@89 8763 chart.dispatch = dispatch;
paulo@89 8764
paulo@89 8765 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 8766
paulo@89 8767 chart._options = Object.create({}, {
paulo@89 8768 // simple options, just get/set the necessary values
paulo@89 8769 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 8770 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 8771 x: {get: function(){return getX;}, set: function(_){getX=_;}},
paulo@89 8772 y: {get: function(){return getY;}, set: function(_){getY=_;}},
paulo@89 8773 yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
paulo@89 8774 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 8775 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 8776 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 8777 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 8778 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 8779 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 8780 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
paulo@89 8781 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
paulo@89 8782 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
paulo@89 8783 // this shows the group name, seems pointless?
paulo@89 8784 //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
paulo@89 8785 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
paulo@89 8786 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 8787 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
paulo@89 8788 valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
paulo@89 8789 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
paulo@89 8790
paulo@89 8791 // options that require extra logic in the setter
paulo@89 8792 margin: {get: function(){return margin;}, set: function(_){
paulo@89 8793 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 8794 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 8795 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 8796 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 8797 }},
paulo@89 8798 duration: {get: function(){return duration;}, set: function(_){
paulo@89 8799 duration = _;
paulo@89 8800 renderWatch.reset(duration);
paulo@89 8801 }},
paulo@89 8802 color: {get: function(){return color;}, set: function(_){
paulo@89 8803 color = nv.utils.getColor(_);
paulo@89 8804 }},
paulo@89 8805 barColor: {get: function(){return barColor;}, set: function(_){
paulo@89 8806 barColor = _ ? nv.utils.getColor(_) : null;
paulo@89 8807 }}
paulo@89 8808 });
paulo@89 8809
paulo@89 8810 nv.utils.initOptions(chart);
paulo@89 8811
paulo@89 8812 return chart;
paulo@89 8813 };
paulo@89 8814
paulo@89 8815 nv.models.multiBarHorizontalChart = function() {
paulo@89 8816 "use strict";
paulo@89 8817
paulo@89 8818 //============================================================
paulo@89 8819 // Public Variables with Default Settings
paulo@89 8820 //------------------------------------------------------------
paulo@89 8821
paulo@89 8822 var multibar = nv.models.multiBarHorizontal()
paulo@89 8823 , xAxis = nv.models.axis()
paulo@89 8824 , yAxis = nv.models.axis()
paulo@89 8825 , legend = nv.models.legend().height(30)
paulo@89 8826 , controls = nv.models.legend().height(30)
paulo@89 8827 , tooltip = nv.models.tooltip()
paulo@89 8828 ;
paulo@89 8829
paulo@89 8830 var margin = {top: 30, right: 20, bottom: 50, left: 60}
paulo@89 8831 , width = null
paulo@89 8832 , height = null
paulo@89 8833 , color = nv.utils.defaultColor()
paulo@89 8834 , showControls = true
paulo@89 8835 , controlLabels = {}
paulo@89 8836 , showLegend = true
paulo@89 8837 , showXAxis = true
paulo@89 8838 , showYAxis = true
paulo@89 8839 , stacked = false
paulo@89 8840 , x //can be accessed via chart.xScale()
paulo@89 8841 , y //can be accessed via chart.yScale()
paulo@89 8842 , state = nv.utils.state()
paulo@89 8843 , defaultState = null
paulo@89 8844 , noData = null
paulo@89 8845 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
paulo@89 8846 , controlWidth = function() { return showControls ? 180 : 0 }
paulo@89 8847 , duration = 250
paulo@89 8848 ;
paulo@89 8849
paulo@89 8850 state.stacked = false; // DEPRECATED Maintained for backward compatibility
paulo@89 8851
paulo@89 8852 multibar.stacked(stacked);
paulo@89 8853
paulo@89 8854 xAxis
paulo@89 8855 .orient('left')
paulo@89 8856 .tickPadding(5)
paulo@89 8857 .showMaxMin(false)
paulo@89 8858 .tickFormat(function(d) { return d })
paulo@89 8859 ;
paulo@89 8860 yAxis
paulo@89 8861 .orient('bottom')
paulo@89 8862 .tickFormat(d3.format(',.1f'))
paulo@89 8863 ;
paulo@89 8864
paulo@89 8865 tooltip
paulo@89 8866 .duration(0)
paulo@89 8867 .valueFormatter(function(d, i) {
paulo@89 8868 return yAxis.tickFormat()(d, i);
paulo@89 8869 })
paulo@89 8870 .headerFormatter(function(d, i) {
paulo@89 8871 return xAxis.tickFormat()(d, i);
paulo@89 8872 });
paulo@89 8873
paulo@89 8874 controls.updateState(false);
paulo@89 8875
paulo@89 8876 //============================================================
paulo@89 8877 // Private Variables
paulo@89 8878 //------------------------------------------------------------
paulo@89 8879
paulo@89 8880 var stateGetter = function(data) {
paulo@89 8881 return function(){
paulo@89 8882 return {
paulo@89 8883 active: data.map(function(d) { return !d.disabled }),
paulo@89 8884 stacked: stacked
paulo@89 8885 };
paulo@89 8886 }
paulo@89 8887 };
paulo@89 8888
paulo@89 8889 var stateSetter = function(data) {
paulo@89 8890 return function(state) {
paulo@89 8891 if (state.stacked !== undefined)
paulo@89 8892 stacked = state.stacked;
paulo@89 8893 if (state.active !== undefined)
paulo@89 8894 data.forEach(function(series,i) {
paulo@89 8895 series.disabled = !state.active[i];
paulo@89 8896 });
paulo@89 8897 }
paulo@89 8898 };
paulo@89 8899
paulo@89 8900 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 8901
paulo@89 8902 function chart(selection) {
paulo@89 8903 renderWatch.reset();
paulo@89 8904 renderWatch.models(multibar);
paulo@89 8905 if (showXAxis) renderWatch.models(xAxis);
paulo@89 8906 if (showYAxis) renderWatch.models(yAxis);
paulo@89 8907
paulo@89 8908 selection.each(function(data) {
paulo@89 8909 var container = d3.select(this),
paulo@89 8910 that = this;
paulo@89 8911 nv.utils.initSVG(container);
paulo@89 8912 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 8913 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 8914
paulo@89 8915 chart.update = function() { container.transition().duration(duration).call(chart) };
paulo@89 8916 chart.container = this;
paulo@89 8917
paulo@89 8918 stacked = multibar.stacked();
paulo@89 8919
paulo@89 8920 state
paulo@89 8921 .setter(stateSetter(data), chart.update)
paulo@89 8922 .getter(stateGetter(data))
paulo@89 8923 .update();
paulo@89 8924
paulo@89 8925 // DEPRECATED set state.disableddisabled
paulo@89 8926 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 8927
paulo@89 8928 if (!defaultState) {
paulo@89 8929 var key;
paulo@89 8930 defaultState = {};
paulo@89 8931 for (key in state) {
paulo@89 8932 if (state[key] instanceof Array)
paulo@89 8933 defaultState[key] = state[key].slice(0);
paulo@89 8934 else
paulo@89 8935 defaultState[key] = state[key];
paulo@89 8936 }
paulo@89 8937 }
paulo@89 8938
paulo@89 8939 // Display No Data message if there's nothing to show.
paulo@89 8940 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 8941 nv.utils.noData(chart, container)
paulo@89 8942 return chart;
paulo@89 8943 } else {
paulo@89 8944 container.selectAll('.nv-noData').remove();
paulo@89 8945 }
paulo@89 8946
paulo@89 8947 // Setup Scales
paulo@89 8948 x = multibar.xScale();
paulo@89 8949 y = multibar.yScale();
paulo@89 8950
paulo@89 8951 // Setup containers and skeleton of chart
paulo@89 8952 var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
paulo@89 8953 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
paulo@89 8954 var g = wrap.select('g');
paulo@89 8955
paulo@89 8956 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 8957 gEnter.append('g').attr('class', 'nv-y nv-axis')
paulo@89 8958 .append('g').attr('class', 'nv-zeroLine')
paulo@89 8959 .append('line');
paulo@89 8960 gEnter.append('g').attr('class', 'nv-barsWrap');
paulo@89 8961 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 8962 gEnter.append('g').attr('class', 'nv-controlsWrap');
paulo@89 8963
paulo@89 8964 // Legend
paulo@89 8965 if (showLegend) {
paulo@89 8966 legend.width(availableWidth - controlWidth());
paulo@89 8967
paulo@89 8968 g.select('.nv-legendWrap')
paulo@89 8969 .datum(data)
paulo@89 8970 .call(legend);
paulo@89 8971
paulo@89 8972 if ( margin.top != legend.height()) {
paulo@89 8973 margin.top = legend.height();
paulo@89 8974 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 8975 }
paulo@89 8976
paulo@89 8977 g.select('.nv-legendWrap')
paulo@89 8978 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
paulo@89 8979 }
paulo@89 8980
paulo@89 8981 // Controls
paulo@89 8982 if (showControls) {
paulo@89 8983 var controlsData = [
paulo@89 8984 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
paulo@89 8985 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
paulo@89 8986 ];
paulo@89 8987
paulo@89 8988 controls.width(controlWidth()).color(['#444', '#444', '#444']);
paulo@89 8989 g.select('.nv-controlsWrap')
paulo@89 8990 .datum(controlsData)
paulo@89 8991 .attr('transform', 'translate(0,' + (-margin.top) +')')
paulo@89 8992 .call(controls);
paulo@89 8993 }
paulo@89 8994
paulo@89 8995 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 8996
paulo@89 8997 // Main Chart Component(s)
paulo@89 8998 multibar
paulo@89 8999 .disabled(data.map(function(series) { return series.disabled }))
paulo@89 9000 .width(availableWidth)
paulo@89 9001 .height(availableHeight)
paulo@89 9002 .color(data.map(function(d,i) {
paulo@89 9003 return d.color || color(d, i);
paulo@89 9004 }).filter(function(d,i) { return !data[i].disabled }));
paulo@89 9005
paulo@89 9006 var barsWrap = g.select('.nv-barsWrap')
paulo@89 9007 .datum(data.filter(function(d) { return !d.disabled }));
paulo@89 9008
paulo@89 9009 barsWrap.transition().call(multibar);
paulo@89 9010
paulo@89 9011 // Setup Axes
paulo@89 9012 if (showXAxis) {
paulo@89 9013 xAxis
paulo@89 9014 .scale(x)
paulo@89 9015 ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
paulo@89 9016 .tickSize(-availableWidth, 0);
paulo@89 9017
paulo@89 9018 g.select('.nv-x.nv-axis').call(xAxis);
paulo@89 9019
paulo@89 9020 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
paulo@89 9021
paulo@89 9022 xTicks
paulo@89 9023 .selectAll('line, text');
paulo@89 9024 }
paulo@89 9025
paulo@89 9026 if (showYAxis) {
paulo@89 9027 yAxis
paulo@89 9028 .scale(y)
paulo@89 9029 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 9030 .tickSize( -availableHeight, 0);
paulo@89 9031
paulo@89 9032 g.select('.nv-y.nv-axis')
paulo@89 9033 .attr('transform', 'translate(0,' + availableHeight + ')');
paulo@89 9034 g.select('.nv-y.nv-axis').call(yAxis);
paulo@89 9035 }
paulo@89 9036
paulo@89 9037 // Zero line
paulo@89 9038 g.select(".nv-zeroLine line")
paulo@89 9039 .attr("x1", y(0))
paulo@89 9040 .attr("x2", y(0))
paulo@89 9041 .attr("y1", 0)
paulo@89 9042 .attr("y2", -availableHeight)
paulo@89 9043 ;
paulo@89 9044
paulo@89 9045 //============================================================
paulo@89 9046 // Event Handling/Dispatching (in chart's scope)
paulo@89 9047 //------------------------------------------------------------
paulo@89 9048
paulo@89 9049 legend.dispatch.on('stateChange', function(newState) {
paulo@89 9050 for (var key in newState)
paulo@89 9051 state[key] = newState[key];
paulo@89 9052 dispatch.stateChange(state);
paulo@89 9053 chart.update();
paulo@89 9054 });
paulo@89 9055
paulo@89 9056 controls.dispatch.on('legendClick', function(d,i) {
paulo@89 9057 if (!d.disabled) return;
paulo@89 9058 controlsData = controlsData.map(function(s) {
paulo@89 9059 s.disabled = true;
paulo@89 9060 return s;
paulo@89 9061 });
paulo@89 9062 d.disabled = false;
paulo@89 9063
paulo@89 9064 switch (d.key) {
paulo@89 9065 case 'Grouped':
paulo@89 9066 multibar.stacked(false);
paulo@89 9067 break;
paulo@89 9068 case 'Stacked':
paulo@89 9069 multibar.stacked(true);
paulo@89 9070 break;
paulo@89 9071 }
paulo@89 9072
paulo@89 9073 state.stacked = multibar.stacked();
paulo@89 9074 dispatch.stateChange(state);
paulo@89 9075 stacked = multibar.stacked();
paulo@89 9076
paulo@89 9077 chart.update();
paulo@89 9078 });
paulo@89 9079
paulo@89 9080 // Update chart from a state object passed to event handler
paulo@89 9081 dispatch.on('changeState', function(e) {
paulo@89 9082
paulo@89 9083 if (typeof e.disabled !== 'undefined') {
paulo@89 9084 data.forEach(function(series,i) {
paulo@89 9085 series.disabled = e.disabled[i];
paulo@89 9086 });
paulo@89 9087
paulo@89 9088 state.disabled = e.disabled;
paulo@89 9089 }
paulo@89 9090
paulo@89 9091 if (typeof e.stacked !== 'undefined') {
paulo@89 9092 multibar.stacked(e.stacked);
paulo@89 9093 state.stacked = e.stacked;
paulo@89 9094 stacked = e.stacked;
paulo@89 9095 }
paulo@89 9096
paulo@89 9097 chart.update();
paulo@89 9098 });
paulo@89 9099 });
paulo@89 9100 renderWatch.renderEnd('multibar horizontal chart immediate');
paulo@89 9101 return chart;
paulo@89 9102 }
paulo@89 9103
paulo@89 9104 //============================================================
paulo@89 9105 // Event Handling/Dispatching (out of chart's scope)
paulo@89 9106 //------------------------------------------------------------
paulo@89 9107
paulo@89 9108 multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 9109 evt.value = chart.x()(evt.data);
paulo@89 9110 evt['series'] = {
paulo@89 9111 key: evt.data.key,
paulo@89 9112 value: chart.y()(evt.data),
paulo@89 9113 color: evt.color
paulo@89 9114 };
paulo@89 9115 tooltip.data(evt).hidden(false);
paulo@89 9116 });
paulo@89 9117
paulo@89 9118 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 9119 tooltip.hidden(true);
paulo@89 9120 });
paulo@89 9121
paulo@89 9122 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 9123 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 9124 });
paulo@89 9125
paulo@89 9126 //============================================================
paulo@89 9127 // Expose Public Variables
paulo@89 9128 //------------------------------------------------------------
paulo@89 9129
paulo@89 9130 // expose chart's sub-components
paulo@89 9131 chart.dispatch = dispatch;
paulo@89 9132 chart.multibar = multibar;
paulo@89 9133 chart.legend = legend;
paulo@89 9134 chart.controls = controls;
paulo@89 9135 chart.xAxis = xAxis;
paulo@89 9136 chart.yAxis = yAxis;
paulo@89 9137 chart.state = state;
paulo@89 9138 chart.tooltip = tooltip;
paulo@89 9139
paulo@89 9140 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 9141
paulo@89 9142 chart._options = Object.create({}, {
paulo@89 9143 // simple options, just get/set the necessary values
paulo@89 9144 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 9145 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 9146 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 9147 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
paulo@89 9148 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
paulo@89 9149 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 9150 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 9151 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 9152 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 9153
paulo@89 9154 // deprecated options
paulo@89 9155 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 9156 // deprecated after 1.7.1
paulo@89 9157 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 9158 tooltip.enabled(!!_);
paulo@89 9159 }},
paulo@89 9160 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 9161 // deprecated after 1.7.1
paulo@89 9162 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 9163 tooltip.contentGenerator(_);
paulo@89 9164 }},
paulo@89 9165
paulo@89 9166 // options that require extra logic in the setter
paulo@89 9167 margin: {get: function(){return margin;}, set: function(_){
paulo@89 9168 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 9169 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 9170 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 9171 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 9172 }},
paulo@89 9173 duration: {get: function(){return duration;}, set: function(_){
paulo@89 9174 duration = _;
paulo@89 9175 renderWatch.reset(duration);
paulo@89 9176 multibar.duration(duration);
paulo@89 9177 xAxis.duration(duration);
paulo@89 9178 yAxis.duration(duration);
paulo@89 9179 }},
paulo@89 9180 color: {get: function(){return color;}, set: function(_){
paulo@89 9181 color = nv.utils.getColor(_);
paulo@89 9182 legend.color(color);
paulo@89 9183 }},
paulo@89 9184 barColor: {get: function(){return multibar.barColor;}, set: function(_){
paulo@89 9185 multibar.barColor(_);
paulo@89 9186 legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
paulo@89 9187 }}
paulo@89 9188 });
paulo@89 9189
paulo@89 9190 nv.utils.inheritOptions(chart, multibar);
paulo@89 9191 nv.utils.initOptions(chart);
paulo@89 9192
paulo@89 9193 return chart;
paulo@89 9194 };
paulo@89 9195 nv.models.multiChart = function() {
paulo@89 9196 "use strict";
paulo@89 9197
paulo@89 9198 //============================================================
paulo@89 9199 // Public Variables with Default Settings
paulo@89 9200 //------------------------------------------------------------
paulo@89 9201
paulo@89 9202 var margin = {top: 30, right: 20, bottom: 50, left: 60},
paulo@89 9203 color = nv.utils.defaultColor(),
paulo@89 9204 width = null,
paulo@89 9205 height = null,
paulo@89 9206 showLegend = true,
paulo@89 9207 noData = null,
paulo@89 9208 yDomain1,
paulo@89 9209 yDomain2,
paulo@89 9210 getX = function(d) { return d.x },
paulo@89 9211 getY = function(d) { return d.y},
paulo@89 9212 interpolate = 'monotone',
paulo@89 9213 useVoronoi = true
paulo@89 9214 ;
paulo@89 9215
paulo@89 9216 //============================================================
paulo@89 9217 // Private Variables
paulo@89 9218 //------------------------------------------------------------
paulo@89 9219
paulo@89 9220 var x = d3.scale.linear(),
paulo@89 9221 yScale1 = d3.scale.linear(),
paulo@89 9222 yScale2 = d3.scale.linear(),
paulo@89 9223
paulo@89 9224 lines1 = nv.models.line().yScale(yScale1),
paulo@89 9225 lines2 = nv.models.line().yScale(yScale2),
paulo@89 9226
paulo@89 9227 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
paulo@89 9228 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
paulo@89 9229
paulo@89 9230 stack1 = nv.models.stackedArea().yScale(yScale1),
paulo@89 9231 stack2 = nv.models.stackedArea().yScale(yScale2),
paulo@89 9232
paulo@89 9233 xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
paulo@89 9234 yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
paulo@89 9235 yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
paulo@89 9236
paulo@89 9237 legend = nv.models.legend().height(30),
paulo@89 9238 tooltip = nv.models.tooltip(),
paulo@89 9239 dispatch = d3.dispatch();
paulo@89 9240
paulo@89 9241 function chart(selection) {
paulo@89 9242 selection.each(function(data) {
paulo@89 9243 var container = d3.select(this),
paulo@89 9244 that = this;
paulo@89 9245 nv.utils.initSVG(container);
paulo@89 9246
paulo@89 9247 chart.update = function() { container.transition().call(chart); };
paulo@89 9248 chart.container = this;
paulo@89 9249
paulo@89 9250 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 9251 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 9252
paulo@89 9253 var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
paulo@89 9254 var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
paulo@89 9255 var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1});
paulo@89 9256 var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2});
paulo@89 9257 var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
paulo@89 9258 var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
paulo@89 9259
paulo@89 9260 // Display noData message if there's nothing to show.
paulo@89 9261 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 9262 nv.utils.noData(chart, container);
paulo@89 9263 return chart;
paulo@89 9264 } else {
paulo@89 9265 container.selectAll('.nv-noData').remove();
paulo@89 9266 }
paulo@89 9267
paulo@89 9268 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
paulo@89 9269 .map(function(d) {
paulo@89 9270 return d.values.map(function(d,i) {
paulo@89 9271 return { x: d.x, y: d.y }
paulo@89 9272 })
paulo@89 9273 });
paulo@89 9274
paulo@89 9275 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
paulo@89 9276 .map(function(d) {
paulo@89 9277 return d.values.map(function(d,i) {
paulo@89 9278 return { x: d.x, y: d.y }
paulo@89 9279 })
paulo@89 9280 });
paulo@89 9281
paulo@89 9282 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
paulo@89 9283 .range([0, availableWidth]);
paulo@89 9284
paulo@89 9285 var wrap = container.selectAll('g.wrap.multiChart').data([data]);
paulo@89 9286 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
paulo@89 9287
paulo@89 9288 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 9289 gEnter.append('g').attr('class', 'nv-y1 nv-axis');
paulo@89 9290 gEnter.append('g').attr('class', 'nv-y2 nv-axis');
paulo@89 9291 gEnter.append('g').attr('class', 'lines1Wrap');
paulo@89 9292 gEnter.append('g').attr('class', 'lines2Wrap');
paulo@89 9293 gEnter.append('g').attr('class', 'bars1Wrap');
paulo@89 9294 gEnter.append('g').attr('class', 'bars2Wrap');
paulo@89 9295 gEnter.append('g').attr('class', 'stack1Wrap');
paulo@89 9296 gEnter.append('g').attr('class', 'stack2Wrap');
paulo@89 9297 gEnter.append('g').attr('class', 'legendWrap');
paulo@89 9298
paulo@89 9299 var g = wrap.select('g');
paulo@89 9300
paulo@89 9301 var color_array = data.map(function(d,i) {
paulo@89 9302 return data[i].color || color(d, i);
paulo@89 9303 });
paulo@89 9304
paulo@89 9305 if (showLegend) {
paulo@89 9306 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
paulo@89 9307 var legendXPosition = legend.align() ? legendWidth : 0;
paulo@89 9308
paulo@89 9309 legend.width(legendWidth);
paulo@89 9310 legend.color(color_array);
paulo@89 9311
paulo@89 9312 g.select('.legendWrap')
paulo@89 9313 .datum(data.map(function(series) {
paulo@89 9314 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
paulo@89 9315 series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
paulo@89 9316 return series;
paulo@89 9317 }))
paulo@89 9318 .call(legend);
paulo@89 9319
paulo@89 9320 if ( margin.top != legend.height()) {
paulo@89 9321 margin.top = legend.height();
paulo@89 9322 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 9323 }
paulo@89 9324
paulo@89 9325 g.select('.legendWrap')
paulo@89 9326 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
paulo@89 9327 }
paulo@89 9328
paulo@89 9329 lines1
paulo@89 9330 .width(availableWidth)
paulo@89 9331 .height(availableHeight)
paulo@89 9332 .interpolate(interpolate)
paulo@89 9333 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
paulo@89 9334 lines2
paulo@89 9335 .width(availableWidth)
paulo@89 9336 .height(availableHeight)
paulo@89 9337 .interpolate(interpolate)
paulo@89 9338 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
paulo@89 9339 bars1
paulo@89 9340 .width(availableWidth)
paulo@89 9341 .height(availableHeight)
paulo@89 9342 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
paulo@89 9343 bars2
paulo@89 9344 .width(availableWidth)
paulo@89 9345 .height(availableHeight)
paulo@89 9346 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
paulo@89 9347 stack1
paulo@89 9348 .width(availableWidth)
paulo@89 9349 .height(availableHeight)
paulo@89 9350 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
paulo@89 9351 stack2
paulo@89 9352 .width(availableWidth)
paulo@89 9353 .height(availableHeight)
paulo@89 9354 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
paulo@89 9355
paulo@89 9356 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 9357
paulo@89 9358 var lines1Wrap = g.select('.lines1Wrap')
paulo@89 9359 .datum(dataLines1.filter(function(d){return !d.disabled}));
paulo@89 9360 var bars1Wrap = g.select('.bars1Wrap')
paulo@89 9361 .datum(dataBars1.filter(function(d){return !d.disabled}));
paulo@89 9362 var stack1Wrap = g.select('.stack1Wrap')
paulo@89 9363 .datum(dataStack1.filter(function(d){return !d.disabled}));
paulo@89 9364 var lines2Wrap = g.select('.lines2Wrap')
paulo@89 9365 .datum(dataLines2.filter(function(d){return !d.disabled}));
paulo@89 9366 var bars2Wrap = g.select('.bars2Wrap')
paulo@89 9367 .datum(dataBars2.filter(function(d){return !d.disabled}));
paulo@89 9368 var stack2Wrap = g.select('.stack2Wrap')
paulo@89 9369 .datum(dataStack2.filter(function(d){return !d.disabled}));
paulo@89 9370
paulo@89 9371 var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
paulo@89 9372 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
paulo@89 9373 }).concat([{x:0, y:0}]) : [];
paulo@89 9374 var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
paulo@89 9375 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
paulo@89 9376 }).concat([{x:0, y:0}]) : [];
paulo@89 9377
paulo@89 9378 yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
paulo@89 9379 .range([0, availableHeight]);
paulo@89 9380
paulo@89 9381 yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
paulo@89 9382 .range([0, availableHeight]);
paulo@89 9383
paulo@89 9384 lines1.yDomain(yScale1.domain());
paulo@89 9385 bars1.yDomain(yScale1.domain());
paulo@89 9386 stack1.yDomain(yScale1.domain());
paulo@89 9387
paulo@89 9388 lines2.yDomain(yScale2.domain());
paulo@89 9389 bars2.yDomain(yScale2.domain());
paulo@89 9390 stack2.yDomain(yScale2.domain());
paulo@89 9391
paulo@89 9392 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
paulo@89 9393 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
paulo@89 9394
paulo@89 9395 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
paulo@89 9396 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
paulo@89 9397
paulo@89 9398 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
paulo@89 9399 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
paulo@89 9400
paulo@89 9401 xAxis
paulo@89 9402 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 9403 .tickSize(-availableHeight, 0);
paulo@89 9404
paulo@89 9405 g.select('.nv-x.nv-axis')
paulo@89 9406 .attr('transform', 'translate(0,' + availableHeight + ')');
paulo@89 9407 d3.transition(g.select('.nv-x.nv-axis'))
paulo@89 9408 .call(xAxis);
paulo@89 9409
paulo@89 9410 yAxis1
paulo@89 9411 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
paulo@89 9412 .tickSize( -availableWidth, 0);
paulo@89 9413
paulo@89 9414
paulo@89 9415 d3.transition(g.select('.nv-y1.nv-axis'))
paulo@89 9416 .call(yAxis1);
paulo@89 9417
paulo@89 9418 yAxis2
paulo@89 9419 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
paulo@89 9420 .tickSize( -availableWidth, 0);
paulo@89 9421
paulo@89 9422 d3.transition(g.select('.nv-y2.nv-axis'))
paulo@89 9423 .call(yAxis2);
paulo@89 9424
paulo@89 9425 g.select('.nv-y1.nv-axis')
paulo@89 9426 .classed('nv-disabled', series1.length ? false : true)
paulo@89 9427 .attr('transform', 'translate(' + x.range()[0] + ',0)');
paulo@89 9428
paulo@89 9429 g.select('.nv-y2.nv-axis')
paulo@89 9430 .classed('nv-disabled', series2.length ? false : true)
paulo@89 9431 .attr('transform', 'translate(' + x.range()[1] + ',0)');
paulo@89 9432
paulo@89 9433 legend.dispatch.on('stateChange', function(newState) {
paulo@89 9434 chart.update();
paulo@89 9435 });
paulo@89 9436
paulo@89 9437 //============================================================
paulo@89 9438 // Event Handling/Dispatching
paulo@89 9439 //------------------------------------------------------------
paulo@89 9440
paulo@89 9441 function mouseover_line(evt) {
paulo@89 9442 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
paulo@89 9443 evt.value = evt.point.x;
paulo@89 9444 evt.series = {
paulo@89 9445 value: evt.point.y,
paulo@89 9446 color: evt.point.color
paulo@89 9447 };
paulo@89 9448 tooltip
paulo@89 9449 .duration(100)
paulo@89 9450 .valueFormatter(function(d, i) {
paulo@89 9451 return yaxis.tickFormat()(d, i);
paulo@89 9452 })
paulo@89 9453 .data(evt)
paulo@89 9454 .position(evt.pos)
paulo@89 9455 .hidden(false);
paulo@89 9456 }
paulo@89 9457
paulo@89 9458 function mouseover_stack(evt) {
paulo@89 9459 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
paulo@89 9460 evt.point['x'] = stack1.x()(evt.point);
paulo@89 9461 evt.point['y'] = stack1.y()(evt.point);
paulo@89 9462 tooltip
paulo@89 9463 .duration(100)
paulo@89 9464 .valueFormatter(function(d, i) {
paulo@89 9465 return yaxis.tickFormat()(d, i);
paulo@89 9466 })
paulo@89 9467 .data(evt)
paulo@89 9468 .position(evt.pos)
paulo@89 9469 .hidden(false);
paulo@89 9470 }
paulo@89 9471
paulo@89 9472 function mouseover_bar(evt) {
paulo@89 9473 var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
paulo@89 9474
paulo@89 9475 evt.value = bars1.x()(evt.data);
paulo@89 9476 evt['series'] = {
paulo@89 9477 value: bars1.y()(evt.data),
paulo@89 9478 color: evt.color
paulo@89 9479 };
paulo@89 9480 tooltip
paulo@89 9481 .duration(0)
paulo@89 9482 .valueFormatter(function(d, i) {
paulo@89 9483 return yaxis.tickFormat()(d, i);
paulo@89 9484 })
paulo@89 9485 .data(evt)
paulo@89 9486 .hidden(false);
paulo@89 9487 }
paulo@89 9488
paulo@89 9489 lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
paulo@89 9490 lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
paulo@89 9491 lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 9492 tooltip.hidden(true)
paulo@89 9493 });
paulo@89 9494 lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 9495 tooltip.hidden(true)
paulo@89 9496 });
paulo@89 9497
paulo@89 9498 stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack);
paulo@89 9499 stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack);
paulo@89 9500 stack1.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 9501 tooltip.hidden(true)
paulo@89 9502 });
paulo@89 9503 stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 9504 tooltip.hidden(true)
paulo@89 9505 });
paulo@89 9506
paulo@89 9507 bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
paulo@89 9508 bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);
paulo@89 9509
paulo@89 9510 bars1.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 9511 tooltip.hidden(true);
paulo@89 9512 });
paulo@89 9513 bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 9514 tooltip.hidden(true);
paulo@89 9515 });
paulo@89 9516 bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 9517 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 9518 });
paulo@89 9519 bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 9520 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 9521 });
paulo@89 9522
paulo@89 9523 });
paulo@89 9524
paulo@89 9525 return chart;
paulo@89 9526 }
paulo@89 9527
paulo@89 9528 //============================================================
paulo@89 9529 // Global getters and setters
paulo@89 9530 //------------------------------------------------------------
paulo@89 9531
paulo@89 9532 chart.dispatch = dispatch;
paulo@89 9533 chart.lines1 = lines1;
paulo@89 9534 chart.lines2 = lines2;
paulo@89 9535 chart.bars1 = bars1;
paulo@89 9536 chart.bars2 = bars2;
paulo@89 9537 chart.stack1 = stack1;
paulo@89 9538 chart.stack2 = stack2;
paulo@89 9539 chart.xAxis = xAxis;
paulo@89 9540 chart.yAxis1 = yAxis1;
paulo@89 9541 chart.yAxis2 = yAxis2;
paulo@89 9542 chart.tooltip = tooltip;
paulo@89 9543
paulo@89 9544 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 9545
paulo@89 9546 chart._options = Object.create({}, {
paulo@89 9547 // simple options, just get/set the necessary values
paulo@89 9548 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 9549 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 9550 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 9551 yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
paulo@89 9552 yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
paulo@89 9553 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 9554 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
paulo@89 9555
paulo@89 9556 // deprecated options
paulo@89 9557 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 9558 // deprecated after 1.7.1
paulo@89 9559 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 9560 tooltip.enabled(!!_);
paulo@89 9561 }},
paulo@89 9562 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 9563 // deprecated after 1.7.1
paulo@89 9564 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 9565 tooltip.contentGenerator(_);
paulo@89 9566 }},
paulo@89 9567
paulo@89 9568 // options that require extra logic in the setter
paulo@89 9569 margin: {get: function(){return margin;}, set: function(_){
paulo@89 9570 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 9571 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 9572 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 9573 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 9574 }},
paulo@89 9575 color: {get: function(){return color;}, set: function(_){
paulo@89 9576 color = nv.utils.getColor(_);
paulo@89 9577 }},
paulo@89 9578 x: {get: function(){return getX;}, set: function(_){
paulo@89 9579 getX = _;
paulo@89 9580 lines1.x(_);
paulo@89 9581 lines2.x(_);
paulo@89 9582 bars1.x(_);
paulo@89 9583 bars2.x(_);
paulo@89 9584 stack1.x(_);
paulo@89 9585 stack2.x(_);
paulo@89 9586 }},
paulo@89 9587 y: {get: function(){return getY;}, set: function(_){
paulo@89 9588 getY = _;
paulo@89 9589 lines1.y(_);
paulo@89 9590 lines2.y(_);
paulo@89 9591 stack1.y(_);
paulo@89 9592 stack2.y(_);
paulo@89 9593 bars1.y(_);
paulo@89 9594 bars2.y(_);
paulo@89 9595 }},
paulo@89 9596 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
paulo@89 9597 useVoronoi=_;
paulo@89 9598 lines1.useVoronoi(_);
paulo@89 9599 lines2.useVoronoi(_);
paulo@89 9600 stack1.useVoronoi(_);
paulo@89 9601 stack2.useVoronoi(_);
paulo@89 9602 }}
paulo@89 9603 });
paulo@89 9604
paulo@89 9605 nv.utils.initOptions(chart);
paulo@89 9606
paulo@89 9607 return chart;
paulo@89 9608 };
paulo@89 9609
paulo@89 9610
paulo@89 9611 nv.models.ohlcBar = function() {
paulo@89 9612 "use strict";
paulo@89 9613
paulo@89 9614 //============================================================
paulo@89 9615 // Public Variables with Default Settings
paulo@89 9616 //------------------------------------------------------------
paulo@89 9617
paulo@89 9618 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 9619 , width = null
paulo@89 9620 , height = null
paulo@89 9621 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 9622 , container = null
paulo@89 9623 , x = d3.scale.linear()
paulo@89 9624 , y = d3.scale.linear()
paulo@89 9625 , getX = function(d) { return d.x }
paulo@89 9626 , getY = function(d) { return d.y }
paulo@89 9627 , getOpen = function(d) { return d.open }
paulo@89 9628 , getClose = function(d) { return d.close }
paulo@89 9629 , getHigh = function(d) { return d.high }
paulo@89 9630 , getLow = function(d) { return d.low }
paulo@89 9631 , forceX = []
paulo@89 9632 , forceY = []
paulo@89 9633 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
paulo@89 9634 , clipEdge = true
paulo@89 9635 , color = nv.utils.defaultColor()
paulo@89 9636 , interactive = false
paulo@89 9637 , xDomain
paulo@89 9638 , yDomain
paulo@89 9639 , xRange
paulo@89 9640 , yRange
paulo@89 9641 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
paulo@89 9642 ;
paulo@89 9643
paulo@89 9644 //============================================================
paulo@89 9645 // Private Variables
paulo@89 9646 //------------------------------------------------------------
paulo@89 9647
paulo@89 9648 function chart(selection) {
paulo@89 9649 selection.each(function(data) {
paulo@89 9650 container = d3.select(this);
paulo@89 9651 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 9652 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 9653
paulo@89 9654 nv.utils.initSVG(container);
paulo@89 9655
paulo@89 9656 // ohlc bar width.
paulo@89 9657 var w = (availableWidth / data[0].values.length) * .9;
paulo@89 9658
paulo@89 9659 // Setup Scales
paulo@89 9660 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
paulo@89 9661
paulo@89 9662 if (padData)
paulo@89 9663 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
paulo@89 9664 else
paulo@89 9665 x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]);
paulo@89 9666
paulo@89 9667 y.domain(yDomain || [
paulo@89 9668 d3.min(data[0].values.map(getLow).concat(forceY)),
paulo@89 9669 d3.max(data[0].values.map(getHigh).concat(forceY))
paulo@89 9670 ]
paulo@89 9671 ).range(yRange || [availableHeight, 0]);
paulo@89 9672
paulo@89 9673 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
paulo@89 9674 if (x.domain()[0] === x.domain()[1])
paulo@89 9675 x.domain()[0] ?
paulo@89 9676 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
paulo@89 9677 : x.domain([-1,1]);
paulo@89 9678
paulo@89 9679 if (y.domain()[0] === y.domain()[1])
paulo@89 9680 y.domain()[0] ?
paulo@89 9681 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
paulo@89 9682 : y.domain([-1,1]);
paulo@89 9683
paulo@89 9684 // Setup containers and skeleton of chart
paulo@89 9685 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
paulo@89 9686 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
paulo@89 9687 var defsEnter = wrapEnter.append('defs');
paulo@89 9688 var gEnter = wrapEnter.append('g');
paulo@89 9689 var g = wrap.select('g');
paulo@89 9690
paulo@89 9691 gEnter.append('g').attr('class', 'nv-ticks');
paulo@89 9692
paulo@89 9693 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 9694
paulo@89 9695 container
paulo@89 9696 .on('click', function(d,i) {
paulo@89 9697 dispatch.chartClick({
paulo@89 9698 data: d,
paulo@89 9699 index: i,
paulo@89 9700 pos: d3.event,
paulo@89 9701 id: id
paulo@89 9702 });
paulo@89 9703 });
paulo@89 9704
paulo@89 9705 defsEnter.append('clipPath')
paulo@89 9706 .attr('id', 'nv-chart-clip-path-' + id)
paulo@89 9707 .append('rect');
paulo@89 9708
paulo@89 9709 wrap.select('#nv-chart-clip-path-' + id + ' rect')
paulo@89 9710 .attr('width', availableWidth)
paulo@89 9711 .attr('height', availableHeight);
paulo@89 9712
paulo@89 9713 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
paulo@89 9714
paulo@89 9715 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
paulo@89 9716 .data(function(d) { return d });
paulo@89 9717 ticks.exit().remove();
paulo@89 9718
paulo@89 9719 ticks.enter().append('path')
paulo@89 9720 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
paulo@89 9721 .attr('d', function(d,i) {
paulo@89 9722 return 'm0,0l0,'
paulo@89 9723 + (y(getOpen(d,i))
paulo@89 9724 - y(getHigh(d,i)))
paulo@89 9725 + 'l'
paulo@89 9726 + (-w/2)
paulo@89 9727 + ',0l'
paulo@89 9728 + (w/2)
paulo@89 9729 + ',0l0,'
paulo@89 9730 + (y(getLow(d,i)) - y(getOpen(d,i)))
paulo@89 9731 + 'l0,'
paulo@89 9732 + (y(getClose(d,i))
paulo@89 9733 - y(getLow(d,i)))
paulo@89 9734 + 'l'
paulo@89 9735 + (w/2)
paulo@89 9736 + ',0l'
paulo@89 9737 + (-w/2)
paulo@89 9738 + ',0z';
paulo@89 9739 })
paulo@89 9740 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
paulo@89 9741 .attr('fill', function(d,i) { return color[0]; })
paulo@89 9742 .attr('stroke', function(d,i) { return color[0]; })
paulo@89 9743 .attr('x', 0 )
paulo@89 9744 .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
paulo@89 9745 .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
paulo@89 9746
paulo@89 9747 // the bar colors are controlled by CSS currently
paulo@89 9748 ticks.attr('class', function(d,i,j) {
paulo@89 9749 return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
paulo@89 9750 });
paulo@89 9751
paulo@89 9752 d3.transition(ticks)
paulo@89 9753 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
paulo@89 9754 .attr('d', function(d,i) {
paulo@89 9755 var w = (availableWidth / data[0].values.length) * .9;
paulo@89 9756 return 'm0,0l0,'
paulo@89 9757 + (y(getOpen(d,i))
paulo@89 9758 - y(getHigh(d,i)))
paulo@89 9759 + 'l'
paulo@89 9760 + (-w/2)
paulo@89 9761 + ',0l'
paulo@89 9762 + (w/2)
paulo@89 9763 + ',0l0,'
paulo@89 9764 + (y(getLow(d,i))
paulo@89 9765 - y(getOpen(d,i)))
paulo@89 9766 + 'l0,'
paulo@89 9767 + (y(getClose(d,i))
paulo@89 9768 - y(getLow(d,i)))
paulo@89 9769 + 'l'
paulo@89 9770 + (w/2)
paulo@89 9771 + ',0l'
paulo@89 9772 + (-w/2)
paulo@89 9773 + ',0z';
paulo@89 9774 });
paulo@89 9775 });
paulo@89 9776
paulo@89 9777 return chart;
paulo@89 9778 }
paulo@89 9779
paulo@89 9780
paulo@89 9781 //Create methods to allow outside functions to highlight a specific bar.
paulo@89 9782 chart.highlightPoint = function(pointIndex, isHoverOver) {
paulo@89 9783 chart.clearHighlights();
paulo@89 9784 container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
paulo@89 9785 .classed("hover", isHoverOver)
paulo@89 9786 ;
paulo@89 9787 };
paulo@89 9788
paulo@89 9789 chart.clearHighlights = function() {
paulo@89 9790 container.select(".nv-ohlcBar .nv-tick.hover")
paulo@89 9791 .classed("hover", false)
paulo@89 9792 ;
paulo@89 9793 };
paulo@89 9794
paulo@89 9795 //============================================================
paulo@89 9796 // Expose Public Variables
paulo@89 9797 //------------------------------------------------------------
paulo@89 9798
paulo@89 9799 chart.dispatch = dispatch;
paulo@89 9800 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 9801
paulo@89 9802 chart._options = Object.create({}, {
paulo@89 9803 // simple options, just get/set the necessary values
paulo@89 9804 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 9805 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 9806 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 9807 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 9808 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 9809 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 9810 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 9811 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 9812 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
paulo@89 9813 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
paulo@89 9814 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
paulo@89 9815 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
paulo@89 9816 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 9817 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
paulo@89 9818
paulo@89 9819 x: {get: function(){return getX;}, set: function(_){getX=_;}},
paulo@89 9820 y: {get: function(){return getY;}, set: function(_){getY=_;}},
paulo@89 9821 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
paulo@89 9822 close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
paulo@89 9823 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
paulo@89 9824 low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
paulo@89 9825
paulo@89 9826 // options that require extra logic in the setter
paulo@89 9827 margin: {get: function(){return margin;}, set: function(_){
paulo@89 9828 margin.top = _.top != undefined ? _.top : margin.top;
paulo@89 9829 margin.right = _.right != undefined ? _.right : margin.right;
paulo@89 9830 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
paulo@89 9831 margin.left = _.left != undefined ? _.left : margin.left;
paulo@89 9832 }},
paulo@89 9833 color: {get: function(){return color;}, set: function(_){
paulo@89 9834 color = nv.utils.getColor(_);
paulo@89 9835 }}
paulo@89 9836 });
paulo@89 9837
paulo@89 9838 nv.utils.initOptions(chart);
paulo@89 9839 return chart;
paulo@89 9840 };
paulo@89 9841 // Code adapted from Jason Davies' "Parallel Coordinates"
paulo@89 9842 // http://bl.ocks.org/jasondavies/1341281
paulo@89 9843 nv.models.parallelCoordinates = function() {
paulo@89 9844 "use strict";
paulo@89 9845
paulo@89 9846 //============================================================
paulo@89 9847 // Public Variables with Default Settings
paulo@89 9848 //------------------------------------------------------------
paulo@89 9849
paulo@89 9850 var margin = {top: 30, right: 0, bottom: 10, left: 0}
paulo@89 9851 , width = null
paulo@89 9852 , height = null
paulo@89 9853 , x = d3.scale.ordinal()
paulo@89 9854 , y = {}
paulo@89 9855 , dimensionNames = []
paulo@89 9856 , dimensionFormats = []
paulo@89 9857 , color = nv.utils.defaultColor()
paulo@89 9858 , filters = []
paulo@89 9859 , active = []
paulo@89 9860 , dragging = []
paulo@89 9861 , lineTension = 1
paulo@89 9862 , dispatch = d3.dispatch('brush', 'elementMouseover', 'elementMouseout')
paulo@89 9863 ;
paulo@89 9864
paulo@89 9865 //============================================================
paulo@89 9866 // Private Variables
paulo@89 9867 //------------------------------------------------------------
paulo@89 9868
paulo@89 9869 function chart(selection) {
paulo@89 9870 selection.each(function(data) {
paulo@89 9871 var container = d3.select(this);
paulo@89 9872 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 9873 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 9874
paulo@89 9875 nv.utils.initSVG(container);
paulo@89 9876
paulo@89 9877 active = data; //set all active before first brush call
paulo@89 9878
paulo@89 9879 // Setup Scales
paulo@89 9880 x.rangePoints([0, availableWidth], 1).domain(dimensionNames);
paulo@89 9881
paulo@89 9882 //Set as true if all values on an axis are missing.
paulo@89 9883 var onlyNanValues = {};
paulo@89 9884 // Extract the list of dimensions and create a scale for each.
paulo@89 9885 dimensionNames.forEach(function(d) {
paulo@89 9886 var extent = d3.extent(data, function(p) { return +p[d]; });
paulo@89 9887 onlyNanValues[d] = false;
paulo@89 9888 //If there is no values to display on an axis, set the extent to 0
paulo@89 9889 if (extent[0] === undefined) {
paulo@89 9890 onlyNanValues[d] = true;
paulo@89 9891 extent[0] = 0;
paulo@89 9892 extent[1] = 0;
paulo@89 9893 }
paulo@89 9894 //Scale axis if there is only one value
paulo@89 9895 if (extent[0] === extent[1]) {
paulo@89 9896 extent[0] = extent[0] - 1;
paulo@89 9897 extent[1] = extent[1] + 1;
paulo@89 9898 }
paulo@89 9899 //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text.
paulo@89 9900 //The remaining 10% are used to display the missingValue line.
paulo@89 9901 y[d] = d3.scale.linear()
paulo@89 9902 .domain(extent)
paulo@89 9903 .range([(availableHeight - 12) * 0.9, 0]);
paulo@89 9904
paulo@89 9905 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
paulo@89 9906
paulo@89 9907 return d != 'name';
paulo@89 9908 });
paulo@89 9909
paulo@89 9910 // Setup containers and skeleton of chart
paulo@89 9911 var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
paulo@89 9912 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
paulo@89 9913 var gEnter = wrapEnter.append('g');
paulo@89 9914 var g = wrap.select('g');
paulo@89 9915
paulo@89 9916 gEnter.append('g').attr('class', 'nv-parallelCoordinates background');
paulo@89 9917 gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground');
paulo@89 9918 gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline');
paulo@89 9919
paulo@89 9920 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 9921
paulo@89 9922 var line = d3.svg.line().interpolate('cardinal').tension(lineTension),
paulo@89 9923 axis = d3.svg.axis().orient('left'),
paulo@89 9924 axisDrag = d3.behavior.drag()
paulo@89 9925 .on('dragstart', dragStart)
paulo@89 9926 .on('drag', dragMove)
paulo@89 9927 .on('dragend', dragEnd);
paulo@89 9928
paulo@89 9929 //Add missing value line at the bottom of the chart
paulo@89 9930 var missingValuesline, missingValueslineText;
paulo@89 9931 var step = x.range()[1] - x.range()[0];
paulo@89 9932 var axisWithMissingValues = [];
paulo@89 9933 var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12];
paulo@89 9934 missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]);
paulo@89 9935 missingValuesline.enter().append('line');
paulo@89 9936 missingValuesline.exit().remove();
paulo@89 9937 missingValuesline.attr("x1", function(d) { return d[0]; })
paulo@89 9938 .attr("y1", function(d) { return d[1]; })
paulo@89 9939 .attr("x2", function(d) { return d[2]; })
paulo@89 9940 .attr("y2", function(d) { return d[3]; });
paulo@89 9941
paulo@89 9942 //Add the text "undefined values" under the missing value line
paulo@89 9943 missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]);
paulo@89 9944 missingValueslineText.append('text').data(["undefined values"]);
paulo@89 9945 missingValueslineText.enter().append('text');
paulo@89 9946 missingValueslineText.exit().remove();
paulo@89 9947 missingValueslineText.attr("y", availableHeight)
paulo@89 9948 //To have the text right align with the missingValues line, substract 92 representing the text size.
paulo@89 9949 .attr("x", availableWidth - 92 - step / 2)
paulo@89 9950 .text(function(d) { return d; });
paulo@89 9951
paulo@89 9952 // Add grey background lines for context.
paulo@89 9953 var background = wrap.select('.background').selectAll('path').data(data);
paulo@89 9954 background.enter().append('path');
paulo@89 9955 background.exit().remove();
paulo@89 9956 background.attr('d', path);
paulo@89 9957
paulo@89 9958 // Add blue foreground lines for focus.
paulo@89 9959 var foreground = wrap.select('.foreground').selectAll('path').data(data);
paulo@89 9960 foreground.enter().append('path')
paulo@89 9961 foreground.exit().remove();
paulo@89 9962 foreground.attr('d', path).attr('stroke', color);
paulo@89 9963 foreground.on("mouseover", function (d, i) {
paulo@89 9964 d3.select(this).classed('hover', true);
paulo@89 9965 dispatch.elementMouseover({
paulo@89 9966 label: d.name,
paulo@89 9967 data: d.data,
paulo@89 9968 index: i,
paulo@89 9969 pos: [d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]]
paulo@89 9970 });
paulo@89 9971
paulo@89 9972 });
paulo@89 9973 foreground.on("mouseout", function (d, i) {
paulo@89 9974 d3.select(this).classed('hover', false);
paulo@89 9975 dispatch.elementMouseout({
paulo@89 9976 label: d.name,
paulo@89 9977 data: d.data,
paulo@89 9978 index: i
paulo@89 9979 });
paulo@89 9980 });
paulo@89 9981
paulo@89 9982 // Add a group element for each dimension.
paulo@89 9983 var dimensions = g.selectAll('.dimension').data(dimensionNames);
paulo@89 9984 var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension');
paulo@89 9985 dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates nv-axis');
paulo@89 9986 dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates-brush');
paulo@89 9987 dimensionsEnter.append('text').attr('class', 'nv-parallelCoordinates nv-label');
paulo@89 9988
paulo@89 9989 dimensions.attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; });
paulo@89 9990 dimensions.exit().remove();
paulo@89 9991
paulo@89 9992 // Add an axis and title.
paulo@89 9993 dimensions.select('.nv-label')
paulo@89 9994 .style("cursor", "move")
paulo@89 9995 .attr('dy', '-1em')
paulo@89 9996 .attr('text-anchor', 'middle')
paulo@89 9997 .text(String)
paulo@89 9998 .on("mouseover", function(d, i) {
paulo@89 9999 dispatch.elementMouseover({
paulo@89 10000 dim: d,
paulo@89 10001 pos: [d3.mouse(this.parentNode.parentNode)[0], d3.mouse(this.parentNode.parentNode)[1]]
paulo@89 10002 });
paulo@89 10003 })
paulo@89 10004 .on("mouseout", function(d, i) {
paulo@89 10005 dispatch.elementMouseout({
paulo@89 10006 dim: d
paulo@89 10007 });
paulo@89 10008 })
paulo@89 10009 .call(axisDrag);
paulo@89 10010
paulo@89 10011 dimensions.select('.nv-axis')
paulo@89 10012 .each(function (d, i) {
paulo@89 10013 d3.select(this).call(axis.scale(y[d]).tickFormat(d3.format(dimensionFormats[i])));
paulo@89 10014 });
paulo@89 10015
paulo@89 10016 dimensions.select('.nv-parallelCoordinates-brush')
paulo@89 10017 .each(function (d) {
paulo@89 10018 d3.select(this).call(y[d].brush);
paulo@89 10019 })
paulo@89 10020 .selectAll('rect')
paulo@89 10021 .attr('x', -8)
paulo@89 10022 .attr('width', 16);
paulo@89 10023
paulo@89 10024 // Returns the path for a given data point.
paulo@89 10025 function path(d) {
paulo@89 10026 return line(dimensionNames.map(function (p) {
paulo@89 10027 //If value if missing, put the value on the missing value line
paulo@89 10028 if(isNaN(d[p]) || isNaN(parseFloat(d[p]))) {
paulo@89 10029 var domain = y[p].domain();
paulo@89 10030 var range = y[p].range();
paulo@89 10031 var min = domain[0] - (domain[1] - domain[0]) / 9;
paulo@89 10032
paulo@89 10033 //If it's not already the case, allow brush to select undefined values
paulo@89 10034 if(axisWithMissingValues.indexOf(p) < 0) {
paulo@89 10035
paulo@89 10036 var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]);
paulo@89 10037 y[p].brush.y(newscale);
paulo@89 10038 axisWithMissingValues.push(p);
paulo@89 10039 }
paulo@89 10040
paulo@89 10041 return [x(p), y[p](min)];
paulo@89 10042 }
paulo@89 10043
paulo@89 10044 //If parallelCoordinate contain missing values show the missing values line otherwise, hide it.
paulo@89 10045 if(axisWithMissingValues.length > 0) {
paulo@89 10046 missingValuesline.style("display", "inline");
paulo@89 10047 missingValueslineText.style("display", "inline");
paulo@89 10048 } else {
paulo@89 10049 missingValuesline.style("display", "none");
paulo@89 10050 missingValueslineText.style("display", "none");
paulo@89 10051 }
paulo@89 10052
paulo@89 10053 return [x(p), y[p](d[p])];
paulo@89 10054 }));
paulo@89 10055 }
paulo@89 10056
paulo@89 10057 // Handles a brush event, toggling the display of foreground lines.
paulo@89 10058 function brush() {
paulo@89 10059 var actives = dimensionNames.filter(function(p) { return !y[p].brush.empty(); }),
paulo@89 10060 extents = actives.map(function(p) { return y[p].brush.extent(); });
paulo@89 10061
paulo@89 10062 filters = []; //erase current filters
paulo@89 10063 actives.forEach(function(d,i) {
paulo@89 10064 filters[i] = {
paulo@89 10065 dimension: d,
paulo@89 10066 extent: extents[i]
paulo@89 10067 }
paulo@89 10068 });
paulo@89 10069
paulo@89 10070 active = []; //erase current active list
paulo@89 10071 foreground.style('display', function(d) {
paulo@89 10072 var isActive = actives.every(function(p, i) {
paulo@89 10073 if(isNaN(d[p]) && extents[i][0] == y[p].brush.y().domain()[0]) return true;
paulo@89 10074 return extents[i][0] <= d[p] && d[p] <= extents[i][1];
paulo@89 10075 });
paulo@89 10076 if (isActive) active.push(d);
paulo@89 10077 return isActive ? null : 'none';
paulo@89 10078 });
paulo@89 10079
paulo@89 10080 dispatch.brush({
paulo@89 10081 filters: filters,
paulo@89 10082 active: active
paulo@89 10083 });
paulo@89 10084 }
paulo@89 10085
paulo@89 10086 function dragStart(d, i) {
paulo@89 10087 dragging[d] = this.parentNode.__origin__ = x(d);
paulo@89 10088 background.attr("visibility", "hidden");
paulo@89 10089
paulo@89 10090 }
paulo@89 10091
paulo@89 10092 function dragMove(d, i) {
paulo@89 10093 dragging[d] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
paulo@89 10094 foreground.attr("d", path);
paulo@89 10095 dimensionNames.sort(function (a, b) { return position(a) - position(b); });
paulo@89 10096 x.domain(dimensionNames);
paulo@89 10097 dimensions.attr("transform", function(d) { return "translate(" + position(d) + ")"; });
paulo@89 10098 }
paulo@89 10099
paulo@89 10100 function dragEnd(d, i) {
paulo@89 10101 delete this.parentNode.__origin__;
paulo@89 10102 delete dragging[d];
paulo@89 10103 d3.select(this.parentNode).attr("transform", "translate(" + x(d) + ")");
paulo@89 10104 foreground
paulo@89 10105 .attr("d", path);
paulo@89 10106 background
paulo@89 10107 .attr("d", path)
paulo@89 10108 .attr("visibility", null);
paulo@89 10109
paulo@89 10110 }
paulo@89 10111
paulo@89 10112 function position(d) {
paulo@89 10113 var v = dragging[d];
paulo@89 10114 return v == null ? x(d) : v;
paulo@89 10115 }
paulo@89 10116 });
paulo@89 10117
paulo@89 10118 return chart;
paulo@89 10119 }
paulo@89 10120
paulo@89 10121 //============================================================
paulo@89 10122 // Expose Public Variables
paulo@89 10123 //------------------------------------------------------------
paulo@89 10124
paulo@89 10125 chart.dispatch = dispatch;
paulo@89 10126 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 10127
paulo@89 10128 chart._options = Object.create({}, {
paulo@89 10129 // simple options, just get/set the necessary values
paulo@89 10130 width: {get: function(){return width;}, set: function(_){width= _;}},
paulo@89 10131 height: {get: function(){return height;}, set: function(_){height= _;}},
paulo@89 10132 dimensionNames: {get: function() { return dimensionNames;}, set: function(_){dimensionNames= _;}},
paulo@89 10133 dimensionFormats : {get: function(){return dimensionFormats;}, set: function (_){dimensionFormats=_;}},
paulo@89 10134 lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}},
paulo@89 10135
paulo@89 10136 // deprecated options
paulo@89 10137 dimensions: {get: function (){return dimensionNames;}, set: function(_){
paulo@89 10138 // deprecated after 1.8.1
paulo@89 10139 nv.deprecated('dimensions', 'use dimensionNames instead');
paulo@89 10140 dimensionNames = _;
paulo@89 10141 }},
paulo@89 10142
paulo@89 10143 // options that require extra logic in the setter
paulo@89 10144 margin: {get: function(){return margin;}, set: function(_){
paulo@89 10145 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 10146 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 10147 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 10148 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 10149 }},
paulo@89 10150 color: {get: function(){return color;}, set: function(_){
paulo@89 10151 color = nv.utils.getColor(_);
paulo@89 10152 }}
paulo@89 10153 });
paulo@89 10154
paulo@89 10155 nv.utils.initOptions(chart);
paulo@89 10156 return chart;
paulo@89 10157 };
paulo@89 10158 nv.models.pie = function() {
paulo@89 10159 "use strict";
paulo@89 10160
paulo@89 10161 //============================================================
paulo@89 10162 // Public Variables with Default Settings
paulo@89 10163 //------------------------------------------------------------
paulo@89 10164
paulo@89 10165 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 10166 , width = 500
paulo@89 10167 , height = 500
paulo@89 10168 , getX = function(d) { return d.x }
paulo@89 10169 , getY = function(d) { return d.y }
paulo@89 10170 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 10171 , container = null
paulo@89 10172 , color = nv.utils.defaultColor()
paulo@89 10173 , valueFormat = d3.format(',.2f')
paulo@89 10174 , showLabels = true
paulo@89 10175 , labelsOutside = false
paulo@89 10176 , labelType = "key"
paulo@89 10177 , labelThreshold = .02 //if slice percentage is under this, don't show label
paulo@89 10178 , donut = false
paulo@89 10179 , title = false
paulo@89 10180 , growOnHover = true
paulo@89 10181 , titleOffset = 0
paulo@89 10182 , labelSunbeamLayout = false
paulo@89 10183 , startAngle = false
paulo@89 10184 , padAngle = false
paulo@89 10185 , endAngle = false
paulo@89 10186 , cornerRadius = 0
paulo@89 10187 , donutRatio = 0.5
paulo@89 10188 , arcsRadius = []
paulo@89 10189 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
paulo@89 10190 ;
paulo@89 10191
paulo@89 10192 var arcs = [];
paulo@89 10193 var arcsOver = [];
paulo@89 10194
paulo@89 10195 //============================================================
paulo@89 10196 // chart function
paulo@89 10197 //------------------------------------------------------------
paulo@89 10198
paulo@89 10199 var renderWatch = nv.utils.renderWatch(dispatch);
paulo@89 10200
paulo@89 10201 function chart(selection) {
paulo@89 10202 renderWatch.reset();
paulo@89 10203 selection.each(function(data) {
paulo@89 10204 var availableWidth = width - margin.left - margin.right
paulo@89 10205 , availableHeight = height - margin.top - margin.bottom
paulo@89 10206 , radius = Math.min(availableWidth, availableHeight) / 2
paulo@89 10207 , arcsRadiusOuter = []
paulo@89 10208 , arcsRadiusInner = []
paulo@89 10209 ;
paulo@89 10210
paulo@89 10211 container = d3.select(this)
paulo@89 10212 if (arcsRadius.length === 0) {
paulo@89 10213 var outer = radius - radius / 5;
paulo@89 10214 var inner = donutRatio * radius;
paulo@89 10215 for (var i = 0; i < data[0].length; i++) {
paulo@89 10216 arcsRadiusOuter.push(outer);
paulo@89 10217 arcsRadiusInner.push(inner);
paulo@89 10218 }
paulo@89 10219 } else {
paulo@89 10220 arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
paulo@89 10221 arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
paulo@89 10222 donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
paulo@89 10223 }
paulo@89 10224 nv.utils.initSVG(container);
paulo@89 10225
paulo@89 10226 // Setup containers and skeleton of chart
paulo@89 10227 var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
paulo@89 10228 var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
paulo@89 10229 var gEnter = wrapEnter.append('g');
paulo@89 10230 var g = wrap.select('g');
paulo@89 10231 var g_pie = gEnter.append('g').attr('class', 'nv-pie');
paulo@89 10232 gEnter.append('g').attr('class', 'nv-pieLabels');
paulo@89 10233
paulo@89 10234 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 10235 g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
paulo@89 10236 g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
paulo@89 10237
paulo@89 10238 //
paulo@89 10239 container.on('click', function(d,i) {
paulo@89 10240 dispatch.chartClick({
paulo@89 10241 data: d,
paulo@89 10242 index: i,
paulo@89 10243 pos: d3.event,
paulo@89 10244 id: id
paulo@89 10245 });
paulo@89 10246 });
paulo@89 10247
paulo@89 10248 arcs = [];
paulo@89 10249 arcsOver = [];
paulo@89 10250 for (var i = 0; i < data[0].length; i++) {
paulo@89 10251
paulo@89 10252 var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
paulo@89 10253 var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);
paulo@89 10254
paulo@89 10255 if (startAngle !== false) {
paulo@89 10256 arc.startAngle(startAngle);
paulo@89 10257 arcOver.startAngle(startAngle);
paulo@89 10258 }
paulo@89 10259 if (endAngle !== false) {
paulo@89 10260 arc.endAngle(endAngle);
paulo@89 10261 arcOver.endAngle(endAngle);
paulo@89 10262 }
paulo@89 10263 if (donut) {
paulo@89 10264 arc.innerRadius(arcsRadiusInner[i]);
paulo@89 10265 arcOver.innerRadius(arcsRadiusInner[i]);
paulo@89 10266 }
paulo@89 10267
paulo@89 10268 if (arc.cornerRadius && cornerRadius) {
paulo@89 10269 arc.cornerRadius(cornerRadius);
paulo@89 10270 arcOver.cornerRadius(cornerRadius);
paulo@89 10271 }
paulo@89 10272
paulo@89 10273 arcs.push(arc);
paulo@89 10274 arcsOver.push(arcOver);
paulo@89 10275 }
paulo@89 10276
paulo@89 10277 // Setup the Pie chart and choose the data element
paulo@89 10278 var pie = d3.layout.pie()
paulo@89 10279 .sort(null)
paulo@89 10280 .value(function(d) { return d.disabled ? 0 : getY(d) });
paulo@89 10281
paulo@89 10282 // padAngle added in d3 3.5
paulo@89 10283 if (pie.padAngle && padAngle) {
paulo@89 10284 pie.padAngle(padAngle);
paulo@89 10285 }
paulo@89 10286
paulo@89 10287 // if title is specified and donut, put it in the middle
paulo@89 10288 if (donut && title) {
paulo@89 10289 g_pie.append("text").attr('class', 'nv-pie-title');
paulo@89 10290
paulo@89 10291 wrap.select('.nv-pie-title')
paulo@89 10292 .style("text-anchor", "middle")
paulo@89 10293 .text(function (d) {
paulo@89 10294 return title;
paulo@89 10295 })
paulo@89 10296 .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px")
paulo@89 10297 .attr("dy", "0.35em") // trick to vertically center text
paulo@89 10298 .attr('transform', function(d, i) {
paulo@89 10299 return 'translate(0, '+ titleOffset + ')';
paulo@89 10300 });
paulo@89 10301 }
paulo@89 10302
paulo@89 10303 var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
paulo@89 10304 var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
paulo@89 10305
paulo@89 10306 slices.exit().remove();
paulo@89 10307 pieLabels.exit().remove();
paulo@89 10308
paulo@89 10309 var ae = slices.enter().append('g');
paulo@89 10310 ae.attr('class', 'nv-slice');
paulo@89 10311 ae.on('mouseover', function(d, i) {
paulo@89 10312 d3.select(this).classed('hover', true);
paulo@89 10313 if (growOnHover) {
paulo@89 10314 d3.select(this).select("path").transition()
paulo@89 10315 .duration(70)
paulo@89 10316 .attr("d", arcsOver[i]);
paulo@89 10317 }
paulo@89 10318 dispatch.elementMouseover({
paulo@89 10319 data: d.data,
paulo@89 10320 index: i,
paulo@89 10321 color: d3.select(this).style("fill")
paulo@89 10322 });
paulo@89 10323 });
paulo@89 10324 ae.on('mouseout', function(d, i) {
paulo@89 10325 d3.select(this).classed('hover', false);
paulo@89 10326 if (growOnHover) {
paulo@89 10327 d3.select(this).select("path").transition()
paulo@89 10328 .duration(50)
paulo@89 10329 .attr("d", arcs[i]);
paulo@89 10330 }
paulo@89 10331 dispatch.elementMouseout({data: d.data, index: i});
paulo@89 10332 });
paulo@89 10333 ae.on('mousemove', function(d, i) {
paulo@89 10334 dispatch.elementMousemove({data: d.data, index: i});
paulo@89 10335 });
paulo@89 10336 ae.on('click', function(d, i) {
paulo@89 10337 dispatch.elementClick({
paulo@89 10338 data: d.data,
paulo@89 10339 index: i,
paulo@89 10340 color: d3.select(this).style("fill")
paulo@89 10341 });
paulo@89 10342 });
paulo@89 10343 ae.on('dblclick', function(d, i) {
paulo@89 10344 dispatch.elementDblClick({
paulo@89 10345 data: d.data,
paulo@89 10346 index: i,
paulo@89 10347 color: d3.select(this).style("fill")
paulo@89 10348 });
paulo@89 10349 });
paulo@89 10350
paulo@89 10351 slices.attr('fill', function(d,i) { return color(d.data, i); });
paulo@89 10352 slices.attr('stroke', function(d,i) { return color(d.data, i); });
paulo@89 10353
paulo@89 10354 var paths = ae.append('path').each(function(d) {
paulo@89 10355 this._current = d;
paulo@89 10356 });
paulo@89 10357
paulo@89 10358 slices.select('path')
paulo@89 10359 .transition()
paulo@89 10360 .attr('d', function (d, i) { return arcs[i](d); })
paulo@89 10361 .attrTween('d', arcTween);
paulo@89 10362
paulo@89 10363 if (showLabels) {
paulo@89 10364 // This does the normal label
paulo@89 10365 var labelsArc = [];
paulo@89 10366 for (var i = 0; i < data[0].length; i++) {
paulo@89 10367 labelsArc.push(arcs[i]);
paulo@89 10368
paulo@89 10369 if (labelsOutside) {
paulo@89 10370 if (donut) {
paulo@89 10371 labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius());
paulo@89 10372 if (startAngle !== false) labelsArc[i].startAngle(startAngle);
paulo@89 10373 if (endAngle !== false) labelsArc[i].endAngle(endAngle);
paulo@89 10374 }
paulo@89 10375 } else if (!donut) {
paulo@89 10376 labelsArc[i].innerRadius(0);
paulo@89 10377 }
paulo@89 10378 }
paulo@89 10379
paulo@89 10380 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
paulo@89 10381 var group = d3.select(this);
paulo@89 10382
paulo@89 10383 group.attr('transform', function (d, i) {
paulo@89 10384 if (labelSunbeamLayout) {
paulo@89 10385 d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
paulo@89 10386 d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
paulo@89 10387 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
paulo@89 10388 if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
paulo@89 10389 rotateAngle -= 90;
paulo@89 10390 } else {
paulo@89 10391 rotateAngle += 90;
paulo@89 10392 }
paulo@89 10393 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
paulo@89 10394 } else {
paulo@89 10395 d.outerRadius = radius + 10; // Set Outer Coordinate
paulo@89 10396 d.innerRadius = radius + 15; // Set Inner Coordinate
paulo@89 10397 return 'translate(' + labelsArc[i].centroid(d) + ')'
paulo@89 10398 }
paulo@89 10399 });
paulo@89 10400
paulo@89 10401 group.append('rect')
paulo@89 10402 .style('stroke', '#fff')
paulo@89 10403 .style('fill', '#fff')
paulo@89 10404 .attr("rx", 3)
paulo@89 10405 .attr("ry", 3);
paulo@89 10406
paulo@89 10407 group.append('text')
paulo@89 10408 .style('text-anchor', labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle') //center the text on it's origin or begin/end if orthogonal aligned
paulo@89 10409 .style('fill', '#000')
paulo@89 10410 });
paulo@89 10411
paulo@89 10412 var labelLocationHash = {};
paulo@89 10413 var avgHeight = 14;
paulo@89 10414 var avgWidth = 140;
paulo@89 10415 var createHashKey = function(coordinates) {
paulo@89 10416 return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
paulo@89 10417 };
paulo@89 10418
paulo@89 10419 pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) {
paulo@89 10420 if (labelSunbeamLayout) {
paulo@89 10421 d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
paulo@89 10422 d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
paulo@89 10423 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
paulo@89 10424 if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
paulo@89 10425 rotateAngle -= 90;
paulo@89 10426 } else {
paulo@89 10427 rotateAngle += 90;
paulo@89 10428 }
paulo@89 10429 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
paulo@89 10430 } else {
paulo@89 10431 d.outerRadius = radius + 10; // Set Outer Coordinate
paulo@89 10432 d.innerRadius = radius + 15; // Set Inner Coordinate
paulo@89 10433
paulo@89 10434 /*
paulo@89 10435 Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
paulo@89 10436 Each label location is hashed, and if a hash collision occurs, we assume an overlap.
paulo@89 10437 Adjust the label's y-position to remove the overlap.
paulo@89 10438 */
paulo@89 10439 var center = labelsArc[i].centroid(d);
paulo@89 10440 if (d.value) {
paulo@89 10441 var hashKey = createHashKey(center);
paulo@89 10442 if (labelLocationHash[hashKey]) {
paulo@89 10443 center[1] -= avgHeight;
paulo@89 10444 }
paulo@89 10445 labelLocationHash[createHashKey(center)] = true;
paulo@89 10446 }
paulo@89 10447 return 'translate(' + center + ')'
paulo@89 10448 }
paulo@89 10449 });
paulo@89 10450
paulo@89 10451 pieLabels.select(".nv-label text")
paulo@89 10452 .style('text-anchor', function(d,i) {
paulo@89 10453 //center the text on it's origin or begin/end if orthogonal aligned
paulo@89 10454 return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle';
paulo@89 10455 })
paulo@89 10456 .text(function(d, i) {
paulo@89 10457 var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
paulo@89 10458 var label = '';
paulo@89 10459 if (!d.value || percent < labelThreshold) return '';
paulo@89 10460
paulo@89 10461 if(typeof labelType === 'function') {
paulo@89 10462 label = labelType(d, i, {
paulo@89 10463 'key': getX(d.data),
paulo@89 10464 'value': getY(d.data),
paulo@89 10465 'percent': valueFormat(percent)
paulo@89 10466 });
paulo@89 10467 } else {
paulo@89 10468 switch (labelType) {
paulo@89 10469 case 'key':
paulo@89 10470 label = getX(d.data);
paulo@89 10471 break;
paulo@89 10472 case 'value':
paulo@89 10473 label = valueFormat(getY(d.data));
paulo@89 10474 break;
paulo@89 10475 case 'percent':
paulo@89 10476 label = d3.format('%')(percent);
paulo@89 10477 break;
paulo@89 10478 }
paulo@89 10479 }
paulo@89 10480 return label;
paulo@89 10481 })
paulo@89 10482 ;
paulo@89 10483 }
paulo@89 10484
paulo@89 10485
paulo@89 10486 // Computes the angle of an arc, converting from radians to degrees.
paulo@89 10487 function angle(d) {
paulo@89 10488 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
paulo@89 10489 return a > 90 ? a - 180 : a;
paulo@89 10490 }
paulo@89 10491
paulo@89 10492 function arcTween(a, idx) {
paulo@89 10493 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
paulo@89 10494 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
paulo@89 10495 if (!donut) a.innerRadius = 0;
paulo@89 10496 var i = d3.interpolate(this._current, a);
paulo@89 10497 this._current = i(0);
paulo@89 10498 return function (t) {
paulo@89 10499 return arcs[idx](i(t));
paulo@89 10500 };
paulo@89 10501 }
paulo@89 10502 });
paulo@89 10503
paulo@89 10504 renderWatch.renderEnd('pie immediate');
paulo@89 10505 return chart;
paulo@89 10506 }
paulo@89 10507
paulo@89 10508 //============================================================
paulo@89 10509 // Expose Public Variables
paulo@89 10510 //------------------------------------------------------------
paulo@89 10511
paulo@89 10512 chart.dispatch = dispatch;
paulo@89 10513 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 10514
paulo@89 10515 chart._options = Object.create({}, {
paulo@89 10516 // simple options, just get/set the necessary values
paulo@89 10517 arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } },
paulo@89 10518 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 10519 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 10520 showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
paulo@89 10521 title: {get: function(){return title;}, set: function(_){title=_;}},
paulo@89 10522 titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
paulo@89 10523 labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
paulo@89 10524 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
paulo@89 10525 x: {get: function(){return getX;}, set: function(_){getX=_;}},
paulo@89 10526 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 10527 endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
paulo@89 10528 startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
paulo@89 10529 padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
paulo@89 10530 cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
paulo@89 10531 donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
paulo@89 10532 labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}},
paulo@89 10533 labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
paulo@89 10534 donut: {get: function(){return donut;}, set: function(_){donut=_;}},
paulo@89 10535 growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
paulo@89 10536
paulo@89 10537 // depreciated after 1.7.1
paulo@89 10538 pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
paulo@89 10539 labelsOutside=_;
paulo@89 10540 nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
paulo@89 10541 }},
paulo@89 10542 // depreciated after 1.7.1
paulo@89 10543 donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
paulo@89 10544 labelsOutside=_;
paulo@89 10545 nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
paulo@89 10546 }},
paulo@89 10547 // deprecated after 1.7.1
paulo@89 10548 labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
paulo@89 10549 valueFormat=_;
paulo@89 10550 nv.deprecated('labelFormat','use valueFormat instead');
paulo@89 10551 }},
paulo@89 10552
paulo@89 10553 // options that require extra logic in the setter
paulo@89 10554 margin: {get: function(){return margin;}, set: function(_){
paulo@89 10555 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
paulo@89 10556 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
paulo@89 10557 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
paulo@89 10558 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
paulo@89 10559 }},
paulo@89 10560 y: {get: function(){return getY;}, set: function(_){
paulo@89 10561 getY=d3.functor(_);
paulo@89 10562 }},
paulo@89 10563 color: {get: function(){return color;}, set: function(_){
paulo@89 10564 color=nv.utils.getColor(_);
paulo@89 10565 }},
paulo@89 10566 labelType: {get: function(){return labelType;}, set: function(_){
paulo@89 10567 labelType= _ || 'key';
paulo@89 10568 }}
paulo@89 10569 });
paulo@89 10570
paulo@89 10571 nv.utils.initOptions(chart);
paulo@89 10572 return chart;
paulo@89 10573 };
paulo@89 10574 nv.models.pieChart = function() {
paulo@89 10575 "use strict";
paulo@89 10576
paulo@89 10577 //============================================================
paulo@89 10578 // Public Variables with Default Settings
paulo@89 10579 //------------------------------------------------------------
paulo@89 10580
paulo@89 10581 var pie = nv.models.pie();
paulo@89 10582 var legend = nv.models.legend();
paulo@89 10583 var tooltip = nv.models.tooltip();
paulo@89 10584
paulo@89 10585 var margin = {top: 30, right: 20, bottom: 20, left: 20}
paulo@89 10586 , width = null
paulo@89 10587 , height = null
paulo@89 10588 , showLegend = true
paulo@89 10589 , legendPosition = "top"
paulo@89 10590 , color = nv.utils.defaultColor()
paulo@89 10591 , state = nv.utils.state()
paulo@89 10592 , defaultState = null
paulo@89 10593 , noData = null
paulo@89 10594 , duration = 250
paulo@89 10595 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
paulo@89 10596 ;
paulo@89 10597
paulo@89 10598 tooltip
paulo@89 10599 .headerEnabled(false)
paulo@89 10600 .duration(0)
paulo@89 10601 .valueFormatter(function(d, i) {
paulo@89 10602 return pie.valueFormat()(d, i);
paulo@89 10603 });
paulo@89 10604
paulo@89 10605 //============================================================
paulo@89 10606 // Private Variables
paulo@89 10607 //------------------------------------------------------------
paulo@89 10608
paulo@89 10609 var renderWatch = nv.utils.renderWatch(dispatch);
paulo@89 10610
paulo@89 10611 var stateGetter = function(data) {
paulo@89 10612 return function(){
paulo@89 10613 return {
paulo@89 10614 active: data.map(function(d) { return !d.disabled })
paulo@89 10615 };
paulo@89 10616 }
paulo@89 10617 };
paulo@89 10618
paulo@89 10619 var stateSetter = function(data) {
paulo@89 10620 return function(state) {
paulo@89 10621 if (state.active !== undefined) {
paulo@89 10622 data.forEach(function (series, i) {
paulo@89 10623 series.disabled = !state.active[i];
paulo@89 10624 });
paulo@89 10625 }
paulo@89 10626 }
paulo@89 10627 };
paulo@89 10628
paulo@89 10629 //============================================================
paulo@89 10630 // Chart function
paulo@89 10631 //------------------------------------------------------------
paulo@89 10632
paulo@89 10633 function chart(selection) {
paulo@89 10634 renderWatch.reset();
paulo@89 10635 renderWatch.models(pie);
paulo@89 10636
paulo@89 10637 selection.each(function(data) {
paulo@89 10638 var container = d3.select(this);
paulo@89 10639 nv.utils.initSVG(container);
paulo@89 10640
paulo@89 10641 var that = this;
paulo@89 10642 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 10643 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 10644
paulo@89 10645 chart.update = function() { container.transition().call(chart); };
paulo@89 10646 chart.container = this;
paulo@89 10647
paulo@89 10648 state.setter(stateSetter(data), chart.update)
paulo@89 10649 .getter(stateGetter(data))
paulo@89 10650 .update();
paulo@89 10651
paulo@89 10652 //set state.disabled
paulo@89 10653 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 10654
paulo@89 10655 if (!defaultState) {
paulo@89 10656 var key;
paulo@89 10657 defaultState = {};
paulo@89 10658 for (key in state) {
paulo@89 10659 if (state[key] instanceof Array)
paulo@89 10660 defaultState[key] = state[key].slice(0);
paulo@89 10661 else
paulo@89 10662 defaultState[key] = state[key];
paulo@89 10663 }
paulo@89 10664 }
paulo@89 10665
paulo@89 10666 // Display No Data message if there's nothing to show.
paulo@89 10667 if (!data || !data.length) {
paulo@89 10668 nv.utils.noData(chart, container);
paulo@89 10669 return chart;
paulo@89 10670 } else {
paulo@89 10671 container.selectAll('.nv-noData').remove();
paulo@89 10672 }
paulo@89 10673
paulo@89 10674 // Setup containers and skeleton of chart
paulo@89 10675 var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
paulo@89 10676 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
paulo@89 10677 var g = wrap.select('g');
paulo@89 10678
paulo@89 10679 gEnter.append('g').attr('class', 'nv-pieWrap');
paulo@89 10680 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 10681
paulo@89 10682 // Legend
paulo@89 10683 if (showLegend) {
paulo@89 10684 if (legendPosition === "top") {
paulo@89 10685 legend.width( availableWidth ).key(pie.x());
paulo@89 10686
paulo@89 10687 wrap.select('.nv-legendWrap')
paulo@89 10688 .datum(data)
paulo@89 10689 .call(legend);
paulo@89 10690
paulo@89 10691 if ( margin.top != legend.height()) {
paulo@89 10692 margin.top = legend.height();
paulo@89 10693 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 10694 }
paulo@89 10695
paulo@89 10696 wrap.select('.nv-legendWrap')
paulo@89 10697 .attr('transform', 'translate(0,' + (-margin.top) +')');
paulo@89 10698 } else if (legendPosition === "right") {
paulo@89 10699 var legendWidth = nv.models.legend().width();
paulo@89 10700 if (availableWidth / 2 < legendWidth) {
paulo@89 10701 legendWidth = (availableWidth / 2)
paulo@89 10702 }
paulo@89 10703 legend.height(availableHeight).key(pie.x());
paulo@89 10704 legend.width(legendWidth);
paulo@89 10705 availableWidth -= legend.width();
paulo@89 10706
paulo@89 10707 wrap.select('.nv-legendWrap')
paulo@89 10708 .datum(data)
paulo@89 10709 .call(legend)
paulo@89 10710 .attr('transform', 'translate(' + (availableWidth) +',0)');
paulo@89 10711 }
paulo@89 10712 }
paulo@89 10713 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 10714
paulo@89 10715 // Main Chart Component(s)
paulo@89 10716 pie.width(availableWidth).height(availableHeight);
paulo@89 10717 var pieWrap = g.select('.nv-pieWrap').datum([data]);
paulo@89 10718 d3.transition(pieWrap).call(pie);
paulo@89 10719
paulo@89 10720 //============================================================
paulo@89 10721 // Event Handling/Dispatching (in chart's scope)
paulo@89 10722 //------------------------------------------------------------
paulo@89 10723
paulo@89 10724 legend.dispatch.on('stateChange', function(newState) {
paulo@89 10725 for (var key in newState) {
paulo@89 10726 state[key] = newState[key];
paulo@89 10727 }
paulo@89 10728 dispatch.stateChange(state);
paulo@89 10729 chart.update();
paulo@89 10730 });
paulo@89 10731
paulo@89 10732 // Update chart from a state object passed to event handler
paulo@89 10733 dispatch.on('changeState', function(e) {
paulo@89 10734 if (typeof e.disabled !== 'undefined') {
paulo@89 10735 data.forEach(function(series,i) {
paulo@89 10736 series.disabled = e.disabled[i];
paulo@89 10737 });
paulo@89 10738 state.disabled = e.disabled;
paulo@89 10739 }
paulo@89 10740 chart.update();
paulo@89 10741 });
paulo@89 10742 });
paulo@89 10743
paulo@89 10744 renderWatch.renderEnd('pieChart immediate');
paulo@89 10745 return chart;
paulo@89 10746 }
paulo@89 10747
paulo@89 10748 //============================================================
paulo@89 10749 // Event Handling/Dispatching (out of chart's scope)
paulo@89 10750 //------------------------------------------------------------
paulo@89 10751
paulo@89 10752 pie.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 10753 evt['series'] = {
paulo@89 10754 key: chart.x()(evt.data),
paulo@89 10755 value: chart.y()(evt.data),
paulo@89 10756 color: evt.color
paulo@89 10757 };
paulo@89 10758 tooltip.data(evt).hidden(false);
paulo@89 10759 });
paulo@89 10760
paulo@89 10761 pie.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 10762 tooltip.hidden(true);
paulo@89 10763 });
paulo@89 10764
paulo@89 10765 pie.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 10766 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 10767 });
paulo@89 10768
paulo@89 10769 //============================================================
paulo@89 10770 // Expose Public Variables
paulo@89 10771 //------------------------------------------------------------
paulo@89 10772
paulo@89 10773 // expose chart's sub-components
paulo@89 10774 chart.legend = legend;
paulo@89 10775 chart.dispatch = dispatch;
paulo@89 10776 chart.pie = pie;
paulo@89 10777 chart.tooltip = tooltip;
paulo@89 10778 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 10779
paulo@89 10780 // use Object get/set functionality to map between vars and chart functions
paulo@89 10781 chart._options = Object.create({}, {
paulo@89 10782 // simple options, just get/set the necessary values
paulo@89 10783 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 10784 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 10785 legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
paulo@89 10786 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 10787
paulo@89 10788 // deprecated options
paulo@89 10789 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 10790 // deprecated after 1.7.1
paulo@89 10791 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 10792 tooltip.enabled(!!_);
paulo@89 10793 }},
paulo@89 10794 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 10795 // deprecated after 1.7.1
paulo@89 10796 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 10797 tooltip.contentGenerator(_);
paulo@89 10798 }},
paulo@89 10799
paulo@89 10800 // options that require extra logic in the setter
paulo@89 10801 color: {get: function(){return color;}, set: function(_){
paulo@89 10802 color = _;
paulo@89 10803 legend.color(color);
paulo@89 10804 pie.color(color);
paulo@89 10805 }},
paulo@89 10806 duration: {get: function(){return duration;}, set: function(_){
paulo@89 10807 duration = _;
paulo@89 10808 renderWatch.reset(duration);
paulo@89 10809 }},
paulo@89 10810 margin: {get: function(){return margin;}, set: function(_){
paulo@89 10811 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 10812 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 10813 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 10814 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 10815 }}
paulo@89 10816 });
paulo@89 10817 nv.utils.inheritOptions(chart, pie);
paulo@89 10818 nv.utils.initOptions(chart);
paulo@89 10819 return chart;
paulo@89 10820 };
paulo@89 10821
paulo@89 10822 nv.models.scatter = function() {
paulo@89 10823 "use strict";
paulo@89 10824
paulo@89 10825 //============================================================
paulo@89 10826 // Public Variables with Default Settings
paulo@89 10827 //------------------------------------------------------------
paulo@89 10828
paulo@89 10829 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 10830 , width = null
paulo@89 10831 , height = null
paulo@89 10832 , color = nv.utils.defaultColor() // chooses color
paulo@89 10833 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
paulo@89 10834 , container = null
paulo@89 10835 , x = d3.scale.linear()
paulo@89 10836 , y = d3.scale.linear()
paulo@89 10837 , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
paulo@89 10838 , getX = function(d) { return d.x } // accessor to get the x value
paulo@89 10839 , getY = function(d) { return d.y } // accessor to get the y value
paulo@89 10840 , getSize = function(d) { return d.size || 1} // accessor to get the point size
paulo@89 10841 , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
paulo@89 10842 , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
paulo@89 10843 , forceY = [] // List of numbers to Force into the Y scale
paulo@89 10844 , forceSize = [] // List of numbers to Force into the Size scale
paulo@89 10845 , interactive = true // If true, plots a voronoi overlay for advanced point intersection
paulo@89 10846 , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
paulo@89 10847 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
paulo@89 10848 , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
paulo@89 10849 , clipEdge = false // if true, masks points within x and y scale
paulo@89 10850 , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
paulo@89 10851 , showVoronoi = false // display the voronoi areas
paulo@89 10852 , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
paulo@89 10853 , xDomain = null // Override x domain (skips the calculation from data)
paulo@89 10854 , yDomain = null // Override y domain
paulo@89 10855 , xRange = null // Override x range
paulo@89 10856 , yRange = null // Override y range
paulo@89 10857 , sizeDomain = null // Override point size domain
paulo@89 10858 , sizeRange = null
paulo@89 10859 , singlePoint = false
paulo@89 10860 , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
paulo@89 10861 , useVoronoi = true
paulo@89 10862 , duration = 250
paulo@89 10863 ;
paulo@89 10864
paulo@89 10865
paulo@89 10866 //============================================================
paulo@89 10867 // Private Variables
paulo@89 10868 //------------------------------------------------------------
paulo@89 10869
paulo@89 10870 var x0, y0, z0 // used to store previous scales
paulo@89 10871 , timeoutID
paulo@89 10872 , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
paulo@89 10873 , renderWatch = nv.utils.renderWatch(dispatch, duration)
paulo@89 10874 , _sizeRange_def = [16, 256]
paulo@89 10875 ;
paulo@89 10876
paulo@89 10877 function chart(selection) {
paulo@89 10878 renderWatch.reset();
paulo@89 10879 selection.each(function(data) {
paulo@89 10880 container = d3.select(this);
paulo@89 10881 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 10882 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 10883
paulo@89 10884 nv.utils.initSVG(container);
paulo@89 10885
paulo@89 10886 //add series index to each data point for reference
paulo@89 10887 data.forEach(function(series, i) {
paulo@89 10888 series.values.forEach(function(point) {
paulo@89 10889 point.series = i;
paulo@89 10890 });
paulo@89 10891 });
paulo@89 10892
paulo@89 10893 // Setup Scales
paulo@89 10894 // remap and flatten the data for use in calculating the scales' domains
paulo@89 10895 var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
paulo@89 10896 d3.merge(
paulo@89 10897 data.map(function(d) {
paulo@89 10898 return d.values.map(function(d,i) {
paulo@89 10899 return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
paulo@89 10900 })
paulo@89 10901 })
paulo@89 10902 );
paulo@89 10903
paulo@89 10904 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
paulo@89 10905
paulo@89 10906 if (padData && data[0])
paulo@89 10907 x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
paulo@89 10908 //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
paulo@89 10909 else
paulo@89 10910 x.range(xRange || [0, availableWidth]);
paulo@89 10911
paulo@89 10912 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
paulo@89 10913 .range(yRange || [availableHeight, 0]);
paulo@89 10914
paulo@89 10915 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
paulo@89 10916 .range(sizeRange || _sizeRange_def);
paulo@89 10917
paulo@89 10918 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
paulo@89 10919 singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1];
paulo@89 10920
paulo@89 10921 if (x.domain()[0] === x.domain()[1])
paulo@89 10922 x.domain()[0] ?
paulo@89 10923 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
paulo@89 10924 : x.domain([-1,1]);
paulo@89 10925
paulo@89 10926 if (y.domain()[0] === y.domain()[1])
paulo@89 10927 y.domain()[0] ?
paulo@89 10928 y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
paulo@89 10929 : y.domain([-1,1]);
paulo@89 10930
paulo@89 10931 if ( isNaN(x.domain()[0])) {
paulo@89 10932 x.domain([-1,1]);
paulo@89 10933 }
paulo@89 10934
paulo@89 10935 if ( isNaN(y.domain()[0])) {
paulo@89 10936 y.domain([-1,1]);
paulo@89 10937 }
paulo@89 10938
paulo@89 10939 x0 = x0 || x;
paulo@89 10940 y0 = y0 || y;
paulo@89 10941 z0 = z0 || z;
paulo@89 10942
paulo@89 10943 // Setup containers and skeleton of chart
paulo@89 10944 var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
paulo@89 10945 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
paulo@89 10946 var defsEnter = wrapEnter.append('defs');
paulo@89 10947 var gEnter = wrapEnter.append('g');
paulo@89 10948 var g = wrap.select('g');
paulo@89 10949
paulo@89 10950 wrap.classed('nv-single-point', singlePoint);
paulo@89 10951 gEnter.append('g').attr('class', 'nv-groups');
paulo@89 10952 gEnter.append('g').attr('class', 'nv-point-paths');
paulo@89 10953 wrapEnter.append('g').attr('class', 'nv-point-clips');
paulo@89 10954
paulo@89 10955 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 10956
paulo@89 10957 defsEnter.append('clipPath')
paulo@89 10958 .attr('id', 'nv-edge-clip-' + id)
paulo@89 10959 .append('rect');
paulo@89 10960
paulo@89 10961 wrap.select('#nv-edge-clip-' + id + ' rect')
paulo@89 10962 .attr('width', availableWidth)
paulo@89 10963 .attr('height', (availableHeight > 0) ? availableHeight : 0);
paulo@89 10964
paulo@89 10965 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
paulo@89 10966
paulo@89 10967 function updateInteractiveLayer() {
paulo@89 10968 // Always clear needs-update flag regardless of whether or not
paulo@89 10969 // we will actually do anything (avoids needless invocations).
paulo@89 10970 needsUpdate = false;
paulo@89 10971
paulo@89 10972 if (!interactive) return false;
paulo@89 10973
paulo@89 10974 // inject series and point index for reference into voronoi
paulo@89 10975 if (useVoronoi === true) {
paulo@89 10976 var vertices = d3.merge(data.map(function(group, groupIndex) {
paulo@89 10977 return group.values
paulo@89 10978 .map(function(point, pointIndex) {
paulo@89 10979 // *Adding noise to make duplicates very unlikely
paulo@89 10980 // *Injecting series and point index for reference
paulo@89 10981 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
paulo@89 10982 */
paulo@89 10983 var pX = getX(point,pointIndex);
paulo@89 10984 var pY = getY(point,pointIndex);
paulo@89 10985
paulo@89 10986 return [x(pX)+ Math.random() * 1e-4,
paulo@89 10987 y(pY)+ Math.random() * 1e-4,
paulo@89 10988 groupIndex,
paulo@89 10989 pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
paulo@89 10990 })
paulo@89 10991 .filter(function(pointArray, pointIndex) {
paulo@89 10992 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
paulo@89 10993 })
paulo@89 10994 })
paulo@89 10995 );
paulo@89 10996
paulo@89 10997 if (vertices.length == 0) return false; // No active points, we're done
paulo@89 10998 if (vertices.length < 3) {
paulo@89 10999 // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
paulo@89 11000 vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
paulo@89 11001 vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
paulo@89 11002 vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
paulo@89 11003 vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
paulo@89 11004 }
paulo@89 11005
paulo@89 11006 // keep voronoi sections from going more than 10 outside of graph
paulo@89 11007 // to avoid overlap with other things like legend etc
paulo@89 11008 var bounds = d3.geom.polygon([
paulo@89 11009 [-10,-10],
paulo@89 11010 [-10,height + 10],
paulo@89 11011 [width + 10,height + 10],
paulo@89 11012 [width + 10,-10]
paulo@89 11013 ]);
paulo@89 11014
paulo@89 11015 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
paulo@89 11016 return {
paulo@89 11017 'data': bounds.clip(d),
paulo@89 11018 'series': vertices[i][2],
paulo@89 11019 'point': vertices[i][3]
paulo@89 11020 }
paulo@89 11021 });
paulo@89 11022
paulo@89 11023 // nuke all voronoi paths on reload and recreate them
paulo@89 11024 wrap.select('.nv-point-paths').selectAll('path').remove();
paulo@89 11025 var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
paulo@89 11026 var vPointPaths = pointPaths
paulo@89 11027 .enter().append("svg:path")
paulo@89 11028 .attr("d", function(d) {
paulo@89 11029 if (!d || !d.data || d.data.length === 0)
paulo@89 11030 return 'M 0 0';
paulo@89 11031 else
paulo@89 11032 return "M" + d.data.join(",") + "Z";
paulo@89 11033 })
paulo@89 11034 .attr("id", function(d,i) {
paulo@89 11035 return "nv-path-"+i; })
paulo@89 11036 .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; })
paulo@89 11037 ;
paulo@89 11038
paulo@89 11039 // good for debugging point hover issues
paulo@89 11040 if (showVoronoi) {
paulo@89 11041 vPointPaths.style("fill", d3.rgb(230, 230, 230))
paulo@89 11042 .style('fill-opacity', 0.4)
paulo@89 11043 .style('stroke-opacity', 1)
paulo@89 11044 .style("stroke", d3.rgb(200,200,200));
paulo@89 11045 }
paulo@89 11046
paulo@89 11047 if (clipVoronoi) {
paulo@89 11048 // voronoi sections are already set to clip,
paulo@89 11049 // just create the circles with the IDs they expect
paulo@89 11050 wrap.select('.nv-point-clips').selectAll('clipPath').remove();
paulo@89 11051 wrap.select('.nv-point-clips').selectAll("clipPath")
paulo@89 11052 .data(vertices)
paulo@89 11053 .enter().append("svg:clipPath")
paulo@89 11054 .attr("id", function(d, i) { return "nv-clip-"+i;})
paulo@89 11055 .append("svg:circle")
paulo@89 11056 .attr('cx', function(d) { return d[0]; })
paulo@89 11057 .attr('cy', function(d) { return d[1]; })
paulo@89 11058 .attr('r', clipRadius);
paulo@89 11059 }
paulo@89 11060
paulo@89 11061 var mouseEventCallback = function(d, mDispatch) {
paulo@89 11062 if (needsUpdate) return 0;
paulo@89 11063 var series = data[d.series];
paulo@89 11064 if (series === undefined) return;
paulo@89 11065 var point = series.values[d.point];
paulo@89 11066 point['color'] = color(series, d.series);
paulo@89 11067
paulo@89 11068 // standardize attributes for tooltip.
paulo@89 11069 point['x'] = getX(point);
paulo@89 11070 point['y'] = getY(point);
paulo@89 11071
paulo@89 11072 // can't just get box of event node since it's actually a voronoi polygon
paulo@89 11073 var box = container.node().getBoundingClientRect();
paulo@89 11074 var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
paulo@89 11075 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
paulo@89 11076
paulo@89 11077 var pos = {
paulo@89 11078 left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10,
paulo@89 11079 top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10
paulo@89 11080 };
paulo@89 11081
paulo@89 11082 mDispatch({
paulo@89 11083 point: point,
paulo@89 11084 series: series,
paulo@89 11085 pos: pos,
paulo@89 11086 seriesIndex: d.series,
paulo@89 11087 pointIndex: d.point
paulo@89 11088 });
paulo@89 11089 };
paulo@89 11090
paulo@89 11091 pointPaths
paulo@89 11092 .on('click', function(d) {
paulo@89 11093 mouseEventCallback(d, dispatch.elementClick);
paulo@89 11094 })
paulo@89 11095 .on('dblclick', function(d) {
paulo@89 11096 mouseEventCallback(d, dispatch.elementDblClick);
paulo@89 11097 })
paulo@89 11098 .on('mouseover', function(d) {
paulo@89 11099 mouseEventCallback(d, dispatch.elementMouseover);
paulo@89 11100 })
paulo@89 11101 .on('mouseout', function(d, i) {
paulo@89 11102 mouseEventCallback(d, dispatch.elementMouseout);
paulo@89 11103 });
paulo@89 11104
paulo@89 11105 } else {
paulo@89 11106 // add event handlers to points instead voronoi paths
paulo@89 11107 wrap.select('.nv-groups').selectAll('.nv-group')
paulo@89 11108 .selectAll('.nv-point')
paulo@89 11109 //.data(dataWithPoints)
paulo@89 11110 //.style('pointer-events', 'auto') // recativate events, disabled by css
paulo@89 11111 .on('click', function(d,i) {
paulo@89 11112 //nv.log('test', d, i);
paulo@89 11113 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
paulo@89 11114 var series = data[d.series],
paulo@89 11115 point = series.values[i];
paulo@89 11116
paulo@89 11117 dispatch.elementClick({
paulo@89 11118 point: point,
paulo@89 11119 series: series,
paulo@89 11120 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
paulo@89 11121 seriesIndex: d.series,
paulo@89 11122 pointIndex: i
paulo@89 11123 });
paulo@89 11124 })
paulo@89 11125 .on('dblclick', function(d,i) {
paulo@89 11126 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
paulo@89 11127 var series = data[d.series],
paulo@89 11128 point = series.values[i];
paulo@89 11129
paulo@89 11130 dispatch.elementDblClick({
paulo@89 11131 point: point,
paulo@89 11132 series: series,
paulo@89 11133 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
paulo@89 11134 seriesIndex: d.series,
paulo@89 11135 pointIndex: i
paulo@89 11136 });
paulo@89 11137 })
paulo@89 11138 .on('mouseover', function(d,i) {
paulo@89 11139 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
paulo@89 11140 var series = data[d.series],
paulo@89 11141 point = series.values[i];
paulo@89 11142
paulo@89 11143 dispatch.elementMouseover({
paulo@89 11144 point: point,
paulo@89 11145 series: series,
paulo@89 11146 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
paulo@89 11147 seriesIndex: d.series,
paulo@89 11148 pointIndex: i,
paulo@89 11149 color: color(d, i)
paulo@89 11150 });
paulo@89 11151 })
paulo@89 11152 .on('mouseout', function(d,i) {
paulo@89 11153 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
paulo@89 11154 var series = data[d.series],
paulo@89 11155 point = series.values[i];
paulo@89 11156
paulo@89 11157 dispatch.elementMouseout({
paulo@89 11158 point: point,
paulo@89 11159 series: series,
paulo@89 11160 seriesIndex: d.series,
paulo@89 11161 pointIndex: i,
paulo@89 11162 color: color(d, i)
paulo@89 11163 });
paulo@89 11164 });
paulo@89 11165 }
paulo@89 11166 }
paulo@89 11167
paulo@89 11168 needsUpdate = true;
paulo@89 11169 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
paulo@89 11170 .data(function(d) { return d }, function(d) { return d.key });
paulo@89 11171 groups.enter().append('g')
paulo@89 11172 .style('stroke-opacity', 1e-6)
paulo@89 11173 .style('fill-opacity', 1e-6);
paulo@89 11174 groups.exit()
paulo@89 11175 .remove();
paulo@89 11176 groups
paulo@89 11177 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
paulo@89 11178 .classed('hover', function(d) { return d.hover });
paulo@89 11179 groups.watchTransition(renderWatch, 'scatter: groups')
paulo@89 11180 .style('fill', function(d,i) { return color(d, i) })
paulo@89 11181 .style('stroke', function(d,i) { return color(d, i) })
paulo@89 11182 .style('stroke-opacity', 1)
paulo@89 11183 .style('fill-opacity', .5);
paulo@89 11184
paulo@89 11185 // create the points, maintaining their IDs from the original data set
paulo@89 11186 var points = groups.selectAll('path.nv-point')
paulo@89 11187 .data(function(d) {
paulo@89 11188 return d.values.map(
paulo@89 11189 function (point, pointIndex) {
paulo@89 11190 return [point, pointIndex]
paulo@89 11191 }).filter(
paulo@89 11192 function(pointArray, pointIndex) {
paulo@89 11193 return pointActive(pointArray[0], pointIndex)
paulo@89 11194 })
paulo@89 11195 });
paulo@89 11196 points.enter().append('path')
paulo@89 11197 .style('fill', function (d) { return d.color })
paulo@89 11198 .style('stroke', function (d) { return d.color })
paulo@89 11199 .attr('transform', function(d) {
paulo@89 11200 return 'translate(' + x0(getX(d[0],d[1])) + ',' + y0(getY(d[0],d[1])) + ')'
paulo@89 11201 })
paulo@89 11202 .attr('d',
paulo@89 11203 nv.utils.symbol()
paulo@89 11204 .type(function(d) { return getShape(d[0]); })
paulo@89 11205 .size(function(d) { return z(getSize(d[0],d[1])) })
paulo@89 11206 );
paulo@89 11207 points.exit().remove();
paulo@89 11208 groups.exit().selectAll('path.nv-point')
paulo@89 11209 .watchTransition(renderWatch, 'scatter exit')
paulo@89 11210 .attr('transform', function(d) {
paulo@89 11211 return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')'
paulo@89 11212 })
paulo@89 11213 .remove();
paulo@89 11214 points.each(function(d) {
paulo@89 11215 d3.select(this)
paulo@89 11216 .classed('nv-point', true)
paulo@89 11217 .classed('nv-point-' + d[1], true)
paulo@89 11218 .classed('nv-noninteractive', !interactive)
paulo@89 11219 .classed('hover',false)
paulo@89 11220 ;
paulo@89 11221 });
paulo@89 11222 points
paulo@89 11223 .watchTransition(renderWatch, 'scatter points')
paulo@89 11224 .attr('transform', function(d) {
paulo@89 11225 //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
paulo@89 11226 return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')'
paulo@89 11227 })
paulo@89 11228 .attr('d',
paulo@89 11229 nv.utils.symbol()
paulo@89 11230 .type(function(d) { return getShape(d[0]); })
paulo@89 11231 .size(function(d) { return z(getSize(d[0],d[1])) })
paulo@89 11232 );
paulo@89 11233
paulo@89 11234 // Delay updating the invisible interactive layer for smoother animation
paulo@89 11235 clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
paulo@89 11236 timeoutID = setTimeout(updateInteractiveLayer, 300);
paulo@89 11237 //updateInteractiveLayer();
paulo@89 11238
paulo@89 11239 //store old scales for use in transitions on update
paulo@89 11240 x0 = x.copy();
paulo@89 11241 y0 = y.copy();
paulo@89 11242 z0 = z.copy();
paulo@89 11243
paulo@89 11244 });
paulo@89 11245 renderWatch.renderEnd('scatter immediate');
paulo@89 11246 return chart;
paulo@89 11247 }
paulo@89 11248
paulo@89 11249 //============================================================
paulo@89 11250 // Expose Public Variables
paulo@89 11251 //------------------------------------------------------------
paulo@89 11252
paulo@89 11253 chart.dispatch = dispatch;
paulo@89 11254 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 11255
paulo@89 11256 // utility function calls provided by this chart
paulo@89 11257 chart._calls = new function() {
paulo@89 11258 this.clearHighlights = function () {
paulo@89 11259 nv.dom.write(function() {
paulo@89 11260 container.selectAll(".nv-point.hover").classed("hover", false);
paulo@89 11261 });
paulo@89 11262 return null;
paulo@89 11263 };
paulo@89 11264 this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
paulo@89 11265 nv.dom.write(function() {
paulo@89 11266 container.select(" .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
paulo@89 11267 .classed("hover", isHoverOver);
paulo@89 11268 });
paulo@89 11269 };
paulo@89 11270 };
paulo@89 11271
paulo@89 11272 // trigger calls from events too
paulo@89 11273 dispatch.on('elementMouseover.point', function(d) {
paulo@89 11274 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
paulo@89 11275 });
paulo@89 11276
paulo@89 11277 dispatch.on('elementMouseout.point', function(d) {
paulo@89 11278 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
paulo@89 11279 });
paulo@89 11280
paulo@89 11281 chart._options = Object.create({}, {
paulo@89 11282 // simple options, just get/set the necessary values
paulo@89 11283 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 11284 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 11285 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 11286 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 11287 pointScale: {get: function(){return z;}, set: function(_){z=_;}},
paulo@89 11288 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 11289 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 11290 pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
paulo@89 11291 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 11292 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 11293 pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
paulo@89 11294 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
paulo@89 11295 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
paulo@89 11296 forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
paulo@89 11297 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
paulo@89 11298 pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
paulo@89 11299 padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
paulo@89 11300 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
paulo@89 11301 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
paulo@89 11302 clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
paulo@89 11303 clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
paulo@89 11304 showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
paulo@89 11305 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 11306
paulo@89 11307
paulo@89 11308 // simple functor options
paulo@89 11309 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
paulo@89 11310 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
paulo@89 11311 pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
paulo@89 11312 pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
paulo@89 11313
paulo@89 11314 // options that require extra logic in the setter
paulo@89 11315 margin: {get: function(){return margin;}, set: function(_){
paulo@89 11316 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 11317 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 11318 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 11319 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 11320 }},
paulo@89 11321 duration: {get: function(){return duration;}, set: function(_){
paulo@89 11322 duration = _;
paulo@89 11323 renderWatch.reset(duration);
paulo@89 11324 }},
paulo@89 11325 color: {get: function(){return color;}, set: function(_){
paulo@89 11326 color = nv.utils.getColor(_);
paulo@89 11327 }},
paulo@89 11328 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
paulo@89 11329 useVoronoi = _;
paulo@89 11330 if (useVoronoi === false) {
paulo@89 11331 clipVoronoi = false;
paulo@89 11332 }
paulo@89 11333 }}
paulo@89 11334 });
paulo@89 11335
paulo@89 11336 nv.utils.initOptions(chart);
paulo@89 11337 return chart;
paulo@89 11338 };
paulo@89 11339
paulo@89 11340 nv.models.scatterChart = function() {
paulo@89 11341 "use strict";
paulo@89 11342
paulo@89 11343 //============================================================
paulo@89 11344 // Public Variables with Default Settings
paulo@89 11345 //------------------------------------------------------------
paulo@89 11346
paulo@89 11347 var scatter = nv.models.scatter()
paulo@89 11348 , xAxis = nv.models.axis()
paulo@89 11349 , yAxis = nv.models.axis()
paulo@89 11350 , legend = nv.models.legend()
paulo@89 11351 , distX = nv.models.distribution()
paulo@89 11352 , distY = nv.models.distribution()
paulo@89 11353 , tooltip = nv.models.tooltip()
paulo@89 11354 ;
paulo@89 11355
paulo@89 11356 var margin = {top: 30, right: 20, bottom: 50, left: 75}
paulo@89 11357 , width = null
paulo@89 11358 , height = null
paulo@89 11359 , container = null
paulo@89 11360 , color = nv.utils.defaultColor()
paulo@89 11361 , x = scatter.xScale()
paulo@89 11362 , y = scatter.yScale()
paulo@89 11363 , showDistX = false
paulo@89 11364 , showDistY = false
paulo@89 11365 , showLegend = true
paulo@89 11366 , showXAxis = true
paulo@89 11367 , showYAxis = true
paulo@89 11368 , rightAlignYAxis = false
paulo@89 11369 , state = nv.utils.state()
paulo@89 11370 , defaultState = null
paulo@89 11371 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
paulo@89 11372 , noData = null
paulo@89 11373 , duration = 250
paulo@89 11374 ;
paulo@89 11375
paulo@89 11376 scatter.xScale(x).yScale(y);
paulo@89 11377 xAxis.orient('bottom').tickPadding(10);
paulo@89 11378 yAxis
paulo@89 11379 .orient((rightAlignYAxis) ? 'right' : 'left')
paulo@89 11380 .tickPadding(10)
paulo@89 11381 ;
paulo@89 11382 distX.axis('x');
paulo@89 11383 distY.axis('y');
paulo@89 11384 tooltip
paulo@89 11385 .headerFormatter(function(d, i) {
paulo@89 11386 return xAxis.tickFormat()(d, i);
paulo@89 11387 })
paulo@89 11388 .valueFormatter(function(d, i) {
paulo@89 11389 return yAxis.tickFormat()(d, i);
paulo@89 11390 });
paulo@89 11391
paulo@89 11392 //============================================================
paulo@89 11393 // Private Variables
paulo@89 11394 //------------------------------------------------------------
paulo@89 11395
paulo@89 11396 var x0, y0
paulo@89 11397 , renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 11398
paulo@89 11399 var stateGetter = function(data) {
paulo@89 11400 return function(){
paulo@89 11401 return {
paulo@89 11402 active: data.map(function(d) { return !d.disabled })
paulo@89 11403 };
paulo@89 11404 }
paulo@89 11405 };
paulo@89 11406
paulo@89 11407 var stateSetter = function(data) {
paulo@89 11408 return function(state) {
paulo@89 11409 if (state.active !== undefined)
paulo@89 11410 data.forEach(function(series,i) {
paulo@89 11411 series.disabled = !state.active[i];
paulo@89 11412 });
paulo@89 11413 }
paulo@89 11414 };
paulo@89 11415
paulo@89 11416 function chart(selection) {
paulo@89 11417 renderWatch.reset();
paulo@89 11418 renderWatch.models(scatter);
paulo@89 11419 if (showXAxis) renderWatch.models(xAxis);
paulo@89 11420 if (showYAxis) renderWatch.models(yAxis);
paulo@89 11421 if (showDistX) renderWatch.models(distX);
paulo@89 11422 if (showDistY) renderWatch.models(distY);
paulo@89 11423
paulo@89 11424 selection.each(function(data) {
paulo@89 11425 var that = this;
paulo@89 11426
paulo@89 11427 container = d3.select(this);
paulo@89 11428 nv.utils.initSVG(container);
paulo@89 11429
paulo@89 11430 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 11431 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 11432
paulo@89 11433 chart.update = function() {
paulo@89 11434 if (duration === 0)
paulo@89 11435 container.call(chart);
paulo@89 11436 else
paulo@89 11437 container.transition().duration(duration).call(chart);
paulo@89 11438 };
paulo@89 11439 chart.container = this;
paulo@89 11440
paulo@89 11441 state
paulo@89 11442 .setter(stateSetter(data), chart.update)
paulo@89 11443 .getter(stateGetter(data))
paulo@89 11444 .update();
paulo@89 11445
paulo@89 11446 // DEPRECATED set state.disableddisabled
paulo@89 11447 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 11448
paulo@89 11449 if (!defaultState) {
paulo@89 11450 var key;
paulo@89 11451 defaultState = {};
paulo@89 11452 for (key in state) {
paulo@89 11453 if (state[key] instanceof Array)
paulo@89 11454 defaultState[key] = state[key].slice(0);
paulo@89 11455 else
paulo@89 11456 defaultState[key] = state[key];
paulo@89 11457 }
paulo@89 11458 }
paulo@89 11459
paulo@89 11460 // Display noData message if there's nothing to show.
paulo@89 11461 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 11462 nv.utils.noData(chart, container);
paulo@89 11463 renderWatch.renderEnd('scatter immediate');
paulo@89 11464 return chart;
paulo@89 11465 } else {
paulo@89 11466 container.selectAll('.nv-noData').remove();
paulo@89 11467 }
paulo@89 11468
paulo@89 11469 // Setup Scales
paulo@89 11470 x = scatter.xScale();
paulo@89 11471 y = scatter.yScale();
paulo@89 11472
paulo@89 11473 // Setup containers and skeleton of chart
paulo@89 11474 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
paulo@89 11475 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
paulo@89 11476 var gEnter = wrapEnter.append('g');
paulo@89 11477 var g = wrap.select('g');
paulo@89 11478
paulo@89 11479 // background for pointer events
paulo@89 11480 gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
paulo@89 11481
paulo@89 11482 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 11483 gEnter.append('g').attr('class', 'nv-y nv-axis');
paulo@89 11484 gEnter.append('g').attr('class', 'nv-scatterWrap');
paulo@89 11485 gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
paulo@89 11486 gEnter.append('g').attr('class', 'nv-distWrap');
paulo@89 11487 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 11488
paulo@89 11489 if (rightAlignYAxis) {
paulo@89 11490 g.select(".nv-y.nv-axis")
paulo@89 11491 .attr("transform", "translate(" + availableWidth + ",0)");
paulo@89 11492 }
paulo@89 11493
paulo@89 11494 // Legend
paulo@89 11495 if (showLegend) {
paulo@89 11496 var legendWidth = availableWidth;
paulo@89 11497 legend.width(legendWidth);
paulo@89 11498
paulo@89 11499 wrap.select('.nv-legendWrap')
paulo@89 11500 .datum(data)
paulo@89 11501 .call(legend);
paulo@89 11502
paulo@89 11503 if ( margin.top != legend.height()) {
paulo@89 11504 margin.top = legend.height();
paulo@89 11505 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 11506 }
paulo@89 11507
paulo@89 11508 wrap.select('.nv-legendWrap')
paulo@89 11509 .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
paulo@89 11510 }
paulo@89 11511
paulo@89 11512 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 11513
paulo@89 11514 // Main Chart Component(s)
paulo@89 11515 scatter
paulo@89 11516 .width(availableWidth)
paulo@89 11517 .height(availableHeight)
paulo@89 11518 .color(data.map(function(d,i) {
paulo@89 11519 d.color = d.color || color(d, i);
paulo@89 11520 return d.color;
paulo@89 11521 }).filter(function(d,i) { return !data[i].disabled }));
paulo@89 11522
paulo@89 11523 wrap.select('.nv-scatterWrap')
paulo@89 11524 .datum(data.filter(function(d) { return !d.disabled }))
paulo@89 11525 .call(scatter);
paulo@89 11526
paulo@89 11527
paulo@89 11528 wrap.select('.nv-regressionLinesWrap')
paulo@89 11529 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
paulo@89 11530
paulo@89 11531 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
paulo@89 11532 .data(function (d) {
paulo@89 11533 return d;
paulo@89 11534 });
paulo@89 11535
paulo@89 11536 regWrap.enter().append('g').attr('class', 'nv-regLines');
paulo@89 11537
paulo@89 11538 var regLine = regWrap.selectAll('.nv-regLine')
paulo@89 11539 .data(function (d) {
paulo@89 11540 return [d]
paulo@89 11541 });
paulo@89 11542
paulo@89 11543 regLine.enter()
paulo@89 11544 .append('line').attr('class', 'nv-regLine')
paulo@89 11545 .style('stroke-opacity', 0);
paulo@89 11546
paulo@89 11547 // don't add lines unless we have slope and intercept to use
paulo@89 11548 regLine.filter(function(d) {
paulo@89 11549 return d.intercept && d.slope;
paulo@89 11550 })
paulo@89 11551 .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
paulo@89 11552 .attr('x1', x.range()[0])
paulo@89 11553 .attr('x2', x.range()[1])
paulo@89 11554 .attr('y1', function (d, i) {
paulo@89 11555 return y(x.domain()[0] * d.slope + d.intercept)
paulo@89 11556 })
paulo@89 11557 .attr('y2', function (d, i) {
paulo@89 11558 return y(x.domain()[1] * d.slope + d.intercept)
paulo@89 11559 })
paulo@89 11560 .style('stroke', function (d, i, j) {
paulo@89 11561 return color(d, j)
paulo@89 11562 })
paulo@89 11563 .style('stroke-opacity', function (d, i) {
paulo@89 11564 return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
paulo@89 11565 });
paulo@89 11566
paulo@89 11567 // Setup Axes
paulo@89 11568 if (showXAxis) {
paulo@89 11569 xAxis
paulo@89 11570 .scale(x)
paulo@89 11571 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 11572 .tickSize( -availableHeight , 0);
paulo@89 11573
paulo@89 11574 g.select('.nv-x.nv-axis')
paulo@89 11575 .attr('transform', 'translate(0,' + y.range()[0] + ')')
paulo@89 11576 .call(xAxis);
paulo@89 11577 }
paulo@89 11578
paulo@89 11579 if (showYAxis) {
paulo@89 11580 yAxis
paulo@89 11581 .scale(y)
paulo@89 11582 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
paulo@89 11583 .tickSize( -availableWidth, 0);
paulo@89 11584
paulo@89 11585 g.select('.nv-y.nv-axis')
paulo@89 11586 .call(yAxis);
paulo@89 11587 }
paulo@89 11588
paulo@89 11589
paulo@89 11590 if (showDistX) {
paulo@89 11591 distX
paulo@89 11592 .getData(scatter.x())
paulo@89 11593 .scale(x)
paulo@89 11594 .width(availableWidth)
paulo@89 11595 .color(data.map(function(d,i) {
paulo@89 11596 return d.color || color(d, i);
paulo@89 11597 }).filter(function(d,i) { return !data[i].disabled }));
paulo@89 11598 gEnter.select('.nv-distWrap').append('g')
paulo@89 11599 .attr('class', 'nv-distributionX');
paulo@89 11600 g.select('.nv-distributionX')
paulo@89 11601 .attr('transform', 'translate(0,' + y.range()[0] + ')')
paulo@89 11602 .datum(data.filter(function(d) { return !d.disabled }))
paulo@89 11603 .call(distX);
paulo@89 11604 }
paulo@89 11605
paulo@89 11606 if (showDistY) {
paulo@89 11607 distY
paulo@89 11608 .getData(scatter.y())
paulo@89 11609 .scale(y)
paulo@89 11610 .width(availableHeight)
paulo@89 11611 .color(data.map(function(d,i) {
paulo@89 11612 return d.color || color(d, i);
paulo@89 11613 }).filter(function(d,i) { return !data[i].disabled }));
paulo@89 11614 gEnter.select('.nv-distWrap').append('g')
paulo@89 11615 .attr('class', 'nv-distributionY');
paulo@89 11616 g.select('.nv-distributionY')
paulo@89 11617 .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
paulo@89 11618 .datum(data.filter(function(d) { return !d.disabled }))
paulo@89 11619 .call(distY);
paulo@89 11620 }
paulo@89 11621
paulo@89 11622 //============================================================
paulo@89 11623 // Event Handling/Dispatching (in chart's scope)
paulo@89 11624 //------------------------------------------------------------
paulo@89 11625
paulo@89 11626 legend.dispatch.on('stateChange', function(newState) {
paulo@89 11627 for (var key in newState)
paulo@89 11628 state[key] = newState[key];
paulo@89 11629 dispatch.stateChange(state);
paulo@89 11630 chart.update();
paulo@89 11631 });
paulo@89 11632
paulo@89 11633 // Update chart from a state object passed to event handler
paulo@89 11634 dispatch.on('changeState', function(e) {
paulo@89 11635 if (typeof e.disabled !== 'undefined') {
paulo@89 11636 data.forEach(function(series,i) {
paulo@89 11637 series.disabled = e.disabled[i];
paulo@89 11638 });
paulo@89 11639 state.disabled = e.disabled;
paulo@89 11640 }
paulo@89 11641 chart.update();
paulo@89 11642 });
paulo@89 11643
paulo@89 11644 // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
paulo@89 11645 scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 11646 tooltip.hidden(true);
paulo@89 11647 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
paulo@89 11648 .attr('y1', 0);
paulo@89 11649 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
paulo@89 11650 .attr('x2', distY.size());
paulo@89 11651 });
paulo@89 11652
paulo@89 11653 scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 11654 container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
paulo@89 11655 .attr('y1', evt.pos.top - availableHeight - margin.top);
paulo@89 11656 container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
paulo@89 11657 .attr('x2', evt.pos.left + distX.size() - margin.left);
paulo@89 11658 tooltip.position(evt.pos).data(evt).hidden(false);
paulo@89 11659 });
paulo@89 11660
paulo@89 11661 //store old scales for use in transitions on update
paulo@89 11662 x0 = x.copy();
paulo@89 11663 y0 = y.copy();
paulo@89 11664
paulo@89 11665 });
paulo@89 11666
paulo@89 11667 renderWatch.renderEnd('scatter with line immediate');
paulo@89 11668 return chart;
paulo@89 11669 }
paulo@89 11670
paulo@89 11671 //============================================================
paulo@89 11672 // Expose Public Variables
paulo@89 11673 //------------------------------------------------------------
paulo@89 11674
paulo@89 11675 // expose chart's sub-components
paulo@89 11676 chart.dispatch = dispatch;
paulo@89 11677 chart.scatter = scatter;
paulo@89 11678 chart.legend = legend;
paulo@89 11679 chart.xAxis = xAxis;
paulo@89 11680 chart.yAxis = yAxis;
paulo@89 11681 chart.distX = distX;
paulo@89 11682 chart.distY = distY;
paulo@89 11683 chart.tooltip = tooltip;
paulo@89 11684
paulo@89 11685 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 11686 chart._options = Object.create({}, {
paulo@89 11687 // simple options, just get/set the necessary values
paulo@89 11688 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 11689 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 11690 container: {get: function(){return container;}, set: function(_){container=_;}},
paulo@89 11691 showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
paulo@89 11692 showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
paulo@89 11693 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 11694 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 11695 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 11696 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 11697 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 11698 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
paulo@89 11699
paulo@89 11700 // deprecated options
paulo@89 11701 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 11702 // deprecated after 1.7.1
paulo@89 11703 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 11704 tooltip.enabled(!!_);
paulo@89 11705 }},
paulo@89 11706 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 11707 // deprecated after 1.7.1
paulo@89 11708 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 11709 tooltip.contentGenerator(_);
paulo@89 11710 }},
paulo@89 11711 tooltipXContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 11712 // deprecated after 1.7.1
paulo@89 11713 nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.');
paulo@89 11714 }},
paulo@89 11715 tooltipYContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 11716 // deprecated after 1.7.1
paulo@89 11717 nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.');
paulo@89 11718 }},
paulo@89 11719
paulo@89 11720 // options that require extra logic in the setter
paulo@89 11721 margin: {get: function(){return margin;}, set: function(_){
paulo@89 11722 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 11723 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 11724 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 11725 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 11726 }},
paulo@89 11727 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
paulo@89 11728 rightAlignYAxis = _;
paulo@89 11729 yAxis.orient( (_) ? 'right' : 'left');
paulo@89 11730 }},
paulo@89 11731 color: {get: function(){return color;}, set: function(_){
paulo@89 11732 color = nv.utils.getColor(_);
paulo@89 11733 legend.color(color);
paulo@89 11734 distX.color(color);
paulo@89 11735 distY.color(color);
paulo@89 11736 }}
paulo@89 11737 });
paulo@89 11738
paulo@89 11739 nv.utils.inheritOptions(chart, scatter);
paulo@89 11740 nv.utils.initOptions(chart);
paulo@89 11741 return chart;
paulo@89 11742 };
paulo@89 11743
paulo@89 11744 nv.models.sparkline = function() {
paulo@89 11745 "use strict";
paulo@89 11746
paulo@89 11747 //============================================================
paulo@89 11748 // Public Variables with Default Settings
paulo@89 11749 //------------------------------------------------------------
paulo@89 11750
paulo@89 11751 var margin = {top: 2, right: 0, bottom: 2, left: 0}
paulo@89 11752 , width = 400
paulo@89 11753 , height = 32
paulo@89 11754 , container = null
paulo@89 11755 , animate = true
paulo@89 11756 , x = d3.scale.linear()
paulo@89 11757 , y = d3.scale.linear()
paulo@89 11758 , getX = function(d) { return d.x }
paulo@89 11759 , getY = function(d) { return d.y }
paulo@89 11760 , color = nv.utils.getColor(['#000'])
paulo@89 11761 , xDomain
paulo@89 11762 , yDomain
paulo@89 11763 , xRange
paulo@89 11764 , yRange
paulo@89 11765 ;
paulo@89 11766
paulo@89 11767 function chart(selection) {
paulo@89 11768 selection.each(function(data) {
paulo@89 11769 var availableWidth = width - margin.left - margin.right,
paulo@89 11770 availableHeight = height - margin.top - margin.bottom;
paulo@89 11771
paulo@89 11772 container = d3.select(this);
paulo@89 11773 nv.utils.initSVG(container);
paulo@89 11774
paulo@89 11775 // Setup Scales
paulo@89 11776 x .domain(xDomain || d3.extent(data, getX ))
paulo@89 11777 .range(xRange || [0, availableWidth]);
paulo@89 11778
paulo@89 11779 y .domain(yDomain || d3.extent(data, getY ))
paulo@89 11780 .range(yRange || [availableHeight, 0]);
paulo@89 11781
paulo@89 11782 // Setup containers and skeleton of chart
paulo@89 11783 var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
paulo@89 11784 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
paulo@89 11785 var gEnter = wrapEnter.append('g');
paulo@89 11786 var g = wrap.select('g');
paulo@89 11787
paulo@89 11788 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
paulo@89 11789
paulo@89 11790 var paths = wrap.selectAll('path')
paulo@89 11791 .data(function(d) { return [d] });
paulo@89 11792 paths.enter().append('path');
paulo@89 11793 paths.exit().remove();
paulo@89 11794 paths
paulo@89 11795 .style('stroke', function(d,i) { return d.color || color(d, i) })
paulo@89 11796 .attr('d', d3.svg.line()
paulo@89 11797 .x(function(d,i) { return x(getX(d,i)) })
paulo@89 11798 .y(function(d,i) { return y(getY(d,i)) })
paulo@89 11799 );
paulo@89 11800
paulo@89 11801 // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
paulo@89 11802 var points = wrap.selectAll('circle.nv-point')
paulo@89 11803 .data(function(data) {
paulo@89 11804 var yValues = data.map(function(d, i) { return getY(d,i); });
paulo@89 11805 function pointIndex(index) {
paulo@89 11806 if (index != -1) {
paulo@89 11807 var result = data[index];
paulo@89 11808 result.pointIndex = index;
paulo@89 11809 return result;
paulo@89 11810 } else {
paulo@89 11811 return null;
paulo@89 11812 }
paulo@89 11813 }
paulo@89 11814 var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
paulo@89 11815 minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
paulo@89 11816 currentPoint = pointIndex(yValues.length - 1);
paulo@89 11817 return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
paulo@89 11818 });
paulo@89 11819 points.enter().append('circle');
paulo@89 11820 points.exit().remove();
paulo@89 11821 points
paulo@89 11822 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
paulo@89 11823 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
paulo@89 11824 .attr('r', 2)
paulo@89 11825 .attr('class', function(d,i) {
paulo@89 11826 return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
paulo@89 11827 getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
paulo@89 11828 });
paulo@89 11829 });
paulo@89 11830
paulo@89 11831 return chart;
paulo@89 11832 }
paulo@89 11833
paulo@89 11834 //============================================================
paulo@89 11835 // Expose Public Variables
paulo@89 11836 //------------------------------------------------------------
paulo@89 11837
paulo@89 11838 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 11839
paulo@89 11840 chart._options = Object.create({}, {
paulo@89 11841 // simple options, just get/set the necessary values
paulo@89 11842 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 11843 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 11844 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
paulo@89 11845 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
paulo@89 11846 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
paulo@89 11847 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
paulo@89 11848 xScale: {get: function(){return x;}, set: function(_){x=_;}},
paulo@89 11849 yScale: {get: function(){return y;}, set: function(_){y=_;}},
paulo@89 11850 animate: {get: function(){return animate;}, set: function(_){animate=_;}},
paulo@89 11851
paulo@89 11852 //functor options
paulo@89 11853 x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
paulo@89 11854 y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
paulo@89 11855
paulo@89 11856 // options that require extra logic in the setter
paulo@89 11857 margin: {get: function(){return margin;}, set: function(_){
paulo@89 11858 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 11859 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 11860 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 11861 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 11862 }},
paulo@89 11863 color: {get: function(){return color;}, set: function(_){
paulo@89 11864 color = nv.utils.getColor(_);
paulo@89 11865 }}
paulo@89 11866 });
paulo@89 11867
paulo@89 11868 nv.utils.initOptions(chart);
paulo@89 11869 return chart;
paulo@89 11870 };
paulo@89 11871
paulo@89 11872 nv.models.sparklinePlus = function() {
paulo@89 11873 "use strict";
paulo@89 11874
paulo@89 11875 //============================================================
paulo@89 11876 // Public Variables with Default Settings
paulo@89 11877 //------------------------------------------------------------
paulo@89 11878
paulo@89 11879 var sparkline = nv.models.sparkline();
paulo@89 11880
paulo@89 11881 var margin = {top: 15, right: 100, bottom: 10, left: 50}
paulo@89 11882 , width = null
paulo@89 11883 , height = null
paulo@89 11884 , x
paulo@89 11885 , y
paulo@89 11886 , index = []
paulo@89 11887 , paused = false
paulo@89 11888 , xTickFormat = d3.format(',r')
paulo@89 11889 , yTickFormat = d3.format(',.2f')
paulo@89 11890 , showLastValue = true
paulo@89 11891 , alignValue = true
paulo@89 11892 , rightAlignValue = false
paulo@89 11893 , noData = null
paulo@89 11894 ;
paulo@89 11895
paulo@89 11896 function chart(selection) {
paulo@89 11897 selection.each(function(data) {
paulo@89 11898 var container = d3.select(this);
paulo@89 11899 nv.utils.initSVG(container);
paulo@89 11900
paulo@89 11901 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 11902 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 11903
paulo@89 11904 chart.update = function() { container.call(chart); };
paulo@89 11905 chart.container = this;
paulo@89 11906
paulo@89 11907 // Display No Data message if there's nothing to show.
paulo@89 11908 if (!data || !data.length) {
paulo@89 11909 nv.utils.noData(chart, container)
paulo@89 11910 return chart;
paulo@89 11911 } else {
paulo@89 11912 container.selectAll('.nv-noData').remove();
paulo@89 11913 }
paulo@89 11914
paulo@89 11915 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
paulo@89 11916
paulo@89 11917 // Setup Scales
paulo@89 11918 x = sparkline.xScale();
paulo@89 11919 y = sparkline.yScale();
paulo@89 11920
paulo@89 11921 // Setup containers and skeleton of chart
paulo@89 11922 var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
paulo@89 11923 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
paulo@89 11924 var gEnter = wrapEnter.append('g');
paulo@89 11925 var g = wrap.select('g');
paulo@89 11926
paulo@89 11927 gEnter.append('g').attr('class', 'nv-sparklineWrap');
paulo@89 11928 gEnter.append('g').attr('class', 'nv-valueWrap');
paulo@89 11929 gEnter.append('g').attr('class', 'nv-hoverArea');
paulo@89 11930
paulo@89 11931 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 11932
paulo@89 11933 // Main Chart Component(s)
paulo@89 11934 var sparklineWrap = g.select('.nv-sparklineWrap');
paulo@89 11935
paulo@89 11936 sparkline.width(availableWidth).height(availableHeight);
paulo@89 11937 sparklineWrap.call(sparkline);
paulo@89 11938
paulo@89 11939 if (showLastValue) {
paulo@89 11940 var valueWrap = g.select('.nv-valueWrap');
paulo@89 11941 var value = valueWrap.selectAll('.nv-currentValue')
paulo@89 11942 .data([currentValue]);
paulo@89 11943
paulo@89 11944 value.enter().append('text').attr('class', 'nv-currentValue')
paulo@89 11945 .attr('dx', rightAlignValue ? -8 : 8)
paulo@89 11946 .attr('dy', '.9em')
paulo@89 11947 .style('text-anchor', rightAlignValue ? 'end' : 'start');
paulo@89 11948
paulo@89 11949 value
paulo@89 11950 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
paulo@89 11951 .attr('y', alignValue ? function (d) {
paulo@89 11952 return y(d)
paulo@89 11953 } : 0)
paulo@89 11954 .style('fill', sparkline.color()(data[data.length - 1], data.length - 1))
paulo@89 11955 .text(yTickFormat(currentValue));
paulo@89 11956 }
paulo@89 11957
paulo@89 11958 gEnter.select('.nv-hoverArea').append('rect')
paulo@89 11959 .on('mousemove', sparklineHover)
paulo@89 11960 .on('click', function() { paused = !paused })
paulo@89 11961 .on('mouseout', function() { index = []; updateValueLine(); });
paulo@89 11962
paulo@89 11963 g.select('.nv-hoverArea rect')
paulo@89 11964 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
paulo@89 11965 .attr('width', availableWidth + margin.left + margin.right)
paulo@89 11966 .attr('height', availableHeight + margin.top);
paulo@89 11967
paulo@89 11968 //index is currently global (within the chart), may or may not keep it that way
paulo@89 11969 function updateValueLine() {
paulo@89 11970 if (paused) return;
paulo@89 11971
paulo@89 11972 var hoverValue = g.selectAll('.nv-hoverValue').data(index);
paulo@89 11973
paulo@89 11974 var hoverEnter = hoverValue.enter()
paulo@89 11975 .append('g').attr('class', 'nv-hoverValue')
paulo@89 11976 .style('stroke-opacity', 0)
paulo@89 11977 .style('fill-opacity', 0);
paulo@89 11978
paulo@89 11979 hoverValue.exit()
paulo@89 11980 .transition().duration(250)
paulo@89 11981 .style('stroke-opacity', 0)
paulo@89 11982 .style('fill-opacity', 0)
paulo@89 11983 .remove();
paulo@89 11984
paulo@89 11985 hoverValue
paulo@89 11986 .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
paulo@89 11987 .transition().duration(250)
paulo@89 11988 .style('stroke-opacity', 1)
paulo@89 11989 .style('fill-opacity', 1);
paulo@89 11990
paulo@89 11991 if (!index.length) return;
paulo@89 11992
paulo@89 11993 hoverEnter.append('line')
paulo@89 11994 .attr('x1', 0)
paulo@89 11995 .attr('y1', -margin.top)
paulo@89 11996 .attr('x2', 0)
paulo@89 11997 .attr('y2', availableHeight);
paulo@89 11998
paulo@89 11999 hoverEnter.append('text').attr('class', 'nv-xValue')
paulo@89 12000 .attr('x', -6)
paulo@89 12001 .attr('y', -margin.top)
paulo@89 12002 .attr('text-anchor', 'end')
paulo@89 12003 .attr('dy', '.9em');
paulo@89 12004
paulo@89 12005 g.select('.nv-hoverValue .nv-xValue')
paulo@89 12006 .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
paulo@89 12007
paulo@89 12008 hoverEnter.append('text').attr('class', 'nv-yValue')
paulo@89 12009 .attr('x', 6)
paulo@89 12010 .attr('y', -margin.top)
paulo@89 12011 .attr('text-anchor', 'start')
paulo@89 12012 .attr('dy', '.9em');
paulo@89 12013
paulo@89 12014 g.select('.nv-hoverValue .nv-yValue')
paulo@89 12015 .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
paulo@89 12016 }
paulo@89 12017
paulo@89 12018 function sparklineHover() {
paulo@89 12019 if (paused) return;
paulo@89 12020
paulo@89 12021 var pos = d3.mouse(this)[0] - margin.left;
paulo@89 12022
paulo@89 12023 function getClosestIndex(data, x) {
paulo@89 12024 var distance = Math.abs(sparkline.x()(data[0], 0) - x);
paulo@89 12025 var closestIndex = 0;
paulo@89 12026 for (var i = 0; i < data.length; i++){
paulo@89 12027 if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
paulo@89 12028 distance = Math.abs(sparkline.x()(data[i], i) - x);
paulo@89 12029 closestIndex = i;
paulo@89 12030 }
paulo@89 12031 }
paulo@89 12032 return closestIndex;
paulo@89 12033 }
paulo@89 12034
paulo@89 12035 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
paulo@89 12036 updateValueLine();
paulo@89 12037 }
paulo@89 12038
paulo@89 12039 });
paulo@89 12040
paulo@89 12041 return chart;
paulo@89 12042 }
paulo@89 12043
paulo@89 12044 //============================================================
paulo@89 12045 // Expose Public Variables
paulo@89 12046 //------------------------------------------------------------
paulo@89 12047
paulo@89 12048 // expose chart's sub-components
paulo@89 12049 chart.sparkline = sparkline;
paulo@89 12050
paulo@89 12051 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 12052
paulo@89 12053 chart._options = Object.create({}, {
paulo@89 12054 // simple options, just get/set the necessary values
paulo@89 12055 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 12056 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 12057 xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
paulo@89 12058 yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
paulo@89 12059 showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}},
paulo@89 12060 alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
paulo@89 12061 rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
paulo@89 12062 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 12063
paulo@89 12064 // options that require extra logic in the setter
paulo@89 12065 margin: {get: function(){return margin;}, set: function(_){
paulo@89 12066 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 12067 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 12068 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 12069 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 12070 }}
paulo@89 12071 });
paulo@89 12072
paulo@89 12073 nv.utils.inheritOptions(chart, sparkline);
paulo@89 12074 nv.utils.initOptions(chart);
paulo@89 12075
paulo@89 12076 return chart;
paulo@89 12077 };
paulo@89 12078
paulo@89 12079 nv.models.stackedArea = function() {
paulo@89 12080 "use strict";
paulo@89 12081
paulo@89 12082 //============================================================
paulo@89 12083 // Public Variables with Default Settings
paulo@89 12084 //------------------------------------------------------------
paulo@89 12085
paulo@89 12086 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 12087 , width = 960
paulo@89 12088 , height = 500
paulo@89 12089 , color = nv.utils.defaultColor() // a function that computes the color
paulo@89 12090 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
paulo@89 12091 , container = null
paulo@89 12092 , getX = function(d) { return d.x } // accessor to get the x value from a data point
paulo@89 12093 , getY = function(d) { return d.y } // accessor to get the y value from a data point
paulo@89 12094 , style = 'stack'
paulo@89 12095 , offset = 'zero'
paulo@89 12096 , order = 'default'
paulo@89 12097 , interpolate = 'linear' // controls the line interpolation
paulo@89 12098 , clipEdge = false // if true, masks lines within x and y scale
paulo@89 12099 , x //can be accessed via chart.xScale()
paulo@89 12100 , y //can be accessed via chart.yScale()
paulo@89 12101 , scatter = nv.models.scatter()
paulo@89 12102 , duration = 250
paulo@89 12103 , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
paulo@89 12104 ;
paulo@89 12105
paulo@89 12106 scatter
paulo@89 12107 .pointSize(2.2) // default size
paulo@89 12108 .pointDomain([2.2, 2.2]) // all the same size by default
paulo@89 12109 ;
paulo@89 12110
paulo@89 12111 /************************************
paulo@89 12112 * offset:
paulo@89 12113 * 'wiggle' (stream)
paulo@89 12114 * 'zero' (stacked)
paulo@89 12115 * 'expand' (normalize to 100%)
paulo@89 12116 * 'silhouette' (simple centered)
paulo@89 12117 *
paulo@89 12118 * order:
paulo@89 12119 * 'inside-out' (stream)
paulo@89 12120 * 'default' (input order)
paulo@89 12121 ************************************/
paulo@89 12122
paulo@89 12123 var renderWatch = nv.utils.renderWatch(dispatch, duration);
paulo@89 12124
paulo@89 12125 function chart(selection) {
paulo@89 12126 renderWatch.reset();
paulo@89 12127 renderWatch.models(scatter);
paulo@89 12128 selection.each(function(data) {
paulo@89 12129 var availableWidth = width - margin.left - margin.right,
paulo@89 12130 availableHeight = height - margin.top - margin.bottom;
paulo@89 12131
paulo@89 12132 container = d3.select(this);
paulo@89 12133 nv.utils.initSVG(container);
paulo@89 12134
paulo@89 12135 // Setup Scales
paulo@89 12136 x = scatter.xScale();
paulo@89 12137 y = scatter.yScale();
paulo@89 12138
paulo@89 12139 var dataRaw = data;
paulo@89 12140 // Injecting point index into each point because d3.layout.stack().out does not give index
paulo@89 12141 data.forEach(function(aseries, i) {
paulo@89 12142 aseries.seriesIndex = i;
paulo@89 12143 aseries.values = aseries.values.map(function(d, j) {
paulo@89 12144 d.index = j;
paulo@89 12145 d.seriesIndex = i;
paulo@89 12146 return d;
paulo@89 12147 });
paulo@89 12148 });
paulo@89 12149
paulo@89 12150 var dataFiltered = data.filter(function(series) {
paulo@89 12151 return !series.disabled;
paulo@89 12152 });
paulo@89 12153
paulo@89 12154 data = d3.layout.stack()
paulo@89 12155 .order(order)
paulo@89 12156 .offset(offset)
paulo@89 12157 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
paulo@89 12158 .x(getX)
paulo@89 12159 .y(getY)
paulo@89 12160 .out(function(d, y0, y) {
paulo@89 12161 d.display = {
paulo@89 12162 y: y,
paulo@89 12163 y0: y0
paulo@89 12164 };
paulo@89 12165 })
paulo@89 12166 (dataFiltered);
paulo@89 12167
paulo@89 12168 // Setup containers and skeleton of chart
paulo@89 12169 var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
paulo@89 12170 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
paulo@89 12171 var defsEnter = wrapEnter.append('defs');
paulo@89 12172 var gEnter = wrapEnter.append('g');
paulo@89 12173 var g = wrap.select('g');
paulo@89 12174
paulo@89 12175 gEnter.append('g').attr('class', 'nv-areaWrap');
paulo@89 12176 gEnter.append('g').attr('class', 'nv-scatterWrap');
paulo@89 12177
paulo@89 12178 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 12179
paulo@89 12180 // If the user has not specified forceY, make sure 0 is included in the domain
paulo@89 12181 // Otherwise, use user-specified values for forceY
paulo@89 12182 if (scatter.forceY().length == 0) {
paulo@89 12183 scatter.forceY().push(0);
paulo@89 12184 }
paulo@89 12185
paulo@89 12186 scatter
paulo@89 12187 .width(availableWidth)
paulo@89 12188 .height(availableHeight)
paulo@89 12189 .x(getX)
paulo@89 12190 .y(function(d) { return d.display.y + d.display.y0 })
paulo@89 12191 .forceY([0])
paulo@89 12192 .color(data.map(function(d,i) {
paulo@89 12193 return d.color || color(d, d.seriesIndex);
paulo@89 12194 }));
paulo@89 12195
paulo@89 12196 var scatterWrap = g.select('.nv-scatterWrap')
paulo@89 12197 .datum(data);
paulo@89 12198
paulo@89 12199 scatterWrap.call(scatter);
paulo@89 12200
paulo@89 12201 defsEnter.append('clipPath')
paulo@89 12202 .attr('id', 'nv-edge-clip-' + id)
paulo@89 12203 .append('rect');
paulo@89 12204
paulo@89 12205 wrap.select('#nv-edge-clip-' + id + ' rect')
paulo@89 12206 .attr('width', availableWidth)
paulo@89 12207 .attr('height', availableHeight);
paulo@89 12208
paulo@89 12209 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
paulo@89 12210
paulo@89 12211 var area = d3.svg.area()
paulo@89 12212 .x(function(d,i) { return x(getX(d,i)) })
paulo@89 12213 .y0(function(d) {
paulo@89 12214 return y(d.display.y0)
paulo@89 12215 })
paulo@89 12216 .y1(function(d) {
paulo@89 12217 return y(d.display.y + d.display.y0)
paulo@89 12218 })
paulo@89 12219 .interpolate(interpolate);
paulo@89 12220
paulo@89 12221 var zeroArea = d3.svg.area()
paulo@89 12222 .x(function(d,i) { return x(getX(d,i)) })
paulo@89 12223 .y0(function(d) { return y(d.display.y0) })
paulo@89 12224 .y1(function(d) { return y(d.display.y0) });
paulo@89 12225
paulo@89 12226 var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
paulo@89 12227 .data(function(d) { return d });
paulo@89 12228
paulo@89 12229 path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
paulo@89 12230 .attr('d', function(d,i){
paulo@89 12231 return zeroArea(d.values, d.seriesIndex);
paulo@89 12232 })
paulo@89 12233 .on('mouseover', function(d,i) {
paulo@89 12234 d3.select(this).classed('hover', true);
paulo@89 12235 dispatch.areaMouseover({
paulo@89 12236 point: d,
paulo@89 12237 series: d.key,
paulo@89 12238 pos: [d3.event.pageX, d3.event.pageY],
paulo@89 12239 seriesIndex: d.seriesIndex
paulo@89 12240 });
paulo@89 12241 })
paulo@89 12242 .on('mouseout', function(d,i) {
paulo@89 12243 d3.select(this).classed('hover', false);
paulo@89 12244 dispatch.areaMouseout({
paulo@89 12245 point: d,
paulo@89 12246 series: d.key,
paulo@89 12247 pos: [d3.event.pageX, d3.event.pageY],
paulo@89 12248 seriesIndex: d.seriesIndex
paulo@89 12249 });
paulo@89 12250 })
paulo@89 12251 .on('click', function(d,i) {
paulo@89 12252 d3.select(this).classed('hover', false);
paulo@89 12253 dispatch.areaClick({
paulo@89 12254 point: d,
paulo@89 12255 series: d.key,
paulo@89 12256 pos: [d3.event.pageX, d3.event.pageY],
paulo@89 12257 seriesIndex: d.seriesIndex
paulo@89 12258 });
paulo@89 12259 });
paulo@89 12260
paulo@89 12261 path.exit().remove();
paulo@89 12262 path.style('fill', function(d,i){
paulo@89 12263 return d.color || color(d, d.seriesIndex)
paulo@89 12264 })
paulo@89 12265 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
paulo@89 12266 path.watchTransition(renderWatch,'stackedArea path')
paulo@89 12267 .attr('d', function(d,i) {
paulo@89 12268 return area(d.values,i)
paulo@89 12269 });
paulo@89 12270
paulo@89 12271 //============================================================
paulo@89 12272 // Event Handling/Dispatching (in chart's scope)
paulo@89 12273 //------------------------------------------------------------
paulo@89 12274
paulo@89 12275 scatter.dispatch.on('elementMouseover.area', function(e) {
paulo@89 12276 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
paulo@89 12277 });
paulo@89 12278 scatter.dispatch.on('elementMouseout.area', function(e) {
paulo@89 12279 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
paulo@89 12280 });
paulo@89 12281
paulo@89 12282 //Special offset functions
paulo@89 12283 chart.d3_stackedOffset_stackPercent = function(stackData) {
paulo@89 12284 var n = stackData.length, //How many series
paulo@89 12285 m = stackData[0].length, //how many points per series
paulo@89 12286 i,
paulo@89 12287 j,
paulo@89 12288 o,
paulo@89 12289 y0 = [];
paulo@89 12290
paulo@89 12291 for (j = 0; j < m; ++j) { //Looping through all points
paulo@89 12292 for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series
paulo@89 12293 o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time.
paulo@89 12294 }
paulo@89 12295
paulo@89 12296 if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0
paulo@89 12297 stackData[i][j][1] /= o;
paulo@89 12298 } else { //(total y value of all series at point in time i) == 0
paulo@89 12299 for (i = 0; i < n; i++) {
paulo@89 12300 stackData[i][j][1] = 0;
paulo@89 12301 }
paulo@89 12302 }
paulo@89 12303 }
paulo@89 12304 for (j = 0; j < m; ++j) y0[j] = 0;
paulo@89 12305 return y0;
paulo@89 12306 };
paulo@89 12307
paulo@89 12308 });
paulo@89 12309
paulo@89 12310 renderWatch.renderEnd('stackedArea immediate');
paulo@89 12311 return chart;
paulo@89 12312 }
paulo@89 12313
paulo@89 12314 //============================================================
paulo@89 12315 // Global getters and setters
paulo@89 12316 //------------------------------------------------------------
paulo@89 12317
paulo@89 12318 chart.dispatch = dispatch;
paulo@89 12319 chart.scatter = scatter;
paulo@89 12320
paulo@89 12321 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
paulo@89 12322 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
paulo@89 12323 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
paulo@89 12324
paulo@89 12325 chart.interpolate = function(_) {
paulo@89 12326 if (!arguments.length) return interpolate;
paulo@89 12327 interpolate = _;
paulo@89 12328 return chart;
paulo@89 12329 };
paulo@89 12330
paulo@89 12331 chart.duration = function(_) {
paulo@89 12332 if (!arguments.length) return duration;
paulo@89 12333 duration = _;
paulo@89 12334 renderWatch.reset(duration);
paulo@89 12335 scatter.duration(duration);
paulo@89 12336 return chart;
paulo@89 12337 };
paulo@89 12338
paulo@89 12339 chart.dispatch = dispatch;
paulo@89 12340 chart.scatter = scatter;
paulo@89 12341 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 12342
paulo@89 12343 chart._options = Object.create({}, {
paulo@89 12344 // simple options, just get/set the necessary values
paulo@89 12345 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 12346 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 12347 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
paulo@89 12348 offset: {get: function(){return offset;}, set: function(_){offset=_;}},
paulo@89 12349 order: {get: function(){return order;}, set: function(_){order=_;}},
paulo@89 12350 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
paulo@89 12351
paulo@89 12352 // simple functor options
paulo@89 12353 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
paulo@89 12354 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
paulo@89 12355
paulo@89 12356 // options that require extra logic in the setter
paulo@89 12357 margin: {get: function(){return margin;}, set: function(_){
paulo@89 12358 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 12359 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 12360 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 12361 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 12362 }},
paulo@89 12363 color: {get: function(){return color;}, set: function(_){
paulo@89 12364 color = nv.utils.getColor(_);
paulo@89 12365 }},
paulo@89 12366 style: {get: function(){return style;}, set: function(_){
paulo@89 12367 style = _;
paulo@89 12368 switch (style) {
paulo@89 12369 case 'stack':
paulo@89 12370 chart.offset('zero');
paulo@89 12371 chart.order('default');
paulo@89 12372 break;
paulo@89 12373 case 'stream':
paulo@89 12374 chart.offset('wiggle');
paulo@89 12375 chart.order('inside-out');
paulo@89 12376 break;
paulo@89 12377 case 'stream-center':
paulo@89 12378 chart.offset('silhouette');
paulo@89 12379 chart.order('inside-out');
paulo@89 12380 break;
paulo@89 12381 case 'expand':
paulo@89 12382 chart.offset('expand');
paulo@89 12383 chart.order('default');
paulo@89 12384 break;
paulo@89 12385 case 'stack_percent':
paulo@89 12386 chart.offset(chart.d3_stackedOffset_stackPercent);
paulo@89 12387 chart.order('default');
paulo@89 12388 break;
paulo@89 12389 }
paulo@89 12390 }},
paulo@89 12391 duration: {get: function(){return duration;}, set: function(_){
paulo@89 12392 duration = _;
paulo@89 12393 renderWatch.reset(duration);
paulo@89 12394 scatter.duration(duration);
paulo@89 12395 }}
paulo@89 12396 });
paulo@89 12397
paulo@89 12398 nv.utils.inheritOptions(chart, scatter);
paulo@89 12399 nv.utils.initOptions(chart);
paulo@89 12400
paulo@89 12401 return chart;
paulo@89 12402 };
paulo@89 12403
paulo@89 12404 nv.models.stackedAreaChart = function() {
paulo@89 12405 "use strict";
paulo@89 12406
paulo@89 12407 //============================================================
paulo@89 12408 // Public Variables with Default Settings
paulo@89 12409 //------------------------------------------------------------
paulo@89 12410
paulo@89 12411 var stacked = nv.models.stackedArea()
paulo@89 12412 , xAxis = nv.models.axis()
paulo@89 12413 , yAxis = nv.models.axis()
paulo@89 12414 , legend = nv.models.legend()
paulo@89 12415 , controls = nv.models.legend()
paulo@89 12416 , interactiveLayer = nv.interactiveGuideline()
paulo@89 12417 , tooltip = nv.models.tooltip()
paulo@89 12418 ;
paulo@89 12419
paulo@89 12420 var margin = {top: 30, right: 25, bottom: 50, left: 60}
paulo@89 12421 , width = null
paulo@89 12422 , height = null
paulo@89 12423 , color = nv.utils.defaultColor()
paulo@89 12424 , showControls = true
paulo@89 12425 , showLegend = true
paulo@89 12426 , showXAxis = true
paulo@89 12427 , showYAxis = true
paulo@89 12428 , rightAlignYAxis = false
paulo@89 12429 , useInteractiveGuideline = false
paulo@89 12430 , x //can be accessed via chart.xScale()
paulo@89 12431 , y //can be accessed via chart.yScale()
paulo@89 12432 , state = nv.utils.state()
paulo@89 12433 , defaultState = null
paulo@89 12434 , noData = null
paulo@89 12435 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
paulo@89 12436 , controlWidth = 250
paulo@89 12437 , controlOptions = ['Stacked','Stream','Expanded']
paulo@89 12438 , controlLabels = {}
paulo@89 12439 , duration = 250
paulo@89 12440 ;
paulo@89 12441
paulo@89 12442 state.style = stacked.style();
paulo@89 12443 xAxis.orient('bottom').tickPadding(7);
paulo@89 12444 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
paulo@89 12445
paulo@89 12446 tooltip
paulo@89 12447 .headerFormatter(function(d, i) {
paulo@89 12448 return xAxis.tickFormat()(d, i);
paulo@89 12449 })
paulo@89 12450 .valueFormatter(function(d, i) {
paulo@89 12451 return yAxis.tickFormat()(d, i);
paulo@89 12452 });
paulo@89 12453
paulo@89 12454 interactiveLayer.tooltip
paulo@89 12455 .headerFormatter(function(d, i) {
paulo@89 12456 return xAxis.tickFormat()(d, i);
paulo@89 12457 })
paulo@89 12458 .valueFormatter(function(d, i) {
paulo@89 12459 return yAxis.tickFormat()(d, i);
paulo@89 12460 });
paulo@89 12461
paulo@89 12462 var oldYTickFormat = null,
paulo@89 12463 oldValueFormatter = null;
paulo@89 12464
paulo@89 12465 controls.updateState(false);
paulo@89 12466
paulo@89 12467 //============================================================
paulo@89 12468 // Private Variables
paulo@89 12469 //------------------------------------------------------------
paulo@89 12470
paulo@89 12471 var renderWatch = nv.utils.renderWatch(dispatch);
paulo@89 12472 var style = stacked.style();
paulo@89 12473
paulo@89 12474 var stateGetter = function(data) {
paulo@89 12475 return function(){
paulo@89 12476 return {
paulo@89 12477 active: data.map(function(d) { return !d.disabled }),
paulo@89 12478 style: stacked.style()
paulo@89 12479 };
paulo@89 12480 }
paulo@89 12481 };
paulo@89 12482
paulo@89 12483 var stateSetter = function(data) {
paulo@89 12484 return function(state) {
paulo@89 12485 if (state.style !== undefined)
paulo@89 12486 style = state.style;
paulo@89 12487 if (state.active !== undefined)
paulo@89 12488 data.forEach(function(series,i) {
paulo@89 12489 series.disabled = !state.active[i];
paulo@89 12490 });
paulo@89 12491 }
paulo@89 12492 };
paulo@89 12493
paulo@89 12494 var percentFormatter = d3.format('%');
paulo@89 12495
paulo@89 12496 function chart(selection) {
paulo@89 12497 renderWatch.reset();
paulo@89 12498 renderWatch.models(stacked);
paulo@89 12499 if (showXAxis) renderWatch.models(xAxis);
paulo@89 12500 if (showYAxis) renderWatch.models(yAxis);
paulo@89 12501
paulo@89 12502 selection.each(function(data) {
paulo@89 12503 var container = d3.select(this),
paulo@89 12504 that = this;
paulo@89 12505 nv.utils.initSVG(container);
paulo@89 12506
paulo@89 12507 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 12508 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 12509
paulo@89 12510 chart.update = function() { container.transition().duration(duration).call(chart); };
paulo@89 12511 chart.container = this;
paulo@89 12512
paulo@89 12513 state
paulo@89 12514 .setter(stateSetter(data), chart.update)
paulo@89 12515 .getter(stateGetter(data))
paulo@89 12516 .update();
paulo@89 12517
paulo@89 12518 // DEPRECATED set state.disabled
paulo@89 12519 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 12520
paulo@89 12521 if (!defaultState) {
paulo@89 12522 var key;
paulo@89 12523 defaultState = {};
paulo@89 12524 for (key in state) {
paulo@89 12525 if (state[key] instanceof Array)
paulo@89 12526 defaultState[key] = state[key].slice(0);
paulo@89 12527 else
paulo@89 12528 defaultState[key] = state[key];
paulo@89 12529 }
paulo@89 12530 }
paulo@89 12531
paulo@89 12532 // Display No Data message if there's nothing to show.
paulo@89 12533 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
paulo@89 12534 nv.utils.noData(chart, container)
paulo@89 12535 return chart;
paulo@89 12536 } else {
paulo@89 12537 container.selectAll('.nv-noData').remove();
paulo@89 12538 }
paulo@89 12539
paulo@89 12540 // Setup Scales
paulo@89 12541 x = stacked.xScale();
paulo@89 12542 y = stacked.yScale();
paulo@89 12543
paulo@89 12544 // Setup containers and skeleton of chart
paulo@89 12545 var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
paulo@89 12546 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
paulo@89 12547 var g = wrap.select('g');
paulo@89 12548
paulo@89 12549 gEnter.append("rect").style("opacity",0);
paulo@89 12550 gEnter.append('g').attr('class', 'nv-x nv-axis');
paulo@89 12551 gEnter.append('g').attr('class', 'nv-y nv-axis');
paulo@89 12552 gEnter.append('g').attr('class', 'nv-stackedWrap');
paulo@89 12553 gEnter.append('g').attr('class', 'nv-legendWrap');
paulo@89 12554 gEnter.append('g').attr('class', 'nv-controlsWrap');
paulo@89 12555 gEnter.append('g').attr('class', 'nv-interactive');
paulo@89 12556
paulo@89 12557 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
paulo@89 12558
paulo@89 12559 // Legend
paulo@89 12560 if (showLegend) {
paulo@89 12561 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
paulo@89 12562
paulo@89 12563 legend.width(legendWidth);
paulo@89 12564 g.select('.nv-legendWrap').datum(data).call(legend);
paulo@89 12565
paulo@89 12566 if ( margin.top != legend.height()) {
paulo@89 12567 margin.top = legend.height();
paulo@89 12568 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 12569 }
paulo@89 12570
paulo@89 12571 g.select('.nv-legendWrap')
paulo@89 12572 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
paulo@89 12573 }
paulo@89 12574
paulo@89 12575 // Controls
paulo@89 12576 if (showControls) {
paulo@89 12577 var controlsData = [
paulo@89 12578 {
paulo@89 12579 key: controlLabels.stacked || 'Stacked',
paulo@89 12580 metaKey: 'Stacked',
paulo@89 12581 disabled: stacked.style() != 'stack',
paulo@89 12582 style: 'stack'
paulo@89 12583 },
paulo@89 12584 {
paulo@89 12585 key: controlLabels.stream || 'Stream',
paulo@89 12586 metaKey: 'Stream',
paulo@89 12587 disabled: stacked.style() != 'stream',
paulo@89 12588 style: 'stream'
paulo@89 12589 },
paulo@89 12590 {
paulo@89 12591 key: controlLabels.expanded || 'Expanded',
paulo@89 12592 metaKey: 'Expanded',
paulo@89 12593 disabled: stacked.style() != 'expand',
paulo@89 12594 style: 'expand'
paulo@89 12595 },
paulo@89 12596 {
paulo@89 12597 key: controlLabels.stack_percent || 'Stack %',
paulo@89 12598 metaKey: 'Stack_Percent',
paulo@89 12599 disabled: stacked.style() != 'stack_percent',
paulo@89 12600 style: 'stack_percent'
paulo@89 12601 }
paulo@89 12602 ];
paulo@89 12603
paulo@89 12604 controlWidth = (controlOptions.length/3) * 260;
paulo@89 12605 controlsData = controlsData.filter(function(d) {
paulo@89 12606 return controlOptions.indexOf(d.metaKey) !== -1;
paulo@89 12607 });
paulo@89 12608
paulo@89 12609 controls
paulo@89 12610 .width( controlWidth )
paulo@89 12611 .color(['#444', '#444', '#444']);
paulo@89 12612
paulo@89 12613 g.select('.nv-controlsWrap')
paulo@89 12614 .datum(controlsData)
paulo@89 12615 .call(controls);
paulo@89 12616
paulo@89 12617 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
paulo@89 12618 margin.top = Math.max(controls.height(), legend.height());
paulo@89 12619 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 12620 }
paulo@89 12621
paulo@89 12622 g.select('.nv-controlsWrap')
paulo@89 12623 .attr('transform', 'translate(0,' + (-margin.top) +')');
paulo@89 12624 }
paulo@89 12625
paulo@89 12626 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 12627
paulo@89 12628 if (rightAlignYAxis) {
paulo@89 12629 g.select(".nv-y.nv-axis")
paulo@89 12630 .attr("transform", "translate(" + availableWidth + ",0)");
paulo@89 12631 }
paulo@89 12632
paulo@89 12633 //Set up interactive layer
paulo@89 12634 if (useInteractiveGuideline) {
paulo@89 12635 interactiveLayer
paulo@89 12636 .width(availableWidth)
paulo@89 12637 .height(availableHeight)
paulo@89 12638 .margin({left: margin.left, top: margin.top})
paulo@89 12639 .svgContainer(container)
paulo@89 12640 .xScale(x);
paulo@89 12641 wrap.select(".nv-interactive").call(interactiveLayer);
paulo@89 12642 }
paulo@89 12643
paulo@89 12644 stacked
paulo@89 12645 .width(availableWidth)
paulo@89 12646 .height(availableHeight);
paulo@89 12647
paulo@89 12648 var stackedWrap = g.select('.nv-stackedWrap')
paulo@89 12649 .datum(data);
paulo@89 12650
paulo@89 12651 stackedWrap.transition().call(stacked);
paulo@89 12652
paulo@89 12653 // Setup Axes
paulo@89 12654 if (showXAxis) {
paulo@89 12655 xAxis.scale(x)
paulo@89 12656 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
paulo@89 12657 .tickSize( -availableHeight, 0);
paulo@89 12658
paulo@89 12659 g.select('.nv-x.nv-axis')
paulo@89 12660 .attr('transform', 'translate(0,' + availableHeight + ')');
paulo@89 12661
paulo@89 12662 g.select('.nv-x.nv-axis')
paulo@89 12663 .transition().duration(0)
paulo@89 12664 .call(xAxis);
paulo@89 12665 }
paulo@89 12666
paulo@89 12667 if (showYAxis) {
paulo@89 12668 var ticks;
paulo@89 12669 if (stacked.offset() === 'wiggle') {
paulo@89 12670 ticks = 0;
paulo@89 12671 }
paulo@89 12672 else {
paulo@89 12673 ticks = nv.utils.calcTicksY(availableHeight/36, data);
paulo@89 12674 }
paulo@89 12675 yAxis.scale(y)
paulo@89 12676 ._ticks(ticks)
paulo@89 12677 .tickSize(-availableWidth, 0);
paulo@89 12678
paulo@89 12679 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
paulo@89 12680 var currentFormat = yAxis.tickFormat();
paulo@89 12681
paulo@89 12682 if ( !oldYTickFormat || currentFormat !== percentFormatter )
paulo@89 12683 oldYTickFormat = currentFormat;
paulo@89 12684
paulo@89 12685 //Forces the yAxis to use percentage in 'expand' mode.
paulo@89 12686 yAxis.tickFormat(percentFormatter);
paulo@89 12687 }
paulo@89 12688 else {
paulo@89 12689 if (oldYTickFormat) {
paulo@89 12690 yAxis.tickFormat(oldYTickFormat);
paulo@89 12691 oldYTickFormat = null;
paulo@89 12692 }
paulo@89 12693 }
paulo@89 12694
paulo@89 12695 g.select('.nv-y.nv-axis')
paulo@89 12696 .transition().duration(0)
paulo@89 12697 .call(yAxis);
paulo@89 12698 }
paulo@89 12699
paulo@89 12700 //============================================================
paulo@89 12701 // Event Handling/Dispatching (in chart's scope)
paulo@89 12702 //------------------------------------------------------------
paulo@89 12703
paulo@89 12704 stacked.dispatch.on('areaClick.toggle', function(e) {
paulo@89 12705 if (data.filter(function(d) { return !d.disabled }).length === 1)
paulo@89 12706 data.forEach(function(d) {
paulo@89 12707 d.disabled = false;
paulo@89 12708 });
paulo@89 12709 else
paulo@89 12710 data.forEach(function(d,i) {
paulo@89 12711 d.disabled = (i != e.seriesIndex);
paulo@89 12712 });
paulo@89 12713
paulo@89 12714 state.disabled = data.map(function(d) { return !!d.disabled });
paulo@89 12715 dispatch.stateChange(state);
paulo@89 12716
paulo@89 12717 chart.update();
paulo@89 12718 });
paulo@89 12719
paulo@89 12720 legend.dispatch.on('stateChange', function(newState) {
paulo@89 12721 for (var key in newState)
paulo@89 12722 state[key] = newState[key];
paulo@89 12723 dispatch.stateChange(state);
paulo@89 12724 chart.update();
paulo@89 12725 });
paulo@89 12726
paulo@89 12727 controls.dispatch.on('legendClick', function(d,i) {
paulo@89 12728 if (!d.disabled) return;
paulo@89 12729
paulo@89 12730 controlsData = controlsData.map(function(s) {
paulo@89 12731 s.disabled = true;
paulo@89 12732 return s;
paulo@89 12733 });
paulo@89 12734 d.disabled = false;
paulo@89 12735
paulo@89 12736 stacked.style(d.style);
paulo@89 12737
paulo@89 12738
paulo@89 12739 state.style = stacked.style();
paulo@89 12740 dispatch.stateChange(state);
paulo@89 12741
paulo@89 12742 chart.update();
paulo@89 12743 });
paulo@89 12744
paulo@89 12745 interactiveLayer.dispatch.on('elementMousemove', function(e) {
paulo@89 12746 stacked.clearHighlights();
paulo@89 12747 var singlePoint, pointIndex, pointXLocation, allData = [];
paulo@89 12748 data
paulo@89 12749 .filter(function(series, i) {
paulo@89 12750 series.seriesIndex = i;
paulo@89 12751 return !series.disabled;
paulo@89 12752 })
paulo@89 12753 .forEach(function(series,i) {
paulo@89 12754 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
paulo@89 12755 var point = series.values[pointIndex];
paulo@89 12756 var pointYValue = chart.y()(point, pointIndex);
paulo@89 12757 if (pointYValue != null) {
paulo@89 12758 stacked.highlightPoint(i, pointIndex, true);
paulo@89 12759 }
paulo@89 12760 if (typeof point === 'undefined') return;
paulo@89 12761 if (typeof singlePoint === 'undefined') singlePoint = point;
paulo@89 12762 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
paulo@89 12763
paulo@89 12764 //If we are in 'expand' mode, use the stacked percent value instead of raw value.
paulo@89 12765 var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
paulo@89 12766 allData.push({
paulo@89 12767 key: series.key,
paulo@89 12768 value: tooltipValue,
paulo@89 12769 color: color(series,series.seriesIndex),
paulo@89 12770 stackedValue: point.display
paulo@89 12771 });
paulo@89 12772 });
paulo@89 12773
paulo@89 12774 allData.reverse();
paulo@89 12775
paulo@89 12776 //Highlight the tooltip entry based on which stack the mouse is closest to.
paulo@89 12777 if (allData.length > 2) {
paulo@89 12778 var yValue = chart.yScale().invert(e.mouseY);
paulo@89 12779 var yDistMax = Infinity, indexToHighlight = null;
paulo@89 12780 allData.forEach(function(series,i) {
paulo@89 12781
paulo@89 12782 //To handle situation where the stacked area chart is negative, we need to use absolute values
paulo@89 12783 //when checking if the mouse Y value is within the stack area.
paulo@89 12784 yValue = Math.abs(yValue);
paulo@89 12785 var stackedY0 = Math.abs(series.stackedValue.y0);
paulo@89 12786 var stackedY = Math.abs(series.stackedValue.y);
paulo@89 12787 if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
paulo@89 12788 {
paulo@89 12789 indexToHighlight = i;
paulo@89 12790 return;
paulo@89 12791 }
paulo@89 12792 });
paulo@89 12793 if (indexToHighlight != null)
paulo@89 12794 allData[indexToHighlight].highlight = true;
paulo@89 12795 }
paulo@89 12796
paulo@89 12797 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
paulo@89 12798
paulo@89 12799 var valueFormatter = interactiveLayer.tooltip.valueFormatter();
paulo@89 12800 // Keeps track of the tooltip valueFormatter if the chart changes to expanded view
paulo@89 12801 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
paulo@89 12802 if ( !oldValueFormatter ) {
paulo@89 12803 oldValueFormatter = valueFormatter;
paulo@89 12804 }
paulo@89 12805 //Forces the tooltip to use percentage in 'expand' mode.
paulo@89 12806 valueFormatter = d3.format(".1%");
paulo@89 12807 }
paulo@89 12808 else {
paulo@89 12809 if (oldValueFormatter) {
paulo@89 12810 valueFormatter = oldValueFormatter;
paulo@89 12811 oldValueFormatter = null;
paulo@89 12812 }
paulo@89 12813 }
paulo@89 12814
paulo@89 12815 interactiveLayer.tooltip
paulo@89 12816 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
paulo@89 12817 .chartContainer(that.parentNode)
paulo@89 12818 .valueFormatter(valueFormatter)
paulo@89 12819 .data(
paulo@89 12820 {
paulo@89 12821 value: xValue,
paulo@89 12822 series: allData
paulo@89 12823 }
paulo@89 12824 )();
paulo@89 12825
paulo@89 12826 interactiveLayer.renderGuideLine(pointXLocation);
paulo@89 12827
paulo@89 12828 });
paulo@89 12829
paulo@89 12830 interactiveLayer.dispatch.on("elementMouseout",function(e) {
paulo@89 12831 stacked.clearHighlights();
paulo@89 12832 });
paulo@89 12833
paulo@89 12834 // Update chart from a state object passed to event handler
paulo@89 12835 dispatch.on('changeState', function(e) {
paulo@89 12836
paulo@89 12837 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
paulo@89 12838 data.forEach(function(series,i) {
paulo@89 12839 series.disabled = e.disabled[i];
paulo@89 12840 });
paulo@89 12841
paulo@89 12842 state.disabled = e.disabled;
paulo@89 12843 }
paulo@89 12844
paulo@89 12845 if (typeof e.style !== 'undefined') {
paulo@89 12846 stacked.style(e.style);
paulo@89 12847 style = e.style;
paulo@89 12848 }
paulo@89 12849
paulo@89 12850 chart.update();
paulo@89 12851 });
paulo@89 12852
paulo@89 12853 });
paulo@89 12854
paulo@89 12855 renderWatch.renderEnd('stacked Area chart immediate');
paulo@89 12856 return chart;
paulo@89 12857 }
paulo@89 12858
paulo@89 12859 //============================================================
paulo@89 12860 // Event Handling/Dispatching (out of chart's scope)
paulo@89 12861 //------------------------------------------------------------
paulo@89 12862
paulo@89 12863 stacked.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 12864 evt.point['x'] = stacked.x()(evt.point);
paulo@89 12865 evt.point['y'] = stacked.y()(evt.point);
paulo@89 12866 tooltip.data(evt).position(evt.pos).hidden(false);
paulo@89 12867 });
paulo@89 12868
paulo@89 12869 stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 12870 tooltip.hidden(true)
paulo@89 12871 });
paulo@89 12872
paulo@89 12873 //============================================================
paulo@89 12874 // Expose Public Variables
paulo@89 12875 //------------------------------------------------------------
paulo@89 12876
paulo@89 12877 // expose chart's sub-components
paulo@89 12878 chart.dispatch = dispatch;
paulo@89 12879 chart.stacked = stacked;
paulo@89 12880 chart.legend = legend;
paulo@89 12881 chart.controls = controls;
paulo@89 12882 chart.xAxis = xAxis;
paulo@89 12883 chart.yAxis = yAxis;
paulo@89 12884 chart.interactiveLayer = interactiveLayer;
paulo@89 12885 chart.tooltip = tooltip;
paulo@89 12886
paulo@89 12887 chart.dispatch = dispatch;
paulo@89 12888 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 12889
paulo@89 12890 chart._options = Object.create({}, {
paulo@89 12891 // simple options, just get/set the necessary values
paulo@89 12892 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 12893 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 12894 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
paulo@89 12895 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
paulo@89 12896 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
paulo@89 12897 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 12898 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 12899 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
paulo@89 12900 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
paulo@89 12901 controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}},
paulo@89 12902
paulo@89 12903 // deprecated options
paulo@89 12904 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
paulo@89 12905 // deprecated after 1.7.1
paulo@89 12906 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
paulo@89 12907 tooltip.enabled(!!_);
paulo@89 12908 }},
paulo@89 12909 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
paulo@89 12910 // deprecated after 1.7.1
paulo@89 12911 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
paulo@89 12912 tooltip.contentGenerator(_);
paulo@89 12913 }},
paulo@89 12914
paulo@89 12915 // options that require extra logic in the setter
paulo@89 12916 margin: {get: function(){return margin;}, set: function(_){
paulo@89 12917 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 12918 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 12919 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 12920 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 12921 }},
paulo@89 12922 duration: {get: function(){return duration;}, set: function(_){
paulo@89 12923 duration = _;
paulo@89 12924 renderWatch.reset(duration);
paulo@89 12925 stacked.duration(duration);
paulo@89 12926 xAxis.duration(duration);
paulo@89 12927 yAxis.duration(duration);
paulo@89 12928 }},
paulo@89 12929 color: {get: function(){return color;}, set: function(_){
paulo@89 12930 color = nv.utils.getColor(_);
paulo@89 12931 legend.color(color);
paulo@89 12932 stacked.color(color);
paulo@89 12933 }},
paulo@89 12934 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
paulo@89 12935 rightAlignYAxis = _;
paulo@89 12936 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
paulo@89 12937 }},
paulo@89 12938 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
paulo@89 12939 useInteractiveGuideline = !!_;
paulo@89 12940 chart.interactive(!_);
paulo@89 12941 chart.useVoronoi(!_);
paulo@89 12942 stacked.scatter.interactive(!_);
paulo@89 12943 }}
paulo@89 12944 });
paulo@89 12945
paulo@89 12946 nv.utils.inheritOptions(chart, stacked);
paulo@89 12947 nv.utils.initOptions(chart);
paulo@89 12948
paulo@89 12949 return chart;
paulo@89 12950 };
paulo@89 12951 // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
paulo@89 12952 nv.models.sunburst = function() {
paulo@89 12953 "use strict";
paulo@89 12954
paulo@89 12955 //============================================================
paulo@89 12956 // Public Variables with Default Settings
paulo@89 12957 //------------------------------------------------------------
paulo@89 12958
paulo@89 12959 var margin = {top: 0, right: 0, bottom: 0, left: 0}
paulo@89 12960 , width = null
paulo@89 12961 , height = null
paulo@89 12962 , mode = "count"
paulo@89 12963 , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }}
paulo@89 12964 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
paulo@89 12965 , container = null
paulo@89 12966 , color = nv.utils.defaultColor()
paulo@89 12967 , duration = 500
paulo@89 12968 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd')
paulo@89 12969 ;
paulo@89 12970
paulo@89 12971 var x = d3.scale.linear().range([0, 2 * Math.PI]);
paulo@89 12972 var y = d3.scale.sqrt();
paulo@89 12973
paulo@89 12974 var partition = d3.layout.partition()
paulo@89 12975 .sort(null)
paulo@89 12976 .value(function(d) { return 1; });
paulo@89 12977
paulo@89 12978 var arc = d3.svg.arc()
paulo@89 12979 .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
paulo@89 12980 .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
paulo@89 12981 .innerRadius(function(d) { return Math.max(0, y(d.y)); })
paulo@89 12982 .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
paulo@89 12983
paulo@89 12984 // Keep track of the current and previous node being displayed as the root.
paulo@89 12985 var node, prevNode;
paulo@89 12986 // Keep track of the root node
paulo@89 12987 var rootNode;
paulo@89 12988
paulo@89 12989 //============================================================
paulo@89 12990 // chart function
paulo@89 12991 //------------------------------------------------------------
paulo@89 12992
paulo@89 12993 var renderWatch = nv.utils.renderWatch(dispatch);
paulo@89 12994
paulo@89 12995 function chart(selection) {
paulo@89 12996 renderWatch.reset();
paulo@89 12997 selection.each(function(data) {
paulo@89 12998 container = d3.select(this);
paulo@89 12999 var availableWidth = nv.utils.availableWidth(width, container, margin);
paulo@89 13000 var availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 13001 var radius = Math.min(availableWidth, availableHeight) / 2;
paulo@89 13002 var path;
paulo@89 13003
paulo@89 13004 nv.utils.initSVG(container);
paulo@89 13005
paulo@89 13006 // Setup containers and skeleton of chart
paulo@89 13007 var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data);
paulo@89 13008 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id);
paulo@89 13009
paulo@89 13010 var g = wrapEnter.selectAll('nv-sunburst');
paulo@89 13011
paulo@89 13012 wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
paulo@89 13013
paulo@89 13014 container.on('click', function (d, i) {
paulo@89 13015 dispatch.chartClick({
paulo@89 13016 data: d,
paulo@89 13017 index: i,
paulo@89 13018 pos: d3.event,
paulo@89 13019 id: id
paulo@89 13020 });
paulo@89 13021 });
paulo@89 13022
paulo@89 13023 y.range([0, radius]);
paulo@89 13024
paulo@89 13025 node = node || data;
paulo@89 13026 rootNode = data[0];
paulo@89 13027 partition.value(modes[mode] || modes["count"]);
paulo@89 13028 path = g.data(partition.nodes).enter()
paulo@89 13029 .append("path")
paulo@89 13030 .attr("d", arc)
paulo@89 13031 .style("fill", function (d) {
paulo@89 13032 return color((d.children ? d : d.parent).name);
paulo@89 13033 })
paulo@89 13034 .style("stroke", "#FFF")
paulo@89 13035 .on("click", function(d) {
paulo@89 13036 if (prevNode !== node && node !== d) prevNode = node;
paulo@89 13037 node = d;
paulo@89 13038 path.transition()
paulo@89 13039 .duration(duration)
paulo@89 13040 .attrTween("d", arcTweenZoom(d));
paulo@89 13041 })
paulo@89 13042 .each(stash)
paulo@89 13043 .on("dblclick", function(d) {
paulo@89 13044 if (prevNode.parent == d) {
paulo@89 13045 path.transition()
paulo@89 13046 .duration(duration)
paulo@89 13047 .attrTween("d", arcTweenZoom(rootNode));
paulo@89 13048 }
paulo@89 13049 })
paulo@89 13050 .each(stash)
paulo@89 13051 .on('mouseover', function(d,i){
paulo@89 13052 d3.select(this).classed('hover', true).style('opacity', 0.8);
paulo@89 13053 dispatch.elementMouseover({
paulo@89 13054 data: d,
paulo@89 13055 color: d3.select(this).style("fill")
paulo@89 13056 });
paulo@89 13057 })
paulo@89 13058 .on('mouseout', function(d,i){
paulo@89 13059 d3.select(this).classed('hover', false).style('opacity', 1);
paulo@89 13060 dispatch.elementMouseout({
paulo@89 13061 data: d
paulo@89 13062 });
paulo@89 13063 })
paulo@89 13064 .on('mousemove', function(d,i){
paulo@89 13065 dispatch.elementMousemove({
paulo@89 13066 data: d
paulo@89 13067 });
paulo@89 13068 });
paulo@89 13069
paulo@89 13070
paulo@89 13071
paulo@89 13072 // Setup for switching data: stash the old values for transition.
paulo@89 13073 function stash(d) {
paulo@89 13074 d.x0 = d.x;
paulo@89 13075 d.dx0 = d.dx;
paulo@89 13076 }
paulo@89 13077
paulo@89 13078 // When switching data: interpolate the arcs in data space.
paulo@89 13079 function arcTweenData(a, i) {
paulo@89 13080 var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a);
paulo@89 13081
paulo@89 13082 function tween(t) {
paulo@89 13083 var b = oi(t);
paulo@89 13084 a.x0 = b.x;
paulo@89 13085 a.dx0 = b.dx;
paulo@89 13086 return arc(b);
paulo@89 13087 }
paulo@89 13088
paulo@89 13089 if (i == 0) {
paulo@89 13090 // If we are on the first arc, adjust the x domain to match the root node
paulo@89 13091 // at the current zoom level. (We only need to do this once.)
paulo@89 13092 var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]);
paulo@89 13093 return function (t) {
paulo@89 13094 x.domain(xd(t));
paulo@89 13095 return tween(t);
paulo@89 13096 };
paulo@89 13097 } else {
paulo@89 13098 return tween;
paulo@89 13099 }
paulo@89 13100 }
paulo@89 13101
paulo@89 13102 // When zooming: interpolate the scales.
paulo@89 13103 function arcTweenZoom(d) {
paulo@89 13104 var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
paulo@89 13105 yd = d3.interpolate(y.domain(), [d.y, 1]),
paulo@89 13106 yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
paulo@89 13107 return function (d, i) {
paulo@89 13108 return i
paulo@89 13109 ? function (t) {
paulo@89 13110 return arc(d);
paulo@89 13111 }
paulo@89 13112 : function (t) {
paulo@89 13113 x.domain(xd(t));
paulo@89 13114 y.domain(yd(t)).range(yr(t));
paulo@89 13115 return arc(d);
paulo@89 13116 };
paulo@89 13117 };
paulo@89 13118 }
paulo@89 13119
paulo@89 13120 });
paulo@89 13121
paulo@89 13122 renderWatch.renderEnd('sunburst immediate');
paulo@89 13123 return chart;
paulo@89 13124 }
paulo@89 13125
paulo@89 13126 //============================================================
paulo@89 13127 // Expose Public Variables
paulo@89 13128 //------------------------------------------------------------
paulo@89 13129
paulo@89 13130 chart.dispatch = dispatch;
paulo@89 13131 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 13132
paulo@89 13133 chart._options = Object.create({}, {
paulo@89 13134 // simple options, just get/set the necessary values
paulo@89 13135 width: {get: function(){return width;}, set: function(_){width=_;}},
paulo@89 13136 height: {get: function(){return height;}, set: function(_){height=_;}},
paulo@89 13137 mode: {get: function(){return mode;}, set: function(_){mode=_;}},
paulo@89 13138 id: {get: function(){return id;}, set: function(_){id=_;}},
paulo@89 13139 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
paulo@89 13140
paulo@89 13141 // options that require extra logic in the setter
paulo@89 13142 margin: {get: function(){return margin;}, set: function(_){
paulo@89 13143 margin.top = _.top != undefined ? _.top : margin.top;
paulo@89 13144 margin.right = _.right != undefined ? _.right : margin.right;
paulo@89 13145 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
paulo@89 13146 margin.left = _.left != undefined ? _.left : margin.left;
paulo@89 13147 }},
paulo@89 13148 color: {get: function(){return color;}, set: function(_){
paulo@89 13149 color=nv.utils.getColor(_);
paulo@89 13150 }}
paulo@89 13151 });
paulo@89 13152
paulo@89 13153 nv.utils.initOptions(chart);
paulo@89 13154 return chart;
paulo@89 13155 };
paulo@89 13156 nv.models.sunburstChart = function() {
paulo@89 13157 "use strict";
paulo@89 13158
paulo@89 13159 //============================================================
paulo@89 13160 // Public Variables with Default Settings
paulo@89 13161 //------------------------------------------------------------
paulo@89 13162
paulo@89 13163 var sunburst = nv.models.sunburst();
paulo@89 13164 var tooltip = nv.models.tooltip();
paulo@89 13165
paulo@89 13166 var margin = {top: 30, right: 20, bottom: 20, left: 20}
paulo@89 13167 , width = null
paulo@89 13168 , height = null
paulo@89 13169 , color = nv.utils.defaultColor()
paulo@89 13170 , id = Math.round(Math.random() * 100000)
paulo@89 13171 , defaultState = null
paulo@89 13172 , noData = null
paulo@89 13173 , duration = 250
paulo@89 13174 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
paulo@89 13175 ;
paulo@89 13176
paulo@89 13177 //============================================================
paulo@89 13178 // Private Variables
paulo@89 13179 //------------------------------------------------------------
paulo@89 13180
paulo@89 13181 var renderWatch = nv.utils.renderWatch(dispatch);
paulo@89 13182 tooltip.headerEnabled(false).duration(0).valueFormatter(function(d, i) {
paulo@89 13183 return d;
paulo@89 13184 });
paulo@89 13185
paulo@89 13186 //============================================================
paulo@89 13187 // Chart function
paulo@89 13188 //------------------------------------------------------------
paulo@89 13189
paulo@89 13190 function chart(selection) {
paulo@89 13191 renderWatch.reset();
paulo@89 13192 renderWatch.models(sunburst);
paulo@89 13193
paulo@89 13194 selection.each(function(data) {
paulo@89 13195 var container = d3.select(this);
paulo@89 13196 nv.utils.initSVG(container);
paulo@89 13197
paulo@89 13198 var that = this;
paulo@89 13199 var availableWidth = nv.utils.availableWidth(width, container, margin),
paulo@89 13200 availableHeight = nv.utils.availableHeight(height, container, margin);
paulo@89 13201
paulo@89 13202 chart.update = function() {
paulo@89 13203 if (duration === 0)
paulo@89 13204 container.call(chart);
paulo@89 13205 else
paulo@89 13206 container.transition().duration(duration).call(chart)
paulo@89 13207 };
paulo@89 13208 chart.container = this;
paulo@89 13209
paulo@89 13210 // Display No Data message if there's nothing to show.
paulo@89 13211 if (!data || !data.length) {
paulo@89 13212 nv.utils.noData(chart, container);
paulo@89 13213 return chart;
paulo@89 13214 } else {
paulo@89 13215 container.selectAll('.nv-noData').remove();
paulo@89 13216 }
paulo@89 13217
paulo@89 13218 // Setup containers and skeleton of chart
paulo@89 13219 var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data);
paulo@89 13220 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g');
paulo@89 13221 var g = wrap.select('g');
paulo@89 13222
paulo@89 13223 gEnter.append('g').attr('class', 'nv-sunburstWrap');
paulo@89 13224
paulo@89 13225 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
paulo@89 13226
paulo@89 13227 // Main Chart Component(s)
paulo@89 13228 sunburst.width(availableWidth).height(availableHeight);
paulo@89 13229 var sunWrap = g.select('.nv-sunburstWrap').datum(data);
paulo@89 13230 d3.transition(sunWrap).call(sunburst);
paulo@89 13231
paulo@89 13232 });
paulo@89 13233
paulo@89 13234 renderWatch.renderEnd('sunburstChart immediate');
paulo@89 13235 return chart;
paulo@89 13236 }
paulo@89 13237
paulo@89 13238 //============================================================
paulo@89 13239 // Event Handling/Dispatching (out of chart's scope)
paulo@89 13240 //------------------------------------------------------------
paulo@89 13241
paulo@89 13242 sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
paulo@89 13243 evt['series'] = {
paulo@89 13244 key: evt.data.name,
paulo@89 13245 value: evt.data.size,
paulo@89 13246 color: evt.color
paulo@89 13247 };
paulo@89 13248 tooltip.data(evt).hidden(false);
paulo@89 13249 });
paulo@89 13250
paulo@89 13251 sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
paulo@89 13252 tooltip.hidden(true);
paulo@89 13253 });
paulo@89 13254
paulo@89 13255 sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
paulo@89 13256 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
paulo@89 13257 });
paulo@89 13258
paulo@89 13259 //============================================================
paulo@89 13260 // Expose Public Variables
paulo@89 13261 //------------------------------------------------------------
paulo@89 13262
paulo@89 13263 // expose chart's sub-components
paulo@89 13264 chart.dispatch = dispatch;
paulo@89 13265 chart.sunburst = sunburst;
paulo@89 13266 chart.tooltip = tooltip;
paulo@89 13267 chart.options = nv.utils.optionsFunc.bind(chart);
paulo@89 13268
paulo@89 13269 // use Object get/set functionality to map between vars and chart functions
paulo@89 13270 chart._options = Object.create({}, {
paulo@89 13271 // simple options, just get/set the necessary values
paulo@89 13272 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
paulo@89 13273 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
paulo@89 13274
paulo@89 13275 // options that require extra logic in the setter
paulo@89 13276 color: {get: function(){return color;}, set: function(_){
paulo@89 13277 color = _;
paulo@89 13278 sunburst.color(color);
paulo@89 13279 }},
paulo@89 13280 duration: {get: function(){return duration;}, set: function(_){
paulo@89 13281 duration = _;
paulo@89 13282 renderWatch.reset(duration);
paulo@89 13283 sunburst.duration(duration);
paulo@89 13284 }},
paulo@89 13285 margin: {get: function(){return margin;}, set: function(_){
paulo@89 13286 margin.top = _.top !== undefined ? _.top : margin.top;
paulo@89 13287 margin.right = _.right !== undefined ? _.right : margin.right;
paulo@89 13288 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
paulo@89 13289 margin.left = _.left !== undefined ? _.left : margin.left;
paulo@89 13290 }}
paulo@89 13291 });
paulo@89 13292 nv.utils.inheritOptions(chart, sunburst);
paulo@89 13293 nv.utils.initOptions(chart);
paulo@89 13294 return chart;
paulo@89 13295 };
paulo@89 13296
paulo@89 13297 nv.version = "1.8.1";
paulo@89 13298 })();