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);