├── .bowerrc
├── .editorconfig
├── .ember-cli
├── .eslintrc.js
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .npmignore
├── .travis.yml
├── .watchmanconfig
├── ARCHITECTURE.md
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── addon
├── .gitkeep
├── components
│ ├── plaid-area
│ │ ├── component.js
│ │ └── template.hbs
│ ├── plaid-axis
│ │ └── component.js
│ ├── plaid-bar
│ │ └── component.js
│ ├── plaid-donut
│ │ ├── component.js
│ │ └── template.hbs
│ ├── plaid-line
│ │ ├── component.js
│ │ └── template.hbs
│ ├── plaid-plot.js
│ ├── plaid-scatter
│ │ ├── component.js
│ │ └── template.hbs
│ ├── plaid-symbol.js
│ └── plaid-text
│ │ ├── component.js
│ │ └── template.hbs
├── helpers
│ ├── area.js
│ ├── curve.js
│ ├── extent.js
│ ├── format-fn.js
│ ├── format.js
│ ├── linear-scale.js
│ └── pair-by.js
├── mixins
│ ├── coordinates.js
│ ├── dimensions.js
│ ├── global-resize.js
│ ├── group-element.js
│ └── plot-area.js
├── templates
│ └── components
│ │ ├── plaid-plot.hbs
│ │ └── plaid-symbol.hbs
└── utils
│ ├── box-expression.js
│ └── computed-extent.js
├── app
├── .gitkeep
├── components
│ ├── plaid-area
│ │ └── component.js
│ ├── plaid-axis
│ │ └── component.js
│ ├── plaid-bar
│ │ └── component.js
│ ├── plaid-donut
│ │ └── component.js
│ ├── plaid-line
│ │ └── component.js
│ ├── plaid-plot.js
│ ├── plaid-scatter
│ │ └── component.js
│ ├── plaid-symbol.js
│ └── plaid-text
│ │ └── component.js
├── helpers
│ ├── area.js
│ ├── curve.js
│ ├── extent.js
│ ├── format-fn.js
│ ├── format.js
│ ├── linear-scale.js
│ └── pair-by.js
└── utils
│ ├── box-expression.js
│ └── computed-extent.js
├── blueprints
├── .jshintrc
└── maximum-plaid
│ └── index.js
├── bower.json
├── config
├── ember-try.js
└── environment.js
├── ember-cli-build.js
├── index.js
├── logo
├── line-chart-screenshot.png
└── maximum-plaid-logo.png
├── maximum-plaid.sublime-project
├── package.json
├── testem.js
├── tests
├── .eslintrc.js
├── .jshintrc
├── dummy
│ ├── app
│ │ ├── app.js
│ │ ├── components
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ ├── .gitkeep
│ │ │ └── index.js
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── index.html
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── resolver.js
│ │ ├── router.js
│ │ ├── routes
│ │ │ ├── .gitkeep
│ │ │ └── index.js
│ │ ├── styles
│ │ │ └── app.scss
│ │ └── templates
│ │ │ ├── application.hbs
│ │ │ ├── charts
│ │ │ └── -line-chart.hbs
│ │ │ ├── components
│ │ │ └── .gitkeep
│ │ │ ├── index.hbs
│ │ │ └── loading.hbs
│ ├── config
│ │ └── environment.js
│ ├── data
│ │ └── time-series.json
│ └── public
│ │ ├── crossdomain.xml
│ │ └── robots.txt
├── helpers
│ ├── destroy-app.js
│ ├── module-for-acceptance.js
│ ├── resolver.js
│ └── start-app.js
├── index.html
├── integration
│ ├── .gitkeep
│ └── components
│ │ ├── plaid-area
│ │ └── component-test.js
│ │ ├── plaid-axis
│ │ └── component-test.js
│ │ ├── plaid-bar
│ │ └── component-test.js
│ │ ├── plaid-donut
│ │ └── component-test.js
│ │ ├── plaid-plot-test.js
│ │ ├── plaid-symbol-test.js
│ │ └── plaid-text
│ │ └── component-test.js
├── test-helper.js
└── unit
│ ├── .gitkeep
│ ├── helpers
│ ├── area-test.js
│ ├── curve-test.js
│ ├── extent-test.js
│ ├── format-fn-test.js
│ ├── format-test.js
│ ├── linear-scale-test.js
│ └── pair-by-test.js
│ ├── mixins
│ ├── coordinates-test.js
│ ├── dimensions-test.js
│ ├── global-resize-test.js
│ ├── group-element-test.js
│ └── plot-area-test.js
│ └── utils
│ ├── box-expression-test.js
│ └── computed-extent-test.js
└── vendor
└── .gitkeep
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/.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 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.hbs]
17 | insert_final_newline = false
18 |
19 | [*.{diff,md}]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parserOptions: {
4 | ecmaVersion: 7,
5 | sourceType: 'module'
6 | },
7 | extends: [
8 | 'eslint:recommended',
9 | 'plugin:ember-suave/recommended'
10 | ],
11 | env: {
12 | 'browser': true,
13 | 'es6': true
14 | },
15 | rules: {
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /connect.lock
14 | /coverage/*
15 | /libpeerconnection.log
16 | npm-debug.log
17 | testem.log
18 | .DS_Store
19 | *.sublime-workspace
20 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "ember-suave"
3 | }
4 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "-Promise"
6 | ],
7 | "browser": true,
8 | "boss": true,
9 | "curly": true,
10 | "debug": false,
11 | "devel": true,
12 | "eqeqeq": true,
13 | "evil": true,
14 | "forin": false,
15 | "immed": false,
16 | "laxbreak": false,
17 | "newcap": true,
18 | "noarg": true,
19 | "noempty": false,
20 | "nonew": false,
21 | "nomen": false,
22 | "onevar": false,
23 | "plusplus": false,
24 | "regexp": false,
25 | "undef": true,
26 | "sub": true,
27 | "strict": false,
28 | "white": false,
29 | "eqnull": true,
30 | "esversion": 6,
31 | "unused": true
32 | }
33 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /config/ember-try.js
3 | /dist
4 | /tests
5 | /tmp
6 | **/.gitkeep
7 | .bowerrc
8 | .editorconfig
9 | .ember-cli
10 | .gitignore
11 | .jshintrc
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "stable"
5 |
6 | sudo: false
7 |
8 | cache:
9 | directories:
10 | - $HOME/.npm
11 | - $HOME/.cache # includes bowers cache
12 |
13 | env:
14 | # we recommend testing LTS's and latest stable release (bonus points to beta/canary)
15 | - EMBER_TRY_SCENARIO=ember-lts-2.4
16 | - EMBER_TRY_SCENARIO=ember-lts-2.8
17 | - EMBER_TRY_SCENARIO=ember-release
18 | - EMBER_TRY_SCENARIO=ember-beta
19 | - EMBER_TRY_SCENARIO=ember-canary
20 |
21 | matrix:
22 | fast_finish: true
23 | allow_failures:
24 | - env: EMBER_TRY_SCENARIO=ember-canary
25 |
26 | before_install:
27 | - npm config set spin false
28 | - npm install -g bower
29 | - bower --version
30 | - npm install phantomjs-prebuilt
31 | - node_modules/phantomjs-prebuilt/bin/phantomjs --version
32 |
33 | install:
34 | - npm install
35 | - bower install
36 |
37 | script:
38 | # Usually, it's ok to finish the test scenario without reverting
39 | # to the addon's original dependency state, skipping "cleanup".
40 | - ember try:one $EMBER_TRY_SCENARIO test --skip-cleanup
41 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/ARCHITECTURE.md:
--------------------------------------------------------------------------------
1 | # Maximum Plaid Architecture
2 |
3 | Maximum Plaid is a visualisation toolset, designed to be a consistent and easy to use high level grammar for specifying data visualisations. It is not designed to be a mapping directly to D3 or to SVG with nice Ember syntax sugar on top, instead it uses D3 as helpers for transforming and scaling data, and presenting the visualisation.
4 |
5 | To understand how this works, we must first break down a visualisation in to smaller building blocks or layers, based on the [Grammar of Graphics](http://www.springer.com/us/book/9780387245447):
6 |
7 | - Data `DATA`: A set of operations which create variables from datasets,
8 | - Transformations `TRANS`: Variable transformations to something our chart can render,
9 | - Scale `SCALE`: Scale transformations (linear, log, etc.),
10 | - Coordinates `COORD`: A coordinate system e.g. Polar, Cartesian Plane,
11 | - Elements `ELEMENT`: graphs (e.g., points) and their aesthetic attributes (e.g., color),
12 | - Guides `GUIDE`: one or more guides (axes, legends, etc.).
13 |
14 | We'll include two additional layers to make things more interesting:
15 |
16 | - Interaction `INT`: interaction controls (e.g zoom, select, pan),
17 | - Animation `ANIM`: animations as data transitions from one state to another to provide data constancy.
18 |
19 | These layers will be tied together to build charts consisting of the following types of Elements:
20 |
21 | - Lines
22 | - Areas
23 | - Pies / Donuts
24 | - Bars
25 | - Scatter plots
26 | - Stacked/Stream based layouts for bars and areas.
27 |
28 | D3 also provides methods for calculating Histograms from data to easily produce binned data for each bar, amongst other transformations.
29 |
30 | ## SVG or Canvas?
31 | On a technical level, D3 supports outputting path data for lines and areas for SVG, as well as calling the drawing APIs of `Canvas` to produce the same shapes in a Canvas context.
32 |
33 | Because of this, we’d like to make it a design goal of supporting both rendering contexts, and not produce a library which simply wraps Ember components around SVG elements. Of course if you’re exclusively doing an SVG chart you may prefer to rendering SVG elements for certain markings within your visualisation.
34 |
35 | ## Syntax
36 | Ember provides an incredibly powerful templating language with composable helpers and components.
37 |
38 | To best explain how this works with data visualisation, lets see an example:
39 |
40 | Assume we have some time series data:
41 |
42 | ```js
43 | let dataPoints = [{timestamp: 1460864483048, value: 1288}, …]
44 | ```
45 |
46 | We could draw a simple line chart like so:
47 |
48 | ```hbs
49 | {{#plaid-plot
50 | xScale=(linear-scale (extent (map-by 'timestamp' dataPoints)) (extent plotArea.width))
51 | yScale=(linear-scale (extent (map-by 'value' dataPoints)) (extent plotArea.height))
52 | as |plot|}}
53 |
54 | {{plot.line dataPoints}}
55 | {{/plaid-plot}}
56 | ```
57 |
58 | First we're initialising a `plot` component which will make it easy and straight forward to apply additional layers within this context. It requires an `xScale` and `yScale` to help position Elements and Guides. You can easily compute both scales using helpers for the respective scaling function you need.
59 |
60 | This is already supported. However, we'll often need more complexity than this. Let’s say we need the line to slide left when new data is appended to the `dataPoints` array. We could add transition support to the `line` component, but this might not always be the behaviour you want and it would limit the usefulness of the `line` component. So we need to _decorate_ this component with some more abilities.
61 |
62 | ```hbs
63 | {{#plaid-plot
64 | xScale=(linear-scale (extent (map-by 'timestamp' dataPoints)) (extent plotArea.width))
65 | yScale=(linear-scale (extent (map-by 'value' dataPoints)) (extent plotArea.height))
66 | as |plot|}}
67 |
68 | {{#plot.transition duration=250 easing=(ease-in-out) as |plot|}}
69 | {{plot.line dataPoints}}
70 | {{/plot.transition-group}}
71 | {{/plaid-plot}}
72 | ```
73 |
74 | Under the hood, if this line is rendering to SVG it will use D3 transition to interpolate its path data over `duration` from current value to future value, and apply that value for each tick of a timer.
75 |
76 | > Potentially this could use `liquid-fire` directly or learn some tricks for how this works and apply it to D3 and Ember.
77 |
78 | To draw an area chart similar to that seen in Skylight, we could use both an area and a line:
79 |
80 | ```hbs
81 | {{#plaid-plot
82 | xScale=(linear-scale (extent (map-by 'timestamp' dataPoints)) (extent plotArea.width))
83 | yScale=(linear-scale (extent (map-by 'value' dataPoints)) (extent plotArea.height))
84 | as |plot|}}
85 |
86 | {{#plot.transition duration=250 easing=(ease-in-out) as |plot|}}
87 | {{plot.area dataPoints fill="#CE93D8"}}
88 | {{plot.line dataPoints stroke="#9C27B0"}}
89 | {{/plot.transition-group}}
90 | {{/plaid-plot}}
91 | ```
92 |
93 | # Interaction
94 | Static charts aren't very useful in ambitious applications, which is why we need some way to interact with them.
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # maximum plaid changelog
2 |
3 | ### HEAD (Dec 06, 2016)
4 |
5 | - Updated to Ember 2.10
6 |
7 | ### 0.1.2 (May 30, 2016)
8 |
9 | - Version bumped because of NPM conflict.
10 |
11 | ### 0.1.1 (May 30, 2016)
12 |
13 | - [#7](https://github.com/ivanvanderbyl/maximum-plaid/pull/7) Upgraded to Ember CLI 2.5
14 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **Deprecated: Please see (and use) [ember-primer](https://github.com/ember-visualization/ember-primer) instead, as this is no longer maintained**
2 |
3 | 
4 |
5 | [](https://travis-ci.org/ivanvanderbyl/maximum-plaid)
6 | [](http://emberobserver.com/addons/maximum-plaid)
7 |
8 | Template driven data visualisation for ambitious applications.
9 |
10 | # Design
11 |
12 | Maximum Plaid is designed to be a high level visualisation library built exclusively for Ember. It uses Ember's templating language to efficiently and declaratively produce easily maintained and tested visualisations.
13 |
14 | We're building this because traditional charting libraries often times have poor separation of concerns from data and presentation, which leads to poor maintainability and code reuse, as well as exhibiting poor performance with medium to large datasets (10K to 100K data points).
15 |
16 | We desire something which exposes abstractions for efficiently expressing a wide variety of visualisations, while balancing expressiveness such that you're not locked in to one particular style of visualisation which doesn't quite express the discoveries you've made in your data.
17 |
18 | # Proposed API
19 |
20 | Visualisations are composed in layers, starting with data transformation down to visualisation and interaction.
21 |
22 | ## Data Transformation
23 |
24 | In order to provide a consistent API for each visualisation layer, we do the data setup ahead of time. After you've loaded your data source, you should transform it to the necessary shape for the type of visualisation you're rendering.
25 |
26 | We provide a few helpers to make this easy (more soon).
27 |
28 | ### Series Transforms
29 |
30 | # `pair-by`(attr, [attr2,] series)
31 |
32 | > Basically a multi-property `map-by`.
33 |
34 | Takes an array of objects containing discrete properties for each axis of your visualisations, and returns an array of tuples containing only the values for each attribute.
35 |
36 | Given the series:
37 |
38 | ```js
39 | let timeSeriesData = [
40 | { timestamp: 1450345920000, value: 1, projectId: 200 },
41 | { timestamp: 1450345930000, value: 2, projectId: 200 },
42 | { timestamp: 1450345940000, value: 3, projectId: 200 },
43 | { timestamp: 1450345950000, value: 4, projectId: 200 },
44 | { timestamp: 1450345960000, value: 5, projectId: 200 }
45 | ];
46 | ```
47 |
48 | Using `pair-by` in your template:
49 |
50 | ```hbs
51 | {{pair-by "timestamp" "value" timeSeriesData}}
52 | ```
53 |
54 | Would produce:
55 |
56 | ```js
57 | [
58 | [1450345920000, 1],
59 | [1450345930000, 2],
60 | [1450345940000, 3],
61 | [1450345950000, 4],
62 | [1450345960000, 5]
63 | ]
64 | ```
65 |
66 | Which is ideal for using as the `values` argument to `plaid.line` and `plaid.area`.
67 |
68 | ## Coordinates
69 |
70 | SVG doesn't use the same coordinate space as CSS, so we have to calculate margins manually. To help out with this we've included an `area` helper, which returns an object containing the positioning data for your chart.
71 |
72 | # `area`(width height [margin="TOP RIGHT BOTTOM LEFT"])
73 |
74 | The margin string follows the typical box model margin convention of TOP, RIGHT, BOTTOM, LEFT. All values are unitless and inherit their units from the coordinate space with the SVG context.
75 |
76 | ## Presentation
77 |
78 | On their own, components for even the simplest elements in a visualisation can quickly require complicated APIs. To solve this, Ember contextual components can provide lower level primitive components with the necessary inputs for scaling and positioning. This reduces the API surface for the user to quickly produce visualisations in very few lines of code.
79 |
80 | ```hbs
81 | {{#plaid-plot xScale yScale plotArea as |plot|}}
82 | {{plot.right-axis}}
83 | {{plot.line values}}
84 | {{/plaid-plot}}
85 | ```
86 |
87 | A more complete example, using scales from [ember-d3-scale](https://github.com/spencer516/ember-d3-scale#linear-scale) and helpers from [ember-composable-helpers](https://github.com/DockYard/ember-composable-helpers)
88 |
89 | ```hbs
90 | {{#with (area 720 220 margin="0 50 72 50") as |plotArea|}}
91 | {{#plaid-plot
92 | (time-scale (extent (map-by "timestamp" responseTimeMean)) (array 0 plotArea.width))
93 | (linear-scale (extent (map-by "value" responseTimeMean) toZero=true) (array plotArea.height 0))
94 | plotArea as |plot|}}
95 |
96 | {{#with (pair-by "timestamp" "value" responseTimeMean) as |values|}}
97 |
98 | {{plot.line values stroke="#673AB7" strokeWidth="2" curve=(curve "basis")}}
99 | {{plot.area values fill="#D1C4E9" curve=(curve "basis")}}
100 | {{plot.bottom-axis values ticks=10}}
101 | {{plot.left-axis values tickFormat=(format-fn "0.1s" suffix="ms") ticks=2}}
102 | {{/with}}
103 | {{/plaid-plot}}
104 | {{/with}}
105 | ```
106 |
107 | This will produce a pretty simple line + area chart:
108 |
109 | [](http://maximum-plaid.com)
110 |
111 | # Components
112 |
113 | ### `plaid-symbol`
114 |
115 | Provides an easy to use and straight forward interface to `d3-shape`'s symbol
116 | generators. `primitive-symbol` can be used in the same way as any Ember component
117 | and will render as a `` tag containing the path data for the specified
118 | symbol type.
119 |
120 | #### Options
121 |
122 | - `type`: Symbol to render, can be any of `circle`, `diamond`, `cross`,
123 | `square`, `star`, `triangle`, `wye`.
124 | - `size`: Specifies the symbol render size. This is the area of the
125 | symbol, which typicall equates to 1/4th of the actual width or height, depending
126 | on the shape.
127 | - `fill`: SVG path `fill` property.
128 | - `stroke`: SVG path `stroke` property.
129 | - `strokeWidth`: SVG path `stroke-width` property.
130 | - `top`: Top offset (applied using transform).
131 | - `left`: Left offset (applied using transform).
132 |
133 |
134 | # Mixins
135 |
136 | ## `PlotArea`
137 |
138 | Provides simple calculations for specifying the position of the main graphic in
139 | a visualisation.
140 |
141 | ### Example:
142 |
143 | ```js
144 | import { PlotArea } from 'maximum-plaid/mixins/plot-area';
145 |
146 | export default Component.extend(PlotArea, {
147 | margin: '16 24',
148 |
149 | didRender() {
150 | const { top, left } = this.get('plotArea');
151 | select(this.element).select('g.plot').attr('transform', `translate(${left},${top})`);
152 | }
153 | });
154 | ```
155 |
156 | # Utils
157 |
158 | ## `computedExtent`
159 |
160 | Creates a computed property which calculates the extent of all inputs provided
161 | using `extent` from d3-array.
162 |
163 | ## Installation
164 |
165 | **NOTE: `maximum-plaid` requires Ember v2.3+**
166 |
167 | ember install maximum-plaid
168 |
169 | ## Running
170 |
171 | * `ember server`
172 | * Visit your app at http://localhost:4200.
173 |
174 | ## Running Tests
175 |
176 | * `npm test` (Runs `ember try:testall` to test your addon against multiple Ember versions)
177 | * `ember test`
178 | * `ember test --server`
179 |
180 | ## Building
181 |
182 | * `ember build`
183 |
184 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/).
185 |
186 | # FAQ
187 |
188 | ### Why `maximum-plaid`?
189 |
190 | **Ivan**: Initially I was searching name which represented both a
191 | type of fabric for the visualisation metaphore, but also I've been told that friends
192 | have lost me in crowds walking around Williamsburg because I wear so much plaid. Also, the goal
193 | of this project is to make data visualisation easy and efficient, so in keeping with
194 | the promised performance mode of the next [Tesla Roadster](http://mashable.com/2015/07/17/new-tesla-roadster/#3NCT_4NpL8qU), I found "maximum plaid" to be
195 | fitting.
196 |
197 | ### You spelt visualization wrong
198 |
199 | That's not a question.
200 |
201 | As the main author of this project is Australian — a country which speaks a
202 | variation of British English, words such as `visualisation` are spelt with an `s`
203 | instead of a `z`. Another word you may find incorrectly spelt is `colour`. Please
204 | don't issue Pull Requests to fix this.
205 |
--------------------------------------------------------------------------------
/addon/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/addon/.gitkeep
--------------------------------------------------------------------------------
/addon/components/plaid-area/component.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import layout from './template';
3 | import { area } from 'd3-shape';
4 | import GroupElement from '../../mixins/group-element';
5 | import computed from 'ember-computed';
6 |
7 | const {
8 | isPresent,
9 | String: { dasherize, w }
10 | } = Ember;
11 |
12 | const AreaComponent = Ember.Component.extend(GroupElement, {
13 | layout,
14 |
15 | /**
16 | * xScale function
17 | *
18 | * @public
19 | * @type {D3 Scale}
20 | */
21 | xScale: null,
22 |
23 | /**
24 | * yScale function
25 | *
26 | * @public
27 | * @type {D3 Scale}
28 | */
29 | yScale: null,
30 |
31 | /**
32 | * Values to render line from. These should be the same as those used
33 | * for the domains of the scaling functions.
34 | *
35 | * @public
36 | * @type {Array}
37 | */
38 | values: [],
39 |
40 | fill: 'black',
41 |
42 | fillOpacity: 1.0,
43 |
44 | didRender() {
45 | let pathAttrs = this.getProperties(w('fill fillOpacity'));
46 | let area = this.selection.select('path.area');
47 | Object.keys(pathAttrs).forEach((attr) => {
48 | let value = pathAttrs[attr];
49 |
50 | if (isPresent(value)) {
51 | area.attr(dasherize(attr), value);
52 | }
53 | });
54 | },
55 |
56 | pathData: computed('values.[]', 'xScale', 'yScale', 'curve', {
57 | get() {
58 | let { values, xScale, yScale, curve } =
59 | this.getProperties('values', 'xScale', 'yScale', 'curve');
60 |
61 | let areaFn = area()
62 | .x((d) => xScale(d[0]))
63 | .y1((d) => yScale(d[1]))
64 | .y0(yScale(0));
65 |
66 | if (curve) {
67 | areaFn.curve(curve);
68 | }
69 |
70 | return areaFn(values);
71 | }
72 | })
73 | });
74 |
75 | AreaComponent.reopenClass({
76 | positionalParams: ['values']
77 | });
78 |
79 | export default AreaComponent;
80 |
--------------------------------------------------------------------------------
/addon/components/plaid-area/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/addon/components/plaid-axis/component.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import GroupElement from '../../mixins/group-element';
3 | import { axisTop, axisRight, axisBottom, axisLeft } from 'd3-axis';
4 |
5 | const {
6 | Component,
7 | run: { scheduleOnce }
8 | } = Ember;
9 |
10 | const AXIS_MAP = {
11 | top: axisTop,
12 | right: axisRight,
13 | bottom: axisBottom,
14 | left: axisLeft
15 | };
16 |
17 | export default Component.extend(GroupElement, {
18 | layout: null,
19 | classNames: [ 'axis', 'Plaid-axis' ],
20 | classNameBindings: [ 'orientation' ],
21 |
22 | y: 0,
23 |
24 | x: 0,
25 |
26 | ticks: 3,
27 |
28 | /**
29 | * Represents the axis orientation. You should always declare this.
30 | *
31 | * @public
32 | * @type {String}
33 | */
34 | orientation: 'top',
35 |
36 | /**
37 | * A scaling function used for this axis.
38 | *
39 | * @public
40 | * @type {Function}
41 | */
42 | scale: null,
43 |
44 | /**
45 | * The format used for the ticks for this axis.
46 | * [See D3 docs for more details](https://github.com/d3/d3-axis#axis_tickFormat)
47 | *
48 | * @public
49 | * @type {Function}
50 | */
51 | tickFormat: null,
52 |
53 | /**
54 | * The inner tick size for the ticks for this axis.
55 | * [See D3 docs for more details](https://github.com/d3/d3-axis#axis_tickSizeInner)
56 | *
57 | * @public
58 | * @type {Number}
59 | */
60 | tickSizeInner: 4,
61 |
62 | /**
63 | * The outer tick size for the ticks for this axis.
64 | * [See D3 docs for more details](https://github.com/d3/d3-axis#axis_tickSizeOuter)
65 | *
66 | * @public
67 | * @type {Number}
68 | */
69 | tickSizeOuter: 8,
70 |
71 | /**
72 | * Explicit tick values for this axis.
73 | * [See D3 docs for more details](https://github.com/d3/d3-axis#axis_tickValues)
74 | *
75 | * @public
76 | * @type {Array}
77 | */
78 | tickValues: null,
79 |
80 | xOffset: 0,
81 |
82 | yOffset: 0,
83 |
84 | didReceiveAttrs() {
85 | scheduleOnce('afterRender', this, this.drawAxis);
86 | },
87 |
88 | drawAxis() {
89 | let { y, x, xOffset, yOffset, scale, orientation, tickFormat, ticks, tickSizeInner, tickSizeOuter, tickValues } =
90 | this.getProperties('y', 'x', 'xOffset', 'yOffset', 'scale', 'orientation', 'tickFormat', 'ticks', 'tickSizeInner', 'tickSizeOuter', 'tickValues');
91 |
92 | let axis = this.createAxis(orientation, scale);
93 |
94 | axis.tickFormat(tickFormat);
95 | axis.tickSize(tickSizeInner, tickSizeOuter);
96 | axis.tickValues(tickValues);
97 | axis.scale(scale);
98 |
99 | if (ticks) {
100 | axis.ticks(ticks);
101 | }
102 |
103 | this.selection.call(axis);
104 | this.selection.attr('transform', `translate(${x + xOffset}, ${y + yOffset})`);
105 | },
106 |
107 | createAxis(orient, scale) {
108 | return AXIS_MAP[orient](scale);
109 | }
110 | });
111 |
--------------------------------------------------------------------------------
/addon/components/plaid-bar/component.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import GroupElement from '../../mixins/group-element';
3 | import { max, min } from 'd3-array';
4 |
5 | const {
6 | assert,
7 | Component,
8 | getProperties,
9 | run: { scheduleOnce },
10 | typeOf
11 | } = Ember;
12 |
13 | const PlaidBarComponent = Component.extend(GroupElement, {
14 | layout: null,
15 |
16 | /**
17 | * Represents the bar orientation. May be "vertical" or "horizontal"
18 | *
19 | * @public
20 | * @type {String}
21 | */
22 | orientation: 'vertical',
23 |
24 | /**
25 | xScale function
26 |
27 | @public
28 | @type {D3 Scale}
29 | */
30 | xScale: null,
31 |
32 | /**
33 | yScale function
34 |
35 | @public
36 | @type {D3 Scale}
37 | */
38 | yScale: null,
39 |
40 | /**
41 | Values to render bars from. These should be the same as those used
42 | for the domains of the scaling functions.
43 |
44 | @public
45 | @type {Array}
46 | */
47 | values: [],
48 |
49 | fill: 'black',
50 |
51 | fillOpacity: 1.0,
52 |
53 | didReceiveAttrs() {
54 | this._super(...arguments);
55 |
56 | let orientation = this.get('orientation');
57 |
58 | assert(`bar chart orientation must be in {vertical,horizontal}, was "${orientation}"`,
59 | orientation === 'vertical' || orientation === 'horizontal');
60 |
61 | let checkScale = orientation === 'vertical' ? 'xScale' : 'yScale';
62 |
63 | assert(`${checkScale} must be a band-scale for ${orientation} bar charts`, typeOf(this.get(checkScale).bandwidth) === 'function');
64 |
65 | scheduleOnce('afterRender', this, this.drawBars);
66 | },
67 |
68 | drawBars() {
69 | let { values, xScale, yScale, fill, fillOpacity, orientation } =
70 | getProperties(this, 'values', 'xScale', 'yScale', 'fill', 'fillOpacity', 'orientation');
71 |
72 | let x, width, y, height;
73 |
74 | if (orientation === 'vertical') {
75 | let maxHeight = max(yScale.range());
76 | x = (d) => xScale(d[0]);
77 | width = xScale.bandwidth();
78 | y = (d) => yScale(d[1]);
79 | height = (d) => maxHeight - yScale(d[1]);
80 | } else {
81 | x = min(xScale.range());
82 | width = (d) => xScale(d[0]);
83 | y = (d) => yScale(d[1]);
84 | height = yScale.bandwidth();
85 | }
86 |
87 | // JOIN new data with old elements
88 | let bars = this.selection.selectAll('.bar').data(values);
89 |
90 | // EXIT old elements not present in new data
91 | bars.exit().remove();
92 |
93 | // ENTER new elements present in new data
94 | let enterBars = bars.enter().append('rect').attr('class', 'bar');
95 |
96 | // MERGE the existing and with the entered and UPDATE
97 | bars.merge(enterBars)
98 | .attr('class', 'bar')
99 | .attr('x', x)
100 | .attr('width', width)
101 | .attr('y', y)
102 | .attr('height', height)
103 | .attr('fill', fill)
104 | .attr('fillOpacity', fillOpacity);
105 | }
106 | });
107 |
108 | PlaidBarComponent.reopenClass({
109 | positionalParams: [ 'values' ]
110 | });
111 |
112 | export default PlaidBarComponent;
113 |
--------------------------------------------------------------------------------
/addon/components/plaid-donut/component.js:
--------------------------------------------------------------------------------
1 | import { arc, pie } from 'd3-shape';
2 | import Ember from 'ember';
3 | import layout from './template';
4 | import GroupElement from '../../mixins/group-element';
5 | import { interpolateInferno, scaleSequential } from 'd3-scale';
6 | import { color } from 'd3-color';
7 | const {
8 | Component,
9 | computed,
10 | get,
11 | getProperties,
12 | run,
13 | run: { scheduleOnce }
14 | } = Ember;
15 |
16 | const DonutComponent = Component.extend(GroupElement, {
17 | layout,
18 |
19 | radius: computed('width', 'height', function() {
20 | let { width, height } = getProperties(this, 'width', 'height');
21 | return Math.min(width, height) / 2;
22 | }),
23 |
24 | transform: computed('width', 'height', function() {
25 | let { width, height } = getProperties(this, 'width', 'height');
26 |
27 | return `translate(${width / 2},${height / 2})`;
28 | }),
29 |
30 | innerRadius: computed('radius', function() {
31 | return get(this, 'radius') - 32;
32 | }),
33 |
34 | outerRadius: computed('radius', function() {
35 | return get(this, 'radius');
36 | }),
37 |
38 | cornerRadius: 5,
39 | colorInterpolator: interpolateInferno,
40 | padDegrees: 5,
41 |
42 | drawnValues: [],
43 |
44 | didReceiveAttrs() {
45 | this._super(...arguments);
46 |
47 | scheduleOnce('afterRender', this, this.draw);
48 | },
49 |
50 | pie: computed('padDegrees', function() {
51 | return pie()
52 | .padAngle(get(this, 'padDegrees') / 360)
53 | .value((d) => d[1]);
54 | }),
55 |
56 | piedValues: computed('pie', 'values.[]', function() {
57 | let { values, pie } = getProperties(this, 'values', 'pie');
58 |
59 | return pie(values);
60 | }),
61 |
62 | arc: computed('cornerRadius', 'innerRadius', 'outerRadius', function() {
63 | let { cornerRadius, innerRadius, outerRadius } =
64 | getProperties(this, 'cornerRadius', 'innerRadius', 'outerRadius');
65 |
66 | return arc()
67 | .cornerRadius(cornerRadius)
68 | .innerRadius(innerRadius)
69 | .outerRadius(outerRadius);
70 | }),
71 |
72 | draw() {
73 | let { piedValues: values, arc, colorInterpolator } = getProperties(this, 'piedValues', 'arc', 'colorInterpolator');
74 | let arcs = this.selection.selectAll('g.arc');
75 | let join = arcs.data(values);
76 | // DATA REMOVE
77 | join.exit().remove();
78 |
79 | // Use a colour interpolator to render the colour for each bar.
80 | let arcColorScale = scaleSequential(colorInterpolator).domain([0, values.length]);
81 |
82 | // DATA ENTER
83 | let enterJoin = join.enter().append('g').attr('class', 'arc');
84 | enterJoin.append('path');
85 |
86 | // DATA MERGE + UPDATE
87 | enterJoin.merge(join)
88 | .attr('data-title', (d) => d.data[0])
89 | .select('path')
90 | .attr('d', arc)
91 | .attr('fill', (d,i) => {
92 | let c = color(arcColorScale(i));
93 | c.opacity = 0.54;
94 | return c.toString();
95 | })
96 | .attr('stroke', (d,i) => arcColorScale(i))
97 | .on('click', (d) => run(this, this.sendAction, 'on-click', d.data[0]))
98 | .on('mouseenter', (d) => run(this, this.sendAction, 'on-enter', d.data[0]))
99 | .on('mouseleave', (d) => run(this, this.sendAction, 'on-leave', d.data[0]));
100 |
101 | }
102 | });
103 |
104 | DonutComponent.reopenClass({
105 | positionalParams: [ 'values' ]
106 | });
107 |
108 | export default DonutComponent;
109 |
--------------------------------------------------------------------------------
/addon/components/plaid-donut/template.hbs:
--------------------------------------------------------------------------------
1 | {{yield}}
2 |
--------------------------------------------------------------------------------
/addon/components/plaid-line/component.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import layout from './template';
3 | import GroupElement from '../../mixins/group-element';
4 | import { line, curveMonotoneX } from 'd3-shape';
5 |
6 | const {
7 | computed,
8 | isPresent,
9 | String: { dasherize, w }
10 | } = Ember;
11 |
12 | const PlaidLineComponent = Ember.Component.extend(GroupElement, {
13 | layout,
14 |
15 | /**
16 | * xScale function
17 | *
18 | * @public
19 | * @type {D3 Scale}
20 | */
21 | xScale: null,
22 |
23 | /**
24 | * yScale function
25 | *
26 | * @public
27 | * @type {D3 Scale}
28 | */
29 | yScale: null,
30 |
31 | /**
32 | * Values to render line from. These should be the same as those used
33 | * for the domains of the scaling functions.
34 | *
35 | * @public
36 | * @type {Array}
37 | */
38 | values: [],
39 |
40 | stroke: 'black',
41 | strokeWidth: 2,
42 | strokeOpacity: 1.0,
43 |
44 | fill: 'none',
45 | fillOpacity: null,
46 |
47 | curve: curveMonotoneX,
48 |
49 | didRender() {
50 | let pathAttrs = this.getProperties(w('stroke strokeWidth strokeOpacity fill fillOpacity'));
51 | let line = this.selection.select('path.line');
52 | Object.keys(pathAttrs).forEach((attr) => {
53 | let value = pathAttrs[attr];
54 |
55 | if (isPresent(value)) {
56 | line.attr(dasherize(attr), value);
57 | }
58 | });
59 | },
60 |
61 | pathData: computed('values.[]', 'xScale', 'yScale', 'curve', {
62 | get() {
63 | let { values, xScale, yScale, curve } =
64 | this.getProperties('values', 'xScale', 'yScale', 'curve');
65 |
66 | let lineFn = line()
67 | .curve(curve)
68 | .x((d) => xScale(d[0]))
69 | .y((d) => yScale(d[1]));
70 |
71 | if (curve) {
72 | lineFn.curve(curve);
73 | }
74 |
75 | return lineFn(values);
76 | }
77 | })
78 | });
79 |
80 | PlaidLineComponent.reopenClass({
81 | positionalParams: ['values']
82 | });
83 |
84 | export default PlaidLineComponent;
85 |
--------------------------------------------------------------------------------
/addon/components/plaid-line/template.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addon/components/plaid-plot.js:
--------------------------------------------------------------------------------
1 | import layout from '../templates/components/plaid-plot';
2 | import Coordinates from 'maximum-plaid/mixins/coordinates';
3 | import Component from 'ember-component';
4 |
5 | let PlotComponent = Component.extend(Coordinates, {
6 | layout,
7 |
8 | tagName: 'svg',
9 |
10 | classNames: ['plaid-plot'],
11 |
12 | attributeBindings: ['plotArea.outerWidth:width', 'plotArea.outerHeight:height'],
13 |
14 | /**
15 | * Represents the xScale, if used.
16 | *
17 | * @public
18 | * @type {D3 Scale}
19 | */
20 | xScale: null,
21 |
22 | /**
23 | * Represents the yScale, if used.
24 | *
25 | * @public
26 | * @type {D3 Scale}
27 | */
28 | yScale: null
29 |
30 | /**
31 | * Represents the coordinates to render the main graphic.
32 | *
33 | * This is typically the output of the Coordinates mixin:
34 | *
35 | * {top, right, bottom, left, width, height, outerWidth, outerHeight}
36 | *
37 | * @public
38 | * @type {Object}
39 | */
40 | // plotArea: {}
41 |
42 | });
43 |
44 | PlotComponent.reopenClass({
45 | positionalParams: ['xScale', 'yScale', 'plotArea']
46 | });
47 |
48 | export default PlotComponent;
49 |
--------------------------------------------------------------------------------
/addon/components/plaid-scatter/component.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import layout from './template';
3 | import GroupElement from '../../mixins/group-element';
4 | const { computed } = Ember;
5 |
6 | const ScatterComponent = Ember.Component.extend(GroupElement, {
7 | layout,
8 |
9 | xScale: null,
10 |
11 | yScale: null,
12 |
13 | values: [],
14 |
15 | positions: computed('values.[]', 'xScale', 'yScale', {
16 | get() {
17 | let { values, xScale, yScale }
18 | = this.getProperties('xScale', 'yScale', 'values');
19 |
20 | return values.map(([timestamp, value]) => {
21 | return {
22 | x: xScale(timestamp),
23 | y: yScale(value)
24 | };
25 | });
26 | }
27 | })
28 | });
29 |
30 | ScatterComponent.reopenClass({
31 | positionalParams: ['values']
32 | });
33 |
34 | export default ScatterComponent;
35 |
--------------------------------------------------------------------------------
/addon/components/plaid-scatter/template.hbs:
--------------------------------------------------------------------------------
1 | {{#each positions as |position|}}
2 | {{yield position.x position.y}}
3 | {{/each}}
4 |
--------------------------------------------------------------------------------
/addon/components/plaid-symbol.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Component from 'ember-component';
3 |
4 | import {
5 | symbol,
6 | symbolCircle,
7 | symbolDiamond,
8 | symbolCross,
9 | symbolSquare,
10 | symbolStar,
11 | symbolTriangle,
12 | symbolWye
13 | } from 'd3-shape';
14 |
15 | const {
16 | computed,
17 | assert,
18 | isPresent,
19 | typeOf
20 | } = Ember;
21 |
22 | /**
23 | * Generates the path data for a symbol as a tag.
24 | *
25 | * @public
26 | * Usage:
27 | *
28 | * {{plaid-symbol "TYPE" x y}}
29 | */
30 |
31 | const SymbolComponent = Component.extend({
32 | tagName: 'path',
33 | attributeBindings: [
34 | 'symbolData:d',
35 | 'transform',
36 | 'stroke',
37 | 'fill',
38 | 'strokeWidth:stroke-width'
39 | ],
40 | classNames: ['symbol'],
41 | classNameBindings: ['type'],
42 |
43 | /**
44 | * Symbol size in area
45 | *
46 | * @public
47 | * @type {Number}
48 | */
49 | size: 16,
50 |
51 | /**
52 | * Symbol to render
53 | *
54 | * Can either be a string or a symbol function.
55 | *
56 | * @public
57 | * @type {String}
58 | */
59 | type: 'diamond',
60 |
61 | /**
62 | * Fill color or style
63 | *
64 | * @public
65 | * @type {String}
66 | */
67 | fill: 'black',
68 |
69 | stroke: 'none',
70 |
71 | strokeWidth: 0,
72 |
73 | /**
74 | * Positioning offset from y
75 | *
76 | * @public
77 | * @type {Number}
78 | */
79 | y: 0,
80 |
81 | /**
82 | * Positioning offset from x
83 | *
84 | * @public
85 | * @type {Number}
86 | */
87 | x: 0,
88 |
89 | /**
90 | * Generates the SVG path data for the given symbol
91 | *
92 | * @public
93 | * @return {String}
94 | */
95 | symbolData: computed('size', 'type', {
96 | get() {
97 | let { size, type } = this.getProperties('size', 'type');
98 | let data;
99 |
100 | if (typeOf(type) !== 'string') {
101 | data = type;
102 | } else {
103 | data = {
104 | 'circle': symbolCircle,
105 | 'diamond': symbolDiamond,
106 | 'cross': symbolCross,
107 | 'square': symbolSquare,
108 | 'star': symbolStar,
109 | 'triangle': symbolTriangle,
110 | 'wye': symbolWye
111 | }[type.toLowerCase()];
112 | assert(`Not a valid symbol type "${type}"`, isPresent(data));
113 | }
114 |
115 | let fn = symbol();
116 | fn.type(data);
117 | fn.size(size);
118 |
119 | return fn();
120 | }
121 | }),
122 |
123 | transform: computed('y', 'x', {
124 | get() {
125 | let { y, x } = this.getProperties('y', 'x');
126 | return `translate(${x},${y})`;
127 | }
128 | })
129 | });
130 |
131 | SymbolComponent.reopenClass({
132 | positionalParams: ['type', 'x', 'y']
133 | });
134 |
135 | export default SymbolComponent;
136 |
--------------------------------------------------------------------------------
/addon/components/plaid-text/component.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import layout from './template';
3 |
4 | const {
5 | computed,
6 | Component,
7 | getProperties
8 | } = Ember;
9 |
10 | const PlaidTextComponent = Component.extend({
11 | tagName: 'text',
12 | layout,
13 | classNames: [ 'plaid-text' ],
14 | attributeBindings: [ 'transform', 'fill', 'dx', 'dy', 'rotate', 'textLength', 'lengthAdjust', 'textAnchor:text-anchor' ],
15 |
16 | textAnchor: 'middle',
17 |
18 | x: 0,
19 | y: 0,
20 | fill: '#000',
21 |
22 | dx: null,
23 | dy: null,
24 | rotate: null,
25 | textLength: null,
26 | lengthAdjust: null,
27 |
28 | value: null,
29 | textRotate: 0,
30 |
31 | transform: computed('x', 'y', 'textRotate', {
32 | get() {
33 | let { x, y, textRotate } = getProperties(this, 'x', 'y', 'textRotate');
34 | return `translate(${x},${y}) rotate(${textRotate})`;
35 | }
36 | })
37 | });
38 |
39 | PlaidTextComponent.reopenClass({
40 | positionalParams: [ 'value' ]
41 | });
42 |
43 | export default PlaidTextComponent;
44 |
--------------------------------------------------------------------------------
/addon/components/plaid-text/template.hbs:
--------------------------------------------------------------------------------
1 | {{value}}
--------------------------------------------------------------------------------
/addon/helpers/area.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import box from '../utils/box-expression';
3 | const { Helper } = Ember;
4 |
5 | export function area(params, hash) {
6 | let [width, height] = params.slice();
7 | hash = Object.assign({}, hash);
8 | let margin = hash.margin ? box(hash.margin) : { top: 0, right: 0, bottom: 0, left: 0 };
9 |
10 | return {
11 | top: margin.top,
12 | left: margin.left,
13 | bottom: height - margin.top,
14 | right: width - margin.right,
15 | height: height - margin.top - margin.bottom,
16 | width: width - margin.left - margin.right,
17 | outerWidth: width,
18 | outerHeight: height,
19 | margin: {
20 | top: margin.top,
21 | left: margin.left,
22 | bottom: margin.bottom,
23 | right: margin.right
24 | }
25 | };
26 | }
27 |
28 | export default Helper.helper(area);
29 |
--------------------------------------------------------------------------------
/addon/helpers/curve.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | const { Helper, isPresent, assert, String: { camelize } } = Ember;
3 |
4 | import {
5 | curveBasisClosed,
6 | curveBasisOpen,
7 | curveBasis,
8 | curveBundle,
9 | curveCardinalClosed,
10 | curveCardinalOpen,
11 | curveCardinal,
12 | curveCatmullRomClosed,
13 | curveCatmullRomOpen,
14 | curveCatmullRom,
15 | curveLinearClosed,
16 | curveLinear,
17 | curveNatural,
18 | curveStep,
19 | curveMonotoneX,
20 | curveMonotoneY
21 | } from 'd3-shape';
22 |
23 | export function curve([curveName], hash) {
24 | if (!hash) {
25 | hash = {};
26 | }
27 |
28 | let curves = {
29 | basisClosed: curveBasisClosed,
30 | basisOpen: curveBasisOpen,
31 | basis: curveBasis,
32 | bundle: curveBundle,
33 | cardinalClosed: curveCardinalClosed,
34 | cardinalOpen: curveCardinalOpen,
35 | cardinal: curveCardinal,
36 | catmullRomClosed: curveCatmullRomClosed,
37 | catmullRomOpen: curveCatmullRomOpen,
38 | catmullRom: curveCatmullRom,
39 | linearClosed: curveLinearClosed,
40 | linear: curveLinear,
41 | natural: curveNatural,
42 | step: curveStep,
43 | monotone: curveMonotoneX,
44 | monotoneX: curveMonotoneX,
45 | monotoneY: curveMonotoneY
46 | };
47 |
48 | let curveFn = curves[camelize(curveName)];
49 |
50 | assert(`No curve with name ${curveName} is available`, isPresent(curveFn));
51 |
52 | Object.keys(hash).forEach((key) => {
53 | if (typeof curveFn[key] === 'function') {
54 | curveFn[key](hash[key]);
55 | }
56 | });
57 |
58 | return curveFn;
59 | }
60 |
61 | export default Helper.helper(curve);
62 |
--------------------------------------------------------------------------------
/addon/helpers/extent.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import { extent as arrayExtent, max } from 'd3-array';
3 | const { Helper, get, isPresent } = Ember;
4 |
5 | export function extent([array, accessor], options) {
6 |
7 | if (isPresent(accessor)) {
8 | array = array.map((d) => get(d, accessor));
9 | }
10 |
11 | if (options && options.toZero === true) {
12 | return [0, max(array)];
13 | } else {
14 | return arrayExtent(array);
15 | }
16 | }
17 |
18 | export default Helper.helper(extent);
19 |
--------------------------------------------------------------------------------
/addon/helpers/format-fn.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import { format } from './format';
3 |
4 | const { Helper } = Ember;
5 |
6 | export function formatFn(params, hash) {
7 | return function formatFnHelper(value) {
8 | hash = Object.assign({}, hash);
9 | hash.format = params[0];
10 | return format([value], hash);
11 | };
12 | }
13 |
14 | export default Helper.helper(formatFn);
15 |
--------------------------------------------------------------------------------
/addon/helpers/format.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import { format as d3Format } from 'd3-format';
3 | const { Helper } = Ember;
4 |
5 | export function format([value], hash) {
6 | hash = Object.assign({}, hash);
7 |
8 | let result;
9 | if (!hash.format) {
10 | hash.format = ',';
11 | }
12 |
13 | if (value < 1 && hash.ignoreSmallValues) {
14 | result = '< 1';
15 | } else {
16 | result = d3Format(hash.format)(value);
17 | }
18 |
19 | if (hash.suffix) {
20 | result = `${result} ${hash.suffix}`;
21 | }
22 |
23 | if (hash.prefix) {
24 | result = `${hash.prefix}${result}`;
25 | }
26 |
27 | return result;
28 | }
29 |
30 | export default Helper.helper(format);
31 |
--------------------------------------------------------------------------------
/addon/helpers/linear-scale.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import { scaleLinear } from 'd3-scale';
3 | const { Helper } = Ember;
4 |
5 | export function linearScale(params, hash = {}) {
6 | params = params.slice();
7 | let [domain, range] = params;
8 | hash = Object.assign({}, hash);
9 |
10 | let scale = scaleLinear().domain(domain);
11 | if (hash && hash.round) {
12 | scale.rangeRound(range);
13 | } else {
14 | scale.range(range);
15 | }
16 |
17 | return scale;
18 | }
19 |
20 | export default Helper.helper(linearScale);
21 |
--------------------------------------------------------------------------------
/addon/helpers/pair-by.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | const { Helper, isArray, assert } = Ember;
4 |
5 | /**
6 | * @pairBy(params);
7 | *
8 | * Creates a pair of values from an array of objects.
9 | *
10 | * Usage:
11 | *
12 | * (pair-by "timestamp" "value" someArrayOfObjects)
13 | * // => [[1450345920000, 1885], [1450345920000, 1885], ...]
14 | *
15 | * @param {Array[...keys, data]} params
16 | * @public
17 | * @return {Array[Array[2]]}
18 | */
19 | export function pairBy(args = []) {
20 | let params = args.slice();
21 | assert('pair-by requires at least 2 arguments: key, data', params.length >= 2);
22 | let data = params.pop();
23 | assert('last argument must be an array of objects', isArray(data));
24 |
25 | let [...keys] = params;
26 |
27 | return data.map((d) => {
28 | return keys.reduce((acc, k, index) => {
29 | acc[index] = d[k];
30 | return acc;
31 | }, []);
32 | });
33 | }
34 |
35 | export default Helper.helper(pairBy);
36 |
--------------------------------------------------------------------------------
/addon/mixins/coordinates.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Dimensions from './dimensions';
3 | import PlotArea from './plot-area';
4 |
5 | const { Mixin } = Ember;
6 |
7 | export default Mixin.create(Dimensions, PlotArea, {
8 | });
9 |
--------------------------------------------------------------------------------
/addon/mixins/dimensions.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import GlobalResize from 'maximum-plaid/mixins/global-resize';
3 |
4 | const {
5 | Mixin,
6 | K,
7 | run: {
8 | throttle,
9 | next
10 | },
11 | on
12 | } = Ember;
13 |
14 | export default Mixin.create(GlobalResize, {
15 |
16 | width: 1,
17 | height: 1,
18 |
19 | prepare: on('didInsertElement', function() {
20 | next(this, this.measureDimensions);
21 | }),
22 |
23 | didMeasureDimensions: K,
24 |
25 | didResize() {
26 | // window.requestAnimationFrame(this.measureDimensions.bind(this));
27 | throttle(this, this.measureDimensions, 100);
28 | },
29 |
30 | measureDimensions() {
31 | if (!this.element) {
32 | return;
33 | }
34 |
35 | let rect = this.element.getBoundingClientRect();
36 | next(this, function() {
37 | this.setProperties({
38 | width: rect.width,
39 | height: rect.height
40 | });
41 |
42 | this.trigger('didMeasureDimensions');
43 | });
44 | }
45 |
46 | });
47 |
--------------------------------------------------------------------------------
/addon/mixins/global-resize.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | const { Mixin, on, $, K, run } = Ember;
4 |
5 | export default Mixin.create({
6 | _setupResizeListener: on('didInsertElement', function() {
7 | $(window).on(`resize.${this.elementId}`, (event) => {
8 | run.next(this, this.didResize, event);
9 | });
10 | }),
11 |
12 | _teardownResizeListener: on('willDestroyElement', function() {
13 | $(window).off(`resize.${this.elementId}`);
14 | }),
15 |
16 | didResize: K
17 | });
18 |
--------------------------------------------------------------------------------
/addon/mixins/group-element.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import { select } from 'd3-selection';
3 |
4 | const { computed, Mixin } = Ember;
5 |
6 | /**
7 | * @public
8 | * GroupElement mixin sets the component's tag to ``, and assigns an instance
9 | * variable of `this.selection` to a D3 selection of itself.
10 | */
11 |
12 | export default Mixin.create({
13 | tagName: 'g',
14 |
15 | x: 0,
16 |
17 | y: 0,
18 |
19 | attributeBindings: ['transform'],
20 |
21 | didInsertElement() {
22 | this.selection = select(this.element);
23 | },
24 |
25 | transform: computed('x', 'y', {
26 | get() {
27 | let { x, y } = this.getProperties('x', 'y');
28 | return `translate(${x},${y})`;
29 | }
30 | })
31 | });
32 |
--------------------------------------------------------------------------------
/addon/mixins/plot-area.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import box from '../utils/box-expression';
3 |
4 | const { computed, Mixin } = Ember;
5 |
6 | export default Mixin.create({
7 |
8 | /**
9 | * Specifies them margin for the main graph, such that you can position
10 | * guides (axis) outside of it.
11 | *
12 | * Accepts either an object specifying {top, right, bottom, left} in units,
13 | * or a CSS equivalent margin string without units.
14 | *
15 | * E.g. "24 10" == {top: 24, right: 10, bottom: 24, left: 10}
16 | *
17 | * @public
18 | * @type {String || Object}
19 | */
20 | margin: '0',
21 |
22 | /**
23 | * Computed dimensions for the actual plot.
24 | *
25 | * @public
26 | * @return {Object}
27 | */
28 | plotArea: computed('width', 'height', 'margin.[]', {
29 | get() {
30 | let height = this.getWithDefault('height', 0);
31 | let width = this.getWithDefault('width', 0);
32 | let margin = box(this.get('margin'));
33 |
34 | return {
35 | top: margin.top,
36 | left: margin.left,
37 | bottom: height - margin.top,
38 | right: width - margin.right,
39 | height: height - margin.top - margin.bottom,
40 | width: width - margin.left - margin.right,
41 | outerWidth: width,
42 | outerHeight: height,
43 | margin: {
44 | top: margin.top,
45 | left: margin.left,
46 | bottom: margin.bottom,
47 | right: margin.right
48 | }
49 | };
50 | }
51 | })
52 | });
53 |
--------------------------------------------------------------------------------
/addon/templates/components/plaid-plot.hbs:
--------------------------------------------------------------------------------
1 | {{yield (hash
2 | line=(component "plaid-line" xScale=xScale yScale=yScale x=plotArea.left y=plotArea.top)
3 | area=(component "plaid-area" xScale=xScale yScale=yScale x=plotArea.left y=plotArea.top)
4 | scatter=(component "plaid-scatter" xScale=xScale yScale=yScale x=plotArea.left y=plotArea.top)
5 | symbol=(component "plaid-symbol" x=plotArea.left y=plotArea.top)
6 | bar=(component "plaid-bar" xScale=xScale yScale=yScale x=plotArea.left y=plotArea.top)
7 | donut=(component "plaid-donut" x=plotArea.left y=plotArea.top width=plotArea.width height=plotArea.height)
8 | text=(component "plaid-text")
9 | donut=(component "plaid-donut" width=plotArea.width height=plotArea.height)
10 |
11 | top-axis=(component "plaid-axis" orientation="top" scale=xScale y=plotArea.top x=plotArea.left)
12 | right-axis=(component "plaid-axis" orientation="right" scale=yScale y=plotArea.top x=plotArea.right)
13 | bottom-axis=(component "plaid-axis" orientation="bottom" scale=xScale y=(add plotArea.height plotArea.top) x=plotArea.left)
14 | left-axis=(component "plaid-axis" orientation="left" scale=yScale y=plotArea.top x=plotArea.left)
15 | )}}
16 |
--------------------------------------------------------------------------------
/addon/templates/components/plaid-symbol.hbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/addon/templates/components/plaid-symbol.hbs
--------------------------------------------------------------------------------
/addon/utils/box-expression.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | const { assert } = Ember;
3 |
4 | export default function box(expr) {
5 | if (typeof expr !== 'object') {
6 | expr = String(expr).split(/\s+/).map(Number);
7 | } else {
8 | return ['left', 'right', 'top', 'bottom'].reduce((accum, dir) => {
9 | accum[dir] = Number(expr[dir]) || 0;
10 |
11 | return accum;
12 | }, {});
13 | }
14 |
15 | assert('Box expr must be have 1-4 numbers', !expr.filter(isNaN).length);
16 |
17 | switch (expr.length) {
18 | // 1 value = all four sides
19 | case 1: return { left: expr[0], right: expr[0], top: expr[0], bottom: expr[0] };
20 | // 2 values = top/bottom, right/left
21 | case 2: return { left: expr[1], right: expr[1], top: expr[0], bottom: expr[0] };
22 | // 3 values = top, both sides, bottom
23 | case 3: return { left: expr[1], right: expr[1], top: expr[0], bottom: expr[2] };
24 | // 4 values = top, right, bottom, left
25 | case 4: return { left: expr[3], right: expr[1], top: expr[0], bottom: expr[2] };
26 | }
27 |
28 | return {
29 | left: 0,
30 | top: 0,
31 | bottom: 0,
32 | right: 0
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/addon/utils/computed-extent.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | const { computed, expandProperties } = Ember;
3 | import { extent } from 'd3-array';
4 |
5 | function expandPropertyList(propertyList) {
6 | return propertyList.reduce((newPropertyList, property) => {
7 | let atEachIndex = property.indexOf('.@each');
8 | if (atEachIndex !== -1) {
9 | return newPropertyList.concat(property.slice(0, atEachIndex));
10 | } else if (property.slice(-2) === '[]') {
11 | return newPropertyList.concat(property.slice(0, -3));
12 | }
13 |
14 | expandProperties(property, (expandedProperties) => {
15 | newPropertyList = newPropertyList.concat(expandedProperties);
16 | });
17 |
18 | return newPropertyList;
19 | }, []);
20 | }
21 |
22 | function extractConstantValues(dependencies) {
23 | return dependencies.filter((property) => {
24 | return Ember.typeOf(property) !== 'string';
25 | });
26 | }
27 |
28 | function rejectConstantValues(dependencies) {
29 | return dependencies.filter((property) => {
30 | return Ember.typeOf(property) === 'string';
31 | });
32 | }
33 |
34 | export default function computedExtent(...dependencies) {
35 | let userSuppliedConstants = extractConstantValues(dependencies) || [];
36 | let expandedParams = expandPropertyList(rejectConstantValues(dependencies));
37 |
38 | let computedFn = computed.apply(this, [...rejectConstantValues(dependencies), {
39 | get() {
40 | let values = [];
41 | values = values.concat(userSuppliedConstants);
42 |
43 | let paramValues = expandedParams.map((p) => this.get(p));
44 | paramValues.forEach((expandedProperty) => {
45 | if (Ember.typeOf(expandedProperty) === 'array') {
46 | values = values.concat(expandedProperty);
47 | } else {
48 | values.push(expandedProperty);
49 | }
50 | });
51 |
52 | return extent(values);
53 | }
54 | }]);
55 |
56 | return computedFn;
57 | }
58 |
--------------------------------------------------------------------------------
/app/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/app/.gitkeep
--------------------------------------------------------------------------------
/app/components/plaid-area/component.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-area/component';
--------------------------------------------------------------------------------
/app/components/plaid-axis/component.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-axis/component';
--------------------------------------------------------------------------------
/app/components/plaid-bar/component.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-bar/component';
2 |
--------------------------------------------------------------------------------
/app/components/plaid-donut/component.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-donut/component';
--------------------------------------------------------------------------------
/app/components/plaid-line/component.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-line/component';
--------------------------------------------------------------------------------
/app/components/plaid-plot.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-plot';
--------------------------------------------------------------------------------
/app/components/plaid-scatter/component.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-scatter/component';
--------------------------------------------------------------------------------
/app/components/plaid-symbol.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-symbol';
--------------------------------------------------------------------------------
/app/components/plaid-text/component.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/components/plaid-text/component';
--------------------------------------------------------------------------------
/app/helpers/area.js:
--------------------------------------------------------------------------------
1 | export { default, area } from 'maximum-plaid/helpers/area';
2 |
--------------------------------------------------------------------------------
/app/helpers/curve.js:
--------------------------------------------------------------------------------
1 | export { default, curve } from 'maximum-plaid/helpers/curve';
2 |
--------------------------------------------------------------------------------
/app/helpers/extent.js:
--------------------------------------------------------------------------------
1 | export { default, extent } from 'maximum-plaid/helpers/extent';
2 |
--------------------------------------------------------------------------------
/app/helpers/format-fn.js:
--------------------------------------------------------------------------------
1 | export { default, formatFn } from 'maximum-plaid/helpers/format-fn';
2 |
--------------------------------------------------------------------------------
/app/helpers/format.js:
--------------------------------------------------------------------------------
1 | export { default, format } from 'maximum-plaid/helpers/format';
2 |
--------------------------------------------------------------------------------
/app/helpers/linear-scale.js:
--------------------------------------------------------------------------------
1 | export { default, linearScale } from 'maximum-plaid/helpers/linear-scale';
2 |
--------------------------------------------------------------------------------
/app/helpers/pair-by.js:
--------------------------------------------------------------------------------
1 | export { default, pairBy } from 'maximum-plaid/helpers/pair-by';
2 |
--------------------------------------------------------------------------------
/app/utils/box-expression.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/utils/box-expression';
2 |
--------------------------------------------------------------------------------
/app/utils/computed-extent.js:
--------------------------------------------------------------------------------
1 | export { default } from 'maximum-plaid/utils/computed-extent';
2 |
--------------------------------------------------------------------------------
/blueprints/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "console"
4 | ],
5 | "strict": false
6 | }
7 |
--------------------------------------------------------------------------------
/blueprints/maximum-plaid/index.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | description: 'Installation blueprint for maximum plaid',
4 | normalizeEntityName: function () {},
5 | beforeInstall: function() {
6 | return this.addAddonsToProject({
7 | packages: [
8 | 'ember-cli-d3-shape@^0.8.5',
9 | 'ember-math-helpers@^1.0.0'
10 | ]
11 | });
12 | }
13 |
14 | // locals: function(options) {
15 | // // Return custom template variables here.
16 | // return {
17 | // foo: options.entity.options.foo
18 | // };
19 | // }
20 |
21 | // afterInstall: function(options) {
22 | // // Perform extra work here.
23 | // }
24 | };
25 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maximum-plaid",
3 | "dependencies": {
4 | "ember": "~2.10.0",
5 | "ember-cli-shims": "0.1.3",
6 | "bourbon": "4.2.6",
7 | "firebase": "^2.1.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | scenarios: [
4 | {
5 | name: 'ember-lts-2.4',
6 | bower: {
7 | dependencies: {
8 | 'ember': 'components/ember#lts-2-4'
9 | },
10 | resolutions: {
11 | 'ember': 'lts-2-4'
12 | }
13 | }
14 | },
15 | {
16 | name: 'ember-lts-2.8',
17 | bower: {
18 | dependencies: {
19 | 'ember': 'components/ember#lts-2-8'
20 | },
21 | resolutions: {
22 | 'ember': 'lts-2-8'
23 | }
24 | }
25 | },
26 | {
27 | name: 'ember-release',
28 | bower: {
29 | dependencies: {
30 | 'ember': 'components/ember#release'
31 | },
32 | resolutions: {
33 | 'ember': 'release'
34 | }
35 | }
36 | },
37 | {
38 | name: 'ember-beta',
39 | bower: {
40 | dependencies: {
41 | 'ember': 'components/ember#beta'
42 | },
43 | resolutions: {
44 | 'ember': 'beta'
45 | }
46 | }
47 | },
48 | {
49 | name: 'ember-canary',
50 | bower: {
51 | dependencies: {
52 | 'ember': 'components/ember#canary'
53 | },
54 | resolutions: {
55 | 'ember': 'canary'
56 | }
57 | }
58 | }
59 | ]
60 | };
61 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | 'use strict';
3 |
4 | module.exports = function(/* environment, appConfig */) {
5 | return { };
6 | };
7 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | /* global require, module */
3 | var EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
4 |
5 | module.exports = function(defaults) {
6 | var app = new EmberAddon(defaults, {
7 | // Add options here
8 | });
9 |
10 | /*
11 | This build file specifies the options for the dummy test app of this
12 | addon, located in `/tests/dummy`
13 | This build file does *not* influence how the addon or the app using it
14 | behave. You most likely want to be modifying `./index.js` or app's build file
15 | */
16 |
17 | return app.toTree();
18 | };
19 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 | 'use strict';
3 |
4 | module.exports = {
5 | name: 'maximum-plaid'
6 | };
7 |
--------------------------------------------------------------------------------
/logo/line-chart-screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/logo/line-chart-screenshot.png
--------------------------------------------------------------------------------
/logo/maximum-plaid-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/logo/maximum-plaid-logo.png
--------------------------------------------------------------------------------
/maximum-plaid.sublime-project:
--------------------------------------------------------------------------------
1 | {
2 | "folders":
3 | [
4 | {
5 | "path": ".",
6 | "name": "MaximumPlaid",
7 | "folder_exclude_patterns": ["node_modules", "bower_components", "tmp"]
8 | }
9 | ],
10 | "settings": {
11 | "tab_size": 2,
12 | "translate_tabs_to_spaces": true
13 | },
14 | "build_systems": [
15 | {
16 | "name": "Ember-CLI Server",
17 | "shell_cmd": "ember s"
18 | },
19 | {
20 | "name": "Ember-CLI Tests",
21 | "shell_cmd": "ember test ci"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "maximum-plaid",
3 | "version": "0.1.3",
4 | "description": "Template driven data visualisation for ambitious applications",
5 | "directories": {
6 | "doc": "doc",
7 | "test": "tests"
8 | },
9 | "license": "MIT",
10 | "author": "Ivan Vanderbyl",
11 | "repository": "ivanvanderbyl/plaid",
12 | "scripts": {
13 | "build": "ember build",
14 | "start": "ember server",
15 | "test": "ember try:each",
16 | "deploy": "ember github-pages:commit --message \"Deploy gh-pages from commit $(git rev-parse HEAD)\"; git push; git checkout -"
17 | },
18 | "dependencies": {
19 | "ember-cli-babel": "^5.1.7",
20 | "ember-math-helpers": "1.0.0",
21 | "ember-d3-helpers": ">=0.5.2",
22 | "ember-cli-htmlbars": "^1.0.10",
23 | "ember-composable-helpers": "^1.1.2",
24 | "ember-d3": "ivanvanderbyl/ember-d3",
25 | "d3": "^4.4.0"
26 | },
27 | "devDependencies": {
28 | "broccoli-asset-rev": "^2.4.5",
29 | "ember-ajax": "^2.4.1",
30 | "ember-cli": "2.10.0",
31 | "ember-cli-app-version": "^2.0.0",
32 | "ember-cli-bourbon": "1.2.2",
33 | "ember-cli-dependency-checker": "^1.3.0",
34 | "ember-cli-eslint": "3.0.0",
35 | "ember-cli-htmlbars-inline-precompile": "^0.3.3",
36 | "ember-cli-inject-live-reload": "^1.4.1",
37 | "ember-cli-jshint": "^2.0.1",
38 | "ember-cli-qunit": "^3.0.1",
39 | "ember-cli-release": "^0.2.9",
40 | "ember-cli-sass": "5.6.0",
41 | "ember-cli-sri": "^2.1.0",
42 | "ember-cli-test-loader": "^1.1.0",
43 | "ember-cli-uglify": "^1.2.0",
44 | "ember-data": "^2.10.0",
45 | "ember-disable-prototype-extensions": "^1.1.0",
46 | "ember-export-application-global": "^1.0.5",
47 | "ember-load-initializers": "^0.5.1",
48 | "ember-resolver": "^2.0.3",
49 | "emberfire": "1.6.6",
50 | "eslint-plugin-ember-suave": "^1.0.0",
51 | "loader.js": "^4.0.10"
52 | },
53 | "keywords": [
54 | "ember-addon",
55 | "d3",
56 | "maximum-plaid",
57 | "data-visualization",
58 | "visualisation",
59 | "charts",
60 | "charting-package"
61 | ],
62 | "engines": {
63 | "node": ">= 4.6"
64 | },
65 | "ember-addon": {
66 | "configPath": "tests/dummy/config"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | "framework": "qunit",
4 | "test_page": "tests/index.html?hidepassed",
5 | "disable_watching": true,
6 | "launch_in_ci": [
7 | "PhantomJS"
8 | ],
9 | "launch_in_dev": [
10 | "PhantomJS",
11 | "Chrome"
12 | ]
13 | };
14 |
--------------------------------------------------------------------------------
/tests/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | 'embertest': true
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/tests/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "location",
6 | "setTimeout",
7 | "$",
8 | "-Promise",
9 | "define",
10 | "console",
11 | "visit",
12 | "exists",
13 | "fillIn",
14 | "click",
15 | "keyEvent",
16 | "triggerEvent",
17 | "find",
18 | "findWithAssert",
19 | "wait",
20 | "DS",
21 | "andThen",
22 | "currentURL",
23 | "currentPath",
24 | "currentRouteName"
25 | ],
26 | "node": false,
27 | "browser": false,
28 | "boss": true,
29 | "curly": true,
30 | "debug": false,
31 | "devel": false,
32 | "eqeqeq": true,
33 | "evil": true,
34 | "forin": false,
35 | "immed": false,
36 | "laxbreak": false,
37 | "newcap": true,
38 | "noarg": true,
39 | "noempty": false,
40 | "nonew": false,
41 | "nomen": false,
42 | "onevar": false,
43 | "plusplus": false,
44 | "regexp": false,
45 | "undef": true,
46 | "sub": true,
47 | "strict": false,
48 | "white": false,
49 | "eqnull": true,
50 | "esversion": 6,
51 | "unused": true
52 | }
53 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Resolver from './resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from './config/environment';
5 |
6 | let App;
7 |
8 | Ember.MODEL_FACTORY_INJECTIONS = true;
9 |
10 | App = Ember.Application.extend({
11 | modulePrefix: config.modulePrefix,
12 | podModulePrefix: config.podModulePrefix,
13 | Resolver
14 | });
15 |
16 | loadInitializers(App, config.modulePrefix);
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/tests/dummy/app/components/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/tests/dummy/app/controllers/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/controllers/index.js:
--------------------------------------------------------------------------------
1 | import Controller from 'ember-controller';
2 |
3 | export default Controller.extend({
4 |
5 | responseTimeMean: [],
6 |
7 | donutData: [
8 | { timestamp: 1450345920000, value: 1, projectId: 200 },
9 | { timestamp: 1450345930000, value: 2, projectId: 200 },
10 | { timestamp: 1450345940000, value: 3, projectId: 200 },
11 | { timestamp: 1450345950000, value: 4, projectId: 200 },
12 | { timestamp: 1450345960000, value: 5, projectId: 200 }
13 | ]
14 |
15 | });
16 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/tests/dummy/app/helpers/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 | {{content-for "head-footer"}}
16 |
17 |
18 | {{content-for "body"}}
19 |
20 |
21 |
22 |
23 | {{content-for "body-footer"}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/tests/dummy/app/models/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import config from './config/environment';
3 |
4 | const Router = Ember.Router.extend({
5 | location: config.locationType,
6 | rootURL: config.rootURL
7 | });
8 |
9 | Router.map(function() {
10 | });
11 |
12 | export default Router;
13 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/tests/dummy/app/routes/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/routes/index.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Route from 'ember-route';
3 |
4 | const {
5 | inject,
6 | RSVP: { Promise },
7 | String: { camelize }
8 | } = Ember;
9 |
10 | export default Route.extend({
11 | firebase: inject.service(),
12 |
13 | model() {
14 | let ref = this.get('firebase');
15 |
16 | return new Promise((resolve, reject) => {
17 | ref.once('value', function(snapshot) {
18 | let value = snapshot.val();
19 | let data = {};
20 | Object.keys(value).forEach((key) => {
21 | data[camelize(key)] = value[key];
22 | });
23 |
24 | resolve(data);
25 | }, reject);
26 | });
27 |
28 | },
29 |
30 | setupController(controller, { responseTimeMean }) {
31 | controller.setProperties({ responseTimeMean });
32 | }
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.scss:
--------------------------------------------------------------------------------
1 | @import "bourbon";
2 |
3 | html, body {
4 | font-family: system, sans-serif;
5 | font-size: 16px;
6 | margin: 0;
7 | padding: 0;
8 | }
9 |
10 | .Plaid-axis {
11 | path.domain, .tick line {
12 | stroke-width: 1;
13 | stroke: black;
14 | stroke-opacity: 0.54;
15 | fill: none;
16 | }
17 |
18 | .tick text {
19 | font-size: 12px;
20 | }
21 |
22 | .axis-title {
23 | font-size: 20px;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | {{outlet}}
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/charts/-line-chart.hbs:
--------------------------------------------------------------------------------
1 | {{#with (area 664 220 margin="0 75 72 16") as |plotArea|}}
2 | {{#plaid-plot
3 | (time-scale (extent (map-by "timestamp" responseTimeMean)) (array 0 plotArea.width))
4 | (linear-scale (extent (map-by "value" responseTimeMean) toZero=true) (array plotArea.height 0))
5 | plotArea as |plot|}}
6 |
7 | {{#with (pair-by "timestamp" "value" responseTimeMean) as |values|}}
8 | {{plot.line values stroke="#673AB7" strokeWidth="2" curve=(curve "basis")}}
9 | {{plot.area values fill="#D1C4E9" curve=(curve "basis")}}
10 | {{#plot.bottom-axis values ticks=10}}
11 | {{plaid-text 'Timestamps' class='axis-title' x=(div plotArea.width 2) y=40}}
12 | {{/plot.bottom-axis}}
13 | {{#plot.right-axis values tickFormat=(format-fn "0.1s" suffix="ms") ticks=2}}
14 | {{plaid-text 'Values' class='axis-title' x=50 y=(div plotArea.height 2) textRotate=90}}
15 | {{/plot.right-axis}}
16 |
17 | {{!-- {{#plot.scatter values as |x y|}}
18 |
19 |
20 | {{/plot.scatter}} --}}
21 | {{/with}}
22 | {{/plaid-plot}}
23 | {{/with}}
24 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/tests/dummy/app/templates/components/.gitkeep
--------------------------------------------------------------------------------
/tests/dummy/app/templates/index.hbs:
--------------------------------------------------------------------------------
1 | Maximum Plaid
2 |
3 | Line
4 |
5 | {{partial "charts/line-chart"}}
6 |
7 | Donut
8 | {{#with (area 664 220) as |plotArea|}}
9 | {{#plaid-plot plotArea=plotArea as |plot|}}
10 |
11 | {{plot.donut (pair-by "timestamp" "value" donutData)}}
12 |
13 | {{/plaid-plot}}
14 | {{/with}}
--------------------------------------------------------------------------------
/tests/dummy/app/templates/loading.hbs:
--------------------------------------------------------------------------------
1 |
2 | Loading...
3 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 |
3 | module.exports = function(environment) {
4 | var ENV = {
5 | modulePrefix: 'dummy',
6 | environment: environment,
7 | rootURL: '/',
8 | locationType: 'auto',
9 | EmberENV: {
10 | FEATURES: {
11 | // Here you can enable experimental features on an ember canary build
12 | // e.g. 'with-controller': true
13 | }
14 | },
15 |
16 | APP: {
17 | // Here you can pass flags/options to your application instance
18 | // when it is created
19 | },
20 |
21 | firebase: 'https://maximum-plaid.firebaseio.com/',
22 | };
23 |
24 | if (environment === 'development') {
25 | // ENV.APP.LOG_RESOLVER = true;
26 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
27 | // ENV.APP.LOG_TRANSITIONS = true;
28 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
29 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
30 | }
31 |
32 | if (environment === 'test') {
33 | // Testem prefers this...
34 | ENV.rootURL = '/';
35 | ENV.locationType = 'none';
36 |
37 | // keep test console output quieter
38 | ENV.APP.LOG_ACTIVE_GENERATION = false;
39 | ENV.APP.LOG_VIEW_LOOKUPS = false;
40 |
41 | ENV.APP.rootElement = '#ember-testing';
42 | }
43 |
44 | if (environment === 'production') {
45 | ENV.locationType = 'hash';
46 | ENV.rootURL = '/maximum-plaid/';
47 |
48 | }
49 |
50 | return ENV;
51 | };
52 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/tests/helpers/destroy-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default function destroyApp(application) {
4 | Ember.run(application, 'destroy');
5 | }
6 |
--------------------------------------------------------------------------------
/tests/helpers/module-for-acceptance.js:
--------------------------------------------------------------------------------
1 | import { module } from 'qunit';
2 | import Ember from 'ember';
3 | import startApp from '../helpers/start-app';
4 | import destroyApp from '../helpers/destroy-app';
5 |
6 | const { RSVP: { Promise } } = Ember;
7 |
8 | export default function(name, options = {}) {
9 | module(name, {
10 | beforeEach() {
11 | this.application = startApp();
12 |
13 | if (options.beforeEach) {
14 | return options.beforeEach.apply(this, arguments);
15 | }
16 | },
17 |
18 | afterEach() {
19 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
20 | return Promise.resolve(afterEach).then(() => destroyApp(this.application));
21 | }
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from '../../resolver';
2 | import config from '../../config/environment';
3 |
4 | const resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Application from '../../app';
3 | import config from '../../config/environment';
4 |
5 | export default function startApp(attrs) {
6 | let application;
7 |
8 | // use defaults, but you can override
9 | let attributes = Ember.assign({}, config.APP, attrs);
10 |
11 | Ember.run(() => {
12 | application = Application.create(attributes);
13 | application.setupForTesting();
14 | application.injectTestHelpers();
15 | });
16 |
17 | return application;
18 | }
19 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {{content-for "body-footer"}}
31 | {{content-for "test-body-footer"}}
32 |
33 |
34 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/tests/integration/.gitkeep
--------------------------------------------------------------------------------
/tests/integration/components/plaid-area/component-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('plaid-area', 'Integration | Component | plaid area', {
5 | integration: true
6 | });
7 |
8 | test('it a an area chart', function(assert) {
9 |
10 | this.set('timeSeriesData', [
11 | { timestamp: 1450345920000, value: 1, projectId: 200 },
12 | { timestamp: 1450345930000, value: 2, projectId: 200 },
13 | { timestamp: 1450345940000, value: 3, projectId: 200 },
14 | { timestamp: 1450345950000, value: 4, projectId: 200 },
15 | { timestamp: 1450345960000, value: 5, projectId: 200 }
16 | ]);
17 |
18 | this.render(hbs`
19 | {{#with (area 664 220 margin="0 50 72 16") as |plotArea|}}
20 | {{#plaid-plot
21 | (time-scale (extent (map-by "timestamp" timeSeriesData)) (array 0 plotArea.width))
22 | (linear-scale (extent (map-by "value" timeSeriesData) toZero=true) (array plotArea.height 0))
23 | plotArea as |plot|}}
24 |
25 | {{plot.area (pair-by "timestamp" "value" timeSeriesData) fill="#D1C4E9"}}
26 |
27 | {{/plaid-plot}}
28 | {{/with}}
29 | `);
30 |
31 | assert.equal(this.$('path.area').attr('d'), 'M0,118.4L149.5,88.8L299,59.2L448.5,29.599999999999994L598,0L598,148L448.5,148L299,148L149.5,148L0,148Z');
32 | });
33 |
--------------------------------------------------------------------------------
/tests/integration/components/plaid-axis/component-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('plaid-axis', 'Integration | Component | plaid axis', {
5 | integration: true
6 | });
7 |
8 | test('it renders an axis', function(assert) {
9 | this.set('timeSeriesData', [
10 | { timestamp: 1450345920000, value: 1, projectId: 200 },
11 | { timestamp: 1450345930000, value: 2, projectId: 200 },
12 | { timestamp: 1450345940000, value: 3, projectId: 200 },
13 | { timestamp: 1450345950000, value: 4, projectId: 200 },
14 | { timestamp: 1450345960000, value: 5, projectId: 200 }
15 | ]);
16 |
17 | this.render(hbs`
18 | {{#with (area 664 220 margin="0 50 72 16") as |plotArea|}}
19 | {{#plaid-plot
20 | (time-scale (extent (map-by "timestamp" timeSeriesData)) (array 0 plotArea.width))
21 | (linear-scale (extent (map-by "value" timeSeriesData) toZero=true) (array plotArea.height 0))
22 | plotArea as |plot|}}
23 |
24 | {{#with (pair-by "timestamp" "value" timeSeriesData) as |values|}}
25 | {{plot.left-axis values ticks=values.length}}
26 | {{/with}}
27 | {{/plaid-plot}}
28 | {{/with}}
29 | `);
30 |
31 | assert.equal(this.$('.axis').length, 1, 'number of axes');
32 |
33 | let ticks = this.$('.tick');
34 | assert.equal(ticks.length, this.get('timeSeriesData.length') + 1, 'number of ticks');
35 |
36 | assert.equal(this.$(ticks[0]).find('text').text(), 0, 'tick 0 text');
37 |
38 | for (let i = 1; i < ticks.length; ++i) {
39 | assert.equal(this.$(ticks[i]).find('text').text(), this.get(`timeSeriesData.${i - 1}.value`), `tick ${i} text`);
40 | }
41 | });
42 |
43 | test('it should bind the orientation as a class', function(assert) {
44 | this.set('timeSeriesData', [
45 | { timestamp: 1450345920000, value: 1, projectId: 200 },
46 | { timestamp: 1450345930000, value: 2, projectId: 200 },
47 | { timestamp: 1450345940000, value: 3, projectId: 200 },
48 | { timestamp: 1450345950000, value: 4, projectId: 200 },
49 | { timestamp: 1450345960000, value: 5, projectId: 200 }
50 | ]);
51 |
52 | this.render(hbs`
53 | {{#with (area 664 220 margin="0 50 72 16") as |plotArea|}}
54 | {{#plaid-plot
55 | (time-scale (extent (map-by "timestamp" timeSeriesData)) (array 0 plotArea.width))
56 | (linear-scale (extent (map-by "value" timeSeriesData) toZero=true) (array plotArea.height 0))
57 | plotArea as |plot|}}
58 |
59 | {{#with (pair-by "timestamp" "value" timeSeriesData) as |values|}}
60 | {{plot.left-axis values ticks=values.length}}
61 | {{/with}}
62 | {{/plaid-plot}}
63 | {{/with}}
64 | `);
65 |
66 | assert.ok(this.$('.axis.left').length, 'axis is there');
67 | });
68 |
69 | test('it should allow tick size to be customized', function(assert) {
70 | this.set('timeSeriesData', [
71 | { timestamp: 1450345920000, value: 1, projectId: 200 },
72 | { timestamp: 1450345930000, value: 2, projectId: 200 },
73 | { timestamp: 1450345940000, value: 3, projectId: 200 },
74 | { timestamp: 1450345950000, value: 4, projectId: 200 },
75 | { timestamp: 1450345960000, value: 5, projectId: 200 }
76 | ]);
77 |
78 | this.render(hbs`
79 | {{#with (area 664 220 margin="0 50 72 16") as |plotArea|}}
80 | {{#plaid-plot
81 | (time-scale (extent (map-by "timestamp" timeSeriesData)) (array 0 plotArea.width))
82 | (linear-scale (extent (map-by "value" timeSeriesData) toZero=true) (array plotArea.height 0))
83 | plotArea as |plot|}}
84 |
85 | {{#with (pair-by "timestamp" "value" timeSeriesData) as |values|}}
86 | {{plot.left-axis values ticks=values.length tickSizeInner=(mult -1 plotArea.width)}}
87 | {{/with}}
88 | {{/plaid-plot}}
89 | {{/with}}
90 | `);
91 |
92 | assert.equal(this.$('.axis').length, 1, 'number of axes');
93 |
94 | let ticks = this.$('.tick');
95 | assert.equal(ticks.length, this.get('timeSeriesData.length') + 1, 'number of ticks');
96 |
97 | for (let i = 0; i < ticks.length; ++i) {
98 | // TODO: would be nice to not magic number the width of 598
99 | assert.equal(this.$(ticks[i]).find('line').attr('x2'), 598, `tick ${i} width`);
100 | }
101 | });
102 |
--------------------------------------------------------------------------------
/tests/integration/components/plaid-bar/component-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 | import wait from 'ember-test-helpers/wait';
4 |
5 | moduleForComponent('plaid-bar', 'Integration | Component | plaid bar', {
6 | integration: true
7 | });
8 |
9 | test('it renders a vertical bar chart', function(assert) {
10 | this.set('fuelEconomy', [
11 | { mpg: 12, vehicles: 580 },
12 | { mpg: 15, vehicles: 420 },
13 | { mpg: 18, vehicles: 1000 },
14 | { mpg: 21, vehicles: 805 },
15 | { mpg: 24, vehicles: 640 },
16 | { mpg: 27, vehicles: 400 },
17 | { mpg: 30, vehicles: 380 },
18 | { mpg: 33, vehicles: 240 },
19 | { mpg: 36, vehicles: 210 },
20 | { mpg: 39, vehicles: 180 },
21 | { mpg: 42, vehicles: 205 }
22 | ]);
23 |
24 | this.render(hbs`
25 | {{#with (area 110 1000) as |plotArea|}}
26 | {{#plaid-plot
27 | xScale=(band-scale (map-by 'mpg' fuelEconomy) (array 0 plotArea.width))
28 | yScale=(linear-scale (extent (map-by 'vehicles' fuelEconomy) toZero=true) (array plotArea.height 0))
29 | plotArea=plotArea as |plot|}}
30 |
31 | {{#with (pair-by 'mpg' 'vehicles' fuelEconomy) as |values|}}
32 |
33 | {{plot.bar values orientation='vertical'}}
34 |
35 | {{/with}}
36 | {{/plaid-plot}}
37 | {{/with}}`);
38 |
39 | return wait()
40 | .then(() => {
41 | let bars = this.$('.bar');
42 |
43 | assert.ok(bars, 'bars');
44 | assert.equal(bars.length, this.get('fuelEconomy.length'), 'number of bars');
45 |
46 | for (let i = 0; i < bars.length; ++i) {
47 | let bar = bars[i];
48 | let barVehicles = this.get(`fuelEconomy.${i}.vehicles`);
49 |
50 | assert.equal(bar.getAttribute('y'), 1000 - barVehicles, `bar ${i} y`);
51 | assert.equal(bar.getAttribute('height'), barVehicles, `bar ${i} height`);
52 |
53 | assert.equal(bar.getAttribute('x'), i * 10, `bar ${i} x`);
54 | assert.equal(bar.getAttribute('width'), 10, `bar ${i} width`);
55 | }
56 | });
57 | });
58 |
59 | test('it renders correctly on resize', function(assert) {
60 | this.set('fuelEconomy', [
61 | { mpg: 12, vehicles: 580 },
62 | { mpg: 15, vehicles: 420 },
63 | { mpg: 18, vehicles: 1000 },
64 | { mpg: 21, vehicles: 805 },
65 | { mpg: 24, vehicles: 640 },
66 | { mpg: 27, vehicles: 400 },
67 | { mpg: 30, vehicles: 380 },
68 | { mpg: 33, vehicles: 240 },
69 | { mpg: 36, vehicles: 210 },
70 | { mpg: 39, vehicles: 180 },
71 | { mpg: 42, vehicles: 205 }
72 | ]);
73 |
74 | this.setProperties({
75 | width: 55,
76 | height: 500
77 | });
78 |
79 | this.render(hbs`
80 | {{#with (area width height) as |plotArea|}}
81 | {{#plaid-plot
82 | xScale=(band-scale (map-by 'mpg' fuelEconomy) (array 0 plotArea.width))
83 | yScale=(linear-scale (extent (map-by 'vehicles' fuelEconomy) toZero=true) (array plotArea.height 0))
84 | plotArea=plotArea as |plot|}}
85 |
86 | {{#with (pair-by 'mpg' 'vehicles' fuelEconomy) as |values|}}
87 |
88 | {{plot.bar values orientation='vertical'}}
89 |
90 | {{/with}}
91 | {{/plaid-plot}}
92 | {{/with}}`);
93 |
94 | return wait()
95 | .then(() => {
96 | let bars = this.$('.bar');
97 |
98 | assert.ok(bars, 'bars');
99 | assert.equal(bars.length, this.get('fuelEconomy.length'), 'number of bars');
100 |
101 | this.setProperties({
102 | width: 110,
103 | height: 1000
104 | });
105 |
106 | return wait();
107 | })
108 | .then(() => {
109 | let bars = this.$('.bar');
110 |
111 | assert.ok(bars, 'bars');
112 | assert.equal(bars.length, this.get('fuelEconomy.length'), 'number of bars');
113 |
114 | for (let i = 0; i < bars.length; ++i) {
115 | let bar = bars[i];
116 | let barVehicles = this.get(`fuelEconomy.${i}.vehicles`);
117 |
118 | assert.equal(bar.getAttribute('y'), 1000 - barVehicles, `bar ${i} y`);
119 | assert.equal(bar.getAttribute('height'), barVehicles, `bar ${i} height`);
120 |
121 | assert.equal(bar.getAttribute('x'), i * 10, `bar ${i} x`);
122 | assert.equal(bar.getAttribute('width'), 10, `bar ${i} width`);
123 | }
124 | });
125 | });
126 |
127 | test('it renders a horizontal bar chart', function(assert) {
128 | this.set('fuelEconomy', [
129 | { mpg: 12, vehicles: 580 },
130 | { mpg: 15, vehicles: 420 },
131 | { mpg: 18, vehicles: 1000 },
132 | { mpg: 21, vehicles: 805 },
133 | { mpg: 24, vehicles: 640 },
134 | { mpg: 27, vehicles: 400 },
135 | { mpg: 30, vehicles: 380 },
136 | { mpg: 33, vehicles: 240 },
137 | { mpg: 36, vehicles: 210 },
138 | { mpg: 39, vehicles: 180 },
139 | { mpg: 42, vehicles: 205 }
140 | ]);
141 |
142 | this.render(hbs`
143 | {{#with (area 1000 110) as |plotArea|}}
144 | {{#plaid-plot
145 | xScale=(linear-scale (extent (map-by 'vehicles' fuelEconomy) toZero=true) (array 0 plotArea.width))
146 | yScale=(band-scale (map-by 'mpg' fuelEconomy) (array 0 plotArea.height))
147 | plotArea=plotArea as |plot|}}
148 |
149 | {{#with (pair-by 'vehicles' 'mpg' fuelEconomy) as |values|}}
150 |
151 | {{plot.bar values orientation='horizontal'}}
152 |
153 | {{/with}}
154 | {{/plaid-plot}}
155 | {{/with}}`);
156 |
157 | return wait()
158 | .then(() => {
159 | let bars = this.$('.bar');
160 |
161 | assert.ok(bars, 'bars');
162 | assert.equal(bars.length, this.get('fuelEconomy.length'), 'number of bars');
163 |
164 | for (let i = 0; i < bars.length; ++i) {
165 | let bar = bars[i];
166 | let barVehicles = this.get(`fuelEconomy.${i}.vehicles`);
167 |
168 | assert.equal(bar.getAttribute('y'), i * 10, `bar ${i} y`);
169 | assert.equal(bar.getAttribute('height'), 10, `bar ${i} height`);
170 |
171 | assert.equal(bar.getAttribute('x'), 0, `bar ${i} x`);
172 | assert.equal(bar.getAttribute('width'), barVehicles, `bar ${i} width`);
173 | }
174 | });
175 | });
176 |
177 | test('it defaults to vertical orientation', function(assert) {
178 | this.set('fuelEconomy', [
179 | { mpg: 12, vehicles: 580 },
180 | { mpg: 15, vehicles: 420 },
181 | { mpg: 18, vehicles: 1000 },
182 | { mpg: 21, vehicles: 805 },
183 | { mpg: 24, vehicles: 640 },
184 | { mpg: 27, vehicles: 400 },
185 | { mpg: 30, vehicles: 380 },
186 | { mpg: 33, vehicles: 240 },
187 | { mpg: 36, vehicles: 210 },
188 | { mpg: 39, vehicles: 180 },
189 | { mpg: 42, vehicles: 205 }
190 | ]);
191 |
192 | this.render(hbs`
193 | {{#with (area 110 1000) as |plotArea|}}
194 | {{#plaid-plot
195 | xScale=(band-scale (map-by 'mpg' fuelEconomy) (array 0 plotArea.width))
196 | yScale=(linear-scale (extent (map-by 'vehicles' fuelEconomy) toZero=true) (array plotArea.height 0))
197 | plotArea=plotArea as |plot|}}
198 |
199 | {{#with (pair-by 'mpg' 'vehicles' fuelEconomy) as |values|}}
200 |
201 | {{plot.bar values}}
202 |
203 | {{/with}}
204 | {{/plaid-plot}}
205 | {{/with}}`);
206 |
207 | return wait()
208 | .then(() => {
209 | let bars = this.$('.bar');
210 |
211 | assert.ok(bars, 'bars');
212 | assert.equal(bars.length, this.get('fuelEconomy.length'), 'number of bars');
213 |
214 | for (let i = 0; i < bars.length; ++i) {
215 | let bar = bars[i];
216 | let barVehicles = this.get(`fuelEconomy.${i}.vehicles`);
217 |
218 | assert.equal(bar.getAttribute('y'), 1000 - barVehicles, `bar ${i} y`);
219 | assert.equal(bar.getAttribute('height'), barVehicles, `bar ${i} height`);
220 |
221 | assert.equal(bar.getAttribute('x'), i * 10, `bar ${i} x`);
222 | assert.equal(bar.getAttribute('width'), 10, `bar ${i} width`);
223 | }
224 | });
225 | });
226 |
227 | test('it errors if orientation is not "vertical" or "horizontal"', function(assert) {
228 | this.set('fuelEconomy', [
229 | { mpg: 12, vehicles: 580 },
230 | { mpg: 15, vehicles: 420 },
231 | { mpg: 18, vehicles: 1000 },
232 | { mpg: 21, vehicles: 805 },
233 | { mpg: 24, vehicles: 640 },
234 | { mpg: 27, vehicles: 400 },
235 | { mpg: 30, vehicles: 380 },
236 | { mpg: 33, vehicles: 240 },
237 | { mpg: 36, vehicles: 210 },
238 | { mpg: 39, vehicles: 180 },
239 | { mpg: 42, vehicles: 205 }
240 | ]);
241 |
242 | assert.throws(() => {
243 | this.render(hbs`
244 | {{#with (area 1000 110) as |plotArea|}}
245 | {{#plaid-plot
246 | xScale=(linear-scale (extent (map-by 'vehicles' fuelEconomy) toZero=true) (array 0 plotArea.width))
247 | yScale=(band-scale (map-by 'mpg' fuelEconomy) (array 0 plotArea.height))
248 | plotArea=plotArea as |plot|}}
249 |
250 | {{#with (pair-by 'vehicles' 'mpg' fuelEconomy) as |values|}}
251 |
252 | {{plot.bar values orientation='bad-orientation'}}
253 |
254 | {{/with}}
255 | {{/plaid-plot}}
256 | {{/with}}`);
257 | }, 'bar chart orientation must be in {vertical,horizontal}, was "bad-orientation"');
258 | });
259 |
260 | test('it errors if orientation is "vertical" and the xScale is not a band-scale', function(assert) {
261 | this.set('fuelEconomy', [
262 | { mpg: 12, vehicles: 580 },
263 | { mpg: 15, vehicles: 420 },
264 | { mpg: 18, vehicles: 1000 },
265 | { mpg: 21, vehicles: 805 },
266 | { mpg: 24, vehicles: 640 },
267 | { mpg: 27, vehicles: 400 },
268 | { mpg: 30, vehicles: 380 },
269 | { mpg: 33, vehicles: 240 },
270 | { mpg: 36, vehicles: 210 },
271 | { mpg: 39, vehicles: 180 },
272 | { mpg: 42, vehicles: 205 }
273 | ]);
274 |
275 | assert.throws(() => {
276 | this.render(hbs`
277 | {{#with (area 1000 110) as |plotArea|}}
278 | {{#plaid-plot
279 | xScale=(linear-scale (extent (map-by 'vehicles' fuelEconomy) toZero=true) (array 0 plotArea.width))
280 | yScale=(band-scale (map-by 'mpg' fuelEconomy) (array 0 plotArea.height))
281 | plotArea=plotArea as |plot|}}
282 |
283 | {{#with (pair-by 'vehicles' 'mpg' fuelEconomy) as |values|}}
284 |
285 | {{plot.bar values orientation='vertical'}}
286 |
287 | {{/with}}
288 | {{/plaid-plot}}
289 | {{/with}}`);
290 | }, 'xScale must be a band-scale for vertical bar charts');
291 | });
292 |
293 | test('it errors if orientation is "horizontal" and the yScale is not a band-scale', function(assert) {
294 | this.set('fuelEconomy', [
295 | { mpg: 12, vehicles: 580 },
296 | { mpg: 15, vehicles: 420 },
297 | { mpg: 18, vehicles: 1000 },
298 | { mpg: 21, vehicles: 805 },
299 | { mpg: 24, vehicles: 640 },
300 | { mpg: 27, vehicles: 400 },
301 | { mpg: 30, vehicles: 380 },
302 | { mpg: 33, vehicles: 240 },
303 | { mpg: 36, vehicles: 210 },
304 | { mpg: 39, vehicles: 180 },
305 | { mpg: 42, vehicles: 205 }
306 | ]);
307 |
308 | assert.throws(() => {
309 | this.render(hbs`
310 | {{#with (area 110 1000) as |plotArea|}}
311 | {{#plaid-plot
312 | xScale=(band-scale (map-by 'mpg' fuelEconomy) (array 0 plotArea.width))
313 | yScale=(linear-scale (extent (map-by 'vehicles' fuelEconomy) toZero=true) (array plotArea.height 0))
314 | plotArea=plotArea as |plot|}}
315 |
316 | {{#with (pair-by 'mpg' 'vehicles' fuelEconomy) as |values|}}
317 |
318 | {{plot.bar values orientation='horizontal'}}
319 |
320 | {{/with}}
321 | {{/plaid-plot}}
322 | {{/with}}`);
323 | }, 'yScale must be a band-scale for horizontal bar charts');
324 | });
325 |
--------------------------------------------------------------------------------
/tests/integration/components/plaid-donut/component-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('plaid-donut', 'Integration | Component | plaid donut', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | // Set any properties with this.set('myProperty', 'value');
10 | // Handle any actions with this.on('myAction', function(val) { ... });
11 |
12 | this.set('timeSeriesData', [
13 | { timestamp: 1450345920000, value: 1, projectId: 200 },
14 | { timestamp: 1450345930000, value: 2, projectId: 200 },
15 | { timestamp: 1450345940000, value: 3, projectId: 200 },
16 | { timestamp: 1450345950000, value: 4, projectId: 200 },
17 | { timestamp: 1450345960000, value: 5, projectId: 200 }
18 | ]);
19 |
20 | this.render(hbs`
21 | {{#with (area 664 220) as |plotArea|}}
22 | {{#plaid-plot plotArea=plotArea as |plot|}}
23 |
24 | {{plot.donut (pair-by "timestamp" "value" timeSeriesData)}}
25 |
26 | {{/plaid-plot}}
27 | {{/with}}
28 | `);
29 |
30 | assert.equal(this.$('.arc').length, this.get('timeSeriesData.length'), 'number of arcs');
31 | });
32 |
--------------------------------------------------------------------------------
/tests/integration/components/plaid-plot-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('plaid-plot', 'Integration | Component | plaid plot', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | // Set any properties with this.set('myProperty', 'value');
10 | // Handle any actions with this.on('myAction', function(val) { ... });
11 |
12 | this.render(hbs`{{plaid-plot}}`);
13 |
14 | assert.equal(this.$().text().trim(), '');
15 |
16 | // Template block usage:
17 | this.render(hbs`
18 | {{#plaid-plot}}
19 | template block text
20 | {{/plaid-plot}}
21 | `);
22 |
23 | assert.equal(this.$().text().trim(), 'template block text');
24 | });
25 |
--------------------------------------------------------------------------------
/tests/integration/components/plaid-symbol-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 | import Ember from 'ember';
4 | import {symbolCircle } from 'd3-shape';
5 |
6 | moduleForComponent('plaid-symbol', 'Integration | Component | plaid symbol', {
7 | integration: true
8 | });
9 |
10 | test('it renders a diamond', function(assert) {
11 | this.render(hbs`{{plaid-symbol type="diamond" size="48"}}`);
12 | assert.equal(this.$('path').attr('d'), 'M0,-6.4474195909412515L3.7224194364083982,0L0,6.4474195909412515L-3.7224194364083982,0Z');
13 | assert.ok(Ember.A(this.$('path').attr('class').split(' ')).contains('diamond'), 'has diamond class');
14 | assert.ok(Ember.A(this.$('path').attr('class').split(' ')).contains('symbol'), 'has symbol class');
15 | });
16 |
17 | test('positioning', function(assert) {
18 | this.render(hbs`{{plaid-symbol type="diamond" size="48" x="24" y="100"}}`);
19 | assert.equal(this.$('path').attr('transform'), 'translate(24,100)');
20 | });
21 |
22 | test('fill, stroke, and stroke-width', function(assert) {
23 | this.render(hbs`{{plaid-symbol fill="red" stroke="black" strokeWidth="2"}}`);
24 | assert.equal(this.$('path.symbol').attr('fill'), 'red');
25 | assert.equal(this.$('path.symbol').attr('stroke'), 'black');
26 | assert.equal(this.$('path.symbol').attr('stroke-width'), '2');
27 | });
28 |
29 | test('type as function', function(assert) {
30 | this.set('symbol', symbolCircle);
31 |
32 | this.render(hbs`{{plaid-symbol type=symbol size="48"}}`);
33 | assert.equal(this.$('path').attr('d'), 'M3.9088200952233594,0A3.9088200952233594,3.9088200952233594,0,1,1,-3.9088200952233594,0A3.9088200952233594,3.9088200952233594,0,1,1,3.9088200952233594,0');
34 | });
35 |
36 | test('shorthand', function(assert) {
37 | this.render(hbs`{{plaid-symbol "diamond" 24 100}}`);
38 | assert.equal(this.$('path').attr('transform'), 'translate(24,100)');
39 | assert.equal(this.$('path').attr('d'), 'M0,-3.7224194364083987L2.149139863647084,0L0,3.7224194364083987L-2.149139863647084,0Z');
40 | });
41 |
--------------------------------------------------------------------------------
/tests/integration/components/plaid-text/component-test.js:
--------------------------------------------------------------------------------
1 | import { moduleForComponent, test } from 'ember-qunit';
2 | import hbs from 'htmlbars-inline-precompile';
3 |
4 | moduleForComponent('plaid-text', 'Integration | Component | plaid text', {
5 | integration: true
6 | });
7 |
8 | test('it renders', function(assert) {
9 | this.render(hbs`{{plaid-text "Label Value"}}`);
10 |
11 | assert.equal(this.$().text().trim(), 'Label Value');
12 | });
13 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import {
3 | setResolver
4 | } from 'ember-qunit';
5 |
6 | setResolver(resolver);
7 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/tests/unit/.gitkeep
--------------------------------------------------------------------------------
/tests/unit/helpers/area-test.js:
--------------------------------------------------------------------------------
1 | import { area } from 'dummy/helpers/area';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | area');
5 |
6 | test('it returns an area hash', function(assert) {
7 | let result = area([640, 320], { margin: '24 16' });
8 | assert.deepEqual(result, {
9 | 'bottom': 296,
10 | 'height': 272,
11 | 'left': 16,
12 | 'outerHeight': 320,
13 | 'outerWidth': 640,
14 | 'right': 624,
15 | 'top': 24,
16 | 'width': 608,
17 | 'margin': { 'top': 24, 'left': 16, 'bottom': 24, 'right': 16 }
18 | }, 'contains correct area attributes');
19 | });
20 |
--------------------------------------------------------------------------------
/tests/unit/helpers/curve-test.js:
--------------------------------------------------------------------------------
1 | import { curve } from 'dummy/helpers/curve';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | curve');
5 |
6 | function isFunction(thing) {
7 | return typeof thing === 'function';
8 | }
9 |
10 | test('it returns a curve function by name', function(assert) {
11 | let fns = [
12 | 'basisClosed',
13 | 'basisOpen',
14 | 'basis',
15 | 'bundle',
16 | 'cardinalClosed',
17 | 'cardinalOpen',
18 | 'cardinal',
19 | 'catmullRomClosed',
20 | 'catmullRomOpen',
21 | 'catmullRom',
22 | 'linearClosed',
23 | 'linear',
24 | 'natural',
25 | 'step',
26 | 'monotone',
27 | 'monotoneX',
28 | 'monotoneY'
29 | ];
30 |
31 | assert.expect(fns.length);
32 |
33 | fns.forEach((name) => {
34 | assert.ok(isFunction(curve([name])), `${name} is available`);
35 | });
36 | });
37 |
38 | test('it accepts dasherized named', function(assert) {
39 | assert.ok(isFunction(curve(['catmull-rom-closed'])), `catmull-rom-closed is available`);
40 | });
41 |
42 | test('it tests is curve is present', function(assert) {
43 | assert.throws(function() {
44 | curve(['not-a-curve']);
45 | }, 'curve is not available');
46 | });
47 |
--------------------------------------------------------------------------------
/tests/unit/helpers/extent-test.js:
--------------------------------------------------------------------------------
1 | import { extent } from 'dummy/helpers/extent';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | extent');
5 |
6 | test('extent of flat array', function(assert) {
7 | let result = extent([[1,2,3,4]]);
8 | assert.deepEqual(result, [1,4]);
9 | });
10 |
11 | test('extent of array of objects', function(assert) {
12 | let result = extent([[
13 | { value: 1 },
14 | { value: 2 },
15 | { value: 3 },
16 | { value: 4 },
17 | { value: 5 }
18 | ], 'value']);
19 | assert.deepEqual(result, [1,5]);
20 | });
21 |
22 | test('toZero option ensures min is zero', function(assert) {
23 | let result = extent([[
24 | { value: 2 },
25 | { value: 3 },
26 | { value: 4 },
27 | { value: 5 }
28 | ], 'value'], { toZero: true });
29 | assert.deepEqual(result, [0,5]);
30 | });
31 |
--------------------------------------------------------------------------------
/tests/unit/helpers/format-fn-test.js:
--------------------------------------------------------------------------------
1 | import { formatFn } from 'dummy/helpers/format-fn';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | format fn');
5 |
6 | test('it returns a function representing a format', function(assert) {
7 | let result = formatFn(['$0.3s'])(12345);
8 |
9 | assert.deepEqual(result, '$12.3k', 'calling format returns a formatted value');
10 | });
11 |
--------------------------------------------------------------------------------
/tests/unit/helpers/format-test.js:
--------------------------------------------------------------------------------
1 | import { format } from 'dummy/helpers/format';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | format');
5 |
6 | // Replace this with your real tests.
7 | test('it works', function(assert) {
8 | let result = format([42]);
9 | assert.ok(result);
10 | });
11 |
--------------------------------------------------------------------------------
/tests/unit/helpers/linear-scale-test.js:
--------------------------------------------------------------------------------
1 | import { linearScale } from 'dummy/helpers/linear-scale';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | linear scale');
5 |
6 | test('creates a scale helper', function(assert) {
7 | let domain = [0,100];
8 | let range = [100, 1000];
9 |
10 | let result = linearScale([domain, range], {})(50);
11 | assert.equal(result, 550, 'computed correct linear scale value');
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/helpers/pair-by-test.js:
--------------------------------------------------------------------------------
1 | import { pairBy } from 'dummy/helpers/pair-by';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Helper | pair by');
5 |
6 | test('it returns an array with values for each attribute', function(assert) {
7 | let data = [
8 | { timestamp: 1450345920000, value: 1, projectId: 200 },
9 | { timestamp: 1450345930000, value: 2, projectId: 200 },
10 | { timestamp: 1450345940000, value: 3, projectId: 200 },
11 | { timestamp: 1450345950000, value: 4, projectId: 200 },
12 | { timestamp: 1450345960000, value: 5, projectId: 200 }
13 | ];
14 |
15 | let result = pairBy(['timestamp', 'value', data]);
16 | assert.deepEqual(result, [
17 | [1450345920000, 1],
18 | [1450345930000, 2],
19 | [1450345940000, 3],
20 | [1450345950000, 4],
21 | [1450345960000, 5]
22 | ]);
23 | });
24 |
--------------------------------------------------------------------------------
/tests/unit/mixins/coordinates-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import CoordinatesMixin from 'maximum-plaid/mixins/coordinates';
3 | import { module, test } from 'qunit';
4 |
5 | module('Unit | Mixin | coordinates');
6 |
7 | // Replace this with your real tests.
8 | test('it works', function(assert) {
9 | let CoordinatesObject = Ember.Object.extend(CoordinatesMixin);
10 | let subject = CoordinatesObject.create();
11 | assert.ok(subject);
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/mixins/dimensions-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import DimensionsMixin from 'maximum-plaid/mixins/dimensions';
3 | import { module, test } from 'qunit';
4 |
5 | module('Unit | Mixin | dimensions');
6 |
7 | // Replace this with your real tests.
8 | test('it works', function(assert) {
9 | let DimensionsObject = Ember.Object.extend(DimensionsMixin);
10 | let subject = DimensionsObject.create();
11 | assert.ok(subject);
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/mixins/global-resize-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import GlobalResizeMixin from 'maximum-plaid/mixins/global-resize';
3 | import { module, test } from 'qunit';
4 |
5 | module('Unit | Mixin | global resize');
6 |
7 | // Replace this with your real tests.
8 | test('it works', function(assert) {
9 | let GlobalResizeObject = Ember.Object.extend(GlobalResizeMixin);
10 | let subject = GlobalResizeObject.create();
11 | assert.ok(subject);
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/mixins/group-element-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import GroupElementMixin from 'maximum-plaid/mixins/group-element';
3 | import { module, test } from 'qunit';
4 |
5 | module('Unit | Mixin | group element');
6 |
7 | // Replace this with your real tests.
8 | test('it works', function(assert) {
9 | let GroupElementObject = Ember.Object.extend(GroupElementMixin);
10 | let subject = GroupElementObject.create();
11 | assert.ok(subject);
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/mixins/plot-area-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import PlotAreaMixin from 'maximum-plaid/mixins/plot-area';
3 | import { module, test } from 'qunit';
4 |
5 | module('Unit | Mixin | plot area');
6 |
7 | // Replace this with your real tests.
8 | test('returns computed area for plot', function(assert) {
9 | let PlotAreaObject = Ember.Object.extend(PlotAreaMixin, {
10 | margin: '10 20'
11 | });
12 |
13 | let subject = PlotAreaObject.create();
14 | assert.equal(subject.get('plotArea.top'), 10, 'plotArea.top');
15 | assert.equal(subject.get('plotArea.left'), 20, 'plotArea.left');
16 | });
17 |
18 | test('accepts object with margin', function(assert) {
19 | let PlotAreaObject = Ember.Object.extend(PlotAreaMixin, {
20 | margin: { top: 10, left: 20 },
21 | height: 200,
22 | width: 200
23 | });
24 |
25 | let subject = PlotAreaObject.create();
26 | assert.equal(subject.get('plotArea.top'), 10, 'plotArea.top');
27 | assert.equal(subject.get('plotArea.left'), 20, 'plotArea.left');
28 | assert.equal(subject.get('plotArea.right'), 200, 'plotArea.right');
29 | assert.equal(subject.get('plotArea.bottom'), 190, 'plotArea.bottom');
30 | });
31 |
32 |
--------------------------------------------------------------------------------
/tests/unit/utils/box-expression-test.js:
--------------------------------------------------------------------------------
1 | import box from 'dummy/utils/box-expression';
2 | import { module, test } from 'qunit';
3 |
4 | module('Unit | Utility | box expression');
5 |
6 | test('parsing string to object', function(assert) {
7 | let result = box('10 20');
8 | assert.deepEqual(result, {
9 | 'bottom': 10,
10 | 'left': 20,
11 | 'right': 20,
12 | 'top': 10
13 | }, 'Accepts top and left margin convention');
14 | });
15 |
16 | test('parsing object to object', function(assert) {
17 | let result = box({
18 | 'bottom': 10,
19 | 'left': 20,
20 | 'right': 20,
21 | 'top': 10
22 | });
23 |
24 | assert.deepEqual(result, {
25 | 'bottom': 10,
26 | 'left': 20,
27 | 'right': 20,
28 | 'top': 10
29 | }, 'Accepts top and left margin convention');
30 | });
31 |
32 | test('parsing object with missing sides', function(assert) {
33 | let result = box({
34 | 'bottom': 10,
35 | 'left': 20
36 | });
37 |
38 | assert.deepEqual(result, {
39 | 'bottom': 10,
40 | 'left': 20,
41 | 'right': 0,
42 | 'top': 0
43 | }, 'Accepts top and left margin convention');
44 | });
45 |
46 |
--------------------------------------------------------------------------------
/tests/unit/utils/computed-extent-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import computedExtent from 'dummy/utils/computed-extent';
3 | import { module, test } from 'qunit';
4 |
5 | module('Unit | Utility | computed extent');
6 |
7 | // Replace this with your real tests.
8 | test('it computes the extent of an array of numbers', function(assert) {
9 | let TestSubject = Ember.Object.extend({
10 | values: [1,2,3,4,5,6,7,8,9,10],
11 |
12 | extentOfNumbers: computedExtent('values.[]')
13 | });
14 |
15 | let subject = TestSubject.create();
16 | assert.deepEqual(subject.get('extentOfNumbers'), [1,10], 'includes start and end');
17 | });
18 |
19 | test('it can accept a primitive', function(assert) {
20 | let TestSubject = Ember.Object.extend({
21 | width: 510,
22 |
23 | xRange: computedExtent(0, 'width')
24 | });
25 |
26 | let subject = TestSubject.create();
27 | assert.deepEqual(subject.get('xRange'), [0, 510], 'includes 0 to width');
28 | });
29 |
30 | test('it can accept multiple dependent keys', function(assert) {
31 | let TestSubject = Ember.Object.extend({
32 | x1: 100,
33 | x2: 150,
34 |
35 | xRange: computedExtent(0, 'x1', 'x2')
36 | });
37 |
38 | let subject = TestSubject.create();
39 | assert.deepEqual(subject.get('xRange'), [0, 150], 'includes 0 to x2');
40 | });
41 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ivanvanderbyl/maximum-plaid/1ee6508917384f3083ec85170ffc5b1c707e0182/vendor/.gitkeep
--------------------------------------------------------------------------------