D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
henryjameslau
Full window
Github gist
Resorting lollipop chart
<!DOCTYPE html> <html lang="en"> <head> <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,600|Open+Sans+Condensed:300' rel='stylesheet' type='text/css'> <title></title> <meta name="description" content=""> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" /> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> <link rel="stylesheet" href="../lib/globalStyle.css" /> <style type="text/css"> .background0 { background: #dadada; } .background1 { background: #7BCAE2; } #triangleEnd { fill: black; } #triangleStart { fill: black; } .x.axis path { display: none; } /* Full width */ .btn-form-fullwidth { display: -webkit-inline-box; display: -ms-inline-flexbox; display: inline-flex; font-family: 'Open Sans',Arial,sans-serif; -webkit-font-smoothing: antialiased; font-weight: 600; font-size: 16px; margin-bottom: 32px; width: 100%; } .form-group-fullwidth { display: -webkit-box; display: -ms-flexbox; display: flex; cursor: pointer; height: 40px; box-sizing: border-box; text-align: center; width: 100%; } .form-group-fullwidth:last-child { border-right: solid 1px #A6A8AB; } .label-primary-fullwidth { padding: 8px 16px; cursor: pointer; color: #333; background: #F9F9F9; border-top: solid 1px #A6A8AB; border-left: solid 1px #A6A8AB; border-bottom: solid 1px #A6A8AB; width: 100%; } .label-primary-fullwidth:hover { background: #003C57; color: #fff; border-color: #003C57; width: 100%; } .radio-primary-fullwidth:checked + .label-primary-fullwidth { background: #206095; color: #fff; border-color: #206095; cursor: default; width: 100%; } .radio-primary-fullwidth { display: none; } </style> </head> <body> <div id="buttons"> <form class="btn-form btn-form-fullwidth" role="radiogroup"> <div class="form-group form-group-fullwidth" role="radio" tabindex="0"> <input id="button-1" class="radio-primary radio-primary-fullwidth" type="radio" name="button" value="1" aria-checked="true" checked> <label class="label-primary label-primary-fullwidth" for="button-1">Females</label> </div> <div class="form-group form-group-fullwidth" role="radio" tabindex="2"> <input id="button-2" class="radio-primary radio-primary-fullwidth" type="radio" name="button" value="2" aria-checked="false"> <label class="label-primary label-primary-fullwidth" for="button-2">Males</label> </div> <div class="form-group form-group-fullwidth" role="radio" tabindex="3"> <input id="button-3" class="radio-primary radio-primary-fullwidth" type="radio" name="button" value="3" aria-checked="false"> <label class="label-primary label-primary-fullwidth" for="button-3">Both</label> </div> </form> </div> <div id="graphic"> <img src="fallback.png" alt="[Chart]" /> </div> <div id="keypoints"> <p></p> </div> <div class="footer"> </div> <script src="https://cdn.ons.gov.uk/vendor/d3/4.2.7/d3.min.js" type="text/javascript"></script> <script src="modernizr.svg.min.js" type="text/javascript"></script> <script src="pym.js" type="text/javascript"></script> <script> var graphic = d3.select('#graphic'); var keypoints = d3.select('#keypoints'); var footer = d3.select(".footer"); var pymChild = null; function drawGraphic() { var graphic_data = data1; var prev_data = data3; var threshold_md = 788; var threshold_sm = dvc.optional.mobileBreakpoint; // 510 //set variables for chart dimensions dependent on width of #graphic if (parseInt(graphic.style("width")) < threshold_sm) { var ball = 4; var margin = { top: dvc.optional.margin_sm[0], right: dvc.optional.margin_sm[1], bottom: dvc.optional.margin_sm[2], left: dvc.optional.margin_sm[3] }; var chart_width = parseInt(graphic.style("width")) - margin.left - margin.right; var height = Math.ceil((chart_width * dvc.optional.aspectRatio_sm[1]) / dvc.optional.aspectRatio_sm[0]) - margin.top - margin.bottom; } else if (parseInt(graphic.style("width")) < threshold_md) { var ball = 4; var margin = { top: dvc.optional.margin_md[0], right: dvc.optional.margin_md[1], bottom: dvc.optional.margin_md[2], left: dvc.optional.margin_md[3] }; var chart_width = parseInt(graphic.style("width")) - margin.left - margin.right; var height = Math.ceil((chart_width * dvc.optional.aspectRatio_md[1]) / dvc.optional.aspectRatio_md[0]) - margin.top - margin.bottom; } else { var ball = 5; var margin = { top: dvc.optional.margin_lg[0], right: dvc.optional.margin_lg[1], bottom: dvc.optional.margin_lg[2], left: dvc.optional.margin_lg[3] } var chart_width = parseInt(graphic.style("width")) - margin.left - margin.right; var height = Math.ceil((chart_width * dvc.optional.aspectRatio_lg[1]) / dvc.optional.aspectRatio_lg[0]) - margin.top - margin.bottom; } // clear out existing graphics graphic.selectAll("*").remove(); keypoints.selectAll("*").remove(); footer.selectAll("*").remove(); var x = d3.scaleLinear() .range([0, chart_width]); var y = d3.scalePoint() .range([0, height], .3); //.rangeRoundBands([0, height]); // .1 y.domain(graphic_data.map(function(d) { return d.name; })); var yAxis = d3.axisLeft(y) var xAxis = d3.axisBottom(x); //specify number or ticks on x axis if (parseInt(graphic.style("width")) <= threshold_sm) { xAxis.ticks(dvc.optional.x_num_ticks_sm_md_lg[0]) } else if (parseInt(graphic.style("width")) <= threshold_md) { xAxis.ticks(dvc.optional.x_num_ticks_sm_md_lg[1]) } else { xAxis.ticks(dvc.optional.x_num_ticks_sm_md_lg[2]) } var lines = graphic_data.map(function(d, i) { //console.log(d + " " +i); return { // lines.x: csv.column_headings 'name': d.name, // might not need since we mapped it in y.domain earlier 'mymin': +d.imin, // + changes string to numeric. 'mymax': +d.imax, 'mymedian': +d.median, 'prev_mymin':0, 'prev_mymax':0 }; }); var memax = d3.max(lines, function(d) { return d.mymax; }); //console.log("memax: "+memax); var memin = d3.min(lines, function(d) { return d.mymin; }); //x domain calculations : zero to intelligent max choice, or intelligent min and max choice, or interval chosen manually if (dvc.essential.xAxisScale == "auto_zero_max") { var xDomain = [0, Math.ceil(d3.max(lines, function(d) { return d.mymax; }))]; } else if (dvc.essential.xAxisScale == "auto_min_max") { var xDomain = [Math.floor(d3.min(lines, function(d) { return d.mymin; })), Math.ceil(d3.max(lines, function(d) { return d.mymax; })) ]; /*var yDomain = [ Math.floor(d3.min(dvc.yData)/dvc.essential.yAxisScaleDivisor)*dvc.essential.yAxisScaleDivisor , Math.ceil(d3.max(dvc.yData)/dvc.essential.yAxisScaleDivisor)*dvc.essential.yAxisScaleDivisor ];*/ } else { var xDomain = dvc.essential.xAxisScale; var yDomain = dvc.essential.yAxisScale; } x.domain(xDomain); // sort these hardwired numbers maybe through the dimensions above var legend = d3.select('#graphic').append('svg') .attr("width", chart_width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom + 30) .append("g") .attr("id", "legend") //create svg for chart var svg = d3.select('svg') .attr("id", "chart") .style("background-color", "#fff") .attr("width", chart_width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom + 80) .append("g") .attr("transform", "translate(" + margin.left + "," + (margin.top + 40) + ")") svg.append("rect") .attr("class", "svgRect") .style("fill", "#fff") .attr("width", chart_width) .attr("height", height); svg.append('g') .attr('class', 'x axis') .attr("transform", "translate(0, " + height + ")") .call(xAxis.tickSize(-height, 0)) .selectAll("line") .style("stroke", "#E5E6E7"); svg.select(".x.axis") .append("text") .attr('class', 'x-label') .attr("y", 35) .attr("x", chart_width) .attr("dy", ".71em") .style("text-anchor", "end") .text(dvc.essential.xAxisLabel[0]); d3.select(".y.axis") .selectAll(".ticks") .attr("class", function(d, i) { return "tick" + i }) //move the x number down a bit d3.selectAll(".tick").select("text").attr("transform", "translate(0,15)") //create y axis, if x axis doesn't start at 0 drop x axis accordingly svg.append('g') .attr('class', 'y axis') .attr('transform', function(d) { if (xDomain[0] != 0) { return 'translate(' + (-30) + ',0)' } else { return 'translate(' + 0 + ', 0)' } }) d3.select('.y') .selectAll('.tick') .attr('class', function(d, i) { return 'tick' + d; }) d3.select('.tick5') .style('font-weight', 'bold') d3.select(".y.axis").selectAll("line").remove(); svg.append("text") .attr('class', 'unit') .attr('transform', "translate(" + -margin.left + "," + eval(-margin.top) + ")") // + (lineNo+1)*20 or -10 .attr("font-size", "12px") .attr("fill", "#666") .text(dvc.essential.yAxisLabel); var paths = svg.append("defs") svg.append("defs").append("marker") .attr("id", "triangleStart") .attr("viewBox", "0 0 20 20") .attr("refX", 0) .attr("refY", 5) .attr("markerUnits", "strokeWidth") .attr("markerWidth", 20) .attr("markerHeight", 20) .attr("orient", "auto") .append("path") .attr("d", "M 0 5 L 10 10 L 10 0 z") .style("fill", '#414042'); svg.append("defs").append("marker") .attr("id", "triangleEnd") .attr("viewBox", "0 0 20 20") .attr("refX", 10) .attr("refY", 5) .attr("markerUnits", "strokeWidth") .attr("markerWidth", 20) .attr("markerHeight", 20) .attr("orient", "auto") .append("path") .attr("d", "M 10 5 L 0 0 L 0 10 z") .style("fill", "#414042"); lines = graphic_data.map(function(d, i) { //console.log(d + " " +i); return { // lines.x: csv.column_headings 'name': d.name, // might not need since we mapped it in y.domain earlier 'mymin': +d.imin, // + changes string to numeric. 'mymax': +d.imax, 'mymedian': +d.median, 'prev_name': prev_data[i].name, 'prev_mymin':+prev_data[i].imin, 'prev_mymax':+prev_data[i].imax }; }); connect_line = svg.selectAll('.line') .data(lines) // ["value"] connect_line .enter() .append('line') .attr('class', function(d, i) { return 'line tiefighter '+d.name.replace(/\s+/g, '') }) .style("stroke", "#dadada") .attr('y1', function(d, i) { return y(d.prev_name); }) .attr('y2', function(d) { return y(d.prev_name); }) .attr('x1', function(d, i) { return x(d.prev_mymin); }) //d.mymin .attr('x2', function(d) { return x(d.prev_mymax); }) d3.select('.England') .style('stroke', dvc.essential.stroke_average_line) // svg.selectAll('.tiefighter')//.append("svg") var dots_min = svg.selectAll('.circlemin') .data(lines) var dots_max = svg.selectAll('.circlemax') .data(lines) dots_min .enter() .append('circle') .attr('class', function(d, i) { return 'circlemin circlemin' + i +' circlemin'+d.name.replace(/\s+/g, '') }) .attr("fill", dvc.essential.colour_palette[0]) // .attr('cy', height) // .attr('cx', 0) .attr('cx', function(d, i) { return x(d.mymin); }) .attr('cy', function(d, i) { return y(d.name); }) .attr('r', ball) d3.select('.circleminEngland') .style('fill', dvc.essential.colour_average) if (dvc.essential.minmaxonly == "true") { dots_max .enter() .append('circle') .attr('class', function(d, i) { return 'circlemax circlemax' + i +' circlemax'+d.name.replace(/\s+/g, '') }) .attr("fill", dvc.essential.colour_palette[1]) .attr('cx', function(d, i) { return x(d.mymax); }) .attr('cy', function(d, i) { return y(d.name); }) .attr('r', ball) } else { svg.append('g').selectAll('.circle') .data(lines) .enter() .append('circle') .attr('class', "circlemax") .attr("fill", dvc.essential.colour_palette[0]) .attr('cx', function(d, i) { return x(d.mymax); }) .attr('cy', function(d, i) { return y(d.name); }) .attr('r', ball); } d3.selectAll("input[name='button']") .on('click', function() { d3.select('.x-label') .text(dvc.essential.xAxisLabel[+this.value-1]); d3.selectAll('.annotext') .style('display','none') d3.selectAll('.annorect') .style('display','none') d3.select('.annotext'+[+this.value-1]) .style('display', 'block') d3.select('.annorect'+[+this.value-1]) .style('display', 'block') // var prev_data = graphic_data; if(+this.value === 1) { graphic_data = data1 } else if(+this.value === 2) { graphic_data = data2 } else if (+this.value === 3){ graphic_data = data3 } tieFight(graphic_data)//, prev_data); }) tieFight(graphic_data) function tieFight(graphic_data){//, prev_data) { var t = d3.transition() .duration(1000) .ease(d3.easeQuadOut) // console.log(graphic_data) lines = graphic_data.map(function(d, i) { //console.log(d + " " +i); return { 'name': d.name, // might not need since we mapped it in y.domain earlier 'mymin': +d.imin, // + changes string to numeric. 'mymax': +d.imax, }; }); newlines={} lines.map(function(d,i){ newlines[d.name]={"min":+d.mymin,"max":+d.mymax} }) lines.sort(function(a,b) { return d3.descending(a.mymax, b.mymax) }) var memax = d3.max(lines, function(d) { return d.mymax; }); //console.log("memax: "+memax); var memin = d3.min(lines, function(d) { return d.mymin; }); y.domain(lines.map(function(d) { return d.name; })); svg.select('.y.axis') .transition(t) .call(yAxis); d3.select('.y') .selectAll('.tick') .attr('class', function(d, i) {console.log(d) return 'tick tick-' + d.replace(/\s+/g, '') }) d3.select('.tick-England') .style('font-weight', 'bold') for(i=0;i<lines.length;i++){ d3.select("."+lines[i].name.replace(/\s+/g, '')) .transition(t) .attr('y1', function(d) { return y(lines[i].name); }) .attr('y2', function(d) { return y(lines[i].name); }) .attr('x1', function(d) { //console.log("x1 "+i+" "+x(d.mymin)); return x(lines[i].mymin); }) //d.mymin .attr('x2', function(d) { return x(lines[i].mymax) }) d3.select(".circlemin"+lines[i].name.replace(/\s+/g, '')) .transition(t) .attr('cx', function(d) { return x(lines[i].mymin); }) .attr('cy', function(d) { return y(lines[i].name); }) .attr('r', ball) d3.select(".circlemax"+lines[i].name.replace(/\s+/g, '')) .transition(t) .attr('cx', function(d) { return x(lines[i].mymax); }) .attr('cy', function(d) { return y(lines[i].name); }) .attr('r', ball) } y.domain(lines.map(function(d) { return d.name; })); svg.selectAll('.tiefighter') svg.select('.x.axis') .transition(t) .call(xAxis); //if not only drawing two dots, draw the median dot. if (dvc.essential.minmaxonly == "true") {} else { columnH = d3.keys(graphic_data[0]).filter(function(key) { return key; }); if (columnH[3] == "median") { svg.append('g').selectAll('.med') .data(lines) .enter() .append('circle') .attr("class", "circlemed") .attr("fill", dvc.essential.colour_palette[1]) .attr('cx', function(d, i) { return x(d.mymedian); }) .attr('cy', function(d, i) { return y(d.name); }) .attr('r', ball * 1.5); } } //end of tiefighter } //create centre line if required if (dvc.optional.centre_line == true) { svg.append("line") .attr("id", "centreline") .attr('y1', 0) .attr('y2', height) .attr('x1', x(dvc.optional.centre_line_value)) .attr('x2', x(dvc.optional.centre_line_value)); } else {} writeAnnotation(); function writeAnnotation() { if (parseInt(graphic.style("width")) < threshold_sm) { dvc.essential.annotationBullet.forEach(function(d, i) { d3.select("#keypoints").append("svg") .attr("width", "20px") .attr("height", "20px") .attr("class", "circles") .append("circle") .attr("class", "annocirc" + (i)) .attr("r", "2") .attr('cy', "12px") .attr("cx", "10px"); d3.select("#keypoints").append("p").text(dvc.essential.annotationBullet[i]); }) // end foreach } else { dvc.essential.annotationChart.forEach(function(d, i) { // draw annotation text based on content of var annotationArray ... svg.append("text") .text(dvc.essential.annotationChart[i]) .attr("class", "annotext annotext" + i) .attr("text-anchor", dvc.essential.annotationAlign[i]) .attr('y', y(dvc.essential.annotationXY[i][1])) .attr('x', x(dvc.essential.annotationXY[i][0])); d3.selectAll(".annotext" + (i)) .each(insertLinebreaks) .each(createBackRect); function insertLinebreaks() { var str = this; var el1 = dvc.essential.annotationChart[i]; var el = el1.data; var words = el1.split(' '); d3.select(this /*str*/ ).text(''); for (var j = 0; j < words.length; j++) { var tspan = d3.select(this).append('tspan').text(words[j]); if (j > 0) tspan.attr('x', x(dvc.essential.annotationXY[i][0])).attr('dy', '22'); } d3.selectAll('.annotext') .style('display','none') d3.selectAll('.annorect') .style('display','none') d3.select('.annotext0') .style('display', 'block') d3.select('.annorect0') .style('display', 'block') }; function createBackRect() { var BBox = this.getBBox() svg.insert("rect", ".annotext" + (i)) .attr('class', 'annorect annorect'+i) .attr("width", BBox.width) .attr("height", BBox.height) .attr("x", BBox.x) .attr("y", BBox.y) .attr("fill", "white") .attr("opacity", 0.4); }; // end function createBackRect() }); // end foreach } // end else ... // If you have labels rather than years then you can split the lines using a double space (in the CSV file). function insertLinebreaksLabels() { var str = this.textContent; var words = str.split(' '); d3.select(this /*str*/ ).text(''); for (var j = 0; j < words.length; j++) { var tspan = d3.select(this).append('tspan').text(words[j]); if (j > 0) tspan .attr('x', -10) .attr('dy', '1em'); } }; d3.selectAll(".y text").each(insertLinebreaksLabels) } // end function writeAnnotation() createLegend(); function createLegend() { var prevX = 0; var prevY = 0; lineNo = 0; var lineNoOld = 0; dvc.essential.legendLabels.forEach(function(d, i) { // draw legend text based on content of var legendLabels ... var_group = d3.select("#legend").append("g") if (dvc.essential.legendStyle != "circle") { var_group.append("rect") .attr("class", "rect" + i) .attr("fill", dvc.essential.colour_palette[i]) .attr("x", 0) .attr("y", 0) .attr("width", function(d) { if (dvc.essential.legendStyle == "rect") { return 15; } else { return 20; } }) .attr("height", function(d) { if (dvc.essential.legendStyle == "rect") { return 15; } else { return 3; } }) } else { var_group.append("circle") .attr("class", "legendcirclesmove circ" + i) .attr("fill", dvc.essential.colour_palette[i]) .attr("cx", 0) .attr("cy", 0) .attr("r", 5); } var_group.append("text") .text(dvc.essential.legendLabels[i]) .attr("class", "legend" + i) .attr("text-anchor", "start") .style("font-size", "12px") .style("fill", "#666") .attr('y', 15) .attr('x', 0); d3.selectAll(".legend" + (i)) .each(calcPosition); function calcPosition() { var BBox = this.getBBox() //prevY =BBox.width d3.select(".legend" + (i)) .attr("y", function(d) { if ((prevX + BBox.width + 50) > parseInt(graphic.style("width"))) { lineNoOld = lineNo; lineNo = lineNo + 1; prevX = 0; } return eval((lineNo * 20) + 20); }) .attr("x", function(d) { return prevX + 25; }) if (dvc.essential.legendStyle != "rect") { d3.select(".circ" + (i)) .attr("cy", function(d) { if ((prevX + BBox.width + 50) > parseInt(graphic.style("width"))) { lineNoOld = lineNo; lineNo = lineNo + 1; prevX = 0; } if (dvc.essential.legendStyle == "circle") { return eval((lineNo * 20) + 5); } else { return eval((lineNo * 20) + 12); } }) .attr("cx", function(d) { return prevX; }) prevX = prevX + BBox.width + 50 d3.selectAll(".legendcirclesmove").attr("transform", "translate(10,10)"); d3.selectAll(".circ1").attr("transform", "translate(10,10)"); } else { d3.select(".rect" + (i)) .attr("y", function(d) { if ((prevX + BBox.width + 50) > parseInt(graphic.style("width"))) { lineNoOld = lineNo; lineNo = lineNo + 1; prevX = 0; } if (dvc.essential.legendStyle == "rect") { return eval((lineNo * 20) + 5); } else { return eval((lineNo * 20) + 12); } }) .attr("x", function(d) { return prevX; }) prevX = prevX + BBox.width + 50 } }; // end function calcPosition() }); // end foreach } // end function createLegend() // css fix d3.selectAll("path").attr("fill", "none"); d3.selectAll("text").attr("font-family", "'Open Sans', sans-serif"); d3.selectAll(".x text").attr("font-size", "12px").attr("fill", "#666"); d3.selectAll(".y text").attr("font-size", "12px").attr("fill", "#666"); d3.selectAll(".y line") .attr("stroke", "#CCC") .attr("stroke-width", "1px") .style("shape-rendering", "crispEdges"); //create link to source d3.select(".footer").append("p") .text("Source: ") .append("a") .attr("href", dvc.essential.sourceURL) .attr("target", "_blank") .html(dvc.essential.sourceText); //use pym to calculate chart dimensions if (pymChild) { pymChild.sendHeight(); } } //check whether browser can cope with svg if (Modernizr.svg) { //load config d3.json("config.json", function(error, config) { dvc = config; d3.queue() .defer(d3.csv, dvc.essential.graphic_data_url[0]) .defer(d3.csv, dvc.essential.graphic_data_url[1]) .defer(d3.csv, dvc.essential.graphic_data_url[2]) .await(function(error, data_csv1, data_csv2, data_csv3) { //load chart data data1 = data_csv1; data2 = data_csv2; data3 = data_csv3; // console.log(data) //use pym to create iframed chart dependent on specified variables pymChild = new pym.Child({ renderCallback: drawGraphic }); }); function init() { //load chart data graphic_data = file1; //use pym to create iframed chart dependent on specified variables pymChild = new pym.Child({ renderCallback: drawGraphic }); } }) } else { //use pym to create iframe containing fallback image (which is set as default) pymChild = new pym.Child(); if (pymChild) { pymChild.sendHeight(); } } </script> </body> </html>
https://cdn.ons.gov.uk/vendor/d3/4.2.7/d3.min.js