Added another layer of abstraction to take care of all the glue and make the chart api extremely simple. First try, so not entirely complete, but does function.

master-patched
Bob Monteverde 12 years ago
parent 1880f11775
commit 4e7475da73

@ -0,0 +1,81 @@
<!DOCTYPE html>
<meta charset="utf-8">
<link href="../src/d3.css" rel="stylesheet" type="text/css">
<style>
body {
overflow-y:scroll;
}
text {
font: 12px sans-serif;
}
#chart1 {
height: 500px;
margin: 10px;
min-width: 100px;
min-height: 100px;
/*
Minimum height and width is a good idea to prevent negative SVG dimensions...
For example width should be =< margin.left + margin.right + 1,
of course 1 pixel for the entire chart would not be very useful, BUT should not have errors
*/
}
</style>
<body>
<div id="chart1">
</div>
<script src="../lib/d3.v2.js"></script>
<script src="../lib/jquery.min.js"></script>
<script src="../nv.d3.js"></script>
<script src="../src/nvtooltip.js"></script>
<script src="../src/models/legend.js"></script>
<script src="../src/models/xaxis.js"></script>
<script src="../src/models/yaxis.js"></script>
<script src="../src/models/line.js"></script>
<script src="../src/models/lineWithLegend.js"></script>
<script src="../src/charts/lineChart.js"></script>
<script>
nv.charts.line()
.data(sinAndCos())
.selector('#chart1')
.yAxisLabel('Voltage (v)')
.build();
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 [
{
values: sin,
key: "Sine Wave",
color: "#ff7f0e"
},
{
values: cos,
key: "Cosine Wave",
color: "#2ca02c"
}
];
}
</script>

@ -13,11 +13,22 @@ text {
font: 12px sans-serif;
}
#chart1 {
height: 500px;
margin: 10px;
min-width: 100px;
min-height: 100px;
/*
Minimum height and width is a good idea to prevent negative SVG dimensions...
For example width should be =< margin.left + margin.right + 1,
of course 1 pixel for the entire chart would not be very useful, BUT should not have errors
*/
}
</style>
<body>
<div id="test1">
<svg></svg>
<div id="chart1">
</div>
<script src="../lib/d3.v2.js"></script>
@ -31,86 +42,76 @@ text {
<script src="../src/models/lineWithLegend.js"></script>
<script>
/************
* Considering making an nv.charts object which will contain abstractions similar to the one below,
* but with the usual d3 reusable style.
* I could make this abstraction inside the layer above, but this layer incorporates some jQuery, so
* thinking nv.charts can be the glue that's not 100% d3
************/
var selector = '#chart1',
chart = nv.models.lineWithLegend(),
data = sinAndCos(),
xTickFormat = d3.format(',r'),
yTickFormat = d3.format(',.2f'),
xAxisLabel = null,
yAxisLabel = 'Voltage (v)',
duration = 500;
//Format A
nv.addGraph({
generate: function() {
var width = $(window).width() - 40,
height = $(window).height() - 40;
var chart = nv.models.lineWithLegend()
.width(width)
.height(height)
//.margin({top: 20, right: 10, bottom: 50, left: 80})
//chart.yAxis.axisLabel('Cumulative');
//chart.xAxis.axisLabel('Date');
var container = d3.select(selector),
width = function() { return parseInt(container.style('width')) },
height = function() { return parseInt(container.style('height')) },
svg = container.append('svg');
chart
.width(width)
.height(height)
chart.yAxis.tickFormat(d3.format(',%'))
chart.xAxis.tickFormat(function(d) {
return d3.time.format('%x')(new Date(d))
})
chart.xAxis
.tickFormat(xTickFormat);
//chart.xaxis.tickFormat(d3.format(".02f"))
chart.yAxis
.tickFormat(yTickFormat)
.axisLabel(yAxisLabel);
var svg = d3.select('#test1 svg')
.attr('width', width)
.attr('height', height)
.datum(sinAndCos())
svg.transition().duration(500).call(chart);
svg
.attr('width', width())
.attr('height', height())
.datum(data)
.transition().duration(duration).call(chart);
return chart;
},
callback: function(graph) {
graph.dispatch.on('tooltipShow', function(e) {
var offset = $('#test1').offset(),
left = e.pos[0] + offset.left,
top = e.pos[1] + offset.top,
formatterY = d3.format(",.2%"),
formatterX = function(d) {
return d3.time.format('%x')(new Date(d))
};
var content = '<h3>' + e.series.key + '</h3>' +
'<p>' +
formatterY(graph.y()(e.point)) + ' on ' + formatterX(graph.x()(e.point)) +
'</p>';
//$('#positionTest').css({'left': left, 'top': top});
nvtooltip.show([left, top], content);
});
callback: function(chart) {
var showTooltip = function(e) {
var offset = $(selector).offset(),
left = e.pos[0] + offset.left,
top = e.pos[1] + offset.top,
formatY = chart.yAxis.tickFormat(), //Assumes using same format as axis, can customize to show higher precision, etc.
formatX = chart.xAxis.tickFormat();
graph.dispatch.on('tooltipHide', function(e) {
nvtooltip.cleanup();
});
// uses the chart's getX and getY, you may customize if x position is not the same as the value you want
// ex. daily data without weekends, x is the index, while you want the date
var content = '<h3>' + e.series.key + '</h3>' +
'<p>' +
formatY(chart.y()(e.point)) + ' at ' + formatX(chart.x()(e.point)) +
'</p>';
nvtooltip.show([left, top], content);
};
chart.dispatch.on('tooltipShow', showTooltip);
chart.dispatch.on('tooltipHide', nvtooltip.cleanup);
$(window).resize(function() {
var width = $(window).width() - 40,
height = $(window).height() - 40,
margin = graph.margin();
if (width < margin.left + margin.right + 20)
width = margin.left + margin.right + 20;
if (height < margin.top + margin.bottom + 20)
height = margin.top + margin.bottom + 20;
graph
.width(width)
.height(height);
d3.select('#test1 svg')
.attr('width', width)
.attr('height', height)
.call(graph);
$(window).resize(function() {
// now that width and height are functions, should be automatic..of course you can always override them
d3.select('#chart1 svg')
.attr('width', chart.width()()) //need to set SVG dimensions, chart is not aware of the SVG component
.attr('height', chart.height()())
.call(chart);
});
}
});

@ -67,15 +67,18 @@ nv.addGraph({
},
callback: function(graph) {
/*
graph.dispatch.on('tooltipShow', function(e) {
var offset = $('#chart').offset(),
left = e.pos[0] + offset.left,
top = e.pos[1] + offset.top,
formatterY = graph.stacked.offset() == 'expand' ? d3.format(',.2%') : d3.format(',.2f'), //TODO: stacked format should be set by caller
formatterX = function(d) { return d };
/*
formatterY = d3.format(",.2%"),
formatterX = function(d) {
return d3.time.format('%x')(new Date(d))
};
*/
var content = '<h3>' + e.series.key + '</h3>' +
'<p>' +
@ -89,7 +92,6 @@ nv.addGraph({
graph.dispatch.on('tooltipHide', function(e) {
nvtooltip.cleanup();
});
*/

@ -91,9 +91,10 @@ var nv = {version: "0.0.1"};
window.nv = nv;
nv.models = {};
nv.graphs = [];
nv.log = {};
nv.models = {}; //stores all the possible models/components
nv.charts = {}; //stores all the ready to use charts
nv.graphs = []; //stores all the graphs currently on the page
nv.log = {}; //stores some statistics and potential error messages
nv.dispatch = d3.dispatch("render_start", "render_end");
@ -124,7 +125,7 @@ nv.dispatch.on("render_end", function(e) {
nv.log.endTime = +new Date;
nv.log.totalTime = nv.log.endTime - nv.log.startTime;
//log('end', nv.log.endTime);
log('total', nv.log.totalTime);
log('total', nv.log.totalTime); //used for development, to keep track of graph generation times
});
@ -1242,8 +1243,8 @@ nv.models.lineWithFocus = function() {
nv.models.lineWithLegend = function() {
var margin = {top: 30, right: 20, bottom: 50, left: 60},
width = 960,
height = 500,
getWidth = function() { return 960 },
getHeight = function() { return 500 },
dotRadius = function() { return 2.5 },
color = d3.scale.category10().range(),
dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
@ -1260,6 +1261,9 @@ nv.models.lineWithLegend = function() {
function chart(selection) {
selection.each(function(data) {
var width = getWidth(),
height = getHeight();
var series = data.filter(function(d) { return !d.disabled })
.map(function(d) { return d.values });
@ -1395,14 +1399,14 @@ nv.models.lineWithLegend = function() {
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
if (!arguments.length) return getWidth;
getWidth = d3.functor(_);
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
if (!arguments.length) return getHeight;
getHeight = d3.functor(_);
return chart;
};
@ -2042,6 +2046,7 @@ nv.models.stackedArea = function() {
* 'default' (input order)
************************************/
var lines = nv.models.line();
function chart(selection) {
selection.each(function(data) {
@ -2049,30 +2054,44 @@ nv.models.stackedArea = function() {
// Need to leave data alone to switch between stacked, stream, and expanded
var dataCopy = JSON.parse(JSON.stringify(data));
//log(dataCopy);
dataCopy = dataCopy.map(function(series) { return series.values })
//compute the data based on offset and order (calc's y0 for every point)
//dataCopy = d3.layout.stack().offset(offset).order(order).values(function(d){ return d.values })(dataCopy);
dataCopy = d3.layout.stack().offset(offset).order(order)(dataCopy);
dataCopy = d3.layout.stack().offset(offset).order(order).values(function(d){ return d.values })(dataCopy);
var mx = dataCopy[0].length - 1, // assumes that all layers have same # of samples & that there is at least one layer
var mx = dataCopy[0].values.length - 1, // assumes that all layers have same # of samples & that there is at least one layer
my = d3.max(dataCopy, function(d) {
return d3.max(d, function(d) {
return d3.max(d.values, function(d) {
return d.y0 + d.y;
});
});
lines
.width(width - margin.left - margin.right)
.height(height - margin.top - margin.bottom)
.y(function(d) { return d.y + d.y0 })
.color(data.map(function(d,i) {
return d.color || color[i % 10];
}).filter(function(d,i) { return !data[i].disabled }));
// Select the wrapper g, if it exists.
var wrap = d3.select(this).selectAll('g.d3stream').data([dataCopy]);
var wrap = d3.select(this).selectAll('g.d3stackedarea').data([dataCopy]);
// Create the skeletal chart on first load.
var gEnter = wrap.enter().append('g').attr('class', 'd3stream').append('g');
var gEnter = wrap.enter().append('g').attr('class', 'd3stackedarea').append('g');
gEnter.append('g').attr('class', 'areaWrap');
gEnter.append('g').attr('class', 'linesWrap');
var g = wrap.select('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var linesWrap = g.select('.linesWrap')
.datum(dataCopy.filter(function(d) { return !d.disabled }))
d3.transition(linesWrap).call(lines);
// Update the stacked graph
var availableWidth = width - margin.left - margin.right,
@ -2088,20 +2107,17 @@ nv.models.stackedArea = function() {
.y0(function(d) { return availableHeight - d.y0 * availableHeight / my; })
.y1(function(d) { return availableHeight - d.y0 * availableHeight / my; })
var path = g.selectAll('path')
var path = g.select('.areaWrap').selectAll('path.area')
.data(function(d) { return d });
//.data(dataCopy);
path.enter().append('path');
path.enter().append('path').attr('class', 'area');
d3.transition(path.exit())
.attr('d', zeroArea)
.attr('d', function(d,i) { return zeroArea(d.values,i) })
.remove();
path
.style('fill-opacity', .75)
.style('stroke-opacity', .75)
.style('fill', function(d,i){ return color[i % 10] })
.style('stroke', function(d,i){ return color[i % 10] });
d3.transition(path)
.attr('d', area);
.attr('d', function(d,i) { return area(d.values,i) })
});
@ -2168,6 +2184,7 @@ nv.models.stackedArea = function() {
return chart;
};
chart.dispatch = lines.dispatch;
return chart;
}
@ -2214,9 +2231,8 @@ nv.models.stackedAreaWithLegend = function() {
x .domain(d3.extent(d3.merge(series), getX ))
.range([0, width - margin.left - margin.right]);
//TODO: remove if stream
y .domain(stacked.offset() == 'zero' ?
d3.extent(d3.merge(series), getY ) :
y .domain(stacked.offset() == 'zero' ?
[0, d3.max(d3.merge(series), getY )] :
[0, 1] // 0 - 100%
)
.range([height - margin.top - margin.bottom, 0]);
@ -2291,6 +2307,7 @@ nv.models.stackedAreaWithLegend = function() {
d.hover = false;
selection.transition().call(chart)
});
*/
stacked.dispatch.on('pointMouseover.tooltip', function(e) {
dispatch.tooltipShow({
@ -2306,7 +2323,6 @@ nv.models.stackedAreaWithLegend = function() {
dispatch.tooltipHide(e);
});
*/
//TODO: margins should be adjusted based on what components are used: axes, axis labels, legend
@ -2358,7 +2374,7 @@ nv.models.stackedAreaWithLegend = function() {
.range(y.range())
.ticks( stacked.offset() == 'wiggle' ? 0 : height / 36 )
.tickSize(-(width - margin.right - margin.left), 0)
.tickFormat(stacked.offset() == 'zero' ? d3.format(',2f') : d3.format('%')); //TODO: stacked format should be set by caller
.tickFormat(stacked.offset() == 'zero' ? d3.format(',.2f') : d3.format('%')); //TODO: stacked format should be set by caller
d3.transition(g.select('.y.axis'))
.call(yAxis);
@ -2409,6 +2425,7 @@ nv.models.stackedAreaWithLegend = function() {
return chart;
};
chart.stacked = stacked;
// Expose the x-axis' tickFormat method.
//chart.xAxis = {};

4
nv.d3.min.js vendored

File diff suppressed because one or more lines are too long

@ -0,0 +1,137 @@
//may make these more specific, like 'time series line with month end data points', etc.
// or may make that yet another layer of abstraction.... trying to not get too crazy
nv.charts.line = function() {
var selector = null,
data = [],
xTickFormat = d3.format(',r'),
yTickFormat = d3.format(',.2f'),
xAxisLabel = null,
yAxisLabel = null,
duration = 500;
var graph = nv.models.lineWithLegend(),
showTooltip = function(e) { //TODO: simplify so all the calcualtions don't need to be done by the user.
var offset = $(selector).offset(),
left = e.pos[0] + offset.left,
top = e.pos[1] + offset.top,
formatY = graph.yAxis.tickFormat(), //Assumes using same format as axis, can customize to show higher precision, etc.
formatX = graph.xAxis.tickFormat();
// uses the chart's getX and getY, you may customize if x position is not the same as the value you want
// ex. daily data without weekends, x is the index, while you want the date
var content = '<h3>' + e.series.key + '</h3>' +
'<p>' +
formatY(graph.y()(e.point)) + ' at ' + formatX(graph.x()(e.point)) +
'</p>';
nvtooltip.show([left, top], content);
};
function chart() {
return chart;
}
chart.build = function() {
if (!selector || !data.length) return chart; //do nothing if you have nothing to work with
nv.addGraph({
generate: function() {
var container = d3.select(selector),
width = function() { return parseInt(container.style('width')) },
height = function() { return parseInt(container.style('height')) },
svg = container.append('svg');
graph
.width(width)
.height(height);
graph.xAxis
.tickFormat(xTickFormat);
graph.yAxis
.tickFormat(yTickFormat)
.axisLabel(yAxisLabel);
svg
.attr('width', width())
.attr('height', height())
.datum(data)
.transition().duration(duration).call(graph);
return graph;
},
callback: function(graph) {
graph.dispatch.on('tooltipShow', showTooltip);
graph.dispatch.on('tooltipHide', nvtooltip.cleanup);
$(window).resize(function() {
// now that width and height are functions, should be automatic..of course you can always override them
d3.select(selector + ' svg')
.attr('width', graph.width()()) //need to set SVG dimensions, chart is not aware of the SVG component
.attr('height', graph.height()())
.call(graph);
});
}
});
return chart;
};
chart.update = function() {
//TODO: create update code
return chart;
};
chart.data = function(_) {
if (!arguments.length) return data;
data = _;
return chart;
};
chart.selector = function(_) {
if (!arguments.length) return selector;
selector = _;
return chart;
};
chart.duration = function(_) {
if (!arguments.length) return duration;
duration = _;
return chart;
};
chart.xTickFormat = function(_) {
if (!arguments.length) return xTickFormat;
xTickFormat = _;
return chart;
};
chart.yTickFormat = function(_) {
if (!arguments.length) return yTickFormat;
yTickFormat = _;
return chart;
};
chart.xAxisLabel = function(_) {
if (!arguments.length) return xAxisLabel;
xAxisLabel = _;
return chart;
};
chart.yAxisLabel = function(_) {
if (!arguments.length) return yAxisLabel;
yAxisLabel = _;
return chart;
};
return chart;
}

@ -2,9 +2,10 @@ var nv = {version: "0.0.1"};
window.nv = nv;
nv.models = {};
nv.graphs = [];
nv.log = {};
nv.models = {}; //stores all the possible models/components
nv.charts = {}; //stores all the ready to use charts
nv.graphs = []; //stores all the graphs currently on the page
nv.log = {}; //stores some statistics and potential error messages
nv.dispatch = d3.dispatch("render_start", "render_end");
@ -35,7 +36,7 @@ nv.dispatch.on("render_end", function(e) {
nv.log.endTime = +new Date;
nv.log.totalTime = nv.log.endTime - nv.log.startTime;
//log('end', nv.log.endTime);
log('total', nv.log.totalTime);
log('total', nv.log.totalTime); //used for development, to keep track of graph generation times
});

@ -49,6 +49,7 @@
.nvtooltip p {
margin: 0;
padding: 0;
text-align: center;
}
.nvtooltip span {
@ -235,10 +236,26 @@ text {
* Stacked Area
*/
.d3stackedarea path {
.d3stackedarea path.area {
fill-opacity: .75;
stroke-opacity: .75;
}
.d3stackedarea .lines path {
stroke-opacity: 0;
}
.d3stackedarea .lines .point {
stroke-opacity: 0;
fill-opacity: 0;
transition: all 250ms linear;
-moz-transition: all 250ms linear;
-webkit-transition: all 250ms linear;
}
.d3stackedarea .lines .point.hover {
stroke-width: 20px;
stroke-opacity: .75;
fill-opacity: 1;
}

@ -1,8 +1,8 @@
nv.models.lineWithLegend = function() {
var margin = {top: 30, right: 20, bottom: 50, left: 60},
width = 960,
height = 500,
getWidth = function() { return 960 },
getHeight = function() { return 500 },
dotRadius = function() { return 2.5 },
color = d3.scale.category10().range(),
dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
@ -19,6 +19,9 @@ nv.models.lineWithLegend = function() {
function chart(selection) {
selection.each(function(data) {
var width = getWidth(),
height = getHeight();
var series = data.filter(function(d) { return !d.disabled })
.map(function(d) { return d.values });
@ -154,14 +157,14 @@ nv.models.lineWithLegend = function() {
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
if (!arguments.length) return getWidth;
getWidth = d3.functor(_);
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
if (!arguments.length) return getHeight;
getHeight = d3.functor(_);
return chart;
};

@ -20,6 +20,7 @@ nv.models.stackedArea = function() {
* 'default' (input order)
************************************/
var lines = nv.models.line();
function chart(selection) {
selection.each(function(data) {
@ -38,16 +39,33 @@ nv.models.stackedArea = function() {
});
});
lines
.width(width - margin.left - margin.right)
.height(height - margin.top - margin.bottom)
.y(function(d) { return d.y + d.y0 })
.color(data.map(function(d,i) {
return d.color || color[i % 10];
}).filter(function(d,i) { return !data[i].disabled }));
// Select the wrapper g, if it exists.
var wrap = d3.select(this).selectAll('g.d3stackedarea').data([dataCopy]);
// Create the skeletal chart on first load.
var gEnter = wrap.enter().append('g').attr('class', 'd3stackedarea').append('g');
gEnter.append('g').attr('class', 'areaWrap');
gEnter.append('g').attr('class', 'linesWrap');
var g = wrap.select('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var linesWrap = g.select('.linesWrap')
.datum(dataCopy.filter(function(d) { return !d.disabled }))
d3.transition(linesWrap).call(lines);
// Update the stacked graph
var availableWidth = width - margin.left - margin.right,
@ -63,9 +81,9 @@ nv.models.stackedArea = function() {
.y0(function(d) { return availableHeight - d.y0 * availableHeight / my; })
.y1(function(d) { return availableHeight - d.y0 * availableHeight / my; })
var path = g.selectAll('path')
var path = g.select('.areaWrap').selectAll('path.area')
.data(function(d) { return d });
path.enter().append('path');
path.enter().append('path').attr('class', 'area');
d3.transition(path.exit())
.attr('d', function(d,i) { return zeroArea(d.values,i) })
.remove();
@ -140,6 +158,7 @@ nv.models.stackedArea = function() {
return chart;
};
chart.dispatch = lines.dispatch;
return chart;
}

@ -117,6 +117,7 @@ nv.models.stackedAreaWithLegend = function() {
d.hover = false;
selection.transition().call(chart)
});
*/
stacked.dispatch.on('pointMouseover.tooltip', function(e) {
dispatch.tooltipShow({
@ -132,7 +133,6 @@ nv.models.stackedAreaWithLegend = function() {
dispatch.tooltipHide(e);
});
*/
//TODO: margins should be adjusted based on what components are used: axes, axis labels, legend
@ -184,7 +184,7 @@ nv.models.stackedAreaWithLegend = function() {
.range(y.range())
.ticks( stacked.offset() == 'wiggle' ? 0 : height / 36 )
.tickSize(-(width - margin.right - margin.left), 0)
.tickFormat(stacked.offset() == 'zero' ? d3.format(',2f') : d3.format('%')); //TODO: stacked format should be set by caller
.tickFormat(stacked.offset() == 'zero' ? d3.format(',.2f') : d3.format('%')); //TODO: stacked format should be set by caller
d3.transition(g.select('.y.axis'))
.call(yAxis);
@ -235,6 +235,7 @@ nv.models.stackedAreaWithLegend = function() {
return chart;
};
chart.stacked = stacked;
// Expose the x-axis' tickFormat method.
//chart.xAxis = {};

Loading…
Cancel
Save