I have been trying to adapt the chained transition script of Mike Bostock to work with multiple lines but I do not get it to work. After the first display the lines and labels fly out of the plot and do not show anymore Whereas everything gets updated (I can see the values of the lines changing when inspecting the javascript console) . I do not understand what I am doing wrong. I will post the (lengthy) code here below (apologies for the length). I would appreciate any help, thank you!
<!DOCTYPE html>
<head>
<title>Modified Chained Transitions</title>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v3.min.js"></script>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
</style>
</head>
<body>
<br>
<button type="button"> Request data</button>
<div id='chart'> </div>
</body>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 750 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var xScale = d3.time.scale()
.range([0, width]);
var yScale = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return xScale(d.date); })
.y(function(d) { return yScale(d.temperature); });
var svg = d3.select("body").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 + ")");
var getNewData = function() {
var data = [];
var counter = 0;
function generate(){
var startDate = new Date;
counter += 1;
var range = counter % 2 === 0 ? 10 : 100;
for (i = 0; i < 100; i++) {
data[i] = {"date": new Date(startDate - i),
"New York": Math.random() * (range - 1),
"San Francisco": Math.random() * (range - 1),
"Austin": Math.random() * (range - 10)};
}
return data;
}
return {
new: function () {return generate()}
};
}; // function getNewData()
var newData = getNewData();
data = newData.new();
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return { date: d.date, temperature: +d[name]};
})
};
});
xScale.domain(d3.extent(data, function(d) { return d.date; }));
yScale.domain([
d3.min(cities, function(c) {
return d3.min(c.values, function(v) { return v.temperature; }); }),
d3.max(cities, function(c) {
return d3.max(c.values, function(v) { return v.temperature; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
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")
.text("Temperature (ºF)");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
city.append("text")
.datum(function(d) { return {name: d.name, values: d.values[0]}; })
.attr("class", "label")
.attr("transform", function(d) { return "translate(" +
xScale(d.values.date) + "," + yScale(d.values.temperature) + ")"; })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; });
d3.selectAll("button").on("click", change);
function change() {
data = newData.new();
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return { date: d.date, temperature: +d[name]};
})
};
});
console.log(cities[0].values[0]);
xScale.domain(d3.extent(data, function(d) { return d.date; }));
yScale.domain([
d3.min(cities, function(c) {
return d3.min(c.values, function(v) { return v.temperature; }); }),
d3.max(cities, function(c) {
return d3.max(c.values, function(v) { return v.temperature; }); })
]);
var t0 = svg.transition().duration(750);
t0.selectAll(".line")
.attr("d", function(cities) { return line(cities.values); })
.style("stroke", function(cities) { return color(cities.name); });
t0.selectAll(".label").attr("transform",
"translate(0,0)").text(function(cities) { return cities.name; });
var t1 = t0.transition();
// t1.selectAll(".line").attr("d", line(data));
// t1.select(".line")
t1.selectAll(".line")
// t1.selectAll(".city")
.attr("d", function(cities) { return line(cities.values); })
.style("stroke", function(cities) { return color(cities.name); });
t1.select(".y.axis").call(yAxis);
t1.select(".x.axis").call(xAxis);
t1.select(".label")
.attr("transform", function(d) { return "translate(" +
xScale(d.values.date) + "," +
yScale(d.values.temperature) + ")"; });
} // function change()
</script>
</html>
I can help fix your transitions but I'm not sure what you are attempting to "chain". In the linked example, Bostock swaps one line for another (transition 1), then fits that line to a new domain (transition 2). You do not seem to want to swap lines, so you fit a new domain and then transition the lines to it (transition 1) but what's transition 2?
Now to answer your more direct question of why your transitions aren't working, it's simply because you never update your data. In the linked example Bostock has both datasets bound to his line and then swaps which he's drawing in the line function. You, though, only ever have the original dataset bound. Quick fix is:
function change() {
... //<-- get new data
// bind your new data
var cities = svg.selectAll(".city")
.data(cities)
// sub selection to transition line
cities
.select(".line")
.transition()
.duration(750)
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); })
// concurrent sub selection to move labels
cities
.select(".label")
.transition()
.duration(750)
.attr("transform", function(d){
var last = d.values[0];
return "translate(" + xScale(last.date) + "," + yScale(last.temperature) + ")";
})
}
Running code:
<!DOCTYPE html>
<head>
<title>Modified Chained Transitions</title>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v3.min.js"></script>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
</style>
</head>
<body>
<br>
<button type="button"> Request data</button>
<div id='chart'> </div>
</body>
<script>
var margin = {
top: 20,
right: 80,
bottom: 30,
left: 50
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var xScale = d3.time.scale()
.range([0, width]);
var yScale = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) {
return xScale(d.date);
})
.y(function(d) {
return yScale(d.temperature);
});
var svg = d3.select("body").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 + ")");
var getNewData = function() {
var data = [];
var counter = 0;
function generate() {
var startDate = new Date;
counter += 1;
var range = counter % 2 === 0 ? 10 : 100;
for (i = 0; i < 100; i++) {
data[i] = {
"date": new Date(startDate - i),
"New York": Math.random() * (range - 1),
"San Francisco": Math.random() * (range - 1),
"Austin": Math.random() * (range - 10)
};
}
return data;
}
return {
new: function() {
return generate()
}
};
}; // function getNewData()
var newData = getNewData();
data = newData.new();
color.domain(d3.keys(data[0]).filter(function(key) {
return key !== "date";
}));
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
date: d.date,
temperature: +d[name]
};
})
};
});
xScale.domain(d3.extent(data, function(d) {
return d.date;
}));
yScale.domain([
d3.min(cities, function(c) {
return d3.min(c.values, function(v) {
return v.temperature;
});
}),
d3.max(cities, function(c) {
return d3.max(c.values, function(v) {
return v.temperature;
});
})
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
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")
.text("Temperature (ºF)");
var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.name);
});
city.append("text")
.datum(function(d) {
return {
name: d.name,
values: d.values[0]
};
})
.attr("class", "label")
.attr("transform", function(d) {
return "translate(" +
xScale(d.values.date) + "," + yScale(d.values.temperature) + ")";
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) {
return d.name;
});
d3.selectAll("button").on("click", change);
function change() {
data = newData.new();
color.domain(d3.keys(data[0]).filter(function(key) {
return key !== "date";
}));
cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
date: d.date,
temperature: +d[name]
};
})
};
});
xScale.domain(d3.extent(data, function(d) {
return d.date;
}));
yScale.domain([
d3.min(cities, function(c) {
return d3.min(c.values, function(v) {
return v.temperature;
});
}),
d3.max(cities, function(c) {
return d3.max(c.values, function(v) {
return v.temperature;
});
})
]);
var cities = svg.selectAll(".city")
.data(cities)
cities
.select(".line")
.transition()
.duration(750)
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return color(d.name);
})
cities
.select(".label")
.transition()
.duration(750)
.attr("transform", function(d) {
var last = d.values[0];
return "translate(" + xScale(last.date) + "," + yScale(last.temperature) + ")";
})
svg.selectAll(".y.axis")
.transition()
.duration(750)
.call(yAxis);
svg.selectAll(".x.axis")
.transition()
.duration(750)
.call(xAxis);
} // function change()
</script>
</html>
Related
I'm trying to write a generic multiline chart function based on Mike Bostock's example (https://bl.ocks.org/mbostock/3884955).
I'm facing an issue wherein, the last tick label on my Monthly graph x-axis does not show up. The last tick appears fine on the Weekly graph x-axis.
JS Fiddle Link:
http://jsfiddle.net/Q5Jag/11879/
I'm suspecting the issue here could be due to the range specified for the x-axis which for some reason ignores the last value. But I'm not exactly sure what is going on here. Could anyone help me debug ?
Here is my code:
function renderMultiLineChart(datafile, chartDiv, xAxisLabel, yAxisLabel, graphCadence){
var margin = {top: 20, right: 60, bottom: 80, left: 60},
width = 760 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%m/%d/%y");
var x = d3.scaleUtc()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
//var xAxis = d3.axisBottom(x).tickFormat(function(d){ return d.x;});
switch(graphCadence) {
case "Daily":
var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Weekly":
var xAxis = d3.axisBottom(x).ticks(d3.timeSaturday.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Monthly":
//var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1))
var xAxis = d3.axisBottom(x).ticks(d3.timeMonth.every(1)).tickFormat(d3.utcFormat("%m/%d"))
break;
}
var yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.count); })
var div = d3.select(chartDiv).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select(chartDiv).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 + ")");
//https://pharos-rest-service-iad.iad.proxy.amazon.com/s3/tool.csv
d3.csv(datafile, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
//console.log(d);
d.date = parseDate(d.date);
});
var datapoints = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, count: +d[name]};
})
};
});
console.log(data);
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(datapoints, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),
d3.max(datapoints, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.style("text-anchor", "end")
.attr("transform", "rotate(-45)");
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 30) + ")")
.style("text-anchor", "middle")
.text(xAxisLabel);
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")
.style("fill", "black") // set the line colour
.text(yAxisLabel);
var datapoint = svg.selectAll(".datapoint")
.data(datapoints)
.enter().append("g")
.attr("class", "datapoint");
datapoint.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
j = -1;
datapoint.selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d,i) { return x(d.date); })
.attr("cy", function(d) { return y(d.count); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", 1);
div.html("<b>"+d.count+"</b>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.style("fill", function(d,i) { if (i == 0) { j++ }; return color(datapoints[j].name); });
var legendRectSize = 8;
var legendSpacing = 80;
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (100+margin.left+margin.right) + ","+(height+margin.bottom-20)+")")
var legend = legendHolder.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr("transform", function (d, i) {
if (i === 0) {
dataL = legendRectSize + legendSpacing
return "translate(0,0)"
} else {
var newdataL = dataL
dataL += legendRectSize + legendSpacing
return "translate(" + (newdataL) + ",0)"
}
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + 5)
.attr('y', legendRectSize)
.text(function(d) { return d; });
});
}
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/323024b01c1eb4d0c07637e183e1e6d7/raw/422ed207cc2c38426fa726795ecd963f153135dd/app_usage","div#multiChartMonthly","Snapshot Date","Metric Count","Monthly")
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/8ed38bdb3b8e44194ee8570ef9cc5b75/raw/d0c85aaf9eaa7e8819fd6e6e210885b0cfa6f47d/app_usage_weekly","div#multiChartWeekly","Snapshot Date","Metric Count","Weekly")
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
/*display: none;*/
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
div.tooltip {
position: absolute;
text-align: center;
/*width: 60px;
height: 28px;*/
padding: 4px;
font: 14px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 10px;
pointer-events: none;
}
.legend {
font-size: 12px;
}
rect {
stroke-width: 2;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="multiChartWeekly">
<div id="multiChartMonthly">
Thanks,
Yogesh
I believe the problem lies in a misalignment between how d3.timeMonth works and how your data is formatted. You see that d3.timeMonth is labeling the first of every month, whereas the data is grouped on the last day of every month. So when you call x.domain(d3.extent(data, function(d) { return d.date; }));, the last data point's date is less than the next label would have been.
One potential solution is to change your parse function to bump all your month dates forward by one day to make them line up with the first of the month. See below.
To remove any dates that end mid-month, you can filter the data set after parsing dates to clean it up.
function renderMultiLineChart(datafile, chartDiv, xAxisLabel, yAxisLabel, graphCadence){
var margin = {top: 20, right: 60, bottom: 80, left: 60},
width = 760 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var parseDate = function(dateString){
if(graphCadence == "Monthly"){
var date = new Date(dateString);
date.setDate(date.getDate()+1);
return date;
}
return d3.timeParse("%m/%d/%y")(dateString);
}
var x = d3.scaleUtc()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
//var xAxis = d3.axisBottom(x).tickFormat(function(d){ return d.x;});
switch(graphCadence) {
case "Daily":
var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Weekly":
var xAxis = d3.axisBottom(x).ticks(d3.timeSaturday.every(1)).tickFormat(d3.timeFormat("%m/%d"))
break;
case "Monthly":
//var xAxis = d3.axisBottom(x).ticks(d3.timeDay.every(1))
var xAxis = d3.axisBottom(x).ticks(d3.timeMonth.every(1)).tickFormat(d3.utcFormat("%m/%d"))
break;
}
var yAxis = d3.axisLeft(y);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.count); })
var div = d3.select(chartDiv).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select(chartDiv).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 + ")");
//https://pharos-rest-service-iad.iad.proxy.amazon.com/s3/tool.csv
d3.csv(datafile, function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
if(graphCadence == "Monthly"){
data = data.filter(function(d){
return d.date.getDate() == 1
});
}
var datapoints = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, count: +d[name]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(datapoints, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),
d3.max(datapoints, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.style("text-anchor", "end")
.attr("transform", "rotate(-45)");
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," +
(height + margin.top + 30) + ")")
.style("text-anchor", "middle")
.text(xAxisLabel);
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")
.style("fill", "black") // set the line colour
.text(yAxisLabel);
var datapoint = svg.selectAll(".datapoint")
.data(datapoints)
.enter().append("g")
.attr("class", "datapoint");
datapoint.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return color(d.name); });
j = -1;
datapoint.selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d,i) { return x(d.date); })
.attr("cy", function(d) { return y(d.count); })
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", 1);
div.html("<b>"+d.count+"</b>")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
})
.style("fill", function(d,i) { if (i == 0) { j++ }; return color(datapoints[j].name); });
var legendRectSize = 8;
var legendSpacing = 80;
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (100+margin.left+margin.right) + ","+(height+margin.bottom-20)+")")
var legend = legendHolder.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr("transform", function (d, i) {
if (i === 0) {
dataL = legendRectSize + legendSpacing
return "translate(0,0)"
} else {
var newdataL = dataL
dataL += legendRectSize + legendSpacing
return "translate(" + (newdataL) + ",0)"
}
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + 5)
.attr('y', legendRectSize)
.text(function(d) { return d; });
});
}
renderMultiLineChart("https://gist.githubusercontent.com/JstnPwll/5a24137a36c9246cf065c58d7f5bb5a5/raw/ff986ee88338e99d10ab93035ffacd3ffe92fd4e/gistfile1.txt","div#multiChartMonthly","Snapshot Date","Metric Count","Monthly")
renderMultiLineChart("https://gist.githubusercontent.com/techyogii/8ed38bdb3b8e44194ee8570ef9cc5b75/raw/d0c85aaf9eaa7e8819fd6e6e210885b0cfa6f47d/app_usage_weekly","div#multiChartWeekly","Snapshot Date","Metric Count","Weekly")
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
/*display: none;*/
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
div.tooltip {
position: absolute;
text-align: center;
/*width: 60px;
height: 28px;*/
padding: 4px;
font: 14px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 10px;
pointer-events: none;
}
.legend {
font-size: 12px;
}
rect {
stroke-width: 2;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="multiChartWeekly">
<div id="multiChartMonthly">
[EDIT] Outputted Graph: https://imgur.com/a/D0wtP
I am trying to shade in the area between 2 lines in a graph. The plots shows commits made by software engineers and shows their tt100 lines of raw and productive code, and i am trying to shade the delta region between the 2 lines. Some of the solutions I have found do not seem to match my approach. I appreciate any help. My code is the following:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis--x path {
display: none;
}
.axis--y path {
display: none;
}
.line {
fill: none;
stroke-width: 1.5px;
}
.grid line {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
}
</style>
<svg width="1080" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 50},
width = svg.attr("width") - margin.left - margin.right - 50,
height = svg.attr("height") - margin.top - margin.bottom;
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.attr("fill", "#f5f5f1")
var parseTime = d3.timeParse("%Y%m");
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
var line = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.time); });
var g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// gridlines in y axis function
function make_y_gridlines() {
return d3.axisLeft(y)
.ticks(5)
}
d3.csv("data.csv", type, function(error, data) {
if (error) throw error;
var employees1 = data.columns.slice(1,3).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
var employees2 = data.columns.slice(3).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
var employees = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, time: d[id]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
0,
d3.max(employees, function(c) { return d3.max(c.values, function(d) { return d.time; }); })
]);
z.domain(employees.map(function(c) { return c.id; }));
// add the Y gridlines
g.append("g")
.attr("class", "grid")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
)
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -45)
.attr("dy", "0.71em")
.attr("fill", "#000")
.text("HOURS TO PRODUCE 100 LINES OF CODE (PRODUCTIVE VS RAW)");
g.append("text")
.attr("x", (width / 4))
.attr("y", 0 - (margin.top / 8))
.attr("text-anchor", "middle")
.style("font-size", "24px")
.text("Churn Over Time");
var employee1 = g.selectAll(".employee1")
.data(employees1)
.enter().append("g")
.attr("class", "employee1");
var employee2 = g.selectAll(".employee2")
.data(employees2)
.enter().append("g")
.attr("class", "employee2");
employee1.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", "A057AE");
employee1.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.time) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
employee2.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", "91BF50");
employee2.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.time) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
});
function type(d, _, columns) {
d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}
</script>
And the data.csv file is the following:
date,Paul Productive Code,Paul Raw Code,Michelle Productive Code,Michelle Raw Code
201801,4.1,3.2,2.2,1.9
201802,4.2,3.5,3.4,1.9
201803,4.1,3.1,3.1,1.9
201804,4.5,3.8,3.2,2.3
201805,6.4,4.7,3.7,2.7
201806,8.6,5.5,3.2,2.2
can you resolve this problem.
I am not able to rotate in X-axis values. can you please check below examples. Now x-axis text is coming horizontally but we wants Vertical alignment.
In my requirement is rotate -60 or -90 only. in "Model 1 , Module 2, Module 3" values i needs to rotate.
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
padding = 0.3;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], padding);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) { return dollarFormatter(d); });
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//d3.csv("data.csv", type, function(error, data) {
var data = [{ name :"Module 1",value : 20 },{ name :"Module 2",value :15},{ name :"Module 3 ",value :45},
{ name :"Final Count ",value :200}];
//console.log(data);
// Transform data (i.e., finding cumulative values and total) for easier charting
var cumulative = 0;
for (var i = 0; i < data.length; i++) {
data[i].start = cumulative;
cumulative += data[i].value;
data[i].end = cumulative;
data[i].class = ( data[i].value >= 0 ) ? 'positive' : 'negative'
}
data.push({
name: 'Total',
end: cumulative,
start: 0,
class: 'total'
});
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.end; })]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", function(d) { return "bar " + d.class })
.attr("transform", function(d) { return "translate(" + x(d.name) + ",0)"; });
bar.append("rect")
.attr("y", function(d) { return y( Math.max(d.start, d.end) ); })
.attr("height", function(d) { return Math.abs( y(d.start) - y(d.end) ); })
.attr("width", x.rangeBand());
bar.append("text")
.attr("x", x.rangeBand() / 2)
.attr("y", function(d) { return y(d.end) + 5; })
.attr("dy", function(d) { return ((d.class=='negative') ? '-' : '') + ".75em" })
.text(function(d) { return dollarFormatter(d.end - d.start);});
bar.filter(function(d) { return d.class != "total" }).append("line")
.attr("class", "connector")
.attr("x1", x.rangeBand() + 5 )
.attr("y1", function(d) { return y(d.end) } )
.attr("x2", x.rangeBand() / ( 1 - padding) - 5 )
.attr("y2", function(d) { return y(d.end) } )
//});
function type(d) {
d.value = +d.value;
return d;
}
function dollarFormatter(n) {
n = Math.round(n);
var result = n;
if (Math.abs(n) > 1000) {
result = Math.round(n/1000) + 'K';
}
return result;
}
.bar.total rect {
fill: steelblue;
}
.bar.positive rect {
fill: darkolivegreen;
}
.bar.negative rect {
fill: crimson;
}
.bar line.connector {
stroke: grey;
stroke-dasharray: 3;
}
.bar text {
fill: white;
font: 10px sans-serif;
text-anchor: middle;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<svg class="chart"></svg>
To rotate only the first 3 ticks ("module 1", "module 2" and "module 3"):
var ticks = d3.selectAll(".x.axis text").each(function(d, i) {
if (i < 3) {
d3.select(this).attr("y", 0)
d3.select(this).attr("x", 10)
d3.select(this).attr("dy", ".35em")
d3.select(this).attr("transform", "rotate(90)")
d3.select(this).style("text-anchor", "start");
}
});
Check the demo:
var margin = {
top: 20,
right: 30,
bottom: 60,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
padding = 0.3;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], padding);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) {
return dollarFormatter(d);
});
var chart = d3.select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//d3.csv("data.csv", type, function(error, data) {
var data = [{
name: "Module 1",
value: 20
}, {
name: "Module 2",
value: 15
}, {
name: "Module 3 ",
value: 45
}, {
name: "Final Count ",
value: 200
}];
//console.log(data);
// Transform data (i.e., finding cumulative values and total) for easier charting
var cumulative = 0;
for (var i = 0; i < data.length; i++) {
data[i].start = cumulative;
cumulative += data[i].value;
data[i].end = cumulative;
data[i].class = (data[i].value >= 0) ? 'positive' : 'negative'
}
data.push({
name: 'Total',
end: cumulative,
start: 0,
class: 'total'
});
x.domain(data.map(function(d) {
return d.name;
}));
y.domain([0, d3.max(data, function(d) {
return d.end;
})]);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var ticks = d3.selectAll(".x.axis text").each(function(d, i) {
if (i < 3) {
d3.select(this).attr("y", 0)
d3.select(this).attr("x", 10)
d3.select(this).attr("dy", ".35em")
d3.select(this).attr("transform", "rotate(90)")
d3.select(this).style("text-anchor", "start");
}
});
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
var bar = chart.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return "bar " + d.class
})
.attr("transform", function(d) {
return "translate(" + x(d.name) + ",0)";
});
bar.append("rect")
.attr("y", function(d) {
return y(Math.max(d.start, d.end));
})
.attr("height", function(d) {
return Math.abs(y(d.start) - y(d.end));
})
.attr("width", x.rangeBand());
bar.append("text")
.attr("x", x.rangeBand() / 2)
.attr("y", function(d) {
return y(d.end) + 5;
})
.attr("dy", function(d) {
return ((d.class == 'negative') ? '-' : '') + ".75em"
})
.text(function(d) {
return dollarFormatter(d.end - d.start);
});
bar.filter(function(d) {
return d.class != "total"
}).append("line")
.attr("class", "connector")
.attr("x1", x.rangeBand() + 5)
.attr("y1", function(d) {
return y(d.end)
})
.attr("x2", x.rangeBand() / (1 - padding) - 5)
.attr("y2", function(d) {
return y(d.end)
})
//});
function type(d) {
d.value = +d.value;
return d;
}
function dollarFormatter(n) {
n = Math.round(n);
var result = n;
if (Math.abs(n) > 1000) {
result = Math.round(n / 1000) + 'K';
}
return result;
}
.bar.total rect {
fill: steelblue;
}
.bar.positive rect {
fill: darkolivegreen;
}
.bar.negative rect {
fill: crimson;
}
.bar line.connector {
stroke: grey;
stroke-dasharray: 3;
}
.bar text {
fill: white;
font: 10px sans-serif;
text-anchor: middle;
}
.axis text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<svg class="chart"></svg>
I want to make D3 bar chart with additional vertical lines on the x axis..
I checked one of the web sites in that below example.
I want make it like that graph, but I want an additional bottom line.
Can you please show me what to change?
var margin = {
top: 20,
right: 20,
bottom: 60,
left: 40
},
width = 560 - margin.left - margin.right,
height = 360 - margin.top - margin.bottom;
var color = {
Mechanical: '#4A7B9D',
Electrical: '#54577C',
Hydraulic: '#ED6A5A'
};
var barPadding = 40;
var data = [{
key: 'Mechanical',
values: [{
key: 'Gear',
value: 11
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}]
}, {
key: 'Electrical',
values: [{
key: 'Switch',
value: 19
}, {
key: 'Plug',
value: 12
}, {
key: 'Cord',
value: 11
}, {
key: 'Fuse',
value: 3
}, {
key: 'Bulb',
value: 2
}]
}, {
key: 'Hydraulic',
values: [{
key: 'Pump',
value: 4
}, {
key: 'Leak',
value: 3
}, {
key: 'Seals',
value: 1
}]
}];
var rangeBands = [];
var cummulative = 0;
data.forEach(function(val, i) {
val.cummulative = cummulative;
cummulative += val.values.length;
val.values.forEach(function(values) {
values.parentKey = val.key;
rangeBands.push(i);
})
});
//console.log(data);
var x_category = d3.scale.linear()
.range([0, width]);
var x_defect = d3.scale.ordinal().domain(rangeBands).rangeRoundBands([0, width], .1);
var x_category_domain = x_defect.rangeBand() * rangeBands.length;
x_category.domain([0, x_category_domain]);
var y = d3.scale.linear()
.range([height, 0]);
y.domain([0, d3.max(data, function(cat) {
return d3.max(cat.values, function(def) {
return def.value;
});
})]);
var category_axis = d3.svg.axis()
.scale(x_category)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style('background-color', 'EFEFEF')
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
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")
.text("Value");
var category_g = svg.selectAll(".category")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return 'category category-' + d.key;
})
.attr("transform", function(d) {
return "translate(" + x_category((d.cummulative * x_defect.rangeBand())) + ",0)";
})
.attr("fill", function(d) {
return color[d.key];
});
var category_label = category_g.selectAll(".category-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
//console.log(d)
return 'category-label category-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.values.length * x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 30;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var defect_g = category_g.selectAll(".defect")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'defect defect-' + d.key;
})
.attr("transform", function(d, i) {
return "translate(" + x_category((i * x_defect.rangeBand())) + ",0)";
});
var defect_label = defect_g.selectAll(".defect-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
//console.log(d)
return 'defect-label defect-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 10;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var rects = defect_g.selectAll('.rect')
.data(function(d) {
return [d];
})
.enter().append("rect")
.attr("class", "rect")
.attr("width", x_category(x_defect.rangeBand() - barPadding))
.attr("x", function(d) {
return x_category(barPadding);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
/* Styles go here */
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Hello Plunker!</h1>
</body>
</html>
Try this way: -
category_g
.append("line")
.style("stroke", "black")
.style("stroke-width", "2px")
.style("stroke-linecap","round")
.attr("x1", function(d) {
var bbox = this.parentNode.getBBox();
return bbox.width+5;
})
.attr("y1", height)
.attr("x2", function(d) {
var bbox = this.parentNode.getBBox();
return bbox.width;
})
.attr("y2", height + 50);
var margin = {
top: 20,
right: 20,
bottom: 60,
left: 40
},
width = 560 - margin.left - margin.right,
height = 360 - margin.top - margin.bottom;
var color = {
Mechanical: '#4A7B9D',
Electrical: '#54577C',
Hydraulic: '#ED6A5A'
};
var barPadding = 40;
var data = [{
key: 'Mechanical',
values: [{
key: 'Gear',
value: 11
}, {
key: 'Bearing',
value: 8
}, {
key: 'Motor',
value: 3
}]
}, {
key: 'Electrical',
values: [{
key: 'Switch',
value: 19
}, {
key: 'Plug',
value: 12
}, {
key: 'Cord',
value: 11
}, {
key: 'Fuse',
value: 3
}, {
key: 'Bulb',
value: 2
}]
}, {
key: 'Hydraulic',
values: [{
key: 'Pump',
value: 4
}, {
key: 'Leak',
value: 3
}, {
key: 'Seals',
value: 1
}]
}];
var rangeBands = [];
var cummulative = 0;
data.forEach(function(val, i) {
val.cummulative = cummulative;
cummulative += val.values.length;
val.values.forEach(function(values) {
values.parentKey = val.key;
rangeBands.push(i);
})
});
//console.log(data);
var x_category = d3.scale.linear()
.range([0, width]);
var x_defect = d3.scale.ordinal().domain(rangeBands).rangeRoundBands([0, width], .1);
var x_category_domain = x_defect.rangeBand() * rangeBands.length;
x_category.domain([0, x_category_domain]);
var y = d3.scale.linear()
.range([height, 0]);
y.domain([0, d3.max(data, function(cat) {
return d3.max(cat.values, function(def) {
return def.value;
});
})]);
var category_axis = d3.svg.axis()
.scale(x_category)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style('background-color', 'EFEFEF')
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
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")
.text("Value");
var category_g = svg.selectAll(".category")
.data(data)
.enter().append("g")
.attr("class", function(d) {
return 'category category-' + d.key;
})
.attr("transform", function(d) {
return "translate(" + x_category((d.cummulative * x_defect.rangeBand())) + ",0)";
})
.attr("fill", function(d) {
return color[d.key];
});
var category_label = category_g.selectAll(".category-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
//console.log(d)
return 'category-label category-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((d.values.length * x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 30;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var defect_g = category_g.selectAll(".defect")
.data(function(d) {
return d.values;
})
.enter().append("g")
.attr("class", function(d) {
return 'defect defect-' + d.key;
})
.attr("transform", function(d, i) {
return "translate(" + x_category((i * x_defect.rangeBand())) + ",0)";
});
var defect_label = defect_g.selectAll(".defect-label")
.data(function(d) {
return [d];
})
.enter().append("text")
.attr("class", function(d) {
//console.log(d)
return 'defect-label defect-label-' + d.key;
})
.attr("transform", function(d) {
var x_label = x_category((x_defect.rangeBand() + barPadding) / 2);
var y_label = height + 10;
return "translate(" + x_label + "," + y_label + ")";
})
.text(function(d) {
return d.key;
})
.attr('text-anchor', 'middle');
var rects = defect_g.selectAll('.rect')
.data(function(d) {
return [d];
})
.enter().append("rect")
.attr("class", "rect")
.attr("width", x_category(x_defect.rangeBand() - barPadding))
.attr("x", function(d) {
return x_category(barPadding);
})
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
category_g
.append("line")
.style("stroke", "black")
.style("stroke-width", "4px")
.style("stroke-linecap","round")
.attr("x1", function(d) {
var bbox = this.parentNode.getBBox();
return bbox.width+7;
})
.attr("y1", height)
.attr("x2", function(d) {
var bbox = this.parentNode.getBBox();
return bbox.width;
})
.attr("y2", height + 50);
/* Styles go here */
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>Hello Plunker!</h1>
</body>
</html>
I am trying convert this chart I made, disregard the styles, using Highcharts, to this D3 multi-line chart.
This is the code for the d3 viz.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis--x path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
</style>
<svg width="960" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 80, bottom: 30, left: 50},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var parseTime = d3.timeParse("%Y%m%d");
var x = d3.scaleTime().range([0, width]),
y = d3.scaleLinear().range([height, 0]),
z = d3.scaleOrdinal(d3.schemeCategory10);
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.temperature); });
d3.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
var cities = data.columns.slice(1).map(function(id) {
return {
id: id,
values: data.map(function(d) {
return {date: d.date, temperature: d[id]};
})
};
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(cities, function(c) { return d3.min(c.values, function(d) { return d.temperature; }); }),
d3.max(cities, function(c) { return d3.max(c.values, function(d) { return d.temperature; }); })
]);
z.domain(cities.map(function(c) { return c.id; }));
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("fill", "#000")
.text("Temperature, ºF");
var city = g.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");
city.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d) { return z(d.id); });
city.append("text")
.datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.temperature) + ")"; })
.attr("x", 3)
.attr("dy", "0.35em")
.style("font", "10px sans-serif")
.text(function(d) { return d.id; });
});
function type(d, _, columns) {
d.date = parseTime(d.date);
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}
</script>
I would like to keep the data in a tsv and just convert the x-axis from using dates to using the raw numbers seen in the Highchart series. So instead of city, date, and temperature it would be batter, game number, and slugging percentage. I would also like to make it so that the lines end, like in the Highchart example, once there is no corresponding value, and not just have the line dip down to 0. It would also be nice if I could keep the hover effect from the Highchart example.
Unfortunately, I have little idea in how to achieve this. I know that I have to use a different function than the parseTime function currently in the script but that's about as far as I've gotten.
Change your scale to:
var x = d3.scaleLinear().range([0, width])
And don't parse to a date:
function type(d, _, columns) {
d.date = +d.date;
for (var i = 1, n = columns.length, c; i < n; ++i) d[c = columns[i]] = +d[c];
return d;
}