4 |
5 |
6 | d3vue
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import * as d3 from 'd3'
6 | import charts from './v-charts'
7 |
8 | Vue.config.productionTip = false
9 | Vue.use(charts);
10 |
11 | Object.defineProperty(Vue.prototype, '$d3', {value: d3});
12 |
13 | /* eslint-disable no-new */
14 | new Vue({
15 | el: '#app',
16 | components: { App },
17 | template: ''
18 | })
19 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Reactive Data with Charting
4 |
Binding data to the DOM using VueJS and using lifecyles to update the associated visualizations.
5 |
6 |
7 |
8 |
9 |
21 |
22 |
59 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Brian Greig
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **If you are currently using d3vue we are halting updates on this project. We have created a new plugin that more deeply integrates with Vue using a standalone component. Check out our Github page for [v-chart-plugin](https://github.com/ignoreintuition/v-chart-plugin)**
2 |
3 | # D3 + Vue: a D3 Plugin for VueJS
4 | 
5 |
6 | > D3 integration with Vue.js
7 |
8 | 
9 |
10 | d3vue is a plugin for VueJS 2 that allows you to take data from your Vue instance and bind that data to a D3 v4 data visualization. d3vue uses the v4 merge syntax so when you can call the same function in your lifecycle events (i.e. mounted, beforeUpdate). The function signature is:
11 |
12 | ```
13 | this.$helpers.chart.barChart(this.$d3, this.dataSet, this.options);
14 | ```
15 | - this.$d3 is a reference to the d3 instances
16 | - this.dataSet is an array of objects from your instances
17 | - this.options includes
18 | - options.selector: selector name to place the graph.
19 | - options.metric: value you are measuring.
20 | - options.dim: value you will be categorizing the data by.
21 | - options.width: width of the chart.
22 | - options.height: height of the chart.
23 | - options.title: chart title
24 |
25 | Other functions are:
26 | ```
27 | this.$helpers.chart.pieChart(...)
28 | this.$helpers.chart.lineGraph(...)
29 | this.$helpers.chart.scatterPlot(...)
30 |
31 | ```
32 |
33 | ## Build Setup
34 |
35 | ``` bash
36 | # install dependencies
37 | npm install
38 |
39 | # serve with hot reload at localhost:8080
40 | npm run dev
41 |
42 | # build for production with minification
43 | npm run build
44 |
45 | # build for production and view the bundle analyzer report
46 | npm run build --report
47 | ```
48 |
49 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "d3vue",
3 | "version": "1.0.0",
4 | "description": "D3 integration with Vue.js",
5 | "author": "Brian Greig ",
6 | "license": "MIT",
7 | "private": true,
8 | "scripts": {
9 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
10 | "start": "npm run dev",
11 | "build": "node build/build.js"
12 | },
13 | "dependencies": {
14 | "atob": "^2.1.2",
15 | "d3": "^5.0.0",
16 | "macaddress": "^0.2.9",
17 | "node-macaddress": "^0.2.4",
18 | "randomatic": "^3.1.0",
19 | "url-parse": "^1.4.3",
20 | "vue": "^2.5.2"
21 | },
22 | "devDependencies": {
23 | "autoprefixer": "^7.1.2",
24 | "babel-core": "^6.22.1",
25 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
26 | "babel-loader": "^7.1.1",
27 | "babel-plugin-syntax-jsx": "^6.18.0",
28 | "babel-plugin-transform-runtime": "^6.22.0",
29 | "babel-plugin-transform-vue-jsx": "^3.5.0",
30 | "babel-preset-env": "^1.3.2",
31 | "babel-preset-stage-2": "^6.22.0",
32 | "chalk": "^2.0.1",
33 | "copy-webpack-plugin": "^4.0.1",
34 | "css-loader": "^0.28.0",
35 | "extract-text-webpack-plugin": "^3.0.0",
36 | "file-loader": "^1.1.4",
37 | "friendly-errors-webpack-plugin": "^1.6.1",
38 | "html-webpack-plugin": "^2.30.1",
39 | "node-notifier": "^5.1.2",
40 | "optimize-css-assets-webpack-plugin": "^3.2.0",
41 | "ora": "^1.2.0",
42 | "portfinder": "^1.0.13",
43 | "postcss-import": "^11.0.0",
44 | "postcss-loader": "^2.0.8",
45 | "postcss-url": "^7.2.1",
46 | "rimraf": "^2.6.0",
47 | "semver": "^5.3.0",
48 | "shelljs": "^0.7.6",
49 | "uglifyjs-webpack-plugin": "^1.1.1",
50 | "url-loader": "^0.5.8",
51 | "vue-loader": "^13.3.0",
52 | "vue-style-loader": "^3.0.1",
53 | "vue-template-compiler": "^2.5.2",
54 | "webpack": "^3.6.0",
55 | "webpack-bundle-analyzer": "^3.3.2",
56 | "webpack-dev-server": "^3.1.11",
57 | "webpack-merge": "^4.1.0"
58 | },
59 | "engines": {
60 | "node": ">= 6.0.0",
61 | "npm": ">= 3.0.0"
62 | },
63 | "browserslist": [
64 | "> 1%",
65 | "last 2 versions",
66 | "not ie <= 8"
67 | ]
68 | }
69 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/d3-vue-example/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: '/',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: true,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
83 |
84 |
85 |
111 |
--------------------------------------------------------------------------------
/src/v-charts/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | install: function(Vue) {
3 | Vue.prototype.$helpers = {
4 | chart: {
5 | d3: {},
6 | ds: {},
7 | /**
8 | * $helpers.chart.barChart
9 | * bind data to a bar graph.
10 | * @param {string} d3 - reference to d3 object.
11 | * @param {string} ds - dataset for the graph.
12 | * @param {Object} options - options for bar graph.
13 | * @param {string} options.selector - selector name to place the graph.
14 | * @param {string} options.metric - value you are measuring.
15 | * @param {string} options.dim - value you will be categorizing the data by.
16 | * @param {string} options.width - width of the chart.
17 | * @param {string} options.height - height of the chart.
18 | * @param {string} options.title - title of the chart.
19 | */
20 | barChart: function(d3, ds, options) {
21 | var metric = options.metric;
22 | var svg = this.init(d3, ds, options.selector);
23 | var offset = options.title ? 20 : 0;
24 | var g = svg.selectAll('rect')
25 | .data(this.ds);
26 |
27 | var maxVal = Math.max.apply(Math, this.ds.map(function(o) {
28 | return o[options.metric];
29 | }));
30 |
31 | var yScale = this.d3.scaleLinear()
32 | .domain([0, maxVal])
33 | .range([options.height, 0]);
34 |
35 | var yAxis = this.d3.axisLeft()
36 | .scale(yScale);
37 |
38 | var xScale = this.initOrdinalScale(options.dim, options.width);
39 | var xAxis = this.d3.axisBottom()
40 | .scale(xScale)
41 |
42 | svg.selectAll('g').remove();
43 | if (options.title) this.addTitle(options.title, svg, options.width);
44 |
45 | g.enter()
46 | .append('rect')
47 | .merge(g)
48 | .attr('class', 'bar')
49 | .attr('width', (d, i) => {
50 | return (options.width / this.ds.length) - 1
51 | })
52 | .attr('height', d => {
53 | return options.height - yScale(d[options.metric])
54 | })
55 | .attr('x', (d, i) => {
56 | return (i * (options.width / this.ds.length)) + 60
57 | })
58 | .attr('y', d => {
59 | return yScale(d[options.metric]);
60 | })
61 | .on('mouseover', d => {
62 | this.addTooltip(d, svg,
63 | this.d3.mouse(this.d3.event.currentTarget)[0],
64 | this.d3.mouse(this.d3.event.currentTarget)[1], metric)
65 | })
66 | .on('mouseout', d => {
67 | this.removeTooltip(svg);
68 | })
69 | .attr('transform', 'translate(0,' + offset + ')');
70 |
71 | this.drawAxis(options.height, svg, xAxis, yAxis, offset);
72 | g.exit().remove();
73 | },
74 |
75 | /**
76 | * $helpers.chart.lineChart
77 | * bind data to a line graph.
78 | * @param {string} d3 - reference to d3 object.
79 | * @param {string} ds - dataset for the graph.
80 | * @param {Object} options - options for bar graph.
81 | * @param {string} options.selector - selector name to place the graph.
82 | * @param {string} options.metric - value you are measuring.
83 | * @param {string} options.dim - value you will be categorizing the data by.
84 | * @param {string} options.width - width of the chart.
85 | * @param {string} options.height - height of the chart.
86 | * @param {string} options.title - title of the chart.
87 | */
88 | lineChart: function(d3, ds, options) {
89 | var metric = options.metric;
90 | var svg = this.init(d3, ds, options.selector);
91 | var offset = options.title ? 20 : 0;
92 | var maxVal = Math.max.apply(Math, this.ds.map(function(o) {
93 | return o[options.metric];
94 | }));
95 |
96 | var minVal = Math.min.apply(Math, this.ds.map(function(o) {
97 | return o[options.metric];
98 | }));
99 |
100 | var yScale = this.d3.scaleLinear()
101 | .domain([minVal, maxVal])
102 | .range([options.height, 0]);
103 |
104 | var yAxis = this.d3.axisLeft()
105 | .scale(yScale);
106 |
107 | var xScale = this.initOrdinalScale(options.dim, options.width);
108 | var xAxis = this.d3.axisBottom()
109 | .scale(xScale)
110 |
111 | var lineFunction = this.d3.line()
112 | .x(function(d, i) {
113 | return xScale(d[options.dim]) + 60;
114 | })
115 | .y(function(d) {
116 | return yScale(d[options.metric]);
117 | })
118 |
119 | svg.selectAll('path').remove();
120 | svg.selectAll('g').remove();
121 |
122 |
123 | if (options.title) this.addTitle(options.title, svg, options.width);
124 |
125 | svg.append('path')
126 | .datum(this.ds)
127 | .attr('fill', 'none')
128 | .attr('stroke', '#ffab00')
129 | .attr('stroke-width', 3)
130 | .attr('d', lineFunction)
131 | .attr('transform', 'translate(0,' + offset + ')');
132 |
133 | this.drawAxis(options.height, svg, xAxis, yAxis, offset);
134 |
135 | svg.exit().remove();
136 | },
137 | /**
138 | * $helpers.chart.pieChart
139 | * bind data to a pie chart.
140 | * @param {string} d3 - reference to d3 object.
141 | * @param {string} ds - dataset for the graph.
142 | * @param {Object} options - options for bar graph.
143 | * @param {string} options.selector - selector name to place the graph.
144 | * @param {string} options.metric - value you are measuring.
145 | * @param {string} options.dim - value you will be categorizing the data by.
146 | * @param {string} options.width - width of the chart.
147 | * @param {string} options.height - height of the chart.
148 | * @param {string} options.title - title of the chart.
149 | */
150 | pieChart: function(d3, ds, options) {
151 | var metric = options.metric;
152 | var svg = this.init(d3, ds, options.selector);
153 | var radius = options.height > options.width ? (options.width - options.width * 0.1) / 2 : (options.height - options.height * 0.1) / 2;
154 | var offset = options.title ? 20 : 0;
155 |
156 | var pie = this.d3.pie()
157 | .sort(null)
158 | .value(function(ds) {
159 | return ds[options.metric];
160 | });
161 |
162 | var path = this.d3.arc()
163 | .outerRadius(radius - 10)
164 | .innerRadius(25);
165 |
166 | var arc = svg.selectAll('.arc')
167 | .data(pie(ds))
168 |
169 | var color = d3.scaleOrdinal()
170 | .range(['#4D4D4D', '#5DA5DA', '#FAA43A', '#60BD68', '#F17CB0',
171 | '#B2912F', '#B276B2', '#DECF3F', '#F15854'
172 | ])
173 | if (options.title) this.addTitle(options.title, svg, options.width);
174 |
175 | arc.enter()
176 | .append('g')
177 | .attr('transform', 'translate(' + options.width / 2 + ',' + options.height / 2 + ')')
178 | .append('path')
179 | .merge(arc)
180 | .attr('class', 'arc')
181 | .attr('d', path)
182 | .attr('fill', function(d, i) {
183 | return color(i);
184 | })
185 | .on('mouseover', d => {
186 | this.addTooltip(d.data, svg, options.width, 0, metric)
187 | })
188 | .on('mouseout', d => {
189 | this.removeTooltip(svg);
190 | })
191 | .attr('transform', 'translate(0,' + offset + ')');
192 |
193 | arc.exit().remove();
194 |
195 | },
196 | /**
197 | * $helpers.chart.scatterPlot
198 | * bind data to a scatter plot.
199 | * @param {string} d3 - reference to d3 object.
200 | * @param {string} ds - dataset for the graph.
201 | * @param {Object} options - options for bar graph.
202 | * @param {string} options.selector - selector name to place the graph.
203 | * @param {string} options.metric - value you are measuring.
204 | * @param {string} options.dim - value you will be categorizing the data by.
205 | * @param {string} options.width - width of the chart.
206 | * @param {string} options.height - height of the chart.
207 | * @param {string} options.title - title of the chart.
208 | */
209 | scatterPlot: function(d3, ds, options) {
210 | var metric = options.metric;
211 | var svg = this.init(d3, ds, options.selector);
212 | var offset = options.title ? 20 : 0;
213 | var maxVal = Math.max.apply(Math, this.ds.map(function(o) {
214 | return o[options.metric];
215 | }));
216 |
217 | var minVal = Math.min.apply(Math, this.ds.map(function(o) {
218 | return o[options.metric];
219 | }));
220 |
221 | var maxVal2 = Math.max.apply(Math, this.ds.map(function(o) {
222 | return o[options.metric2];
223 | }));
224 |
225 | var minVal2 = Math.min.apply(Math, this.ds.map(function(o) {
226 | return o[options.metric2];
227 | }));
228 |
229 | var g = svg.selectAll('circle')
230 | .data(this.ds);
231 |
232 | var yScale = this.d3.scaleLinear()
233 | .domain([minVal, maxVal])
234 | .range([options.height, 0]);
235 |
236 | var yAxis = this.d3.axisLeft()
237 | .scale(yScale);
238 |
239 | var xScale = this.d3.scaleLinear()
240 | .domain([minVal2, maxVal2])
241 | .range([0, options.width]);
242 |
243 | var xAxis = this.d3.axisBottom()
244 | .scale(xScale)
245 |
246 | svg.selectAll('g').remove();
247 |
248 | if (options.title) this.addTitle(options.title, svg, options.width);
249 |
250 | g.enter()
251 | .append('circle')
252 | .attr('r', '4')
253 | .attr('class', 'point')
254 | .merge(g)
255 | .attr('cx', (d, i) => {
256 | return (xScale(d[options.metric2])) + 60
257 | })
258 | .attr('cy', d => {
259 | return yScale(d[options.metric]);
260 | })
261 | .attr('transform', 'translate(0,' + offset + ')')
262 |
263 | this.drawAxis(options.height, svg, xAxis, yAxis, offset);
264 |
265 | svg.exit().remove();
266 | },
267 |
268 | /* Helper Function */
269 | init: function(d3, ds, selector) {
270 | this.d3 = d3;
271 | this.ds = ds;
272 | return this.d3.select(selector)
273 | },
274 |
275 | initOrdinalScale: function(dim, width) {
276 | var domainArr = [];
277 | var rangeArr = [];
278 |
279 | this.ds.forEach((t) => {
280 | domainArr.push(t[dim])
281 | })
282 | this.ds.forEach((t, i) => {
283 | rangeArr.push(width * i / this.ds.length)
284 | })
285 |
286 | var xScale = this.d3.scaleOrdinal()
287 | .domain(domainArr)
288 | .range(rangeArr);
289 | return xScale;
290 | },
291 |
292 | drawAxis: function(height, svg, xAxis, yAxis, offset) {
293 | offset = offset || 0;
294 | svg.append('g')
295 | .attr('transform', 'translate(50,' + offset + ')')
296 | .call(yAxis);
297 |
298 | svg.append('g')
299 | .attr('transform', 'translate(70,' + (height + offset + 5) + ')')
300 | .call(xAxis);
301 | },
302 |
303 | addTooltip: function(d, svg, x, y, v) {
304 | svg.append('text')
305 | .attr('x', x)
306 | .attr('y', y)
307 | .attr('class', 'tt')
308 | .text(d.name + ': ' + d[v]);
309 | },
310 |
311 | removeTooltip: function(svg) {
312 | svg.selectAll('.tt').remove();
313 | },
314 |
315 | addTitle: function(t, svg, w) {
316 | svg.append('text')
317 | .attr('x', w / 2)
318 | .attr('text-anchor', 'middle')
319 | .attr('y', 0)
320 | .text(t);
321 | }
322 | }
323 | }
324 | }
325 | }
326 |
--------------------------------------------------------------------------------