|
var HEIGHT = 200; |
|
|
|
var chart = new d3Kit.Skeleton('#chart', { |
|
margin: {top: 30, right: 25, bottom: 30, left: 60}, |
|
initialHeight: HEIGHT*2+100 |
|
}) |
|
.autoResize('width') |
|
.on('resize', visualize) |
|
.on('data', visualize); |
|
|
|
var layers = chart.getLayerOrganizer(); |
|
layers.create([ |
|
'x-axis', |
|
{'length': ['axis','graph']}, |
|
{'mass':['axis','graph']}, |
|
{'cursor': ['flag']}, |
|
'cover' |
|
]); |
|
|
|
layers.get('x-axis') |
|
.classed('axis', true) |
|
.attr('transform', 'translate('+0+','+(HEIGHT+10)+')'); |
|
layers.get('length.axis') |
|
.classed('axis', true); |
|
layers.get('mass.axis') |
|
.classed('axis', true); |
|
layers.get('mass') |
|
.attr('transform', 'translate('+0+','+(HEIGHT+40)+')'); |
|
|
|
layers.get('length').append('text') |
|
.classed('chart-title', true) |
|
.attr('x', 10) |
|
.attr('y', 20) |
|
.text('Length (cm)'); |
|
|
|
layers.get('mass').append('text') |
|
.classed('chart-title', true) |
|
.attr('x', 10) |
|
.attr('y', 20) |
|
.text('Weight (g)'); |
|
|
|
layers.get('x-axis').append('text') |
|
.classed('chart-title', true) |
|
.attr('x', chart.getInnerWidth()/2) |
|
.attr('y', 40) |
|
.style('text-anchor', 'middle') |
|
.text('Age (weeks)'); |
|
|
|
layers.get('cursor') |
|
.style('opacity', 0) |
|
.append('line') |
|
.classed('cursor', true) |
|
.attr('y1', 0) |
|
.attr('y2', chart.getInnerHeight()); |
|
|
|
layers.get('cursor.flag').append('rect') |
|
.attr('width', 260) |
|
.attr('height', 50); |
|
layers.get('cursor.flag').append('text') |
|
.attr('x', 7) |
|
.attr('y', 20) |
|
.classed('week', true); |
|
layers.get('cursor.flag').append('text') |
|
.attr('x', 7) |
|
.attr('y', 40) |
|
.classed('estimated', true); |
|
|
|
var cover = layers.get('cover').append('rect') |
|
.style('fill', 'rgba(255,255,255,0)') |
|
.on('mouseover', showCursor) |
|
.on('mousemove', moveCursor) |
|
.on('mouseout', hideCursor); |
|
|
|
var xScale = d3.scale.linear(); |
|
var yScale1 = d3.scale.linear(); |
|
var yScale2 = d3.scale.linear(); |
|
var format = d3.format(',.2f'); |
|
|
|
var xAxis = d3.svg.axis() |
|
.scale(xScale) |
|
.tickValues([8,12,16,20,24,28,32,36,40]) |
|
.orient('bottom'); |
|
|
|
var yAxis1 = d3.svg.axis() |
|
.scale(yScale1) |
|
.orient('left'); |
|
|
|
var yAxis2 = d3.svg.axis() |
|
.scale(yScale2) |
|
.orient('left'); |
|
|
|
var area1 = d3.svg.area() |
|
.interpolate('basis') |
|
.x(function(d) { return xScale(d.week); }) |
|
.y0(200) |
|
.y1(function(d) { return yScale1(d.length); }); |
|
|
|
var area2 = d3.svg.area() |
|
.interpolate('basis') |
|
.x(function(d) { return xScale(d.week); }) |
|
.y0(200) |
|
.y1(function(d) { return yScale2(d.mass); }); |
|
|
|
function visualize(){ |
|
var data = chart.data(); |
|
|
|
if(!data) return; |
|
|
|
xScale.domain(d3.extent(data, function(d){return d.week;})) |
|
.range([0, chart.getInnerWidth()]); |
|
yScale1.domain(d3.extent(data, function(d){return d.length;})) |
|
.range([200, 0]); |
|
yScale2.domain(d3.extent(data, function(d){return d.mass;})) |
|
.range([200, 0]); |
|
|
|
drawLength(data); |
|
drawMass(data); |
|
|
|
layers.get('x-axis').select('text.chart-title').attr('x', chart.getInnerWidth()/2); |
|
layers.get('x-axis').call(xAxis); |
|
|
|
cover |
|
.attr('width', chart.getInnerWidth()) |
|
.attr('height', chart.getInnerHeight()); |
|
} |
|
|
|
layers.get('length.graph').append('path'); |
|
layers.get('mass.graph').append('path'); |
|
|
|
function drawLength(data){ |
|
var container = layers.get('length.graph'); |
|
|
|
container.select('path') |
|
.datum(data) |
|
.classed('area', true) |
|
.attr('d', area1); |
|
|
|
layers.get('length.axis') |
|
.call(yAxis1); |
|
|
|
var selection = container.selectAll('circle.point') |
|
.data(data, function(d){return d.week;}); |
|
|
|
selection.enter() |
|
.append('circle') |
|
.classed('point', true) |
|
.attr('r', 3) |
|
.attr('cx', function(d){return xScale(d.week);}) |
|
.attr('cy', function(d){return yScale1(d.length);}); |
|
|
|
selection |
|
.attr('cx', function(d){return xScale(d.week);}) |
|
.attr('cy', function(d){return yScale1(d.length);}); |
|
} |
|
|
|
function drawMass(data){ |
|
var container = layers.get('mass.graph'); |
|
|
|
container.select('path') |
|
.datum(data) |
|
.classed('area', true) |
|
.attr('d', area2); |
|
|
|
layers.get('mass.axis') |
|
.call(yAxis2); |
|
|
|
var selection = container.selectAll('circle.point') |
|
.data(data, function(d){return d.week;}); |
|
|
|
selection.enter() |
|
.append('circle') |
|
.classed('point', true) |
|
.attr('r', 3) |
|
.attr('cx', function(d){return xScale(d.week);}) |
|
.attr('cy', function(d){return yScale2(d.mass);}); |
|
|
|
selection |
|
.attr('cx', function(d){return xScale(d.week);}) |
|
.attr('cy', function(d){return yScale2(d.mass);}); |
|
} |
|
|
|
function showCursor(){ |
|
layers.get('cursor') |
|
.transition() |
|
.style('opacity', 1); |
|
|
|
moveCursor(); |
|
} |
|
|
|
function moveCursor(){ |
|
var pos = d3.mouse(layers.get('cover').node()); |
|
var week = xScale.invert(pos[0]); |
|
|
|
var roundedWeek = Math.floor(week); |
|
var estimatedDay = Math.floor((week - roundedWeek) * 7); |
|
var estimatedWeek = roundedWeek + estimatedDay * 1/7; |
|
|
|
var cursorX = xScale(estimatedWeek); |
|
|
|
layers.get('cursor') |
|
.attr('transform', 'translate('+cursorX+','+0+')'); |
|
|
|
var flagX = chart.getInnerWidth() - cursorX < 260 ? -260 : 0; |
|
var data = chart.data(); |
|
var estimated = data ? interpolate(data, estimatedWeek) : { |
|
week: 0, |
|
length: 0, |
|
mass: 0 |
|
}; |
|
|
|
layers.get('cursor.flag') |
|
.attr('transform', 'translate('+flagX+','+(pos[1]-55)+')'); |
|
|
|
layers.get('cursor.flag').select('text.week') |
|
.text(formatWeek(roundedWeek, estimatedDay)); |
|
|
|
layers.get('cursor.flag').select('text.estimated') |
|
.text('Length: '+format(estimated.length)+'cm, Weight: '+format(estimated.mass)+'g'); |
|
} |
|
|
|
function formatWeek(week, day){ |
|
return week + ' weeks' + (day>0? (' ' + day + ' day'+(day>1?'s':'')): ''); |
|
} |
|
|
|
function hideCursor(){ |
|
layers.get('cursor') |
|
.transition() |
|
.style('opacity', 0); |
|
} |
|
|
|
function interpolate(data, week){ |
|
var prevWeek = Math.floor(week); |
|
var nextWeek = prevWeek + 1; |
|
|
|
if(prevWeek<8){ |
|
return data[0]; |
|
} |
|
else if(nextWeek>data[data.length-1].week){ |
|
return data[data.length-1]; |
|
} |
|
|
|
var prevData = data[prevWeek-8]; |
|
var nextData = data[nextWeek-8]; |
|
|
|
return { |
|
week: week, |
|
length: prevData.length + (week-prevWeek) * (nextData.length - prevData.length), |
|
mass: prevData.mass + (week-prevWeek) * (nextData.mass - prevData.mass), |
|
}; |
|
} |
|
|
|
d3.json('fetal.json', function(error, data){ |
|
chart.data(data); |
|
}); |
|
|
|
d3.select(self.frameElement).style('height', chart.options().defaultChartHeight); |