Skip to content

Instantly share code, notes, and snippets.

@bobmonteverde
Created March 11, 2012 06:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bobmonteverde/2015279 to your computer and use it in GitHub Desktop.
Save bobmonteverde/2015279 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
text {
font: 12px sans-serif;
}
.axis path {
fill: none;
stroke: #000;
stroke-opacity: .75;
shape-rendering: crispEdges;
}
.x.axis path.domain,
.y.axis path.domain {
stroke-opacity: .75;
}
.axis line {
fill: none;
stroke: #000;
stroke-opacity: .25;
shape-rendering: crispEdges;
}
.axis line.zero {
stroke-opacity: .75;
}
.lines path {
fill: none;
stroke-width: 1.5px;
stroke-opacity: 1;
}
.point-paths path {
stroke: none;
}
.point.hover {
stroke: #000 !important;
stroke-width: 15px;
stroke-opacity: .2;
}
</style>
<body>
<svg id="test1"></svg>
<script src="http://mbostock.github.com/d3/d3.v2.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
function genericLineChart() {
var margin = {top: 20, right: 10, bottom: 40, left: 60},
width = 960,
height = 500,
animate = 500,
dotRadius = function() { return 2.5 },
xAxisRender = true,
yAxisRender = true,
xAxisLabelText = false,
yAxisLabelText = false,
color = d3.scale.category20().range();
//TODO: consider calculating margins based on if axes, axis labels, legend, etc. exist.
var x = d3.scale.linear(),
y = d3.scale.linear(),
xAxis = d3.svg.axis().scale(x).orient('bottom'), //TODO: Time scale for time series?
yAxis = d3.svg.axis().scale(y).orient('left');
function chart(selection) {
selection.each(function(data) {
//TODO: implement optional accessors to remap data automatically
var series = data.map(function(d) { return d.data });
x .domain(d3.extent(d3.merge(series), function(d) { return d.x } ))
.range([0, width - margin.left - margin.right]);
y .domain(d3.extent(d3.merge(series), function(d) { return d.y } ))
.range([height - margin.top - margin.bottom, 0]);
xAxis.ticks( width / 100 ).tickSize(-(height - margin.top - margin.bottom), 0);
yAxis.ticks( height / 36 ).tickSize(-(width - margin.right - margin.left), 0);
var wrap = d3.select(this).selectAll('g.wrap').data([data]);
var gEnter = wrap.enter().append('g').attr('class', 'wrap d3line').append('g');
gEnter.append('g').attr('class', 'x axis');
gEnter.append('g').attr('class', 'y axis');
gEnter.append('g').attr('class', 'lines');
gEnter.append('g').attr('class', 'point-clips');
gEnter.append('g').attr('class', 'points');
gEnter.append('g').attr('class', 'point-paths');
var g = wrap.select('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
//TODO: Think of a more elegant solution to store series index and series color on data points
var vertices = d3.merge(data.map(function(d,i) {
return d.data.map(function(p) {
return [x(p.x), y(p.y), i, d.color];
})
}));
//TODO: make IDs unique for when there are multiple charts on one page
var voronoiClip = gEnter.append('g').attr('class', 'voronoi-clip')
.append('clipPath')
.attr('id', 'voronoi-clip-path')
.append('rect');
wrap.select('.voronoi-clip rect')
.attr('x', -10)
.attr('y', -10)
.attr('width', width - margin.left - margin.right + 20)
.attr('height', height - margin.top - margin.bottom + 20);
wrap.select('.point-paths')
.attr('clip-path', 'url(#voronoi-clip-path)');
var pointClips = wrap.select('.point-clips').selectAll('.clip-path')
.data(vertices);
pointClips.enter().append('clipPath').attr('class', 'clip-path')
.attr('id', function(d, i) { return 'clip-' + i })
.append('circle')
.attr('r', 20);
pointClips.exit().remove();
pointClips
.attr('transform', function(d) { return d[3] !== 'none' ?
'translate(' + d[0] + ',' + d[1] + ')' :
'translate(-100,-100)' })
var pointPaths = wrap.select('.point-paths').selectAll('path')
.data(d3.geom.voronoi(vertices));
pointPaths.enter().append('path')
.attr('class', function(d,i) { return 'path-'+i; })
.style('fill', d3.rgb(230, 230, 230,0))
.style('fill-opacity', 0);
pointPaths
.attr('clip-path', function(d,i) { return 'url(#clip-'+i+')'; })
.attr('d', function(d) { return 'M' + d.join(',') + 'Z'; })
wrap.select('.point-paths').selectAll('path')
.on('mouseover', function(d, i) {
wrap.select('circle.point-' + i)
.classed('hover', true)
//TODO: show toolip
//log(Math.round(x.invert(wrap.select('circle#point-' + i).attr('cx'))*10000)/10000,
//Math.round(y.invert(wrap.select('circle#point-' + i).attr('cy'))*10000)/10000);
})
.on('mouseout', function(d, i) {
wrap.select('circle.point-' + i)
.classed('hover', false)
//TODO: hide toolip
});
//TODO: Fix the way I do point detection so that the other points implementation
// where points are stored with their series can be used
var points = wrap.select('.points').selectAll('circle.point')
.data(vertices)
points.enter().append('circle')
.attr('class', function(d,i) { return 'point point-'+i; })
.attr('cx', function(d) { return d[0] })
.attr('cy', function(d) { return y.range()[0] })
.style('fill', function(d, i){ return d[3] || color[d[2] * 2 % 20] })
points.exit().remove();
points
.attr('r', dotRadius())
.transition().duration(animate)
.attr('cx', function(d,i) {
if (typeof d[0] === 'object') console.log("Error on Point#" + i + "d value IS :", d, "-----SHOULD BE: ", vertices[i])
return vertices[i][0]
})
.attr('cy', function(d,i) { return vertices[i][1] })
//.attr('cx', function(d) { return d[0] })
//.attr('cy', function(d) { return d[1] })
var lines = wrap.select('.lines').selectAll('.line')
.data(function(d) { return d });
lines.enter().append('g').attr('class', 'line');
lines.exit().remove();
lines
.style('fill', function(d, i){ return d.color || color[i * 2 % 20] })
.style('stroke', function(d, i){ return d.color || color[i * 2 % 20] });
var paths = lines.selectAll('path')
.data(function(d) { return [d.data] });
paths.enter().append('path')
.attr('d', d3.svg.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y.range()[0] })
);
paths.exit().remove();
paths
.transition().duration(animate)
.attr('d', d3.svg.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y(d.y) })
);
/*
//Old way I was plotting points, leaving incase I think of a better way for point detection
var points = lines.selectAll('circle.point')
.data(function(d) { return d.data });
points.enter().append('circle').attr('class', 'point')
.attr('cx', function(d) { return x(d.x) })
.attr('cy', function(d) { return y(y.domain()[0]) });
points.exit().remove();
points
.transition().duration(animate)
.attr('cx', function(d) { return x(d.x) })
.attr('cy', function(d) { return y(d.y) })
.attr('r', dotRadius());
*/
//TODO: Extract axes component with Labels for reuse
//TODO: Add point distribution on axes like scatterize demo, for scatter plots
var xAxisLabel = g.select('.x.axis').selectAll('text.axislabel')
.data([xAxisLabelText || null]);
xAxisLabel.enter().append('text').attr('class', 'axislabel')
.attr('text-anchor', 'middle')
.attr('x', x.range()[1] / 2)
.attr('y', 30);
xAxisLabel.exit().remove();
xAxisLabel.text(function(d) { return d });
var yAxisLabel = g.select('.y.axis').selectAll('text.axislabel')
.data([yAxisLabelText || null]);
yAxisLabel.enter().append('text').attr('class', 'axislabel')
.attr('transform', 'rotate(-90)')
.attr('text-anchor', 'middle')
.attr('y', -30);
yAxisLabel.exit().remove();
yAxisLabel
.attr('x', -y.range()[0] / 2)
.text(function(d) { return d });
g.select('.x.axis')
.attr('transform', 'translate(0,' + y.range()[0] + ')')
.call(xAxis)
.selectAll('line.tick')
.filter(function(d) { return !d })
.classed('zero', true);
g.select('.y.axis')
.call(yAxis)
.selectAll('line.tick')
.filter(function(d) { return !d })
.classed('zero', true);
});
return chart;
}
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.dotRadius = function(_) {
if (!arguments.length) return dotRadius;
dotRadius = d3.functor(_);
return chart;
};
chart.animate = function(_) {
if (!arguments.length) return animate;
animate = _;
return chart;
};
//TODO: fix this so that tickFormat returns instance of chart
//TODO: make axes optional
//TODO (MAYBE): expose axes directly on chart
// Expose the x-axis' tickFormat method.
chart.xAxis = {};
d3.rebind(chart.xAxis, xAxis, 'tickFormat');
chart.xAxis.label = function(_) {
if (!arguments.length) return xAxisLabelText;
xAxisLabelText = _;
return chart;
}
// Expose the y-axis' tickFormat method.
chart.yAxis = {};
d3.rebind(chart.yAxis, yAxis, 'tickFormat');
chart.yAxis.label = function(_) {
if (!arguments.length) return yAxisLabelText;
yAxisLabelText = _;
return chart;
}
return chart;
}
$(document).ready(function() {
var margin = {top: 20, right: 10, bottom: 50, left: 60},
chart = genericLineChart()
.xAxis.label('Time (ms)')
.width(width(margin))
.height(height(margin))
.yAxis.label('Voltage (v)');
//chart.xaxis.tickFormat(d3.format(".02f"))
d3.select('#test1')
.datum(sinAndCos())
.attr('width', width(margin))
.attr('height', height(margin))
.call(chart);
$(window).resize(function() {
var margin = chart.margin(),
animate = chart.animate();
chart
.animate(0)
.width(width(margin))
.height(height(margin));
d3.select('#test1')
.attr('width', width(margin))
.attr('height', height(margin))
.call(chart);
chart
.animate(animate);
});
function width(margin) {
var w = $(window).width() - 40;
return ( (w - margin.left - margin.right - 20) < 0 ) ? margin.left + margin.right + 2 : w;
}
function height(margin) {
var h = $(window).height() - 40;
return ( h - margin.top - margin.bottom - 20 < 0 ) ?
margin.top + margin.bottom + 2 : h;
}
//data
function sinAndCos() {
var sin = [],
cos = [];
for (var i = 0; i < 100; i++) {
sin.push({x: i, y: Math.sin(i/10)});
cos.push({x: i, y: .5 * Math.cos(i/10)});
}
return [
{
data: sin,
label: "Sine Wave",
color: "#ff7f0e"
},
{
data: cos,
label: "Cosine Wave",
color: "#2ca02c"
}
];
}
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment