├── scripts └── pre-commit.sh ├── web ├── ep │ ├── blank.gif │ └── list.js ├── img │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png ├── docs │ └── public │ │ └── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ └── novecento-bold.woff ├── examples │ ├── morley2.csv │ ├── pie.html │ ├── scatter.html │ ├── ord.html │ ├── line.html │ ├── bar.html │ ├── morley.csv │ ├── index.html │ ├── heat.html │ ├── morley3.csv │ ├── scatter-brushing.html │ ├── scatter-series.html │ ├── multi-scatter.html │ ├── box-plot-time.html │ ├── box-plot.html │ ├── composite.html │ ├── series.html │ ├── cust.html │ ├── number.html │ ├── right-axis.html │ └── heatmap-filtering.html ├── highlighter │ ├── shAutoloader.js │ ├── shBrushJScript.js │ ├── shBrushXml.js │ └── shThemeDefault.css ├── vc │ └── state2code.rb ├── crime │ └── filter_stats.rb ├── geo │ └── us-counties.json └── css │ └── dc.css ├── index.js ├── regression ├── inject-serializer.js └── stock-regression-test.js ├── src ├── errors.js ├── footer.js ├── logger.js ├── banner.js ├── events.js ├── margin-mixin.js ├── data-count.js ├── filters.js ├── utils.js ├── cap-mixin.js ├── color-mixin.js ├── number-display.js ├── bubble-chart.js ├── legend.js ├── series-chart.js ├── data-grid.js └── bubble-mixin.js ├── .gitignore ├── bower.json ├── .travis.yml ├── .jshintrc ├── .mailmap ├── spec ├── helpers │ ├── spec-helper.js │ ├── custom_matchers.js │ └── jasmine-jsreporter.js ├── event-spec.js ├── logger-spec.js ├── filter-dates-spec.js ├── color-spec.js ├── data-grid-spec.js ├── utils-spec.js ├── bubble-overlay-spec.js ├── series-chart-spec.js ├── data-addition-spec.js ├── filters-spec.js └── data-count-spec.js ├── AUTHORS ├── package.json ├── .jscsrc ├── README.md ├── CONTRIBUTING.md └── dc.css /scripts/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | grunt jshint 4 | -------------------------------------------------------------------------------- /web/ep/blank.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/ep/blank.gif -------------------------------------------------------------------------------- /web/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /web/docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /web/docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /web/docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /web/docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /web/docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /web/docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /web/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /web/docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /web/docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /web/docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tscanlin/dc.js/master/web/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Import DC and dependencies 2 | 3 | d3 = require("d3"); 4 | crossfilter = require("crossfilter"); 5 | module.exports = require("./dc"); 6 | -------------------------------------------------------------------------------- /regression/inject-serializer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | dc.disableTransitions = true; 3 | window.onload = function () { 4 | alert(JSON.stringify(['rendered', d3.select('html').node().innerHTML])); 5 | }; 6 | })(); 7 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | dc.errors = {}; 2 | 3 | dc.errors.Exception = function (msg) { 4 | var _msg = msg || 'Unexpected internal error'; 5 | 6 | this.message = _msg; 7 | 8 | this.toString = function () { 9 | return _msg; 10 | }; 11 | }; 12 | 13 | dc.errors.InvalidStateException = function () { 14 | dc.errors.Exception.apply(this, arguments); 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # tmp files 2 | *.log 3 | *~ 4 | 5 | # local files 6 | node_modules 7 | 8 | # ide files 9 | *.iml 10 | .idea 11 | 12 | # vim files 13 | .*.swp 14 | 15 | # mac 16 | .DS_Store 17 | 18 | # ctag 19 | .tags 20 | 21 | # files for gh-pages and grunt-contrib-jasmine 22 | .grunt 23 | 24 | # jasmine spec runner 25 | spec/index.html 26 | 27 | # coverage reports 28 | coverage 29 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dcjs", 3 | "version": "2.0.0-alpha.2", 4 | "main": ["dc.js", "dc.css"], 5 | "ignore": [ 6 | ".gitignore", 7 | ".travis.yml", 8 | "build.xml", 9 | "globals.js", 10 | "index.js", 11 | "make", 12 | "package.json", 13 | "test", 14 | "src" 15 | ], 16 | "dependencies": { 17 | "d3": "3.x", 18 | "crossfilter": "1.x" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | script: 5 | - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then grunt ci; else grunt ci-pull; fi' 6 | env: 7 | global: 8 | - secure: Kt+5IJDJRVwr28xRnmR5YDsJceXDcDR21/JUBfk6DYFixPbIq7LCPnZUmiSZQs8akU95ucXwB5hsirUAdEhXdYKilec6go70lticVlZBLy8IdJ+Di1uPwMOeMHvalC2P0woIRJSMzP8u5E+e+5ASggTjsXID7/p1rE0jXtoOueQ= 9 | - secure: TED5eLMxEsyIGzKP8xxhyRDbKlIX9POzj1qgan40a8rkwIVzkdglkviLAXJJeP0ilc1GeGz1ctXyA6NAZt7RHB79mdQbH9iV1AsR29ItH4jdBs7eWRybDRKYRlmOntH0n7zlJDB4qQMxvNdI8BKYrPQymgZ3jtRJ/INOvLerg1g= 10 | -------------------------------------------------------------------------------- /src/footer.js: -------------------------------------------------------------------------------- 1 | // Renamed functions 2 | 3 | dc.abstractBubbleChart = dc.bubbleMixin; 4 | dc.baseChart = dc.baseMixin; 5 | dc.capped = dc.capMixin; 6 | dc.colorChart = dc.colorMixin; 7 | dc.coordinateGridChart = dc.coordinateGridMixin; 8 | dc.marginable = dc.marginMixin; 9 | dc.stackableChart = dc.stackMixin; 10 | 11 | return dc;} 12 | if(typeof define === "function" && define.amd) { 13 | define(["d3"], _dc); 14 | } else if(typeof module === "object" && module.exports) { 15 | module.exports = _dc(d3); 16 | } else { 17 | this.dc = _dc(d3); 18 | } 19 | } 20 | )(); 21 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | dc.logger = {}; 2 | 3 | dc.logger.enableDebugLog = false; 4 | 5 | dc.logger.warn = function (msg) { 6 | if (console) { 7 | if (console.warn) { 8 | console.warn(msg); 9 | } else if (console.log) { 10 | console.log(msg); 11 | } 12 | } 13 | 14 | return dc.logger; 15 | }; 16 | 17 | dc.logger.debug = function (msg) { 18 | if (dc.logger.enableDebugLog && console) { 19 | if (console.debug) { 20 | console.debug(msg); 21 | } else if (console.log) { 22 | console.log(msg); 23 | } 24 | } 25 | 26 | return dc.logger; 27 | }; 28 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "evil": false, 7 | "immed": true, 8 | "latedef": true, 9 | "loopfunc": true, 10 | "maxlen": 120, 11 | "multistr": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "quotmark": "single", 18 | "sub": true, 19 | "undef": true, 20 | "unused": true, 21 | "globals" : { 22 | "console": false, 23 | "crossfilter" : false, 24 | "d3" : false, 25 | "dc" : false, 26 | "global": false, 27 | "module": false, 28 | "process": false, 29 | "require": false 30 | } 31 | } -------------------------------------------------------------------------------- /src/banner.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * dc %VERSION% 3 | * http://dc-js.github.io/dc.js/ 4 | * Copyright 2012 Nick Zhu and other contributors 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | (function() { function _dc(d3) { 20 | 'use strict'; 21 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Jacob Rideout Jacob Rideout 2 | Leo Sun Leo Sun 3 | Reinout Verkerk Trilex 4 | Robert Thompson rthompson-au 5 | Robin Thomas robin900 6 | Rob Dawson rojotek 7 | Christophe Geiser krikrou 8 | Stephen Cassidy stephen 9 | Jon von Gillern vongillern 10 | Luca Pandini CarettaCaretta 11 | Mu Jing PasghettiCode 12 | -------------------------------------------------------------------------------- /spec/helpers/spec-helper.js: -------------------------------------------------------------------------------- 1 | beforeEach(function() { 2 | d3.select("body").append("div").attr("id", "test-content"); 3 | jasmine.clock().install(); 4 | }); 5 | 6 | afterEach(function () { 7 | dc.deregisterAllCharts(); 8 | dc.renderlet(null); 9 | d3.selectAll("#test-content").remove(); 10 | jasmine.clock().uninstall(); 11 | }); 12 | 13 | function appendChartID(id) { 14 | return d3.select("#test-content").append("div").attr("id", id); 15 | } 16 | 17 | function coordsFromTranslate(translationString) { 18 | var result = parseTranslate(translationString); 19 | expect(result).not.toBeNull(); 20 | return { x: +result[1], y: +result[2] }; 21 | } 22 | 23 | // use UTC dates because these tests will be run in many time zones 24 | function makeDate(year, month, day) { 25 | if(typeof year === 'string' || arguments.length !== 3) 26 | throw new Error("makeDate takes year, month, day"); 27 | return new Date(Date.UTC(year, month, day, 0, 0, 0, 0)); 28 | } 29 | -------------------------------------------------------------------------------- /web/examples/morley2.csv: -------------------------------------------------------------------------------- 1 | Expt,Run,Speed 2 | 1,1,850 3 | 1,2,740 4 | 1,3,900 5 | 1,4,1070 6 | 1,5,930 7 | 1,6,850 8 | 1,7,950 9 | 1,8,980 10 | 1,9,980 11 | 1,10,880 12 | 1,11,1000 13 | 1,12,980 14 | 1,13,930 15 | 1,14,650 16 | 1,15,760 17 | 1,16,810 18 | 1,17,1000 19 | 1,18,1000 20 | 1,19,960 21 | 1,20,960 22 | 3,1,880 23 | 3,2,870 24 | 3,3,840 25 | 3,4,820 26 | 3,5,730 27 | 3,6,720 28 | 3,7,610 29 | 3,8,860 30 | 3,9,990 31 | 3,10,940 32 | 3,11,880 33 | 3,12,910 34 | 3,13,850 35 | 3,14,870 36 | 3,15,840 37 | 3,16,840 38 | 3,17,850 39 | 3,18,840 40 | 3,19,840 41 | 3,20,840 42 | 4,1,890 43 | 4,2,810 44 | 4,3,810 45 | 4,4,820 46 | 4,5,800 47 | 4,6,770 48 | 4,7,760 49 | 4,8,740 50 | 4,9,750 51 | 4,10,760 52 | 4,11,910 53 | 4,12,920 54 | 4,13,890 55 | 4,14,860 56 | 4,15,880 57 | 4,16,720 58 | 4,17,840 59 | 4,18,850 60 | 4,19,850 61 | 4,20,780 62 | 5,1,890 63 | 5,2,840 64 | 5,3,780 65 | 5,4,810 66 | 5,5,760 67 | 5,6,810 68 | 5,7,790 69 | 5,8,810 70 | 5,9,820 71 | 5,10,850 72 | 5,11,870 73 | 5,12,870 74 | 5,13,810 75 | 5,14,740 76 | 5,15,810 77 | 5,16,940 78 | 5,17,950 79 | 5,18,800 80 | 5,19,810 81 | 5,20,870 82 | -------------------------------------------------------------------------------- /web/examples/pie.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Pie Chart Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | dc.events = { 2 | current: null 3 | }; 4 | 5 | /** 6 | #### dc.events.trigger(function[, delay]) 7 | This function triggers a throttled event function with a specified delay (in milli-seconds). Events 8 | that are triggered repetitively due to user interaction such brush dragging might flood the library 9 | and invoke more renders than can be executed in time. Using this function to wrap your event 10 | function allows the library to smooth out the rendering by throttling events and only responding to 11 | the most recent event. 12 | 13 | ```js 14 | chart.renderlet(function(chart){ 15 | // smooth the rendering through event throttling 16 | dc.events.trigger(function(){ 17 | // focus some other chart to the range selected by user on this chart 18 | someOtherChart.focus(chart.filter()); 19 | }); 20 | }) 21 | ``` 22 | **/ 23 | dc.events.trigger = function (closure, delay) { 24 | if (!delay) { 25 | closure(); 26 | return; 27 | } 28 | 29 | dc.events.current = closure; 30 | 31 | setTimeout(function () { 32 | if (closure === dc.events.current) { 33 | closure(); 34 | } 35 | }, delay); 36 | }; 37 | -------------------------------------------------------------------------------- /src/margin-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Margin Mixin 3 | Margin is a mixin that provides margin utility functions for both the Row Chart and Coordinate Grid 4 | Charts. 5 | 6 | **/ 7 | dc.marginMixin = function (_chart) { 8 | var _margin = {top: 10, right: 50, bottom: 30, left: 30}; 9 | 10 | /** 11 | #### .margins([margins]) 12 | Get or set the margins for a particular coordinate grid chart instance. The margins is stored as 13 | an associative Javascript array. Default margins: {top: 10, right: 50, bottom: 30, left: 30}. 14 | 15 | The margins can be accessed directly from the getter. 16 | ```js 17 | var leftMargin = chart.margins().left; // 30 by default 18 | chart.margins().left = 50; 19 | leftMargin = chart.margins().left; // now 50 20 | ``` 21 | 22 | **/ 23 | _chart.margins = function (m) { 24 | if (!arguments.length) { 25 | return _margin; 26 | } 27 | _margin = m; 28 | return _chart; 29 | }; 30 | 31 | _chart.effectiveWidth = function () { 32 | return _chart.width() - _chart.margins().left - _chart.margins().right; 33 | }; 34 | 35 | _chart.effectiveHeight = function () { 36 | return _chart.height() - _chart.margins().top - _chart.margins().bottom; 37 | }; 38 | 39 | return _chart; 40 | }; 41 | -------------------------------------------------------------------------------- /web/examples/scatter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Scatter Plot Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /web/examples/ord.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Ordinal Bar Chart Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /spec/event-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc event engine', function() { 2 | describe('event execution', function() { 3 | var engine, trigger; 4 | beforeEach(function() { 5 | engine = dc.events; 6 | trigger = jasmine.createSpy("trigger"); 7 | }); 8 | 9 | it('event can be dispatched immediately', function() { 10 | engine.trigger(trigger); 11 | expect(trigger).toHaveBeenCalled(); 12 | }); 13 | 14 | it('event can be dispatched with delay', function() { 15 | engine.trigger(trigger, 100); 16 | expect(trigger).not.toHaveBeenCalled(); 17 | jasmine.clock().tick(101); 18 | expect(trigger).toHaveBeenCalled(); 19 | }); 20 | 21 | it('multiple events dispatched with delay should be throttled', function() { 22 | var times = 0; 23 | var i = 0; 24 | 25 | /* jshint -W083 */ 26 | while (i < 10) { 27 | engine.trigger(function() { 28 | times++; 29 | }, 10); 30 | i++; 31 | } 32 | /* jshint +W083 */ 33 | 34 | setTimeout(function() { 35 | expect(times).toEqual(1); 36 | }, 10); 37 | }); 38 | afterEach(function() { 39 | }); 40 | }); 41 | }); 42 | 43 | 44 | -------------------------------------------------------------------------------- /web/examples/line.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Line Chart Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /web/examples/bar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Bar Chart Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /web/examples/morley.csv: -------------------------------------------------------------------------------- 1 | Expt,Run,Speed 2 | 1,1,850 3 | 1,2,740 4 | 1,3,900 5 | 1,4,1070 6 | 1,5,930 7 | 1,6,850 8 | 1,7,950 9 | 1,8,980 10 | 1,9,980 11 | 1,10,880 12 | 1,11,1000 13 | 1,12,980 14 | 1,13,930 15 | 1,14,650 16 | 1,15,760 17 | 1,16,810 18 | 1,17,1000 19 | 1,18,1000 20 | 1,19,960 21 | 1,20,960 22 | 2,1,960 23 | 2,2,940 24 | 2,3,960 25 | 2,4,940 26 | 2,5,880 27 | 2,6,800 28 | 2,7,850 29 | 2,8,880 30 | 2,9,900 31 | 2,10,840 32 | 2,11,830 33 | 2,12,790 34 | 2,13,810 35 | 2,14,880 36 | 2,15,880 37 | 2,16,830 38 | 2,17,800 39 | 2,18,790 40 | 2,19,760 41 | 2,20,800 42 | 3,1,880 43 | 3,2,880 44 | 3,3,880 45 | 3,4,860 46 | 3,5,720 47 | 3,6,720 48 | 3,7,620 49 | 3,8,860 50 | 3,9,970 51 | 3,10,950 52 | 3,11,880 53 | 3,12,910 54 | 3,13,850 55 | 3,14,870 56 | 3,15,840 57 | 3,16,840 58 | 3,17,850 59 | 3,18,840 60 | 3,19,840 61 | 3,20,840 62 | 4,1,890 63 | 4,2,810 64 | 4,3,810 65 | 4,4,820 66 | 4,5,800 67 | 4,6,770 68 | 4,7,760 69 | 4,8,740 70 | 4,9,750 71 | 4,10,760 72 | 4,11,910 73 | 4,12,920 74 | 4,13,890 75 | 4,14,860 76 | 4,15,880 77 | 4,16,720 78 | 4,17,840 79 | 4,18,850 80 | 4,19,850 81 | 4,20,780 82 | 5,1,890 83 | 5,2,840 84 | 5,3,780 85 | 5,4,810 86 | 5,5,760 87 | 5,6,810 88 | 5,7,790 89 | 5,8,810 90 | 5,9,820 91 | 5,10,850 92 | 5,11,870 93 | 5,12,870 94 | 5,13,810 95 | 5,14,740 96 | 5,15,810 97 | 5,16,940 98 | 5,17,950 99 | 5,18,800 100 | 5,19,810 101 | 5,20,870 102 | -------------------------------------------------------------------------------- /web/highlighter/shAutoloader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('(2(){1 h=5;h.I=2(){2 n(c,a){4(1 d=0;dIndex of dc.js examples 2 | 3 |
4 |

Examples of using dc.js

5 |

An attempt to present a simple example of each chart type. Contributions welcome.

6 |

Source here.

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
bar.htmlbox-plot-time.htmlbox-plot.htmlcomposite.htmlcust.html
heat.htmlheatmap-filtering.htmlline.htmlmulti-scatter.htmlnumber.html
ord.htmlpie.htmlright-axis.htmlscatter-brushing.htmlscatter-series.html
scatter.htmlseries.html
34 |
-------------------------------------------------------------------------------- /web/examples/heat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Heatmap Example 5 | 6 | 7 | 13 | 14 | 15 |

Michelson–Morley experiment

16 |
17 | 18 | 19 | 20 | 21 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /web/examples/morley3.csv: -------------------------------------------------------------------------------- 1 | Expt,Run,Speed 2 | 1,1,850 3 | 1,2,740 4 | 1,3,900 5 | 1,4,1070 6 | 1,5,930 7 | 1,6,850 8 | 1,7,950 9 | 1,8,980 10 | 1,9,980 11 | 1,10,880 12 | 1,11,1000 13 | 1,12,980 14 | 1,13,930 15 | 1,14,650 16 | 1,15,760 17 | 1,16,810 18 | 1,17,1000 19 | 1,18,1000 20 | 1,19,960 21 | 1,20,960 22 | 2,1,960 23 | 2,2,940 24 | 2,3,960 25 | 2,4,940 26 | 2,5,880 27 | 2,6,800 28 | 2,7,850 29 | 2,8,880 30 | 2,9,900 31 | 2,10,840 32 | 2,11,830 33 | 2,12,790 34 | 2,13,810 35 | 2,14,880 36 | 2,15,880 37 | 2,16,830 38 | 2,17,800 39 | 2,18,790 40 | 2,19,760 41 | 2,20,800 42 | 3,1,880 43 | 3,2,880 44 | 3,3,880 45 | 3,4,860 46 | 3,5,720 47 | 3,6,720 48 | 3,7,620 49 | 3,8,860 50 | 3,9,970 51 | 3,10,950 52 | 3,11,880 53 | 3,12,910 54 | 3,13,850 55 | 3,14,870 56 | 3,15,840 57 | 3,16,840 58 | 3,17,850 59 | 3,18,840 60 | 3,19,840 61 | 3,20,840 62 | 4,1,890 63 | 4,2,810 64 | 4,3,810 65 | 4,4,820 66 | 4,5,800 67 | 4,6,770 68 | 4,7,760 69 | 4,8,740 70 | 4,9,750 71 | 4,10,760 72 | 4,11,910 73 | 4,12,920 74 | 4,13,890 75 | 4,14,860 76 | 4,15,880 77 | 4,16,720 78 | 4,17,840 79 | 4,18,850 80 | 4,19,850 81 | 4,20,780 82 | 5,1,890 83 | 5,2,840 84 | 5,3,780 85 | 5,4,810 86 | 5,5,760 87 | 5,6,810 88 | 5,7,790 89 | 5,8,810 90 | 5,9,820 91 | 5,10,850 92 | 5,11,870 93 | 5,12,870 94 | 5,13,810 95 | 5,14,740 96 | 5,15,810 97 | 5,16,940 98 | 5,17,950 99 | 5,18,800 100 | 5,19,810 101 | 5,20,870 102 | 6,1,990 103 | 6,2,940 104 | 6,3,880 105 | 6,4,910 106 | 6,5,760 107 | 6,6,910 108 | 6,7,890 109 | 6,8,910 110 | 6,9,920 111 | 6,10,950 112 | 6,11,970 113 | 6,12,970 114 | 6,13,910 115 | 6,14,840 116 | 6,15,910 117 | 6,16,1140 118 | 6,17,1150 119 | 6,18,900 120 | 6,19,910 121 | 6,20,970 122 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Nick Zhu http://nzhu.blogspot.ca/ 2 | Robin Thomas 3 | Paul English 4 | Sean Bell 5 | Steven Lindberg 6 | Travis Swicegood 7 | Ben West 8 | Leo Sun 9 | Robert Thompson 10 | Chaks Chigurupati 11 | Josh Reed 12 | Rob Dawson 13 | Spencer Markowski 14 | Miles McCrocklin 15 | Christophe Geiser 16 | Jacob Rideout http://jacobrideout.net/ 17 | Lorcan Coyle 18 | Luca Pandini 19 | Andrew Swan 20 | Gordon Woodhull 21 | Mu Jing 22 | Calvin Sim 23 | Reinout Verkerk 24 | Bertrand Dechoux 25 | Philippe Martinez 26 | Stephen Cassidy 27 | Jon von Gillern 28 | Olivier Mornard 29 | Stephen Levine 30 | Jeffrey Steinmetz 31 | James King 32 | Matt Hull 33 | Neal Ruggles 34 | Matt Traynham 35 | Xavier Dutoit 36 | Siddharth Gupta 37 | David Long 38 | Austin Lyons 39 | Alan Breck 40 | Mohammad Bagher Ehtemam 41 | Douglas Ross 42 | Jared Camins-Esakov 43 | Chris Meier 44 | Devon Terrell 45 | -------------------------------------------------------------------------------- /web/highlighter/shBrushJScript.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | var keywords = 'break case catch continue ' + 25 | 'default delete do else false ' + 26 | 'for function if in instanceof ' + 27 | 'new null return super switch ' + 28 | 'this throw true try typeof var while with' 29 | ; 30 | 31 | var r = SyntaxHighlighter.regexLib; 32 | 33 | this.regexList = [ 34 | { regex: r.multiLineDoubleQuotedString, css: 'string' }, // double quoted strings 35 | { regex: r.multiLineSingleQuotedString, css: 'string' }, // single quoted strings 36 | { regex: r.singleLineCComments, css: 'comments' }, // one line comments 37 | { regex: r.multiLineCComments, css: 'comments' }, // multiline comments 38 | { regex: /\s*#.*/gm, css: 'preprocessor' }, // preprocessor tags like #region and #endregion 39 | { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' } // keywords 40 | ]; 41 | 42 | this.forHtmlScript(r.scriptScriptTags); 43 | }; 44 | 45 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 46 | Brush.aliases = ['js', 'jscript', 'javascript']; 47 | 48 | SyntaxHighlighter.brushes.JScript = Brush; 49 | 50 | // CommonJS 51 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 52 | })(); 53 | -------------------------------------------------------------------------------- /web/examples/scatter-brushing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Scatter Plot Brushing Example 5 | 6 | 7 | 8 | 9 |

Brush on one chart to see the points filtered on the other.

10 | 11 |
12 |
13 | 14 | 15 | 16 | 17 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /web/vc/state2code.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | state_code_map = { 4 | 'ALABAMA'=>'AL', 5 | 'ALASKA'=>'AK', 6 | 'ARIZONA'=>'AZ', 7 | 'ARKANSAS'=>'AR', 8 | 'CALIFORNIA'=>'CA', 9 | 'COLORADO'=>'CO', 10 | 'CONNECTICUT'=>'CT', 11 | 'DELAWARE'=>'DE', 12 | 'DISTRICT OF COLUMBIA'=>'DC', 13 | 'FLORIDA'=>'FL', 14 | 'GEORGIA'=>'GA', 15 | 'HAWAII'=>'HI', 16 | 'IDAHO'=>'ID', 17 | 'ILLINOIS'=>'IL', 18 | 'INDIANA'=>'IN', 19 | 'IOWA'=>'IA', 20 | 'KANSAS'=>'KS', 21 | 'KENTUCKY'=>'KY', 22 | 'LOUISIANA'=>'LA', 23 | 'MAINE'=>'ME', 24 | 'MARYLAND'=>'MD', 25 | 'MASSACHUSETTS'=>'MA', 26 | 'MICHIGAN'=>'MI', 27 | 'MINNESOTA'=>'MN', 28 | 'MISSISSIPPI'=>'MS', 29 | 'MISSOURI'=>'MO', 30 | 'MONTANA'=>'MT', 31 | 'NEBRASKA'=>'NE', 32 | 'NEVADA'=>'NV', 33 | 'NEW HAMPSHIRE'=>'NH', 34 | 'NEW JERSEY'=>'NJ', 35 | 'NEW MEXICO'=>'NM', 36 | 'NEW YORK'=>'NY', 37 | 'NORTH CAROLINA'=>'NC', 38 | 'NORTH DAKOTA'=>'ND', 39 | 'OHIO'=>'OH', 40 | 'OKLAHOMA'=>'OK', 41 | 'OREGON'=>'OR', 42 | 'PENNSYLVANIA'=>'PA', 43 | 'RHODE ISLAND'=>'RI', 44 | 'SOUTH CAROLINA'=>'SC', 45 | 'SOUTH DAKOTA'=>'SD', 46 | 'TENNESSEE'=>'TN', 47 | 'TEXAS'=>'TX', 48 | 'UTAH'=>'UT', 49 | 'VERMONT'=>'VT', 50 | 'VIRGINIA'=>'VA', 51 | 'WASHINGTON'=>'WA', 52 | 'WEST VIRGINIA'=>'WV', 53 | 'WISCONSIN'=>'WI', 54 | 'WYOMING'=>'WY', 55 | 'AMERICAN SAMOA'=>'AS', 56 | 'GUAM'=>'GU', 57 | 'NORTHERN MARIANA ISLANDS'=>'MP', 58 | 'PUERTO RICO'=>'PR', 59 | 'VIRGIN ISLANDS'=>'VI' 60 | } 61 | 62 | File.open(ARGV[0], 'r') { |file| 63 | file.each_line { |line| 64 | new_line = line 65 | line =~ /"name":"([\w\s]+)"/ 66 | if ($1) 67 | state_name = $1.upcase() 68 | state_code = state_code_map[state_name] 69 | new_line = line.sub(/"name":"#{$1}"/, %{"name":"#{state_code}"}) 70 | end 71 | 72 | puts new_line 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dc", 3 | "version": "2.0.0-alpha.2", 4 | "license": "Apache-2.0", 5 | "copyright": "2014", 6 | "description": "A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js ", 7 | "keywords": [ 8 | "visualization", 9 | "svg", 10 | "animation", 11 | "canvas", 12 | "chart", 13 | "dimensional", 14 | "crossfilter", 15 | "d3" 16 | ], 17 | "homepage": "http://dc-js.github.io/dc.js/", 18 | "bugs": "https://github.com/dc-js/dc.js/issues", 19 | "author": { 20 | "name": "Nick Zhu", 21 | "url": "http://nzhu.blogspot.ca/" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/dc-js/dc.js.git" 26 | }, 27 | "dependencies": { 28 | "crossfilter": "1.x", 29 | "d3": "3.x" 30 | }, 31 | "devDependencies": { 32 | "emu": "~0.0.2", 33 | "grunt": "~0.4.1", 34 | "grunt-cli": "~0.1.9", 35 | "grunt-contrib-concat": "~0.3.0", 36 | "grunt-contrib-connect": "~0.7.1", 37 | "grunt-contrib-copy": "~0.4.1", 38 | "grunt-contrib-jasmine": "~0.7.0", 39 | "grunt-contrib-jshint": "~0.10.0", 40 | "grunt-contrib-uglify": "~0.2.4", 41 | "grunt-contrib-watch": "~0.5.3", 42 | "grunt-debug-task": "~0.1.4", 43 | "grunt-docco2": "~0.2.0", 44 | "grunt-fileindex": "^0.1.0", 45 | "grunt-gh-pages": "~0.8.0", 46 | "grunt-jscs": "~0.6.2", 47 | "grunt-lib-phantomjs": "~0.5.0", 48 | "grunt-markdown": "~0.5.0", 49 | "grunt-saucelabs": "~8.1.1", 50 | "grunt-sed": "~0.1.1", 51 | "grunt-shell": "~0.5.0", 52 | "grunt-template-jasmine-istanbul": "~0.3.0", 53 | "marked": "~0.2.10", 54 | "uglify-js": "2.4.x" 55 | }, 56 | "scripts": { 57 | "test": "grunt test" 58 | }, 59 | "npmName": "dc", 60 | "npmFileMap": [ 61 | { 62 | "basePath": "/", 63 | "files": [ 64 | "dc.css", 65 | "dc.min.js", 66 | "dc.js" 67 | ] 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /web/examples/scatter-series.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Series Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /web/examples/multi-scatter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Scatter Plot Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /web/examples/box-plot-time.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Box-Plot Example 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /web/examples/box-plot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Box-Plot Example 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 16 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /web/examples/composite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Composite Chart Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": [ 3 | "if", 4 | "else", 5 | "for", 6 | "while", 7 | "do", 8 | "try", 9 | "catch" 10 | ], 11 | "requireOperatorBeforeLineBreak": true, 12 | "requireCamelCaseOrUpperCaseIdentifiers": true, 13 | "maximumLineLength": { 14 | "value": 120, 15 | "allowComments": true, 16 | "allowRegex": true 17 | }, 18 | "validateIndentation": 4, 19 | "validateQuoteMarks": "'", 20 | "disallowMultipleLineStrings": true, 21 | "disallowMixedSpacesAndTabs": true, 22 | "disallowTrailingWhitespace": true, 23 | "requireSpaceAfterKeywords": [ 24 | "if", 25 | "else", 26 | "for", 27 | "while", 28 | "do", 29 | "switch", 30 | "return", 31 | "try", 32 | "catch" 33 | ], 34 | "requireSpaceBeforeBinaryOperators": [ 35 | "=", 36 | "+=", 37 | "-=", 38 | "*=", 39 | "/=", 40 | "%=", 41 | "<<=", 42 | ">>=", 43 | ">>>=", 44 | "&=", 45 | "|=", 46 | "^=", 47 | "+=", 48 | "+", 49 | "-", 50 | "*", 51 | "/", 52 | "%", 53 | "<<", 54 | ">>", 55 | ">>>", 56 | "&", 57 | "|", 58 | "^", 59 | "&&", 60 | "||", 61 | "===", 62 | "==", 63 | ">=", 64 | "<=", 65 | "<", 66 | ">", 67 | "!=", 68 | "!==" 69 | ], 70 | "requireSpaceAfterBinaryOperators": true, 71 | "requireSpacesInConditionalExpression": true, 72 | "requireSpaceBeforeBlockStatements": true, 73 | "requireSpacesInFunctionExpression": { 74 | "beforeOpeningRoundBrace": true, 75 | "beforeOpeningCurlyBrace": true 76 | }, 77 | "disallowSpacesInsideObjectBrackets": "all", 78 | "disallowSpacesInsideArrayBrackets": "all", 79 | "disallowSpacesInsideParentheses": true, 80 | "validateJSDoc": { 81 | "checkParamNames": true, 82 | "requireParamTypes": true 83 | }, 84 | "disallowMultipleLineBreaks": true 85 | } -------------------------------------------------------------------------------- /web/crime/filter_stats.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | city_names = [ 4 | 'Toronto, Ontario', 5 | 'Ottawa-Gatineau, Ontario/Quebec', 6 | 'Vancouver, British Columbia', 7 | 'Montreal, Quebec', 8 | 'Edmonton, Alberta', 9 | 'Winnipeg, Manitoba', 10 | 'Saskatoon, Saskatchewan', 11 | 'Calgary, Alberta', 12 | 'Quebec, Quebec', 13 | 'Halifax, Nova Scotia', 14 | 'St. John\'s, Newfoundland and Labrador', 15 | 'Saint John, New Brunswick', 16 | 'Yukon', 17 | 'Northwest Territories', 18 | 'Nunavut' 19 | ] 20 | 21 | crime_types = [ 22 | '"Total, all violations",Actual incidents', 23 | '"Total, all violations","Rate per 100,000 population"', 24 | 'Total violent Criminal Code violations,Actual incidents', 25 | 'Total violent Criminal Code violations,"Rate per 100,000 population"', 26 | 'Homicide,Actual incidents', 27 | 'Homicide,"Rate per 100,000 population"' 28 | ] 29 | 30 | subs = [ 31 | ['Toronto, Ontario', 'Toronto'], 32 | ['Ottawa-Gatineau, Ontario/Quebec', 'Ottawa'], 33 | ['Vancouver, British Columbia', 'Vancouver'], 34 | ['Montreal, Quebec', 'Montreal'], 35 | ['Edmonton, Alberta', 'Edmonton'], 36 | ['Winnipeg, Manitoba', 'Winnipeg'], 37 | ['Saskatoon, Saskatchewan', 'Saskatoon'], 38 | ['Calgary, Alberta', 'Calgary'], 39 | ['Quebec, Quebec', 'Quebec'], 40 | ['Halifax, Nova Scotia', 'Halifax'], 41 | ['St. John\'s, Newfoundland and Labrador', 'St. John\'s'], 42 | ['Saint John, New Brunswick', 'Saint John'] 43 | ] 44 | 45 | puts "year,city,type,sub_type,number" 46 | 47 | File.open(ARGV[0], "r:ISO8859-1") { |file| 48 | file.each_line() { |line| 49 | begin 50 | catch :line do 51 | city_names.each { |city| 52 | if (line =~ /.*#{city}.*/) 53 | crime_types.each { |crime| 54 | if (line =~ /.*#{crime}.*/) 55 | subs.each{|sub_pair| 56 | line = line.gsub(sub_pair[0], sub_pair[1]) 57 | } 58 | puts line 59 | throw :line 60 | end 61 | } 62 | end 63 | } 64 | end 65 | rescue ArgumentError 66 | # do nothing 67 | end 68 | } 69 | } -------------------------------------------------------------------------------- /web/highlighter/shBrushXml.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | ;(function() 18 | { 19 | // CommonJS 20 | typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; 21 | 22 | function Brush() 23 | { 24 | function process(match, regexInfo) 25 | { 26 | var constructor = SyntaxHighlighter.Match, 27 | code = match[0], 28 | tag = new XRegExp('(<|<)[\\s\\/\\?]*(?[:\\w-\\.]+)', 'xg').exec(code), 29 | result = [] 30 | ; 31 | 32 | if (match.attributes != null) 33 | { 34 | var attributes, 35 | regex = new XRegExp('(? [\\w:\\-\\.]+)' + 36 | '\\s*=\\s*' + 37 | '(? ".*?"|\'.*?\'|\\w+)', 38 | 'xg'); 39 | 40 | while ((attributes = regex.exec(code)) != null) 41 | { 42 | result.push(new constructor(attributes.name, match.index + attributes.index, 'color1')); 43 | result.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string')); 44 | } 45 | } 46 | 47 | if (tag != null) 48 | result.push( 49 | new constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword') 50 | ); 51 | 52 | return result; 53 | } 54 | 55 | this.regexList = [ 56 | { regex: new XRegExp('(\\<|<)\\!\\[[\\w\\s]*?\\[(.|\\s)*?\\]\\](\\>|>)', 'gm'), css: 'color2' }, // 57 | { regex: SyntaxHighlighter.regexLib.xmlComments, css: 'comments' }, // 58 | { regex: new XRegExp('(<|<)[\\s\\/\\?]*(\\w+)(?.*?)[\\s\\/\\?]*(>|>)', 'sg'), func: process } 59 | ]; 60 | }; 61 | 62 | Brush.prototype = new SyntaxHighlighter.Highlighter(); 63 | Brush.aliases = ['xml', 'xhtml', 'xslt', 'html']; 64 | 65 | SyntaxHighlighter.brushes.Xml = Brush; 66 | 67 | // CommonJS 68 | typeof(exports) != 'undefined' ? exports.Brush = Brush : null; 69 | })(); 70 | -------------------------------------------------------------------------------- /web/examples/series.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Series Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/dc-js/dc.js.png?branch=master)](http://travis-ci.org/dc-js/dc.js) 2 | [![Sauce Status](https://saucelabs.com/buildstatus/sclevine)](https://saucelabs.com/u/sclevine) 3 | [![NPM Status](https://badge.fury.io/js/dc.png)](http://badge.fury.io/js/dc) 4 | 5 | dc.js 6 | ===== 7 | 8 | Dimensional charting built to work natively with crossfilter rendered using d3.js. Check out the 9 | [example page](http://dc-js.github.com/dc.js/) with a quick five minutes how to guide. For a 10 | detailed [API reference](https://github.com/dc-js/dc.js/blob/master/web/docs/api-1.6.0.md) and 11 | more please visit the [Wiki](https://github.com/dc-js/dc.js/wiki). 12 | 13 | 14 | CDN location 15 | -------------------- 16 | ``` 17 | http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.js 18 | http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.min.js 19 | http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.css 20 | ``` 21 | Please do not use github.io as a CDN unless you need the bleeding-edge features. 22 | 23 | [More info on the Wiki.](https://github.com/dc-js/dc.js/wiki#cdn-location) 24 | 25 | 26 | Install with npm 27 | -------------------- 28 | ``` 29 | npm install dc 30 | ``` 31 | 32 | 33 | Install without npm 34 | -------------------- 35 | Download 36 | * [d3.js](https://github.com/mbostock/d3) 37 | * [crossfilter.js](https://github.com/square/crossfilter) 38 | * [dc.js - stable](https://github.com/dc-js/dc.js/releases) 39 | * [dc.js - bleeding edge (master)](https://github.com/dc-js/dc.js) 40 | 41 | 42 | How to build dc.js locally 43 | --------------------------- 44 | 45 | ### Prerequisite modules 46 | 47 | Make sure the following packages are installed on your machine 48 | * node.js 49 | * npm 50 | 51 | ### Install dependencies 52 | ``` 53 | dc.js$ npm install 54 | ``` 55 | 56 | ### Build and Test 57 | ``` 58 | dc.js$ grunt test 59 | ``` 60 | 61 | Developing dc.js 62 | ---------------- 63 | 64 | ### Start the development server 65 | ``` 66 | dc.js$ grunt server 67 | ``` 68 | 69 | * Jasmine specs are hosted at http://localhost:8888/spec 70 | * The stock example is at http://localhost:8888/web 71 | * More examples are at http://localhost:8888/web/examples 72 | 73 | License 74 | -------------------- 75 | 76 | dc.js is an open source javascript library and licensed under 77 | [Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html). 78 | -------------------------------------------------------------------------------- /web/examples/cust.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Number Display Example 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 | 14 | 15 | 16 | 17 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /spec/logger-spec.js: -------------------------------------------------------------------------------- 1 | describe("dc.logger", function() { 2 | var message = "Watch out for the bears"; 3 | 4 | describe("logging a warning", function () { 5 | describe("when console.warn is defined", function () { 6 | beforeEach(function () { 7 | console.warn = function (msg) {}; 8 | spyOn(console, "warn"); 9 | dc.logger.warn(message); 10 | }); 11 | 12 | it("should log the message using console.warn", function (){ 13 | expect(console.warn).toHaveBeenCalledWith(message); 14 | }); 15 | }); 16 | 17 | describe("when console.warn is not defined but console.log is", function () { 18 | beforeEach(function () { 19 | console.warn = undefined; 20 | spyOn(console, "log"); 21 | dc.logger.warn(message); 22 | }); 23 | 24 | it("should log the message using console.log", function (){ 25 | expect(console.log).toHaveBeenCalledWith(message); 26 | }); 27 | }); 28 | }); 29 | 30 | describe("debug flag", function () { 31 | it("is off by default", function() { 32 | expect(dc.logger.enableDebugLog).toBeFalsy(); 33 | }); 34 | }); 35 | 36 | describe("debug logging", function () { 37 | describe("when debugging is disabled", function (){ 38 | beforeEach(function () { 39 | dc.logger.enableDebugLog = false; 40 | console.debug = function (msg) {}; 41 | spyOn(console, "debug"); 42 | dc.logger.debug(message); 43 | }); 44 | 45 | it("should log nothing", function (){ 46 | expect(console.debug).not.toHaveBeenCalled(); 47 | }); 48 | }); 49 | 50 | describe("when debugging is enabled", function () { 51 | beforeEach(function () { 52 | dc.logger.enableDebugLog = true; 53 | }); 54 | 55 | describe("when console.debug is defined", function () { 56 | beforeEach(function () { 57 | console.debug = function (msg) {}; 58 | spyOn(console, "debug"); 59 | dc.logger.debug(message); 60 | }); 61 | 62 | it("should log the message using console.debug", function (){ 63 | expect(console.debug).toHaveBeenCalledWith(message); 64 | }); 65 | }); 66 | 67 | describe("when console.debug is not defined", function () { 68 | beforeEach(function () { 69 | console.debug = undefined; 70 | spyOn(console, "log"); 71 | dc.logger.debug(message); 72 | }); 73 | 74 | it("should log the message using console.log", function (){ 75 | expect(console.log).toHaveBeenCalledWith(message); 76 | }); 77 | }); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /web/ep/list.js: -------------------------------------------------------------------------------- 1 | 2 | function list (selector,path,data) { 3 | var ndx = crossfilter(data), 4 | all = ndx.groupAll(); 5 | 6 | var pie_group = dc.pieChart(selector + " .group").innerRadius(20).radius(70); 7 | var group = ndx.dimension(function(d) { 8 | if (typeof d.party == "undefined") return ""; 9 | return d.party; 10 | }); 11 | var groupGroup = group.group().reduceSum(function(d) { return 1; }); 12 | 13 | var pie_gender = dc.pieChart(selector + " .gender").radius(70); 14 | var gender = ndx.dimension(function(d) { 15 | if (typeof d.gender_id == "undefined") return ""; 16 | return d.gender; 17 | }); 18 | 19 | var groupGender = gender.group().reduceSum(function(d) { return 1; }); 20 | 21 | var bar_country = dc.barChart(selector + " .country"); 22 | var country = ndx.dimension(function(d) { 23 | if (typeof d.country == "undefined") return ""; 24 | return d.country; 25 | }); 26 | var countryGroup = country.group().reduceSum(function(d) { return 1; }); 27 | 28 | pie_gender 29 | .width(200) 30 | .height(200) 31 | .dimension(gender) 32 | .group(groupGender); 33 | 34 | pie_group 35 | .width(200) 36 | .height(200) 37 | .dimension(group) 38 | .colors(d3.scale.category10()) 39 | .group(groupGroup) 40 | .renderlet(function (chart) { 41 | }); 42 | 43 | bar_country 44 | .width(444) 45 | .height(200) 46 | .outerPadding(0) 47 | .gap(1) 48 | .margins({top: 0, right: 0, bottom: 95, left: 30}) 49 | .x(d3.scale.ordinal()) 50 | .xUnits(dc.units.ordinal) 51 | .brushOn(false) 52 | .elasticY(true) 53 | .yAxisLabel("#MEPs") 54 | .dimension(country) 55 | .group(countryGroup); 56 | 57 | bar_country.on("postRender", function(c) {rotateBarChartLabels();} ); 58 | 59 | 60 | function rotateBarChartLabels() { 61 | d3.selectAll(selector+ ' .country .axis.x text') 62 | .style("text-anchor", "end" ) 63 | .attr("transform", function(d) { return "rotate(-90, -4, 9) "; }); 64 | } 65 | 66 | 67 | dc.dataCount(".dc-data-count") 68 | .dimension(ndx) 69 | .group(all); 70 | 71 | dc.dataTable(".dc-data-table") 72 | .dimension(country) 73 | .group(function (d) { 74 | return d.country; 75 | }) 76 | .size(1000) 77 | .columns([ 78 | function (d) { 79 | return d.gender || ""; 80 | }, 81 | function (d) { 82 | return d.first_name || ""; 83 | }, 84 | function (d) { 85 | return d.last_name || ""; 86 | }, 87 | function (d) { 88 | return d.country || ""; 89 | }, 90 | function (d) { 91 | return d.party || ""; 92 | } 93 | ]) 94 | .sortBy(function (d) { 95 | return d.last_name; 96 | }) 97 | .order(d3.ascending) 98 | .renderlet(function (table) { 99 | }); 100 | 101 | 102 | 103 | dc.renderAll(); 104 | 105 | } 106 | 107 | -------------------------------------------------------------------------------- /web/examples/number.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Number Display Example 5 | 6 | 7 | 18 | 19 | 20 | 21 |
22 | Inline Number Display. We have Jumping on the bed. 23 |
24 | 25 |
26 | 27 |

28 | We have seen a mean of over experiments! 29 |

30 | 31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /spec/filter-dates-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc.filter-dates', function() { 2 | // do date filters work correctly? 3 | // adapted from a fiddle demonstrating the problem by Matt Traynham 4 | // see it fail with 1.7.1: http://jsfiddle.net/gordonwoodhull/Q2H9C/4/ 5 | // see it win with 2.0: http://jsfiddle.net/gordonwoodhull/Q2H9C/3/ 6 | // (Thanks!!) 7 | 8 | var group, dateDim1, dateDim2, group1, group2, 9 | row1, row2; 10 | var width = 400; 11 | var height = 200; 12 | var margins = {top: 15, right: 10, bottom: 20, left: 40}; 13 | beforeEach(function () { 14 | // Months are 0 indexed... 15 | var start = makeDate(2013, 10, 1); 16 | var end = makeDate(2013, 11, 1); 17 | var stringLength = 2; 18 | 19 | // Generate Random Data [Date, VowelString, Random Number, Random Measure] 20 | var data = []; 21 | for(var i = 0; i < 2000; i++) { 22 | data[i] = [ 23 | randomDate(start, end), 24 | randomVowelString(stringLength), 25 | Math.floor(Math.random() * 20), 26 | Math.floor(Math.random() * 30000) 27 | ]; 28 | } 29 | 30 | var ndx = crossfilter(data); 31 | dateDim1 = ndx.dimension(function(d) { return d[0]; }); 32 | dateDim2 = ndx.dimension(function(d) { return d[0]; }); 33 | 34 | group1 = dateDim1.group().reduceSum(function(d) { return d[3]; }); 35 | group2 = dateDim2.group().reduceSum(function(d) { return d[3]; }); 36 | 37 | appendChartID(row1); 38 | appendChartID(row2); 39 | 40 | row1 = dc.rowChart("row1") 41 | .width(width) 42 | .height(height) 43 | .margins(margins) 44 | .dimension(dateDim1) 45 | .group(group1) 46 | .gap(1) 47 | .render(); 48 | 49 | row2 = dc.rowChart("row2") 50 | .width(width) 51 | .height(height) 52 | .margins(margins) 53 | .dimension(dateDim2) 54 | .group(group2) 55 | .gap(1) 56 | .render(); 57 | }); 58 | 59 | it('filtering on 11/8 should keep only that row', function() { 60 | row1.filter(makeDate(2013, 10, 8)); 61 | expect(group1.all()[6].value).not.toEqual(0); 62 | expect(group2.all()[6].value).toEqual(0); 63 | expect(group2.all()[7]).toEqual(group1.all()[7]); 64 | expect(group2.all()[8].value).toEqual(0); 65 | }); 66 | 67 | it('filtering on 11/17 should keep only that row', function() { 68 | row1.filter(makeDate(2013, 10, 17)); 69 | expect(group1.all()[15].value).not.toEqual(0); 70 | expect(group2.all()[15].value).toEqual(0); 71 | expect(group2.all()[16]).toEqual(group1.all()[16]); 72 | expect(group2.all()[17].value).toEqual(0); 73 | }); 74 | 75 | // Create a Random Date 76 | function randomDate(start, end) { 77 | var d = new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); 78 | d.setUTCHours(0,0,0,0); 79 | return d; 80 | } 81 | 82 | // Create a Random String of vowels 83 | var vowels = ["a","e","i","o","u","y"]; 84 | function randomVowelString(length) { 85 | var val = ""; 86 | for(var i = 0; i < length; i++) { 87 | val = val + vowels[Math.floor(Math.random() * (vowels.length - 1))]; 88 | } 89 | return val; 90 | } 91 | }); 92 | -------------------------------------------------------------------------------- /spec/color-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc.colorMixin', function() { 2 | function colorTest(chart, domain, test) { 3 | chart.colorDomain(domain); 4 | return (test || domain).map(chart.getColor); 5 | } 6 | 7 | function identity(d) { return d; } 8 | 9 | describe('with ordinal domain' , function() { 10 | var chart, domain; 11 | 12 | beforeEach(function() { 13 | chart = dc.colorMixin({}); 14 | chart.colorAccessor(identity); 15 | domain = ["a","b","c","d","e"]; 16 | }); 17 | 18 | it('default', function() { 19 | expect(colorTest(chart,domain)).toEqual(['#3182bd','#6baed6','#9ecae1','#c6dbef','#e6550d']); 20 | }); 21 | 22 | it('custom', function() { 23 | chart.colors(d3.scale.category10()); 24 | expect(colorTest(chart,domain)).toEqual(['#1f77b4','#ff7f0e','#2ca02c','#d62728','#9467bd']); 25 | }); 26 | 27 | it('ordinal', function() { 28 | chart.ordinalColors(['red','green','blue']); 29 | expect(colorTest(chart,domain)).toEqual(['red','green','blue','red','green']); 30 | }); 31 | 32 | it('linear', function() { 33 | // GIGO: mapping ordinal domain to linear scale is nonsensical 34 | // actually it gets scaled to NaN and then d3 corrects it 35 | chart.linearColors(['#FF0000','#00FF00']); 36 | expect(colorTest(chart,domain)).toEqual([ '#000000', '#000000', '#000000', '#000000', '#000000' ]); 37 | }); 38 | }); 39 | describe('with numeric domain' , function() { 40 | var chart, domain, test; 41 | 42 | beforeEach(function() { 43 | chart = dc.colorChart({}); 44 | chart.colorAccessor(identity); 45 | domain = [1,100]; 46 | test = [0,1,50,100,101,1]; 47 | }); 48 | 49 | it('default', function() { 50 | expect(colorTest(chart,domain,test)).toEqual(['#9ecae1','#3182bd','#c6dbef','#6baed6','#e6550d','#3182bd']); 51 | }); 52 | 53 | it('custom', function() { 54 | chart.colors(d3.scale.category10()); 55 | expect(colorTest(chart,domain,test)).toEqual(['#2ca02c', '#1f77b4', '#d62728', '#ff7f0e', '#9467bd', '#1f77b4' ]); 56 | }); 57 | 58 | it('ordinal', function() { 59 | chart.ordinalColors(['red','green','blue']); 60 | expect(colorTest(chart,domain,test)).toEqual(['blue', 'red', 'red', 'green', 'green', 'red' ]); 61 | }); 62 | 63 | it('linear', function() { 64 | chart.linearColors(['#4575b4','#ffffbf']); 65 | expect(colorTest(chart,domain,test)).toEqual(['#4773b3', '#4575b4', '#4dc6c1', '#ffffbf', '#ffffc0', '#4575b4' ]); 66 | }); 67 | }); 68 | describe('calculateColorDomain' , function() { 69 | var chart; 70 | 71 | beforeEach(function() { 72 | var data = crossfilter(loadDateFixture()); 73 | var valueDimension = data.dimension(function(d) { 74 | return d.value; 75 | }); 76 | var valueGroup = valueDimension.group(); 77 | chart = dc.colorChart(dc.baseChart({})) 78 | .colorAccessor(function(d){return d.value;}) 79 | .group(valueGroup); 80 | }); 81 | 82 | it('check domain', function() { 83 | chart.calculateColorDomain(); 84 | expect(chart.colorDomain()).toEqual([1,3]); 85 | }); 86 | }); 87 | }); 88 | 89 | -------------------------------------------------------------------------------- /web/highlighter/shThemeDefault.css: -------------------------------------------------------------------------------- 1 | /** 2 | * SyntaxHighlighter 3 | * http://alexgorbatchev.com/SyntaxHighlighter 4 | * 5 | * SyntaxHighlighter is donationware. If you are using it, please donate. 6 | * http://alexgorbatchev.com/SyntaxHighlighter/donate.html 7 | * 8 | * @version 9 | * 3.0.83 (July 02 2010) 10 | * 11 | * @copyright 12 | * Copyright (C) 2004-2010 Alex Gorbatchev. 13 | * 14 | * @license 15 | * Dual licensed under the MIT and GPL licenses. 16 | */ 17 | .syntaxhighlighter { 18 | background-color: white !important; 19 | } 20 | .syntaxhighlighter .line.alt1 { 21 | background-color: white !important; 22 | } 23 | .syntaxhighlighter .line.alt2 { 24 | background-color: white !important; 25 | } 26 | .syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 { 27 | background-color: #e0e0e0 !important; 28 | } 29 | .syntaxhighlighter .line.highlighted.number { 30 | color: black !important; 31 | } 32 | .syntaxhighlighter table caption { 33 | color: black !important; 34 | } 35 | .syntaxhighlighter .gutter { 36 | color: #afafaf !important; 37 | } 38 | .syntaxhighlighter .gutter .line { 39 | border-right: 3px solid #6ce26c !important; 40 | } 41 | .syntaxhighlighter .gutter .line.highlighted { 42 | background-color: #6ce26c !important; 43 | color: white !important; 44 | } 45 | .syntaxhighlighter.printing .line .content { 46 | border: none !important; 47 | } 48 | .syntaxhighlighter.collapsed { 49 | overflow: visible !important; 50 | } 51 | .syntaxhighlighter.collapsed .toolbar { 52 | color: blue !important; 53 | background: white !important; 54 | border: 1px solid #6ce26c !important; 55 | } 56 | .syntaxhighlighter.collapsed .toolbar a { 57 | color: blue !important; 58 | } 59 | .syntaxhighlighter.collapsed .toolbar a:hover { 60 | color: red !important; 61 | } 62 | .syntaxhighlighter .toolbar { 63 | color: white !important; 64 | background: #6ce26c !important; 65 | border: none !important; 66 | } 67 | .syntaxhighlighter .toolbar a { 68 | color: white !important; 69 | } 70 | .syntaxhighlighter .toolbar a:hover { 71 | color: black !important; 72 | } 73 | .syntaxhighlighter .plain, .syntaxhighlighter .plain a { 74 | color: black !important; 75 | } 76 | .syntaxhighlighter .comments, .syntaxhighlighter .comments a { 77 | color: #008200 !important; 78 | } 79 | .syntaxhighlighter .string, .syntaxhighlighter .string a { 80 | color: blue !important; 81 | } 82 | .syntaxhighlighter .keyword { 83 | color: #006699 !important; 84 | } 85 | .syntaxhighlighter .preprocessor { 86 | color: gray !important; 87 | } 88 | .syntaxhighlighter .variable { 89 | color: #aa7700 !important; 90 | } 91 | .syntaxhighlighter .value { 92 | color: #009900 !important; 93 | } 94 | .syntaxhighlighter .functions { 95 | color: #ff1493 !important; 96 | } 97 | .syntaxhighlighter .constants { 98 | color: #0066cc !important; 99 | } 100 | .syntaxhighlighter .script { 101 | font-weight: bold !important; 102 | color: #006699 !important; 103 | background-color: none !important; 104 | } 105 | .syntaxhighlighter .color1, .syntaxhighlighter .color1 a { 106 | color: gray !important; 107 | } 108 | .syntaxhighlighter .color2, .syntaxhighlighter .color2 a { 109 | color: #ff1493 !important; 110 | } 111 | .syntaxhighlighter .color3, .syntaxhighlighter .color3 a { 112 | color: red !important; 113 | } 114 | 115 | .syntaxhighlighter .keyword { 116 | font-weight: bold !important; 117 | } 118 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Issue Submission Guidlines 4 | 5 | * If you have a problem with your code, please ask your question on stackoverflow.com or the [user group](https://groups.google.com/forum/?fromgroups#!forum/dc-js-user-group) 6 | * It will be far, far easier for others to understand your problem or bug if you demonstrate it with a short example on http://jsfiddle.net/ or on http://bl.ocks.org/ 7 | * For bugs and feature requests submit a [github issue](http://github.com/dc-js/dc.js/issues) 8 | * Please include the version of DC you are using 9 | * If you can, please try the latest version of DC on the [master](https://raw.github.com/dc-js/dc.js/master/dc.js) branch to see if your issue has already been addressed or is otherwise affected by recent changes. 10 | 11 | ## Pull Request Guidelines 12 | 13 | * Fork the repository 14 | * Make changes to the files in `src/` not dc.js 15 | * Add tests to `spec/`. Feel free to create a new file if needed. 16 | * Run `grunt server` and go to http://localhost:8888/spec to develop your tests. 17 | * Run `grunt lint` to fix any final linting issues via jshint. 18 | * Run `grunt test` to confirm that all tests will pass on phantomjs. 19 | * Run `grunt jshint` and `grunt jscs` to confirm that your code meets the dc.js style guidelines 20 | * Commit your changes to `src/*` and `spec/*` but not any build artifacts. (Build artifacts include `dc.*js*`, `web/docs/*`, `web/js/*`) 21 | * Submit a pull request 22 | * If you merge master or another branch into your patchset, please rebase against master. 23 | * The DC maintainer team will review and build the artifacts when merging 24 | * If you continue making changes to your fork of `dc.js`, create a branch for each pull request 25 | 26 | #### Coding Conventions 27 | 28 | * Avoid tabs and trailing whitespace 29 | * Please try to follow the existing code formatting 30 | * We use jshint and jscs to verify most of our coding conventions 31 | 32 | It helps keep on top of the conventions if you create a git pre-commit hook `.git/hooks/pre-commit`: 33 | ``` 34 | #!/usr/bin/env sh 35 | 36 | grunt jshint 37 | grunt jscs 38 | ``` 39 | 40 | (You also need to make it executable with `chmod u+x .git/hooks/pre-commit`) 41 | 42 | Or you can just run the commands manually before committing. 43 | 44 | #### Testing Notes 45 | 46 | Running `grunt server` will host the jasmine specs at http://localhost:8888/spec. 47 | Please use `.transitionDuration(0)` for all chart tests. 48 | 49 | # Merging Pull Requests 50 | 51 | _for maintainers_ 52 | 53 | Ensure origin looks like this in `.git/config`. The key element here is the second fetch statement 54 | ``` 55 | [remote "origin"] 56 | url = git@github.com:dc-js/dc.js.git 57 | fetch = +refs/heads/*:refs/remotes/origin/* 58 | fetch = +refs/pull/*/head:refs/remotes/origin/pr/* 59 | ``` 60 | 61 | Run these commands (or their approximation): 62 | ``` 63 | # clean master branch 64 | git stash 65 | 66 | # update node modules that may be pulled into `web/` 67 | npm update 68 | 69 | # double check your aren't going to blow away local commits 70 | git fetch origin 71 | git checkout master 72 | git diff origin/master 73 | 74 | # Merge - subsitute the PR number for $PR. Warning this runs git reset --hard, ensure you are ready 75 | grunt merge:$PR 76 | 77 | # manually verify the changes and review the demos/examples 78 | 79 | # deploy 80 | git push origin master 81 | 82 | # review changes before site deployment 83 | git diff origin/gh-pages master:web 84 | grunt web 85 | ``` 86 | 87 | If needed, the baseline test for the demos can be rebuilt by running `grunt update-stock-regression`. 88 | -------------------------------------------------------------------------------- /src/data-count.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Data Count Widget 3 | Includes: [Base Mixin](#base-mixin) 4 | 5 | The data count widget is a simple widget designed to display the number of records selected by the 6 | current filters out of the total number of records in the data set. Once created the data count widget 7 | will automatically update the text content of the following elements under the parent element. 8 | 9 | * '.total-count' - total number of records 10 | * '.filter-count' - number of records matched by the current filters 11 | 12 | Examples: 13 | 14 | * [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) 15 | #### dc.dataCount(parent[, chartGroup]) 16 | Create a data count widget and attach it to the given parent element. 17 | 18 | Parameters: 19 | 20 | * parent : string | node | selection - any valid 21 | [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying 22 | a dom block element such as a div; or a dom element or d3 selection. 23 | * chartGroup : string (optional) - name of the chart group this widget should be placed in. 24 | The data count widget will only react to filter changes in the chart group. 25 | 26 | Returns: 27 | A newly created data count widget instance 28 | #### .dimension(allData) - **mandatory** 29 | For the data count widget the only valid dimension is the entire data set. 30 | #### .group(groupAll) - **mandatory** 31 | For the data count widget the only valid group is the group returned by `dimension.groupAll()`. 32 | 33 | ```js 34 | var ndx = crossfilter(data); 35 | var all = ndx.groupAll(); 36 | 37 | dc.dataCount('.dc-data-count') 38 | .dimension(ndx) 39 | .group(all); 40 | ``` 41 | 42 | **/ 43 | dc.dataCount = function (parent, chartGroup) { 44 | var _formatNumber = d3.format(',d'); 45 | var _chart = dc.baseMixin({}); 46 | var _html = {some:'', all:''}; 47 | 48 | /** 49 | #### html([object]) 50 | Gets or sets an optional object specifying HTML templates to use depending how many items are 51 | selected. The text `%total-count` will replaced with the total number of records, and the text 52 | `%filter-count` will be replaced with the number of selected records. 53 | - all: HTML template to use if all items are selected 54 | - some: HTML template to use if not all items are selected 55 | 56 | ```js 57 | counter.html({ 58 | some: '%filter-count out of %total-count records selected', 59 | all: 'All records selected. Click on charts to apply filters' 60 | }) 61 | ``` 62 | **/ 63 | _chart.html = function (s) { 64 | if (!arguments.length) { 65 | return _html; 66 | } 67 | if (s.all) { 68 | _html.all = s.all; 69 | } 70 | if (s.some) { 71 | _html.some = s.some; 72 | } 73 | return _chart; 74 | }; 75 | 76 | _chart._doRender = function () { 77 | var tot = _chart.dimension().size(), 78 | val = _chart.group().value(); 79 | var all = _formatNumber(tot); 80 | var selected = _formatNumber(val); 81 | 82 | if ((tot === val) && (_html.all !== '')) { 83 | _chart.root().html(_html.all.replace('%total-count', all).replace('%filter-count', selected)); 84 | } else if (_html.some !== '') { 85 | _chart.root().html(_html.some.replace('%total-count', all).replace('%filter-count', selected)); 86 | } else { 87 | _chart.selectAll('.total-count').text(all); 88 | _chart.selectAll('.filter-count').text(selected); 89 | } 90 | return _chart; 91 | }; 92 | 93 | _chart._doRedraw = function () { 94 | return _chart._doRender(); 95 | }; 96 | 97 | return _chart.anchor(parent, chartGroup); 98 | }; 99 | -------------------------------------------------------------------------------- /src/filters.js: -------------------------------------------------------------------------------- 1 | dc.filters = {}; 2 | 3 | /** 4 | ## Filters 5 | The dc.js filters are functions which are passed into crossfilter to chose which records will be 6 | accumulated to produce values for the charts. In the crossfilter model, any filters applied on one 7 | dimension will affect all the other dimensions but not that one. dc always applies a filter 8 | function to the dimension; the function combines multiple filters and if any of them accept a 9 | record, it is filtered in. 10 | 11 | These filter constructors are used as appropriate by the various charts to implement brushing. We 12 | mention below which chart uses which filter. In some cases, many instances of a filter will be added. 13 | 14 | **/ 15 | 16 | /** 17 | #### dc.filters.RangedFilter(low, high) 18 | RangedFilter is a filter which accepts keys between `low` and `high`. It is used to implement X 19 | axis brushing for the [coordinate grid charts](#coordinate-grid-mixin). 20 | **/ 21 | dc.filters.RangedFilter = function (low, high) { 22 | var range = new Array(low, high); 23 | range.isFiltered = function (value) { 24 | return value >= this[0] && value < this[1]; 25 | }; 26 | 27 | return range; 28 | }; 29 | 30 | /** 31 | #### dc.filters.TwoDimensionalFilter(array) 32 | TwoDimensionalFilter is a filter which accepts a single two-dimensional value. It is used by the 33 | [heat map chart](#heat-map) to include particular cells as they are clicked. (Rows and columns are 34 | filtered by filtering all the cells in the row or column.) 35 | **/ 36 | dc.filters.TwoDimensionalFilter = function (array) { 37 | if (array === null) { return null; } 38 | 39 | var filter = array; 40 | filter.isFiltered = function (value) { 41 | return value.length && value.length === filter.length && 42 | value[0] === filter[0] && value[1] === filter[1]; 43 | }; 44 | 45 | return filter; 46 | }; 47 | 48 | /** 49 | #### dc.filters.RangedTwoDimensionalFilter(array) 50 | The RangedTwoDimensionalFilter allows filtering all values which fit within a rectangular 51 | region. It is used by the [scatter plot](#scatter-plot) to implement rectangular brushing. 52 | 53 | It takes two two-dimensional points in the form `[[x1,y1],[x2,y2]]`, and normalizes them so that 54 | `x1 <= x2` and `y1 <- y2`. It then returns a filter which accepts any points which are in the 55 | rectangular range including the lower values but excluding the higher values. 56 | 57 | If an array of two values are given to the RangedTwoDimensionalFilter, it interprets the values as 58 | two x coordinates `x1` and `x2` and returns a filter which accepts any points for which `x1 <= x < 59 | x2`. 60 | **/ 61 | dc.filters.RangedTwoDimensionalFilter = function (array) { 62 | if (array === null) { return null; } 63 | 64 | var filter = array; 65 | var fromBottomLeft; 66 | 67 | if (filter[0] instanceof Array) { 68 | fromBottomLeft = [ 69 | [Math.min(array[0][0], array[1][0]), Math.min(array[0][1], array[1][1])], 70 | [Math.max(array[0][0], array[1][0]), Math.max(array[0][1], array[1][1])] 71 | ]; 72 | } else { 73 | fromBottomLeft = [[array[0], -Infinity], [array[1], Infinity]]; 74 | } 75 | 76 | filter.isFiltered = function (value) { 77 | var x, y; 78 | 79 | if (value instanceof Array) { 80 | if (value.length !== 2) { 81 | return false; 82 | } 83 | x = value[0]; 84 | y = value[1]; 85 | } else { 86 | x = value; 87 | y = fromBottomLeft[0][1]; 88 | } 89 | 90 | return x >= fromBottomLeft[0][0] && x < fromBottomLeft[1][0] && 91 | y >= fromBottomLeft[0][1] && y < fromBottomLeft[1][1]; 92 | }; 93 | 94 | return filter; 95 | }; 96 | -------------------------------------------------------------------------------- /web/examples/right-axis.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dc.js - Right Axis Example 5 | 6 | 7 | 8 | 9 | 10 |
11 | Monthly Index Abs Move & Volume Chart 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | dc.dateFormat = d3.time.format('%m/%d/%Y'); 2 | 3 | dc.printers = {}; 4 | 5 | dc.printers.filters = function (filters) { 6 | var s = ''; 7 | 8 | for (var i = 0; i < filters.length; ++i) { 9 | if (i > 0) { 10 | s += ', '; 11 | } 12 | s += dc.printers.filter(filters[i]); 13 | } 14 | 15 | return s; 16 | }; 17 | 18 | dc.printers.filter = function (filter) { 19 | var s = ''; 20 | 21 | if (typeof filter !== 'undefined' && filter !== null) { 22 | if (filter instanceof Array) { 23 | if (filter.length >= 2) { 24 | s = '[' + dc.utils.printSingleValue(filter[0]) + ' -> ' + dc.utils.printSingleValue(filter[1]) + ']'; 25 | } else if (filter.length >= 1) { 26 | s = dc.utils.printSingleValue(filter[0]); 27 | } 28 | } else { 29 | s = dc.utils.printSingleValue(filter); 30 | } 31 | } 32 | 33 | return s; 34 | }; 35 | 36 | dc.pluck = function (n, f) { 37 | if (!f) { 38 | return function (d) { return d[n]; }; 39 | } 40 | return function (d, i) { return f.call(d, d[n], i); }; 41 | }; 42 | 43 | dc.utils = {}; 44 | 45 | dc.utils.printSingleValue = function (filter) { 46 | var s = '' + filter; 47 | 48 | if (filter instanceof Date) { 49 | s = dc.dateFormat(filter); 50 | } else if (typeof(filter) === 'string') { 51 | s = filter; 52 | } else if (dc.utils.isFloat(filter)) { 53 | s = dc.utils.printSingleValue.fformat(filter); 54 | } else if (dc.utils.isInteger(filter)) { 55 | s = Math.round(filter); 56 | } 57 | 58 | return s; 59 | }; 60 | dc.utils.printSingleValue.fformat = d3.format('.2f'); 61 | 62 | // FIXME: these assume than any string r is a percentage (whether or not it 63 | // includes %). They also generate strange results if l is a string. 64 | dc.utils.add = function (l, r) { 65 | if (typeof r === 'string') { 66 | r = r.replace('%', ''); 67 | } 68 | 69 | if (l instanceof Date) { 70 | if (typeof r === 'string') { 71 | r = +r; 72 | } 73 | var d = new Date(); 74 | d.setTime(l.getTime()); 75 | d.setDate(l.getDate() + r); 76 | return d; 77 | } else if (typeof r === 'string') { 78 | var percentage = (+r / 100); 79 | return l > 0 ? l * (1 + percentage) : l * (1 - percentage); 80 | } else { 81 | return l + r; 82 | } 83 | }; 84 | 85 | dc.utils.subtract = function (l, r) { 86 | if (typeof r === 'string') { 87 | r = r.replace('%', ''); 88 | } 89 | 90 | if (l instanceof Date) { 91 | if (typeof r === 'string') { 92 | r = +r; 93 | } 94 | var d = new Date(); 95 | d.setTime(l.getTime()); 96 | d.setDate(l.getDate() - r); 97 | return d; 98 | } else if (typeof r === 'string') { 99 | var percentage = (+r / 100); 100 | return l < 0 ? l * (1 + percentage) : l * (1 - percentage); 101 | } else { 102 | return l - r; 103 | } 104 | }; 105 | 106 | dc.utils.isNumber = function (n) { 107 | return n === +n; 108 | }; 109 | 110 | dc.utils.isFloat = function (n) { 111 | return n === +n && n !== (n | 0); 112 | }; 113 | 114 | dc.utils.isInteger = function (n) { 115 | return n === +n && n === (n | 0); 116 | }; 117 | 118 | dc.utils.isNegligible = function (n) { 119 | return !dc.utils.isNumber(n) || (n < dc.constants.NEGLIGIBLE_NUMBER && n > -dc.constants.NEGLIGIBLE_NUMBER); 120 | }; 121 | 122 | dc.utils.clamp = function (val, min, max) { 123 | return val < min ? min : (val > max ? max : val); 124 | }; 125 | 126 | var _idCounter = 0; 127 | dc.utils.uniqueId = function () { 128 | return ++_idCounter; 129 | }; 130 | 131 | dc.utils.nameToId = function (name) { 132 | return name.toLowerCase().replace(/[\s]/g, '_').replace(/[\.']/g, ''); 133 | }; 134 | 135 | dc.utils.appendOrSelect = function (parent, name) { 136 | var element = parent.select(name); 137 | if (element.empty()) { 138 | element = parent.append(name); 139 | } 140 | return element; 141 | }; 142 | 143 | dc.utils.safeNumber = function (n) { return dc.utils.isNumber(+n) ? +n : 0;}; 144 | -------------------------------------------------------------------------------- /src/cap-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Cap Mixin 3 | Cap is a mixin that groups small data elements below a _cap_ into an *others* grouping for both the 4 | Row and Pie Charts. 5 | 6 | The top ordered elements in the group up to the cap amount will be kept in the chart, and the rest 7 | will be replaced with an *others* element, with value equal to the sum of the replaced values. The 8 | keys of the elements below the cap limit are recorded in order to filter by those keys when the 9 | *others* element is clicked. 10 | 11 | **/ 12 | dc.capMixin = function (_chart) { 13 | 14 | var _cap = Infinity; 15 | 16 | var _othersLabel = 'Others'; 17 | 18 | var _othersGrouper = function (topRows) { 19 | var topRowsSum = d3.sum(topRows, _chart.valueAccessor()), 20 | allRows = _chart.group().all(), 21 | allRowsSum = d3.sum(allRows, _chart.valueAccessor()), 22 | topKeys = topRows.map(_chart.keyAccessor()), 23 | allKeys = allRows.map(_chart.keyAccessor()), 24 | topSet = d3.set(topKeys), 25 | others = allKeys.filter(function (d) {return !topSet.has(d);}); 26 | if (allRowsSum > topRowsSum) { 27 | return topRows.concat([{'others': others, 'key': _othersLabel, 'value': allRowsSum - topRowsSum}]); 28 | } 29 | return topRows; 30 | }; 31 | 32 | _chart.cappedKeyAccessor = function (d, i) { 33 | if (d.others) { 34 | return d.key; 35 | } 36 | return _chart.keyAccessor()(d, i); 37 | }; 38 | 39 | _chart.cappedValueAccessor = function (d, i) { 40 | if (d.others) { 41 | return d.value; 42 | } 43 | return _chart.valueAccessor()(d, i); 44 | }; 45 | 46 | _chart.data(function (group) { 47 | if (_cap === Infinity) { 48 | return _chart._computeOrderedGroups(group.all()); 49 | } else { 50 | var topRows = group.top(_cap); // ordered by crossfilter group order (default value) 51 | topRows = _chart._computeOrderedGroups(topRows); // re-order using ordering (default key) 52 | if (_othersGrouper) { 53 | return _othersGrouper(topRows); 54 | } 55 | return topRows; 56 | } 57 | }); 58 | 59 | /** 60 | #### .cap([count]) 61 | Get or set the count of elements to that will be included in the cap. 62 | **/ 63 | _chart.cap = function (_) { 64 | if (!arguments.length) { 65 | return _cap; 66 | } 67 | _cap = _; 68 | return _chart; 69 | }; 70 | 71 | /** 72 | #### .othersLabel([label]) 73 | Get or set the label for *Others* slice when slices cap is specified. Default label is **Others**. 74 | **/ 75 | _chart.othersLabel = function (_) { 76 | if (!arguments.length) { 77 | return _othersLabel; 78 | } 79 | _othersLabel = _; 80 | return _chart; 81 | }; 82 | 83 | /** 84 | #### .othersGrouper([grouperFunction]) 85 | Get or set the grouper function that will perform the insertion of data for the *Others* slice 86 | if the slices cap is specified. If set to a falsy value, no others will be added. By default the 87 | grouper function computes the sum of all values below the cap. 88 | ```js 89 | chart.othersGrouper(function (data) { 90 | // compute the value for others, presumably the sum of all values below the cap 91 | var othersSum = yourComputeOthersValueLogic(data) 92 | 93 | // the keys are needed to properly filter when the others element is clicked 94 | var othersKeys = yourComputeOthersKeysArrayLogic(data); 95 | 96 | // add the others row to the dataset 97 | data.push({'key': 'Others', 'value': othersSum, 'others': othersKeys }); 98 | 99 | return data; 100 | }); 101 | ``` 102 | **/ 103 | _chart.othersGrouper = function (_) { 104 | if (!arguments.length) { 105 | return _othersGrouper; 106 | } 107 | _othersGrouper = _; 108 | return _chart; 109 | }; 110 | 111 | dc.override(_chart, 'onClick', function (d) { 112 | if (d.others) { 113 | _chart.filter([d.others]); 114 | } 115 | _chart._onClick(d); 116 | }); 117 | 118 | return _chart; 119 | }; 120 | -------------------------------------------------------------------------------- /spec/data-grid-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc.dataGrid', function() { 2 | var id, chart, data; 3 | var dateFixture; 4 | var dimension, group; 5 | var countryDimension; 6 | 7 | beforeEach(function() { 8 | dateFixture = loadDateFixture(); 9 | data = crossfilter(dateFixture); 10 | dimension = data.dimension(function(d) { 11 | return d3.time.day.utc(d.dd); 12 | }); 13 | countryDimension = data.dimension(function(d) { 14 | return d.countrycode; 15 | }); 16 | 17 | id = "data-grid"; 18 | appendChartID(id); 19 | chart = dc.dataGrid("#" + id) 20 | .dimension(dimension) 21 | .group(function(d) { 22 | return "Data Grid"; 23 | }) 24 | .transitionDuration(0) 25 | .size(3) 26 | .sortBy(function(d){return d.id;}) 27 | .order(d3.descending) 28 | .html(function(d) { 29 | return "
"+d.state+":"+d.value+"
"; 30 | }); 31 | chart.render(); 32 | var pause="dummy"; 33 | }); 34 | 35 | describe('creation', function() { 36 | it('generates something', function() { 37 | expect(chart).not.toBeNull(); 38 | }); 39 | it('registers', function() { 40 | expect(dc.hasChart(chart)).toBeTruthy(); 41 | }); 42 | it('sets size', function() { 43 | expect(chart.size()).toEqual(3); 44 | }); 45 | it('sets sortBy', function() { 46 | expect(chart.sortBy()).not.toBeNull(); 47 | }); 48 | it('sets order', function() { 49 | expect(chart.order()).toBe(d3.descending); 50 | }); 51 | it('sets the group label', function() { 52 | expect(chart.selectAll(".dc-grid-group h1.dc-grid-label")[0][0].innerHTML).toEqual("Data Grid"); 53 | }); 54 | it('creates id div', function() { 55 | expect(chart.selectAll(".dc-grid-item div#id_9")[0].length).toEqual(1); 56 | expect(chart.selectAll(".dc-grid-item div#id_8")[0].length).toEqual(1); 57 | expect(chart.selectAll(".dc-grid-item div#id_3")[0].length).toEqual(1); 58 | }); 59 | it('creates div content', function() { 60 | expect(chart.selectAll(".dc-grid-item div")[0][0].innerHTML).toEqual("Mississippi:44"); 61 | expect(chart.selectAll(".dc-grid-item div")[0][1].innerHTML).toEqual("Mississippi:33"); 62 | expect(chart.selectAll(".dc-grid-item div")[0][2].innerHTML).toEqual("Delaware:33"); 63 | }); 64 | }); 65 | 66 | describe('external filter', function() { 67 | beforeEach(function() { 68 | countryDimension.filter("CA"); 69 | chart.redraw(); 70 | }); 71 | it('renders only filtered data set', function() { 72 | expect(chart.selectAll(".dc-grid-item div")[0].length).toEqual(2); 73 | }); 74 | it('renders the correctly filtered records', function() { 75 | expect(chart.selectAll(".dc-grid-item div")[0][0].innerHTML).toEqual('Ontario:22'); 76 | expect(chart.selectAll(".dc-grid-item div")[0][1].innerHTML).toEqual('Ontario:55'); 77 | }); 78 | }); 79 | 80 | describe('renderlet', function() { 81 | var derlet; 82 | beforeEach(function() { 83 | derlet = jasmine.createSpy('renderlet', function(chart) { 84 | chart.selectAll(".dc-grid-label").text("changed"); 85 | }); 86 | derlet.and.callThrough(); 87 | chart.renderlet(derlet); 88 | }); 89 | it('custom renderlet should be invoked with render', function() { 90 | chart.render(); 91 | expect(chart.selectAll(".dc-grid-label").text()).toEqual("changed"); 92 | expect(derlet).toHaveBeenCalled(); 93 | }); 94 | it('custom renderlet should be invoked with redraw', function() { 95 | chart.redraw(); 96 | expect(chart.selectAll(".dc-grid-label").text()).toEqual("changed"); 97 | expect(derlet).toHaveBeenCalled(); 98 | }); 99 | }); 100 | 101 | afterEach(function() { 102 | dimension.filterAll(); 103 | countryDimension.filterAll(); 104 | }); 105 | }); 106 | 107 | 108 | -------------------------------------------------------------------------------- /web/geo/us-counties.json: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[ 2 | {"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-141.00045,68.498147],[-146.000897,68.487193],[-145.99542,67.999745],[-146.970315,67.999745],[-155.300742,67.999745],[-157.157424,68.207869],[-161.993563,68.2243],[-162.683657,68.300977],[-164.502001,68.229777],[-164.496525,68.021653],[-165.361881,68.02713],[-166.681824,68.339316],[-166.216284,68.881533],[-164.255539,68.930825],[-163.110859,69.374457],[-163.034181,69.724981],[-161.851162,70.311014],[-161.396576,70.239814],[-159.649432,70.792985],[-158.033735,70.831323],[-156.812377,71.285909],[-155.585543,71.170894],[-155.974405,70.809416],[-155.032372,71.148986],[-154.183446,70.7656],[-153.235935,70.924431],[-152.26104,70.842277],[-152.419871,70.606769],[-151.877654,70.431507],[-149.462323,70.519138],[-147.682318,70.201475],[-145.858496,70.168614],[-144.620708,69.971444],[-143.914183,70.130275],[-142.747594,70.042644],[-141.378359,69.63735],[-141.00045,69.648304],[-141.00045,68.498147]]]},"id":"02185"}, 3 | {"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-146.970315,67.999745],[-145.99542,67.999745],[-146.000897,68.487193],[-141.00045,68.498147],[-141.00045,65.841831],[-141.838422,65.463922],[-142.605194,65.392722],[-144.06206,64.680719],[-143.886798,65.09149],[-145.644896,65.031244],[-146.19259,65.452968],[-146.696468,65.321521],[-148.66269,65.211983],[-148.646259,64.604042],[-147.99998,64.341149],[-149.232292,64.35758],[-150.749404,64.363057],[-151.302575,64.012533],[-152.047439,64.001579],[-152.244609,63.656532],[-152.852549,63.651055],[-152.436302,63.169084],[-153.000427,62.725452],[-153.000427,62.292773],[-153.734337,62.02988],[-157.064316,62.02988],[-158.532136,62.117511],[-159.266046,61.947726],[-160.536697,61.947726],[-161.046052,62.205142],[-160.848882,63.010253],[-160.690051,63.536039],[-159.698725,63.793455],[-159.956141,64.050871],[-159.978049,64.746443],[-159.578232,64.921705],[-159.594663,65.524168],[-159.594663,65.956846],[-158.964815,66.121155],[-157.896811,66.126632],[-157.891334,66.477156],[-155.563635,66.307371],[-155.514342,66.570264],[-154.199877,66.718141],[-154.145107,67.161773],[-154.747571,67.254881],[-154.687324,67.599929],[-155.350034,67.775191],[-155.300742,67.999745],[-146.970315,67.999745]]]},"id":"02290"}, 4 | {"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-162.683657,68.300977],[-161.993563,68.2243],[-157.157424,68.207869],[-155.300742,67.999745],[-155.350034,67.775191],[-154.687324,67.599929],[-154.747571,67.254881],[-154.145107,67.161773],[-154.199877,66.718141],[-155.514342,66.570264],[-155.563635,66.307371],[-157.891334,66.477156],[-157.896811,66.126632],[-158.964815,66.121155],[-159.594663,65.956846],[-159.594663,65.524168],[-159.802787,65.436537],[-163.757138,65.436537],[-164.244585,65.781584],[-164.403417,66.581218],[-163.751661,66.553833],[-163.768091,66.060908],[-161.840208,66.02257],[-160.991283,66.23617],[-162.502918,66.740049],[-161.884024,66.718141],[-161.851162,67.052235],[-162.902735,67.008419],[-163.740707,67.128912],[-164.009077,67.534205],[-165.361881,68.02713],[-164.496525,68.021653],[-164.502001,68.229777],[-162.683657,68.300977]]]},"id":"02188"}, 5 | {"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-164.403417,66.581218],[-164.244585,65.781584],[-163.757138,65.436537],[-159.802787,65.436537],[-159.594663,65.524168],[-159.578232,64.921705],[-159.978049,64.746443],[-159.956141,64.050871],[-159.698725,63.793455],[-160.690051,63.536039],[-160.848882,63.010253],[-161.993563,63.010253],[-162.585072,63.273146],[-162.294794,63.541516],[-161.13916,63.503177],[-160.766728,63.771547],[-161.374669,64.532842],[-160.783159,64.719058],[-161.144637,64.921705],[-162.541257,64.532842],[-162.787719,64.324718],[-163.598306,64.565704],[-165.000403,64.434257],[-166.188899,64.576658],[-166.884471,65.140782],[-166.34773,65.277706],[-167.47598,65.414629],[-168.105828,65.682999],[-165.88219,66.312848],[-164.403417,66.581218]]]},"id":"02180"}, 6 | {"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-141.838422,65.463922],[-141.00045,65.841831],[-141.00045,61.903911],[-141.827468,61.898434],[-141.964392,62.511851],[-142.314916,62.681636],[-143.103595,62.615913],[-143.125503,63.114314],[-145.146494,63.136222],[-145.360095,63.223853],[-146.482868,63.174561],[-146.488345,63.481269],[-146.975792,63.481269],[-147.003177,64.258995],[-146.236405,64.308287],[-144.06206,64.680719],[-142.605194,65.392722],[-141.838422,65.463922]]]},"id":"02240"}]} -------------------------------------------------------------------------------- /spec/utils-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc utils', function() { 2 | describe('dc.printer.filters', function() { 3 | var printer; 4 | beforeEach(function() { 5 | printer = dc.printers.filters; 6 | }); 7 | it('print simple string', function() { 8 | expect(printer(["a"])).toEqual("a"); 9 | }); 10 | it('print range', function() { 11 | expect(printer([[10, 30]])).toEqual("[10 -> 30]"); 12 | }); 13 | it('print simple string and a range', function() { 14 | expect(printer(["a", [10, 30]]), "a).toEqual([10 -> 30]"); 15 | }); 16 | }); 17 | 18 | describe('dc.printer.filter', function() { 19 | var printer; 20 | beforeEach(function() { 21 | printer = dc.printers.filter; 22 | dc.dateFormat = d3.time.format.utc("%m/%d/%Y"); 23 | }); 24 | it('print simple string', function() { 25 | expect(printer("a")).toEqual("a"); 26 | }); 27 | it('print date string', function() { 28 | expect(printer(makeDate(2012, 1, 1))).toEqual("02/01/2012"); 29 | }); 30 | it('print int range', function() { 31 | expect(printer([10, 30])).toEqual("[10 -> 30]"); 32 | }); 33 | it('print float range', function() { 34 | expect(printer([10.124244, 30.635623])).toEqual("[10.12 -> 30.64]"); 35 | }); 36 | it('print date range', function() { 37 | expect(printer([makeDate(2012, 1, 1), makeDate(2012, 1, 15)])).toEqual("[02/01/2012 -> 02/15/2012]"); 38 | }); 39 | it('print single element array', function() { 40 | expect(printer([makeDate(2012, 1, 1)])).toEqual("02/01/2012"); 41 | }); 42 | it('print null', function() { 43 | expect(printer(null)).toEqual(""); 44 | }); 45 | it('print zero', function() { 46 | expect(printer(0)).toEqual(0); 47 | }); 48 | }); 49 | 50 | describe('dc.utils.nameToId', function() { 51 | it('id should be escaped properly', function() { 52 | expect(dc.utils.nameToId("St. John's")).toEqual("st_johns"); 53 | }); 54 | }); 55 | 56 | describe('dc.utils.add', function() { 57 | var add; 58 | beforeEach(function() { 59 | add = dc.utils.add; 60 | }); 61 | it('should be able to add days', function() { 62 | var date = add(makeDate(2012, 0, 1), 10); 63 | expect(date.toString()).toEqual((makeDate(2012, 0, 11)).toString()); 64 | }); 65 | it('should be able to add numbers', function() { 66 | var num = add(10, 10); 67 | expect(num).toEqual(20); 68 | }); 69 | it('should be able to add numbers w/ %', function() { 70 | var num = add(10, "10%"); 71 | expect(num).toEqual(11); 72 | }); 73 | it('should be able to add negative numbers w/ %', function() { 74 | var num = add(-10, "10%"); 75 | expect(num).toEqual(-9); 76 | }); 77 | it('should ignore % when adding dates', function() { 78 | var date = add(makeDate(2012, 0, 1), "10%"); 79 | expect(date.toString()).toEqual(makeDate(2012, 0, 11).toString()); 80 | }); 81 | }); 82 | describe('dc.utils.subtract', function() { 83 | var subtract; 84 | beforeEach(function() { 85 | subtract = dc.utils.subtract; 86 | }); 87 | it('should be able to subtract dates', function() { 88 | var date = subtract(makeDate(2012, 0, 11), 10); 89 | expect(date.toString()).toEqual((makeDate(2012, 0, 1)).toString()); 90 | }); 91 | it('should be able to subtract numbers', function() { 92 | var num = subtract(10, 10); 93 | expect(num).toEqual(0); 94 | }); 95 | it('should be able to subtract numbers w/ %', function() { 96 | var num = subtract(10, "10%"); 97 | expect(num).toEqual(9); 98 | }); 99 | it('should be able to subtract negative numbers w/ %', function() { 100 | var num = subtract(-10, "10%"); 101 | expect(num).toEqual(-11); 102 | }); 103 | it('should ignore % when subtracting dates', function() { 104 | var date = subtract(makeDate(2012, 0, 11), "10%"); 105 | expect(date.toString()).toEqual(makeDate(2012, 0, 1).toString()); 106 | }); 107 | }); 108 | }); 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/color-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Color Mixin 3 | The Color Mixin is an abstract chart functional class providing universal coloring support 4 | as a mix-in for any concrete chart implementation. 5 | 6 | **/ 7 | 8 | dc.colorMixin = function (_chart) { 9 | var _colors = d3.scale.category20c(); 10 | var _defaultAccessor = true; 11 | 12 | var _colorAccessor = function (d) { return _chart.keyAccessor()(d); }; 13 | 14 | /** 15 | #### .colors([colorScale]) 16 | Retrieve current color scale or set a new color scale. This methods accepts any function that 17 | operates like a d3 scale. If not set the default is 18 | `d3.scale.category20c()`. 19 | ```js 20 | // alternate categorical scale 21 | chart.colors(d3.scale.category20b()); 22 | 23 | // ordinal scale 24 | chart.colors(d3.scale.ordinal().range(['red','green','blue'])); 25 | // convenience method, the same as above 26 | chart.ordinalColors(['red','green','blue']); 27 | 28 | // set a linear scale 29 | chart.linearColors(["#4575b4", "#ffffbf", "#a50026"]); 30 | ``` 31 | **/ 32 | _chart.colors = function (_) { 33 | if (!arguments.length) { 34 | return _colors; 35 | } 36 | if (_ instanceof Array) { 37 | _colors = d3.scale.quantize().range(_); // deprecated legacy support, note: this fails for ordinal domains 38 | } else { 39 | _colors = d3.functor(_); 40 | } 41 | return _chart; 42 | }; 43 | 44 | /** 45 | #### .ordinalColors(r) 46 | Convenience method to set the color scale to d3.scale.ordinal with range `r`. 47 | 48 | **/ 49 | _chart.ordinalColors = function (r) { 50 | return _chart.colors(d3.scale.ordinal().range(r)); 51 | }; 52 | 53 | /** 54 | #### .linearColors(r) 55 | Convenience method to set the color scale to an Hcl interpolated linear scale with range `r`. 56 | 57 | **/ 58 | _chart.linearColors = function (r) { 59 | return _chart.colors(d3.scale.linear() 60 | .range(r) 61 | .interpolate(d3.interpolateHcl)); 62 | }; 63 | 64 | /** 65 | #### .colorAccessor([colorAccessorFunction]) 66 | Set or the get color accessor function. This function will be used to map a data point in a 67 | crossfilter group to a color value on the color scale. The default function uses the key 68 | accessor. 69 | ```js 70 | // default index based color accessor 71 | .colorAccessor(function (d, i){return i;}) 72 | // color accessor for a multi-value crossfilter reduction 73 | .colorAccessor(function (d){return d.value.absGain;}) 74 | ``` 75 | **/ 76 | _chart.colorAccessor = function (_) { 77 | if (!arguments.length) { 78 | return _colorAccessor; 79 | } 80 | _colorAccessor = _; 81 | _defaultAccessor = false; 82 | return _chart; 83 | }; 84 | 85 | // what is this? 86 | _chart.defaultColorAccessor = function () { 87 | return _defaultAccessor; 88 | }; 89 | 90 | /** 91 | #### .colorDomain([domain]) 92 | Set or get the current domain for the color mapping function. The domain must be supplied as an 93 | array. 94 | 95 | Note: previously this method accepted a callback function. Instead you may use a custom scale 96 | set by `.colors`. 97 | 98 | **/ 99 | _chart.colorDomain = function (_) { 100 | if (!arguments.length) { 101 | return _colors.domain(); 102 | } 103 | _colors.domain(_); 104 | return _chart; 105 | }; 106 | 107 | /** 108 | #### .calculateColorDomain() 109 | Set the domain by determining the min and max values as retrieved by `.colorAccessor` over the 110 | chart's dataset. 111 | 112 | **/ 113 | _chart.calculateColorDomain = function () { 114 | var newDomain = [d3.min(_chart.data(), _chart.colorAccessor()), 115 | d3.max(_chart.data(), _chart.colorAccessor())]; 116 | _colors.domain(newDomain); 117 | }; 118 | 119 | /** 120 | #### .getColor(d [, i]) 121 | Get the color for the datum d and counter i. This is used internally by charts to retrieve a color. 122 | 123 | **/ 124 | _chart.getColor = function (d, i) { 125 | return _colors(_colorAccessor.call(this, d, i)); 126 | }; 127 | 128 | /** 129 | #### .colorCalculator([value]) 130 | Gets or sets chart.getColor. 131 | **/ 132 | _chart.colorCalculator = function (_) { 133 | if (!arguments.length) { 134 | return _chart.getColor; 135 | } 136 | _chart.getColor = _; 137 | return _chart; 138 | }; 139 | 140 | return _chart; 141 | }; 142 | -------------------------------------------------------------------------------- /spec/bubble-overlay-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc.bubbleOverlay', function() { 2 | var chart, data; 3 | var dimension, group; 4 | 5 | describe('creation', function() { 6 | beforeEach(function() { 7 | data = crossfilter(loadDateFixture()); 8 | dimension = data.dimension(function(d){return d.state;}); 9 | group = dimension.group().reduceSum(function(d){return d.value;}); 10 | 11 | var id = "bubble-overlay"; 12 | var parent = appendChartID(id); 13 | var svg = parent.append("svg"); 14 | 15 | chart = dc.bubbleOverlay("#" + id) 16 | .svg(svg) 17 | .dimension(dimension) 18 | .group(group) 19 | .width(300) 20 | .height(200) 21 | .transitionDuration(0) 22 | .title(function(d){return "Title: " + d.key;}) 23 | .r(d3.scale.linear().domain([0, 500])) 24 | .ordinalColors(['blue']) 25 | .point("California", 100, 120) 26 | .point("Colorado", 300, 120) 27 | .point("Delaware", 500, 220) 28 | .point("Ontario", 180, 90) 29 | .point("Mississippi", 120, 220) 30 | .point("Oklahoma", 200, 350); 31 | 32 | chart.render(); 33 | }); 34 | 35 | it('should generate an instance of the dc chart', function() { 36 | expect(dc.instanceOfChart(chart)).toBeTruthy(); 37 | }); 38 | 39 | it('should be registered', function() { 40 | expect(dc.hasChart(chart)).toBeTruthy(); 41 | }); 42 | 43 | it('should generate the correct number of overlay groups', function() { 44 | expect(chart.selectAll("g.node")[0].length).toEqual(6); 45 | }); 46 | 47 | it('should generate a correct class name for the overlay groups', function() { 48 | expect(d3.select(chart.selectAll("g.node")[0][0]).attr("class")).toEqual("node california"); 49 | expect(d3.select(chart.selectAll("g.node")[0][3]).attr("class")).toEqual("node ontario"); 50 | }); 51 | 52 | it('should generate the correct number of overlay bubbles', function() { 53 | expect(chart.selectAll("circle.bubble")[0].length).toEqual(6); 54 | }); 55 | 56 | it('should generate a correct translate for overlay groups', function() { 57 | expect(d3.select(chart.selectAll("g.node")[0][0]).attr("transform")).toMatchTranslate(100, 120); 58 | expect(d3.select(chart.selectAll("g.node")[0][3]).attr("transform")).toMatchTranslate(180, 90); 59 | }); 60 | 61 | it('should generate correct radii for circles', function() { 62 | expect(d3.select(chart.selectAll("circle.bubble")[0][0]).attr("r")).toEqual("34.64"); 63 | expect(d3.select(chart.selectAll("circle.bubble")[0][3]).attr("r")).toEqual("22.32"); 64 | }); 65 | 66 | it('should generate correct labels', function() { 67 | expect(d3.select(chart.selectAll("g.node text")[0][0]).text()).toEqual("California"); 68 | expect(d3.select(chart.selectAll("g.node text")[0][3]).text()).toEqual("Ontario"); 69 | }); 70 | 71 | it('should generate the label only once', function() { 72 | chart.redraw(); 73 | expect(chart.selectAll("g.node text")[0].length).toEqual(6); 74 | }); 75 | 76 | it('generate the correct titles', function() { 77 | expect(d3.select(chart.selectAll("g.node title")[0][0]).text()).toEqual("Title: California"); 78 | expect(d3.select(chart.selectAll("g.node title")[0][3]).text()).toEqual("Title: Ontario"); 79 | }); 80 | 81 | it('should only generate titles once', function() { 82 | chart.redraw(); 83 | expect(chart.selectAll("g.node title")[0].length).toEqual(6); 84 | }); 85 | 86 | it('should fill circles with the specified colors', function() { 87 | expect(d3.select(chart.selectAll("circle.bubble")[0][0]).attr("fill")).toEqual("blue"); 88 | expect(d3.select(chart.selectAll("circle.bubble")[0][3]).attr("fill")).toEqual("blue"); 89 | }); 90 | 91 | it('should highlight the filtered bubbles', function() { 92 | chart.filter("Colorado"); 93 | chart.filter("California"); 94 | chart.redraw(); 95 | expect(d3.select(chart.selectAll("g.node")[0][0]).attr("class")).toEqual("node california selected"); 96 | expect(d3.select(chart.selectAll("g.node")[0][1]).attr("class")).toEqual("node colorado selected"); 97 | expect(d3.select(chart.selectAll("g.node")[0][3]).attr("class")).toEqual("node ontario deselected"); 98 | }); 99 | }); 100 | }); 101 | 102 | 103 | -------------------------------------------------------------------------------- /spec/series-chart-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc.seriesChart', function() { 2 | 3 | var chart; 4 | var colorRows = [ 5 | {colData:1, rowData: 1, colorData: 1}, 6 | {colData:1, rowData: 2, colorData: 2}, 7 | {colData:2, rowData: 1, colorData: 3}, 8 | {colData:2, rowData: 2, colorData: 4} 9 | ]; 10 | var colorData = crossfilter(colorRows); 11 | 12 | beforeEach(function() { 13 | var dimensionColorData = colorData.dimension(function (d) { return [+d.colData, +d.rowData]; }); 14 | var groupColorData = dimensionColorData.group().reduceSum(function(d) { return +d.colorData; }); 15 | 16 | 17 | id = "series-chart"; 18 | appendChartID(id); 19 | chart = dc.seriesChart("#" + id); 20 | 21 | chart 22 | .width(210) 23 | .height(210) 24 | .x(d3.scale.linear().domain([1,2])) 25 | .dimension(dimensionColorData) 26 | .group(groupColorData) 27 | .ordinalColors(["#000001", "#000002"]) 28 | .seriesAccessor(function(d) { return +d.key[0];}) 29 | .keyAccessor(function(d) { return +d.key[1];}) 30 | .valueAccessor(function(d) { return +d.value ;}) 31 | .childOptions({renderArea: true, dashStyle: [3, 1, 1]}) 32 | .transitionDuration(0); 33 | }); 34 | 35 | describe('#render', function() { 36 | beforeEach(function() { 37 | chart.render(); 38 | }); 39 | 40 | it('should create the svg', function() { 41 | expect(chart.svg()).not.toBeNull(); 42 | }); 43 | 44 | it('should position generated lineCharts using the data', function() { 45 | var lines = chart.selectAll("path.line"); 46 | 47 | expect(d3.select(lines[0][0]).attr("d")).toMatchPath("M0,128L130,85"); 48 | expect(d3.select(lines[0][1]).attr("d")).toMatchPath("M0,43L130,0"); 49 | }); 50 | 51 | 52 | it('should color lines using the colors in the data', function() { 53 | var lines = chart.selectAll("path.line"); 54 | 55 | expect(d3.select(lines[0][0]).attr("stroke")).toBe("#000001"); 56 | expect(d3.select(lines[0][1]).attr("stroke")).toBe("#000002"); 57 | }); 58 | 59 | describe('with brush off', function () { 60 | it('should create line chart dots', function () { 61 | chart.brushOn(false).render(); 62 | var dots = chart.selectAll('circle.dot'); 63 | expect(dots[0].length).toEqual(4); 64 | chart.brushOn(true); 65 | }); 66 | }); 67 | }); 68 | 69 | 70 | describe('series sorting', function() { 71 | beforeEach(function() { 72 | chart 73 | .seriesSort(d3.descending) 74 | .render(); 75 | }); 76 | 77 | it('should order lineCharts in the order specified', function() { 78 | var lines = chart.selectAll("path.line"); 79 | 80 | expect(d3.select(lines[0][1]).attr("d")).toMatchPath("M0,128L130,85"); 81 | expect(d3.select(lines[0][0]).attr("d")).toMatchPath("M0,43L130,0"); 82 | }); 83 | }); 84 | 85 | describe('chart options', function () { 86 | beforeEach(function() { 87 | chart.render(); 88 | }); 89 | 90 | it('should apply options to all lines in the chart', function () { 91 | var lines = chart.selectAll("path.line"); 92 | var areas = chart.selectAll("path.area"); 93 | 94 | expect(d3.select(lines[0][0]).attr("stroke-dasharray")).toBe("3,1,1"); 95 | expect(d3.select(lines[0][1]).attr("stroke-dasharray")).toBe("3,1,1"); 96 | 97 | expect(d3.select(areas[0][0]).attr("fill")).toBe("#000001"); 98 | expect(d3.select(areas[0][1]).attr("fill")).toBe("#000002"); 99 | }); 100 | }); 101 | 102 | describe('#redraw', function () { 103 | var colorRows2 = [ 104 | {colData:1, rowData: 1, colorData: 1}, 105 | {colData:1, rowData: 2, colorData: 2}, 106 | {colData:2, rowData: 1, colorData: 3}, 107 | {colData:2, rowData: 2, colorData: 4}, 108 | {colData:3, rowData: 1, colorData: 5}, 109 | {colData:3, rowData: 2, colorData: 6} 110 | ]; 111 | var colorData2 = crossfilter(colorRows2); 112 | beforeEach(function () { 113 | chart.brushOn(false); 114 | chart.render(); 115 | 116 | var dimensionData = colorData2.dimension(function (d) { return [+d.colData, +d.rowData]; }); 117 | var groupData = dimensionData.group().reduceSum(function(d) { return +d.colorData; }); 118 | 119 | chart.dimension(dimensionData).group(groupData); 120 | 121 | 122 | chart.redraw(); 123 | }); 124 | 125 | afterEach(function () { 126 | chart.brushOn(true); 127 | }); 128 | 129 | it ('is redrawn with dots', function () { 130 | var dots = chart.selectAll('circle.dot'); 131 | expect(dots[0].length).toEqual(6); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /spec/data-addition-spec.js: -------------------------------------------------------------------------------- 1 | describe('Dynamic data addition in crossfilter', function() { 2 | var width = 200; 3 | var height = 200; 4 | var radius = 100; 5 | var baseData, moreData; 6 | 7 | beforeEach(function() { 8 | baseData = crossfilter(loadDateFixture()); 9 | moreData = loadDateFixture2(); 10 | }); 11 | 12 | function occurrences(str, value) { 13 | return (str.split(value)).length - 1; 14 | } 15 | 16 | describe('pie chart slice addition', function() { 17 | var valueDimension, valueGroup; 18 | var chart; 19 | function buildPieChart(id) { 20 | var div = appendChartID(id); 21 | div.append("a").attr("class", "reset").style("display", "none"); 22 | div.append("span").attr("class", "filter").style("display", "none"); 23 | var chart = dc.pieChart("#" + id); 24 | chart.dimension(valueDimension).group(valueGroup) 25 | .width(width) 26 | .height(height) 27 | .radius(radius) 28 | .transitionDuration(0); 29 | chart.render(); 30 | baseData.add(moreData); 31 | chart.expireCache(); 32 | return chart; 33 | } 34 | beforeEach(function() { 35 | valueDimension = baseData.dimension(function(d) { 36 | return d.value; 37 | }); 38 | valueGroup = valueDimension.group(); 39 | chart = buildPieChart("pie-chart"); 40 | chart.redraw(); 41 | }); 42 | it('slice g should be created with class', function() { 43 | expect(chart.selectAll("svg g g.pie-slice").data().length).toEqual(7); 44 | }); 45 | it('slice path should be created', function() { 46 | expect(chart.selectAll("svg g g.pie-slice path").data().length).toEqual(7); 47 | }); 48 | it('default function should be used to dynamically generate label', function() { 49 | expect(d3.select(chart.selectAll("text.pie-slice")[0][0]).text()).toEqual("11"); 50 | }); 51 | it('pie chart slices should be in numerical order', function() { 52 | expect(chart.selectAll("text.pie-slice").data().map(function(slice) { return slice.data.key; })) 53 | .toEqual(["11","22","33","44","55","66","76"]); 54 | }); 55 | it('default function should be used to dynamically generate title', function() { 56 | expect(d3.select(chart.selectAll("g.pie-slice title")[0][0]).text()).toEqual("11: 1"); 57 | }); 58 | afterEach(function() { 59 | valueDimension.filterAll(); 60 | }); 61 | }); 62 | describe('line chart segment addition', function() { 63 | var timeDimension, timeGroup; 64 | var chart; 65 | function buildLineChart(id) { 66 | appendChartID(id); 67 | var chart = dc.lineChart("#" + id); 68 | chart.dimension(timeDimension).group(timeGroup) 69 | .width(width).height(height) 70 | .x(d3.time.scale.utc().domain([makeDate(2012, 4, 20), makeDate(2012, 7, 15)])) 71 | .transitionDuration(0) 72 | .xUnits(d3.time.days.utc) 73 | .brushOn(false) 74 | .renderArea(true) 75 | .renderTitle(true); 76 | chart.render(); 77 | baseData.add(moreData); 78 | chart.expireCache(); 79 | return chart; 80 | } 81 | beforeEach(function() { 82 | timeDimension = baseData.dimension(function(d) { 83 | return d.dd; 84 | }); 85 | timeGroup = timeDimension.group(); 86 | chart = buildLineChart("line-chart"); 87 | chart.render(); 88 | }); 89 | it('number of dots should equal the size of the group', function() { 90 | expect(chart.selectAll("circle.dot")[0].length).toEqual(timeGroup.size()); 91 | }); 92 | it('number of line segments should equal the size of the group', function() { 93 | var path = chart.selectAll("path.line").attr("d"); 94 | expect(occurrences(path, 'L') + 1).toEqual(timeGroup.size()); 95 | }); 96 | it('number of area segments should equal twice the size of the group', function() { 97 | var path = chart.selectAll("path.area").attr("d"); 98 | expect(occurrences(path, 'L') + 1).toEqual(timeGroup.size() * 2); 99 | }); 100 | 101 | describe('resetting line chart with fewer data points', function() { 102 | beforeEach(function() { 103 | var chart = buildLineChart("stackable-line-chart"); 104 | chart.render(); 105 | 106 | timeDimension.filterAll(); 107 | baseData.remove(); 108 | baseData.add(moreData); 109 | chart.render(); 110 | }); 111 | 112 | it('it should not contain stale data points', function() { 113 | expect(chart.data()[0].values.length).toEqual(2); 114 | }); 115 | }); 116 | 117 | afterEach(function() { 118 | timeDimension.filterAll(); 119 | }); 120 | }); 121 | }); 122 | -------------------------------------------------------------------------------- /src/number-display.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Number Display Widget 3 | Includes: [Base Mixin](#base-mixin) 4 | 5 | A display of a single numeric value. 6 | 7 | Examples: 8 | 9 | * [Test Example](http://dc-js.github.io/dc.js/examples/number.html) 10 | #### dc.numberDisplay(parent[, chartGroup]) 11 | Create a Number Display instance and attach it to the given parent element. 12 | 13 | Unlike other charts, you do not need to set a dimension. Instead a group object must be provided and 14 | a valueAccessor that returns a single value. 15 | 16 | Parameters: 17 | 18 | * parent : string | node | selection - any valid 19 | [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying 20 | a dom block element such as a div; or a dom element or d3 selection. 21 | * chartGroup : string (optional) - name of the chart group this chart instance should be placed in. 22 | The number display widget will only react to filter changes in the chart group. 23 | 24 | Returns: 25 | A newly created number display instance 26 | 27 | ```js 28 | // create a number display under #chart-container1 element using the default global chart group 29 | var display1 = dc.numberDisplay('#chart-container1'); 30 | ``` 31 | 32 | **/ 33 | dc.numberDisplay = function (parent, chartGroup) { 34 | var SPAN_CLASS = 'number-display'; 35 | var _formatNumber = d3.format('.2s'); 36 | var _chart = dc.baseMixin({}); 37 | var _html = {one:'', some:'', none:''}; 38 | 39 | // dimension not required 40 | _chart._mandatoryAttributes(['group']); 41 | 42 | /** 43 | #### .html([object]) 44 | Gets or sets an optional object specifying HTML templates to use depending on the number 45 | displayed. The text `%number` will be replaced with the current value. 46 | - one: HTML template to use if the number is 1 47 | - zero: HTML template to use if the number is 0 48 | - some: HTML template to use otherwise 49 | 50 | ```js 51 | numberWidget.html({ 52 | one:'%number record', 53 | some:'%number records', 54 | none:'no records'}) 55 | ``` 56 | **/ 57 | 58 | _chart.html = function (s) { 59 | if (!arguments.length) { 60 | return _html; 61 | } 62 | if (s.none) { 63 | _html.none = s.none;//if none available 64 | } else if (s.one) { 65 | _html.none = s.one;//if none not available use one 66 | } else if (s.some) { 67 | _html.none = s.some;//if none and one not available use some 68 | } 69 | if (s.one) { 70 | _html.one = s.one;//if one available 71 | } else if (s.some) { 72 | _html.one = s.some;//if one not available use some 73 | } 74 | if (s.some) { 75 | _html.some = s.some;//if some available 76 | } else if (s.one) { 77 | _html.some = s.one;//if some not available use one 78 | } 79 | return _chart; 80 | }; 81 | 82 | /** 83 | #### .value() 84 | Calculate and return the underlying value of the display 85 | **/ 86 | 87 | _chart.value = function () { 88 | return _chart.data(); 89 | }; 90 | 91 | _chart.data(function (group) { 92 | var valObj = group.value ? group.value() : group.top(1)[0]; 93 | return _chart.valueAccessor()(valObj); 94 | }); 95 | 96 | _chart.transitionDuration(250); // good default 97 | 98 | _chart._doRender = function () { 99 | var newValue = _chart.value(), 100 | span = _chart.selectAll('.' + SPAN_CLASS); 101 | 102 | if (span.empty()) { 103 | span = span.data([0]) 104 | .enter() 105 | .append('span') 106 | .attr('class', SPAN_CLASS); 107 | } 108 | 109 | span.transition() 110 | .duration(_chart.transitionDuration()) 111 | .ease('quad-out-in') 112 | .tween('text', function () { 113 | var interp = d3.interpolateNumber(this.lastValue || 0, newValue); 114 | this.lastValue = newValue; 115 | return function (t) { 116 | var html = null, num = _chart.formatNumber()(interp(t)); 117 | if (newValue === 0 && (_html.none !== '')) { 118 | html = _html.none; 119 | } else if (newValue === 1 && (_html.one !== '')) { 120 | html = _html.one; 121 | } else if (_html.some !== '') { 122 | html = _html.some; 123 | } 124 | this.innerHTML = html ? html.replace('%number', num) : num; 125 | }; 126 | }); 127 | }; 128 | 129 | _chart._doRedraw = function () { 130 | return _chart._doRender(); 131 | }; 132 | 133 | /** 134 | #### .formatNumber([formatter]) 135 | Get or set a function to format the value for the display. By default `d3.format('.2s');` is used. 136 | 137 | **/ 138 | _chart.formatNumber = function (_) { 139 | if (!arguments.length) { 140 | return _formatNumber; 141 | } 142 | _formatNumber = _; 143 | return _chart; 144 | }; 145 | 146 | return _chart.anchor(parent, chartGroup); 147 | }; 148 | -------------------------------------------------------------------------------- /regression/stock-regression-test.js: -------------------------------------------------------------------------------- 1 | var grunt = require("grunt"); 2 | var phantomjs = require('grunt-lib-phantomjs').init(grunt); 3 | require("./difflib.js"); 4 | 5 | module.exports = { 6 | testStockExample: function (asyncDone, showDiff) { 7 | var passed = false; 8 | process.env.TZ = "UTC"; 9 | 10 | phantomjs.on('rendered', function(pageStr) { 11 | require("fs").readFile(__dirname + '/rendered-stock-fixture.html', function (err, data) { 12 | var fixtureStr = data.toString(); 13 | 14 | if (err) { 15 | grunt.log.error("Failed to open stock example."); 16 | } else { 17 | var diffs = diffPages(fixtureStr, pageStr); 18 | if (diffs.length > 0) { 19 | grunt.log.error("Failed comparison to stock example."); 20 | grunt.log.error("If these changes are intentional, please run `grunt update-stock-example` to overwrite the fixture."); 21 | if (showDiff) { 22 | grunt.log.writeln("\n" + diffs + "\n"); 23 | } else { 24 | grunt.log.error("Run `grunt test-stock-example:diff` to see differences."); 25 | } 26 | } else { 27 | grunt.log.writeln("Passed comparison to stock example."); 28 | passed = true; 29 | } 30 | } 31 | phantomjs.halt(); 32 | }); 33 | }); 34 | 35 | phantomjs.spawn('web/index.html', { 36 | options: { 37 | inject: __dirname + "/inject-serializer.js" 38 | }, 39 | done: function () { 40 | if (!passed) { 41 | grunt.fatal("Failed regression test."); 42 | } 43 | asyncDone(); 44 | } 45 | }); 46 | }, 47 | 48 | updateStockExample: function (asyncDone) { 49 | var ok = false; 50 | process.env.TZ = "UTC"; 51 | 52 | phantomjs.on('rendered', function(pageStr) { 53 | require("fs").writeFile(__dirname + '/rendered-stock-fixture.html', pageStr, function (err) { 54 | if (!err) { 55 | grunt.log.writeln("Overwrote stock example."); 56 | ok = true; 57 | } 58 | phantomjs.halt(); 59 | }); 60 | }); 61 | 62 | phantomjs.spawn('web/index.html', { 63 | options: { 64 | inject: __dirname + "/inject-serializer.js" 65 | }, 66 | done: function () { 67 | if (!ok) { 68 | grunt.fatal("Failed to overwrite stock example."); 69 | } 70 | asyncDone(); 71 | } 72 | }); 73 | } 74 | }; 75 | 76 | function diffPages(first, second) { 77 | first = filterExceptions(first); 78 | second = filterExceptions(second); 79 | 80 | var firstLines = difflib.stringAsLines(first); 81 | var secondLines = difflib.stringAsLines(second); 82 | var seq = new difflib.SequenceMatcher(firstLines, secondLines); 83 | var ops = seq.get_opcodes(); 84 | var diffs = []; 85 | 86 | for (var i = 0; i < ops.length; i++) { 87 | var op = ops[i]; 88 | var firstDiff = firstLines.slice(op[1], op[2]).join("\n"); 89 | var secondDiff = secondLines.slice(op[3], op[4]).join("\n"); 90 | 91 | if (op[0] === 'replace') { 92 | if (!onlyDiffersByDelta(firstDiff, secondDiff, 0.01)) { 93 | diffs.push("Replacement:"); 94 | diffs.push(firstDiff.red); 95 | diffs.push(secondDiff.green); 96 | } 97 | } else if (op[0] === 'insert') { 98 | diffs.push("Insertion:"); 99 | diffs.push(secondDiff.green); 100 | } else if (op[0] === 'delete') { 101 | diffs.push("Deletion:"); 102 | diffs.push(firstDiff.red); 103 | } 104 | } 105 | 106 | return diffs.join("\n\n"); 107 | } 108 | 109 | // TODO: remove use of clientHeight in legend and remove this function 110 | function filterExceptions(fixtureStr) { 111 | fixtureStr = fixtureStr.replace(/Monthly Index Move<\/text>/, "EXCEPTION"); 112 | fixtureStr = fixtureStr.replace(/Monthly Index Average<\/text>/, "EXCEPTION"); 113 | return fixtureStr; 114 | } 115 | 116 | function onlyDiffersByDelta(firstLine, secondLine, delta) { 117 | var findNums = /(?:[0-9]*\.[0-9]+|[0-9]+)/g; 118 | 119 | var firstNums = firstLine.match(findNums); 120 | var secondNums = secondLine.match(findNums); 121 | 122 | var firstWithoutNums = firstLine.replace(findNums, "NUMBER"); 123 | var secondWithoutNums = secondLine.replace(findNums, "NUMBER"); 124 | 125 | if (firstWithoutNums !== secondWithoutNums) { 126 | return false; 127 | } 128 | 129 | if (secondNums.length !== firstNums.length) { 130 | return false; 131 | } 132 | 133 | for (var i = 0; i < firstNums.length; i++) { 134 | if (Math.abs(+firstNums[i] - +secondNums[i]) > delta) { 135 | return false; 136 | } 137 | } 138 | 139 | return true; 140 | } 141 | -------------------------------------------------------------------------------- /src/bubble-chart.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Bubble Chart 3 | Includes: [Bubble Mixin](#bubble-mixin), [Coordinate Grid Mixin](#coordinate-grid-mixin) 4 | 5 | A concrete implementation of a general purpose bubble chart that allows data visualization using the 6 | following dimensions: 7 | 8 | * x axis position 9 | * y axis position 10 | * bubble radius 11 | * color 12 | 13 | Examples: 14 | * [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) 15 | * [US Venture Capital Landscape 2011](http://dc-js.github.com/dc.js/vc/index.html) 16 | #### dc.bubbleChart(parent[, chartGroup]) 17 | Create a bubble chart instance and attach it to the given parent element. 18 | 19 | Parameters: 20 | * parent : string | node | selection | compositeChart - any valid 21 | [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying 22 | a dom block element such as a div; or a dom element or d3 selection. 23 | * chartGroup : string (optional) - name of the chart group this chart instance should be placed in. 24 | Interaction with a chart will only trigger events and redraws within the chart's group. 25 | 26 | Returns: 27 | A newly created bubble chart instance 28 | 29 | ```js 30 | // create a bubble chart under #chart-container1 element using the default global chart group 31 | var bubbleChart1 = dc.bubbleChart('#chart-container1'); 32 | // create a bubble chart under #chart-container2 element using chart group A 33 | var bubbleChart2 = dc.bubbleChart('#chart-container2', 'chartGroupA'); 34 | ``` 35 | 36 | **/ 37 | dc.bubbleChart = function (parent, chartGroup) { 38 | var _chart = dc.bubbleMixin(dc.coordinateGridMixin({})); 39 | 40 | var _elasticRadius = false; 41 | 42 | _chart.transitionDuration(750); 43 | 44 | var bubbleLocator = function (d) { 45 | return 'translate(' + (bubbleX(d)) + ',' + (bubbleY(d)) + ')'; 46 | }; 47 | 48 | /** 49 | #### .elasticRadius([boolean]) 50 | Turn on or off the elastic bubble radius feature, or return the value of the flag. If this 51 | feature is turned on, then bubble radii will be automatically rescaled to fit the chart better. 52 | 53 | **/ 54 | _chart.elasticRadius = function (_) { 55 | if (!arguments.length) { 56 | return _elasticRadius; 57 | } 58 | _elasticRadius = _; 59 | return _chart; 60 | }; 61 | 62 | _chart.plotData = function () { 63 | if (_elasticRadius) { 64 | _chart.r().domain([_chart.rMin(), _chart.rMax()]); 65 | } 66 | 67 | _chart.r().range([_chart.MIN_RADIUS, _chart.xAxisLength() * _chart.maxBubbleRelativeSize()]); 68 | 69 | var bubbleG = _chart.chartBodyG().selectAll('g.' + _chart.BUBBLE_NODE_CLASS) 70 | .data(_chart.data(), function (d) { return d.key; }); 71 | 72 | renderNodes(bubbleG); 73 | 74 | updateNodes(bubbleG); 75 | 76 | removeNodes(bubbleG); 77 | 78 | _chart.fadeDeselectedArea(); 79 | }; 80 | 81 | function renderNodes(bubbleG) { 82 | var bubbleGEnter = bubbleG.enter().append('g'); 83 | 84 | bubbleGEnter 85 | .attr('class', _chart.BUBBLE_NODE_CLASS) 86 | .attr('transform', bubbleLocator) 87 | .append('circle').attr('class', function (d, i) { 88 | return _chart.BUBBLE_CLASS + ' _' + i; 89 | }) 90 | .on('click', _chart.onClick) 91 | .attr('fill', _chart.getColor) 92 | .attr('r', 0); 93 | dc.transition(bubbleG, _chart.transitionDuration()) 94 | .selectAll('circle.' + _chart.BUBBLE_CLASS) 95 | .attr('r', function (d) { 96 | return _chart.bubbleR(d); 97 | }) 98 | .attr('opacity', function (d) { 99 | return (_chart.bubbleR(d) > 0) ? 1 : 0; 100 | }); 101 | 102 | _chart._doRenderLabel(bubbleGEnter); 103 | 104 | _chart._doRenderTitles(bubbleGEnter); 105 | } 106 | 107 | function updateNodes(bubbleG) { 108 | dc.transition(bubbleG, _chart.transitionDuration()) 109 | .attr('transform', bubbleLocator) 110 | .selectAll('circle.' + _chart.BUBBLE_CLASS) 111 | .attr('fill', _chart.getColor) 112 | .attr('r', function (d) { 113 | return _chart.bubbleR(d); 114 | }) 115 | .attr('opacity', function (d) { 116 | return (_chart.bubbleR(d) > 0) ? 1 : 0; 117 | }); 118 | 119 | _chart.doUpdateLabels(bubbleG); 120 | _chart.doUpdateTitles(bubbleG); 121 | } 122 | 123 | function removeNodes(bubbleG) { 124 | bubbleG.exit().remove(); 125 | } 126 | 127 | function bubbleX(d) { 128 | var x = _chart.x()(_chart.keyAccessor()(d)); 129 | if (isNaN(x)) { 130 | x = 0; 131 | } 132 | return x; 133 | } 134 | 135 | function bubbleY(d) { 136 | var y = _chart.y()(_chart.valueAccessor()(d)); 137 | if (isNaN(y)) { 138 | y = 0; 139 | } 140 | return y; 141 | } 142 | 143 | _chart.renderBrush = function () { 144 | // override default x axis brush from parent chart 145 | }; 146 | 147 | _chart.redrawBrush = function () { 148 | // override default x axis brush from parent chart 149 | _chart.fadeDeselectedArea(); 150 | }; 151 | 152 | return _chart.anchor(parent, chartGroup); 153 | }; 154 | -------------------------------------------------------------------------------- /spec/helpers/custom_matchers.js: -------------------------------------------------------------------------------- 1 | function parseTranslate(actual) { 2 | var parts = /translate\((-?[\d\.]*)(?:[, ](.*))?\)/.exec(actual); 3 | if(!parts) 4 | return null; 5 | if(parts[2]===undefined) 6 | parts[2] = 0; 7 | expect(parts.length).toEqual(3); 8 | return parts; 9 | } 10 | 11 | function parseTranslateRotate(actual) { 12 | var parts = /translate\((-?[\d\.]*)(?:[, ](.*))?\)[, ]rotate\((-?[\d\.]*)\)/.exec(actual); 13 | if(!parts) 14 | return null; 15 | if(parts[2]===undefined) 16 | parts[2] = 0; 17 | expect(parts.length).toEqual(4); 18 | return parts; 19 | } 20 | 21 | function parsePath(path) { 22 | // an svg path is a string of any number of letters 23 | // each followed by zero or more numbers separated by spaces or commas 24 | var instrexp = /([a-z])[^a-z]*/gi, 25 | argexp = /(-?\d+(?:\.\d*)?)[, ]*/gi; 26 | var match, result = [], die = 99; 27 | while((match = instrexp.exec(path))) { 28 | var instr = match[0]; 29 | var cmd = {op: match[1], args: []}; 30 | argexp.lastIndex = 0; 31 | while((match = argexp.exec(instr))) 32 | cmd.args.push(match[1]); 33 | result.push(cmd); 34 | if(!--die) throw "give up"; 35 | } 36 | return result; 37 | } 38 | 39 | // there doesn't seem to be any way to access jasmine custom matchers 40 | function compareWithinDelta(actual, expected, delta){ 41 | if (delta === undefined) { 42 | delta = 1e-6; 43 | } 44 | 45 | var result = {}; 46 | 47 | result.pass = actual >= (+expected-delta) && actual <= (+expected+delta); 48 | 49 | var pre = "Expected " + actual + " to ", 50 | post = "be within [" + (+expected-delta) + "/" + (+expected+delta) + "]"; 51 | 52 | if(result.pass){ 53 | result.message = pre + "not " + post; 54 | } 55 | else{ 56 | result.message = pre + post; 57 | } 58 | 59 | return result; 60 | } 61 | 62 | // note: to make this reusable as a boolean predicate, it only returns the first 63 | // failure instead of using expect 64 | function comparePaths(actual, expected, delta) { 65 | delta = delta || 1; // default delta of 1px 66 | var got = parsePath(actual), 67 | wanted = parsePath(expected); 68 | if(got.length != wanted.length) 69 | return { 70 | pass: false, 71 | message: "actual number of path cmds " + actual.length + 72 | " did not match expected number " + expected.length 73 | }; 74 | for(var i = 0; i!=got.length; ++i) { 75 | var command_num = "path command #" + i; 76 | if(got[i].op.toUpperCase() != wanted[i].op.toUpperCase()) 77 | return { 78 | pass: false, 79 | message: command_num + " actual '" + got[i].op.toUpperCase() + 80 | "' != expected '" + wanted[i].op.toUpperCase() + "'" 81 | }; 82 | if(got[i].args.length != wanted[i].args.length) 83 | return { 84 | pass: false, 85 | message: command_num + " number of arguments " + 86 | got[i].args.length + " != expected " + wanted[i].args.length 87 | }; 88 | for(var j = 0; j 2 | 3 | 4 | dc.js - Heatmap Filtering Example 5 | 6 | 7 | 9 | 10 | 11 |

Michelson–Morley experiment

12 |
13 |
14 | 15 | 16 | 17 | 18 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /spec/filters-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc.filters', function () { 2 | describe('RangedFilter', function () { 3 | var filter; 4 | beforeEach(function () { 5 | filter = dc.filters.RangedFilter(0, 10); 6 | }); 7 | 8 | it('should act like an array', function () { 9 | expect([filter[0], filter[1]]).toEqual([0, 10]); 10 | }); 11 | 12 | describe("isFiltered", function () { 13 | it('should return false when the number is out of range', function () { 14 | expect(filter.isFiltered(1234)).toBeFalsy(); 15 | }); 16 | 17 | it('should return true when the number is in range', function () { 18 | expect(filter.isFiltered(8.1)).toBeTruthy(); 19 | }); 20 | 21 | it('should include the left bounds', function () { 22 | expect(filter.isFiltered(0)).toBeTruthy(); 23 | }); 24 | 25 | it('should exclude the right bounds', function () { 26 | expect(filter.isFiltered(10)).toBeFalsy(); 27 | }); 28 | }); 29 | }); 30 | 31 | describe('TwoDimensionalFilter', function () { 32 | var filter; 33 | beforeEach(function () { 34 | filter = dc.filters.TwoDimensionalFilter([1,2]); 35 | }); 36 | 37 | describe('isFiltered', function () { 38 | it('should return true if both dimensions are equal', function () { 39 | expect(filter.isFiltered([1,2])).toBeTruthy(); 40 | }); 41 | 42 | it('should return false if either dimension is not equal to the filter', function () { 43 | expect(filter.isFiltered([1,5])).toBeFalsy(); 44 | }); 45 | 46 | it('should return false if the dimensionality is less', function () { 47 | expect(filter.isFiltered([1])).toBeFalsy(); 48 | }); 49 | 50 | it('should return false if the dimensionality is more', function () { 51 | expect(filter.isFiltered([1,2,3])).toBeFalsy(); 52 | }); 53 | 54 | it('should return false if the value is not an array', function () { 55 | expect(filter.isFiltered(1)).toBeFalsy(); 56 | }); 57 | }); 58 | }); 59 | 60 | describe('RangedTwoDimensionalFilter', function () { 61 | var filter; 62 | 63 | 64 | it('should return null if filtered with null', function () { 65 | expect(dc.filters.RangedTwoDimensionalFilter(null)).toBe(null); 66 | }); 67 | 68 | describe('two-dimensional filtering', function () { 69 | beforeEach(function () { 70 | filter = dc.filters.RangedTwoDimensionalFilter([[0, 1],[10, 20]]); 71 | }); 72 | 73 | it('should return true if on bottom left of filter rectangle', function () { 74 | expect(filter.isFiltered([0,1])).toBeTruthy(); 75 | }); 76 | 77 | it('should return false if on bottom right of filter rectangle', function () { 78 | expect(filter.isFiltered([10,1])).toBeFalsy(); 79 | }); 80 | 81 | it('should return false for the top left of filter rectangle', function () { 82 | expect(filter.isFiltered([0,20])).toBeFalsy(); 83 | }); 84 | 85 | it('should return false for the top right of filter rectangle', function () { 86 | expect(filter.isFiltered([10,20])).toBeFalsy(); 87 | }); 88 | 89 | it('should return true for a point inside the filter rectangle', function () { 90 | expect(filter.isFiltered([5,5])).toBeTruthy(); 91 | }); 92 | 93 | it('should return false for a point to the right and above the filter rectangle', function () { 94 | expect(filter.isFiltered([11,21])).toBeFalsy(); 95 | }); 96 | 97 | it('should return false for a point to the left and below the filter rectangle', function () { 98 | expect(filter.isFiltered([-1,-1])).toBeFalsy(); 99 | }); 100 | 101 | describe('when a single value is considered', function() { 102 | it('should filter that value using only x coordinates', function() { 103 | expect(filter.isFiltered(5)).toBeTruthy(); 104 | expect(filter.isFiltered(0)).toBeTruthy(); 105 | expect(filter.isFiltered(10)).toBeFalsy(); 106 | }); 107 | }); 108 | }); 109 | 110 | describe('one-dimensional filtering', function () { 111 | beforeEach(function () { 112 | filter = dc.filters.RangedTwoDimensionalFilter([10, 20]); 113 | }); 114 | 115 | it('should return true while inside the range', function () { 116 | expect(filter.isFiltered([15,10])).toBeTruthy(); 117 | }); 118 | 119 | it('should return false while to the left of the range', function () { 120 | expect(filter.isFiltered([5,10])).toBeFalsy(); 121 | }); 122 | 123 | it('should return true while on the left edge of the range', function () { 124 | expect(filter.isFiltered([10,10])).toBeTruthy(); 125 | }); 126 | 127 | it('should return false while on the right edge of the range', function () { 128 | expect(filter.isFiltered([20,10])).toBeFalsy(); 129 | }); 130 | 131 | describe('when a single value is considered', function() { 132 | it('should filter that value using only x coordinates', function() { 133 | expect(filter.isFiltered(10)).toBeTruthy(); 134 | expect(filter.isFiltered(15)).toBeTruthy(); 135 | expect(filter.isFiltered(20)).toBeFalsy(); 136 | }); 137 | }); 138 | }); 139 | 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /spec/helpers/jasmine-jsreporter.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the Jasmine JSReporter project from Ivan De Marino. 3 | 4 | Copyright (C) 2011 Ivan De Marino (aka detro, aka detronizator), http://blog.ivandemarino.me, ivan.de.marino@gmail.com 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of the nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL IVAN DE MARINO BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | (function () { 31 | var finalResults; 32 | 33 | // Ensure that Jasmine library is loaded first 34 | if (!jasmine) { 35 | throw new Exception("[Jasmine JSReporter] 'Jasmine' library not found"); 36 | } 37 | 38 | /** 39 | * Round an amount to the given number of Digits. 40 | * If no number of digits is given, than '2' is assumed. 41 | * @param amount Amount to round 42 | * @param numOfDecDigits Number of Digits to round to. Default value is '2'. 43 | * @return Rounded amount */ 44 | function round (amount, numOfDecDigits) { 45 | numOfDecDigits = numOfDecDigits || 2; 46 | return Math.round(amount * Math.pow(10, numOfDecDigits)) / Math.pow(10, numOfDecDigits); 47 | } 48 | 49 | /** 50 | * Collect information about a Suite, recursively, and return a JSON result. 51 | * @param suite The Jasmine Suite to get data from 52 | */ 53 | function getSuiteData (suite) { 54 | var suiteData = { 55 | passed: true, 56 | durationSec : 0, 57 | suites: [], 58 | description : suite.description, 59 | specs: [] 60 | }; 61 | 62 | for (var i = 0; i < suite.children.length; ++i) { 63 | var childFailed = false; 64 | 65 | if (suite.children[i] instanceof jasmine.Spec) { 66 | var specResult = suite.children[i].result; 67 | childFailed = specResult.status === "failed"; 68 | 69 | // TEMPORARY PATCH FOR SAUCE LABS LENGTH ISSUES 70 | if (!childFailed) continue; 71 | 72 | suiteData.specs.push({ 73 | description : specResult.description, 74 | durationSec : specResult.duration / 1000, 75 | passed : specResult.status === "passed", 76 | skipped : specResult.status === "disabled" || specResult.status === "pending", 77 | passedCount : specResult.status === "passed" ? 1 : 0, 78 | failedCount : childFailed ? 1 : 0, 79 | totalCount : specResult.status !== "disabled" ? 1 : 0 80 | }); 81 | } else if (suite.children[i] instanceof jasmine.Suite) { 82 | var childSuiteData = getSuiteData(suite.children[i]); 83 | childFailed = !childSuiteData.passed; 84 | 85 | // TEMPORARY PATCH FOR SAUCE LABS LENGTH ISSUES 86 | if (!childFailed) continue; 87 | 88 | suiteData.suites.push(childSuiteData); 89 | } 90 | 91 | suiteData.passed = childFailed ? false : suiteData.passed; 92 | } 93 | 94 | // Rounding duration numbers to 3 decimal digits 95 | suiteData.durationSec = round(suite.result.duration / 1000, 4); 96 | 97 | return suiteData; 98 | } 99 | 100 | jasmine.getJSReport = function () { 101 | if (finalResults) { 102 | return finalResults; 103 | } 104 | 105 | return null; 106 | }; 107 | 108 | jasmine.getJSReportAsString = function () { 109 | return JSON.stringify(jasmine.getJSReport()); 110 | }; 111 | 112 | var JSReporter = function () {}; 113 | JSReporter.prototype = { 114 | jasmineDone: function () { 115 | // Attach results to the "jasmine" object to make those results easy to scrap/find 116 | var results = getSuiteData(jasmine.getEnv().topSuite()); 117 | var totalDuration = 0; 118 | 119 | var specs = results.specs; 120 | for (var i = 0; i < specs.length; ++i) { 121 | totalDuration += specs[i].durationSec; 122 | } 123 | 124 | var suites = results.suites; 125 | for (i = 0; i < suites.length; ++i) { 126 | totalDuration += suites[i].durationSec; 127 | } 128 | 129 | results.durationSec = round(totalDuration, 4); 130 | 131 | finalResults = results; 132 | } 133 | }; 134 | 135 | // export public 136 | jasmine.JSReporter = JSReporter; 137 | jasmine.getEnv().addReporter(new jasmine.JSReporter()); 138 | })(); 139 | 140 | -------------------------------------------------------------------------------- /src/legend.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Legend 3 | Legend is a attachable widget that can be added to other dc charts to render horizontal legend 4 | labels. 5 | 6 | ```js 7 | chart.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5)) 8 | ``` 9 | 10 | Examples: 11 | * [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) 12 | * [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) 13 | 14 | **/ 15 | dc.legend = function () { 16 | var LABEL_GAP = 2; 17 | 18 | var _legend = {}, 19 | _parent, 20 | _x = 0, 21 | _y = 0, 22 | _itemHeight = 12, 23 | _gap = 5, 24 | _horizontal = false, 25 | _legendWidth = 560, 26 | _itemWidth = 70; 27 | 28 | var _g; 29 | 30 | _legend.parent = function (p) { 31 | if (!arguments.length) { 32 | return _parent; 33 | } 34 | _parent = p; 35 | return _legend; 36 | }; 37 | 38 | _legend.render = function () { 39 | _parent.svg().select('g.dc-legend').remove(); 40 | _g = _parent.svg().append('g') 41 | .attr('class', 'dc-legend') 42 | .attr('transform', 'translate(' + _x + ',' + _y + ')'); 43 | var legendables = _parent.legendables(); 44 | 45 | var itemEnter = _g.selectAll('g.dc-legend-item') 46 | .data(legendables) 47 | .enter() 48 | .append('g') 49 | .attr('class', 'dc-legend-item') 50 | .on('mouseover', function (d) { 51 | _parent.legendHighlight(d); 52 | }) 53 | .on('mouseout', function (d) { 54 | _parent.legendReset(d); 55 | }) 56 | .on('click', function (d) { 57 | d.chart.legendToggle(d); 58 | }); 59 | 60 | _g.selectAll('g.dc-legend-item') 61 | .classed('fadeout', function (d) { 62 | return d.chart.isLegendableHidden(d); 63 | }); 64 | 65 | if (legendables.some(dc.pluck('dashstyle'))) { 66 | itemEnter 67 | .append('line') 68 | .attr('x1', 0) 69 | .attr('y1', _itemHeight / 2) 70 | .attr('x2', _itemHeight) 71 | .attr('y2', _itemHeight / 2) 72 | .attr('stroke-width', 2) 73 | .attr('stroke-dasharray', dc.pluck('dashstyle')) 74 | .attr('stroke', dc.pluck('color')); 75 | } else { 76 | itemEnter 77 | .append('rect') 78 | .attr('width', _itemHeight) 79 | .attr('height', _itemHeight) 80 | .attr('fill', function (d) {return d ? d.color : 'blue';}); 81 | } 82 | 83 | itemEnter.append('text') 84 | .text(dc.pluck('name')) 85 | .attr('x', _itemHeight + LABEL_GAP) 86 | .attr('y', function () { 87 | return _itemHeight / 2 + (this.clientHeight ? this.clientHeight : 13) / 2 - 2; 88 | }); 89 | 90 | var _cumulativeLegendTextWidth = 0; 91 | var row = 0; 92 | itemEnter.attr('transform', function (d, i) { 93 | if (_horizontal) { 94 | var translateBy = 'translate(' + _cumulativeLegendTextWidth + ',' + row * legendItemHeight() + ')'; 95 | if ((_cumulativeLegendTextWidth + _itemWidth) >= _legendWidth) { 96 | ++row ; 97 | _cumulativeLegendTextWidth = 0 ; 98 | } else { 99 | _cumulativeLegendTextWidth += _itemWidth; 100 | } 101 | return translateBy; 102 | } 103 | else { 104 | return 'translate(0,' + i * legendItemHeight() + ')'; 105 | } 106 | }); 107 | }; 108 | 109 | function legendItemHeight() { 110 | return _gap + _itemHeight; 111 | } 112 | 113 | /** 114 | #### .x([value]) 115 | Set or get x coordinate for legend widget. Default: 0. 116 | **/ 117 | _legend.x = function (x) { 118 | if (!arguments.length) { 119 | return _x; 120 | } 121 | _x = x; 122 | return _legend; 123 | }; 124 | 125 | /** 126 | #### .y([value]) 127 | Set or get y coordinate for legend widget. Default: 0. 128 | **/ 129 | _legend.y = function (y) { 130 | if (!arguments.length) { 131 | return _y; 132 | } 133 | _y = y; 134 | return _legend; 135 | }; 136 | 137 | /** 138 | #### .gap([value]) 139 | Set or get gap between legend items. Default: 5. 140 | **/ 141 | _legend.gap = function (gap) { 142 | if (!arguments.length) { 143 | return _gap; 144 | } 145 | _gap = gap; 146 | return _legend; 147 | }; 148 | 149 | /** 150 | #### .itemHeight([value]) 151 | Set or get legend item height. Default: 12. 152 | **/ 153 | _legend.itemHeight = function (h) { 154 | if (!arguments.length) { 155 | return _itemHeight; 156 | } 157 | _itemHeight = h; 158 | return _legend; 159 | }; 160 | 161 | /** 162 | #### .horizontal([boolean]) 163 | Position legend horizontally instead of vertically 164 | **/ 165 | _legend.horizontal = function (_) { 166 | if (!arguments.length) { 167 | return _horizontal; 168 | } 169 | _horizontal = _; 170 | return _legend; 171 | }; 172 | 173 | /** 174 | #### .legendWidth([value]) 175 | Maximum width for horizontal legend. Default: 560. 176 | **/ 177 | _legend.legendWidth = function (_) { 178 | if (!arguments.length) { 179 | return _legendWidth; 180 | } 181 | _legendWidth = _; 182 | return _legend; 183 | }; 184 | 185 | /** 186 | #### .itemWidth([value]) 187 | legendItem width for horizontal legend. Default: 70. 188 | **/ 189 | _legend.itemWidth = function (_) { 190 | if (!arguments.length) { 191 | return _itemWidth; 192 | } 193 | _itemWidth = _; 194 | return _legend; 195 | }; 196 | 197 | return _legend; 198 | }; 199 | -------------------------------------------------------------------------------- /src/series-chart.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Series Chart 3 | 4 | Includes: [Composite Chart](#composite chart) 5 | 6 | A series chart is a chart that shows multiple series of data overlaid on one chart, where the 7 | series is specified in the data. It is a specialization of Composite Chart and inherits all 8 | composite features other than recomposing the chart. 9 | 10 | #### dc.seriesChart(parent[, chartGroup]) 11 | Create a series chart instance and attach it to the given parent element. 12 | 13 | Parameters: 14 | * parent : string | node | selection - any valid 15 | [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying 16 | a dom block element such as a div; or a dom element or d3 selection. 17 | 18 | * chartGroup : string (optional) - name of the chart group this chart instance should be placed in. 19 | Interaction with a chart will only trigger events and redraws within the chart's group. 20 | 21 | Returns: 22 | A newly created series chart instance 23 | 24 | ```js 25 | // create a series chart under #chart-container1 element using the default global chart group 26 | var seriesChart1 = dc.seriesChart("#chart-container1"); 27 | // create a series chart under #chart-container2 element using chart group A 28 | var seriesChart2 = dc.seriesChart("#chart-container2", "chartGroupA"); 29 | ``` 30 | 31 | **/ 32 | dc.seriesChart = function (parent, chartGroup) { 33 | var _chart = dc.compositeChart(parent, chartGroup); 34 | 35 | function keySort(a, b) { 36 | return d3.ascending(_chart.keyAccessor()(a), _chart.keyAccessor()(b)); 37 | } 38 | 39 | var _charts = {}; 40 | var _chartFunction = dc.lineChart; 41 | var _seriesAccessor; 42 | var _seriesSort = d3.ascending; 43 | var _valueSort = keySort; 44 | 45 | _chart._mandatoryAttributes().push('seriesAccessor', 'chart'); 46 | _chart.shareColors(true); 47 | 48 | _chart._preprocessData = function () { 49 | var keep = []; 50 | var childrenChanged; 51 | var nester = d3.nest().key(_seriesAccessor); 52 | if (_seriesSort) { 53 | nester.sortKeys(_seriesSort); 54 | } 55 | if (_valueSort) { 56 | nester.sortValues(_valueSort); 57 | } 58 | var nesting = nester.entries(_chart.data()); 59 | var children = 60 | nesting.map(function (sub, i) { 61 | var subChart = _charts[sub.key] || _chartFunction.call(_chart, _chart, chartGroup, sub.key, i); 62 | if (!_charts[sub.key]) { 63 | childrenChanged = true; 64 | } 65 | _charts[sub.key] = subChart; 66 | keep.push(sub.key); 67 | return subChart 68 | .dimension(_chart.dimension()) 69 | .group({all:d3.functor(sub.values)}, sub.key) 70 | .keyAccessor(_chart.keyAccessor()) 71 | .valueAccessor(_chart.valueAccessor()) 72 | .brushOn(_chart.brushOn()); 73 | }); 74 | // this works around the fact compositeChart doesn't really 75 | // have a removal interface 76 | Object.keys(_charts) 77 | .filter(function (c) {return keep.indexOf(c) === -1;}) 78 | .forEach(function (c) { 79 | clearChart(c); 80 | childrenChanged = true; 81 | }); 82 | _chart._compose(children); 83 | if (childrenChanged && _chart.legend()) { 84 | _chart.legend().render(); 85 | } 86 | }; 87 | 88 | function clearChart(c) { 89 | if (_charts[c].g()) { 90 | _charts[c].g().remove(); 91 | } 92 | delete _charts[c]; 93 | } 94 | 95 | function resetChildren() { 96 | Object.keys(_charts).map(clearChart); 97 | _charts = {}; 98 | } 99 | 100 | /** 101 | #### .chart([function]) 102 | Get or set the chart function, which generates the child charts. Default: dc.lineChart 103 | 104 | ``` 105 | // put interpolation on the line charts used for the series 106 | chart.chart(function(c) { return dc.lineChart(c).interpolate('basis'); }) 107 | // do a scatter series chart 108 | chart.chart(dc.scatterPlot) 109 | ``` 110 | 111 | **/ 112 | _chart.chart = function (_) { 113 | if (!arguments.length) { 114 | return _chartFunction; 115 | } 116 | _chartFunction = _; 117 | resetChildren(); 118 | return _chart; 119 | }; 120 | 121 | /** 122 | #### .seriesAccessor([accessor]) 123 | Get or set accessor function for the displayed series. Given a datum, this function 124 | should return the series that datum belongs to. 125 | **/ 126 | _chart.seriesAccessor = function (_) { 127 | if (!arguments.length) { 128 | return _seriesAccessor; 129 | } 130 | _seriesAccessor = _; 131 | resetChildren(); 132 | return _chart; 133 | }; 134 | 135 | /** 136 | #### .seriesSort([sortFunction]) 137 | Get or set a function to sort the list of series by, given series values. 138 | 139 | Example: 140 | ``` 141 | chart.seriesSort(d3.descending); 142 | ``` 143 | **/ 144 | _chart.seriesSort = function (_) { 145 | if (!arguments.length) { 146 | return _seriesSort; 147 | } 148 | _seriesSort = _; 149 | resetChildren(); 150 | return _chart; 151 | }; 152 | 153 | /** 154 | #### .valueSort([sortFunction]) 155 | Get or set a function to sort each series values by. By default this is the key accessor which, 156 | for example, will ensure a lineChart series connects its points in increasing key/x order, 157 | rather than haphazardly. 158 | **/ 159 | _chart.valueSort = function (_) { 160 | if (!arguments.length) { 161 | return _valueSort; 162 | } 163 | _valueSort = _; 164 | resetChildren(); 165 | return _chart; 166 | }; 167 | 168 | // make compose private 169 | _chart._compose = _chart.compose; 170 | delete _chart.compose; 171 | 172 | return _chart; 173 | }; 174 | -------------------------------------------------------------------------------- /src/data-grid.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Data Grid Widget 3 | 4 | Includes: [Base Mixin](#base-mixin) 5 | 6 | Data grid is a simple widget designed to list the filtered records, providing 7 | a simple way to define how the items are displayed. 8 | 9 | Examples: 10 | * [List of members of the european parliament](http://europarl.me/dc.js/web/ep/index.html) 11 | 12 | #### dc.dataGrid(parent[, chartGroup]) 13 | Create a data grid widget instance and attach it to the given parent element. 14 | 15 | Parameters: 16 | * parent : string | node | selection - any valid 17 | [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying 18 | a dom block element such as a div; or a dom element or d3 selection. 19 | 20 | * chartGroup : string (optional) - name of the chart group this chart instance should be placed in. 21 | Interaction with a chart will only trigger events and redraws within the chart's group. 22 | 23 | Returns: 24 | A newly created data grid widget instance 25 | 26 | **/ 27 | dc.dataGrid = function (parent, chartGroup) { 28 | var LABEL_CSS_CLASS = 'dc-grid-label'; 29 | var ITEM_CSS_CLASS = 'dc-grid-item'; 30 | var GROUP_CSS_CLASS = 'dc-grid-group'; 31 | var GRID_CSS_CLASS = 'dc-grid-top'; 32 | 33 | var _chart = dc.baseMixin({}); 34 | 35 | var _size = 999; // shouldn't be needed, but you might 36 | var _html = function (d) { return 'you need to provide an html() handling param: ' + JSON.stringify(d); }; 37 | var _sortBy = function (d) { 38 | return d; 39 | }; 40 | var _order = d3.ascending; 41 | 42 | var _htmlGroup = function (d) { 43 | return '

' + 44 | _chart.keyAccessor()(d) + '

'; 45 | }; 46 | 47 | _chart._doRender = function () { 48 | _chart.selectAll('div.' + GRID_CSS_CLASS).remove(); 49 | 50 | renderItems(renderGroups()); 51 | 52 | return _chart; 53 | }; 54 | 55 | function renderGroups() { 56 | var groups = _chart.root().selectAll('div.' + GRID_CSS_CLASS) 57 | .data(nestEntries(), function (d) { 58 | return _chart.keyAccessor()(d); 59 | }); 60 | 61 | var itemGroup = groups 62 | .enter() 63 | .append('div') 64 | .attr('class', GRID_CSS_CLASS); 65 | 66 | if (_htmlGroup) { 67 | itemGroup 68 | .html(function (d) { 69 | return _htmlGroup(d); 70 | }); 71 | } 72 | 73 | groups.exit().remove(); 74 | return itemGroup; 75 | } 76 | 77 | function nestEntries() { 78 | var entries = _chart.dimension().top(_size); 79 | 80 | return d3.nest() 81 | .key(_chart.group()) 82 | .sortKeys(_order) 83 | .entries(entries.sort(function (a, b) { 84 | return _order(_sortBy(a), _sortBy(b)); 85 | })); 86 | } 87 | 88 | function renderItems(groups) { 89 | var items = groups.order() 90 | .selectAll('div.' + ITEM_CSS_CLASS) 91 | .data(function (d) { 92 | return d.values; 93 | }); 94 | 95 | items.enter() 96 | .append('div') 97 | .attr('class', ITEM_CSS_CLASS) 98 | .html(function (d) { 99 | return _html(d); 100 | }); 101 | 102 | items.exit().remove(); 103 | 104 | return items; 105 | } 106 | 107 | _chart._doRedraw = function () { 108 | return _chart._doRender(); 109 | }; 110 | 111 | /** 112 | #### .size([size]) 113 | Get or set the grid size which determines the number of items displayed by the widget. 114 | 115 | **/ 116 | _chart.size = function (s) { 117 | if (!arguments.length) { 118 | return _size; 119 | } 120 | _size = s; 121 | return _chart; 122 | }; 123 | 124 | /** 125 | #### .html( function (data) { return ''; }) 126 | Get or set the function that formats an item. The data grid widget uses a 127 | function to generate dynamic html. Use your favourite templating engine or 128 | generate the string directly. 129 | ```js 130 | chart.html(function (d) { return '
'+data.exampleString+'
';}); 131 | ``` 132 | 133 | **/ 134 | _chart.html = function (_) { 135 | if (!arguments.length) { 136 | return _html; 137 | } 138 | _html = _; 139 | return _chart; 140 | }; 141 | 142 | /** 143 | #### .htmlGroup( function (data) { return ''; }) 144 | Get or set the function that formats a group label. 145 | ```js 146 | chart.htmlGroup (function (d) { return '

'.d.key . 'with ' . d.values.length .' items

'}); 147 | ``` 148 | 149 | **/ 150 | _chart.htmlGroup = function (_) { 151 | if (!arguments.length) { 152 | return _htmlGroup; 153 | } 154 | _htmlGroup = _; 155 | return _chart; 156 | }; 157 | 158 | /** 159 | #### .sortBy([sortByFunction]) 160 | Get or set sort-by function. This function works as a value accessor at the item 161 | level and returns a particular field to be sorted. 162 | by. Default: identity function 163 | 164 | ```js 165 | chart.sortBy(function(d) { 166 | return d.date; 167 | }); 168 | ``` 169 | 170 | **/ 171 | _chart.sortBy = function (_) { 172 | if (!arguments.length) { 173 | return _sortBy; 174 | } 175 | _sortBy = _; 176 | return _chart; 177 | }; 178 | 179 | /** 180 | #### .order([order]) 181 | Get or set sort order function. Default value: ``` d3.ascending ``` 182 | 183 | ```js 184 | chart.order(d3.descending); 185 | ``` 186 | 187 | **/ 188 | _chart.order = function (_) { 189 | if (!arguments.length) { 190 | return _order; 191 | } 192 | _order = _; 193 | return _chart; 194 | }; 195 | 196 | return _chart.anchor(parent, chartGroup); 197 | }; 198 | -------------------------------------------------------------------------------- /src/bubble-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | ## Bubble Mixin 3 | Includes: [Color Mixin](#color-mixin) 4 | 5 | This Mixin provides reusable functionalities for any chart that needs to visualize data using bubbles. 6 | 7 | **/ 8 | dc.bubbleMixin = function (_chart) { 9 | var _maxBubbleRelativeSize = 0.3; 10 | var _minRadiusWithLabel = 10; 11 | 12 | _chart.BUBBLE_NODE_CLASS = 'node'; 13 | _chart.BUBBLE_CLASS = 'bubble'; 14 | _chart.MIN_RADIUS = 10; 15 | 16 | _chart = dc.colorMixin(_chart); 17 | 18 | _chart.renderLabel(true); 19 | 20 | _chart.data(function (group) { 21 | return group.top(Infinity); 22 | }); 23 | 24 | var _r = d3.scale.linear().domain([0, 100]); 25 | 26 | var _rValueAccessor = function (d) { 27 | return d.r; 28 | }; 29 | 30 | /** 31 | #### .r([bubbleRadiusScale]) 32 | Get or set the bubble radius scale. By default the bubble chart uses 33 | `d3.scale.linear().domain([0, 100])` as its r scale . 34 | 35 | **/ 36 | _chart.r = function (_) { 37 | if (!arguments.length) { 38 | return _r; 39 | } 40 | _r = _; 41 | return _chart; 42 | }; 43 | 44 | /** 45 | #### .radiusValueAccessor([radiusValueAccessor]) 46 | Get or set the radius value accessor function. If set, the radius value accessor function will 47 | be used to retrieve a data value for each bubble. The data retrieved then will be mapped using 48 | the r scale to the actual bubble radius. This allows you to encode a data dimension using bubble 49 | size. 50 | 51 | **/ 52 | _chart.radiusValueAccessor = function (_) { 53 | if (!arguments.length) { 54 | return _rValueAccessor; 55 | } 56 | _rValueAccessor = _; 57 | return _chart; 58 | }; 59 | 60 | _chart.rMin = function () { 61 | var min = d3.min(_chart.data(), function (e) { 62 | return _chart.radiusValueAccessor()(e); 63 | }); 64 | return min; 65 | }; 66 | 67 | _chart.rMax = function () { 68 | var max = d3.max(_chart.data(), function (e) { 69 | return _chart.radiusValueAccessor()(e); 70 | }); 71 | return max; 72 | }; 73 | 74 | _chart.bubbleR = function (d) { 75 | var value = _chart.radiusValueAccessor()(d); 76 | var r = _chart.r()(value); 77 | if (isNaN(r) || value <= 0) { 78 | r = 0; 79 | } 80 | return r; 81 | }; 82 | 83 | var labelFunction = function (d) { 84 | return _chart.label()(d); 85 | }; 86 | 87 | var labelOpacity = function (d) { 88 | return (_chart.bubbleR(d) > _minRadiusWithLabel) ? 1 : 0; 89 | }; 90 | 91 | _chart._doRenderLabel = function (bubbleGEnter) { 92 | if (_chart.renderLabel()) { 93 | var label = bubbleGEnter.select('text'); 94 | 95 | if (label.empty()) { 96 | label = bubbleGEnter.append('text') 97 | .attr('text-anchor', 'middle') 98 | .attr('dy', '.3em') 99 | .on('click', _chart.onClick); 100 | } 101 | 102 | label 103 | .attr('opacity', 0) 104 | .text(labelFunction); 105 | dc.transition(label, _chart.transitionDuration()) 106 | .attr('opacity', labelOpacity); 107 | } 108 | }; 109 | 110 | _chart.doUpdateLabels = function (bubbleGEnter) { 111 | if (_chart.renderLabel()) { 112 | var labels = bubbleGEnter.selectAll('text') 113 | .text(labelFunction); 114 | dc.transition(labels, _chart.transitionDuration()) 115 | .attr('opacity', labelOpacity); 116 | } 117 | }; 118 | 119 | var titleFunction = function (d) { 120 | return _chart.title()(d); 121 | }; 122 | 123 | _chart._doRenderTitles = function (g) { 124 | if (_chart.renderTitle()) { 125 | var title = g.select('title'); 126 | 127 | if (title.empty()) { 128 | g.append('title').text(titleFunction); 129 | } 130 | } 131 | }; 132 | 133 | _chart.doUpdateTitles = function (g) { 134 | if (_chart.renderTitle()) { 135 | g.selectAll('title').text(titleFunction); 136 | } 137 | }; 138 | 139 | /** 140 | #### .minRadiusWithLabel([radius]) 141 | Get or set the minimum radius for label rendering. If a bubble's radius is less than this value 142 | then no label will be rendered. Default: 10 143 | 144 | **/ 145 | _chart.minRadiusWithLabel = function (_) { 146 | if (!arguments.length) { 147 | return _minRadiusWithLabel; 148 | } 149 | _minRadiusWithLabel = _; 150 | return _chart; 151 | }; 152 | 153 | /** 154 | #### .maxBubbleRelativeSize([relativeSize]) 155 | Get or set the maximum relative size of a bubble to the length of x axis. This value is useful 156 | when the difference in radius between bubbles is too great. Default: 0.3 157 | 158 | **/ 159 | _chart.maxBubbleRelativeSize = function (_) { 160 | if (!arguments.length) { 161 | return _maxBubbleRelativeSize; 162 | } 163 | _maxBubbleRelativeSize = _; 164 | return _chart; 165 | }; 166 | 167 | _chart.fadeDeselectedArea = function () { 168 | if (_chart.hasFilter()) { 169 | _chart.selectAll('g.' + _chart.BUBBLE_NODE_CLASS).each(function (d) { 170 | if (_chart.isSelectedNode(d)) { 171 | _chart.highlightSelected(this); 172 | } else { 173 | _chart.fadeDeselected(this); 174 | } 175 | }); 176 | } else { 177 | _chart.selectAll('g.' + _chart.BUBBLE_NODE_CLASS).each(function () { 178 | _chart.resetHighlight(this); 179 | }); 180 | } 181 | }; 182 | 183 | _chart.isSelectedNode = function (d) { 184 | return _chart.hasFilter(d.key); 185 | }; 186 | 187 | _chart.onClick = function (d) { 188 | var filter = d.key; 189 | dc.events.trigger(function () { 190 | _chart.filter(filter); 191 | _chart.redrawGroup(); 192 | }); 193 | }; 194 | 195 | return _chart; 196 | }; 197 | -------------------------------------------------------------------------------- /spec/data-count-spec.js: -------------------------------------------------------------------------------- 1 | describe('dc.dataCount', function() { 2 | var data, countryDimension, groupAll; 3 | var chart; 4 | beforeEach(function() { 5 | data = crossfilter(loadDateFixture()); 6 | groupAll = data.groupAll(); 7 | countryDimension = data.dimension(function(d) { 8 | return d.countrycode; 9 | }); 10 | countryDimension.filter("CA"); 11 | }); 12 | function buildChart(id){ 13 | var chart = dc.dataCount("#" + id) 14 | .transitionDuration(0) 15 | .dimension(data) 16 | .group(groupAll); 17 | chart.render(); 18 | d3.timer.flush(); 19 | return chart; 20 | } 21 | describe('creation', function() { 22 | beforeEach(function() { 23 | var id = "data-count"; 24 | var div = appendChartID(id); 25 | div.append("span").attr("class", "filter-count"); 26 | div.append("span").attr("class", "total-count"); 27 | chart = buildChart(id); 28 | }); 29 | it('should generate something', function() { 30 | expect(chart).not.toBeNull(); 31 | }); 32 | it('should be registered', function() { 33 | expect(dc.hasChart(chart)).toBeTruthy(); 34 | }); 35 | it('should fill in the total count', function() { 36 | expect(chart.select("span.total-count").text()).toEqual("10"); 37 | }); 38 | it('should fill in the filter count', function() { 39 | expect(chart.select("span.filter-count").text()).toEqual("2"); 40 | }); 41 | describe('redraw', function() { 42 | beforeEach(function() { 43 | countryDimension.filterAll(); 44 | chart.redraw(); 45 | return chart; 46 | }); 47 | it('should fill in the updated total count', function() { 48 | expect(chart.select("span.total-count").text()).toEqual("10"); 49 | }); 50 | it('should fill in the updated filter count', function() { 51 | expect(chart.select("span.filter-count").text()).toEqual("10"); 52 | }); 53 | }); 54 | afterEach(function() { 55 | countryDimension.filterAll(); 56 | }); 57 | }); 58 | 59 | describe('creation with html attribute', function() { 60 | beforeEach(function() { 61 | var id = "data-count"; 62 | var div = appendChartID(id); 63 | div.append("span").attr("class", "filter-count"); 64 | div.append("span").attr("class", "total-count"); 65 | chart = buildChart(id); 66 | chart.html({some:"%filter-count selected from %total-count",all:"All Records Selected"}); 67 | chart.redraw(); 68 | }); 69 | it('should generate something', function() { 70 | expect(chart).not.toBeNull(); 71 | }); 72 | it('should be registered', function() { 73 | expect(dc.hasChart(chart)).toBeTruthy(); 74 | }); 75 | it('should fill the element replacing %filter-count and %total-count', function() { 76 | expect(chart.root().text()).toEqual("2 selected from 10"); 77 | }); 78 | describe('when all selected', function() { 79 | beforeEach(function() { 80 | countryDimension.filterAll(); 81 | chart.redraw(); 82 | return chart; 83 | }); 84 | it('should use html.all', function() { 85 | expect(chart.root().text()).toEqual("All Records Selected"); 86 | }); 87 | }); 88 | afterEach(function() { 89 | countryDimension.filterAll(); 90 | }); 91 | }); 92 | 93 | describe('creation with just html.some attribute', function() { 94 | beforeEach(function() { 95 | var id = "data-count"; 96 | var div = appendChartID(id); 97 | div.append("span").attr("class", "filter-count"); 98 | div.append("span").attr("class", "total-count"); 99 | chart = buildChart(id); 100 | chart.html({some:"%filter-count selected from %total-count"}); 101 | chart.redraw(); 102 | }); 103 | it('should fill the element replacing %filter-count and %total-count', function() { 104 | expect(chart.root().text()).toEqual("2 selected from 10"); 105 | }); 106 | describe('when all selected', function() { 107 | beforeEach(function() { 108 | countryDimension.filterAll(); 109 | chart.redraw(); 110 | return chart; 111 | }); 112 | it('should use html.some for all', function() { 113 | expect(chart.root().text()).toEqual("10 selected from 10"); 114 | }); 115 | }); 116 | afterEach(function() { 117 | countryDimension.filterAll(); 118 | }); 119 | }); 120 | 121 | describe('creation with just html.all attribute', function() { 122 | beforeEach(function() { 123 | var id = "data-count"; 124 | var div = appendChartID(id); 125 | div.append("span").attr("class", "filter-count"); 126 | div.append("span").attr("class", "total-count"); 127 | chart = buildChart(id); 128 | chart.html({all:"All Records Selected"}); 129 | chart.redraw(); 130 | }); 131 | it('should fill in the total count', function() { 132 | expect(chart.select("span.total-count").text()).toEqual("10"); 133 | }); 134 | it('should fill in the filter count', function() { 135 | expect(chart.select("span.filter-count").text()).toEqual("2"); 136 | }); 137 | describe('when all selected', function() { 138 | beforeEach(function() { 139 | countryDimension.filterAll(); 140 | chart.redraw(); 141 | return chart; 142 | }); 143 | it('should use html.all for all', function() { 144 | expect(chart.root().text()).toEqual("All Records Selected"); 145 | }); 146 | }); 147 | afterEach(function() { 148 | countryDimension.filterAll(); 149 | }); 150 | }); 151 | 152 | }); 153 | -------------------------------------------------------------------------------- /dc.css: -------------------------------------------------------------------------------- 1 | div.dc-chart { 2 | float: left; 3 | } 4 | 5 | .dc-chart rect.bar { 6 | stroke: none; 7 | cursor: pointer; 8 | } 9 | 10 | .dc-chart rect.bar:hover { 11 | fill-opacity: .5; 12 | } 13 | 14 | .dc-chart rect.stack1 { 15 | stroke: none; 16 | fill: red; 17 | } 18 | 19 | .dc-chart rect.stack2 { 20 | stroke: none; 21 | fill: green; 22 | } 23 | 24 | .dc-chart rect.deselected { 25 | stroke: none; 26 | fill: #ccc; 27 | } 28 | 29 | .dc-chart .empty-chart .pie-slice path { 30 | fill: #FFEEEE; 31 | cursor: default; 32 | } 33 | 34 | .dc-chart .empty-chart .pie-slice { 35 | cursor: default; 36 | } 37 | 38 | .dc-chart .pie-slice { 39 | fill: white; 40 | font-size: 12px; 41 | cursor: pointer; 42 | } 43 | 44 | .dc-chart .pie-slice.external{ 45 | fill: black; 46 | } 47 | 48 | .dc-chart .pie-slice :hover { 49 | fill-opacity: .8; 50 | } 51 | 52 | .dc-chart .pie-slice.highlight { 53 | fill-opacity: .8; 54 | } 55 | 56 | .dc-chart .selected path { 57 | stroke-width: 3; 58 | stroke: #ccc; 59 | fill-opacity: 1; 60 | } 61 | 62 | .dc-chart .deselected path { 63 | stroke: none; 64 | fill-opacity: .5; 65 | fill: #ccc; 66 | } 67 | 68 | .dc-chart .axis path, .axis line { 69 | fill: none; 70 | stroke: #000; 71 | shape-rendering: crispEdges; 72 | } 73 | 74 | .dc-chart .axis text { 75 | font: 10px sans-serif; 76 | } 77 | 78 | .dc-chart .grid-line { 79 | fill: none; 80 | stroke: #ccc; 81 | opacity: .5; 82 | shape-rendering: crispEdges; 83 | } 84 | 85 | .dc-chart .grid-line line { 86 | fill: none; 87 | stroke: #ccc; 88 | opacity: .5; 89 | shape-rendering: crispEdges; 90 | } 91 | 92 | .dc-chart .brush rect.background { 93 | z-index: -999; 94 | } 95 | 96 | .dc-chart .brush rect.extent { 97 | fill: steelblue; 98 | fill-opacity: .125; 99 | } 100 | 101 | .dc-chart .brush .resize path { 102 | fill: #eee; 103 | stroke: #666; 104 | } 105 | 106 | .dc-chart path.line { 107 | fill: none; 108 | stroke-width: 1.5px; 109 | } 110 | 111 | .dc-chart circle.dot { 112 | stroke: none; 113 | } 114 | 115 | .dc-chart g.dc-tooltip path { 116 | fill: none; 117 | stroke: grey; 118 | stroke-opacity: .8; 119 | } 120 | 121 | .dc-chart path.area { 122 | fill-opacity: .3; 123 | stroke: none; 124 | } 125 | 126 | .dc-chart .node { 127 | font-size: 0.7em; 128 | cursor: pointer; 129 | } 130 | 131 | .dc-chart .node :hover { 132 | fill-opacity: .8; 133 | } 134 | 135 | .dc-chart .selected circle { 136 | stroke-width: 3; 137 | stroke: #ccc; 138 | fill-opacity: 1; 139 | } 140 | 141 | .dc-chart .deselected circle { 142 | stroke: none; 143 | fill-opacity: .5; 144 | fill: #ccc; 145 | } 146 | 147 | .dc-chart .bubble { 148 | stroke: none; 149 | fill-opacity: 0.6; 150 | } 151 | 152 | .dc-data-count { 153 | float: right; 154 | margin-top: 15px; 155 | margin-right: 15px; 156 | } 157 | 158 | .dc-data-count .filter-count { 159 | color: #3182bd; 160 | font-weight: bold; 161 | } 162 | 163 | .dc-data-count .total-count { 164 | color: #3182bd; 165 | font-weight: bold; 166 | } 167 | 168 | .dc-data-table { 169 | } 170 | 171 | .dc-chart g.state { 172 | cursor: pointer; 173 | } 174 | 175 | .dc-chart g.state :hover { 176 | fill-opacity: .8; 177 | } 178 | 179 | .dc-chart g.state path { 180 | stroke: white; 181 | } 182 | 183 | .dc-chart g.selected path { 184 | } 185 | 186 | .dc-chart g.deselected path { 187 | fill: grey; 188 | } 189 | 190 | .dc-chart g.selected text { 191 | } 192 | 193 | .dc-chart g.deselected text { 194 | display: none; 195 | } 196 | 197 | .dc-chart g.county path { 198 | stroke: white; 199 | fill: none; 200 | } 201 | 202 | .dc-chart g.debug rect { 203 | fill: blue; 204 | fill-opacity: .2; 205 | } 206 | 207 | .dc-chart g.row rect { 208 | fill-opacity: 0.8; 209 | cursor: pointer; 210 | } 211 | 212 | .dc-chart g.row rect:hover { 213 | fill-opacity: 0.6; 214 | } 215 | 216 | .dc-chart g.row text { 217 | fill: white; 218 | font-size: 12px; 219 | cursor: pointer; 220 | } 221 | 222 | .dc-legend { 223 | font-size: 11px; 224 | } 225 | 226 | .dc-legend-item { 227 | cursor: pointer; 228 | } 229 | 230 | .dc-chart g.axis text { 231 | /* Makes it so the user can't accidentally click and select text that is meant as a label only */ 232 | -webkit-user-select: none; /* Chrome/Safari */ 233 | -moz-user-select: none; /* Firefox */ 234 | -ms-user-select: none; /* IE10 */ 235 | -o-user-select: none; 236 | user-select: none; 237 | pointer-events: none; 238 | } 239 | 240 | .dc-chart path.highlight { 241 | stroke-width: 3; 242 | fill-opacity: 1; 243 | stroke-opacity: 1; 244 | } 245 | 246 | .dc-chart .highlight { 247 | fill-opacity: 1; 248 | stroke-opacity: 1; 249 | } 250 | 251 | .dc-chart .fadeout { 252 | fill-opacity: 0.2; 253 | stroke-opacity: 0.2; 254 | } 255 | 256 | .dc-chart path.dc-symbol, g.dc-legend-item.fadeout { 257 | fill-opacity: 0.5; 258 | stroke-opacity: 0.5; 259 | } 260 | 261 | .dc-hard .number-display { 262 | float: none; 263 | } 264 | 265 | .dc-chart .box text { 266 | font: 10px sans-serif; 267 | -webkit-user-select: none; /* Chrome/Safari */ 268 | -moz-user-select: none; /* Firefox */ 269 | -ms-user-select: none; /* IE10 */ 270 | -o-user-select: none; 271 | user-select: none; 272 | pointer-events: none; 273 | } 274 | 275 | .dc-chart .box line, 276 | .dc-chart .box circle { 277 | fill: #fff; 278 | stroke: #000; 279 | stroke-width: 1.5px; 280 | } 281 | 282 | .dc-chart .box rect { 283 | stroke: #000; 284 | stroke-width: 1.5px; 285 | } 286 | 287 | .dc-chart .box .center { 288 | stroke-dasharray: 3,3; 289 | } 290 | 291 | .dc-chart .box .outlier { 292 | fill: none; 293 | stroke: #ccc; 294 | } 295 | 296 | .dc-chart .box.deselected .box { 297 | fill: #ccc; 298 | } 299 | 300 | .dc-chart .box.deselected { 301 | opacity: .5; 302 | } 303 | 304 | .dc-chart .symbol{ 305 | stroke: none; 306 | } 307 | 308 | .dc-chart .heatmap .box-group.deselected rect { 309 | stroke: none; 310 | fill-opacity: .5; 311 | fill: #ccc; 312 | } 313 | 314 | .dc-chart .heatmap g.axis text { 315 | pointer-events: all; 316 | cursor: pointer; 317 | } 318 | -------------------------------------------------------------------------------- /web/css/dc.css: -------------------------------------------------------------------------------- 1 | div.dc-chart { 2 | float: left; 3 | } 4 | 5 | .dc-chart rect.bar { 6 | stroke: none; 7 | cursor: pointer; 8 | } 9 | 10 | .dc-chart rect.bar:hover { 11 | fill-opacity: .5; 12 | } 13 | 14 | .dc-chart rect.stack1 { 15 | stroke: none; 16 | fill: red; 17 | } 18 | 19 | .dc-chart rect.stack2 { 20 | stroke: none; 21 | fill: green; 22 | } 23 | 24 | .dc-chart rect.deselected { 25 | stroke: none; 26 | fill: #ccc; 27 | } 28 | 29 | .dc-chart .empty-chart .pie-slice path { 30 | fill: #FFEEEE; 31 | cursor: default; 32 | } 33 | 34 | .dc-chart .empty-chart .pie-slice { 35 | cursor: default; 36 | } 37 | 38 | .dc-chart .pie-slice { 39 | fill: white; 40 | font-size: 12px; 41 | cursor: pointer; 42 | } 43 | 44 | .dc-chart .pie-slice.external{ 45 | fill: black; 46 | } 47 | 48 | .dc-chart .pie-slice :hover { 49 | fill-opacity: .8; 50 | } 51 | 52 | .dc-chart .pie-slice.highlight { 53 | fill-opacity: .8; 54 | } 55 | 56 | .dc-chart .selected path { 57 | stroke-width: 3; 58 | stroke: #ccc; 59 | fill-opacity: 1; 60 | } 61 | 62 | .dc-chart .deselected path { 63 | stroke: none; 64 | fill-opacity: .5; 65 | fill: #ccc; 66 | } 67 | 68 | .dc-chart .axis path, .axis line { 69 | fill: none; 70 | stroke: #000; 71 | shape-rendering: crispEdges; 72 | } 73 | 74 | .dc-chart .axis text { 75 | font: 10px sans-serif; 76 | } 77 | 78 | .dc-chart .grid-line { 79 | fill: none; 80 | stroke: #ccc; 81 | opacity: .5; 82 | shape-rendering: crispEdges; 83 | } 84 | 85 | .dc-chart .grid-line line { 86 | fill: none; 87 | stroke: #ccc; 88 | opacity: .5; 89 | shape-rendering: crispEdges; 90 | } 91 | 92 | .dc-chart .brush rect.background { 93 | z-index: -999; 94 | } 95 | 96 | .dc-chart .brush rect.extent { 97 | fill: steelblue; 98 | fill-opacity: .125; 99 | } 100 | 101 | .dc-chart .brush .resize path { 102 | fill: #eee; 103 | stroke: #666; 104 | } 105 | 106 | .dc-chart path.line { 107 | fill: none; 108 | stroke-width: 1.5px; 109 | } 110 | 111 | .dc-chart circle.dot { 112 | stroke: none; 113 | } 114 | 115 | .dc-chart g.dc-tooltip path { 116 | fill: none; 117 | stroke: grey; 118 | stroke-opacity: .8; 119 | } 120 | 121 | .dc-chart path.area { 122 | fill-opacity: .3; 123 | stroke: none; 124 | } 125 | 126 | .dc-chart .node { 127 | font-size: 0.7em; 128 | cursor: pointer; 129 | } 130 | 131 | .dc-chart .node :hover { 132 | fill-opacity: .8; 133 | } 134 | 135 | .dc-chart .selected circle { 136 | stroke-width: 3; 137 | stroke: #ccc; 138 | fill-opacity: 1; 139 | } 140 | 141 | .dc-chart .deselected circle { 142 | stroke: none; 143 | fill-opacity: .5; 144 | fill: #ccc; 145 | } 146 | 147 | .dc-chart .bubble { 148 | stroke: none; 149 | fill-opacity: 0.6; 150 | } 151 | 152 | .dc-data-count { 153 | float: right; 154 | margin-top: 15px; 155 | margin-right: 15px; 156 | } 157 | 158 | .dc-data-count .filter-count { 159 | color: #3182bd; 160 | font-weight: bold; 161 | } 162 | 163 | .dc-data-count .total-count { 164 | color: #3182bd; 165 | font-weight: bold; 166 | } 167 | 168 | .dc-data-table { 169 | } 170 | 171 | .dc-chart g.state { 172 | cursor: pointer; 173 | } 174 | 175 | .dc-chart g.state :hover { 176 | fill-opacity: .8; 177 | } 178 | 179 | .dc-chart g.state path { 180 | stroke: white; 181 | } 182 | 183 | .dc-chart g.selected path { 184 | } 185 | 186 | .dc-chart g.deselected path { 187 | fill: grey; 188 | } 189 | 190 | .dc-chart g.selected text { 191 | } 192 | 193 | .dc-chart g.deselected text { 194 | display: none; 195 | } 196 | 197 | .dc-chart g.county path { 198 | stroke: white; 199 | fill: none; 200 | } 201 | 202 | .dc-chart g.debug rect { 203 | fill: blue; 204 | fill-opacity: .2; 205 | } 206 | 207 | .dc-chart g.row rect { 208 | fill-opacity: 0.8; 209 | cursor: pointer; 210 | } 211 | 212 | .dc-chart g.row rect:hover { 213 | fill-opacity: 0.6; 214 | } 215 | 216 | .dc-chart g.row text { 217 | fill: white; 218 | font-size: 12px; 219 | cursor: pointer; 220 | } 221 | 222 | .dc-legend { 223 | font-size: 11px; 224 | } 225 | 226 | .dc-legend-item { 227 | cursor: pointer; 228 | } 229 | 230 | .dc-chart g.axis text { 231 | /* Makes it so the user can't accidentally click and select text that is meant as a label only */ 232 | -webkit-user-select: none; /* Chrome/Safari */ 233 | -moz-user-select: none; /* Firefox */ 234 | -ms-user-select: none; /* IE10 */ 235 | -o-user-select: none; 236 | user-select: none; 237 | pointer-events: none; 238 | } 239 | 240 | .dc-chart path.highlight { 241 | stroke-width: 3; 242 | fill-opacity: 1; 243 | stroke-opacity: 1; 244 | } 245 | 246 | .dc-chart .highlight { 247 | fill-opacity: 1; 248 | stroke-opacity: 1; 249 | } 250 | 251 | .dc-chart .fadeout { 252 | fill-opacity: 0.2; 253 | stroke-opacity: 0.2; 254 | } 255 | 256 | .dc-chart path.dc-symbol, g.dc-legend-item.fadeout { 257 | fill-opacity: 0.5; 258 | stroke-opacity: 0.5; 259 | } 260 | 261 | .dc-hard .number-display { 262 | float: none; 263 | } 264 | 265 | .dc-chart .box text { 266 | font: 10px sans-serif; 267 | -webkit-user-select: none; /* Chrome/Safari */ 268 | -moz-user-select: none; /* Firefox */ 269 | -ms-user-select: none; /* IE10 */ 270 | -o-user-select: none; 271 | user-select: none; 272 | pointer-events: none; 273 | } 274 | 275 | .dc-chart .box line, 276 | .dc-chart .box circle { 277 | fill: #fff; 278 | stroke: #000; 279 | stroke-width: 1.5px; 280 | } 281 | 282 | .dc-chart .box rect { 283 | stroke: #000; 284 | stroke-width: 1.5px; 285 | } 286 | 287 | .dc-chart .box .center { 288 | stroke-dasharray: 3,3; 289 | } 290 | 291 | .dc-chart .box .outlier { 292 | fill: none; 293 | stroke: #ccc; 294 | } 295 | 296 | .dc-chart .box.deselected .box { 297 | fill: #ccc; 298 | } 299 | 300 | .dc-chart .box.deselected { 301 | opacity: .5; 302 | } 303 | 304 | .dc-chart .symbol{ 305 | stroke: none; 306 | } 307 | 308 | .dc-chart .heatmap .box-group.deselected rect { 309 | stroke: none; 310 | fill-opacity: .5; 311 | fill: #ccc; 312 | } 313 | 314 | .dc-chart .heatmap g.axis text { 315 | pointer-events: all; 316 | cursor: pointer; 317 | } 318 | --------------------------------------------------------------------------------