Skip to content

Instantly share code, notes, and snippets.

@gtb104
Last active August 11, 2016 18:43
Show Gist options
  • Save gtb104/1d3589ae29f469247b9b86ea2cc48c46 to your computer and use it in GitHub Desktop.
Save gtb104/1d3589ae29f469247b9b86ea2cc48c46 to your computer and use it in GitHub Desktop.
Non Declarative D3

Non Declarative D3

This is a non-declarative approach to creating reusable D3 graph.

The main method of reusability will be a library of modules used to create specific portions of a D3 graph.

import Ember from 'ember';
export default Ember.Controller.extend({
appName: 'Non Declarative D3',
graphData: [[
{x:1469385570980, y:3},
{x:1469731166074, y:6},
{x:1469903961473, y:5},
{x:1470076750392, y:2}
]],
init() {
this._super(...arguments);
Ember.run.later(this, () => {
console.log('ADD DATA!!!!!');
this.set('graphData', [[
{x:1469385570980, y:3},
{x:1469731166074, y:6},
{x:1469903961473, y:5},
{x:1470076750392, y:2},
{x:new Date().getTime(), y:7}
]]);
}, 3000);
}
});
const min = (d, accessorFn) => d3.min(d.map(_d => d3.min(_d, accessorFn)));
const max = (d, accessorFn) => d3.max(d.map(d => d3.max(d, accessorFn)));
const extent = (d, accessorFn) => [min(d, accessorFn), max(d, accessorFn)];
const computeExtent = (data, accessorFn) => {
return extent(data, accessorFn);
};
const createScale = (scaleFn, domain, range) => {
return scaleFn().domain(domain).range(range).clamp(true);
};
export default { computeExtent, createScale };
/**
* Acceptable curve values
* d3.curveBasis
* d3.curveBasisClosed
* d3.curveBasisOpen
* d3.curveBundle
* d3.curveCardinal
* d3.curveCardinalClosed
* d3.curveCardinalOpen
* d3.curveCatmullRom
* d3.curveCatmullRomClosed
* d3.curveCatmullRomOpen
* d3.curveLinear
* d3.curveLinearClosed
* d3.curveMonotoneX
* d3.curveMonotoneY
* d3.curveNatural
* d3.curveStep
* d3.curveStepAfter
* d3.curveStepBefore
*/
const createLine = (xAccessorFn, yAccessorFn, curve) => {
return d3.line().x(xAccessorFn).y(yAccessorFn).curve(curve);
};
const createArea = (xAccessorFn, yAccessorFn, height, curve) => {
return d3.area().x(xAccessorFn).y0(height).y1(yAccessorFn).curve(curve);
};
export default { createLine, createArea }
const calcGraphWidth = (width, marginLeft, marginRight) => width - marginLeft - marginRight;
const calcGraphHeight = (height, marginTop, marginBottom) => height - marginTop - marginBottom;
const baseSvg = (element, width, height, margin) => {
const svg = d3.select(element).append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
svg.append('rect')
.attr('width', width - margin.left - margin.right)
.attr('height', height - margin.bottom - margin.top)
.attr('class', 'chart-background');
return svg;
};
export default { baseSvg, calcGraphWidth, calcGraphHeight };
import Ember from 'ember';
import Chart from '../lib/chart';
import Scales from '../lib/chart-scale';
import Shapes from '../lib/chart-shapes';
const {
Component,
computed
} = Ember;
const log = console.log;
export default Component.extend({
width: 400,
height: 150,
showXaxis: true,
showYaxis: false,
// Adjust margins so that the axes fit
margin: {top: 5, bottom: 30, left: 30, right: 0},
graphWidth: computed('width', 'margin', function() {
const width = this.get('width');
const margin = this.get('margin');
return Chart.calcGraphWidth(width, margin.left, margin.right);
}),
graphHeight: computed('height', 'margin', function() {
const height = this.get('height');
const margin = this.get('margin');
return Chart.calcGraphWidth(height, margin.top, margin.bottom);
}),
xScale: computed('graphWidth', 'data', function() {
const data = this.getAttr('data');
const domain = Scales.computeExtent(data, (d) => d.x);
const graphWidth = this.get('graphWidth');
const range = [0, graphWidth];
return Scales.createScale(d3.scaleTime, domain, range);
}),
yScale: computed('graphHeight', 'data', function() {
const data = this.getAttr('data');
const domain = Scales.computeExtent(data, (d) => d.y);
const graphHeight = this.get('graphHeight');
const range = [graphHeight, 0];
return Scales.createScale(d3.scaleLinear, domain, range);
}),
xAxisTransform: computed('graphHeight', function() {
const graphHeight = this.get('graphHeight');
return `translate(0,${graphHeight})`;
}),
lineFn: computed('xScale', 'yScale', function() {
const xScale = this.get('xScale');
const yScale = this.get('yScale');
const xAccessor = (d) => xScale(d.x);
const yAccessor = (d) => yScale(d.y);
return Shapes.createLine(xAccessor, yAccessor, d3.curveLinear);
}),
didUpdateAttrs(attrs) {
this._super(...arguments);
const data = this.getAttr('data');
const lineFn = this.get('lineFn');
const xScale = this.get('xScale');
const yScale = this.get('yScale');
this.redrawPath(data, lineFn);
if (this.get('showXaxis')) {
this.redrawXaxis(xScale);
}
if (this.get('showYaxis')) {
this.redrawYaxis(yScale);
}
},
didInsertElement() {
this._super(...arguments);
const el = this.element;
const width = this.get('width');
const height = this.get('height');
const margin = this.get('margin');
const svg = Chart.baseSvg(el, width, height, margin);
const lineFn = this.get('lineFn');
const xScale = this.get('xScale');
const yScale = this.get('yScale');
const data = this.getAttr('data');
const path = svg.append('path')
.data(data)
.attr('class', 'chart-line')
.attr('d', lineFn);
if (this.get('showXaxis')) {
const xAxis = d3.axisBottom(xScale);
const xAxisTransform = this.get('xAxisTransform');
svg.append('g')
.attr('class', 'axis xAxis')
.attr('transform', xAxisTransform)
.call(xAxis)
.selectAll('text')
.attr('transform', 'rotate(-25)')
.style('text-anchor', 'end');
}
if (this.get('showYaxis')) {
const yAxis = d3.axisLeft(yScale);
svg.append('g')
.attr('class', 'axis yAxis')
.call(yAxis);
}
},
redrawPath(data, lineFn) {
d3.select('.chart-line')
.data(data)
.transition().duration(750)
.attr('d', lineFn);
},
redrawXaxis(xScale) {
d3.select('.xAxis')
.transition().duration(750)
.call(d3.axisBottom(xScale))
.selectAll('text')
.attr('transform', 'rotate(-25)')
.style('text-anchor', 'end');
},
redrawYaxis(yScale) {
d3.select('.yAxis')
.transition().duration(750)
.call(d3.axisLeft(yScale));
}
});
body {
margin: 12px 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 12pt;
}
.chart-background {
fill: #f9f9f9;
}
.chart-line {
fill: none;
stroke: steelblue;
stroke-width: 2;
}
.chart-area {
fill: lightsteelblue;
stroke: steelblue;
stroke-width: 2;
}
.tick line,
.tick text {
shape-rendering: crispEdges;
}
<h1>Welcome to {{appName}}</h1>
{{line-chart
data=graphData
}}
{
"version": "0.10.5",
"EmberENV": {
"FEATURES": {}
},
"options": {
"use_pods": true,
"enable-testing": false
},
"dependencies": {
"jquery": "https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.js",
"ember": "2.7.0",
"ember-template-compiler": "2.7.0",
"d3": "https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.1/d3.min.js"
},
"addons": {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment