D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
chrisjackson4256
Full window
Github gist
survival analysis viz
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>SCCA Survival Analysis</title> <script src="https://d3js.org/d3.v4.min.js"></script> <style type="text/css"> body { font: 10px sans-serif; } .axis path, .axis line { fill: none; stroke: #000; shape-rendering: crispEdges; } .x.axis path { stroke: #000; } .line { fill: none; stroke: steelblue; stroke-width: 1.5px; } #title { margin: auto; font-family: sans-serif; color: black; stroke: black; stroke-width: 1px; text-align: center; } #survivalAllStages { margin-top: 50px; margin-left: auto; margin-right: auto; width: 960px; height: 600px; } #stg1ConfIntBands { margin: auto; width: 960px; height: 600px; } #stg2ConfIntBands { margin: auto; width: 960px; height: 600px; } #stg3ConfIntBands { margin: auto; width: 960px; height: 600px; } #stg4ConfIntBands { margin: auto; width: 960px; height: 600px; } .selector { margin: auto; display: inline-block; } .button { margin-top: 20px; margin-left: auto; margin-right: auto; } .container { text-align:center; } .centerwrapper { margin: auto; } #disease { display: inline-block; } #race { display: inline-block; } #sex { display: inline-block; } #age { display: inline-block; } #run { display: inline-block; } #stg1CenterLine { fill: none; stroke: #1f77b4; stroke-width: 1.5px; } #stg1UpperLine { fill: none; stroke: #1f77b4; stroke-width: 1.5px; stroke-dasharray: 2; } #stg1LowerLine { fill: none; stroke: #1f77b4; stroke-width: 1.5px; stroke-dasharray: 2; } #stg1Bands { fill: #1f77b4; fill-opacity: 0.3; stroke: none; } #stg2CenterLine { fill: none; stroke: #ff7f0e; stroke-width: 1.5px; } #stg2UpperLine { fill: none; stroke: #ff7f0e; stroke-width: 1.5px; stroke-dasharray: 2; } #stg2LowerLine { fill: none; stroke: #ff7f0e; stroke-width: 1.5px; stroke-dasharray: 2; } #stg2Bands { fill: #ff7f0e; fill-opacity: 0.3; stroke: none; } #stg3CenterLine { fill: none; stroke: #2ca02c; stroke-width: 1.5px; } #stg3UpperLine { fill: none; stroke: #2ca02c; stroke-width: 1.5px; stroke-dasharray: 2; } #stg3LowerLine { fill: none; stroke: #2ca02c; stroke-width: 1.5px; stroke-dasharray: 2; } #stg3Bands { fill: #2ca02c; fill-opacity: 0.3; stroke: none; } #stg4CenterLine { fill: none; stroke: #d62728; stroke-width: 1.5px; } #stg4UpperLine { fill: none; stroke: #d62728; stroke-width: 1.5px; stroke-dasharray: 2; } #stg4LowerLine { fill: none; stroke: #d62728; stroke-width: 1.5px; stroke-dasharray: 2; } #stg4Bands { fill: #d62728; fill-opacity: 0.3; stroke: none; } </style> </head> <body> <div id="title"> <h1>SCCA Survival Analysis</h1> </div> <div class = "container"> <div class="centerwrapper"> <div class='selector'> <select id='disease'> <option value=''>Select a disease:</option> <option value='Breast'>Breast</option> <option value='lungandBronchus'>Lung & Bronchus</option> <option value='pancreas'>Pancreas</option> </select> </div> <div class='selector'> <select id='race'> <option value=''>Select a race:</option> <option value='all'>All</option> <option value='white'>White</option> <option value='other'>Other</option> </select> </div> <div class='selector'> <select id='sex'> <option value=''>Select a gender:</option> <option value='all'>All</option> <option value='female'>Female</option> <option value='male'>Male</option> </select> </div> <div class='selector'> <select id='age'> <option value=''>Select an age group:</option> <option value='all'>All</option> <option value='18-64'>18 - 64</option> <option value='18-90'>18 - 90</option> <option value='over_65'>Over 65</option> </select> </div> <div class='button'> <div id='run'> <button>Run Analysis</button> </div> </div> </div> </div> <div id='survivalAllStages'></div> <div id='stg1ConfIntBands'></div> <div id='stg2ConfIntBands'></div> <div id='stg3ConfIntBands'></div> <div id='stg4ConfIntBands'></div> </body> </html> <!-- D3 Survival Analysis Curves --> <script> var margin = { top: 20, right: 80, bottom: 40, left: 50}, width = 900 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; var x = d3.scaleLinear() .domain([0, 10]) .range([0, width]); var y = d3.scaleLinear() .domain([0, 1]) .range([height, 0]); var color = d3.scaleOrdinal(d3.schemeCategory10); var xAxis = d3.axisBottom() .scale(x) .tickValues([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); var yAxis = d3.axisLeft() .scale(y); var line = d3.line() .x(function(d) { return x(d.years); }) .y(function(d) { return y(d.survivalPct); }); var stg1BandArea = d3.area() .x(function(d) { return x(d.years); }) .y0(function(d) { return y(d.stage1Lower); }) .y1(function(d) { return y(d.stage1Upper)}); var stg2BandArea = d3.area() .x(function(d) { return x(d.years); }) .y0(function(d) { return y(d.stage2Lower); }) .y1(function(d) { return y(d.stage2Upper)}); var stg3BandArea = d3.area() .x(function(d) { return x(d.years); }) .y0(function(d) { return y(d.stage3Lower); }) .y1(function(d) { return y(d.stage3Upper)}); var stg4BandArea = d3.area() .x(function(d) { return x(d.years); }) .y0(function(d) { return y(d.stage4Lower); }) .y1(function(d) { return y(d.stage4Upper)}); // ~~~~~~~~~~~~~~~~~~~~~~~ // SVG for all stages plot // ~~~~~~~~~~~~~~~~~~~~~~~ var svg = d3.select("#survivalAllStages") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // plot title svg.append("text") .attr("transform", "translate(" + width / 2 + ", 0)") .attr("text-anchor", "middle") .text("Survival Rate for All Stages") .style("font-size", 18) .style("font-family", "sans-serif"); svg.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); // label for x axis svg.append("text") .attr("transform", "translate(" + width / 2 + ", " + (height + margin.top + 12) + ")") .attr("text-anchor", "middle") .text("Years Since Diagnosis") .style("font-size", 14) .style("font-family", "sans-serif"); svg.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end"); // label for y axis svg.append("text") .attr("transform", "rotate(-90)") .attr("y", 0 - margin.left) .attr("x",0 - (height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text("Survival Rate") .style("font-size", 14) .style("font-family", "sans-serif"); // ~~~~~~~~~~~~~~~~~~~~~~~ // SVG for Stage 1 Bands // ~~~~~~~~~~~~~~~~~~~~~~~ var svgS1 = d3.select("#stg1ConfIntBands") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // plot title svgS1.append("text") .attr("transform", "translate(" + width / 2 + ", 0)") .attr("text-anchor", "middle") .text("Stage 1 Survival Rate with 95% Confidence Interval") .style("font-size", 18) .style("font-family", "sans-serif"); svgS1.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); // label for x axis svgS1.append("text") .attr("transform", "translate(" + width / 2 + ", " + (height + margin.top + 12) + ")") .attr("text-anchor", "middle") .text("Years Since Diagnosis") .style("font-size", 14) .style("font-family", "sans-serif"); svgS1.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end"); // label for y axis svgS1.append("text") .attr("transform", "rotate(-90)") .attr("y", 0 - margin.left) .attr("x",0 - (height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text("Survival Rate") .style("font-size", 14) .style("font-family", "sans-serif"); // ~~~~~~~~~~~~~~~~~~~~~~~ // SVG for Stage 2 Bands // ~~~~~~~~~~~~~~~~~~~~~~~ var svgS2 = d3.select("#stg2ConfIntBands") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // plot title svgS2.append("text") .attr("transform", "translate(" + width / 2 + ", 0)") .attr("text-anchor", "middle") .text("Stage 2 Survival Rate with 95% Confidence Interval") .style("font-size", 18) .style("font-family", "sans-serif"); svgS2.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); // label for x axis svgS2.append("text") .attr("transform", "translate(" + width / 2 + ", " + (height + margin.top + 12) + ")") .attr("text-anchor", "middle") .text("Years Since Diagnosis") .style("font-size", 14) .style("font-family", "sans-serif"); svgS2.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end"); // label for y axis svgS2.append("text") .attr("transform", "rotate(-90)") .attr("y", 0 - margin.left) .attr("x",0 - (height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text("Survival Rate") .style("font-size", 14) .style("font-family", "sans-serif"); // ~~~~~~~~~~~~~~~~~~~~~~~ // SVG for Stage 3 Bands // ~~~~~~~~~~~~~~~~~~~~~~~ var svgS3 = d3.select("#stg3ConfIntBands") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // plot title svgS3.append("text") .attr("transform", "translate(" + width / 2 + ", 0)") .attr("text-anchor", "middle") .text("Stage 3 Survival Rate with 95% Confidence Interval") .style("font-size", 18) .style("font-family", "sans-serif"); svgS3.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); // label for x axis svgS3.append("text") .attr("transform", "translate(" + width / 2 + ", " + (height + margin.top + 12) + ")") .attr("text-anchor", "middle") .text("Years Since Diagnosis") .style("font-size", 14) .style("font-family", "sans-serif"); svgS3.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end"); // label for y axis svgS3.append("text") .attr("transform", "rotate(-90)") .attr("y", 0 - margin.left) .attr("x",0 - (height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text("Survival Rate") .style("font-size", 14) .style("font-family", "sans-serif"); // ~~~~~~~~~~~~~~~~~~~~~~~ // SVG for Stage 4 Bands // ~~~~~~~~~~~~~~~~~~~~~~~ var svgS4 = d3.select("#stg4ConfIntBands") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // plot title svgS4.append("text") .attr("transform", "translate(" + width / 2 + ", 0)") .attr("text-anchor", "middle") .text("Stage 4 Survival Rate with 95% Confidence Interval") .style("font-size", 18) .style("font-family", "sans-serif"); svgS4.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height + ")") .call(xAxis); // label for x axis svgS4.append("text") .attr("transform", "translate(" + width / 2 + ", " + (height + margin.top + 12) + ")") .attr("text-anchor", "middle") .text("Years Since Diagnosis") .style("font-size", 14) .style("font-family", "sans-serif"); svgS4.append("g") .attr("class", "y axis") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".71em") .style("text-anchor", "end"); // label for y axis svgS4.append("text") .attr("transform", "rotate(-90)") .attr("y", 0 - margin.left) .attr("x",0 - (height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text("Survival Rate") .style("font-size", 14) .style("font-family", "sans-serif"); // row conversion functions var rowConverter = function(d) { return { years: parseFloat(d['years']), stg1Samples: parseInt(d['Stage_1_sample_size']), stage1: parseFloat(d['Stage_1']), stg2Samples: parseInt(d['Stage_2_sample_size']), stage2: parseFloat(d['Stage_2']), stg3Samples: parseInt(d['Stage_3_sample_size']), stage3: parseFloat(d['Stage_3']), stg4Samples: parseInt(d['Stage_4_sample_size']), stage4: parseFloat(d['Stage_4']) } }; var rowConverterStg1 = function(d) { return { samples: parseInt(d['Stage_1_sample_size']), years: parseFloat(d['years']), stage1: parseFloat(d['Stage_1']), stage1Upper: parseFloat(d['Stage_1_upper']), stage1Lower: parseFloat(d['Stage_1_lower']) } }; var rowConverterStg2 = function(d) { return { samples: parseInt(d['Stage_2_sample_size']), years: parseFloat(d['years']), stage2: parseFloat(d['Stage_2']), stage2Upper: parseFloat(d['Stage_2_upper']), stage2Lower: parseFloat(d['Stage_2_lower']) } }; var rowConverterStg3 = function(d) { return { samples: parseInt(d['Stage_3_sample_size']), years: parseFloat(d['years']), stage3: parseFloat(d['Stage_3']), stage3Upper: parseFloat(d['Stage_3_upper']), stage3Lower: parseFloat(d['Stage_3_lower']) } }; var rowConverterStg4 = function(d) { return { samples: parseInt(d['Stage_4_sample_size']), years: parseFloat(d['years']), stage4: parseFloat(d['Stage_4']), stage4Upper: parseFloat(d['Stage_4_upper']), stage4Lower: parseFloat(d['Stage_4_lower']) } }; d3.select("#run") .on("click", function() { svg.selectAll(".line").remove(); svg.selectAll(".stage").remove(); svg.selectAll(".text").remove(); svg.selectAll(".circle").remove(); svg.selectAll(".mouse-per-line").remove(); // construct the filename to open from the selector input var diseaseSect = document.getElementById("disease"); var disease = diseaseSect.options[diseaseSect.selectedIndex].value; var raceSect = document.getElementById("race"); var race = raceSect.options[raceSect.selectedIndex].value; var sexSect = document.getElementById("sex"); var sex = sexSect.options[sexSect.selectedIndex].value; var ageSect = document.getElementById("age"); var age = ageSect.options[ageSect.selectedIndex].value; var filename = disease + "_race_" + race + "_sex_" + sex + "_age_" + age + ".csv" // ~~~~~~~~~~~~~~~~~~~~ // Draw All Stages plot // ~~~~~~~~~~~~~~~~~~~~ d3.csv(filename, rowConverter, function(data) { // sample size var sampleSize = data[0]['stg1Samples'] + data[0]['stg2Samples'] + data[0]['stg3Samples'] + data[0]['stg4Samples']; color.domain(d3.keys(data[0]).filter(function(key) { return key !== "years" && key !== "stg1Samples" && key !== "stg2Samples" && key !== "stg3Samples" && key !== "stg3Samples" && key !== "stg4Samples"; })); var stages = color.domain().map(function(name) { return { name: name, values: data.map(function(d) { return { years: d.years, survivalPct: +d[name] }; }) }; }); var legend = svg.selectAll(".legend") .data(stages) .enter() .append('g') .attr('class', 'legend'); legend.append('rect') .attr('x', 20) .attr('y', function(d, i) { return i * 20 + 350; }) .attr('width', 10) .attr('height', 10) .style('fill', function(d) { console.log(color(d.name)); return color(d.name); }); legend.append('text') .attr('x', 35) .attr('y', function(d, i) { return (i * 20) + 9 + 350; }) .text(function(d) { return d.name; }); var stage = svg.selectAll(".stage") .data(stages) .enter().append("g") .attr("class", "stage"); stage.append("path") .attr("class", "line") .attr("d", function(d) { return line(d.values); }) .style("stroke", function(d) { return color(d.name); }); var mouseG = svg.append("g") .attr("class", "mouse-over-effects"); var lines = document.getElementsByClassName('line'); var mousePerLine = mouseG.selectAll('.mouse-per-line') .data(stages) .enter() .append("g") .attr("class", "mouse-per-line"); mousePerLine.append("circle") .attr("r", 7) .style("stroke", function(d) { return color(d.name); }) .style("fill", "none") .style("stroke-width", "1px") .style("opacity", "0"); mousePerLine.append("text") .attr("transform", "translate(10,3)"); mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas .attr('width', width) // can't catch mouse events on a g element .attr('height', height) .attr('fill', 'none') .attr('pointer-events', 'all') .on('mouseout', function() { // on mouse out hide line, circles and text d3.select(".mouse-line") .style("opacity", "0"); d3.selectAll(".mouse-per-line circle") .style("opacity", "0"); d3.selectAll(".mouse-per-line text") .style("opacity", "0"); }) .on('mouseover', function() { // on mouse in show line, circles and text d3.select(".mouse-line") .style("opacity", "1"); d3.selectAll(".mouse-per-line circle") .style("opacity", "1"); d3.selectAll(".mouse-per-line text") .style("opacity", "1"); }) .on('mousemove', function() { // mouse moving over canvas var mouse = d3.mouse(this); d3.select(".mouse-line") .attr("d", function() { var d = "M" + mouse[0] + "," + height; d += " " + mouse[0] + "," + 0; return d; }); d3.selectAll(".mouse-per-line") .attr("transform", function(d, i) { var xDate = x.invert(mouse[0]), bisect = d3.bisector(function(d) { return d.date; }).right; idx = bisect(d.values, xDate); var beginning = 0, end = lines[i].getTotalLength(), target = null; while (true){ target = Math.floor((beginning + end) / 2); pos = lines[i].getPointAtLength(target); if ((target === end || target === beginning) && pos.x !== mouse[0]) { break; } if (pos.x > mouse[0]) end = target; else if (pos.x < mouse[0]) beginning = target; else break; //position found } d3.select(this).select('text') .text(y.invert(pos.y).toFixed(2)); return "translate(" + mouse[0] + "," + pos.y +")"; }); }); /* // write sample size svg.append("text") .attr("x", x(1)) .attr("y", y(0.1)) .attr("class", "text") .style("font-family", "sans-serif") .style("font-size", 14) .text("Sample Size: ", +sampleSize); */ }); // ~~~~~~~~~~~~~~~~~~~~ // Stage 1 Band Plot // ~~~~~~~~~~~~~~~~~~~~ svgS1.selectAll(".line").remove(); svgS1.selectAll("#stg1Bands").remove(); svgS1.selectAll(".stage").remove(); svgS1.selectAll(".text").remove(); svgS1.selectAll(".circle").remove(); svgS1.selectAll(".mouse-per-line").remove(); d3.csv(filename, rowConverterStg1, function(data) { // sample size var sampleSize = data[0]['samples']; color.domain(d3.keys(data[0]).filter(function(key) { return key !== "years"; })); var stages = color.domain().map(function(name) { return { name: name, values: data.map(function(d) { return { years: d.years, survivalPct: +d[name] }; }) }; }); // append stages data var stage1 = svgS1.selectAll(".stage") .data(stages) .enter().append("g") .attr("class", "stage"); // draw area between lines to be shaded svgS1.append("path") .datum(data) .attr("id", "stg1Bands") .attr("d", stg1BandArea ); // draw lines (central, upper and lower) stage1.append("path") .attr("class", "line") .attr("id", function(d) { if (d.name == "stage1") { return "stg1CenterLine"; } else if (d.name == "stage1Upper") { return "stg1UpperLine"; } else if (d.name == "stage1Lower") { return "stg1LowerLine"; }}) .attr("d", function(d) { return line(d.values); }); var mouseG1 = svgS1.append("g") .attr("class", "mouse-over-effects"); var lines = document.getElementsByClassName('line'); var mousePerLine = mouseG1.selectAll('.mouse-per-line') .data(stages) .enter() .append("g") .attr("class", "mouse-per-line"); mousePerLine.append("circle") .attr("r", 7) .style("stroke", function(d) { return "#1f77b4"; }) .style("fill", "none") .style("stroke-width", "1px") .style("opacity", "0"); mousePerLine.append("text") .attr("transform", "translate(10,3)"); mouseG1.append('svg:rect') // append a rect to catch mouse movements on canvas .attr('width', width) // can't catch mouse events on a g element .attr('height', height) .attr('fill', 'none') .attr('pointer-events', 'all') .on('mouseout', function() { // on mouse out hide line, circles and text d3.select(".mouse-line") .style("opacity", "0"); d3.selectAll(".mouse-per-line circle") .style("opacity", "0"); d3.selectAll(".mouse-per-line text") .style("opacity", "0"); }) .on('mouseover', function() { // on mouse in show line, circles and text d3.select(".mouse-line") .style("opacity", "1"); d3.selectAll(".mouse-per-line circle") .style("opacity", "1"); d3.selectAll(".mouse-per-line text") .style("opacity", "1"); }) .on('mousemove', function() { // mouse moving over canvas var mouse = d3.mouse(this); d3.select(".mouse-line") .attr("d", function() { var d = "M" + mouse[0] + "," + height; d += " " + mouse[0] + "," + 0; return d; }); d3.selectAll(".mouse-per-line") .attr("transform", function(d, i) { var xDate = x.invert(mouse[0]), bisect = d3.bisector(function(d) { return d.date; }).right; idx = bisect(d.values, xDate); var beginning = 0, end = lines[i].getTotalLength(), target = null; while (true){ target = Math.floor((beginning + end) / 2); pos = lines[i].getPointAtLength(target); if ((target === end || target === beginning) && pos.x !== mouse[0]) { break; } if (pos.x > mouse[0]) end = target; else if (pos.x < mouse[0]) beginning = target; else break; //position found } d3.select(this).select('text') .text(y.invert(pos.y).toFixed(2)); return "translate(" + mouse[0] + "," + pos.y +")"; }); }); // write sample size svgS1.append("text") .attr("x", x(1)) .attr("y", y(0.1)) .attr("class", "text") .style("font-family", "sans-serif") .style("font-size", 14) .text("Sample Size: " + sampleSize); }); // ~~~~~~~~~~~~~~~~~~~~ // Stage 2 Band Plot // ~~~~~~~~~~~~~~~~~~~~ svgS2.selectAll(".line").remove(); svgS2.selectAll("#stg2Bands").remove(); svgS2.selectAll(".stage").remove(); svgS2.selectAll(".text").remove(); svgS2.selectAll(".circle").remove(); svgS2.selectAll(".mouse-per-line").remove(); d3.csv(filename, rowConverterStg2, function(data) { // sample size var sampleSize = data[0]['samples']; color.domain(d3.keys(data[0]).filter(function(key) { return key !== "years"; })); var stages = color.domain().map(function(name) { return { name: name, values: data.map(function(d) { return { years: d.years, survivalPct: +d[name] }; }) }; }); // append stages data var stage2 = svgS2.selectAll(".stage") .data(stages) .enter().append("g") .attr("class", "stage"); // draw area between lines to be shaded svgS2.append("path") .datum(data) .attr("id", "stg2Bands") .attr("d", stg2BandArea ); // draw lines (central, upper and lower) stage2.append("path") .attr("class", "line") .attr("id", function(d) { if (d.name == "stage2") { return "stg2CenterLine"; } else if (d.name == "stage2Upper") { return "stg2UpperLine"; } else if (d.name == "stage2Lower") { return "stg2LowerLine"; }}) .attr("d", function(d) { return line(d.values); }); var mouseG2 = svgS2.append("g") .attr("class", "mouse-over-effects"); var lines = document.getElementsByClassName('line'); var mousePerLine = mouseG2.selectAll('.mouse-per-line') .data(stages) .enter() .append("g") .attr("class", "mouse-per-line"); mousePerLine.append("circle") .attr("r", 7) .style("stroke", function(d) { return "#ff7f0e"; }) .style("fill", "none") .style("stroke-width", "1px") .style("opacity", "0"); mousePerLine.append("text") .attr("transform", "translate(10,3)"); mouseG2.append('svg:rect') // append a rect to catch mouse movements on canvas .attr('width', width) // can't catch mouse events on a g element .attr('height', height) .attr('fill', 'none') .attr('pointer-events', 'all') .on('mouseout', function() { // on mouse out hide line, circles and text d3.select(".mouse-line") .style("opacity", "0"); d3.selectAll(".mouse-per-line circle") .style("opacity", "0"); d3.selectAll(".mouse-per-line text") .style("opacity", "0"); }) .on('mouseover', function() { // on mouse in show line, circles and text d3.select(".mouse-line") .style("opacity", "1"); d3.selectAll(".mouse-per-line circle") .style("opacity", "1"); d3.selectAll(".mouse-per-line text") .style("opacity", "1"); }) .on('mousemove', function() { // mouse moving over canvas var mouse = d3.mouse(this); d3.select(".mouse-line") .attr("d", function() { var d = "M" + mouse[0] + "," + height; d += " " + mouse[0] + "," + 0; return d; }); d3.selectAll(".mouse-per-line") .attr("transform", function(d, i) { var xDate = x.invert(mouse[0]), bisect = d3.bisector(function(d) { return d.date; }).right; idx = bisect(d.values, xDate); var beginning = 0, end = lines[i].getTotalLength(), target = null; while (true){ target = Math.floor((beginning + end) / 2); pos = lines[i].getPointAtLength(target); if ((target === end || target === beginning) && pos.x !== mouse[0]) { break; } if (pos.x > mouse[0]) end = target; else if (pos.x < mouse[0]) beginning = target; else break; //position found } d3.select(this).select('text') .text(y.invert(pos.y).toFixed(2)); return "translate(" + mouse[0] + "," + pos.y +")"; }); }); // write sample size svgS2.append("text") .attr("x", x(1)) .attr("y", y(0.1)) .attr("class", "text") .style("font-family", "sans-serif") .style("font-size", 14) .text("Sample Size: " + sampleSize); }); // ~~~~~~~~~~~~~~~~~~~~ // Stage 3 Band Plot // ~~~~~~~~~~~~~~~~~~~~ svgS3.selectAll(".line").remove(); svgS3.selectAll("#stg3Bands").remove(); svgS3.selectAll(".stage").remove(); svgS3.selectAll(".text").remove(); svgS3.selectAll(".circle").remove(); svgS3.selectAll(".mouse-per-line").remove(); d3.csv(filename, rowConverterStg3, function(data) { // sample size var sampleSize = data[0]['samples']; color.domain(d3.keys(data[0]).filter(function(key) { return key !== "years"; })); var stages = color.domain().map(function(name) { return { name: name, values: data.map(function(d) { return { years: d.years, survivalPct: +d[name] }; }) }; }); // append stages data var stage3 = svgS3.selectAll(".stage") .data(stages) .enter().append("g") .attr("class", "stage"); // draw area between lines to be shaded svgS3.append("path") .datum(data) .attr("id", "stg3Bands") .attr("d", stg3BandArea ); // draw lines (central, upper and lower) stage3.append("path") .attr("class", "line") .attr("id", function(d) { if (d.name == "stage3") { return "stg3CenterLine"; } else if (d.name == "stage3Upper") { return "stg3UpperLine"; } else if (d.name == "stage3Lower") { return "stg3LowerLine"; }}) .attr("d", function(d) { return line(d.values); }); var mouseG3 = svgS3.append("g") .attr("class", "mouse-over-effects"); var lines = document.getElementsByClassName('line'); var mousePerLine = mouseG3.selectAll('.mouse-per-line') .data(stages) .enter() .append("g") .attr("class", "mouse-per-line"); mousePerLine.append("circle") .attr("r", 7) .style("stroke", function(d) { return "#2ca02c"; }) .style("fill", "none") .style("stroke-width", "1px") .style("opacity", "0"); mousePerLine.append("text") .attr("transform", "translate(10,3)"); mouseG3.append('svg:rect') // append a rect to catch mouse movements on canvas .attr('width', width) // can't catch mouse events on a g element .attr('height', height) .attr('fill', 'none') .attr('pointer-events', 'all') .on('mouseout', function() { // on mouse out hide line, circles and text d3.select(".mouse-line") .style("opacity", "0"); d3.selectAll(".mouse-per-line circle") .style("opacity", "0"); d3.selectAll(".mouse-per-line text") .style("opacity", "0"); }) .on('mouseover', function() { // on mouse in show line, circles and text d3.select(".mouse-line") .style("opacity", "1"); d3.selectAll(".mouse-per-line circle") .style("opacity", "1"); d3.selectAll(".mouse-per-line text") .style("opacity", "1"); }) .on('mousemove', function() { // mouse moving over canvas var mouse = d3.mouse(this); d3.select(".mouse-line") .attr("d", function() { var d = "M" + mouse[0] + "," + height; d += " " + mouse[0] + "," + 0; return d; }); d3.selectAll(".mouse-per-line") .attr("transform", function(d, i) { var xDate = x.invert(mouse[0]), bisect = d3.bisector(function(d) { return d.date; }).right; idx = bisect(d.values, xDate); var beginning = 0, end = lines[i].getTotalLength(), target = null; while (true){ target = Math.floor((beginning + end) / 2); pos = lines[i].getPointAtLength(target); if ((target === end || target === beginning) && pos.x !== mouse[0]) { break; } if (pos.x > mouse[0]) end = target; else if (pos.x < mouse[0]) beginning = target; else break; //position found } d3.select(this).select('text') .text(y.invert(pos.y).toFixed(2)); return "translate(" + mouse[0] + "," + pos.y +")"; }); }); // write sample size svgS3.append("text") .attr("x", x(1)) .attr("y", y(0.1)) .attr("class", "text") .style("font-family", "sans-serif") .style("font-size", 14) .text("Sample Size: " + sampleSize); }); // ~~~~~~~~~~~~~~~~~~~~ // Stage 4 Band Plot // ~~~~~~~~~~~~~~~~~~~~ svgS4.selectAll(".line").remove(); svgS4.selectAll("#stg4Bands").remove(); svgS4.selectAll(".stage").remove(); svgS4.selectAll(".text").remove(); svgS4.selectAll(".circle").remove(); svgS4.selectAll(".mouse-per-line").remove(); d3.csv(filename, rowConverterStg4, function(data) { // sample size var sampleSize = data[0]['samples']; color.domain(d3.keys(data[0]).filter(function(key) { return key !== "years"; })); var stages = color.domain().map(function(name) { return { name: name, values: data.map(function(d) { return { years: d.years, survivalPct: +d[name] }; }) }; }); // append stages data var stage4 = svgS4.selectAll(".stage") .data(stages) .enter().append("g") .attr("class", "stage"); // draw area between lines to be shaded svgS4.append("path") .datum(data) .attr("id", "stg4Bands") .attr("d", stg4BandArea ); // draw lines (central, upper and lower) stage4.append("path") .attr("class", "line") .attr("id", function(d) { if (d.name == "stage4") { return "stg4CenterLine"; } else if (d.name == "stage4Upper") { return "stg4UpperLine"; } else if (d.name == "stage4Lower") { return "stg4LowerLine"; }}) .attr("d", function(d) { return line(d.values); }); var mouseG4 = svgS4.append("g") .attr("class", "mouse-over-effects"); var lines = document.getElementsByClassName('line'); var mousePerLine = mouseG4.selectAll('.mouse-per-line') .data(stages) .enter() .append("g") .attr("class", "mouse-per-line"); mousePerLine.append("circle") .attr("r", 7) .style("stroke", function(d) { return "#d62728"; }) .style("fill", "none") .style("stroke-width", "1px") .style("opacity", "0"); mousePerLine.append("text") .attr("transform", "translate(10,3)"); mouseG4.append('svg:rect') // append a rect to catch mouse movements on canvas .attr('width', width) // can't catch mouse events on a g element .attr('height', height) .attr('fill', 'none') .attr('pointer-events', 'all') .on('mouseout', function() { // on mouse out hide line, circles and text d3.select(".mouse-line") .style("opacity", "0"); d3.selectAll(".mouse-per-line circle") .style("opacity", "0"); d3.selectAll(".mouse-per-line text") .style("opacity", "0"); }) .on('mouseover', function() { // on mouse in show line, circles and text d3.select(".mouse-line") .style("opacity", "1"); d3.selectAll(".mouse-per-line circle") .style("opacity", "1"); d3.selectAll(".mouse-per-line text") .style("opacity", "1"); }) .on('mousemove', function() { // mouse moving over canvas var mouse = d3.mouse(this); d3.select(".mouse-line") .attr("d", function() { var d = "M" + mouse[0] + "," + height; d += " " + mouse[0] + "," + 0; return d; }); d3.selectAll(".mouse-per-line") .attr("transform", function(d, i) { var xDate = x.invert(mouse[0]), bisect = d3.bisector(function(d) { return d.date; }).right; idx = bisect(d.values, xDate); var beginning = 0, end = lines[i].getTotalLength(), target = null; while (true){ target = Math.floor((beginning + end) / 2); pos = lines[i].getPointAtLength(target); if ((target === end || target === beginning) && pos.x !== mouse[0]) { break; } if (pos.x > mouse[0]) end = target; else if (pos.x < mouse[0]) beginning = target; else break; //position found } d3.select(this).select('text') .text(y.invert(pos.y).toFixed(2)); return "translate(" + mouse[0] + "," + pos.y +")"; }); }); // write sample size svgS4.append("text") .attr("x", x(1)) .attr("y", y(0.1)) .attr("class", "text") .style("font-family", "sans-serif") .style("font-size", 14) .text("Sample Size: " + sampleSize); }); }); </script>
https://d3js.org/d3.v4.min.js