├── src
├── scss
│ └── .gitignore
└── js
│ ├── MG.js
│ ├── common
│ ├── register.js
│ ├── window_listeners.js
│ ├── hooks.js
│ ├── chart_title.js
│ ├── markers.js
│ ├── init.js
│ └── data_graphic.js
│ ├── misc
│ ├── error.js
│ ├── formatters.js
│ ├── transitions.js
│ └── smoothers.js
│ ├── charts
│ ├── missing.js
│ ├── table.js
│ └── point.js
│ └── layout
│ ├── button.js
│ └── bootstrap_dropdown.js
├── tests
├── common
│ ├── .gitkeep
│ ├── data_graphic_test.js
│ ├── chart_title_test.js
│ ├── hooks_test.js
│ ├── markers_test.js
│ ├── y_axis_test.js
│ ├── x_axis_test.js
│ └── init_test.js
├── misc
│ ├── .gitkeep
│ ├── process_test.js
│ └── utility_test.js
├── helpers.js
└── charts
│ ├── histogram_test.js
│ ├── bar_test.js
│ ├── missing_test.js
│ └── point_test.js
├── gulpfile.js
├── index.js
├── .jshintignore
├── examples
├── images
│ ├── divider.png
│ └── og-logo.png
├── css
│ ├── metricsgraphics-demo-accessible.css
│ ├── addons
│ │ └── mg_line_brushing.css
│ ├── highlightjs-default.css
│ ├── railscasts.css
│ └── metricsgraphics-demo.css
├── data
│ ├── neg2.json
│ ├── small-range.json
│ ├── xnotdate.json
│ ├── missing-y.json
│ ├── missing-is-hidden.json
│ ├── missing-is-hidden-accessor.json
│ ├── make_fake_data.py
│ ├── firefox_releases.json
│ ├── ufo-sightings.json
│ ├── brief-1.json
│ └── some_currency.json
├── charts
│ ├── addons.htm
│ ├── other.htm
│ ├── auto-time-formatting.htm
│ ├── updating.htm
│ └── annotations.htm
├── js
│ └── main.js
└── examples.htm
├── .gitignore
├── .jshintrc
├── .travis.yml
├── .editorconfig
├── bower.json
├── contribute.json
├── testem.json
├── HOOKS.md
├── package.json
├── gulp
└── index.js
└── dist
└── metricsgraphics.css
/src/scss/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/common/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/misc/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/js/MG.js:
--------------------------------------------------------------------------------
1 | window.MG = {version: '2.7.0'};
2 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('./gulp');
4 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./dist/metricsgraphics');
2 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | src/js/common/bootstrap_tooltip_popover.js
2 | src/js/layout/bootstrap_dropdown.js
3 |
--------------------------------------------------------------------------------
/examples/images/divider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antillas21/metrics-graphics/master/examples/images/divider.png
--------------------------------------------------------------------------------
/examples/images/og-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antillas21/metrics-graphics/master/examples/images/og-logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 |
4 | other/divider.psd
5 |
6 | other/htaccess.txt
7 |
8 | .DS_Store
9 |
10 | bare.html
11 |
--------------------------------------------------------------------------------
/src/js/common/register.js:
--------------------------------------------------------------------------------
1 | function register(chartType, descriptor, defaults) {
2 | MG.charts[chartType] = {
3 | descriptor: descriptor,
4 | defaults: defaults || {}
5 | };
6 | }
7 |
8 | MG.register = register;
9 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "$": false,
4 | "jQuery": false,
5 | "MG": false,
6 | "d3": false
7 | },
8 | "laxbreak": true,
9 | "validthis": true,
10 | "loopfunc": true,
11 | "sub": true
12 | }
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | script: npm run test-ci
5 | notifications:
6 | irc:
7 | channels:
8 | - "irc.mozilla.org#metrics"
9 | on_success: change
10 | on_failure: always
11 | use_notice: true
12 |
--------------------------------------------------------------------------------
/tests/misc/process_test.js:
--------------------------------------------------------------------------------
1 | module('process');
2 |
3 | test('args.missing_is_zero doesn\'t throw a "args.data[0][0] is undefined" error', function() {
4 | var data = [{"date": new Date('2014-02-02'), "value": 6}];
5 | var params = {
6 | data: data,
7 | target: "#qunit-fixture",
8 | missing_is_zero: true
9 | };
10 |
11 | MG.data_graphic(params);
12 |
13 | equal(params.data.length, 1, 'args.data is defined');
14 | });
--------------------------------------------------------------------------------
/examples/css/metricsgraphics-demo-accessible.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 16px;
3 | }
4 |
5 | .mg-x-axis text,
6 | .mg-y-axis text,
7 | .mg-histogram .axis text {
8 | opacity: 0.9;
9 | }
10 |
11 | .mg-x-axis line,
12 | .mg-y-axis line {
13 | opacity: 1;
14 | }
15 |
16 | .mg-markers text,
17 | .mg-year-marker text,
18 | .mg-baselines text {
19 | opacity: 0.9;
20 | }
21 |
22 | .mg-markers line,
23 | .mg-year-marker line,
24 | .mg-baselines line {
25 | opacity: 1;
26 | }
--------------------------------------------------------------------------------
/src/js/misc/error.js:
--------------------------------------------------------------------------------
1 | // call this to add a warning icon to a graph and log an error to the console
2 | function error (args) {
3 | console.log('ERROR : ', args.target, ' : ', args.error);
4 |
5 | d3.select(args.target).select('.mg-chart-title')
6 | .append('i')
7 | .attr('class', 'fa fa-x fa-exclamation-circle warning');
8 | }
9 |
10 | function internal_error (args) {
11 | console.log('INTERNAL ERROR : ', args.target, ' : ', args.internal_error);
12 | }
13 |
14 | MG.error = error;
15 |
--------------------------------------------------------------------------------
/examples/css/addons/mg_line_brushing.css:
--------------------------------------------------------------------------------
1 | .mg-brush-container {
2 | cursor: crosshair; }
3 |
4 | .mg-brush-container.mg-brushing {
5 | cursor: ew-resize; }
6 |
7 | .mg-brushed, .mg-brushed * {
8 | cursor: zoom-out !important; }
9 |
10 | .mg-brush rect.mg-extent {
11 | fill: rgba(0, 0, 0, 0.3); }
12 |
13 | .mg-brushing-in-progress {
14 | -webkit-touch-callout: none;
15 | -webkit-user-select: none;
16 | -khtml-user-select: none;
17 | -moz-user-select: none;
18 | -ms-user-select: none;
19 | user-select: none; }
20 |
--------------------------------------------------------------------------------
/tests/helpers.js:
--------------------------------------------------------------------------------
1 | function generateMouseEvent(type) {
2 | var event = document.createEvent('MouseEvent');
3 | event.initEvent(type, true, true);
4 | return event;
5 | }
6 |
7 | // essentially the same as $.extend
8 | function extend(){
9 | var result = {},
10 | $__arguments = [].slice.call(arguments);
11 |
12 | $__arguments.forEach(function(obj) {
13 | for (var prop in obj) {
14 | if (obj.hasOwnProperty(prop)) {
15 | result[prop] = obj[prop];
16 | }
17 | }
18 | });
19 | return result;
20 | }
21 |
--------------------------------------------------------------------------------
/examples/data/neg2.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "measure": 43.34,
4 | "subject": 32.0
5 | },
6 | {
7 | "measure": 53.3423,
8 | "subject": 20.27
9 | },
10 | {
11 | "measure": -10.343,
12 | "subject": 12.42
13 | },
14 | {
15 | "measure": -1,
16 | "subject": -1.69
17 | },
18 | {
19 | "measure": 10,
20 | "subject": -10.01
21 | },
22 | {
23 | "measure": 25,
24 | "subject": -21.59
25 | },
26 | {
27 | "measure": -20.343,
28 | "subject": -31.86
29 | }
30 | ]
--------------------------------------------------------------------------------
/examples/data/small-range.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "date": "2014-01-01",
4 | "value": 1
5 | },
6 | {
7 | "date": "2014-01-02",
8 | "value": 3
9 | },
10 | {
11 | "date": "2014-01-03",
12 | "value": 2
13 | },
14 | {
15 | "date": "2014-01-04",
16 | "value": 2
17 | },
18 | {
19 | "date": "2014-01-05",
20 | "value": 2
21 | },
22 | {
23 | "date": "2014-01-06",
24 | "value": 1
25 | }
26 | ]
--------------------------------------------------------------------------------
/examples/data/xnotdate.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "males": 50,
4 | "females": 12
5 | },
6 | {
7 | "males": 95,
8 | "females": 66
9 | },
10 | {
11 | "males": 143,
12 | "females": 89
13 | },
14 | {
15 | "males": 198,
16 | "females": 105
17 | },
18 | {
19 | "males": 244,
20 | "females": 533
21 | },
22 | {
23 | "males": 277,
24 | "females": 175
25 | },
26 | {
27 | "males": 344,
28 | "females": 401
29 | },
30 | {
31 | "males": 441,
32 | "females": 1299
33 | }
34 | ]
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 4
14 |
15 | [*.js]
16 | indent_style = space
17 | indent_size = 4
18 |
19 | [*.hbs]
20 | indent_style = space
21 | indent_size = 4
22 |
23 | [*.css]
24 | indent_style = space
25 | indent_size = 4
26 |
27 | [*.html]
28 | indent_style = space
29 | indent_size = 4
30 |
31 | [*.{diff,md}]
32 | trim_trailing_whitespace = false
33 |
--------------------------------------------------------------------------------
/src/js/misc/formatters.js:
--------------------------------------------------------------------------------
1 | function format_rollover_number(args) {
2 | var num;
3 | if (args.format === 'count') {
4 | num = function(d_) {
5 | var is_float = d_ % 1 !== 0;
6 | var n = d3.format("0,000");
7 | d_ = is_float ? d3.round(d_, args.decimals) : d_;
8 | return n(d_);
9 | };
10 | } else {
11 | num = function(d_) {
12 | var fmt_string = (args.decimals ? '.' + args.decimals : '' ) + '%';
13 | var n = d3.format(fmt_string);
14 | return n(d_);
15 | };
16 | }
17 | return num;
18 | }
19 |
20 | MG.format_rollover_number = format_rollover_number;
21 |
--------------------------------------------------------------------------------
/src/js/common/window_listeners.js:
--------------------------------------------------------------------------------
1 | function mg_window_listeners(args) {
2 | mg_if_aspect_ratio_resize_svg(args);
3 | }
4 |
5 | function mg_if_aspect_ratio_resize_svg(args) {
6 | //have we asked the svg to fill a div, if so resize with div
7 | if (args.full_width || args.full_height) {
8 | window.addEventListener('resize', function() {
9 | var svg = d3.select(args.target).select('svg');
10 | var aspect = svg.attr('height') / svg.attr('width');
11 | var newWidth = get_width(args.target);
12 |
13 | svg.attr('width', newWidth);
14 | svg.attr('height', aspect * newWidth);
15 | }, true);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/data/missing-y.json:
--------------------------------------------------------------------------------
1 |
2 | [
3 | {
4 | "date": "2014-01-08",
5 | "value": 500
6 | },
7 | {
8 | "date": "2014-01-12",
9 | "value": 500
10 | },
11 | {
12 | "date": "2014-01-19",
13 | "value": 2000
14 | },
15 | {
16 | "date": "2014-01-20",
17 | "value": 2200
18 | },
19 | {
20 | "date": "2014-01-21",
21 | "value": 2300
22 | },
23 | {
24 | "date": "2014-04-23",
25 | "value": 500
26 | },
27 | {
28 | "date": "2014-04-24",
29 | "value": 1500
30 | },
31 | {
32 | "date": "2014-04-25",
33 | "value": 3000
34 | }
35 | ]
36 |
--------------------------------------------------------------------------------
/examples/data/missing-is-hidden.json:
--------------------------------------------------------------------------------
1 |
2 | [
3 | {
4 | "date": "2014-01-08",
5 | "value": 500
6 | },
7 | {
8 | "date": "2014-01-09",
9 | "value": 500
10 | },
11 | {
12 | "date": "2014-01-10",
13 | "value": 400
14 | },
15 | {
16 | "date": "2014-02-12",
17 | "value": 500
18 | },
19 | {
20 | "date": "2014-02-13",
21 | "value": 100
22 | },
23 | {
24 | "date": "2014-02-14",
25 | "value": null
26 | },
27 | {
28 | "date": "2014-02-15",
29 | "value": 30
30 | },
31 | {
32 | "date": "2014-02-16",
33 | "value": 300
34 | },
35 | {
36 | "date": "2014-02-17",
37 | "value": 200
38 | }
39 | ]
40 |
--------------------------------------------------------------------------------
/src/js/misc/transitions.js:
--------------------------------------------------------------------------------
1 | // http://bl.ocks.org/mbostock/3916621
2 | function path_tween(d1, precision) {
3 | return function() {
4 | var path0 = this,
5 | path1 = path0.cloneNode(),
6 | n0 = path0.getTotalLength() || 0,
7 | n1 = (path1.setAttribute("d", d1), path1).getTotalLength() || 0;
8 |
9 | // Uniform sampling of distance based on specified precision.
10 | var distances = [0], i = 0, dt = precision / Math.max(n0, n1);
11 | while ((i += dt) < 1) distances.push(i);
12 | distances.push(1);
13 |
14 | // Compute point-interpolators at each distance.
15 | var points = distances.map(function(t) {
16 | var p0 = path0.getPointAtLength(t * n0),
17 | p1 = path1.getPointAtLength(t * n1);
18 | return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]);
19 | });
20 |
21 | return function(t) {
22 | return t < 1 ? "M" + points.map(function(p) { return p(t); }).join("L") : d1;
23 | };
24 | };
25 | }
26 |
27 | MG.path_tween = path_tween;
28 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "metrics-graphics",
3 | "main": [
4 | "dist/metricsgraphics.js",
5 | "dist/metricsgraphics.css"
6 | ],
7 | "dependencies": {
8 | "jquery": ">=1.11.1",
9 | "d3": ">=3.4.8"
10 | },
11 | "ignore": [
12 | ".DS_Store",
13 | ".git",
14 | ".gitignore",
15 | "examples",
16 | "gulp",
17 | "gulpfile.js",
18 | "index.js",
19 | "node_modules",
20 | "package.json",
21 | "src",
22 | "testem.json",
23 | "tests"
24 | ],
25 | "license": "MPL-2.0",
26 | "authors": [
27 | "Ali Almossawi", "Ali Almossawi (http://twitter.com/alialmossawi)>",
28 | "Hamilton Ulmer", "Hamilton Ulmer (http://twitter.com/hamiltonulmer)>"
29 | ],
30 | "homepage": "http://metricsgraphicsjs.org",
31 | "repository": {
32 | "type": "git",
33 | "url": "git://github.com/mozilla/metrics-graphics.git"
34 | },
35 | "keywords": [
36 | "metrics-graphics",
37 | "metricsgraphicsjs",
38 | "metricsgraphics",
39 | "metricsgraphics.js",
40 | "d3 charts"
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/contribute.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MetricsGraphics.js",
3 | "description": "A library optimized for concise, principled data graphics and layouts.",
4 | "repository": {
5 | "url": "https://github.com/mozilla/metrics-graphics",
6 | "license": "MPL2",
7 | "tests": "https://travis-ci.org/mozilla/metrics-graphics/"
8 | },
9 | "participate": {
10 | "home": "http://metricsgraphicsjs.org/",
11 | "docs": "https://github.com/mozilla/metrics-graphics/wiki#resources",
12 | "irc": "irc://irc.mozilla.org/#metrics",
13 | "irc-contacts": [
14 | "almossawi",
15 | "hulmer"
16 | ]
17 | },
18 | "bugs": {
19 | "list": "https://github.com/mozilla/metrics-graphics/issues",
20 | "report": "https://github.com/mozilla/metrics-graphics/issues/new",
21 | "mentored": "https://github.com/mozilla/metrics-graphics/labels/help%20wanted"
22 | },
23 | "keywords": [
24 | "nodejs",
25 | "d3",
26 | "data",
27 | "graphics"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/testem.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "qunit",
3 | "src_files": [
4 | "node_modules/jquery/dist/jquery.js",
5 | "node_modules/d3/d3.js",
6 |
7 | "src/js/MG.js",
8 | "src/js/common/register.js",
9 | "src/js/common/hooks.js",
10 | "src/js/common/data_graphic.js",
11 | "src/js/common/bootstrap_tooltip_popover.js",
12 | "src/js/common/chart_title.js",
13 | "src/js/common/y_axis.js",
14 | "src/js/common/x_axis.js",
15 | "src/js/common/init.js",
16 | "src/js/common/markers.js",
17 | "src/js/common/window_listeners.js",
18 | "src/js/layout/bootstrap_dropdown.js",
19 | "src/js/layout/button.js",
20 | "src/js/charts/line.js",
21 | "src/js/charts/histogram.js",
22 | "src/js/charts/point.js",
23 | "src/js/charts/bar.js",
24 | "src/js/charts/table.js",
25 | "src/js/charts/missing.js",
26 | "src/js/misc/process.js",
27 | "src/js/misc/smoothers.js",
28 | "src/js/misc/formatters.js",
29 | "src/js/misc/transitions.js",
30 | "src/js/misc/utility.js",
31 | "src/js/misc/error.js",
32 |
33 | "tests/helpers.js",
34 |
35 | "tests/**/*_test.js"
36 | ],
37 | "launch_in_ci": [
38 | "PhantomJS"
39 | ],
40 | "launch_in_dev": [
41 | "PhantomJS",
42 | "Chrome"
43 | ],
44 | "phantomjs_debug_port": 9000
45 | }
46 |
--------------------------------------------------------------------------------
/HOOKS.md:
--------------------------------------------------------------------------------
1 | # Hooks
2 | ### Global
3 | | Name | args | Description |
4 | |------|------|-------------|
5 | | `global.defaults` | `defaults` | Passes the global defaults prior to merging with args and chart-specific defaults |
6 | | `global.before_init` | `args` | Called before initializing a chart. Allows pre-processing of the arguments passed into `MG.data_graphic`. |
7 | | `x_axis.process_min_max` | `args`, `min_x`, `max_x` | Called after calculating the min and max values for the X axis |
8 | | `y_axis.process_min_max` | `args`, `min_y`, `max_y` | Called after calculating the min and max values for the Y axis |
9 |
10 | ### Line
11 | | Name | args | Description | Notes |
12 | |------|------|-------------|-------|
13 | | `line.after_init` | `lineChart` - chart descriptor | Called after intializing the chart | |
14 | | `line.after_rollover` | `args` | Called after setting up the rollover | |
15 | | `line.before_all_series` | `args` | Called before rendering the chart. | Returning `false` will prevent the default rendering process from being executed. |
16 | | `line.before_each_series` | `data[i]` - The current data in the for loop `args` | Called within the render loop, before any other render takes place. | |
17 | | `line.after_each_series` | `data[i]` - The current data in the for loop `args` | Called within the render loop, after the default render has taken place. | |
18 |
--------------------------------------------------------------------------------
/examples/data/missing-is-hidden-accessor.json:
--------------------------------------------------------------------------------
1 |
2 | [
3 | {
4 | "date": "2014-01-08",
5 | "value": 500
6 | },
7 | {
8 | "date": "2014-01-09",
9 | "value": 500
10 | },
11 | {
12 | "date": "2014-01-10",
13 | "value": 400
14 | },
15 | {
16 | "date": "2014-01-11",
17 | "value": 500,
18 | "dead": true
19 | },
20 | {
21 | "date": "2014-01-12",
22 | "value": 400
23 | },
24 | {
25 | "date": "2014-01-13",
26 | "value": 430
27 | },
28 | {
29 | "date": "2014-01-14",
30 | "value": 410
31 | },
32 | {
33 | "date": "2014-01-15",
34 | "value": 200,
35 | "dead": true
36 | },
37 | {
38 | "date": "2014-01-16",
39 | "value": 500
40 | },
41 | {
42 | "date": "2014-01-17",
43 | "value": 100
44 | },
45 | {
46 | "date": "2014-01-18",
47 | "value": 30
48 | },
49 | {
50 | "date": "2014-01-19",
51 | "value": 300
52 | },
53 | {
54 | "date": "2014-01-20",
55 | "value": 200
56 | }
57 | ]
58 |
--------------------------------------------------------------------------------
/src/js/common/hooks.js:
--------------------------------------------------------------------------------
1 | /**
2 | Record of all registered hooks.
3 | For internal use only.
4 | */
5 | MG._hooks = {};
6 |
7 | /**
8 | Add a hook callthrough to the stack.
9 |
10 | Hooks are executed in the order that they were registered.
11 | */
12 | MG.add_hook = function(name, func, context) {
13 | var hooks;
14 |
15 | if (!MG._hooks[name]) {
16 | MG._hooks[name] = [];
17 | }
18 |
19 | hooks = MG._hooks[name];
20 |
21 | var already_registered =
22 | hooks.filter(function(hook) {
23 | return hook.func === func;
24 | })
25 | .length > 0;
26 |
27 | if (already_registered) {
28 | throw 'That function is already registered.';
29 | }
30 |
31 | hooks.push({
32 | func: func,
33 | context: context
34 | });
35 | };
36 |
37 | /**
38 | Execute registered hooks.
39 |
40 | Optional arguments
41 | */
42 | MG.call_hook = function(name) {
43 | var hooks = MG._hooks[name],
44 | result = [].slice.apply(arguments, [1]),
45 | processed;
46 |
47 | if (hooks) {
48 | hooks.forEach(function(hook) {
49 | if (hook.func) {
50 | var params = processed || result;
51 |
52 | if (params && params.constructor !== Array) {
53 | params = [params];
54 | }
55 |
56 | params = [].concat.apply([], params);
57 | processed = hook.func.apply(hook.context, params);
58 | }
59 | });
60 | }
61 |
62 | return processed || result;
63 | };
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "metrics-graphics",
3 | "version": "2.7.0",
4 | "description": "A library optimized for concise, principled data graphics and layouts",
5 | "main": "dist/metricsgraphics.js",
6 | "scripts": {
7 | "build": "gulp build:js",
8 | "test": "gulp test",
9 | "test-ci": "./node_modules/testem/testem.js ci testem.json"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/mozilla/metrics-graphics.git"
14 | },
15 | "keywords": [
16 | "metrics-graphics",
17 | "metricsgraphicsjs",
18 | "metricsgraphics",
19 | "metricsgraphics.js",
20 | "d3 charts"
21 | ],
22 | "author": "Mozilla",
23 | "contributors": [
24 | "Ali Almossawi (http://twitter.com/alialmossawi)",
25 | "Hamilton Ulmer (http://twitter.com/hamiltonulmer)"
26 | ],
27 | "license": "MPL-2.0",
28 | "bugs": {
29 | "url": "https://github.com/mozilla/metrics-graphics/issues"
30 | },
31 | "engines": {
32 | "node": ">=0.8.0"
33 | },
34 | "homepage": "http://metricsgraphicsjs.org",
35 | "dependencies": {
36 | "jquery": ">=1.11.1",
37 | "d3": ">=3.4.8"
38 | },
39 | "devDependencies": {
40 | "gulp": "^3.8.10",
41 | "gulp-concat": "^2.4.2",
42 | "gulp-connect": "^2.2.0",
43 | "gulp-es6-module-transpiler": "^0.2.0",
44 | "gulp-jshint": "^1.9.0",
45 | "gulp-rename": "^1.2.0",
46 | "gulp-rimraf": "^0.1.1",
47 | "gulp-testem": "0.0.1",
48 | "gulp-uglify": "^1.0.2",
49 | "gulp-umd": "^0.1.3",
50 | "gulp-util": "^3.0.1",
51 | "qunitjs": "^1.16.0",
52 | "require-dir": "^0.1.0",
53 | "testem": "^0.6.24"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/misc/utility_test.js:
--------------------------------------------------------------------------------
1 | module('utility');
2 |
3 | test('MG.convert.date', function() {
4 | var data = [{'date': '2014-01-01', 'value': 12},
5 | {'date': '2014-03-01', 'value': 18}];
6 |
7 | MG.convert.date(data, 'date');
8 | equal($.type(data[0].date), 'date', 'First date is of type date');
9 | equal($.type(data[0].date), 'date', 'Second date is of type date');
10 | });
11 |
12 | test('MG.convert.date with an alternative timestamp style', function() {
13 | var data = [{'date': '2014-20-12', 'value': 12},
14 | {'date': '2014-21-12', 'value': 18}];
15 |
16 | MG.convert.date(data, 'date', '%Y-%d-%m');
17 | equal($.type(data[0].date), 'date', 'First date is of type date');
18 | equal($.type(data[0].date), 'date', 'Second date is of type date');
19 | });
20 |
21 | test('MG.convert.number', function() {
22 | var data = [{'date': '2014-20-12', 'value': '12'},
23 | {'date': '2014-21-12', 'value': '18'}];
24 |
25 | MG.convert.number(data, 'value');
26 | equal($.type(data[0].value), 'number', 'First value is a number');
27 | equal($.type(data[0].value), 'number', 'Second value is a number');
28 | });
29 |
30 | test('mg_get_svg_child_of', function(){
31 | d3.select('#qunit-fixture').append('svg');
32 |
33 | var svg_element_with_node = mg_get_svg_child_of(document.querySelector('#qunit-fixture'));
34 | var svg_element_with_text = mg_get_svg_child_of('#qunit-fixture');
35 |
36 | equal(svg_element_with_node.length, 1, 'Node-based argument should return a d3 selection with svg.');
37 | equal(svg_element_with_node.length, 1, 'Selector-based argument should return a d3 selection with svg.');
38 | });
39 |
40 |
41 | test('mg_target_ref', function() {
42 | var chart_area2 = document.createElement('div');
43 | mg_target_ref(chart_area2);
44 | ok(chart_area2.getAttribute('data-mg-uid').match(/mg-[\d]/), 'applies generated ID to DOM element');
45 | });
46 |
--------------------------------------------------------------------------------
/tests/charts/histogram_test.js:
--------------------------------------------------------------------------------
1 | module('histogram');
2 |
3 | test('A solitary active datapoint exists', function() {
4 | var params = {
5 | target: '#qunit-fixture',
6 | data: d3.range(10000).map(d3.random.bates(10)),
7 | chart_type: 'histogram',
8 | linked: true
9 | };
10 |
11 | MG.data_graphic(params);
12 | equal(document.querySelectorAll('.mg-active-datapoint').length, 1, 'One active datapoint exists');
13 | });
14 |
15 | test('Rollovers exist', function() {
16 | var params = {
17 | target: '#qunit-fixture',
18 | data: d3.range(10000).map(d3.random.bates(10)),
19 | chart_type: 'histogram',
20 | linked: true
21 | };
22 |
23 | MG.data_graphic(params);
24 | ok(document.querySelector('.mg-rollover-rect'), 'Rollovers exist');
25 | });
26 |
27 | test('We have only one set of rollovers', function() {
28 | var params = {
29 | target: '#qunit-fixture',
30 | data: d3.range(10000).map(d3.random.bates(10)),
31 | chart_type: 'histogram',
32 | linked: true
33 | };
34 |
35 | MG.data_graphic(params);
36 | equal(document.querySelectorAll('.mg-rollover-rect').length, 1, 'One set of rollovers exists');
37 | });
38 |
39 | test('Linked chart has the required class set', function() {
40 | var params = {
41 | target: '#qunit-fixture',
42 | data: d3.range(10000).map(d3.random.bates(10)),
43 | chart_type: 'histogram',
44 | linked: true
45 | };
46 |
47 | MG.data_graphic(params);
48 | var matches = document.querySelector(params.target + ' svg').getAttribute('class').match(/linked/);
49 | ok(matches, 'Linked chart has class `linked` set');
50 | });
51 |
52 | test('Histogram exists', function() {
53 | var params = {
54 | target: '#qunit-fixture',
55 | data: d3.range(10000).map(d3.random.bates(10)),
56 | chart_type: 'histogram',
57 | linked: true
58 | };
59 |
60 | MG.data_graphic(params);
61 | ok(document.querySelector('.mg-histogram'), 'Histogram exists');
62 | });
--------------------------------------------------------------------------------
/tests/common/data_graphic_test.js:
--------------------------------------------------------------------------------
1 | module('data_graphic');
2 |
3 | test('Required arguments are set', function() {
4 | var params = {
5 | target: '#qunit-fixture',
6 | data: [{'date': new Date('2014-11-01'), 'value': 12},
7 | {'date': new Date('2014-11-02'), 'value': 18}]
8 | };
9 |
10 | MG.data_graphic(params);
11 |
12 | ok(params.width, 'args.width is set');
13 | ok(params.height, 'args.height is set');
14 | ok(params.data, 'args.data is set');
15 | ok(params.target, 'args.target is set');
16 | });
17 |
18 | test('Dom element works as target', function() {
19 | var params = {
20 | target: document.getElementById('qunit-fixture'),
21 | data: [{'date': new Date('2014-11-01'), 'value': 12},
22 | {'date': new Date('2014-11-02'), 'value': 18}]
23 | };
24 |
25 | MG.data_graphic(params);
26 |
27 | ok(document.querySelector('#qunit-fixture svg') != null, 'passing in dom element works properly');
28 | });
29 |
30 | // Can be removed in 2.x
31 | test('Correctly aliases callbacks when using 1.x-style method names', function() {
32 | var mouseoverCalled = false,
33 | mouseoutCalled = false,
34 |
35 | params = {
36 | target: '#qunit-fixture',
37 | data: [{value: 1, label: 'One'}],
38 | chart_type: 'bar',
39 | rollover_callback: function() {
40 | mouseoverCalled = true;
41 | },
42 | rollout_callback: function() {
43 | mouseoutCalled = true;
44 | }
45 | };
46 |
47 | MG.data_graphic(params);
48 |
49 | var bar = document.getElementsByClassName('mg-bar-rollover')[0];
50 |
51 | bar.dispatchEvent(generateMouseEvent('mouseover'));
52 | equal(mouseoverCalled, true, 'rollover_callback was called');
53 |
54 | bar.dispatchEvent(generateMouseEvent('mouseout'));
55 | equal(mouseoutCalled, true, 'rollout_callback was called');
56 |
57 | ok(MG.deprecations.rollover_callback.warned, 'rollover_callback deprecation notice displayed');
58 | ok(MG.deprecations.rollout_callback.warned, 'rollout_callback deprecation notice displayed');
59 | });
--------------------------------------------------------------------------------
/examples/charts/addons.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
<script src='js/addons/mg_line_brushing.js'></script>
7 | <link rel='stylesheet' href='css/addons/mg_line_brushing.css' />
8 | d3.json('data/fake_users2.json', function(data) {
9 | for (var i = 0; i < data.length; i++) {
10 | data[i] = MG.convert.date(data[i], 'date');
11 | }
12 |
13 | MG.data_graphic({
14 | title: "Brushing Addon by Dan De Havilland",
15 | description: "Drag the crosshair over the chart to zoom. For further details about this addon, take a look at its GitHub repo .",
16 | data: data,
17 | top: 70,
18 | width: 600,
19 | height: 240,
20 | right: 40,
21 | missing_is_hidden: true,
22 | target: '#brushing'
23 | });
24 | });
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/data/make_fake_data.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from datetime import datetime, date, timedelta
3 | import json
4 | from random import random
5 | import argparse
6 |
7 | parser = argparse.ArgumentParser(description="Fake data maker.")
8 | parser.add_argument('-s', '--start', help="start date", nargs=1)
9 | parser.add_argument('-d', '--small', help="allow decimal values", action="store_true")
10 | parser.add_argument('-r', '--range', help="how many days from start date", nargs=1)
11 | parser.add_argument('-m', '--magnitude', help="size of change", nargs=1)
12 | parser.add_argument('-p', '--percentage', help="whether or not this is a percentage", action="store_true")
13 | parser.add_argument('-n', '--let_negative', help="let the data values go negative", action="store_true")
14 | parser.add_argument('-v', '--start_value', help='start value', nargs=1)
15 | args = parser.parse_args()
16 |
17 | doy = {
18 | 0: 1,
19 | 1:.9,
20 | 2:1,
21 | 3:1.1,
22 | 4:1.05,
23 | 5:.7,
24 | 6:.75
25 | }
26 | if args.percentage: convert = lambda x: float(x)
27 | elif args.small: convert = lambda x: float(x)
28 | else: convert = lambda x: int(x)
29 |
30 | current_date = datetime.strptime(args.start[0], '%Y-%m-%d').date()
31 | length = int(args.range[0])
32 |
33 | baseline = convert(args.start_value[0])
34 |
35 | if args.magnitude: magnitude = convert(args.magnitude[0])
36 | elif args.percentage: magnitude = baseline/50.
37 | elif args.small: magnitude = baseline/20.
38 | else: magnitude = baseline/5
39 | out = []
40 |
41 | # Needed to automatically convert dates to strings in json.
42 | dthandler = lambda obj: obj.isoformat() if isinstance(obj, date) else None
43 |
44 | for i in xrange(length):
45 | out.append({'date': current_date, 'value': baseline})
46 | nb = convert(magnitude * (random()-.5))
47 | if args.percentage:
48 | if (baseline+nb < 0 or baseline+nb > 1) and (not args.let_negative): pass
49 | else: baseline += nb
50 | else:
51 | if baseline+nb < 0 and not args.let_negative: pass
52 | else: baseline += nb
53 |
54 | current_date += timedelta(days=1)
55 |
56 | sys.stdout.write(json.dumps(out,default=dthandler, indent=4))
--------------------------------------------------------------------------------
/src/js/common/chart_title.js:
--------------------------------------------------------------------------------
1 | function mg_remove_element (svg, elem) {
2 | svg.select(elem).remove();
3 | }
4 |
5 | function mg_add_chart_title_element (svg, args) {
6 |
7 | }
8 |
9 | function chart_title(args) {
10 | 'use strict';
11 |
12 | var svg = mg_get_svg_child_of(args.target);
13 |
14 | //remove the current title if it exists
15 | //svg.select('.mg-header').remove();
16 | mg_remove_element(svg, '.mg-header');
17 | if (args.target && args.title) {
18 | var chartTitle = svg.insert('text')
19 | .attr('class', 'mg-header')
20 | .attr('x', (args.width + args.left - args.right) / 2)
21 | .attr('y', args.title_y_position)
22 | .attr('text-anchor', 'middle')
23 | .attr('dy', '0.55em');
24 |
25 | //show the title
26 | chartTitle.append('tspan')
27 | .attr('class', 'mg-chart-title')
28 | .text(args.title);
29 |
30 | //show and activate the description icon if we have a description
31 | if (args.show_tooltips && args.description) {
32 | chartTitle.append('tspan')
33 | .attr('class', 'mg-chart-description')
34 | .attr('dx', '0.3em')
35 | .text('\uf059');
36 |
37 | //now that the title is an svg text element, we'll have to trigger
38 | //mouseenter, mouseleave events manually for the popover to work properly
39 | var $chartTitle = $(chartTitle.node());
40 | $chartTitle.popover({
41 | html: true,
42 | animation: false,
43 | placement: 'top',
44 | content: args.description,
45 | container: args.target,
46 | trigger: 'manual',
47 | template: ''
48 | }).on('mouseenter', function() {
49 | d3.selectAll(args.target)
50 | .selectAll('.mg-popover')
51 | .remove();
52 |
53 | $(this).popover('show');
54 | $(args.target).select('.popover')
55 | .on('mouseleave', function () {
56 | $chartTitle.popover('hide');
57 | });
58 | }).on('mouseleave', function () {
59 | setTimeout(function () {
60 | if (!$('.popover:hover').length) {
61 | $chartTitle.popover('hide');
62 | }
63 | }, 120);
64 | });
65 | }
66 | }
67 |
68 | if (args.error) {
69 | error(args);
70 | }
71 | }
72 |
73 | MG.chart_title = chart_title;
74 |
--------------------------------------------------------------------------------
/tests/common/chart_title_test.js:
--------------------------------------------------------------------------------
1 | module('chart_title');
2 |
3 | test('Chart title is updated', function() {
4 | var params = {
5 | title: 'foo',
6 | target: '#qunit-fixture',
7 | data: [{'date': new Date('2014-01-01'), 'value': 12},
8 | {'date': new Date('2014-03-01'), 'value': 18}]
9 | };
10 |
11 | var params2 = MG.clone(params);
12 | params2.title = 'bar';
13 |
14 | MG.data_graphic(params);
15 | MG.data_graphic(params2);
16 |
17 | equal(document.querySelector('.mg-chart-title').textContent, 'bar', 'Chart title is foo');
18 | });
19 |
20 | test('Chart title is removed if title is set to blank', function() {
21 | var params = {
22 | title: 'foo',
23 | target: '#qunit-fixture',
24 | data: [{'date': new Date('2014-01-01'), 'value': 12},
25 | {'date': new Date('2014-03-01'), 'value': 18}]
26 | };
27 |
28 | var params2 = MG.clone(params);
29 | params2.title = '';
30 |
31 | MG.data_graphic(params);
32 | MG.data_graphic(params2);
33 | equal(document.querySelector('.mg-chart-title'), null, 'Chart title is not added');
34 | });
35 |
36 | test('Chart title is removed if title is not set', function() {
37 | var params = {
38 | title: 'foo',
39 | target: '#qunit-fixture',
40 | data: [{'date': new Date('2014-01-01'), 'value': 12},
41 | {'date': new Date('2014-03-01'), 'value': 18}]
42 | };
43 |
44 | var params2 = MG.clone(params);
45 | delete params2.title;
46 |
47 | MG.data_graphic(params);
48 | MG.data_graphic(params2);
49 | equal(document.querySelector('.mg-chart-title'), null, 'Chart title is not added');
50 | });
51 |
52 | test('When a description is set, we get a question mark', function() {
53 | var params = {
54 | title: 'foo',
55 | description: 'bar',
56 | target: '#qunit-fixture',
57 | data: [{'date': new Date('2014-01-01'), 'value': 12},
58 | {'date': new Date('2014-03-01'), 'value': 18}],
59 | show_tooltips: true
60 | };
61 |
62 | MG.data_graphic(params);
63 | ok(document.querySelector('.mg-chart-description'), 'Description icon exists');
64 | });
65 |
66 | test('When an error is set, we get an exclamation icon', function() {
67 | var params = {
68 | title: 'foo',
69 | description: 'bar',
70 | target: '#qunit-fixture',
71 | data: [{'date': new Date('2014-01-01'), 'value': 12},
72 | {'date': new Date('2014-03-01'), 'value': 18}],
73 | error: 'lorem ipsum'
74 | };
75 |
76 | MG.data_graphic(params);
77 | ok(document.querySelector('.mg-chart-title .warning'), 'Error icon exists');
78 | });
79 |
80 | test('Chart title is not duplicated on redraw', function() {
81 | var params = {
82 | title: 'foo',
83 | target: '#qunit-fixture',
84 | data: [{'date': new Date('2014-01-01'), 'value': 12},
85 | {'date': new Date('2014-03-01'), 'value': 18}]
86 | };
87 |
88 | var params2 = MG.clone(params);
89 | MG.data_graphic(params);
90 | MG.data_graphic(params2);
91 |
92 | equal(document.querySelectorAll('.mg-chart-title').length, 1, 'there is once chart title');
93 | });
94 |
--------------------------------------------------------------------------------
/examples/data/firefox_releases.json:
--------------------------------------------------------------------------------
1 | {
2 | "releases": [
3 | {
4 | "version": "30.0",
5 | "date": "2014-06-01",
6 | "end": "2014-08-01"
7 | },
8 | {
9 | "version": "29.0",
10 | "date": "2014-04-29",
11 | "end": "2014-06-01"
12 |
13 | },
14 | {
15 | "version": "28.0",
16 | "date": "2014-03-18",
17 | "end": "2014-04-29"
18 | },
19 | {
20 | "version": "27.0",
21 | "date": "2014-01-21",
22 | "end": "2014-03-18"
23 | },
24 | {
25 | "version": "26.0",
26 | "date": "2013-12-10",
27 | "end": "2014-01-21"
28 | },
29 | {
30 | "version": "25.0",
31 | "date": "2013-10-29",
32 | "end": "2013-12-10"
33 |
34 | },
35 | {
36 | "version": "24.0",
37 | "date": "2013-09-17",
38 | "end": "2013-10-29"
39 |
40 | },
41 | {
42 | "version": "23.0",
43 | "date": "2013-08-06",
44 | "end": "2013-09-17"
45 |
46 | },
47 | {
48 | "version": "22.0",
49 | "date": "2013-06-25"
50 | },
51 | {
52 | "version": "21.0",
53 | "date": "2013-05-14"
54 | },
55 | {
56 | "version": "20.0",
57 | "date": "2013-04-02"
58 | },
59 | {
60 | "version": "19.0",
61 | "date": "2013-02-19"
62 | },
63 | {
64 | "version": "18.0",
65 | "date": "2013-01-08"
66 | },
67 | {
68 | "version": "17.0.1",
69 | "date": "2012-11-30"
70 | },
71 | {
72 | "version": "17.0",
73 | "date": "2012-11-20"
74 | },
75 | {
76 | "version": "16.0.2",
77 | "date": "2012-10-26"
78 | },
79 | {
80 | "version": "16.0.1",
81 | "date": "2012-10-11"
82 | },
83 | {
84 | "version": "16.0",
85 | "date": "2012-10-09"
86 | },
87 | {
88 | "version": "15.0.1",
89 | "date": "2012-09-06"
90 | },
91 | {
92 | "version": "15.0",
93 | "date": "2012-08-28"
94 | },
95 | {
96 | "version": "14.0",
97 | "date": "2012-07-17"
98 | },
99 | {
100 | "version": "13.0.1",
101 | "date": "2012-06-15"
102 | },
103 | {
104 | "version": "13.0",
105 | "date": "2012-06-05"
106 | },
107 | {
108 | "version": "12.0",
109 | "date": "2012-04-24"
110 | },
111 | {
112 | "version": "11.0",
113 | "date": "2012-03-13"
114 | },
115 | {
116 | "version": "10.0",
117 | "date": "2012-01-31"
118 | },
119 | {
120 | "version": "9.0",
121 | "date": "2011-12-20"
122 | },
123 | {
124 | "version": "8.0",
125 | "date": "2011-11-08"
126 | },
127 | {
128 | "version": "7.0",
129 | "date": "2011-09-27"
130 | },
131 | {
132 | "version": "6.0",
133 | "date": "2011-08-16"
134 | },
135 | {
136 | "version": "5.0",
137 | "date": "2011-06-21"
138 | },
139 | {
140 | "version": "4.0",
141 | "date": "2011-03-22"
142 | },
143 | {
144 | "version": "3.6",
145 | "date": "2010-01-21"
146 | },
147 | {
148 | "version": "3.5",
149 | "date": "2009-06-30"
150 | },
151 | {
152 | "version": "3.0",
153 | "date": "2008-06-17"
154 | },
155 | {
156 | "version": "2.0",
157 | "date": "2006-10-24"
158 | },
159 | {
160 | "version": "1.5",
161 | "date": "2005-11-29"
162 | },
163 | {
164 | "version": "1.0",
165 | "date": "2004-11-09"
166 | }
167 | ]
168 | }
169 |
--------------------------------------------------------------------------------
/tests/charts/bar_test.js:
--------------------------------------------------------------------------------
1 | var target = '#qunit-fixture',
2 | defaults;
3 |
4 | module('bar', {
5 | setup: function() {
6 | defaults = {
7 | target: target,
8 | chart_type: 'bar',
9 | x_accessor: 'value',
10 | y_accessor: 'label',
11 | transition_on_update: false,
12 | data: [{
13 | label: 'Bar 1',
14 | value: 100
15 | },{
16 | label: 'Bar 2',
17 | value: 200
18 | },{
19 | label: 'Bar 3',
20 | value: 300
21 | }]
22 | };
23 | }
24 | });
25 |
26 | test('Correct number of bars are added', function() {
27 | expect(1);
28 | MG.data_graphic(defaults);
29 | equal(document.querySelectorAll('.mg-bar').length, 3, 'Should have 3 bars');
30 | });
31 |
32 | test('Triggers callbacks when provided', function() {
33 | var mouseoverCalled = false,
34 | mousemoveCalled = false,
35 | mouseoutCalled = false,
36 |
37 | params = extend(defaults, {
38 | mouseover: function() {
39 | mouseoverCalled = true;
40 | },
41 | mousemove: function() {
42 | mousemoveCalled = true;
43 | },
44 | mouseout: function() {
45 | mouseoutCalled = true;
46 | }
47 | });
48 |
49 | MG.data_graphic(params);
50 |
51 | var bar = document.getElementsByClassName('mg-bar-rollover')[0];
52 |
53 | bar.dispatchEvent(generateMouseEvent('mouseover'));
54 | equal(mouseoverCalled, true, 'mouseover was called');
55 |
56 | bar.dispatchEvent(generateMouseEvent('mousemove'));
57 | equal(mousemoveCalled, true, 'mousemove was called');
58 |
59 | bar.dispatchEvent(generateMouseEvent('mouseout'));
60 | equal(mouseoutCalled, true, 'mouseout was called');
61 | });
62 |
63 | test('When updating', function() {
64 | var bars = [{
65 | label: 'Bar 1',
66 | value: 100,
67 | predictor: 75,
68 | baseline: 50
69 | }];
70 |
71 | var params = extend(defaults, {
72 | data: bars,
73 | height: 100,
74 | width: 300,
75 | orientation: 'vertical',
76 | predictor_accessor: 'predictor',
77 | baseline_accessor: 'baseline',
78 | animate_on_load: false,
79 | transition_on_update: false
80 | });
81 |
82 | MG.data_graphic(params);
83 | equal(164, d3.select(target).select('.mg-barplot .mg-bar').attr('width'), 'initial bar size is correct');
84 | equal(123, d3.select(target).select('.mg-barplot .mg-bar-prediction').attr('width'), 'initial predictor size is correct');
85 | equal(160, d3.select(target).select('.mg-barplot .mg-bar-baseline').attr('x1'), 'initial baseline position is correct');
86 |
87 | params.data[0][0].value = 50;
88 | params.data[0][0].predictor = 100;
89 | params.data[0][0].baseline = 75;
90 |
91 | MG.data_graphic(params);
92 | equal(82, d3.select(target).select('.mg-barplot .mg-bar').attr('width'), 'the bars are redrawn with correct sizes');
93 | equal(164, d3.select(target).select('.mg-barplot .mg-bar-prediction').attr('width'), 'the predictors are redrawn with correct sizes');
94 | equal(201, d3.select(target).select('.mg-barplot .mg-bar-baseline').attr('x1'), 'the baseline is redrawn in the correct position');
95 | });
96 |
--------------------------------------------------------------------------------
/tests/charts/missing_test.js:
--------------------------------------------------------------------------------
1 | module('missing');
2 |
3 | test('Missing chart\'s text matches specified missing_text', function() {
4 | var params = {
5 | target: '#qunit-fixture',
6 | chart_type: 'missing-data',
7 | missing_text: 'In an astral plane that was never meant to fly...'
8 | };
9 |
10 | MG.data_graphic(params);
11 | equal(document.querySelector('.mg-missing-text').textContent,
12 | params.missing_text,
13 | 'Missing chart\'s text matches missing_text');
14 | });
15 |
16 | test('Only one mg-missing-pane on multiple calls to the same target element', function() {
17 | var params = {
18 | target: '#qunit-fixture',
19 | chart_type: 'missing-data',
20 | missing_text: 'In an astral plane that was never meant to fly...'
21 | };
22 |
23 | MG.data_graphic(params);
24 | MG.data_graphic(MG.clone(params));
25 |
26 | equal(document.querySelectorAll(params.target + ' .mg-missing-pane').length, 1, 'We only have one mg-missing-pane');
27 | });
28 |
29 | test('Only one mg-missing-text on multiple calls to the same target element', function() {
30 | var params = {
31 | target: '#qunit-fixture',
32 | chart_type: 'missing-data',
33 | missing_text: 'In an astral plane that was never meant to fly...'
34 | };
35 |
36 | MG.data_graphic(params);
37 | MG.data_graphic(MG.clone(params));
38 |
39 | equal(document.querySelectorAll(params.target + ' .mg-missing-text').length, 1, 'We only have one mg-missing-text');
40 | });
41 |
42 | test('missing chart obeys full_width: true', function() {
43 | var params = {
44 | target: '#qunit-fixture',
45 | chart_type: 'missing-data',
46 | full_width: true,
47 | missing_text: 'In an astral plane that was never meant to fly...'
48 | };
49 | document.querySelector('#qunit-fixture').style.width='700px';
50 |
51 | MG.data_graphic(params);
52 |
53 | equal(document.querySelector('#qunit-fixture svg').getAttribute('width'), 700, 'The missing chart svg has same width as parent element.');
54 | });
55 |
56 | test('missing chart obeys full_height: true', function() {
57 | var params = {
58 | target: '#qunit-fixture',
59 | chart_type: 'missing-data',
60 | full_height: true,
61 | missing_text: 'In an astral plane that was never meant to fly...'
62 | };
63 | document.querySelector('#qunit-fixture').style.height='700px';
64 |
65 | MG.data_graphic(params);
66 |
67 | equal(document.querySelector('#qunit-fixture svg').getAttribute('height'), 700, 'The missing chart svg has same width as parent element.');
68 | });
69 |
70 | test('Missing chart\'s width is set correctly on subsequent calls to existing chart', function() {
71 | var params_0 = {
72 | target: '#qunit-fixture',
73 | chart_type: 'missing-data',
74 | missing_text: 'In an astral plane that was never meant to fly...'
75 | };
76 |
77 | var params = {
78 | target: '#qunit-fixture',
79 | chart_type: 'missing-data',
80 | missing_text: 'In an astral plane that was never meant to fly...',
81 | width: 200,
82 | height: 100,
83 | };
84 |
85 | MG.data_graphic(params_0);
86 | MG.data_graphic(params);
87 |
88 | var width = document.querySelector(params.target + ' svg').offsetWidth;
89 | ok(width == 200, 'SVG\'s width matches latest specified width');
90 | });
--------------------------------------------------------------------------------
/examples/css/highlightjs-default.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #f0f0f0;
12 | -webkit-text-size-adjust: none;
13 | }
14 |
15 | .hljs,
16 | .hljs-subst,
17 | .hljs-tag .hljs-title,
18 | .nginx .hljs-title {
19 | color: black;
20 | }
21 |
22 | .hljs-string,
23 | .hljs-title,
24 | .hljs-constant,
25 | .hljs-parent,
26 | .hljs-tag .hljs-value,
27 | .hljs-rules .hljs-value,
28 | .hljs-preprocessor,
29 | .hljs-pragma,
30 | .haml .hljs-symbol,
31 | .ruby .hljs-symbol,
32 | .ruby .hljs-symbol .hljs-string,
33 | .hljs-template_tag,
34 | .django .hljs-variable,
35 | .smalltalk .hljs-class,
36 | .hljs-addition,
37 | .hljs-flow,
38 | .hljs-stream,
39 | .bash .hljs-variable,
40 | .apache .hljs-tag,
41 | .apache .hljs-cbracket,
42 | .tex .hljs-command,
43 | .tex .hljs-special,
44 | .erlang_repl .hljs-function_or_atom,
45 | .asciidoc .hljs-header,
46 | .markdown .hljs-header,
47 | .coffeescript .hljs-attribute {
48 | color: #e6337c;
49 | }
50 |
51 | .smartquote,
52 | .hljs-comment,
53 | .hljs-annotation,
54 | .hljs-template_comment,
55 | .diff .hljs-header,
56 | .hljs-chunk,
57 | .asciidoc .hljs-blockquote,
58 | .markdown .hljs-blockquote {
59 | color: #888;
60 | }
61 |
62 | .hljs-number,
63 | .hljs-date,
64 | .hljs-regexp,
65 | .hljs-literal,
66 | .hljs-hexcolor,
67 | .smalltalk .hljs-symbol,
68 | .smalltalk .hljs-char,
69 | .go .hljs-constant,
70 | .hljs-change,
71 | .lasso .hljs-variable,
72 | .makefile .hljs-variable,
73 | .asciidoc .hljs-bullet,
74 | .markdown .hljs-bullet,
75 | .asciidoc .hljs-link_url,
76 | .markdown .hljs-link_url {
77 | color: #366797;
78 | }
79 |
80 | .hljs-label,
81 | .hljs-javadoc,
82 | .ruby .hljs-string,
83 | .hljs-decorator,
84 | .hljs-filter .hljs-argument,
85 | .hljs-localvars,
86 | .hljs-array,
87 | .hljs-attr_selector,
88 | .hljs-important,
89 | .hljs-pseudo,
90 | .hljs-pi,
91 | .haml .hljs-bullet,
92 | .hljs-doctype,
93 | .hljs-deletion,
94 | .hljs-envvar,
95 | .hljs-shebang,
96 | .apache .hljs-sqbracket,
97 | .nginx .hljs-built_in,
98 | .tex .hljs-formula,
99 | .erlang_repl .hljs-reserved,
100 | .hljs-prompt,
101 | .asciidoc .hljs-link_label,
102 | .markdown .hljs-link_label,
103 | .vhdl .hljs-attribute,
104 | .clojure .hljs-attribute,
105 | .asciidoc .hljs-attribute,
106 | .lasso .hljs-attribute,
107 | .coffeescript .hljs-property,
108 | .hljs-phony {
109 | color: #88f;
110 | }
111 |
112 | .hljs-keyword,
113 | .hljs-id,
114 | .hljs-title,
115 | .hljs-built_in,
116 | .css .hljs-tag,
117 | .hljs-javadoctag,
118 | .hljs-phpdoc,
119 | .hljs-dartdoc,
120 | .hljs-yardoctag,
121 | .smalltalk .hljs-class,
122 | .hljs-winutils,
123 | .bash .hljs-variable,
124 | .apache .hljs-tag,
125 | .hljs-type,
126 | .hljs-typename,
127 | .tex .hljs-command,
128 | .asciidoc .hljs-strong,
129 | .markdown .hljs-strong,
130 | .hljs-request,
131 | .hljs-status {
132 | font-weight: bold;
133 | }
134 |
135 | .asciidoc .hljs-emphasis,
136 | .markdown .hljs-emphasis {
137 | font-style: italic;
138 | }
139 |
140 | .nginx .hljs-built_in {
141 | font-weight: normal;
142 | }
143 |
144 | .coffeescript .javascript,
145 | .javascript .xml,
146 | .lasso .markup,
147 | .tex .hljs-formula,
148 | .xml .javascript,
149 | .xml .vbscript,
150 | .xml .css,
151 | .xml .hljs-cdata {
152 | opacity: 0.5;
153 | }
154 |
--------------------------------------------------------------------------------
/examples/css/railscasts.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Railscasts-like style (c) Visoft, Inc. (Damien White)
4 |
5 | */
6 |
7 | .hljs {
8 | display: block;
9 | overflow-x: auto;
10 | padding: 0.5em;
11 | background: #232323;
12 | color: #e6e1dc;
13 | -webkit-text-size-adjust: none;
14 | }
15 |
16 | .hljs-comment,
17 | .hljs-javadoc,
18 | .hljs-shebang {
19 | color: #bc9458;
20 | font-style: italic;
21 | }
22 |
23 | .hljs-keyword,
24 | .ruby .hljs-function .hljs-keyword,
25 | .hljs-request,
26 | .hljs-status,
27 | .nginx .hljs-title,
28 | .method,
29 | .hljs-list .hljs-title {
30 | color: #c26230;
31 | }
32 |
33 | .hljs-string,
34 | .hljs-number,
35 | .hljs-regexp,
36 | .hljs-tag .hljs-value,
37 | .hljs-cdata,
38 | .hljs-filter .hljs-argument,
39 | .hljs-attr_selector,
40 | .apache .hljs-cbracket,
41 | .hljs-date,
42 | .tex .hljs-command,
43 | .asciidoc .hljs-link_label,
44 | .markdown .hljs-link_label {
45 | color: #a5c261;
46 | }
47 |
48 | .hljs-subst {
49 | color: #519f50;
50 | }
51 |
52 | .hljs-tag,
53 | .hljs-tag .hljs-keyword,
54 | .hljs-tag .hljs-title,
55 | .hljs-doctype,
56 | .hljs-sub .hljs-identifier,
57 | .hljs-pi,
58 | .input_number {
59 | color: #e8bf6a;
60 | }
61 |
62 | .hljs-identifier {
63 | color: #d0d0ff;
64 | }
65 |
66 | .hljs-class .hljs-title,
67 | .hljs-type,
68 | .smalltalk .hljs-class,
69 | .hljs-javadoctag,
70 | .hljs-yardoctag,
71 | .hljs-phpdoc,
72 | .hljs-dartdoc {
73 | text-decoration: none;
74 | }
75 |
76 | .hljs-constant {
77 | color: #da4939;
78 | }
79 |
80 |
81 | .hljs-symbol,
82 | .hljs-built_in,
83 | .ruby .hljs-symbol .hljs-string,
84 | .ruby .hljs-symbol .hljs-identifier,
85 | .asciidoc .hljs-link_url,
86 | .markdown .hljs-link_url,
87 | .hljs-attribute {
88 | color: #6d9cbe;
89 | }
90 |
91 | .asciidoc .hljs-link_url,
92 | .markdown .hljs-link_url {
93 | text-decoration: underline;
94 | }
95 |
96 |
97 |
98 | .hljs-params,
99 | .hljs-variable,
100 | .clojure .hljs-attribute {
101 | color: #d0d0ff;
102 | }
103 |
104 | .css .hljs-tag,
105 | .hljs-rules .hljs-property,
106 | .hljs-pseudo,
107 | .tex .hljs-special {
108 | color: #cda869;
109 | }
110 |
111 | .css .hljs-class {
112 | color: #9b703f;
113 | }
114 |
115 | .hljs-rules .hljs-keyword {
116 | color: #c5af75;
117 | }
118 |
119 | .hljs-rules .hljs-value {
120 | color: #cf6a4c;
121 | }
122 |
123 | .css .hljs-id {
124 | color: #8b98ab;
125 | }
126 |
127 | .hljs-annotation,
128 | .apache .hljs-sqbracket,
129 | .nginx .hljs-built_in {
130 | color: #9b859d;
131 | }
132 |
133 | .hljs-preprocessor,
134 | .hljs-preprocessor *,
135 | .hljs-pragma {
136 | color: #8996a8 !important;
137 | }
138 |
139 | .hljs-hexcolor,
140 | .css .hljs-value .hljs-number {
141 | color: #a5c261;
142 | }
143 |
144 | .hljs-title,
145 | .hljs-decorator,
146 | .css .hljs-function {
147 | color: #ffc66d;
148 | }
149 |
150 | .diff .hljs-header,
151 | .hljs-chunk {
152 | background-color: #2f33ab;
153 | color: #e6e1dc;
154 | display: inline-block;
155 | width: 100%;
156 | }
157 |
158 | .diff .hljs-change {
159 | background-color: #4a410d;
160 | color: #f8f8f8;
161 | display: inline-block;
162 | width: 100%;
163 | }
164 |
165 | .hljs-addition {
166 | background-color: #144212;
167 | color: #e6e1dc;
168 | display: inline-block;
169 | width: 100%;
170 | }
171 |
172 | .hljs-deletion {
173 | background-color: #600;
174 | color: #e6e1dc;
175 | display: inline-block;
176 | width: 100%;
177 | }
178 |
179 | .coffeescript .javascript,
180 | .javascript .xml,
181 | .tex .hljs-formula,
182 | .xml .javascript,
183 | .xml .vbscript,
184 | .xml .css,
185 | .xml .hljs-cdata {
186 | opacity: 0.7;
187 | }
188 |
--------------------------------------------------------------------------------
/src/js/common/markers.js:
--------------------------------------------------------------------------------
1 | function mg_return_label (d) {
2 | return d.label;
3 | }
4 |
5 | function mg_remove_existing_markers (svg) {
6 | svg.selectAll('.mg-markers').remove();
7 | svg.selectAll('.mg-baselines').remove();
8 | }
9 |
10 | function mg_in_range (args) {
11 | return function (d) {
12 | return (args.scales.X(d[args.x_accessor]) > mg_get_plot_left(args))
13 | && (args.scales.X(d[args.x_accessor]) < mg_get_plot_right(args));
14 | };
15 | }
16 |
17 | function mg_x_position (args) {
18 | return function (d) {
19 | return args.scales.X(d[args.x_accessor]);
20 | };
21 | }
22 |
23 | function mg_x_position_fixed (args) {
24 | var _mg_x_pos = mg_x_position(args);
25 | return function (d) {
26 | return _mg_x_pos(d).toFixed(2);
27 | };
28 | }
29 |
30 | function mg_y_position_fixed (args) {
31 | var _mg_y_pos = args.scales.Y;
32 | return function (d) {
33 | return _mg_y_pos(d.value).toFixed(2);
34 | };
35 | }
36 |
37 | function mg_place_annotations(checker, class_name, args, svg, line_fcn, text_fcn){
38 | var g;
39 | if (checker) {
40 | g = svg.append('g').attr('class', class_name);
41 | line_fcn(g, args);
42 | text_fcn(g, args);
43 | }
44 | }
45 |
46 | function mg_place_markers (args, svg) {
47 | mg_place_annotations(args.markers, 'mg-markers', args, svg, mg_place_marker_lines, mg_place_marker_text);
48 | }
49 |
50 | function mg_place_baselines (args, svg) {
51 | mg_place_annotations(args.baselines, 'mg-baselines', args, svg, mg_place_baseline_lines, mg_place_baseline_text);
52 | }
53 |
54 | function mg_place_marker_lines (gm, args) {
55 | var x_pos_fixed = mg_x_position_fixed(args);
56 | gm.selectAll('.mg-markers')
57 | .data(args.markers.filter(mg_in_range(args)))
58 | .enter()
59 | .append('line')
60 | .attr('x1', x_pos_fixed)
61 | .attr('x2', x_pos_fixed)
62 | .attr('y1', args.top)
63 | .attr('y2', mg_get_plot_bottom(args))
64 | .attr('class', function (d) {
65 | return d.lineclass;
66 | })
67 | .attr('stroke-dasharray', '3,1');
68 | }
69 |
70 | function mg_place_marker_text (gm, args) {
71 | gm.selectAll('.mg-markers')
72 | .data(args.markers.filter(mg_in_range(args)))
73 | .enter()
74 | .append('text')
75 | .attr('class', function (d) { return d.textclass || ''; })
76 | .classed('mg-marker-text', true)
77 | .attr('x', mg_x_position(args))
78 | .attr('y', args.top * 0.95)
79 | .attr('text-anchor', 'middle')
80 | .text(mg_return_label)
81 | .each(function (d) {
82 | if (d.click) d3.select(this).style('cursor', 'pointer').on('click', d.click);
83 | });
84 | mg_prevent_horizontal_overlap(gm.selectAll('.mg-marker-text')[0], args);
85 | }
86 |
87 | function mg_place_baseline_lines (gb, args) {
88 | var y_pos = mg_y_position_fixed(args);
89 | gb.selectAll('.mg-baselines')
90 | .data(args.baselines)
91 | .enter().append('line')
92 | .attr('x1', mg_get_plot_left(args))
93 | .attr('x2', mg_get_plot_right(args))
94 | .attr('y1', y_pos)
95 | .attr('y2', y_pos);
96 | }
97 |
98 | function mg_place_baseline_text (gb, args) {
99 | var y_pos = mg_y_position_fixed(args);
100 | gb.selectAll('.mg-baselines')
101 | .data(args.baselines)
102 | .enter().append('text')
103 | .attr('x', mg_get_plot_right(args))
104 | .attr('y', y_pos)
105 | .attr('dy', -3)
106 | .attr('text-anchor', 'end')
107 | .text(mg_return_label);
108 | }
109 |
110 | function markers (args) {
111 | 'use strict';
112 | var svg = mg_get_svg_child_of(args.target);
113 | mg_remove_existing_markers(svg);
114 | mg_place_markers(args, svg);
115 | mg_place_baselines(args, svg);
116 | return this;
117 | }
118 |
119 | MG.markers = markers;
120 |
--------------------------------------------------------------------------------
/examples/css/metricsgraphics-demo.css:
--------------------------------------------------------------------------------
1 | a,
2 | a:active,
3 | a:visited {
4 | color: steelblue;
5 | text-decoration: none;
6 | }
7 |
8 | a:hover {
9 | color: steelblue;
10 | }
11 |
12 | a.active {
13 | background-color: steelblue;
14 | border: 0;
15 | border-radius: 24px;
16 | color: #fff;
17 | padding: 2px 8px 2px 8px;
18 | text-decoration: none;
19 | }
20 |
21 | a.examples-options.selected {
22 | font-weight: bold;
23 | }
24 |
25 | a:hover.examples-options {
26 | text-decoration: none;
27 | }
28 |
29 | body {
30 | background-color: #fff;
31 | color: #000;
32 | font-family: 'Open Sans', sans-serif, Arial;
33 | }
34 |
35 | .btn-group {
36 | display: inline-block;
37 | margin-left: 3px;
38 | margin-right: 3px;
39 | }
40 |
41 | .btn-group span.which-button {
42 | font-weight: 300;
43 | color: darkgray;
44 | }
45 |
46 | .btn-group span.title {
47 | margin-left: 8px;
48 | margin-right: 8px;
49 | }
50 |
51 | .data-column {
52 | font-size: 0.9rem;
53 | margin: 0;
54 | position: absolute;
55 | right: 0;
56 | }
57 |
58 | #description {
59 | margin: auto;
60 | margin-bottom: 50px;
61 | width: 100%;
62 | }
63 |
64 | .divider {
65 | color: #000000;
66 | opacity: 0.2;
67 | }
68 |
69 | .examples-options {
70 | font-size: 0.8rem;
71 | }
72 |
73 | .float-right {
74 | float: right;
75 | }
76 |
77 | .footer {
78 | font-size: 0.9rem;
79 | padding: 40px;
80 | text-align: center;
81 | }
82 |
83 | .head {
84 | color: black;
85 | margin: auto;
86 | margin-bottom: 20px;
87 | width: 100%;
88 | }
89 |
90 | .head h1 {
91 | font-size: 4.1rem;
92 | font-weight: 300;
93 | margin: 45px 0 0 0;
94 | }
95 |
96 | ul {
97 | clear: both;
98 | font-size: 1.2rem;
99 | padding: 10px 0 0 0;
100 | }
101 |
102 | .head ul li,
103 | ul.examples li {
104 | display: inline;
105 | list-style: none;
106 | padding-right: 15px;
107 | }
108 |
109 | ul li.no-padding {
110 | padding: 0;
111 | }
112 |
113 | ul.examples li {
114 | line-height: 30px;
115 | }
116 |
117 | html {
118 | font-size: 12px;
119 | }
120 |
121 | .legend {
122 | font-size: 0.9rem;
123 | padding: 25px 0 15px 0;
124 | }
125 |
126 | #logo {
127 | margin: 60px 0 30px 0;
128 | height: 79px;
129 | width: 400px;
130 | }
131 |
132 | p {
133 | font-family: 'PT Serif', serif;
134 | font-size: 1.3rem;
135 | line-height: 24px;
136 | }
137 |
138 | .pill {
139 | font-family: 'Open Sans', serif;
140 | font-size: 1rem;
141 | text-transform: uppercase;
142 | }
143 |
144 | .pill:hover {
145 | text-decoration: none;
146 | }
147 |
148 | pre {
149 | background-color: transparent;
150 | border: 0;
151 | border-left: 2px solid #ccc;
152 | border-radius: 0;
153 | font-size: 0.9rem;
154 | margin: 40px 0 0 10px;
155 | overflow-x: auto;
156 | padding: 0;
157 | width: 100%;
158 | word-wrap: normal;
159 | }
160 |
161 | pre code {
162 | white-space: inherit;
163 | }
164 |
165 | pre code.hljs {
166 | background-color: transparent;
167 | }
168 |
169 | pre, code, text-area {
170 | font-family: "Menlo", monospace;
171 | }
172 |
173 | svg {
174 | margin-top: 30px;
175 | }
176 |
177 | #table1 .chart_title {
178 | margin-left: 0;
179 | }
180 |
181 | #torso {
182 | margin: 0 auto;
183 | text-align: center;
184 | width: 100%;
185 | }
186 |
187 | #torso div {
188 | display: inline-block;
189 | }
190 |
191 | #trunk {
192 | margin-top: 40px;
193 | }
194 |
195 | #trunk h2 {
196 | font-size: 1.2rem;
197 | }
198 |
199 | #trunk h2.trunk-title {
200 | font-size: 2.2rem;
201 | font-weight: 300;
202 | text-transform: uppercase;
203 | }
204 |
205 |
206 | .trunk-section {
207 | border-top: 1px solid #ccc;
208 | padding-bottom: 25px;
209 | padding-top: 15px;
210 | }
211 |
212 | .wip {
213 | background-color: #f1f1f1;
214 | font-size: 1.3rem;
215 | margin-top: 60px;
216 | padding: 10px;
217 | border-top-left-radius: 6px;
218 | border-top-right-radius: 6px;
219 | opacity: 0.8;
220 | }
--------------------------------------------------------------------------------
/tests/common/hooks_test.js:
--------------------------------------------------------------------------------
1 | module('hooks', {
2 | setup: function() {
3 | delete MG._hooks.test;
4 | }
5 | });
6 |
7 | test('multiple hooks with the same name execute in order', function() {
8 | var result = '';
9 |
10 | function hookOne() {
11 | result = result + 'one';
12 | }
13 |
14 | function hookTwo() {
15 | result = result + 'two';
16 | }
17 |
18 | MG.add_hook('test', hookOne);
19 | MG.add_hook('test', hookTwo);
20 |
21 | MG.call_hook('test');
22 |
23 | equal(result, 'onetwo', 'both hooks are called');
24 | });
25 |
26 | test('hooks can have context', function() {
27 | var result = {};
28 |
29 | function contextedHook() {
30 | this.foo = 'bar';
31 | }
32 |
33 | MG.add_hook('test', contextedHook, result);
34 |
35 | MG.call_hook('test');
36 |
37 | equal(result.foo, 'bar', 'exectued in the correct context');
38 | });
39 |
40 | test('hooks accept single arguments', function() {
41 | var result;
42 |
43 | function singleArgHook(arg) {
44 | result = arg;
45 | equal(typeof arg, 'string', 'correctly passed as a string')
46 | }
47 |
48 | MG.add_hook('test', singleArgHook, null);
49 |
50 | MG.call_hook('test', 'one');
51 |
52 | equal(result, 'one', 'single argument is received');
53 | });
54 |
55 |
56 | test('hooks accept multiple arguments', function() {
57 | var result;
58 |
59 | function multipleArgHook(arg1, arg2, arg3) {
60 | result = [arg1, arg2, arg3].join(' ');
61 |
62 | ok([arg1, arg2, arg3].every(function(arg) {
63 | return typeof arg === 'string';
64 | }), 'correctly passed as strings');
65 | }
66 |
67 | MG.add_hook('test', multipleArgHook);
68 |
69 | MG.call_hook('test', 'one', 'two', 'three');
70 |
71 | equal(result, 'one two three', 'multiple arguments are passed correctly');
72 | });
73 |
74 | test('hooks are chained - result from one passed into the next', function() {
75 | var initial = 2,
76 | result;
77 |
78 | function hookOne(arg) {
79 | return arg * 2;
80 | }
81 |
82 | function hookTwo(arg) {
83 | return arg - 1;
84 | }
85 |
86 | MG.add_hook('test', hookOne);
87 | MG.add_hook('test', hookTwo);
88 |
89 | result = MG.call_hook('test', initial);
90 |
91 | equal(result, 3, 'result has been chained');
92 | });
93 |
94 | test('hooks should return multiple inputs as an array', function() {
95 | var result;
96 |
97 | function hookOne(arg1, arg2, arg3) {
98 | return [arg1, arg2, arg3];
99 | }
100 |
101 | function hookTwo(arg1, arg2, arg3) {
102 | return [arg3, arg2, arg1];
103 | }
104 |
105 | MG.add_hook('test', hookOne);
106 | MG.add_hook('test', hookTwo);
107 |
108 | result = MG.call_hook('test', [1,2,3]);
109 |
110 | equal(result.join('-'), '3-2-1', 'array is passed in the result')
111 | });
112 |
113 | test('if the result from a chained hook is undefined', function() {
114 | var initial = 2;
115 |
116 | function hookOne(arg) {
117 | // don't return anything
118 | }
119 |
120 | function hookTwo(arg) {
121 | equal(arg, initial, 'initial value is used');
122 | }
123 |
124 | MG.add_hook('test', hookOne);
125 | MG.add_hook('test', hookTwo);
126 | result = MG.call_hook('test', initial);
127 |
128 |
129 | delete MG._hooks.test;
130 |
131 | function hookThree(arg) {
132 | return arg - 1;
133 | }
134 |
135 | function hookFour(arg) {
136 | // don't return anything
137 | }
138 |
139 | function hookFive(arg) {
140 | equal(initial, arg - 1, 'processed value is passed if it is already set');
141 | }
142 |
143 | MG.add_hook('test', hookOne);
144 | MG.add_hook('test', hookTwo);
145 | result = MG.call_hook('test', initial);
146 | });
147 |
148 | test('a hook can only have one registered instance of any function', function() {
149 | function hookOne() {}
150 |
151 | MG.add_hook('test', hookOne);
152 |
153 | try {
154 | MG.add_hook('test', hookOne);
155 | }
156 | catch(error) {
157 | equal(error, 'That function is already registered.', 'an exception is raised');
158 | }
159 | });
160 |
--------------------------------------------------------------------------------
/gulp/index.js:
--------------------------------------------------------------------------------
1 | // Gulp and plugins
2 | var
3 | gulp = require('gulp'),
4 | umd = require('gulp-umd'),
5 | rimraf = require('gulp-rimraf'),
6 | uglify = require('gulp-uglify'),
7 | concat = require('gulp-concat'),
8 | rename = require('gulp-rename'),
9 | //sass = require('gulp-sass'), // for building css from scss
10 | //minifycss = require('gulp-minify-css'), // for minifiing css
11 | jshint = require('gulp-jshint'),
12 | testem = require('gulp-testem'),
13 | connect = require('gulp-connect'),
14 | es6ModuleTranspiler = require("gulp-es6-module-transpiler");
15 |
16 | // paths
17 | var
18 | src = './src/js/',
19 | //scss = './scss/',
20 | //scssFiles = [],
21 | //scssDependencies = [],
22 | dist = './dist/',
23 | jsFiles = [
24 | src + 'MG.js',
25 | src + 'common/register.js',
26 | src + 'common/hooks.js',
27 | src + 'common/data_graphic.js',
28 | src + 'common/bootstrap_tooltip_popover.js',
29 | src + 'common/chart_title.js',
30 | src + 'common/y_axis.js',
31 | src + 'common/x_axis.js',
32 | src + 'common/init.js',
33 | src + 'common/markers.js',
34 | src + 'common/window_listeners.js',
35 | src + 'layout/bootstrap_dropdown.js',
36 | src + 'layout/button.js',
37 | src + 'charts/line.js',
38 | src + 'charts/histogram.js',
39 | src + 'charts/point.js',
40 | src + 'charts/bar.js',
41 | src + 'charts/table.js',
42 | src + 'charts/missing.js',
43 | src + 'misc/process.js',
44 | src + 'misc/smoothers.js',
45 | src + 'misc/formatters.js',
46 | src + 'misc/transitions.js',
47 | src + 'misc/utility.js',
48 | src + 'misc/error.js'
49 | ];
50 |
51 |
52 | gulp.task('default', ['jshint', 'test', 'build:js']);
53 |
54 | gulp.task('clean', function () {
55 | return gulp.src([dist + 'metricsgraphics.js', dist + 'metricsgraphics.min.js'], {read: false})
56 | .pipe(rimraf());
57 | });
58 |
59 | // build css files from scss
60 | //gulp.task('build:css', ['clean'], function () {
61 | // return gulp.src(scssFiles)
62 | // .pipe(sass({includePaths: scssDependencies}))
63 | // .pipe(minifycss())
64 | // .pipe(gulp.dest(dist));
65 | //});
66 |
67 | // create 'metricsgraphics.js' and 'metricsgraphics.min.js' from source js
68 | gulp.task('build:js', ['clean'], function () {
69 | return gulp.src(jsFiles)
70 | .pipe(concat({path: 'metricsgraphics.js'}))
71 | .pipe(umd(
72 | {
73 | dependencies:function() {
74 | return [{
75 | name: 'd3',
76 | amd: 'd3',
77 | cjs: 'd3',
78 | global: 'd3',
79 | param: 'd3'
80 | },
81 | {
82 | name: 'jquery',
83 | amd: 'jquery',
84 | cjs: 'jquery',
85 | global: 'jQuery',
86 | param: '$'
87 | }];
88 | },
89 | exports: function() {
90 | return "MG";
91 | },
92 | namespace: function() {
93 | return "MG";
94 | }
95 | }
96 | ))
97 | .pipe(gulp.dest(dist))
98 | .pipe(rename('metricsgraphics.min.js'))
99 | .pipe(uglify())
100 | .pipe(gulp.dest(dist));
101 | });
102 |
103 | // Check source js files with jshint
104 | gulp.task('jshint', function () {
105 | return gulp.src(jsFiles)
106 | .pipe(jshint())
107 | .pipe(jshint.reporter('default'));
108 | });
109 |
110 | // Run test suite server (testem')
111 | gulp.task('test', function() {
112 | return gulp.src([''])
113 | .pipe(testem({
114 | configFile: 'testem.json'
115 | }));
116 | });
117 |
118 |
119 | // Development server tasks
120 | // NOTE: these paths will need changing when the SCSS source is ready
121 | var roots = ['dist', 'examples', 'src', 'bower_components'],
122 | watchables = roots.map(function(root) {
123 | return root + '/**/*';
124 | });
125 |
126 | gulp.task('dev:watch', function() { return gulp.watch(watchables, ['jshint', 'dev:reload']); });
127 | gulp.task('dev:reload', function() { return gulp.src(watchables).pipe(connect.reload()); });
128 | gulp.task('serve', ['jshint', 'dev:serve', 'dev:watch']);
129 |
130 | gulp.task('dev:serve', function() {
131 | connect.server({
132 | root: roots,
133 | port: 4300,
134 | livereload: true
135 | });
136 | });
137 |
--------------------------------------------------------------------------------
/examples/js/main.js:
--------------------------------------------------------------------------------
1 | var theme = 'light';
2 |
3 | (function() {
4 | 'use strict';
5 |
6 | //set the active pill and section on first load
7 | var section = (document.location.hash) ? document.location.hash.slice(1) : 'lines';
8 |
9 | $('#trunk').load('charts/' + section + '.htm', function() {
10 | $('pre code').each(function(i, block) {
11 | hljs.highlightBlock(block);
12 | });
13 | });
14 |
15 | $('.examples li a#goto-' + section).addClass('active');
16 |
17 | //handle mouse clicks and so on
18 | assignEventListeners();
19 |
20 | function assignEventListeners() {
21 | $('ul.examples li a.pill').on('click', function(event) {
22 | event.preventDefault();
23 | $('ul.examples li a.pill').removeClass('active');
24 | $(this).addClass('active');
25 |
26 | var section = $(this).attr('id').slice(5);
27 | $('#trunk').load('charts/' + section + '.htm', function() {
28 | $('pre code').each(function(i, block) {
29 | hljs.highlightBlock(block);
30 | });
31 | });
32 |
33 | document.location.hash = section;
34 |
35 | return false;
36 | })
37 |
38 | $('#dark-css').on('click', function () {
39 | theme = 'dark';
40 |
41 | $('.missing')
42 | .css('background-image', 'url(images/missing-data-dark.png)');
43 |
44 | $('.wip')
45 | .css('background-color', '#3b3b3b');
46 |
47 | $('.trunk-section')
48 | .css('border-top-color', '#5e5e5e');
49 |
50 | $('.mg-missing-background')
51 | .css('stroke', '#ccc');
52 |
53 | $('.head ul li a.pill').removeClass('active');
54 | $(this).toggleClass('active');
55 | $('#dark').attr({href : 'css/metricsgraphics-demo-dark.css'});
56 | $('#dark-code').attr({href : 'css/railscasts.css'});
57 | $('#accessible').attr({href : ''});
58 |
59 | return false;
60 | });
61 |
62 | $('#light-css').on('click', function () {
63 | theme = 'light';
64 |
65 | $('.missing')
66 | .css('background-image', 'url(images/missing-data.png)');
67 |
68 | $('.wip')
69 | .css('background-color', '#f1f1f1');
70 |
71 | $('.trunk-section')
72 | .css('border-top-color', '#ccc');
73 |
74 | $('.mg-missing-background')
75 | .css('stroke', 'blue');
76 |
77 | $('.head ul li a.pill').removeClass('active');
78 | $(this).toggleClass('active');
79 | $('#dark').attr({href : ''});
80 | $('#dark-code').attr({href : ''});
81 | $('#accessible').attr({href : ''});
82 |
83 | return false;
84 | });
85 |
86 | $('#accessible-css').on('click', function () {
87 | $('.head ul li a.pill').removeClass('active');
88 | $(this).toggleClass('active');
89 | $('#accessible').attr({href : 'css/metricsgraphics-demo-accessible.css'});
90 |
91 | return false;
92 | });
93 | }
94 |
95 | // replace all SVG images with inline SVG
96 | // http://stackoverflow.com/questions/11978995/how-to-change-color-of-svg
97 | // -image-using-css-jquery-svg-image-replacement
98 | $('img.svg').each(function() {
99 | var $img = jQuery(this);
100 | var imgID = $img.attr('id');
101 | var imgClass = $img.attr('class');
102 | var imgURL = $img.attr('src');
103 |
104 | $.get(imgURL, function(data) {
105 | // Get the SVG tag, ignore the rest
106 | var $svg = jQuery(data).find('svg');
107 |
108 | // Add replaced image's ID to the new SVG
109 | if (typeof imgID !== 'undefined') {
110 | $svg = $svg.attr('id', imgID);
111 | }
112 | // Add replaced image's classes to the new SVG
113 | if (typeof imgClass !== 'undefined') {
114 | $svg = $svg.attr('class', imgClass + ' replaced-svg');
115 | }
116 |
117 | // Remove any invalid XML tags as per http://validator.w3.org
118 | $svg = $svg.removeAttr('xmlns:a');
119 |
120 | // Replace image with new SVG
121 | $img.replaceWith($svg);
122 |
123 | }, 'xml');
124 | });
125 | })();
126 |
--------------------------------------------------------------------------------
/examples/charts/other.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
d3.json('data/fake_users3.json', function(data) {
7 | for(var i = 0; i < data.length; i++) {
8 | data[i] = MG.convert.date(data[i], 'date');
9 | }
10 |
11 | MG.data_graphic({
12 | title: "Preserving the aspect ratio",
13 | description: "You can automatically set the width or height of a data graphic to fit its parent element. When done the graphic will rescale to fit the size of the parent element while preserving its aspect ratio.",
14 | data: data,
15 | full_width: true,
16 | height: 300,
17 | right: 40,
18 | x_extended_ticks: true,
19 | target: '#aspect1',
20 | x_accessor: 'date',
21 | y_accessor: 'value'
22 | });
23 | });
24 |
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
d3.json('data/brief-1.json', function(data) {
37 | data = MG.convert.date(data, 'date');
38 | MG.data_graphic({
39 | title: "Small Text Inferred By Size",
40 | description: "If args.width - args.left - args.right is smaller than args.small_width_threshold , and the similarly for the height, the text size automatically scales so that it is slightly smaller.",
41 | data: data,
42 | width: 240,
43 | height: 140,
44 | right: 20,
45 | xax_count: 4,
46 | target: '#small1'
47 | });
48 | });
49 |
50 | d3.json('data/brief-2.json', function(data) {
51 | data = MG.convert.date(data, 'date');
52 | MG.data_graphic({
53 | title: "Small Text",
54 | description: "By adding small_text to true, we can force the use of smaller axis text regardless of the width or height.",
55 | data: data,
56 | width: 295,
57 | height: 150,
58 | right: 10,
59 | small_text: true,
60 | xax_count: 4,
61 | target: '#small2'
62 | });
63 | });
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/tests/charts/point_test.js:
--------------------------------------------------------------------------------
1 | module('point');
2 |
3 | test('A solitary active datapoint exists', function() {
4 | var params = {
5 | target: '#qunit-fixture',
6 | data: [{'date': new Date('2014-01-01'), 'value': 12},
7 | {'date': new Date('2014-03-01'), 'value': 18}],
8 | chart_type: 'point'
9 | };
10 |
11 | MG.data_graphic(params);
12 | equal(document.querySelectorAll('.mg-active-datapoint').length, 1, 'One active datapoint exists');
13 | });
14 |
15 | test('Rollovers exist', function() {
16 | var params = {
17 | target: '#qunit-fixture',
18 | data: [[{'date': new Date('2014-01-01'), 'value': 12},
19 | {'date': new Date('2014-03-01'), 'value': 18}],
20 | [{'date': new Date('2014-01-01'), 'value': 120},
21 | {'date': new Date('2014-03-01'), 'value': 180}]],
22 | chart_type: 'point'
23 | };
24 |
25 | MG.data_graphic(params);
26 | ok(document.querySelector('.mg-voronoi'), 'Rollovers exist');
27 | });
28 |
29 | test('We have only one set of rollovers', function() {
30 | var params = {
31 | target: '#qunit-fixture',
32 | data: [[{'date': new Date('2014-01-01'), 'value': 12},
33 | {'date': new Date('2014-03-01'), 'value': 18}],
34 | [{'date': new Date('2014-01-01'), 'value': 120},
35 | {'date': new Date('2014-03-01'), 'value': 180}]],
36 | chart_type: 'point'
37 | };
38 |
39 | MG.data_graphic(params);
40 | equal(document.querySelectorAll('.mg-voronoi').length, 1, 'One set of rollovers exists');
41 | });
42 |
43 | test('args.x_rug', function() {
44 | var params = {
45 | target: '#qunit-fixture',
46 | data: [{'date': new Date('2014-01-01'), 'value': 12},
47 | {'date': new Date('2014-03-01'), 'value': 18}],
48 | x_rug: true,
49 | chart_type: 'point'
50 | };
51 |
52 | MG.data_graphic(params);
53 | ok(document.querySelector('.mg-x-rug'), 'X-axis rugplot exists');
54 | });
55 |
56 | test('Only one rugplot is added on multiple calls to the same target element', function() {
57 | var params = {
58 | target: '#qunit-fixture',
59 | data: [{'date': new Date('2014-01-01'), 'value': 12},
60 | {'date': new Date('2014-03-01'), 'value': 18}],
61 | x_rug: true,
62 | chart_type: 'point'
63 | };
64 |
65 | MG.data_graphic(params);
66 | MG.data_graphic(MG.clone(params));
67 |
68 | equal(document.querySelectorAll('.mg-x-rug').length, 2, 'We only have one rugplot (two ticks) on the x-axis');
69 | });
70 |
71 | test('args.y_rug', function() {
72 | var params = {
73 | target: '#qunit-fixture',
74 | data: [{'date': new Date('2014-01-01'), 'value': 12},
75 | {'date': new Date('2014-03-01'), 'value': 18}],
76 | y_rug: true,
77 | chart_type: 'point'
78 | };
79 |
80 | MG.data_graphic(params);
81 | ok(document.querySelector('.mg-y-rug'), 'Y-axis rugplot exists');
82 | });
83 |
84 | test('Only one rugplot is added on multiple calls to the same target element', function() {
85 | var params = {
86 | target: '#qunit-fixture',
87 | data: [{'date': new Date('2014-01-01'), 'value': 12},
88 | {'date': new Date('2014-03-01'), 'value': 18}],
89 | y_rug: true,
90 | chart_type: 'point'
91 | };
92 |
93 | MG.data_graphic(params);
94 | MG.data_graphic(MG.clone(params));
95 | equal(document.querySelectorAll('.mg-y-rug').length, 2, 'We only have one rugplot (two ticks) on the y-axis');
96 | });
97 |
98 | test('args.least_squares', function() {
99 | var params = {
100 | target: '#qunit-fixture',
101 | data: [{'date': new Date('2014-01-01'), 'value': 12},
102 | {'date': new Date('2014-03-01'), 'value': 18}],
103 | chart_type: 'point',
104 | least_squares: true
105 | };
106 |
107 | MG.data_graphic(params);
108 | ok(document.querySelector('.mg-least-squares-line'), 'Least-squares line exists');
109 | });
110 |
111 | test('Only one least-squares line is added on multiple calls to the same target element', function() {
112 | var params = {
113 | target: '#qunit-fixture',
114 | data: [{'date': new Date('2014-01-01'), 'value': 12},
115 | {'date': new Date('2014-03-01'), 'value': 18}],
116 | chart_type: 'point',
117 | least_squares: true
118 | };
119 |
120 | MG.data_graphic(params);
121 | MG.data_graphic(MG.clone(params));
122 | equal(document.querySelectorAll('.mg-least-squares-line').length, 1, 'We only have one least-squares line');
123 | });
--------------------------------------------------------------------------------
/src/js/charts/missing.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | function mg_missing_add_text (svg, args) {
5 | svg.selectAll('.mg-missing-text').data([args.missing_text])
6 | .enter().append('text')
7 | .attr('class', 'mg-missing-text')
8 | .attr('x', args.width / 2)
9 | .attr('y', args.height / 2)
10 | .attr('dy', '.50em')
11 | .attr('text-anchor', 'middle')
12 | .text(args.missing_text);
13 | }
14 |
15 | function mg_missing_x_scale (args) {
16 | args.scales.X = d3.scale.linear()
17 | .domain([0, args.data.length])
18 | .range([mg_get_plot_left(args), mg_get_plot_right(args)]);
19 | args.scalefns.yf = function (di) { return args.scales.Y(di.y); };
20 | }
21 |
22 | function mg_missing_y_scale (args) {
23 | args.scales.Y = d3.scale.linear()
24 | .domain([-2, 2])
25 | .range([args.height - args.bottom - args.buffer * 2, args.top]);
26 | args.scalefns.xf = function (di) { return args.scales.X(di.x); };
27 | }
28 |
29 | function mg_make_fake_data (args) {
30 | var data = [];
31 | for (var x = 1; x <= 50; x++) {
32 | data.push({'x': x, 'y': Math.random() - (x * 0.03)});
33 | }
34 | args.data = data;
35 | }
36 |
37 |
38 |
39 | function mg_add_missing_background_rect (g, args) {
40 | g.append('svg:rect')
41 | .classed('mg-missing-background', true)
42 | .attr('x', args.buffer)
43 | .attr('y', args.buffer)
44 | .attr('width', args.width - args.buffer * 2)
45 | .attr('height', args.height - args.buffer * 2)
46 | .attr('rx', 15)
47 | .attr('ry', 15);
48 | }
49 |
50 | function mg_missing_add_line (g, args) {
51 | var line = d3.svg.line()
52 | .x(args.scalefns.xf)
53 | .y(args.scalefns.yf)
54 | .interpolate(args.interpolate);
55 | g.append('path')
56 | .attr('class', 'mg-main-line mg-line1-color')
57 | .attr('d', line(args.data));
58 | }
59 |
60 | function mg_missing_add_area (g, args) {
61 | var area = d3.svg.area()
62 | .x(args.scalefns.xf)
63 | .y0(args.scales.Y.range()[0])
64 | .y1(args.scalefns.yf)
65 | .interpolate(args.interpolate);
66 | g.append('path')
67 | .attr('class', 'mg-main-area mg-area1-color')
68 | .attr('d', area(args.data));
69 | }
70 |
71 | function mg_remove_all_children (args) {
72 | d3.select(args.target).selectAll('svg *').remove();
73 | }
74 |
75 | function mg_missing_remove_legend (args) {
76 | if (args.legend_target) {
77 | d3.select(args.legend_target).html('');
78 | }
79 | }
80 |
81 | function missingData (args) {
82 | this.init = function (args) {
83 | this.args = args;
84 |
85 | mg_init_compute_width(args);
86 | mg_init_compute_height(args);
87 |
88 | chart_title(args);
89 |
90 | // create svg if one doesn't exist
91 |
92 | var container = d3.select(args.target);
93 | mg_raise_container_error(container, args);
94 | var svg = container.selectAll('svg');
95 | mg_remove_svg_if_chart_type_has_changed(svg, args);
96 | svg = mg_add_svg_if_it_doesnt_exist(svg, args);
97 | mg_adjust_width_and_height_if_changed(svg, args);
98 | mg_set_viewbox_for_scaling(svg, args);
99 | mg_remove_all_children(args);
100 |
101 | svg.classed('mg-missing', true);
102 | mg_missing_remove_legend(args);
103 |
104 | // are we adding a background placeholder
105 | if (args.show_missing_background) {
106 | mg_make_fake_data(args);
107 | mg_missing_x_scale(args);
108 | mg_missing_y_scale(args);
109 | var g = mg_add_g(svg, 'mg-missing-pane');
110 |
111 | mg_add_missing_background_rect(g, args);
112 | mg_missing_add_line(g, args);
113 | mg_missing_add_area(g, args);
114 | }
115 |
116 | mg_missing_add_text(svg, args);
117 |
118 | this.windowListeners();
119 |
120 | return this;
121 | };
122 |
123 | this.windowListeners = function () {
124 | mg_window_listeners(this.args);
125 | return this;
126 | };
127 |
128 | this.init(args);
129 | }
130 |
131 | var defaults = {
132 | top: 40, // the size of the top margin
133 | bottom: 30, // the size of the bottom margin
134 | right: 10, // size of the right margin
135 | left: 10, // size of the left margin
136 | buffer: 8, // the buffer between the actual chart area and the margins
137 | legend_target: '',
138 | width: 350,
139 | height: 220,
140 | missing_text: 'Data currently missing or unavailable',
141 | scalefns: {},
142 | scales: {},
143 | show_tooltips: true,
144 | show_missing_background: true,
145 | interpolate: 'cardinal'
146 | };
147 |
148 | MG.register('missing-data', missingData, defaults);
149 | }).call(this);
150 |
--------------------------------------------------------------------------------
/src/js/layout/button.js:
--------------------------------------------------------------------------------
1 | MG.button_layout = function(target) {
2 | 'use strict';
3 | this.target = target;
4 | this.feature_set = {};
5 | this.public_name = {};
6 | this.sorters = {};
7 | this.manual = [];
8 | this.manual_map = {};
9 | this.manual_callback = {};
10 |
11 | this._strip_punctuation = function(s) {
12 | var punctuationless = s.replace(/[^a-zA-Z0-9 _]+/g, '');
13 | var finalString = punctuationless.replace(/ +?/g, '');
14 | return finalString;
15 | };
16 |
17 | this.data = function(data) {
18 | this._data = data;
19 | return this;
20 | };
21 |
22 | this.manual_button = function(feature, feature_set, callback) {
23 | this.feature_set[feature]=feature_set;
24 | this.manual_map[this._strip_punctuation(feature)] = feature;
25 | this.manual_callback[feature]=callback;// the default is going to be the first feature.
26 | return this;
27 | };
28 |
29 | this.button = function(feature) {
30 | if (arguments.length > 1) {
31 | this.public_name[feature] = arguments[1];
32 | }
33 |
34 | if (arguments.length > 2) {
35 | this.sorters[feature] = arguments[2];
36 | }
37 |
38 | this.feature_set[feature] = [];
39 | return this;
40 | };
41 |
42 | this.callback = function(callback) {
43 | this._callback = callback;
44 | return this;
45 | };
46 |
47 | this.display = function() {
48 | var callback = this._callback;
49 | var manual_callback = this.manual_callback;
50 | var manual_map = this.manual_map;
51 |
52 | var d,f, features, feat;
53 | features = Object.keys(this.feature_set);
54 |
55 | var mapDtoF = function(f) { return d[f]; };
56 |
57 | var i;
58 |
59 | // build out this.feature_set with this.data
60 | for (i = 0; i < this._data.length; i++) {
61 | d = this._data[i];
62 | f = features.map(mapDtoF);
63 | for (var j = 0; j < features.length; j++) {
64 | feat = features[j];
65 | if (this.feature_set[feat].indexOf(f[j]) === -1) {
66 | this.feature_set[feat].push(f[j]);
67 | }
68 | }
69 | }
70 |
71 | for (feat in this.feature_set) {
72 | if (this.sorters.hasOwnProperty(feat)) {
73 | this.feature_set[feat].sort(this.sorters[feat]);
74 | }
75 | }
76 |
77 | $(this.target).empty();
78 |
79 | $(this.target).append("
");
80 |
81 | var dropdownLiAClick = function() {
82 | var k = $(this).data('key');
83 | var feature = $(this).data('feature');
84 | var manual_feature;
85 | $('.' + feature + '-btns button.btn span.title').html(k);
86 | if (!manual_map.hasOwnProperty(feature)) {
87 | callback(feature, k);
88 | } else {
89 | manual_feature = manual_map[feature];
90 | manual_callback[manual_feature](k);
91 | }
92 |
93 | return false;
94 | };
95 |
96 | for (var feature in this.feature_set) {
97 | features = this.feature_set[feature];
98 | $(this.target + ' div.segments').append(
99 | '' + // This never changes.
100 | '' +
101 | "" + (this.public_name.hasOwnProperty(feature) ? this.public_name[feature] : feature) +" " +
102 | "" + (this.manual_callback.hasOwnProperty(feature) ? this.feature_set[feature][0] : 'all') + " " + // if a manual button, don't default to all in label.
103 | ' ' +
104 | ' ' +
105 | ''
109 | + '
');
110 |
111 | for (i = 0; i < features.length; i++) {
112 | if (features[i] !== 'all' && features[i] !== undefined) { // strange bug with undefined being added to manual buttons.
113 | $(this.target + ' div.' + this._strip_punctuation(feature) + '-btns ul.dropdown-menu').append(
114 | ''
115 | + features[i] + ' '
116 | );
117 | }
118 | }
119 |
120 | $('.' + this._strip_punctuation(feature) + '-btns .dropdown-menu li a').on('click', dropdownLiAClick);
121 | }
122 |
123 | return this;
124 | };
125 |
126 | return this;
127 | };
128 |
--------------------------------------------------------------------------------
/examples/charts/auto-time-formatting.htm:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
function fake_data(length, seconds) {
13 | var d = new Date();
14 | var v = 100000;
15 | var data=[];
16 |
17 | for (var i = 0; i < length; i++) {
18 | v += (Math.random() - 0.5) * 10000;
19 | data.push({date: MG.clone(d), value: v});
20 | d = new Date(d.getTime() + seconds * 1000);
21 | }
22 | return data;
23 | }
24 |
25 | function fake_days(length) {
26 | var d = new Date();
27 | var v = 100000;
28 |
29 | var data = [];
30 | for (var i = 0; i < length; i++) {
31 | v += (Math.random() - 0.5) * 10000;
32 | data.push({date: MG.clone(d), value: v});
33 | d.setDate(d.getDate() + 1);
34 | }
35 | return data;
36 | }
37 |
38 | var less_than_a_minute = fake_data(25, 1);
39 | var less_than_a_day = fake_data(25,60 * 20);
40 | var a_few_days = fake_data(75,60 * 60);
41 | var many_days = fake_days(25);
42 |
43 | MG.data_graphic({
44 | title: "Less Than A Minute",
45 | data: less_than_a_minute,
46 | target: '#time1',
47 | width: 600,
48 | height: 200,
49 | right: 40
50 | });
51 |
52 | MG.data_graphic({
53 | title: "Less Than A Day",
54 | data: less_than_a_day,
55 | target: '#time2',
56 | width: 600,
57 | height: 200,
58 | right: 40
59 | });
60 |
61 | MG.data_graphic({
62 | title: "A Few Days",
63 | data: a_few_days,
64 | target: '#time3',
65 | width: 600,
66 | height: 200,
67 | right: 40
68 | });
69 |
70 | MG.data_graphic({
71 | title: "Over A Large Span of Days",
72 | data: many_days,
73 | target: '#time4',
74 | width: 600,
75 | height: 200,
76 | right: 40
77 | });
78 |
79 |
80 |
81 |
82 |
83 |
84 |
89 |
90 |
91 |
MG.data_graphic({
92 | title: "European Clock",
93 | description: 'By setting european_clock to true, you can default to European-style time. This is at the moment experimental, and the formatting may change.',
94 | data: less_than_a_minute,
95 | target: '#european',
96 | european_clock: true,
97 | width: 600,
98 | height: 200,
99 | right: 40
100 | });
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/examples/data/ufo-sightings.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "year": "1945",
4 | "sightings": 6
5 | },
6 | {
7 | "year": "1946",
8 | "sightings": 8
9 | },
10 | {
11 | "year": "1947",
12 | "sightings": 34
13 | },
14 | {
15 | "year": "1948",
16 | "sightings": 8
17 | },
18 | {
19 | "year": "1949",
20 | "sightings": 15
21 | },
22 | {
23 | "year": "1950",
24 | "sightings": 25
25 | },
26 | {
27 | "year": "1951",
28 | "sightings": 20
29 | },
30 | {
31 | "year": "1952",
32 | "sightings": 48
33 | },
34 | {
35 | "year": "1953",
36 | "sightings": 34
37 | },
38 | {
39 | "year": "1954",
40 | "sightings": 50
41 | },
42 | {
43 | "year": "1955",
44 | "sightings": 31
45 | },
46 | {
47 | "year": "1956",
48 | "sightings": 38
49 | },
50 | {
51 | "year": "1957",
52 | "sightings": 67
53 | },
54 | {
55 | "year": "1958",
56 | "sightings": 40
57 | },
58 | {
59 | "year": "1959",
60 | "sightings": 47
61 | },
62 | {
63 | "year": "1960",
64 | "sightings": 64
65 | },
66 | {
67 | "year": "1961",
68 | "sightings": 39
69 | },
70 | {
71 | "year": "1962",
72 | "sightings": 55
73 | },
74 | {
75 | "year": "1963",
76 | "sightings": 75
77 | },
78 | {
79 | "year": "1964",
80 | "sightings": 77
81 | },
82 | {
83 | "year": "1965",
84 | "sightings": 167
85 | },
86 | {
87 | "year": "1966",
88 | "sightings": 169
89 | },
90 | {
91 | "year": "1967",
92 | "sightings": 178
93 | },
94 | {
95 | "year": "1968",
96 | "sightings": 183
97 | },
98 | {
99 | "year": "1969",
100 | "sightings": 138
101 | },
102 | {
103 | "year": "1970",
104 | "sightings": 126
105 | },
106 | {
107 | "year": "1971",
108 | "sightings": 110
109 | },
110 | {
111 | "year": "1972",
112 | "sightings": 146
113 | },
114 | {
115 | "year": "1973",
116 | "sightings": 209
117 | },
118 | {
119 | "year": "1974",
120 | "sightings": 241
121 | },
122 | {
123 | "year": "1975",
124 | "sightings": 279
125 | },
126 | {
127 | "year": "1976",
128 | "sightings": 246
129 | },
130 | {
131 | "year": "1977",
132 | "sightings": 239
133 | },
134 | {
135 | "year": "1978",
136 | "sightings": 301
137 | },
138 | {
139 | "year": "1979",
140 | "sightings": 221
141 | },
142 | {
143 | "year": "1980",
144 | "sightings": 211
145 | },
146 | {
147 | "year": "1981",
148 | "sightings": 146
149 | },
150 | {
151 | "year": "1982",
152 | "sightings": 182
153 | },
154 | {
155 | "year": "1983",
156 | "sightings": 132
157 | },
158 | {
159 | "year": "1984",
160 | "sightings": 172
161 | },
162 | {
163 | "year": "1985",
164 | "sightings": 192
165 | },
166 | {
167 | "year": "1986",
168 | "sightings": 173
169 | },
170 | {
171 | "year": "1987",
172 | "sightings": 193
173 | },
174 | {
175 | "year": "1988",
176 | "sightings": 203
177 | },
178 | {
179 | "year": "1989",
180 | "sightings": 220
181 | },
182 | {
183 | "year": "1990",
184 | "sightings": 217
185 | },
186 | {
187 | "year": "1991",
188 | "sightings": 210
189 | },
190 | {
191 | "year": "1992",
192 | "sightings": 228
193 | },
194 | {
195 | "year": "1993",
196 | "sightings": 285
197 | },
198 | {
199 | "year": "1994",
200 | "sightings": 381
201 | },
202 | {
203 | "year": "1995",
204 | "sightings": 1336
205 | },
206 | {
207 | "year": "1996",
208 | "sightings": 862
209 | },
210 | {
211 | "year": "1997",
212 | "sightings": 1248
213 | },
214 | {
215 | "year": "1998",
216 | "sightings": 1812
217 | },
218 | {
219 | "year": "1999",
220 | "sightings": 2906
221 | },
222 | {
223 | "year": "2000",
224 | "sightings": 2780
225 | },
226 | {
227 | "year": "2001",
228 | "sightings": 3105
229 | },
230 | {
231 | "year": "2002",
232 | "sightings": 3176
233 | },
234 | {
235 | "year": "2003",
236 | "sightings": 3896
237 | },
238 | {
239 | "year": "2004",
240 | "sightings": 4208
241 | },
242 | {
243 | "year": "2005",
244 | "sightings": 3996
245 | },
246 | {
247 | "year": "2006",
248 | "sightings": 3590
249 | },
250 | {
251 | "year": "2007",
252 | "sightings": 4195
253 | },
254 | {
255 | "year": "2008",
256 | "sightings": 4705
257 | },
258 | {
259 | "year": "2009",
260 | "sightings": 4297
261 | },
262 | {
263 | "year": "2010",
264 | "sightings": 2531
265 | }
266 | ]
--------------------------------------------------------------------------------
/tests/common/markers_test.js:
--------------------------------------------------------------------------------
1 | module('markers');
2 |
3 | test('All markers are added if they lie within the visible range', function() {
4 | var markers = [{
5 | 'date': new Date('2014-02-01'),
6 | 'label': '1st Milestone'
7 | }, {
8 | 'date': new Date('2014-02-02'),
9 | 'label': '2nd Milestone'
10 | }];
11 |
12 | var params = {
13 | target: '#qunit-fixture',
14 | data: [{'date': new Date('2014-01-01'), 'value': 12},
15 | {'date': new Date('2014-03-01'), 'value': 18}],
16 | markers: markers
17 | };
18 |
19 | MG.data_graphic(params);
20 | equal(document.querySelectorAll(params.target + ' .mg-markers line').length, markers.length, 'Two markers added');
21 | });
22 |
23 | test('Markers that lie outside the visible range are excluded', function() {
24 | var markers = [{
25 | 'date': new Date('2014-02-01'),
26 | 'label': '1st Milestone'
27 | }, {
28 | 'date': new Date('2014-02-02'),
29 | 'label': '2nd Milestone'
30 | }];
31 |
32 | var params = {
33 | target: '#qunit-fixture',
34 | data: [{'date': new Date('2014-02-01'), 'value': 12},
35 | {'date': new Date('2014-03-01'), 'value': 18}],
36 | markers: markers
37 | };
38 |
39 | MG.data_graphic(params);
40 | equal(document.querySelectorAll(params.target + ' .mg-markers line').length, 1, 'One marker added');
41 | });
42 |
43 | test('All baselines are added', function() {
44 | var baselines = [{value:50, label:'a baseline'}];
45 |
46 | var params = {
47 | target: '#qunit-fixture',
48 | data: [{'date': new Date('2014-01-01'), 'value': 100},
49 | {'date': new Date('2014-03-01'), 'value': 10}],
50 | baselines: baselines
51 | };
52 |
53 | MG.data_graphic(params);
54 | equal(document.querySelectorAll(params.target + ' .mg-baselines line').length, markers.length, 'One baseline added');
55 | });
56 |
57 | test('Markers\' texts are correctly added', function() {
58 | var markers = [{
59 | 'date': new Date('2014-02-01'),
60 | 'label': '1st Milestone'
61 | }, {
62 | 'date': new Date('2014-02-02'),
63 | 'label': '2nd Milestone'
64 | }];
65 |
66 | var params = {
67 | target: '#qunit-fixture',
68 | data: [{'date': new Date('2014-01-01'), 'value': 100},
69 | {'date': new Date('2014-03-01'), 'value': 10}],
70 | markers: markers
71 | };
72 |
73 | MG.data_graphic(params);
74 | equal(document.querySelectorAll(params.target + ' .mg-markers text')[0].textContent, markers[0].label, 'First marker\'s text matches specified one');
75 | equal(document.querySelectorAll(params.target + ' .mg-markers text')[1].textContent, markers[1].label, 'Second marker\'s text matches specified one');
76 | });
77 |
78 | test('Baseline text is correctly added', function() {
79 | var baselines = [{value:50, label:'a baseline'}];
80 |
81 | var params = {
82 | target: '#qunit-fixture',
83 | data: [{'date': new Date('2014-01-01'), 'value': 100},
84 | {'date': new Date('2014-03-01'), 'value': 10}],
85 | baselines: baselines
86 | };
87 |
88 | MG.data_graphic(params);
89 | equal(document.querySelectorAll(params.target + ' .mg-baselines text')[0].textContent, baselines[0].label, 'Baseline text matches specified one');
90 | });
91 |
92 | test('When an existing chart is updated with no markers, existing markers are cleared', function() {
93 | var markers = [{
94 | 'date': new Date('2014-11-02'),
95 | 'label': 'Lorem Ipsum'
96 | }];
97 |
98 | var params_0 = {
99 | target: '#qunit-fixture',
100 | data: [{'date': new Date('2014-11-01'), 'value': 12},
101 | {'date': new Date('2014-11-03'), 'value': 18}],
102 | markers: markers
103 | };
104 |
105 | var params = {
106 | target: '#qunit-fixture',
107 | data: [{'date': new Date('2014-11-01'), 'value': 14},
108 | {'date': new Date('2014-11-03'), 'value': 20}],
109 | };
110 |
111 | MG.data_graphic(params_0);
112 | MG.data_graphic(params);
113 |
114 | equal(document.querySelectorAll('.mg-markers').length, 0, 'Old markers were cleared');
115 | });
116 |
117 | test('When an existing chart is updated with no baselines, existing baselines are cleared', function() {
118 | var baselines = [{
119 | 'value': 10,
120 | 'label': 'Lorem Ipsum'
121 | }];
122 |
123 | var params_0 = {
124 | target: '#qunit-fixture',
125 | data: [{'date': new Date('2014-11-01'), 'value': 12},
126 | {'date': new Date('2014-11-03'), 'value': 18}],
127 | baselines: baselines
128 | };
129 |
130 | var params = {
131 | target: '#qunit-fixture',
132 | data: [{'date': new Date('2014-11-01'), 'value': 14},
133 | {'date': new Date('2014-11-03'), 'value': 20}],
134 | };
135 |
136 | MG.data_graphic(params_0);
137 | MG.data_graphic(params);
138 |
139 | equal(document.querySelectorAll('.mg-baselines').length, 0, 'Old baselines were cleared');
140 | });
141 |
142 | // test('args.small_text', function() {
143 | // var baselines = [{value:50, label:'a baseline'}];
144 |
145 | // var params = {
146 | // target: '#qunit-fixture',
147 | // data: [{'date': new Date('2014-01-01'), 'value': 100},
148 | // {'date': new Date('2014-03-01'), 'value': 10}],
149 | // baselines: baselines,
150 | // small_text: true
151 | // };
152 |
153 | // MG.data_graphic(params);
154 | // ok(document.querySelector('.mg-baselines-small'), 'Small baselines is set');
155 | // });
156 |
--------------------------------------------------------------------------------
/examples/charts/updating.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Release
7 | Beta
9 | Alpha
11 |
12 |
13 |
15 | All time
17 | Past 2 months
19 | Past month
21 |
22 |
23 |
24 |
25 |
26 |
var globals = {};
27 |
28 | var split_by_params = {
29 | title: "Downloads by Channel",
30 | description: "We sometimes have the need to split the data and then gracefully update the graphic with the newly selected subset of data.",
31 | width: 600,
32 | height: 200,
33 | right: 40,
34 | xax_count: 4,
35 | target: '#split_by',
36 | x_accessor: 'date',
37 | y_accessor: 'release'
38 | };
39 |
40 | var modify_time_period_params = {
41 | title: "Beta Downloads",
42 | description: "We sometimes have the need to view data for just the past n days. Here, the transition_on_update option is set to false.",
43 | width: 600,
44 | height: 200,
45 | right: 40,
46 | show_secondary_x_label: false,
47 | xax_count: 4,
48 | target: '#modify_time_period',
49 | x_accessor: 'date',
50 | y_accessor: 'beta'
51 | }
52 |
53 | d3.json('data/split_by.json', function(data) {
54 | data = MG.convert.date(data, 'date');
55 | globals.data = data;
56 |
57 | split_by_params.data = data;
58 | MG.data_graphic(split_by_params);
59 |
60 | modify_time_period_params.data = data;
61 | MG.data_graphic(modify_time_period_params);
62 | });
63 |
64 | $('.split-by-controls button').click(function() {
65 | var new_y_accessor = $(this).data('y_accessor');
66 | split_by_params.y_accessor = new_y_accessor;
67 |
68 | // change button state
69 | $(this).addClass('active').siblings().removeClass('active');
70 |
71 | // update data
72 | delete split_by_params.xax_format;
73 | MG.data_graphic(split_by_params);
74 | });
75 |
76 | $('.modify-time-period-controls button').click(function() {
77 | var past_n_days = $(this).data('time_period');
78 | var data = modify_time_period(globals.data, past_n_days);
79 |
80 | // change button state
81 | $(this).addClass('active').siblings().removeClass('active');
82 |
83 | delete modify_time_period_params.xax_format;
84 | modify_time_period_params.data = data;
85 | MG.data_graphic(modify_time_period_params);
86 | });
87 |
88 | function modify_time_period(data, past_n_days) {
89 | if (past_n_days !== '') {
90 | return MG.clone(data).slice(past_n_days * -1);
91 | }
92 |
93 | return data;
94 | }
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/js/common/init.js:
--------------------------------------------------------------------------------
1 | function mg_merge_args_with_defaults (args) {
2 | var defaults = {
3 | target: null,
4 | title: null,
5 | description: null
6 | };
7 | if (!args) { args = {}; }
8 |
9 | if (!args.processed) {
10 | args.processed = {};
11 | }
12 |
13 | args = merge_with_defaults(args, defaults);
14 | return args;
15 | }
16 |
17 | function mg_is_time_series (args) {
18 | var first_elem = mg_flatten_array(args.processed.original_data || args.data)[0];
19 | args.time_series = first_elem[args.processed.original_x_accessor || args.x_accessor] instanceof Date;
20 | }
21 |
22 | function mg_init_compute_width (args) {
23 | var svg_width = args.width;
24 | // are we setting the aspect ratio?
25 | if (args.full_width) {
26 | // get parent element
27 | svg_width = get_width(args.target);
28 | }
29 | args.width = svg_width;
30 | }
31 |
32 | function mg_init_compute_height (args) {
33 | var svg_height = args.height;
34 | if (args.full_height) {
35 | svg_height = get_height(args.target);
36 | }
37 |
38 | if (args.chart_type === 'bar' && svg_height === null) {
39 | svg_height = args.height = args.data[0].length * args.bar_height + args.top + args.bottom;
40 | }
41 |
42 | args.height = svg_height;
43 | }
44 |
45 | function mg_remove_svg_if_chart_type_has_changed (svg, args) {
46 | if ((!svg.selectAll('.mg-main-line').empty() && args.chart_type !== 'line') ||
47 | (!svg.selectAll('.mg-points').empty() && args.chart_type !== 'point') ||
48 | (!svg.selectAll('.mg-histogram').empty() && args.chart_type !== 'histogram') ||
49 | (!svg.selectAll('.mg-barplot').empty() && args.chart_type !== 'bar')
50 | ) {
51 | svg.remove();
52 | }
53 | }
54 |
55 | function mg_add_svg_if_it_doesnt_exist (svg, args) {
56 | if (mg_get_svg_child_of(args.target).empty()) {
57 | svg = d3.select(args.target)
58 | .append('svg')
59 | .classed('linked', args.linked)
60 | .attr('width', args.width)
61 | .attr('height', args.height);
62 | }
63 | return svg;
64 | }
65 |
66 | function mg_add_clip_path_for_plot_area (svg, args) {
67 | svg.selectAll('.mg-clip-path').remove();
68 | svg.append('defs')
69 | .attr('class', 'mg-clip-path')
70 | .append('clipPath')
71 | .attr('id', 'mg-plot-window-' + mg_target_ref(args.target))
72 | .append('svg:rect')
73 | .attr('x', args.left)
74 | .attr('y', args.top)
75 | .attr('width', args.width - args.left - args.right - args.buffer)
76 | .attr('height', args.height - args.top - args.bottom - args.buffer + 1);
77 | }
78 |
79 | function mg_adjust_width_and_height_if_changed (svg, args) {
80 | if (args.width !== Number(svg.attr('width'))) {
81 | svg.attr('width', args.width);
82 | }
83 | if (args.height !== Number(svg.attr('height'))) {
84 | svg.attr('height', args.height);
85 | }
86 | }
87 |
88 | function mg_set_viewbox_for_scaling (svg, args) {
89 | // we need to reconsider how we handle automatic scaling
90 | svg.attr('viewBox', '0 0 ' + args.width + ' ' + args.height);
91 | if (args.full_width || args.full_height) {
92 | svg.attr('preserveAspectRatio', 'xMinYMin meet');
93 | }
94 | }
95 |
96 | function mg_remove_missing_classes_and_text (svg) {
97 | // remove missing class
98 | svg.classed('mg-missing', false);
99 |
100 | // remove missing text
101 | svg.selectAll('.mg-missing-text').remove();
102 | svg.selectAll('.mg-missing-pane').remove();
103 | }
104 |
105 | function mg_remove_outdated_lines (svg, args) {
106 | // if we're updating an existing chart and we have fewer lines than
107 | // before, remove the outdated lines, e.g. if we had 3 lines, and we're calling
108 | // data_graphic() on the same target with 2 lines, remove the 3rd line
109 |
110 | var i = 0;
111 | if (svg.selectAll('.mg-main-line')[0].length >= args.data.length) {
112 | // now, the thing is we can't just remove, say, line3 if we have a custom
113 | // line-color map, instead, see which are the lines to be removed, and delete those
114 | if (args.custom_line_color_map.length > 0) {
115 | var array_full_series = function (len) {
116 | var arr = new Array(len);
117 | for (var i = 0; i < arr.length; i++) { arr[i] = i + 1; }
118 | return arr;
119 | };
120 |
121 | // get an array of lines ids to remove
122 | var lines_to_remove = arr_diff(
123 | array_full_series(args.max_data_size),
124 | args.custom_line_color_map);
125 |
126 | for (i = 0; i < lines_to_remove.length; i++) {
127 | svg.selectAll('.mg-main-line.mg-line' + lines_to_remove[i] + '-color')
128 | .remove();
129 | }
130 | } else {
131 | // if we don't have a custom line-color map, just remove the lines from the end
132 |
133 | var num_of_new = args.data.length;
134 | var num_of_existing = svg.selectAll('.mg-main-line')[0].length;
135 |
136 | for (i = num_of_existing; i > num_of_new; i--) {
137 | svg.selectAll('.mg-main-line.mg-line' + i + '-color')
138 | .remove();
139 | }
140 | }
141 | }
142 | }
143 |
144 | function mg_raise_container_error(container, args){
145 | if (container.empty()) {
146 | console.warn('The specified target element "' + args.target + '" could not be found in the page. The chart will not be rendered.');
147 | return;
148 | }
149 | }
150 |
151 | function init (args) {
152 | 'use strict';
153 | args = arguments[0];
154 | args = mg_merge_args_with_defaults(args);
155 | // If you pass in a dom element for args.target, the expectation
156 | // of a string elsewhere will break.
157 | var container = d3.select(args.target);
158 | mg_raise_container_error(container, args);
159 |
160 | var svg = container.selectAll('svg');
161 |
162 | mg_is_time_series(args);
163 | mg_init_compute_width(args);
164 | mg_init_compute_height(args);
165 |
166 | mg_remove_svg_if_chart_type_has_changed(svg, args);
167 | svg = mg_add_svg_if_it_doesnt_exist(svg, args);
168 |
169 | mg_add_clip_path_for_plot_area(svg, args);
170 | mg_adjust_width_and_height_if_changed(svg, args);
171 | mg_set_viewbox_for_scaling(svg, args);
172 | mg_remove_missing_classes_and_text(svg);
173 | chart_title(args);
174 | mg_remove_outdated_lines(svg, args);
175 |
176 | return this;
177 | }
178 |
179 | MG.init = init;
180 |
--------------------------------------------------------------------------------
/tests/common/y_axis_test.js:
--------------------------------------------------------------------------------
1 | module('y_axis');
2 |
3 | test('Y-axis is added', function() {
4 | var params = {
5 | target: '#qunit-fixture',
6 | data: [{'date': new Date('2014-01-01'), 'value': 12},
7 | {'date': new Date('2014-03-01'), 'value': 18}]
8 | };
9 |
10 | MG.data_graphic(params);
11 | ok(document.querySelector('.mg-y-axis'), 'Y-axis is added');
12 | });
13 |
14 | test('args.y_axis set to false', function() {
15 | var params = {
16 | target: '#qunit-fixture',
17 | data: [{'date': new Date('2014-01-01'), 'value': 12},
18 | {'date': new Date('2014-03-01'), 'value': 18}],
19 | y_axis: false
20 | };
21 |
22 | MG.data_graphic(params);
23 | equal(document.querySelector('.mg-y-axis'), null, 'Y-axis is not added');
24 | });
25 |
26 | test('Only one y-axis is added on multiple calls to the same target element', function() {
27 | var params = {
28 | target: '#qunit-fixture',
29 | data: [{'date': new Date('2014-01-01'), 'value': 12},
30 | {'date': new Date('2014-03-01'), 'value': 18}]
31 | };
32 |
33 | MG.data_graphic(params);
34 | MG.data_graphic(MG.clone(params));
35 |
36 | equal(document.querySelectorAll(params.target + ' .mg-y-axis').length, 1, 'We only have one y-axis');
37 | });
38 |
39 | test('args.y_label', function() {
40 | var params = {
41 | target: '#qunit-fixture',
42 | data: [{'date': new Date('2014-01-01'), 'value': 12},
43 | {'date': new Date('2014-03-01'), 'value': 18}],
44 | y_label: 'foo bar'
45 | };
46 |
47 | MG.data_graphic(params);
48 | ok(document.querySelector('.mg-y-axis .label'), 'Y-axis label exists');
49 | });
50 |
51 | test('Y-axis doesn\'t break when data object is of length 1', function() {
52 | var params = {
53 | target: '#qunit-fixture',
54 | data: [{'date': new Date('2014-01-01'), 'value': 12}]
55 | };
56 |
57 | MG.data_graphic(params);
58 | ok(document.querySelector('.mg-y-axis'), 'Y-axis exists');
59 | });
60 |
61 | // test('args.small_text', function() {
62 | // var params = {
63 | // target: '#qunit-fixture',
64 | // data: [{'date': new Date('2014-01-01'), 'value': 12}],
65 | // small_text: true,
66 | // };
67 |
68 | // MG.data_graphic(params);
69 | // ok(document.querySelector('.mg-y-axis-small'), 'Small y-axis is set');
70 | // });
71 |
72 | test('args.y_rug', function() {
73 | var params = {
74 | target: '#qunit-fixture',
75 | data: [{'date': new Date('2014-01-01'), 'value': 12},
76 | {'date': new Date('2014-03-01'), 'value': 18}],
77 | y_rug: true
78 | };
79 |
80 | MG.data_graphic(params);
81 | ok(document.querySelector('.mg-y-rug'), 'Y-axis rugplot exists');
82 | });
83 |
84 | test('Only one rugplot is added on multiple calls to the same target element', function() {
85 | var params = {
86 | target: '#qunit-fixture',
87 | data: [{'date': new Date('2014-01-01'), 'value': 12},
88 | {'date': new Date('2014-03-01'), 'value': 18}],
89 | y_rug: true
90 | };
91 |
92 | MG.data_graphic(params);
93 | MG.data_graphic(MG.clone(params));
94 | equal(document.querySelectorAll('.mg-y-rug').length, 2, 'We only have one rugplot on the y-axis');
95 | });
96 |
97 | test('Default min_y is 0', function() {
98 | var params = {
99 | target: '#qunit-fixture',
100 | data: [{'date': new Date('2014-01-01'), 'value': 12},
101 | {'date': new Date('2014-03-01'), 'value': 18}]
102 | };
103 |
104 | MG.data_graphic(params);
105 | equal(document.querySelector('.mg-y-axis text').textContent, 0, 'Y-axis starts at 0');
106 | });
107 |
108 | test('args.min_y_from_data', function() {
109 | var params = {
110 | target: '#qunit-fixture',
111 | data: [{'date': new Date('2014-01-01'), 'value': 12},
112 | {'date': new Date('2014-03-01'), 'value': 18}],
113 | min_y_from_data: true
114 | };
115 |
116 | MG.data_graphic(params);
117 | equal(document.querySelector('.mg-y-axis text').textContent, 12, 'Y-axis starts at 12');
118 | });
119 |
120 | test('args.min_y set to arbitrary value', function() {
121 | var params = {
122 | target: '#qunit-fixture',
123 | data: [{'date': new Date('2014-01-01'), 'value': 12},
124 | {'date': new Date('2014-03-01'), 'value': 18}],
125 | min_y: 5
126 | };
127 |
128 | MG.data_graphic(params);
129 | equal(document.querySelector('.mg-y-axis text').textContent, 5, 'Y-axis starts at 5');
130 | });
131 |
132 | test('args.y_extended_ticks', function() {
133 | var params = {
134 | target: '#qunit-fixture',
135 | data: [{'date': new Date('2014-01-01'), 'value': 12},
136 | {'date': new Date('2014-03-01'), 'value': 18}],
137 | y_extended_ticks: true
138 | };
139 |
140 | MG.data_graphic(params);
141 | ok(document.querySelector('.mg-extended-y-ticks'), 'Y-axis extended ticks exist');
142 | });
143 |
144 | test('args.format is set to percentage', function() {
145 | var params = {
146 | target: '#qunit-fixture',
147 | data: [{'date': new Date('2014-01-01'), 'value': 0.12},
148 | {'date': new Date('2014-03-01'), 'value': 0.18}],
149 | format: 'percentage'
150 | };
151 |
152 | MG.data_graphic(params);
153 | equal(document.querySelector('.mg-y-axis text').textContent.slice(-1), '%', 'Y-axis units are %');
154 | });
155 |
156 | test('args.yax_units', function() {
157 | var params = {
158 | target: '#qunit-fixture',
159 | data: [{'date': new Date('2014-01-01'), 'value': 2.12},
160 | {'date': new Date('2014-03-01'), 'value': 4.18}],
161 | yax_units: '$',
162 | };
163 |
164 | MG.data_graphic(params);
165 | equal(document.querySelector('.mg-y-axis text').textContent[0], '$', 'Y-axis units are $');
166 | });
167 |
168 | test('When args.max_y is set, ignore inflator', function() {
169 | var params = {
170 | target: '#qunit-fixture',
171 | data: [{'date': new Date('2014-01-01'), 'value': 12},
172 | {'date': new Date('2014-03-01'), 'value': 18}],
173 | max_y: 50,
174 | };
175 |
176 | MG.data_graphic(params);
177 | var nodes = document.querySelectorAll('.mg-y-axis text');
178 | equal(nodes[nodes.length - 1].textContent, 50, 'Maximum y-axis value is 50');
179 | });
180 |
--------------------------------------------------------------------------------
/src/js/layout/bootstrap_dropdown.js:
--------------------------------------------------------------------------------
1 | if (typeof jQuery !== 'undefined') {
2 | /*!
3 | * Bootstrap v3.3.1 (http://getbootstrap.com)
4 | * Copyright 2011-2014 Twitter, Inc.
5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
6 | */
7 |
8 | /*!
9 | * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=c3834cc5b59ef727da53)
10 | * Config saved to config.json and https://gist.github.com/c3834cc5b59ef727da53
11 | */
12 |
13 | /* ========================================================================
14 | * Bootstrap: dropdown.js v3.3.1
15 | * http://getbootstrap.com/javascript/#dropdowns
16 | * ========================================================================
17 | * Copyright 2011-2014 Twitter, Inc.
18 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
19 | * ======================================================================== */
20 |
21 |
22 | +function ($) {
23 | 'use strict';
24 |
25 | if(typeof $().dropdown == 'function')
26 | return true;
27 |
28 | // DROPDOWN CLASS DEFINITION
29 | // =========================
30 |
31 | var backdrop = '.dropdown-backdrop';
32 | var toggle = '[data-toggle="dropdown"]';
33 | var Dropdown = function (element) {
34 | $(element).on('click.bs.dropdown', this.toggle);
35 | };
36 |
37 | Dropdown.VERSION = '3.3.1';
38 |
39 | Dropdown.prototype.toggle = function (e) {
40 | var $this = $(this);
41 |
42 | if ($this.is('.disabled, :disabled')) return;
43 |
44 | var $parent = getParent($this);
45 | var isActive = $parent.hasClass('open');
46 |
47 | clearMenus();
48 |
49 | if (!isActive) {
50 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
51 | // if mobile we use a backdrop because click events don't delegate
52 | $('
').insertAfter($(this)).on('click', clearMenus);
53 | }
54 |
55 | var relatedTarget = { relatedTarget: this };
56 | $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget));
57 |
58 | if (e.isDefaultPrevented()) return;
59 |
60 | $this
61 | .trigger('focus')
62 | .attr('aria-expanded', 'true');
63 |
64 | $parent
65 | .toggleClass('open')
66 | .trigger('shown.bs.dropdown', relatedTarget);
67 | }
68 |
69 | return false;
70 | };
71 |
72 | Dropdown.prototype.keydown = function (e) {
73 | if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return;
74 |
75 | var $this = $(this);
76 |
77 | e.preventDefault();
78 | e.stopPropagation();
79 |
80 | if ($this.is('.disabled, :disabled')) return;
81 |
82 | var $parent = getParent($this);
83 | var isActive = $parent.hasClass('open');
84 |
85 | if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
86 | if (e.which == 27) $parent.find(toggle).trigger('focus');
87 | return $this.trigger('click');
88 | }
89 |
90 | var desc = ' li:not(.divider):visible a';
91 | var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc);
92 |
93 | if (!$items.length) return;
94 |
95 | var index = $items.index(e.target);
96 |
97 | if (e.which == 38 && index > 0) index--; // up
98 | if (e.which == 40 && index < $items.length - 1) index++; // down
99 | if (!~index) index = 0;
100 |
101 | $items.eq(index).trigger('focus');
102 | };
103 |
104 | function clearMenus(e) {
105 | if (e && e.which === 3) return;
106 | $(backdrop).remove();
107 | $(toggle).each(function () {
108 | var $this = $(this);
109 | var $parent = getParent($this);
110 | var relatedTarget = { relatedTarget: this };
111 |
112 | if (!$parent.hasClass('open')) return;
113 |
114 | $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget));
115 |
116 | if (e.isDefaultPrevented()) return;
117 |
118 | $this.attr('aria-expanded', 'false');
119 | $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget);
120 | });
121 | }
122 |
123 | function getParent($this) {
124 | var selector = $this.attr('data-target');
125 |
126 | if (!selector) {
127 | selector = $this.attr('href');
128 | selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, ''); // strip for ie7
129 | }
130 |
131 | var $parent = selector && $(selector);
132 |
133 | return $parent && $parent.length ? $parent : $this.parent();
134 | }
135 |
136 |
137 | // DROPDOWN PLUGIN DEFINITION
138 | // ==========================
139 |
140 | function Plugin(option) {
141 | return this.each(function () {
142 | var $this = $(this);
143 | var data = $this.data('bs.dropdown');
144 |
145 | if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)));
146 | if (typeof option == 'string') data[option].call($this);
147 | });
148 | }
149 |
150 | var old = $.fn.dropdown;
151 |
152 | $.fn.dropdown = Plugin;
153 | $.fn.dropdown.Constructor = Dropdown;
154 |
155 |
156 | // DROPDOWN NO CONFLICT
157 | // ====================
158 |
159 | $.fn.dropdown.noConflict = function () {
160 | $.fn.dropdown = old;
161 | return this;
162 | };
163 |
164 |
165 | // APPLY TO STANDARD DROPDOWN ELEMENTS
166 | // ===================================
167 |
168 | $(document)
169 | .on('click.bs.dropdown.data-api', clearMenus)
170 | .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation(); })
171 | .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
172 | .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
173 | .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
174 | .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown);
175 |
176 | }(jQuery);
177 | }
--------------------------------------------------------------------------------
/examples/examples.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | MetricsGraphics.js - Examples
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
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 |
69 |
70 |
71 |
72 |
73 |
74 |
85 |
86 |
87 |
88 |
89 |
102 |
103 |
104 |
105 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/dist/metricsgraphics.css:
--------------------------------------------------------------------------------
1 | .mg-active-datapoint {
2 | fill: black;
3 | font-size: 0.9rem;
4 | font-weight: 400;
5 | opacity: 0.8;
6 | }
7 |
8 | .mg-area1-color {
9 | fill: #0000ff;
10 | }
11 |
12 | .mg-area2-color {
13 | fill: #05b378;
14 | }
15 |
16 | .mg-area3-color {
17 | fill: #db4437;
18 | }
19 |
20 | .mg-area4-color {
21 | fill: #f8b128;
22 | }
23 |
24 | .mg-area5-color {
25 | fill: #5c5c5c;
26 | }
27 |
28 | .mg-barplot rect.mg-bar {
29 | shape-rendering: auto;
30 | fill: #b6b6fc;
31 | }
32 |
33 | .mg-barplot rect.mg-bar.active {
34 | fill: #9e9efc;
35 | }
36 |
37 | .mg-barplot .mg-bar-prediction {
38 | fill: #5b5b5b;
39 | }
40 |
41 | .mg-barplot .mg-bar-baseline {
42 | stroke: #5b5b5b;
43 | stroke-width: 2;
44 | }
45 |
46 | .mg-baselines line {
47 | opacity: 1;
48 | shape-rendering: auto;
49 | stroke: #b3b2b2;
50 | stroke-width: 1px;
51 | }
52 |
53 | .mg-baselines text {
54 | fill: black;
55 | font-size: 0.9rem;
56 | opacity: 0.6;
57 | stroke: none;
58 | }
59 |
60 | .mg-baselines-small text {
61 | font-size: 0.6rem;
62 | }
63 |
64 | .mg-header {
65 | cursor: default;
66 | font-size: 1.2rem;
67 | }
68 |
69 | .mg-header .mg-chart-description {
70 | fill: #ccc;
71 | font-family: FontAwesome;
72 | font-size: 1.2rem;
73 | }
74 |
75 | .mg-points circle {
76 | opacity: 0.85;
77 | }
78 |
79 | .mg-popover {
80 | font-size: 0.95rem;
81 | }
82 |
83 | .mg-popover-content {
84 | cursor: auto;
85 | line-height: 17px;
86 | }
87 |
88 | .mg-data-table {
89 | margin-top: 30px;
90 | }
91 |
92 | .mg-data-table thead tr th {
93 | border-bottom: 1px solid darkgray;
94 | cursor: default;
95 | font-size: 1.1rem;
96 | font-weight: normal;
97 | padding: 5px 5px 8px 5px;
98 | text-align: right;
99 | }
100 |
101 | .mg-data-table thead tr th .fa {
102 | color: #ccc;
103 | padding-left: 4px;
104 | }
105 |
106 | .mg-data-table thead tr th .popover {
107 | font-size: 1rem;
108 | font-weight: normal;
109 | }
110 |
111 | .mg-data-table .secondary-title {
112 | color: darkgray;
113 | }
114 |
115 | .mg-data-table tbody tr td {
116 | margin: 2px;
117 | padding: 5px;
118 | vertical-align: top;
119 | }
120 |
121 | .mg-data-table tbody tr td.table-text {
122 | opacity: 0.8;
123 | padding-left: 30px;
124 | }
125 |
126 | .mg-y-axis line.mg-extended-y-ticks {
127 | opacity: 0.4;
128 | }
129 |
130 | .mg-x-axis line.mg-extended-x-ticks {
131 | opacity: 0.4;
132 | }
133 |
134 | .mg-histogram .axis path,
135 | .mg-histogram .axis line {
136 | fill: none;
137 | opacity: 0.7;
138 | shape-rendering: auto;
139 | stroke: #ccc;
140 | }
141 |
142 | .mg-histogram .mg-bar rect {
143 | fill: #b6b6fc;
144 | shape-rendering: auto;
145 | }
146 |
147 | .mg-histogram .mg-bar rect.active {
148 | fill: #9e9efc;
149 | }
150 |
151 | .mg-least-squares-line {
152 | stroke: red;
153 | stroke-width: 1px;
154 | }
155 |
156 | .mg-lowess-line {
157 | fill: none;
158 | stroke: red;
159 | }
160 |
161 | .mg-line1-color {
162 | stroke: #4040e8;
163 | }
164 |
165 | .mg-hover-line1-color {
166 | fill: #4040e8;
167 | }
168 |
169 | .mg-line2-color {
170 | stroke: #05b378;
171 | }
172 |
173 | .mg-hover-line2-color {
174 | fill: #05b378;
175 | }
176 |
177 | .mg-line3-color {
178 | stroke: #db4437;
179 | }
180 |
181 | .mg-hover-line3-color {
182 | fill: #db4437;
183 | }
184 |
185 | .mg-line4-color {
186 | stroke: #f8b128;
187 | }
188 |
189 | .mg-hover-line4-color {
190 | fill: #f8b128;
191 | }
192 |
193 | .mg-line5-color {
194 | stroke: #5c5c5c;
195 | }
196 |
197 | .mg-hover-line5-color {
198 | fill: #5c5c5c;
199 | }
200 |
201 | .mg-line-legend text {
202 | font-size: 0.9rem;
203 | font-weight: 300;
204 | stroke: none;
205 | }
206 |
207 | .mg-line1-legend-color {
208 | color: #4040e8;
209 | fill: #4040e8;
210 | }
211 |
212 | .mg-line2-legend-color {
213 | color: #05b378;
214 | fill: #05b378;
215 | }
216 |
217 | .mg-line3-legend-color {
218 | color: #db4437;
219 | fill: #db4437;
220 | }
221 |
222 | .mg-line4-legend-color {
223 | color: #f8b128;
224 | fill: #f8b128;
225 | }
226 |
227 | .mg-line5-legend-color {
228 | color: #5c5c5c;
229 | fill: #5c5c5c;
230 | }
231 |
232 | .mg-main-area-solid svg .mg-main-area {
233 | fill: #ccccff;
234 | opacity: 1;
235 | }
236 |
237 | .mg-markers line {
238 | opacity: 1;
239 | shape-rendering: auto;
240 | stroke: #b3b2b2;
241 | stroke-width: 1px;
242 | }
243 |
244 | .mg-markers text {
245 | fill: black;
246 | font-size: 0.8rem;
247 | opacity: 0.6;
248 | }
249 |
250 | .mg-missing-text {
251 | opacity: 0.9;
252 | }
253 |
254 | .mg-missing-background {
255 | stroke: blue;
256 | fill: none;
257 | stroke-dasharray: 10,5;
258 | stroke-opacity: 0.05;
259 | stroke-width: 2;
260 | }
261 |
262 | .mg-missing .mg-main-line {
263 | opacity: 0.1;
264 | }
265 |
266 | .mg-missing .mg-main-area {
267 | opacity: 0.03;
268 | }
269 |
270 | path.mg-main-area {
271 | opacity: 0.2;
272 | stroke: none;
273 | }
274 |
275 | path.mg-confidence-band {
276 | fill: #ccc;
277 | opacity: 0.4;
278 | stroke: none;
279 | }
280 |
281 | path.mg-main-line {
282 | fill: none;
283 | opacity: 0.8;
284 | stroke-width: 1.1px;
285 | }
286 |
287 | .mg-points circle {
288 | fill-opacity: 0.4;
289 | stroke-opacity: 1;
290 | }
291 |
292 | circle.mg-points-mono {
293 | fill: #0000ff;
294 | stroke: #0000ff;
295 | }
296 |
297 | /* a selected point in a scatterplot */
298 | .mg-points circle.selected {
299 | fill-opacity: 1;
300 | stroke-opacity: 1;
301 | }
302 |
303 | .mg-voronoi path {
304 | fill: none;
305 | pointer-events: all;
306 | stroke: none;
307 | stroke-opacity: 0.1;
308 | }
309 |
310 | .mg-x-rug-mono,
311 | .mg-y-rug-mono {
312 | stroke: black;
313 | }
314 |
315 | .mg-x-axis line,
316 | .mg-y-axis line {
317 | opacity: 1;
318 | shape-rendering: auto;
319 | stroke: #b3b2b2;
320 | stroke-width: 1px;
321 | }
322 |
323 | .mg-x-axis text,
324 | .mg-y-axis text,
325 | .mg-histogram .axis text {
326 | fill: black;
327 | font-size: 0.9rem;
328 | opacity: 0.6;
329 | }
330 |
331 | .mg-x-axis .label,
332 | .mg-y-axis .label,
333 | .mg-axis .label {
334 | font-size: 0.8rem;
335 | text-transform: uppercase;
336 | font-weight: 400;
337 | }
338 |
339 | .mg-x-axis-small text,
340 | .mg-y-axis-small text,
341 | .mg-active-datapoint-small {
342 | font-size: 0.6rem;
343 | }
344 |
345 | .mg-x-axis-small .label,
346 | .mg-y-axis-small .label {
347 | font-size: 0.65rem;
348 | }
349 |
350 | .mg-european-hours {
351 | }
352 |
353 | .mg-year-marker text {
354 | fill: black;
355 | font-size: 0.7rem;
356 | opacity: 0.6;
357 | }
358 |
359 | .mg-year-marker line {
360 | opacity: 1;
361 | shape-rendering: auto;
362 | stroke: #b3b2b2;
363 | stroke-width: 1px;
364 | }
365 |
366 | .mg-year-marker-small text {
367 | font-size: 0.6rem;
368 | }
369 |
--------------------------------------------------------------------------------
/examples/charts/annotations.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
d3.json('data/some_percentage.json', function(data) {
7 | for (var i = 0; i < data.length; i++) {
8 | data[i] = MG.convert.date(data[i], 'date');
9 | }
10 |
11 | var markers = [{
12 | 'date': new Date('2014-02-01T00:00:00.000Z'),
13 | 'label': '1st Milestone'
14 | }, {
15 | 'date': new Date('2014-03-15T00:00:00.000Z'),
16 | 'label': '2nd Milestone'
17 | }];
18 |
19 | MG.data_graphic({
20 | title: "Markers",
21 | description: "Markers are vertical lines that can be added at arbitrary points. Markers that are close to each other won't collide.",
22 | data: data,
23 | width: 600,
24 | height: 200,
25 | right: 40,
26 | markers: markers,
27 | format: 'percentage',
28 | target: '#markers'
29 | });
30 | });
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
d3.json('data/some_percentage.json', function(data) {
41 | for (var i = 0; i < data.length; i++) {
42 | data[i] = MG.convert.date(data[i], 'date');
43 | }
44 |
45 | var clicker = function() {
46 | window.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ', '_blank');
47 | };
48 |
49 | var markers = [{
50 | 'date': new Date('2014-02-01T00:00:00.000Z'),
51 | 'label': "Click me",
52 | 'click': clicker
53 | }, {
54 | 'date': new Date('2014-03-15T00:00:00.000Z'),
55 | 'label': "Nothing to see here"
56 | }];
57 |
58 | MG.data_graphic({
59 | title: "Clickable Markers",
60 | description: "You can assign arbitrary functions to markers' click events.",
61 | data: data,
62 | width: 600,
63 | height: 200,
64 | right: 40,
65 | markers: markers,
66 | format: 'percentage',
67 | target: '#markers-clickable'
68 | });
69 | });
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
d3.json('data/fake_users1.json', function(data) {
80 | data = MG.convert.date(data, 'date');
81 | MG.data_graphic({
82 | title: "Baselines",
83 | description: "Baselines are horizontal lines that can added at arbitrary points.",
84 | data: data,
85 | baselines: [{value: 160000000, label: 'a baseline'}],
86 | width: 600,
87 | height: 200,
88 | right: 40,
89 | target: '#baselines'
90 | });
91 | });
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
d3.json('data/fake_users1.json', function(data) {
102 | data = MG.convert.date(data, 'date');
103 |
104 | var markers = [{
105 | 'date': new Date('2014-03-17T00:00:00.000Z'),
106 | 'label': 'Look, a spike!'
107 | }];
108 |
109 | MG.data_graphic({
110 | title: "Annotating a Point",
111 | description: "By setting the graphic's target a class name of mg-main-area-solid, markers don't extend down to the bottom of the graphic, which better draws attention to, say, spikes.",
112 | data: data,
113 | width: 600,
114 | height: 200,
115 | right: 40,
116 | markers: markers,
117 | target: '#spike'
118 | });
119 | });
120 |
121 |
122 |
123 |
124 |
211 |
--------------------------------------------------------------------------------
/src/js/misc/smoothers.js:
--------------------------------------------------------------------------------
1 | function add_ls(args) {
2 | var svg = mg_get_svg_child_of(args.target);
3 | var data = args.data[0];
4 | var min_x = d3.min(data, function(d) { return d[args.x_accessor]; });
5 | var max_x = d3.max(data, function(d) { return d[args.x_accessor]; });
6 |
7 | d3.select(args.target).selectAll('.mg-least-squares-line').remove();
8 |
9 | svg.append('svg:line')
10 | .attr('x1', args.scales.X(min_x))
11 | .attr('x2', args.scales.X(max_x))
12 | .attr('y1', args.scales.Y(args.ls_line.fit(min_x)) )
13 | .attr('y2', args.scales.Y(args.ls_line.fit(max_x)) )
14 | .attr('class', 'mg-least-squares-line');
15 | }
16 |
17 | MG.add_ls = add_ls;
18 |
19 | function add_lowess(args) {
20 | var svg = d3.select($(args.target).find('svg').get(0));
21 | var lowess = args.lowess_line;
22 |
23 | var line = d3.svg.line()
24 | .x(function(d) { return args.scales.X(d.x); })
25 | .y(function(d) { return args.scales.Y(d.y); })
26 | .interpolate(args.interpolate);
27 |
28 | svg.append('path')
29 | .attr('d', line(lowess))
30 | .attr('class', 'mg-lowess-line');
31 | }
32 |
33 | MG.add_lowess = add_lowess;
34 |
35 | function lowess_robust(x, y, alpha, inc) {
36 | // Used http://www.unc.edu/courses/2007spring/biol/145/001/docs/lectures/Oct27.html
37 | // for the clear explanation of robust lowess.
38 |
39 | // calculate the the first pass.
40 | var _l;
41 | var r = [];
42 | var yhat = d3.mean(y);
43 | var i;
44 | for (i = 0; i < x.length; i += 1) { r.push(1); }
45 | _l = _calculate_lowess_fit(x,y,alpha, inc, r);
46 | var x_proto = _l.x;
47 | var y_proto = _l.y;
48 |
49 | // Now, take the fit, recalculate the weights, and re-run LOWESS using r*w instead of w.
50 |
51 | for (i = 0; i < 100; i += 1) {
52 | r = d3.zip(y_proto, y).map(function(yi) {
53 | return Math.abs(yi[1] - yi[0]);
54 | });
55 |
56 | var q = d3.quantile(r.sort(), 0.5);
57 |
58 | r = r.map(function(ri) {
59 | return _bisquare_weight(ri / (6 * q));
60 | });
61 |
62 | _l = _calculate_lowess_fit(x,y,alpha,inc, r);
63 | x_proto = _l.x;
64 | y_proto = _l.y;
65 | }
66 |
67 | return d3.zip(x_proto, y_proto).map(function(d) {
68 | var p = {};
69 | p.x = d[0];
70 | p.y = d[1];
71 | return p;
72 | });
73 | }
74 |
75 | MG.lowess_robust = lowess_robust;
76 |
77 | function lowess(x, y, alpha, inc) {
78 | var r = [];
79 | for (var i = 0; i < x.length; i += 1) { r.push(1); }
80 | var _l = _calculate_lowess_fit(x, y, alpha, inc, r);
81 | }
82 |
83 | MG.lowess = lowess;
84 |
85 | function least_squares(x_, y_) {
86 | var x, y, xi, yi,
87 | _x = 0,
88 | _y = 0,
89 | _xy = 0,
90 | _xx = 0;
91 |
92 | var n = x_.length;
93 | if (x_[0] instanceof Date) {
94 | x = x_.map(function(d) {
95 | return d.getTime();
96 | });
97 | } else {
98 | x = x_;
99 | }
100 |
101 | if (y_[0] instanceof Date) {
102 | y = y_.map(function(d) {
103 | return d.getTime();
104 | });
105 | } else {
106 | y = y_;
107 | }
108 |
109 | var xhat = d3.mean(x);
110 | var yhat = d3.mean(y);
111 | var numerator = 0, denominator = 0;
112 |
113 | for (var i = 0; i < x.length; i++) {
114 | xi = x[i];
115 | yi = y[i];
116 | numerator += (xi - xhat) * (yi - yhat);
117 | denominator += (xi - xhat) * (xi - xhat);
118 | }
119 |
120 | var beta = numerator / denominator;
121 | var x0 = yhat - beta * xhat;
122 |
123 | return {
124 | x0: x0,
125 | beta: beta,
126 | fit: function(x) {
127 | return x0 + x * beta;
128 | }
129 | };
130 | }
131 |
132 | MG.least_squares = least_squares;
133 |
134 | function _pow_weight(u, w) {
135 | if (u >= 0 && u <= 1) {
136 | return Math.pow(1 - Math.pow(u,w), w);
137 | } else {
138 | return 0;
139 | }
140 | }
141 |
142 | function _bisquare_weight(u) {
143 | return _pow_weight(u, 2);
144 | }
145 |
146 | function _tricube_weight(u) {
147 | return _pow_weight(u, 3);
148 | }
149 |
150 | function _neighborhood_width(x0, xis) {
151 | return Array.max(xis.map(function(xi) {
152 | return Math.abs(x0 - xi);
153 | }));
154 | }
155 |
156 | function _manhattan(x1,x2) {
157 | return Math.abs(x1 - x2);
158 | }
159 |
160 | function _weighted_means(wxy) {
161 | var wsum = d3.sum(wxy.map(function(wxyi) { return wxyi.w; }));
162 |
163 | return {
164 | xbar: d3.sum(wxy.map(function(wxyi) {
165 | return wxyi.w * wxyi.x;
166 | })) / wsum,
167 | ybar:d3.sum(wxy.map(function(wxyi) {
168 | return wxyi.w * wxyi.y;
169 | })) / wsum
170 | };
171 | }
172 |
173 | function _weighted_beta(wxy, xbar, ybar) {
174 | var num = d3.sum(wxy.map(function(wxyi) {
175 | return Math.pow(wxyi.w, 2) * (wxyi.x - xbar) * (wxyi.y - ybar);
176 | }));
177 |
178 | var denom = d3.sum(wxy.map(function(wxyi) {
179 | return Math.pow(wxyi.w, 2) * Math.pow(wxyi.x - xbar, 2);
180 | }));
181 |
182 | return num / denom;
183 | }
184 |
185 | function _weighted_least_squares(wxy) {
186 | var ybar, xbar, beta_i, x0;
187 |
188 | var _wm = _weighted_means(wxy);
189 |
190 | xbar = _wm.xbar;
191 | ybar = _wm.ybar;
192 |
193 | var beta = _weighted_beta(wxy, xbar, ybar);
194 |
195 | return {
196 | beta : beta,
197 | xbar : xbar,
198 | ybar : ybar,
199 | x0 : ybar - beta * xbar
200 |
201 | };
202 | }
203 |
204 | function _calculate_lowess_fit(x, y, alpha, inc, residuals) {
205 | // alpha - smoothing factor. 0 < alpha < 1/
206 | //
207 | //
208 | var k = Math.floor(x.length * alpha);
209 |
210 | var sorted_x = x.slice();
211 |
212 | sorted_x.sort(function(a,b) {
213 | if (a < b) { return -1; }
214 | else if (a > b) { return 1; }
215 |
216 | return 0;
217 | });
218 |
219 | var x_max = d3.quantile(sorted_x, 0.98);
220 | var x_min = d3.quantile(sorted_x, 0.02);
221 |
222 | var xy = d3.zip(x, y, residuals).sort();
223 |
224 | var size = Math.abs(x_max - x_min) / inc;
225 |
226 | var smallest = x_min;
227 | var largest = x_max;
228 | var x_proto = d3.range(smallest, largest, size);
229 |
230 | var xi_neighbors;
231 | var x_i, beta_i, x0_i, delta_i, xbar, ybar;
232 |
233 | // for each prototype, find its fit.
234 | var y_proto = [];
235 |
236 | for (var i = 0; i < x_proto.length; i += 1) {
237 | x_i = x_proto[i];
238 |
239 | // get k closest neighbors.
240 | xi_neighbors = xy.map(function(xyi) {
241 | return [
242 | Math.abs(xyi[0] - x_i),
243 | xyi[0],
244 | xyi[1],
245 | xyi[2]];
246 | }).sort().slice(0, k);
247 |
248 | // Get the largest distance in the neighbor set.
249 | delta_i = d3.max(xi_neighbors)[0];
250 |
251 | // Prepare the weights for mean calculation and WLS.
252 |
253 | xi_neighbors = xi_neighbors.map(function(wxy) {
254 | return {
255 | w : _tricube_weight(wxy[0] / delta_i) * wxy[3],
256 | x : wxy[1],
257 | y :wxy[2]
258 | };
259 | });
260 |
261 | // Find the weighted least squares, obviously.
262 | var _output = _weighted_least_squares(xi_neighbors);
263 |
264 | x0_i = _output.x0;
265 | beta_i = _output.beta;
266 |
267 | //
268 | y_proto.push(x0_i + beta_i * x_i);
269 | }
270 |
271 | return {x: x_proto, y: y_proto};
272 | }
273 |
--------------------------------------------------------------------------------
/src/js/common/data_graphic.js:
--------------------------------------------------------------------------------
1 | MG.globals = {};
2 | MG.deprecations = {
3 | rollover_callback: { replacement: 'mouseover', version: '2.0' },
4 | rollout_callback: { replacement: 'mouseout', version: '2.0' },
5 | show_years: { replacement: 'show_secondary_x_label', version: '2.1' },
6 | xax_start_at_min: { replacement: 'axes_not_compact', version: '2.7' }
7 | };
8 | MG.globals.link = false;
9 | MG.globals.version = "1.1";
10 |
11 | MG.charts = {};
12 |
13 | MG.data_graphic = function(args) {
14 | 'use strict';
15 | var defaults = {
16 | missing_is_zero: false, // if true, missing values will be treated as zeros
17 | missing_is_hidden: false, // if true, missing values will appear as broken segments
18 | missing_is_hidden_accessor: null, // the accessor that determines the boolean value for missing data points
19 | legend: '' , // an array identifying the labels for a chart's lines
20 | legend_target: '', // if set, the specified element is populated with a legend
21 | error: '', // if set, a graph will show an error icon and log the error to the console
22 | animate_on_load: false, // animate lines on load
23 | top: 65, // the size of the top margin
24 | title_y_position: 10, // how many pixels from the top edge (0) should we show the title at
25 | bottom: 30, // the size of the bottom margin
26 | right: 10, // size of the right margin
27 | left: 50, // size of the left margin
28 | buffer: 8, // the buffer between the actual chart area and the margins
29 | width: 350, // the width of the entire graphic
30 | height: 220, // the height of the entire graphic
31 | full_width: false, // sets the graphic width to be the width of the parent element and resizes dynamically
32 | full_height: false, // sets the graphic width to be the width of the parent element and resizes dynamically
33 | small_height_threshold: 120, // the height threshold for when smaller text appears
34 | small_width_threshold: 160, // the width threshold for when smaller text appears
35 | //small_text: false, // coerces small text regardless of graphic size
36 | xax_count: 6, // number of x axis ticks
37 | xax_tick_length: 5, // x axis tick length
38 | axes_not_compact: true,
39 | yax_count: 5, // number of y axis ticks
40 | yax_tick_length: 5, // y axis tick length
41 | x_extended_ticks: false, // extends x axis ticks across chart - useful for tall charts
42 | y_extended_ticks: false, // extends y axis ticks across chart - useful for long charts
43 | y_scale_type: 'linear',
44 | max_x: null,
45 | max_y: null,
46 | min_x: null,
47 | min_y: null, // if set, y axis starts at an arbitrary value
48 | min_y_from_data: false, // if set, y axis will start at minimum value rather than at 0
49 | point_size: 2.5, // the size of the dot that appears on a line on mouse-over
50 | x_accessor: 'date',
51 | xax_units: '',
52 | x_label: '',
53 | x_sort: true,
54 | x_axis: true,
55 | y_axis: true,
56 | y_accessor: 'value',
57 | y_label: '',
58 | yax_units: '',
59 | x_rug: false,
60 | y_rug: false,
61 | x_rollover_format: null, //
62 | y_rollover_format: null, //
63 | transition_on_update: true,
64 | mouseover: null,
65 | show_rollover_text: true,
66 | show_confidence_band: null, // given [l, u] shows a confidence at each point from l to u
67 | xax_format: null, // xax_format is a function that formats the labels for the x axis.
68 | area: true,
69 | chart_type: 'line',
70 | data: [],
71 | decimals: 2, // the number of decimals in any rollover
72 | format: 'count', // format = {count, percentage}
73 | inflator: 10/9, // for setting y axis max
74 | linked: false, // links together all other graphs with linked:true, so rollovers in one trigger rollovers in the others
75 | linked_format: '%Y-%m-%d', // What granularity to link on for graphs. Default is at day
76 | list: false,
77 | baselines: null, // sets the baseline lines
78 | markers: null, // sets the marker lines
79 | scalefns: {},
80 | scales: {},
81 | utc_time: false,
82 | european_clock: false,
83 | show_year_markers: false,
84 | show_secondary_x_label: true,
85 | target: '#viz',
86 | interpolate: 'cardinal', // interpolation method to use when rendering lines
87 | interpolate_tension: 0.7, // its range is from 0 to 1; increase if your data is irregular and you notice artifacts
88 | custom_line_color_map: [], // allows arbitrary mapping of lines to colors, e.g. [2,3] will map line 1 to color 2 and line 2 to color 3
89 | colors: null, // UNIMPLEMENTED - allows direct color mapping to line colors. Will eventually require
90 | max_data_size: null, // explicitly specify the the max number of line series, for use with custom_line_color_map
91 | aggregate_rollover: false, // links the lines in a multi-line chart
92 | show_tooltips: true // if enabled, a chart's description will appear in a tooltip (requires jquery)
93 | };
94 |
95 | MG.call_hook('global.defaults', defaults);
96 |
97 | if (!args) { args = {}; }
98 |
99 | var selected_chart = MG.charts[args.chart_type || defaults.chart_type];
100 | merge_with_defaults(args, selected_chart.defaults, defaults);
101 |
102 | if (args.list) {
103 | args.x_accessor = 0;
104 | args.y_accessor = 1;
105 | }
106 |
107 | // check for deprecated parameters
108 | for (var key in MG.deprecations) {
109 | if (args.hasOwnProperty(key)) {
110 | var deprecation = MG.deprecations[key],
111 | message = 'Use of `args.' + key + '` has been deprecated',
112 | replacement = deprecation.replacement,
113 | version;
114 |
115 | // transparently alias the deprecated
116 | if (replacement) {
117 | if (args[replacement]) {
118 | message += '. The replacement - `args.' + replacement + '` - has already been defined. This definition will be discarded.';
119 | } else {
120 | args[replacement] = args[key];
121 | }
122 | }
123 |
124 | if (deprecation.warned) {
125 | continue;
126 | }
127 |
128 | deprecation.warned = true;
129 |
130 | if (replacement) {
131 | message += ' in favor of `args.' + replacement + '`';
132 | }
133 |
134 | warn_deprecation(message, deprecation.version);
135 | }
136 | }
137 |
138 | MG.call_hook('global.before_init', args);
139 |
140 | new selected_chart.descriptor(args);
141 |
142 | return args.data;
143 | };
144 |
--------------------------------------------------------------------------------
/tests/common/x_axis_test.js:
--------------------------------------------------------------------------------
1 | module('x_axis');
2 |
3 | test('X-axis is added', function() {
4 | var params = {
5 | target: '#qunit-fixture',
6 | data: [{'date': new Date('2014-01-01'), 'value': 12},
7 | {'date': new Date('2014-03-01'), 'value': 18}]
8 | };
9 |
10 | MG.data_graphic(params);
11 | ok(document.querySelector('.mg-x-axis'), 'X-axis is added');
12 | });
13 |
14 | test('args.x_axis set to false', function() {
15 | var params = {
16 | target: '#qunit-fixture',
17 | data: [{'date': new Date('2014-01-01'), 'value': 12},
18 | {'date': new Date('2014-03-01'), 'value': 18}],
19 | x_axis: false
20 | };
21 |
22 | MG.data_graphic(params);
23 | equal(document.querySelector('.mg-x-axis'), null, 'X-axis is not added');
24 | });
25 |
26 | test('Only one x-axis is added on multiple calls to the same target element', function() {
27 | var params = {
28 | target: '#qunit-fixture',
29 | data: [{'date': new Date('2014-01-01'), 'value': 12},
30 | {'date': new Date('2014-03-01'), 'value': 18}]
31 | };
32 |
33 | MG.data_graphic(params);
34 | MG.data_graphic(MG.clone(params));
35 |
36 | equal(document.querySelectorAll(params.target + ' .mg-x-axis').length, 1, 'We only have one x-axis');
37 | });
38 |
39 | test('args.show_secondary_x_label: true', function() {
40 | var params = {
41 | target: '#qunit-fixture',
42 | data: [{'date': new Date('2014-01-01'), 'value': 12},
43 | {'date': new Date('2014-03-01'), 'value': 18}]
44 | };
45 |
46 | MG.data_graphic(params);
47 | ok(document.querySelector('.mg-year-marker'), 'Year marker exists');
48 | });
49 |
50 | test('args.show_secondary_x_label: false', function() {
51 | var params = {
52 | target: '#qunit-fixture',
53 | data: [{'date': new Date('2014-01-01'), 'value': 12},
54 | {'date': new Date('2014-03-01'), 'value': 18}],
55 | show_secondary_x_label: false
56 | };
57 |
58 | MG.data_graphic(params);
59 | equal(document.querySelector('.mg-year-marker'), null, 'Year marker not added');
60 | });
61 |
62 | test('args.x_label', function() {
63 | var params = {
64 | target: '#qunit-fixture',
65 | data: [{'date': new Date('2014-01-01'), 'value': 12},
66 | {'date': new Date('2014-03-01'), 'value': 18}],
67 | x_label: 'foo bar'
68 | };
69 |
70 | MG.data_graphic(params);
71 | ok(document.querySelector('.mg-x-axis .label'), 'X-axis label exists');
72 | });
73 |
74 | test('X-axis doesn\'t break when data object is of length 1', function() {
75 | var params = {
76 | target: '#qunit-fixture',
77 | data: [{'date': new Date('2014-01-01'), 'value': 12}]
78 | };
79 |
80 | MG.data_graphic(params);
81 | ok(document.querySelector('.mg-x-axis'), 'X-axis exists');
82 | });
83 |
84 | // test('args.small_text', function() {
85 | // var params = {
86 | // target: '#qunit-fixture',
87 | // data: [{'date': new Date('2014-01-01'), 'value': 12}],
88 | // small_text: true,
89 | // };
90 |
91 | // MG.data_graphic(params);
92 | // ok(document.querySelector('.mg-x-axis-small'), 'Small x-axis is set');
93 | // });
94 |
95 | // test('args.small_text and args.show_secondary_x_label', function() {
96 | // var params = {
97 | // target: '#qunit-fixture',
98 | // data: [{'date': new Date('2014-01-01'), 'value': 12},
99 | // {'date': new Date('2014-03-01'), 'value': 18}],
100 | // small_text: true
101 | // };
102 |
103 | // MG.data_graphic(params);
104 | // ok(document.querySelector('.mg-year-marker-small'), 'Small year-marker is set');
105 | // });
106 |
107 | test('args.x_rug', function() {
108 | var params = {
109 | target: '#qunit-fixture',
110 | data: [{'date': new Date('2014-01-01'), 'value': 12},
111 | {'date': new Date('2014-03-01'), 'value': 18}],
112 | x_rug: true
113 | };
114 |
115 | MG.data_graphic(params);
116 | ok(document.querySelector('.mg-x-rug'), 'X-axis rugplot exists');
117 | });
118 |
119 | test('Only one rugplot is added on multiple calls to the same target element', function() {
120 | var params = {
121 | target: '#qunit-fixture',
122 | data: [{'date': new Date('2014-01-01'), 'value': 12},
123 | {'date': new Date('2014-03-01'), 'value': 18}],
124 | x_rug: true
125 | };
126 |
127 | MG.data_graphic(params);
128 | MG.data_graphic(MG.clone(params));
129 |
130 | equal(document.querySelectorAll('.mg-x-rug').length, 2, 'We only have one rugplot on the x-axis');
131 | });
132 |
133 | test('args.x_extended_ticks', function() {
134 | var params = {
135 | target: '#qunit-fixture',
136 | data: [{'date': new Date('2014-01-01'), 'value': 12},
137 | {'date': new Date('2014-03-01'), 'value': 18}],
138 | x_extended_ticks: true
139 | };
140 |
141 | MG.data_graphic(params);
142 | ok(document.querySelector('.mg-extended-x-ticks'), 'X-axis extended ticks exist');
143 | });
144 |
145 | test('Correctly calculates min and max values for line, point and histogram charts', function() {
146 | var args;
147 |
148 | // single series
149 | args = {
150 | processed: {},
151 | x_accessor: 'x',
152 | chart_type: 'line',
153 | data: [
154 | [
155 | {x: 4},
156 | {x: 5},
157 | {x: 6},
158 | {x: 7}
159 | ]
160 | ]
161 | };
162 | mg_find_min_max_x(args);
163 | equal(args.processed.min_x, 4, 'min is correct for single series');
164 | equal(args.processed.max_x, 7, 'max is correct for single series');
165 |
166 | // multiple series
167 | args = {
168 | processed: {},
169 | x_accessor: 'x',
170 | chart_type: 'line',
171 | data: [
172 | [
173 | {x: 1},
174 | {x: 2},
175 | {x: 3},
176 | {x: 4}
177 | ], [
178 | {x: 5},
179 | {x: 6},
180 | {x: 7}
181 | ]
182 | ]
183 | };
184 | mg_find_min_max_x(args);
185 | equal(args.processed.min_x, 1, 'min is correct for multiple series');
186 | equal(args.processed.max_x, 7, 'max is correct for multiple series');
187 | });
188 |
189 | test('Correctly calculates min and max values for bar chart', function() {
190 | var args;
191 |
192 | // single series
193 | args = {
194 | processed: {},
195 | x_accessor: 'x',
196 | baseline_accessor: 'b',
197 | predictor_accessor: 'p',
198 | chart_type: 'bar',
199 | data: [
200 | [
201 | {x: 4, b: 3, p: 2},
202 | {x: 5, b: 2, p: 6},
203 | {x: 6, b: 1, p: 10},
204 | {x: 7, b: 0, p: 12}
205 | ]
206 | ]
207 | };
208 | mg_find_min_max_x(args);
209 | equal(args.processed.min_x, 0, 'min is correct');
210 | equal(args.processed.max_x, 12, 'max is correct');
211 | });
212 |
213 | test('Ensure that custom xax_format isn\'t deleted', function() {
214 | var params = {
215 | title: 'foo',
216 | target: '.result',
217 | xax_format: function(d) { return 'humbug'; },
218 | data: [{'date': new Date('2014-01-01'), 'value': 12},
219 | {'date': new Date('2014-03-01'), 'value': 18}]
220 | };
221 |
222 | MG.data_graphic(params);
223 | equal(params.xax_format(), 'humbug', 'xax_format hasn\'t been overriden');
224 | });
225 |
226 | test('Ensure that default null xax_format is respected; allow MG to recalculate the default on redraw', function() {
227 | var params = {
228 | title: 'foo',
229 | target: '.result',
230 | xax_format: null,
231 | data: [{'date': new Date('2014-01-01'), 'value': 12},
232 | {'date': new Date('2014-03-01'), 'value': 18}]
233 | };
234 |
235 | MG.data_graphic(params);
236 | equal(params.xax_format, null, 'xax_format is still null');
237 | });
238 |
--------------------------------------------------------------------------------
/src/js/charts/table.js:
--------------------------------------------------------------------------------
1 | /*
2 | Data Tables
3 |
4 | Along with histograms, bars, lines, and scatters, a simple data table can take you far.
5 | We often just want to look at numbers, organized as a table, where columns are variables,
6 | and rows are data points. Sometimes we want a cell to have a small graphic as the main
7 | column element, in which case we want small multiples. sometimes we want to
8 |
9 | var table = New data_table(data)
10 | .target('div#data-table')
11 | .title({accessor: 'point_name', align: 'left'})
12 | .description({accessor: 'description'})
13 | .number({accessor: ''})
14 |
15 | */
16 |
17 | MG.data_table = function(args) {
18 | 'use strict';
19 | this.args = args;
20 | this.args.standard_col = { width: 150, font_size: 12, font_weight: 'normal' };
21 | this.args.columns = [];
22 | this.formatting_options = [['color', 'color'], ['font-weight', 'font_weight'], ['font-style', 'font_style'], ['font-size', 'font_size']];
23 |
24 | this._strip_punctuation = function(s) {
25 | var punctuationless = s.replace(/[^a-zA-Z0-9 _]+/g, '');
26 | var finalString = punctuationless.replace(/ +?/g, '');
27 | return finalString;
28 | };
29 |
30 | this._format_element = function(element, value, args) {
31 | this.formatting_options.forEach(function(fo) {
32 | var attr = fo[0];
33 | var key = fo[1];
34 | if (args[key]) element.style(attr,
35 | typeof args[key] === 'string' ||
36 | typeof args[key] === 'number' ?
37 | args[key] : args[key](value));
38 | });
39 | };
40 |
41 | this._add_column = function(_args, arg_type) {
42 | var standard_column = this.args.standard_col;
43 | var args = merge_with_defaults(MG.clone(_args), MG.clone(standard_column));
44 | args.type = arg_type;
45 | this.args.columns.push(args);
46 | };
47 |
48 | this.target = function() {
49 | var target = arguments[0];
50 | this.args.target = target;
51 | return this;
52 | };
53 |
54 | this.title = function() {
55 | this._add_column(arguments[0], 'title');
56 | return this;
57 | };
58 |
59 | this.text = function() {
60 | this._add_column(arguments[0], 'text');
61 | return this;
62 | };
63 |
64 | this.bullet = function() {
65 | /*
66 | text label
67 | main value
68 | comparative measure
69 | any number of ranges
70 |
71 | additional args:
72 | no title
73 | xmin, xmax
74 | format: percentage
75 | xax_formatter
76 | */
77 | return this;
78 | };
79 |
80 | this.sparkline = function() {
81 | return this;
82 | };
83 |
84 | this.number = function() {
85 | this._add_column(arguments[0], 'number');
86 | return this;
87 | };
88 |
89 | this.display = function() {
90 | var args = this.args;
91 |
92 | chart_title(args);
93 |
94 | var target = args.target;
95 | var table = d3.select(target).append('table').classed('mg-data-table', true);
96 | var colgroup = table.append('colgroup');
97 | var thead = table.append('thead');
98 | var tbody = table.append('tbody');
99 | var this_column;
100 | var this_title;
101 |
102 | var tr, th, td_accessor, td_type, td_value, th_text, td_text, td;
103 | var col;
104 | var h;
105 |
106 | tr = thead.append('tr');
107 |
108 | for (h = 0; h < args.columns.length; h++) {
109 | var this_col = args.columns[h];
110 | td_type = this_col.type;
111 | th_text = this_col.label;
112 | th_text = th_text === undefined ? '' : th_text;
113 | th = tr.append('th')
114 | .style('width', this_col.width)
115 | .style('text-align', td_type === 'title' ? 'left' : 'right')
116 | .text(th_text);
117 |
118 | if (args.show_tooltips && this_col.description) {
119 | th.append('i')
120 | .classed('fa', true)
121 | .classed('fa-question-circle', true)
122 | .classed('fa-inverse', true);
123 |
124 | $(th[0]).popover({
125 | html: true,
126 | animation: false,
127 | content: this_col.description,
128 | trigger: 'hover',
129 | placement: 'top',
130 | container: $(th[0])
131 | });
132 | }
133 | }
134 |
135 | for (h = 0; h < args.columns.length; h++) {
136 | col = colgroup.append('col');
137 | if (args.columns[h].type === 'number') {
138 | col.attr('align', 'char').attr('char', '.');
139 | }
140 | }
141 |
142 | for (var i=0; i < args.data.length; i++) {
143 | tr = tbody.append('tr');
144 | for (var j = 0; j < args.columns.length; j++) {
145 | this_column = args.columns[j];
146 | td_accessor = this_column.accessor;
147 | td_value = td_text = args.data[i][td_accessor];
148 | td_type = this_column.type;
149 |
150 | if (td_type === 'number') {
151 | //td_text may need to be rounded
152 | if (this_column.hasOwnProperty('round') && !this_column.hasOwnProperty('format')) {
153 | // round according to the number value in this_column.round
154 | td_text = d3.format('0,.'+this_column.round+'f')(td_text);
155 | }
156 |
157 | if (this_column.hasOwnProperty('value_formatter')) {
158 | // provide a function that formats the text according to the function this_column.format.
159 | td_text = this_column.value_formatter(td_text);
160 | }
161 |
162 | if (this_column.hasOwnProperty('format')) {
163 | // this is a shorthand for percentage formatting, and others if need be.
164 | // supported: 'percentage', 'count', 'temperature'
165 |
166 | if (this_column.round) {
167 | td_text = d3.round(td_text, this_column.round);
168 | }
169 |
170 | var this_format = this_column.format;
171 | var formatter;
172 |
173 | if (this_format === 'percentage') formatter = d3.format('%p');
174 | if (this_format === 'count') formatter = d3.format("0,000");
175 | if (this_format === 'temperature') formatter = function(t) { return t +'°'; };
176 |
177 | td_text = formatter(td_text);
178 | }
179 |
180 | if (this_column.hasOwnProperty('currency')) {
181 | // this is another shorthand for formatting according to a currency amount, which gets appended to front of number
182 | td_text = this_column.currency + td_text;
183 | }
184 | }
185 |
186 | td = tr.append('td')
187 | .classed('table-' + td_type, true)
188 | .classed('table-' + td_type + '-' + this._strip_punctuation(td_accessor), true)
189 | .attr('data-value', td_value)
190 | .style('width', this_column.width)
191 | .style('text-align', td_type === 'title' || td_type === 'text' ? 'left' : 'right');
192 |
193 | this._format_element(td, td_value, this_column);
194 |
195 | if (td_type === 'title') {
196 | this_title = td.append('div').text(td_text);
197 | this._format_element(this_title, td_text, this_column);
198 |
199 | if (args.columns[j].hasOwnProperty('secondary_accessor')) {
200 | td.append('div')
201 | .text(args.data[i][args.columns[j].secondary_accessor])
202 | .classed("secondary-title", true);
203 | }
204 | } else {
205 | td.text(td_text);
206 | }
207 | }
208 | }
209 |
210 | return this;
211 | };
212 |
213 | return this;
214 | };
215 |
--------------------------------------------------------------------------------
/examples/data/brief-1.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "date": "2014-02-01",
4 | "value": 15000000
5 | },
6 | {
7 | "date": "2014-02-02",
8 | "value": 16487625
9 | },
10 | {
11 | "date": "2014-02-03",
12 | "value": 17097434
13 | },
14 | {
15 | "date": "2014-02-04",
16 | "value": 17694420
17 | },
18 | {
19 | "date": "2014-02-05",
20 | "value": 17014381
21 | },
22 | {
23 | "date": "2014-02-06",
24 | "value": 15578984
25 | },
26 | {
27 | "date": "2014-02-07",
28 | "value": 14718953
29 | },
30 | {
31 | "date": "2014-02-08",
32 | "value": 15020669
33 | },
34 | {
35 | "date": "2014-02-09",
36 | "value": 13889722
37 | },
38 | {
39 | "date": "2014-02-10",
40 | "value": 13979897
41 | },
42 | {
43 | "date": "2014-02-11",
44 | "value": 14595566
45 | },
46 | {
47 | "date": "2014-02-12",
48 | "value": 14123256
49 | },
50 | {
51 | "date": "2014-02-13",
52 | "value": 14083675
53 | },
54 | {
55 | "date": "2014-02-14",
56 | "value": 15068426
57 | },
58 | {
59 | "date": "2014-02-15",
60 | "value": 15368056
61 | },
62 | {
63 | "date": "2014-02-16",
64 | "value": 15277054
65 | },
66 | {
67 | "date": "2014-02-17",
68 | "value": 14376243
69 | },
70 | {
71 | "date": "2014-02-18",
72 | "value": 13058892
73 | },
74 | {
75 | "date": "2014-02-19",
76 | "value": 12367653
77 | },
78 | {
79 | "date": "2014-02-20",
80 | "value": 13184423
81 | },
82 | {
83 | "date": "2014-02-21",
84 | "value": 14367203
85 | },
86 | {
87 | "date": "2014-02-22",
88 | "value": 14656447
89 | },
90 | {
91 | "date": "2014-02-23",
92 | "value": 14724526
93 | },
94 | {
95 | "date": "2014-02-24",
96 | "value": 14938129
97 | },
98 | {
99 | "date": "2014-02-25",
100 | "value": 14205617
101 | },
102 | {
103 | "date": "2014-02-26",
104 | "value": 14596607
105 | },
106 | {
107 | "date": "2014-02-27",
108 | "value": 13982597
109 | },
110 | {
111 | "date": "2014-02-28",
112 | "value": 15107034
113 | },
114 | {
115 | "date": "2014-03-01",
116 | "value": 13646739
117 | },
118 | {
119 | "date": "2014-03-02",
120 | "value": 14214763
121 | },
122 | {
123 | "date": "2014-03-03",
124 | "value": 14952136
125 | },
126 | {
127 | "date": "2014-03-04",
128 | "value": 14643933
129 | },
130 | {
131 | "date": "2014-03-05",
132 | "value": 13611435
133 | },
134 | {
135 | "date": "2014-03-06",
136 | "value": 12569788
137 | },
138 | {
139 | "date": "2014-03-07",
140 | "value": 11344469
141 | },
142 | {
143 | "date": "2014-03-08",
144 | "value": 12687132
145 | },
146 | {
147 | "date": "2014-03-09",
148 | "value": 11831144
149 | },
150 | {
151 | "date": "2014-03-10",
152 | "value": 10480837
153 | },
154 | {
155 | "date": "2014-03-11",
156 | "value": 9051161
157 | },
158 | {
159 | "date": "2014-03-12",
160 | "value": 9964784
161 | },
162 | {
163 | "date": "2014-03-13",
164 | "value": 11035006
165 | },
166 | {
167 | "date": "2014-03-14",
168 | "value": 10081289
169 | },
170 | {
171 | "date": "2014-03-15",
172 | "value": 9793897
173 | },
174 | {
175 | "date": "2014-03-16",
176 | "value": 9177447
177 | },
178 | {
179 | "date": "2014-03-17",
180 | "value": 8035348
181 | },
182 | {
183 | "date": "2014-03-18",
184 | "value": 6770242
185 | },
186 | {
187 | "date": "2014-03-19",
188 | "value": 7272077
189 | },
190 | {
191 | "date": "2014-03-20",
192 | "value": 8216348
193 | },
194 | {
195 | "date": "2014-03-21",
196 | "value": 8576584
197 | },
198 | {
199 | "date": "2014-03-22",
200 | "value": 9421060
201 | },
202 | {
203 | "date": "2014-03-23",
204 | "value": 10872288
205 | },
206 | {
207 | "date": "2014-03-24",
208 | "value": 9537996
209 | },
210 | {
211 | "date": "2014-03-25",
212 | "value": 9560363
213 | },
214 | {
215 | "date": "2014-03-26",
216 | "value": 8182813
217 | },
218 | {
219 | "date": "2014-03-27",
220 | "value": 9068173
221 | },
222 | {
223 | "date": "2014-03-28",
224 | "value": 10390251
225 | },
226 | {
227 | "date": "2014-03-29",
228 | "value": 9714081
229 | },
230 | {
231 | "date": "2014-03-30",
232 | "value": 9994670
233 | },
234 | {
235 | "date": "2014-03-31",
236 | "value": 9317878
237 | },
238 | {
239 | "date": "2014-04-01",
240 | "value": 8209077
241 | },
242 | {
243 | "date": "2014-04-02",
244 | "value": 9230830
245 | },
246 | {
247 | "date": "2014-04-03",
248 | "value": 8978342
249 | },
250 | {
251 | "date": "2014-04-04",
252 | "value": 8361854
253 | },
254 | {
255 | "date": "2014-04-05",
256 | "value": 9345999
257 | },
258 | {
259 | "date": "2014-04-06",
260 | "value": 7965407
261 | },
262 | {
263 | "date": "2014-04-07",
264 | "value": 8909276
265 | },
266 | {
267 | "date": "2014-04-08",
268 | "value": 8935489
269 | },
270 | {
271 | "date": "2014-04-09",
272 | "value": 8634997
273 | },
274 | {
275 | "date": "2014-04-10",
276 | "value": 8795592
277 | },
278 | {
279 | "date": "2014-04-11",
280 | "value": 7513086
281 | },
282 | {
283 | "date": "2014-04-12",
284 | "value": 8408561
285 | },
286 | {
287 | "date": "2014-04-13",
288 | "value": 7780649
289 | },
290 | {
291 | "date": "2014-04-14",
292 | "value": 7524281
293 | },
294 | {
295 | "date": "2014-04-15",
296 | "value": 8498062
297 | },
298 | {
299 | "date": "2014-04-16",
300 | "value": 7922453
301 | },
302 | {
303 | "date": "2014-04-17",
304 | "value": 9304312
305 | },
306 | {
307 | "date": "2014-04-18",
308 | "value": 8199457
309 | },
310 | {
311 | "date": "2014-04-19",
312 | "value": 8926136
313 | },
314 | {
315 | "date": "2014-04-20",
316 | "value": 7558184
317 | },
318 | {
319 | "date": "2014-04-21",
320 | "value": 6417511
321 | },
322 | {
323 | "date": "2014-04-22",
324 | "value": 5748831
325 | },
326 | {
327 | "date": "2014-04-23",
328 | "value": 6503022
329 | },
330 | {
331 | "date": "2014-04-24",
332 | "value": 6429606
333 | },
334 | {
335 | "date": "2014-04-25",
336 | "value": 5057410
337 | },
338 | {
339 | "date": "2014-04-26",
340 | "value": 5924669
341 | },
342 | {
343 | "date": "2014-04-27",
344 | "value": 4728239
345 | },
346 | {
347 | "date": "2014-04-28",
348 | "value": 3918540
349 | },
350 | {
351 | "date": "2014-04-29",
352 | "value": 2821473
353 | },
354 | {
355 | "date": "2014-04-30",
356 | "value": 1995781
357 | },
358 | {
359 | "date": "2014-05-01",
360 | "value": 1123626
361 | },
362 | {
363 | "date": "2014-05-02",
364 | "value": 516067
365 | },
366 | {
367 | "date": "2014-05-03",
368 | "value": 816831
369 | },
370 | {
371 | "date": "2014-05-04",
372 | "value": 816831
373 | },
374 | {
375 | "date": "2014-05-05",
376 | "value": 816831
377 | },
378 | {
379 | "date": "2014-05-06",
380 | "value": 1103818
381 | },
382 | {
383 | "date": "2014-05-07",
384 | "value": 958188
385 | },
386 | {
387 | "date": "2014-05-08",
388 | "value": 592995
389 | },
390 | {
391 | "date": "2014-05-09",
392 | "value": 856066
393 | },
394 | {
395 | "date": "2014-05-10",
396 | "value": 1766761
397 | },
398 | {
399 | "date": "2014-05-11",
400 | "value": 1330557
401 | }
402 | ]
--------------------------------------------------------------------------------
/examples/data/some_currency.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "date": "2014-02-01",
4 | "value": 15000000
5 | },
6 | {
7 | "date": "2014-02-02",
8 | "value": 16487625
9 | },
10 | {
11 | "date": "2014-02-03",
12 | "value": 17097434
13 | },
14 | {
15 | "date": "2014-02-04",
16 | "value": 17694420
17 | },
18 | {
19 | "date": "2014-02-05",
20 | "value": 17014381
21 | },
22 | {
23 | "date": "2014-02-06",
24 | "value": 15578984
25 | },
26 | {
27 | "date": "2014-02-07",
28 | "value": 14718953
29 | },
30 | {
31 | "date": "2014-02-08",
32 | "value": 15020669
33 | },
34 | {
35 | "date": "2014-02-09",
36 | "value": 13889722
37 | },
38 | {
39 | "date": "2014-02-10",
40 | "value": 13979897
41 | },
42 | {
43 | "date": "2014-02-11",
44 | "value": 14595566
45 | },
46 | {
47 | "date": "2014-02-12",
48 | "value": 14123256
49 | },
50 | {
51 | "date": "2014-02-13",
52 | "value": 14083675
53 | },
54 | {
55 | "date": "2014-02-14",
56 | "value": 15068426
57 | },
58 | {
59 | "date": "2014-02-15",
60 | "value": 15368056
61 | },
62 | {
63 | "date": "2014-02-16",
64 | "value": 15277054
65 | },
66 | {
67 | "date": "2014-02-17",
68 | "value": 14376243
69 | },
70 | {
71 | "date": "2014-02-18",
72 | "value": 13058892
73 | },
74 | {
75 | "date": "2014-02-19",
76 | "value": 12367653
77 | },
78 | {
79 | "date": "2014-02-20",
80 | "value": 13184423
81 | },
82 | {
83 | "date": "2014-02-21",
84 | "value": 14367203
85 | },
86 | {
87 | "date": "2014-02-22",
88 | "value": 14656447
89 | },
90 | {
91 | "date": "2014-02-23",
92 | "value": 14724526
93 | },
94 | {
95 | "date": "2014-02-24",
96 | "value": 14938129
97 | },
98 | {
99 | "date": "2014-02-25",
100 | "value": 14205617
101 | },
102 | {
103 | "date": "2014-02-26",
104 | "value": 14596607
105 | },
106 | {
107 | "date": "2014-02-27",
108 | "value": 13982597
109 | },
110 | {
111 | "date": "2014-02-28",
112 | "value": 15107034
113 | },
114 | {
115 | "date": "2014-03-01",
116 | "value": 13646739
117 | },
118 | {
119 | "date": "2014-03-02",
120 | "value": 14214763
121 | },
122 | {
123 | "date": "2014-03-03",
124 | "value": 14952136
125 | },
126 | {
127 | "date": "2014-03-04",
128 | "value": 14643933
129 | },
130 | {
131 | "date": "2014-03-05",
132 | "value": 13611435
133 | },
134 | {
135 | "date": "2014-03-06",
136 | "value": 12569788
137 | },
138 | {
139 | "date": "2014-03-07",
140 | "value": 11344469
141 | },
142 | {
143 | "date": "2014-03-08",
144 | "value": 12687132
145 | },
146 | {
147 | "date": "2014-03-09",
148 | "value": 11831144
149 | },
150 | {
151 | "date": "2014-03-10",
152 | "value": 10480837
153 | },
154 | {
155 | "date": "2014-03-11",
156 | "value": 9051161
157 | },
158 | {
159 | "date": "2014-03-12",
160 | "value": 9964784
161 | },
162 | {
163 | "date": "2014-03-13",
164 | "value": 11035006
165 | },
166 | {
167 | "date": "2014-03-14",
168 | "value": 10081289
169 | },
170 | {
171 | "date": "2014-03-15",
172 | "value": 9793897
173 | },
174 | {
175 | "date": "2014-03-16",
176 | "value": 9177447
177 | },
178 | {
179 | "date": "2014-03-17",
180 | "value": 8035348
181 | },
182 | {
183 | "date": "2014-03-18",
184 | "value": 6770242
185 | },
186 | {
187 | "date": "2014-03-19",
188 | "value": 7272077
189 | },
190 | {
191 | "date": "2014-03-20",
192 | "value": 8216348
193 | },
194 | {
195 | "date": "2014-03-21",
196 | "value": 8576584
197 | },
198 | {
199 | "date": "2014-03-22",
200 | "value": 9421060
201 | },
202 | {
203 | "date": "2014-03-23",
204 | "value": 10872288
205 | },
206 | {
207 | "date": "2014-03-24",
208 | "value": 9537996
209 | },
210 | {
211 | "date": "2014-03-25",
212 | "value": 9560363
213 | },
214 | {
215 | "date": "2014-03-26",
216 | "value": 8182813
217 | },
218 | {
219 | "date": "2014-03-27",
220 | "value": 9068173
221 | },
222 | {
223 | "date": "2014-03-28",
224 | "value": 10390251
225 | },
226 | {
227 | "date": "2014-03-29",
228 | "value": 9714081
229 | },
230 | {
231 | "date": "2014-03-30",
232 | "value": 9994670
233 | },
234 | {
235 | "date": "2014-03-31",
236 | "value": 9317878
237 | },
238 | {
239 | "date": "2014-04-01",
240 | "value": 8209077
241 | },
242 | {
243 | "date": "2014-04-02",
244 | "value": 9230830
245 | },
246 | {
247 | "date": "2014-04-03",
248 | "value": 8978342
249 | },
250 | {
251 | "date": "2014-04-04",
252 | "value": 8361854
253 | },
254 | {
255 | "date": "2014-04-05",
256 | "value": 9345999
257 | },
258 | {
259 | "date": "2014-04-06",
260 | "value": 7965407
261 | },
262 | {
263 | "date": "2014-04-07",
264 | "value": 8909276
265 | },
266 | {
267 | "date": "2014-04-08",
268 | "value": 8935489
269 | },
270 | {
271 | "date": "2014-04-09",
272 | "value": 8634997
273 | },
274 | {
275 | "date": "2014-04-10",
276 | "value": 8795592
277 | },
278 | {
279 | "date": "2014-04-11",
280 | "value": 7513086
281 | },
282 | {
283 | "date": "2014-04-12",
284 | "value": 8408561
285 | },
286 | {
287 | "date": "2014-04-13",
288 | "value": 7780649
289 | },
290 | {
291 | "date": "2014-04-14",
292 | "value": 7524281
293 | },
294 | {
295 | "date": "2014-04-15",
296 | "value": 8498062
297 | },
298 | {
299 | "date": "2014-04-16",
300 | "value": 7922453
301 | },
302 | {
303 | "date": "2014-04-17",
304 | "value": 9304312
305 | },
306 | {
307 | "date": "2014-04-18",
308 | "value": 8199457
309 | },
310 | {
311 | "date": "2014-04-19",
312 | "value": 8926136
313 | },
314 | {
315 | "date": "2014-04-20",
316 | "value": 7558184
317 | },
318 | {
319 | "date": "2014-04-21",
320 | "value": 6417511
321 | },
322 | {
323 | "date": "2014-04-22",
324 | "value": 5748831
325 | },
326 | {
327 | "date": "2014-04-23",
328 | "value": 6503022
329 | },
330 | {
331 | "date": "2014-04-24",
332 | "value": 6429606
333 | },
334 | {
335 | "date": "2014-04-25",
336 | "value": 5057410
337 | },
338 | {
339 | "date": "2014-04-26",
340 | "value": 5924669
341 | },
342 | {
343 | "date": "2014-04-27",
344 | "value": 4728239
345 | },
346 | {
347 | "date": "2014-04-28",
348 | "value": 3918540
349 | },
350 | {
351 | "date": "2014-04-29",
352 | "value": 2821473
353 | },
354 | {
355 | "date": "2014-04-30",
356 | "value": 1995781
357 | },
358 | {
359 | "date": "2014-05-01",
360 | "value": 1123626
361 | },
362 | {
363 | "date": "2014-05-02",
364 | "value": 516067
365 | },
366 | {
367 | "date": "2014-05-03",
368 | "value": 816831
369 | },
370 | {
371 | "date": "2014-05-04",
372 | "value": 816831
373 | },
374 | {
375 | "date": "2014-05-05",
376 | "value": 816831
377 | },
378 | {
379 | "date": "2014-05-06",
380 | "value": 1103818
381 | },
382 | {
383 | "date": "2014-05-07",
384 | "value": 958188
385 | },
386 | {
387 | "date": "2014-05-08",
388 | "value": 592995
389 | },
390 | {
391 | "date": "2014-05-09",
392 | "value": 856066
393 | },
394 | {
395 | "date": "2014-05-10",
396 | "value": 1766761
397 | },
398 | {
399 | "date": "2014-05-11",
400 | "value": 1330557
401 | }
402 | ]
--------------------------------------------------------------------------------
/src/js/charts/point.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | function pointChart(args) {
5 |
6 | this.init = function(args) {
7 | this.args = args;
8 |
9 | raw_data_transformation(args);
10 | process_point(args);
11 | init(args);
12 | x_axis(args);
13 | y_axis(args);
14 |
15 | this.mainPlot();
16 | this.markers();
17 | this.rollover();
18 | this.windowListeners();
19 |
20 | return this;
21 | };
22 |
23 | this.markers = function() {
24 | markers(args);
25 | if (args.least_squares) {
26 | add_ls(args);
27 | }
28 |
29 | return this;
30 | };
31 |
32 | this.mainPlot = function() {
33 | var svg = mg_get_svg_child_of(args.target);
34 | var g;
35 |
36 | //remove the old points, add new one
37 | svg.selectAll('.mg-points').remove();
38 |
39 | // plot the points, pretty straight-forward
40 | g = svg.append('g')
41 | .classed('mg-points', true);
42 |
43 | var pts = g.selectAll('circle')
44 | .data(args.data[0])
45 | .enter().append('svg:circle')
46 | .attr('class', function(d, i) { return 'path-' + i; })
47 | .attr('cx', args.scalefns.xf)
48 | .attr('cy', args.scalefns.yf);
49 |
50 | //are we coloring our points, or just using the default color?
51 | if (args.color_accessor !== null) {
52 | pts.attr('fill', args.scalefns.color);
53 | pts.attr('stroke', args.scalefns.color);
54 | } else {
55 | pts.classed('mg-points-mono', true);
56 | }
57 |
58 | if (args.size_accessor !== null) {
59 | pts.attr('r', args.scalefns.size);
60 | } else {
61 | pts.attr('r', args.point_size);
62 | }
63 |
64 | return this;
65 | };
66 |
67 | this.rollover = function() {
68 | var svg = mg_get_svg_child_of(args.target);
69 |
70 | //remove the old rollovers if they already exist
71 | svg.selectAll('.mg-voronoi').remove();
72 |
73 | //remove the old rollover text and circle if they already exist
74 | svg.selectAll('.mg-active-datapoint').remove();
75 |
76 | //add rollover text
77 | svg.append('text')
78 | .attr('class', 'mg-active-datapoint')
79 | .attr('xml:space', 'preserve')
80 | .attr('x', args.width - args.right)
81 | .attr('y', args.top * 0.75)
82 | .attr('text-anchor', 'end');
83 |
84 | //add rollover paths
85 | var voronoi = d3.geom.voronoi()
86 | .x(args.scalefns.xf)
87 | .y(args.scalefns.yf)
88 | .clipExtent([[args.buffer, args.buffer + args.title_y_position], [args.width - args.buffer, args.height - args.buffer]]);
89 |
90 | var paths = svg.append('g')
91 | .attr('class', 'mg-voronoi');
92 |
93 | paths.selectAll('path')
94 | .data(voronoi(args.data[0]))
95 | .enter().append('path')
96 | .attr('d', function(d) {
97 | if (d === undefined) {
98 | return;
99 | }
100 |
101 | return 'M' + d.join(',') + 'Z';
102 | })
103 | .attr('class', function(d,i) {
104 | return 'path-' + i;
105 | })
106 | .style('fill-opacity', 0)
107 | .on('mouseover', this.rolloverOn(args))
108 | .on('mouseout', this.rolloverOff(args))
109 | .on('mousemove', this.rolloverMove(args));
110 |
111 | return this;
112 | };
113 |
114 | this.rolloverOn = function(args) {
115 | var svg = mg_get_svg_child_of(args.target);
116 |
117 | return function(d, i) {
118 | svg.selectAll('.mg-points circle')
119 | .classed('selected', false);
120 |
121 | //highlight active point
122 | var pts = svg.selectAll('.mg-points circle.path-' + i)
123 | .classed('selected', true);
124 |
125 | if (args.size_accessor) {
126 | pts.attr('r', function(di) {
127 | return args.scalefns.size(di) + 1;
128 | });
129 | } else {
130 | pts.attr('r', args.point_size);
131 | }
132 |
133 | //trigger mouseover on all points for this class name in .linked charts
134 | if (args.linked && !MG.globals.link) {
135 | MG.globals.link = true;
136 |
137 | //trigger mouseover on matching point in .linked charts
138 | d3.selectAll('.mg-voronoi .path-' + i)
139 | .each(function() {
140 | d3.select(this).on('mouseover')(d,i);
141 | });
142 | }
143 |
144 | var fmt = MG.time_format(args.utc_time, '%b %e, %Y');
145 | var num = format_rollover_number(args);
146 |
147 | //update rollover text
148 | if (args.show_rollover_text) {
149 | svg.select('.mg-active-datapoint')
150 | .text(function() {
151 | if (args.time_series) {
152 | var dd = new Date(+d.point[args.x_accessor]);
153 | dd.setDate(dd.getDate());
154 |
155 | return fmt(dd) + ' ' + args.yax_units + num(d.point[args.y_accessor]);
156 | } else {
157 | return args.x_accessor + ': ' + num(d.point[args.x_accessor])
158 | + ', ' + args.y_accessor + ': ' + args.yax_units
159 | + num(d.point[args.y_accessor]);
160 | }
161 | });
162 | }
163 |
164 | if (args.mouseover) {
165 | args.mouseover(d, i);
166 | }
167 | };
168 | };
169 |
170 | this.rolloverOff = function(args) {
171 | var svg = mg_get_svg_child_of(args.target);
172 |
173 | return function(d,i) {
174 | if (args.linked && MG.globals.link) {
175 | MG.globals.link = false;
176 |
177 | d3.selectAll('.mg-voronoi .path-' + i)
178 | .each(function() {
179 | d3.select(this).on('mouseout')(d,i);
180 | });
181 | }
182 |
183 | //reset active point
184 | var pts = svg.selectAll('.mg-points circle')
185 | .classed('unselected', false)
186 | .classed('selected', false);
187 |
188 | if (args.size_accessor) {
189 | pts.attr('r', args.scalefns.size);
190 | } else {
191 | pts.attr('r', args.point_size);
192 | }
193 |
194 | //reset active data point text
195 | svg.select('.mg-active-datapoint')
196 | .text('');
197 |
198 | if (args.mouseout) {
199 | args.mouseout(d, i);
200 | }
201 | };
202 | };
203 |
204 | this.rolloverMove = function(args) {
205 | return function(d, i) {
206 | if (args.mousemove) {
207 | args.mousemove(d, i);
208 | }
209 | };
210 | };
211 |
212 | this.update = function(args) {
213 | return this;
214 | };
215 |
216 | this.windowListeners = function() {
217 | mg_window_listeners(this.args);
218 | return this;
219 | };
220 |
221 | this.init(args);
222 | }
223 |
224 | var defaults = {
225 | buffer: 16,
226 | ls: false,
227 | lowess: false,
228 | point_size: 2.5,
229 | size_accessor: null,
230 | color_accessor: null,
231 | size_range: null, // when we set a size_accessor option, this array determines the size range, e.g. [1,5]
232 | color_range: null, // e.g. ['blue', 'red'] to color different groups of points
233 | size_domain: null,
234 | color_domain: null,
235 | color_type: 'number' // can be either 'number' - the color scale is quantitative - or 'category' - the color scale is qualitative.
236 | };
237 |
238 | MG.register('point', pointChart, defaults);
239 | }).call(this);
240 |
--------------------------------------------------------------------------------
/tests/common/init_test.js:
--------------------------------------------------------------------------------
1 | module('init');
2 |
3 | test('MG properly detects time series vs. not.', function () {
4 | var params1 = {
5 | target: '#qunit-fixture',
6 | data: [{'date': new Date('2014-11-01'), 'value': 12},
7 | {'date': new Date('2014-11-02'), 'value': 18}],
8 | x_accessor: 'date'
9 | };
10 |
11 | var params2 = {
12 | target: '#qunit-fixture',
13 | data: [{'date': 5434, 'value': 12},
14 | {'date': 5435, 'value': 18}],
15 | x_accessor: 'date'
16 | };
17 |
18 | var params3 = {
19 | target: '#qunit-fixture',
20 | data: [[{'date': new Date('2014-11-01'), 'value': 12},
21 | {'date': new Date('2014-11-02'), 'value': 18}],
22 | [{'date': new Date('2014-11-01'), 'value': 32},
23 | {'date': new Date('2014-11-02'), 'value': 43}]],
24 | x_accessor: 'date'
25 | };
26 | mg_merge_args_with_defaults(params1);
27 | mg_merge_args_with_defaults(params2);
28 | mg_merge_args_with_defaults(params3);
29 | mg_is_time_series(params1);
30 | mg_is_time_series(params2);
31 | mg_is_time_series(params3);
32 |
33 | ok(params1.time_series === true, 'Date-accessed data set is a time series.');
34 | ok(params2.time_series === false, 'Number-accessed data set is not a time series.');
35 | ok(params3.time_series === true, 'Nested data set w/ dates detected as time series.');
36 | });
37 |
38 | test("Chart's width is set correctly on subsequent calls to existing chart", function () {
39 | var params_0 = {
40 | target: '#qunit-fixture',
41 | data: [{'date': new Date('2014-11-01'), 'value': 12},
42 | {'date': new Date('2014-11-02'), 'value': 18}],
43 | };
44 |
45 | var params = {
46 | target: '#qunit-fixture',
47 | data: [{'date': new Date('2014-11-01'), 'value': 12},
48 | {'date': new Date('2014-11-02'), 'value': 18}],
49 | width: 200,
50 | height: 100,
51 | };
52 |
53 | MG.data_graphic(params_0);
54 | MG.data_graphic(params);
55 |
56 | var width = document.querySelector(params.target + ' svg').offsetWidth;
57 | ok(width === 200, "SVG's width matches latest specified width");
58 | });
59 |
60 | test("Chart's width is set to parents if full_width: true", function () {
61 | var params = {
62 | target: '#qunit-fixture',
63 | full_width: true,
64 | data: [{'date': new Date('2014-11-01'), 'value': 12},
65 | {'date': new Date('2014-11-02'), 'value': 18}],
66 | height: 100
67 | };
68 | MG.data_graphic(params);
69 |
70 | var svg_width = document.querySelector(params.target + ' svg').offsetWidth;
71 | var div_width = document.querySelector(params.target).offsetWidth;
72 |
73 | equal(div_width, svg_width, "SVG's width matches parent upon using full_width: true");
74 | });
75 |
76 | test("Chart's height is set to parents if full_height: true", function () {
77 | var params = {
78 | target: '#qunit-fixture',
79 | full_height: true,
80 | data: [{'date': new Date('2014-11-01'), 'value': 12},
81 | {'date': new Date('2014-11-02'), 'value': 18}],
82 | width: 500
83 | };
84 |
85 | document.querySelector(params.target).style.height = '500px';
86 | MG.data_graphic(params);
87 |
88 | var svg_height = document.querySelector(params.target + ' svg').offsetHeight;
89 | var div_height = document.querySelector(params.target).offsetHeight;
90 |
91 | equal(div_height, svg_height, "SVG's height matches parent upon using full_height: true");
92 | });
93 |
94 | test("Won't add SVG element if an SVG element already exists in parent.", function () {
95 | var args1 = {
96 | target: '#qunit-fixture div#exists',
97 | width: 500,
98 | height: 200,
99 | linked: false,
100 | svg: 'FLAG'
101 | };
102 |
103 | var qunit = document.querySelector('#qunit-fixture');
104 | var div = document.createElement('div');
105 | div.id = 'exists';
106 | div.appendChild(document.createElement('svg'));
107 | qunit.appendChild(div);
108 | var first_number = document.querySelectorAll('svg').length;
109 | mg_add_svg_if_it_doesnt_exist('', args1);
110 | var second_number = document.querySelectorAll('svg').length;
111 | equal(first_number, second_number, 'SVG element not added if it already exists.');
112 | });
113 |
114 | test("Chart's height is set correctly on subsequent calls to existing chart", function () {
115 | var params_0 = {
116 | target: '#qunit-fixture',
117 | data: [{'date': new Date('2014-11-01'), 'value': 12},
118 | {'date': new Date('2014-11-02'), 'value': 18}],
119 | };
120 |
121 | var params = {
122 | target: '#qunit-fixture',
123 | data: [{'date': new Date('2014-11-01'), 'value': 12},
124 | {'date': new Date('2014-11-02'), 'value': 18}],
125 | width: 200,
126 | height: 100,
127 | };
128 |
129 | MG.data_graphic(params_0);
130 | MG.data_graphic(params);
131 |
132 | var height = document.querySelector(params.target + ' svg').offsetHeight;
133 | ok(height == params.height, "SVG's height matches latest specified height");
134 | });
135 |
136 | test('Charts are plotted correctly when MG is called multiple times on the same target element', function () {
137 | var params_0 = {
138 | target: '#qunit-fixture',
139 | data: [{'date': new Date('2014-11-01'), 'value': 12},
140 | {'date': new Date('2014-11-02'), 'value': 18}]
141 | };
142 |
143 | var params = {
144 | target: '#qunit-fixture',
145 | data: [{'date': new Date('2014-11-01'), 'value': 12},
146 | {'date': new Date('2014-11-02'), 'value': 18}],
147 | width: 200,
148 | height: 100,
149 | };
150 |
151 | MG.data_graphic(params_0);
152 | MG.data_graphic(params);
153 |
154 | // ensure chart types change appropriately
155 | var line = document.querySelector('.mg-main-line');
156 | ok(line, 'chart_type is `line`, line chart is plotted');
157 |
158 | // check all the other chart types
159 | var chart_types = [{id: 'point', domElement: '.mg-points'},
160 | {id: 'histogram', domElement: '.mg-histogram'},
161 | {id: 'bar', domElement: '.mg-barplot'}];
162 |
163 | for (var i = 0; i < chart_types.length; i++) {
164 | var params = {
165 | target: '#qunit-fixture',
166 | data: [{'date': new Date('2014-11-01'), 'value': 12},
167 | {'date': new Date('2014-11-02'), 'value': 18}],
168 | chart_type: chart_types[i].id,
169 | width: 200,
170 | height: 100,
171 | };
172 |
173 | MG.data_graphic(params);
174 | ok(document.querySelector(chart_types[i].domElement),
175 | 'chart_type switched to `' + chart_types[i].id + '`, the correct chart type is plotted');
176 |
177 | // ensure old chart was removed
178 | equal(document.querySelectorAll('.mg-main-line').length, 0, 'line chart (old one) was removed');
179 | }
180 | });
181 |
182 | test('Missing chart has required class name set', function () {
183 | expect(1);
184 | var params = {
185 | target: '#qunit-fixture',
186 | data: [{'date': new Date('2014-11-01'), 'value': 12},
187 | {'date': new Date('2014-11-02'), 'value': 18}],
188 | chart_type: 'missing-data'
189 | };
190 |
191 | MG.data_graphic(params);
192 |
193 | var matches = document.querySelector(params.target + ' svg').getAttribute('class').match(/mg-missing/);
194 | ok(matches, 'Missing chart has class `missing` set');
195 | });
196 |
197 | test('Linked chart has the required class set', function () {
198 | var params = {
199 | target: '#qunit-fixture',
200 | data: [{'date': new Date('2014-11-01'), 'value': 12},
201 | {'date': new Date('2014-11-02'), 'value': 18}],
202 | linked: true
203 | };
204 |
205 | MG.data_graphic(params);
206 |
207 | var matches = document.querySelector(params.target + ' svg').getAttribute('class').match(/linked/);
208 | ok(matches, 'Linked chart has class `linked` set');
209 | });
210 |
211 | test('args.time_series is set to true when data is time-series', function () {
212 | var params = {
213 | target: '#qunit-fixture',
214 | data: [{'foo': new Date('2014-11-01'), 'value': 12},
215 | {'foo': new Date('2014-11-02'), 'value': 18}],
216 | x_accessor: 'foo'
217 | };
218 |
219 | MG.data_graphic(params);
220 | ok(params.time_series, 'args.time_series is set to true when data is time-series');
221 | });
222 |
223 | test('args.time_series is set to false when data is not time-series', function () {
224 | var params = {
225 | target: '#qunit-fixture',
226 | data: [{'foo': 100, 'value': 12},
227 | {'foo': 200, 'value': 18}],
228 | x_accessor: 'foo'
229 | };
230 |
231 | MG.data_graphic(params);
232 | equal(params.time_series, false, 'args.time_series is set to false when data is not time-series');
233 | });
234 |
235 | test('Only one clip path is added on multiple calls to the same target element', function () {
236 | var params = {
237 | target: '#qunit-fixture',
238 | data: [{'date': new Date('2014-01-01'), 'value': 12, 'l': 10, 'u': 14},
239 | {'date': new Date('2014-03-01'), 'value': 18, 'l': 16, 'u': 20}]
240 | };
241 |
242 | MG.data_graphic(params);
243 | MG.data_graphic(MG.clone(params));
244 |
245 | equal(document.querySelectorAll('.mg-clip-path').length, 1, 'We only have one clip path');
246 | });
247 |
--------------------------------------------------------------------------------