Last active
December 29, 2017 02:41
-
-
Save mwburke/9873c09ac6c21d6ac9153e54892cf5ec to your computer and use it in GitHub Desktop.
D3 Slopegraph
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
All data taken from https://www.kdnuggets.com/2017/05/poll-analytics-data-science-machine-learning-software-leaders.html/2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[{"Tool":"Python","Users":1516,"After":52.6,"Before":45.8, "Comments": "Python is growing in popularity"},{"Tool":"R","Users":1502,"After":52.1,"Before":49.0},{"Tool":"SQL","Users":1006,"After":34.9,"Before":35.5},{"Tool":"RapidMiner","Users":946,"After":32.8,"Before":32.6, "Comments": "13.6% of users only use RapidMiner"},{"Tool":"Excel","Users":810,"After":28.1,"Before":33.6},{"Tool":"Spark","Users":654,"After":22.7,"Before":21.6},{"Tool":"Anaconda","Users":629,"After":21.8,"Before":16.0},{"Tool":"Tensorflow","Users":581,"After":20.2,"Before":6.8, "Comments": "Tensorflow is growing greatly among users since public release"},{"Tool":"scikit-learn","Users":561,"After":19.5,"Before":17.2},{"Tool":"Hadoop","Users":431,"After":15.0,"Before":22.1},{"Tool":"Java","Users":399,"After":13.8,"Before":16.8},{"Tool":"Scala","Users":214,"After":7.4,"Before":6.2}] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>D3 Slopegraph</title> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=650, user-scalable=yes"> | |
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js"></script> | |
<script src="http://d3js.org/d3.v4.min.js"></script> | |
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> | |
<!--[if IE]> | |
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script> | |
<![endif]--> | |
<link rel="stylesheet" href="style.css"> | |
<!-- move this into your css file --> | |
</head> | |
<body> | |
<div id="container"> | |
<p>Data Science Tools: <b>2016</b> to <b>2017</b> | |
<br><i>% Usage</i></p> | |
<div id="chart"> | |
</div> | |
<p>Source: <a href="https://www.kdnuggets.com/2017/05/poll-analytics-data-science-machine-learning-software-leaders.html">KDnuggets Analytics/Data Science 2017 Software Poll</a></p> | |
</div> | |
<script src="slopegraph.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
d3.json('data.json', function(error, data) { | |
if (error) throw error; | |
// Label each point as increasing/decreasing above thresholds | |
// or just little to no change | |
var arrayLength = data.length; | |
for (var i = 0; i < arrayLength; i++) { | |
change = data[i]['After'] - data[i]['Before']; | |
if (change < -3) { | |
data[i]['Change'] = 'negative'; | |
} else if (change > 5) { | |
data[i]['Change'] = 'positive'; | |
} else { | |
data[i]['Change'] = 'neutral'; | |
} | |
} | |
var opts = { | |
width: 600, | |
height: 500, | |
margin: {top: 20, right: 100, bottom: 30, left: 150} | |
}; | |
// Calculate area chart takes up out of entire svg | |
var chartHeight = opts.height - opts.margin.top - opts.margin.bottom; | |
var chartWidth = opts.width - opts.margin.left - opts.margin.right; | |
var svg = d3.select('#chart') | |
.append('svg') | |
.attr('width', opts.width) | |
.attr('height', opts.height); | |
// Create scale for positioning data correctly in chart | |
var vertScale = d3.scaleLinear().domain([6, 53]).range([opts.margin.bottom, chartHeight]); | |
// Labels overlap, need to precompute chart height placement | |
// and adjust to avoid label overlap | |
// First, calculate the right and left side chart placements | |
for (var i = 0; i < arrayLength; i++) { | |
data[i]['AfterY'] = vertScale(data[i]['After']); | |
data[i]['BeforeY'] = vertScale(data[i]['Before']); | |
} | |
// Next, use a basic heuristic to pull labels up or down | |
// If next item is too close to next one, move label up | |
// If next item is too close and item above has been moved up, keep the same value, | |
// and move next value down | |
data.sort(function(a, b) { | |
return b.Before-a.Before; | |
}) | |
for (var i = 1; i < (arrayLength - 1); i++) { | |
if ((data[i]['BeforeY']-data[i+1]['BeforeY']) < 15) { | |
if ((data[i-1]['BeforeY']-data[i]['BeforeY']) < 15) { | |
data[i+1]['BeforeY'] = data[i+1]['BeforeY'] - 10; | |
} else { | |
data[i]['BeforeY'] = data[i]['BeforeY'] + 10; | |
} | |
} | |
} | |
data.sort(function(a, b) { | |
return b.After-a.After; | |
}) | |
console.log(data); | |
for (var i = 1; i < (arrayLength - 1); i++) { | |
if ((data[i]['AfterY']-data[i+1]['AfterY']) < 15) { | |
if ((data[i-1]['AfterY']-data[i]['AfterY']) < 15) { | |
data[i+1]['AfterY'] = data[i+1]['AfterY'] - 10; | |
} else { | |
data[i]['AfterY'] = data[i]['AfterY'] + 10; | |
} | |
} else if ((data[i-1]['AfterY']-data[i]['AfterY']) < 15) { | |
data[i]['AfterY'] = data[i]['AfterY'] - 10; | |
} | |
} | |
data.sort(function(a, b) { | |
return b.Before-a.Before; | |
}) | |
// Create slopegraph labels | |
svg.selectAll('text.labels') | |
.data(data) | |
.enter() | |
.append('text') | |
.text(function(d) { | |
return d.Tool | |
}) | |
.attr('class', function(d) { | |
return 'label ' + d.Change; | |
}) | |
.attr('text-anchor', 'end') | |
.attr('x', opts.margin.left * .6) | |
.attr('y', function(d) { | |
return opts.margin.top + chartHeight - d.BeforeY; | |
}) | |
.attr('dy', '.35em'); | |
// Create slopegraph left value labels | |
svg.selectAll('text.leftvalues') | |
.data(data) | |
.enter() | |
.append('text') | |
.attr('class', function(d) { | |
return d.Change; | |
}) | |
.text(function(d) { | |
return Math.round(d.Before) + '%' | |
}) | |
.attr('text-anchor', 'end') | |
.attr('x', opts.margin.left * .85) | |
.attr('y', function(d) { | |
return opts.margin.top + chartHeight - d.BeforeY; | |
}) | |
.attr('dy', '.35em'); | |
// Create slopegraph left value labels | |
svg.selectAll('text.rightvalues') | |
.data(data) | |
.enter() | |
.append('text') | |
.attr('class', function(d) { | |
return d.Change; | |
}) | |
.text(function(d) { | |
return Math.round(d.After) + '%' | |
}) | |
.attr('text-anchor', 'start') | |
.attr('x', chartWidth + opts.margin.right) | |
.attr('y', function(d) { | |
return opts.margin.top + chartHeight - d.AfterY; | |
}) | |
.attr('dy', '.35em'); | |
// Create slopegraph lines | |
svg.selectAll('line.slope-line') | |
.data(data) | |
.enter() | |
.append('line') | |
.attr('class', function(d) { | |
return 'slope-line ' + d.Change; | |
}) | |
.attr('x1', opts.margin.left) | |
.attr('x2', chartWidth + opts.margin.right * 0.75) | |
.attr('y1', function(d) { | |
return opts.margin.top + chartHeight - vertScale(d.Before); | |
}) | |
.attr('y2', function(d) { | |
return opts.margin.top + chartHeight - vertScale(d.After); | |
}); | |
// Create slopegraph left circles | |
svg.selectAll('line.left-circle') | |
.data(data) | |
.enter() | |
.append('circle') | |
.attr('class', function(d) { | |
return d.Change; | |
}) | |
.attr('cx', opts.margin.left) | |
.attr('cy', function(d) { | |
return opts.margin.top + chartHeight - vertScale(d.Before); | |
}) | |
.attr('r', 6); | |
// Create slopegraph right circles | |
svg.selectAll('line.left-circle') | |
.data(data) | |
.enter() | |
.append('circle') | |
.attr('class', function(d) { | |
return d.Change; | |
}) | |
.attr('cx',chartWidth + opts.margin.right * 0.75) | |
.attr('cy', function(d) { | |
return opts.margin.top + chartHeight - vertScale(d.After); | |
}) | |
.attr('r', 6); | |
// Create bottom area denoting years | |
svg.append("line") | |
.attr('x1', opts.margin.left) | |
.attr('x2', opts.margin.left) | |
.attr('y1', opts.height - opts.margin.bottom) | |
.attr('y2', opts.height - opts.margin.bottom * 0.7) | |
.attr('stroke', 'grey') | |
.attr('stroke-width', '2px'); | |
svg.append("line") | |
.attr('x1', chartWidth + opts.margin.right * 0.75) | |
.attr('x2', chartWidth + opts.margin.right * 0.75) | |
.attr('y1', opts.height - opts.margin.bottom) | |
.attr('y2', opts.height - opts.margin.bottom * 0.7) | |
.attr('stroke', 'grey') | |
.attr('stroke-width', '2px'); | |
svg.append("line") | |
.attr('x1', opts.margin.left) | |
.attr('x2', chartWidth + opts.margin.right * 0.75) | |
.attr('y1', opts.height - opts.margin.bottom * 0.7) | |
.attr('y2', opts.height - opts.margin.bottom * 0.7) | |
.attr('stroke', 'grey') | |
.attr('stroke-width', '2px'); | |
svg.append('text') | |
.text('2016') | |
.attr('class', 'neutral') | |
.attr('x', opts.margin.left) | |
.attr('y', opts.height - opts.margin.bottom * 0.05) | |
.attr('text-anchor', 'start'); | |
svg.append('text') | |
.text('2017') | |
.attr('class', 'neutral') | |
.attr('x', chartWidth + opts.margin.right * 0.75) | |
.attr('y', opts.height - opts.margin.bottom * 0.05) | |
.attr('text-anchor', 'end'); | |
// Get y values of notes and add notes to viz | |
var pythonY = data.filter(function (ind) { | |
return ind.Tool == 'Python'; | |
}); | |
var rapidMinerY = data.filter(function (ind) { | |
return ind.Tool == 'RapidMiner'; | |
}); | |
var tensorflowY = data.filter(function (ind) { | |
return ind.Tool == 'Tensorflow'; | |
}); | |
svg.selectAll('text-comments') | |
.data(data) | |
.enter() | |
.append('text') | |
.text(function(d) { | |
return d.Comments; | |
}) | |
.attr('class', 'neutral') | |
.attr('text-anchor', 'start') | |
.attr('x', chartWidth + opts.margin.right) | |
.attr('y', function(d) { | |
return opts.margin.top + chartHeight - d.AfterY; | |
}) | |
.attr('dy', '.25em') | |
.call(wrap, opts.margin.right); | |
function wrap(text, width) { | |
text.each(function() { | |
var text = d3.select(this), | |
words = text.text().split(/\s+/).reverse(), | |
word, | |
line = [], | |
lineNumber = 0, | |
lineHeight = 1.1, // ems | |
y = text.attr("y"), | |
dy = parseFloat(text.attr("dy")), | |
tspan = text.text(null).append("tspan").attr("x", chartWidth + opts.margin.left).attr("y", y).attr("dy", dy + "em"); | |
while (word = words.pop()) { | |
line.push(word); | |
tspan.text(line.join(" ")); | |
if (tspan.node().getComputedTextLength() > width) { | |
line.pop(); | |
tspan.text(line.join(" ")); | |
line = [word]; | |
tspan = text.append("tspan").attr("x", chartWidth + opts.margin.left).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); | |
} | |
} | |
}); | |
} | |
}); | |
function round10(x) { | |
return Math.round(x / 10) * 10; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body { | |
font-family: Arial, Helvetica, sans-serif; | |
} | |
line { | |
stroke-width: 3px; | |
stroke-opacity: 0.7; | |
} | |
line.positive { | |
stroke: #1b4fa3; | |
stroke-width: 5px; | |
stroke-opacity: 0.8; | |
} | |
line.negative { | |
stroke: #991616; | |
stroke-width: 5px; | |
stroke-opacity: 0.8; | |
} | |
line.neutral { | |
stroke: grey; | |
} | |
text.positive { | |
fill: #1b4fa3; | |
font-weight: 600; | |
} | |
text.negative { | |
fill: #991616; | |
font-weight: 600; | |
} | |
text.neutral { | |
fill: grey; | |
} | |
.label { | |
font-weight: 600; | |
} | |
circle.positive { | |
fill: #1b4fa3; | |
} | |
circle.negative { | |
fill: #991616; | |
} | |
circle.neutral { | |
fill: grey; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment