Skip to content

Instantly share code, notes, and snippets.

@timproDev
Last active April 4, 2018 19:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timproDev/b051510ce77f805f4876f4125687e8a3 to your computer and use it in GitHub Desktop.
Save timproDev/b051510ce77f805f4876f4125687e8a3 to your computer and use it in GitHub Desktop.
Multi Line with Hover
line {
shape-rendering: crispEdges; }
.aboveLabelTickText {
font-family: 'Raleway', sans-serif;
font-size: 1rem;
fill: #404040; }
.barLabel {
font-family: 'Raleway', sans-serif;
font-size: 1.15rem; }
.legend-item text {
font-family: 'Raleway', sans-serif;
font-size: .85rem; }
.center-label {
font-family: 'Raleway', sans-serif;
font-size: 1rem;
letter-spacing: -.5px;
fill: #808080;
text-transform: uppercase; }
.center-label-value-null {
font-family: 'Raleway', sans-serif;
font-size: 1.5rem;
letter-spacing: -.75px; }
#lineChartMultiBlocks .cov-text {
font-family: 'Raleway', sans-serif;
font-size: 13px; }
.barLabelLight {
font-family: 'Raleway', sans-serif;
font-size: .75rem; }
text.barLabelLight {
cursor: default;
letter-spacing: -.5px; }
.global-chart-container {
text-align: center;
}
.global-chart-container > section > h5 {
padding: 0;
margin: 0;
font-family: 'Raleway', sans-serif;
font-size: 26px;
font-weight: normal;
color: #bfbfbf;
width: 50%;
margin: 0 auto; }
.prm-box, .tooltip {
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.5);
border-radius: 2px;
background-color: #fff; }
.mx-dv-container {
text-align: center;
margin: 2rem auto;
padding: .5rem 1rem 1rem 1rem;
border: 1px solid #f2f2f2;
border-radius: 3px;
max-width: 775px;
/* IE HACK CANVAS */ }
.mx-dv-container .plot-wrap.no-svg {
display: none;
padding-bottom: 1rem; }
.mx-dv-container .plot-wrap.no-svg img {
width: 100%;
max-width: 706px; }
.mx-dv-container .svg-container {
position: relative; }
.mx-dv-container .svg-container canvas {
display: block;
height: 100%;
visibility: hidden; }
.mx-dv-container .svg-container svg {
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%; }
.mx-dv-container .chart-title {
padding: 1.15rem 0 0 0;
margin: 0;
text-align: left; }
.mx-dv-container .chart-premise, .mx-dv-container .chart-citation {
color: #808080;
padding: .25rem 0 1rem 0;
margin: 0;
text-align: left; }
.mx-dv-container .chart-premise p, .mx-dv-container .chart-premise input, .mx-dv-container .chart-citation p, .mx-dv-container .chart-citation input {
margin: 0;
text-align: left;
display: inline-block;
vertical-align: middle;
padding: 0 .75rem .25rem 0; }
.mx-dv-container .chart-citation {
color: #bdbdbd; }
.mx-dv-container .raw-wrap {
text-align: right;
padding: 0 1.5rem 0 0;
margin: 0; }
.mx-dv-container .raw-wrap .raw-btn {
cursor: pointer;
margin: 0 0 .75rem 0;
padding: 0; }
.tooltip {
top: -1000px;
position: fixed;
padding: .65rem;
pointer-events: none;
max-width: 10%;
z-index: 500;
opacity: .9; }
.tooltip-info {
font-family: 'Raleway', sans-serif;
color: #45555f;
padding: 0;
margin: 0;
display: block;
width: 100%; }
.tooltip-info span {
font-family: 'Raleway', sans-serif; }
.tooltip-hidden {
opacity: 0;
transition: all .3s;
transition-delay: .1s; }
.tooltip.small {
padding: .25rem; }
.tooltip.small .tooltip-info {
font-size: .85rem; }
@media only screen and (max-width: 40em) {
.mx-dv-container .plot-wrap.no-svg {
display: block; }
.mx-dv-container .plot-wrap.w-svg {
display: none; } }
Genre 2012 2013 2014 2015 2016 2017
Tuvan Throat Singing 0 0.545454545 0.65 -0.755555556 0.25 -0.1
Modern Pan Flute Music 0 0.45 -0.06 0.034482759 0.16 -0.05
Post-Modern 0 1 -0.025 -0.35 0.6 -1.25
Chinese Opera 0 0.7 0.176470588 0.3 0.192307692 0.096774194
Nintendocore 0 0.75 1.125 -1.33 0 -0.25
Chap Hop 0 0.22 0 0.09 -0.08 0.04
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3 Factory</title>
<link href="https://fonts.googleapis.com/css?family=Raleway" rel="stylesheet">
<link rel="stylesheet" href="app.css">
</head>
<body>
<div class="global-chart-container">
<section style="width:75%; margin:3rem 0; display: inline-block;">
<div class="mx-dv-container" id="lineChartMultiBlocks"><div class="plot-wrap w-svg"></div></div>
</section>
</div>
<script src="line-chart-multi.js"></script>
<script src="line-chart-multi-init.js"></script>
<script src="https://rawgit.com/timproDev/d3-first-timer/master/js/d3v4-473-jetpack.js"></script>
</body>
</html>
document.addEventListener("DOMContentLoaded", function(){
var path = '';
var elemContainer = ['#lineChartMultiBlocks'];
var uiEl = '.chart-ui-container input';
var cssFile = 'line-chart-multi.css';
var dataFile = 'data.csv';
var dataPoint = 'Genre';
var lineChartMulti = new LineChartMulti({
elem:document.querySelector(elemContainer[0]),
dataPoint: dataPoint,
dataByGroup:'header_one_name',
transSpeed:700
});
chartInit(lineChartMulti, elemContainer[0], dataFile);
function chartInit(chartInst, elem, dataFile) {
d3.csv((path + dataFile),
function(data) {
var dataNest2013 = d3.nest()
.key(function(d) {return d['Genre'];})
.key(function(d) {return d['2013'];})
.entries(data)
.map(function(d){
return {
'Genre' : d.key,
"year":"2013",
"value":d.values[0].key
}
})
var dataNest2014 = d3.nest()
.key(function(d) {return d['Genre'];})
.key(function(d) {return d['2014'];})
.entries(data)
.map(function(d){
return {
'Genre' : d.key,
"year":"2014",
"value":d.values[0].key
}
})
var dataNest2015 = d3.nest()
.key(function(d) {return d['Genre'];})
.key(function(d) {return d['2015'];})
.entries(data)
.map(function(d){
return {
'Genre' : d.key,
"year":"2015",
"value":d.values[0].key
}
})
var dataNest2016 = d3.nest()
.key(function(d) {return d['Genre'];})
.key(function(d) {return d['2016'];})
.entries(data)
.map(function(d){
return {
'Genre' : d.key,
"year":"2016",
"value":d.values[0].key
}
})
var dataNest2017 = d3.nest()
.key(function(d) {return d['Genre'];})
.key(function(d) {return d['2017'];})
.entries(data)
.map(function(d){
return {
'Genre' : d.key,
"year":"2017",
"value":d.values[0].key
}
})
dataNest2014.forEach(function(d,i){
dataNest2013.splice((i+(i+1)),0,d);
});
dataNest2015.forEach(function(d,i){
dataNest2013.splice((i+(i+1)),0,d);
});
dataNest2016.forEach(function(d,i){
dataNest2013.splice((i+(i+1)),0,d);
});
dataNest2017.forEach(function(d,i){
dataNest2013.splice((i+(i+1)),0,d);
});
dataNest2013.sort(function(a,b){
return d3.descending(a['Genre'], b['Genre'])
});
dataNest2013.forEach(function(d){
d.value = +d.value;
})
console.log('Here I had to manipulate the data so to convert some headers and their values into columns. In this case, the years were originally the header. Thanks to the power of d3.nest and .map, I created new columns years as the values.');
console.log('raw data:', data);
console.log('nested data:', dataNest2013);
chartInst.setData(dataNest2013);
});
}
// include resize event
d3.select(window).on('resize', function(){
lineChartMulti.resize();
});
});
var LineChartMulti = function(opts){
this.dataByGroup = opts.dataByGroup;
this.element = opts.elem.querySelector('.plot-wrap.w-svg');
this.transSpeed = opts.transSpeed;
this.dataPoint = opts.dataPoint;
}
LineChartMulti.prototype.draw = function(){
var self = this
this.margin = {
top:140,
left:40,
right:10,
bottom:30
};
this.width = this.element.offsetWidth - this.margin.left - this.margin.right;
this.height = this.width / 1.25 - (this.margin.top - this.margin.bottom);
this.element.innerHTML = '';
var svg = d3.select(this.element).append('svg');
svg.attr('width', (this.width + this.margin.left + this.margin.right));
svg.attr('height', (this.height + this.margin.top + this.margin.bottom));
this.plot = svg.append('g')
.attr('transform',`translate(${this.margin.left},${this.margin.top})`);
this.createScales()
this.addAxes()
this.buildLine()
}
LineChartMulti.prototype.createScales = function() {
var self = this
this.interpolateTypes = [
d3.curveLinear,
d3.curveStepBefore,
d3.curveStepAfter,
d3.curveBasis,
d3.curveBasisOpen,
d3.curveBasisClosed,
d3.curveBundle,
d3.curveCardinal,
d3.curveCardinal,
d3.curveCardinalOpen,
d3.curveCardinalClosed,
d3.curveNatural
];
this.percentFormat = d3.format(",.1%");
this.xScale = d3.scalePoint().domain(["2013","2014","2015","2016", "2017"]).range([20,(this.width-20)]);
this.yScale = d3.scaleLinear().domain([-2,2]).range([this.height,0]);
this.colorScale = d3.scaleOrdinal(d3.schemeCategory10);
}
LineChartMulti.prototype.addAxes = function(){
var self = this
this.xAxis = d3.axisBottom(this.xScale);
this.yAxis = d3.axisLeft(this.yScale).tickSize(-this.width).tickFormat(this.percentFormat);
this.plot.append('g.x-axis').call(this.xAxis).attr('transform','translate(0,' + this.height + ')');
this.plot.append('g.y-axis').call(this.yAxis);
this.plot.selectAll('.domain').attr('display','none');
this.plot.selectAll('.y-axis .tick line').attr('stroke','#efefef');
}
LineChartMulti.prototype.buildLine = function(){
var self = this
this.plot
.append('line')
.attr('x1',0)
.attr('x2',this.width)
.attr('y1',this.yScale(0))
.attr('y2',this.yScale(0))
.attr('fill','none')
.attr('stroke','#505050')
.attr('stroke-width','1');
this.lineGen2 = d3.line()
.x( function(d){ return self.xScale(d.year); })
.y( function(d){ return self.yScale(d.value); })
.curve(this.interpolateTypes[0]);
this.nestedData = d3.nest()
.key(function(d){ return d[self.dataPoint]; })
.sortValues(function(a,b) { return parseFloat(a.year) - parseFloat(b.year); })
.entries(this.data);
this.plot.selectAll(".cov-path")
.data(this.nestedData)
.enter()
.append("path.cov-path")
.attr("d", function(d){
return self.lineGen2(d.values);
})
.attr('fill','none')
.attr('stroke',function(d){
return self.colorScale(d.key);
})
.attr('stroke-width','8')
.attr('opacity','.5');
this.labels = this.plot.selectAll(".cov-text")
.data(this.data)
.enter()
.append("text.cov-text")
.attr('text-anchor','middle')
.attr("dy", function(d){
if(d.value > 0) {
return "-10px";
} else {
return "20px";
}
})
.attr("x",function(d){
return self.xScale(d.year);
})
.attr("y",function(d){
return self.yScale(d.value);
})
.attr('opacity','0')
.attr('font-size','.75rem')
.text(function(d){
return self.percentFormat(d.value);
});
var legendXPos = -this.margin.left;
// stack layout
this.legend = this.plot.append("g.legend")
.selectAll('g.legend-item')
.data(this.nestedData)
.enter()
.append('g.legend-item');
this.legend
.append('rect.legend-color')
.attr("fill", function(d) { return self.colorScale(d.key); })
.attr('width',10)
.attr('height',10)
.attr('x', legendXPos)
.attr("y", function(d, i){ return (i * 20) - self.margin.top + 10;});
this.legend
.append('text.legend-text')
.text(function(d){ return d.key; })
.attr('x', legendXPos + 15)
.attr("y", function(d, i){ return ((i * 20) + 9) - self.margin.top + 10;});
this.plot.selectAll('path.cov-path, text.legend-text, rect.legend-color')
.on('mouseenter', function(d){
var thisData = d;
self.plot.selectAll('path.cov-path, text.legend-text, rect.legend-color').transition(20).style('opacity','.25');
self.plot.selectAll('path.cov-path, text.legend-text, rect.legend-color').transition(20).style('opacity',function (d,i) {
return (d === thisData) ? 1.0 : 0.25;
});
self.plot.selectAll('.cov-text').transition(20).style('opacity',function (d,i) {
return (d[self.dataPoint] === thisData.key) ? 1.0 : 0;
});
})
.on('mouseleave', function(d){
self.plot.selectAll('text.legend-text, rect.legend-color').transition(20).style('opacity','1');
self.plot.selectAll('text.cov-text').transition(20).style('opacity','0');
self.plot.selectAll('path.cov-path').transition(20).style('opacity','.25');
self.plot.selectAll('path.cov-path').transition(20).style('opacity',function () {
return .5;
});
});
this.plot.selectAll('.cov-path, .legend-color, .legend-text').attr('cursor','default');
}
LineChartMulti.prototype.setData = function(newData) {
this.data = newData;
this.draw();
}
LineChartMulti.prototype.resize = function(){
this.width = parseInt(d3.select(this.element).style('width'), 10);
this.draw();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment