Convert string timestamp to hour axis - d3.js

I am trying to create a scatter plot based on two timestamps in d3, but i'm not sure the proper way to use the d3-time-format methods to properly parse the values in the timestamp format and build a range based on a 24-hour period. So far I built a function to loop through my array and use the d3 methods to convert the string to a d3-readable format, but I can seem to figure out how to format the output so it converts from date format to time format. My questions are does d3 accept time format? And how can I go about converting the date object to a time object? At the moment I have a y-axis that presents ticks for years.
Provided is my full code:
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<style>
div.tooltip {
position: absolute;
text-align: center;
width: 100px;
height: 30px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<script>
var data = [
{
"x": "23:19:30",
"y": "08:07:00"
},
{
"x": "22:55:30",
"y": "06:08:00"
},
{
"x": "21:14:30",
"y": "06:13:30"
},
{
"x": "21:24:30",
"y": "06:04:30"
},
{
"x": "21:24:00",
"y": "06:04:00"
},
{
"x": "21:28:00",
"y": "06:09:00"
},
{
"x": "23:23:30",
"y": "08:20:30"
},
{
"x": "23:47:00",
"y": "08:01:30"
},
{
"x": "00:03:00",
"y": "06:49:30"
},
{
"x": "21:46:00",
"y": "06:21:00"
},
{
"x": "21:58:00",
"y": "06:02:30"
},
{
"x": "21:33:00",
"y": "05:56:00"
},
{
"x": "22:33:00",
"y": "06:15:30"
},
{
"x": "23:49:00",
"y": "07:10:30"
},
{
"x": "23:46:30",
"y": "08:35:30"
},
{
"x": "23:15:30",
"y": "05:59:30"
},
{
"x": "21:26:00",
"y": "06:05:00"
},
{
"x": "21:26:30",
"y": "05:54:00"
},
{
"x": "21:06:00",
"y": "05:53:00"
},
{
"x": "21:25:00",
"y": "05:47:30"
},
{
"x": "00:29:30",
"y": "08:59:30"
},
{
"x": "01:14:00",
"y": "08:09:30"
},
{
"x": "23:12:30",
"y": "06:06:30"
},
{
"x": "21:26:00",
"y": "05:52:30"
},
{
"x": "21:18:30",
"y": "05:47:00"
},
{
"x": "20:54:30",
"y": "07:07:30"
},
{
"x": "21:36:00",
"y": "05:53:30"
},
{
"x": "00:28:00",
"y": "08:00:00"
},
{
"x": "23:21:30",
"y": "07:58:30"
},
{
"x": "21:34:00",
"y": "05:51:00"
},
{
"x": "21:23:30",
"y": "05:58:00"
},
{
"x": "21:05:30",
"y": "05:53:00"
},
{
"x": "21:33:30",
"y": "05:39:30"
},
{
"x": "23:49:30",
"y": "06:50:00"
},
{
"x": "01:11:00",
"y": "08:37:30"
},
{
"x": "22:34:30",
"y": "05:15:00"
},
{
"x": "22:49:30",
"y": "05:55:00"
},
{
"x": "22:06:30",
"y": "06:03:00"
},
{
"x": "21:32:30",
"y": "06:01:00"
},
{
"x": "21:49:00",
"y": "05:39:30"
},
{
"x": "22:47:30",
"y": "08:27:30"
},
{
"x": "21:26:30",
"y": "05:51:00"
},
{
"x": "21:47:30",
"y": "05:51:00"
},
{
"x": "21:28:00",
"y": "05:47:30"
},
{
"x": "21:32:00",
"y": "05:47:30"
},
{
"x": "21:13:30",
"y": "05:46:00"
},
{
"x": "23:42:30",
"y": "06:45:00"
},
{
"x": "21:33:00",
"y": "05:48:00"
},
{
"x": "21:45:00",
"y": "05:51:00"
},
{
"x": "21:29:30",
"y": "06:06:00"
},
{
"x": "21:16:00",
"y": "05:43:00"
},
{
"x": "21:14:00",
"y": "05:46:30"
},
{
"x": "00:01:30",
"y": "07:25:30"
},
{
"x": "02:24:00",
"y": "10:35:30"
},
{
"x": "22:29:30",
"y": "07:04:00"
},
{
"x": "21:43:30",
"y": "05:51:00"
},
{
"x": "21:31:30",
"y": "05:45:00"
},
{
"x": "22:16:30",
"y": "05:50:30"
},
{
"x": "21:59:00",
"y": "05:47:00"
},
{
"x": "02:55:30",
"y": "11:15:30"
},
{
"x": "02:57:00",
"y": "07:23:00"
},
{
"x": "21:49:30",
"y": "06:48:30"
},
{
"x": "21:31:30",
"y": "05:26:30"
}
]
// D3 date parser
for (var i=0; i < data.length; i++){
var parser = d3.timeParse("%I:%M:%S")
data[i].x = parser(data[i].x);
data[i].y = parser(data[i].y);
}
console.log(data)
var margin = { top: 10, right: 30, bottom: 30, left: 60 }
var width = 800 - margin.left - margin.right;
var height = 800 - margin.top - margin.bottom;
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("#my_dataviz")
.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 + ")"); // translate(margin left, margin top)
var x = d3.scaleTime()
.domain([d3.min(data, function(d) { return d.date }), d3.max(data, function(d) { return d.date })])
.range([0, width]);
svg.append("g")
.attr("transform", "translate(" + 0 + "," + height + ")")
.call(d3.axisBottom(x))
// .tickFormat(d3.time.format);
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," + (height + margin.top + 20) + ")")
.style("text-anchor", "middle")
.text("Date");
var y = d3.scaleTime()
.domain([0, d3.max(data, function(d){ return +d.y })])
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Time Asleep (Minutes)");
// Add line path
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.date) })
.y(function(d) { return y(d.y) })
);
// Add the scatterplot (data points)
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3)
.attr("cx", function(d){ return x(d.date) })
.attr("cy", function(d){ return y(d.y) })
// Add tooltip on hover
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.x + "<br/>" + d.label)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 30) + "px")
})
// Remove tooltip after hover
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
</script>

To format the date, use d3.timeParse. This will parse any date string to a Date object. D3 can understand and work with time objects with timeScale.
I've renamed your original data to rawData. We also need to sort the data by the X axis:
var toDate = d3.timeParse("%H:%M:%S")
var data = rawData.map(d => ({
x: toDate(d.x),
y: toDate(d.y),
})).sort((a, b) => d3.descending(a.x, b.x)
To calculate the domain, use d3.extent which will compute the domain automatically.
var x = d3.scaleTime()
.domain(d3.extent(data, d => d.x))
.range([0, width]);
<head></head>
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<style>
div.tooltip {
position: absolute;
text-align: center;
width: 100px;
height: 30px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
</style>
<script>
var rawData = [
{
"x": "23:19:30",
"y": "08:07:00"
},
{
"x": "22:55:30",
"y": "06:08:00"
},
{
"x": "21:14:30",
"y": "06:13:30"
},
{
"x": "21:24:30",
"y": "06:04:30"
},
{
"x": "21:24:00",
"y": "06:04:00"
},
{
"x": "21:28:00",
"y": "06:09:00"
},
{
"x": "23:23:30",
"y": "08:20:30"
},
{
"x": "23:47:00",
"y": "08:01:30"
},
{
"x": "00:03:00",
"y": "06:49:30"
},
{
"x": "21:46:00",
"y": "06:21:00"
},
{
"x": "21:58:00",
"y": "06:02:30"
},
{
"x": "21:33:00",
"y": "05:56:00"
},
{
"x": "22:33:00",
"y": "06:15:30"
},
{
"x": "23:49:00",
"y": "07:10:30"
},
{
"x": "23:46:30",
"y": "08:35:30"
},
{
"x": "23:15:30",
"y": "05:59:30"
},
{
"x": "21:26:00",
"y": "06:05:00"
},
{
"x": "21:26:30",
"y": "05:54:00"
},
{
"x": "21:06:00",
"y": "05:53:00"
},
{
"x": "21:25:00",
"y": "05:47:30"
},
{
"x": "00:29:30",
"y": "08:59:30"
},
{
"x": "01:14:00",
"y": "08:09:30"
},
{
"x": "23:12:30",
"y": "06:06:30"
},
{
"x": "21:26:00",
"y": "05:52:30"
},
{
"x": "21:18:30",
"y": "05:47:00"
},
{
"x": "20:54:30",
"y": "07:07:30"
},
{
"x": "21:36:00",
"y": "05:53:30"
},
{
"x": "00:28:00",
"y": "08:00:00"
},
{
"x": "23:21:30",
"y": "07:58:30"
},
{
"x": "21:34:00",
"y": "05:51:00"
},
{
"x": "21:23:30",
"y": "05:58:00"
},
{
"x": "21:05:30",
"y": "05:53:00"
},
{
"x": "21:33:30",
"y": "05:39:30"
},
{
"x": "23:49:30",
"y": "06:50:00"
},
{
"x": "01:11:00",
"y": "08:37:30"
},
{
"x": "22:34:30",
"y": "05:15:00"
},
{
"x": "22:49:30",
"y": "05:55:00"
},
{
"x": "22:06:30",
"y": "06:03:00"
},
{
"x": "21:32:30",
"y": "06:01:00"
},
{
"x": "21:49:00",
"y": "05:39:30"
},
{
"x": "22:47:30",
"y": "08:27:30"
},
{
"x": "21:26:30",
"y": "05:51:00"
},
{
"x": "21:47:30",
"y": "05:51:00"
},
{
"x": "21:28:00",
"y": "05:47:30"
},
{
"x": "21:32:00",
"y": "05:47:30"
},
{
"x": "21:13:30",
"y": "05:46:00"
},
{
"x": "23:42:30",
"y": "06:45:00"
},
{
"x": "21:33:00",
"y": "05:48:00"
},
{
"x": "21:45:00",
"y": "05:51:00"
},
{
"x": "21:29:30",
"y": "06:06:00"
},
{
"x": "21:16:00",
"y": "05:43:00"
},
{
"x": "21:14:00",
"y": "05:46:30"
},
{
"x": "00:01:30",
"y": "07:25:30"
},
{
"x": "02:24:00",
"y": "10:35:30"
},
{
"x": "22:29:30",
"y": "07:04:00"
},
{
"x": "21:43:30",
"y": "05:51:00"
},
{
"x": "21:31:30",
"y": "05:45:00"
},
{
"x": "22:16:30",
"y": "05:50:30"
},
{
"x": "21:59:00",
"y": "05:47:00"
},
{
"x": "02:55:30",
"y": "11:15:30"
},
{
"x": "02:57:00",
"y": "07:23:00"
},
{
"x": "21:49:30",
"y": "06:48:30"
},
{
"x": "21:31:30",
"y": "05:26:30"
}
]
// D3 date parser
var toDate = d3.timeParse("%H:%M:%S")
var data = rawData.map(d => ({
x: toDate(d.x),
y: toDate(d.y),
})).sort((a, b) => d3.descending(a.x, b.x))
console.log(data[0])
var margin = { top: 10, right: 30, bottom: 30, left: 60 }
var width = 800 - margin.left - margin.right;
var height = 800 - margin.top - margin.bottom;
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var svg = d3.select("#my_dataviz")
.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 + ")"); // translate(margin left, margin top)
var x = d3.scaleTime()
.domain(d3.extent(data, d => d.x))
.range([0, width]);
svg.append("g")
.attr("transform", "translate(" + 0 + "," + height + ")")
.call(d3.axisBottom(x))
// .tickFormat(d3.time.format);
// text label for the x axis
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," + (height + margin.top + 20) + ")")
.style("text-anchor", "middle")
.text("Date");
var y = d3.scaleTime()
.domain(d3.extent(data, d => d.y))
.range([height, 0]);
svg.append("g")
.call(d3.axisLeft(y));
// text label for the y axis
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Time Asleep (Minutes)");
// Add line path
svg.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("d", d3.line()
.x(function(d) { return x(d.x) })
.y(function(d) { return y(d.y) })
);
// Add the scatterplot (data points)
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3)
.attr("cx", function(d){ return x(d.x) })
.attr("cy", function(d){ return y(d.y) })
// Add tooltip on hover
.on("mouseover", function(d) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html(d.x + "<br/>" + d.label)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 30) + "px")
})
// Remove tooltip after hover
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
</script>

Related

Last bin in histogram has smaller width than others

I have a D3.js histogram graph that has an issue where my last x-axis bin is significantly smaller than than the rest of the bins which are equal width. At first I thought this was a axis issue, but the axis is set with the typical width and height range and domains, making me believe it has to do with my bar code, but I'm not sure what would create just one wrong width for a bing, while the other bins are equal width. Provided below is my code. Any help would be appreciated!
Code:
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<style>
#sleep-dist-histo {
stroke-width: 0.5;
stroke: goldenrod;
}
</style>
<script>
var data = [
{ "x": "7.41" },
{ "x": "7.01" },
{ "x": "8.39" },
{ "x": "8.22" },
{ "x": "8.31" },
{ "x": "8.33" },
{ "x": "8.16" },
{ "x": "8.00" },
{ "x": "6.04" },
{ "x": "8.18" },
{ "x": "7.47" },
{ "x": "7.57" },
{ "x": "7.11" },
{ "x": "6.59" },
{ "x": "7.51" },
{ "x": "6.31" },
{ "x": "8.01" },
{ "x": "8.14" },
{ "x": "8.26" },
{ "x": "7.44" },
{ "x": "7.54" },
{ "x": "6.23" },
{ "x": "6.25" },
{ "x": "7.50" },
{ "x": "7.45" },
{ "x": "9.34" },
{ "x": "7.53" },
{ "x": "7.11" },
{ "x": "8.17" },
{ "x": "8.02" },
{ "x": "8.12" },
{ "x": "8.14" },
{ "x": "7.49" },
{ "x": "6.35" },
{ "x": "7.07" },
{ "x": "5.46" },
{ "x": "6.56" },
{ "x": "7.41" },
{ "x": "8.06" },
{ "x": "7.31" },
{ "x": "8.53" },
{ "x": "7.42" },
{ "x": "7.41" },
{ "x": "7.57" },
{ "x": "7.38" },
{ "x": "8.04" },
{ "x": "6.29" },
{ "x": "7.52" },
{ "x": "7.42" },
{ "x": "8.06" },
{ "x": "8.09" },
{ "x": "8.03" },
{ "x": "7.06" },
{ "x": "7.33" },
{ "x": "8.09" },
{ "x": "7.47" },
{ "x": "7.54" },
{ "x": "7.31" },
{ "x": "7.30" },
{ "x": "7.50" },
{ "x": "4.07" },
{ "x": "8.22" },
{ "x": "7.44" }
]
// console.log(data)
var margin = { top: 15, right: 30, bottom: 50, left: 60 }
var width = 800 - margin.left - margin.right;
var height = 800 - margin.top - margin.bottom;
var svg = d3.select("#my_dataviz")
.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 + ")"); // translate(margin left, margin top)
// Append x-scale and x-scale tick labels
var xAxisGroup = svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + 0 + "," + height + ")");
// Set x-scale range
var x = d3.scaleLinear()
.range([0, width]);
// Append x-scale title
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," + (height + margin.bottom) + ")")
.style("text-anchor", "middle")
.text("Time Asleep in Hours (Bins)");
// Set y-scale range
var y = d3.scaleLinear()
.range([height, margin.top]);
// Append y-scale
var yAxisGroup = svg.append("g")
.attr("class", "y-axis");
// Append y-scale title
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Count of Days Asleep");
d3.interval(function(){
update(data)
}, 1000);
update(data);
function update(data){
// X and Y axis domains
x.domain([0, d3.max(data, function(d) { return +d.x })])
var xAxisCall = d3.axisBottom(x);
var yAxisCall = d3.axisLeft(y);
xAxisGroup.call(xAxisCall);
yAxisGroup.call(yAxisCall);
var histogram = d3.histogram()
.value(function(d) { return d.x })
.domain(x.domain())
.thresholds(x.ticks());
var bins = histogram(data);
y.domain([0, d3.max(bins, function(d){ return d.length; })]);
// Append bars
svg.selectAll("bar")
.data(bins)
.enter()
.append("rect")
.attr("x", 1)
.attr("transform", function(d) { return "translate(" + x(d.x0) + "," + y(d.length) + ")"; })
.attr("width", function(d) { return x(d.x1) - x(d.x0) - 1 ; })
.attr("height", function(d) { return height - y(d.length); })
.style("fill", "steelblue")
.attr("id", "sleep-dist-histo");
svg.selectAll("label")
.data(bins)
.enter()
.append("text")
.text(function(d){ return d.length })
.attr("x", function(d){ return (x(d.x0) + x(d.x1)) / 2; })
.attr("y", function(d){ return y(d.length) - 15; })
.attr("class", "label")
.attr("dy", ".71em")
// .style("text-anchor", "end")
;
}
</script>
It is because the domain of x is [0, 9.34], so it ended up with 9.34 instead of 10 (to keep the same width)
Solution:
add .nice() to x to "Extends the domain so that it starts and ends on nice round values. " (See Here);
remove the last element of the new bin data otherwise there will be a zero from 10 to 11
<meta charset="utf-8">
<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>
<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>
<style>
#sleep-dist-histo {
stroke-width: 0.5;
stroke: goldenrod;
}
</style>
<script>
var data = [
{ "x": "7.41" },
{ "x": "7.01" },
{ "x": "8.39" },
{ "x": "8.22" },
{ "x": "8.31" },
{ "x": "8.33" },
{ "x": "8.16" },
{ "x": "8.00" },
{ "x": "6.04" },
{ "x": "8.18" },
{ "x": "7.47" },
{ "x": "7.57" },
{ "x": "7.11" },
{ "x": "6.59" },
{ "x": "7.51" },
{ "x": "6.31" },
{ "x": "8.01" },
{ "x": "8.14" },
{ "x": "8.26" },
{ "x": "7.44" },
{ "x": "7.54" },
{ "x": "6.23" },
{ "x": "6.25" },
{ "x": "7.50" },
{ "x": "7.45" },
{ "x": "9.34" },
{ "x": "7.53" },
{ "x": "7.11" },
{ "x": "8.17" },
{ "x": "8.02" },
{ "x": "8.12" },
{ "x": "8.14" },
{ "x": "7.49" },
{ "x": "6.35" },
{ "x": "7.07" },
{ "x": "5.46" },
{ "x": "6.56" },
{ "x": "7.41" },
{ "x": "8.06" },
{ "x": "7.31" },
{ "x": "8.53" },
{ "x": "7.42" },
{ "x": "7.41" },
{ "x": "7.57" },
{ "x": "7.38" },
{ "x": "8.04" },
{ "x": "6.29" },
{ "x": "7.52" },
{ "x": "7.42" },
{ "x": "8.06" },
{ "x": "8.09" },
{ "x": "8.03" },
{ "x": "7.06" },
{ "x": "7.33" },
{ "x": "8.09" },
{ "x": "7.47" },
{ "x": "7.54" },
{ "x": "7.31" },
{ "x": "7.30" },
{ "x": "7.50" },
{ "x": "4.07" },
{ "x": "8.22" },
{ "x": "7.44" }
]
// console.log(data)
var margin = { top: 15, right: 30, bottom: 50, left: 60 }
var width = 800 - margin.left - margin.right;
var height = 800 - margin.top - margin.bottom;
var svg = d3.select("#my_dataviz")
.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 + ")"); // translate(margin left, margin top)
// Append x-scale and x-scale tick labels
var xAxisGroup = svg.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(" + 0 + "," + height + ")");
// Set x-scale range
var x = d3.scaleLinear()
.range([0, width]);
// Append x-scale title
svg.append("text")
.attr("transform",
"translate(" + (width/2) + " ," + (height + margin.bottom) + ")")
.style("text-anchor", "middle")
.text("Time Asleep in Hours (Bins)");
// Set y-scale range
var y = d3.scaleLinear()
.range([height, margin.top]);
// Append y-scale
var yAxisGroup = svg.append("g")
.attr("class", "y-axis");
// Append y-scale title
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0 - margin.left)
.attr("x",0 - (height / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Count of Days Asleep");
/* d3.interval(function(){
update(data)
}, 1000);
*/
update(data);
function update(data){
// X and Y axis domains
x.domain([0, d3.max(data, function(d) { return (+d.x) })]).nice()
var xAxisCall = d3.axisBottom(x);
var yAxisCall = d3.axisLeft(y);
xAxisGroup.call(xAxisCall);
yAxisGroup.call(yAxisCall);
var histogram = d3.histogram()
.value(function(d) { return +d.x })
.domain(x.domain())
.thresholds(x.ticks());
var bins = histogram(data);
y.domain([0, d3.max(bins, function(d){ return d.length; })]);
bins = bins.slice(0, bins.length-1)
// Append bars
svg.selectAll("bar")
.data(bins)
.enter()
.append("rect")
.attr("x", 1)
.attr("transform", function(d) { return "translate(" + x(d.x0) + "," + y(d.length) + ")"; })
.attr("width", function(d) { return x(d.x1) - x(d.x0) - 1 ; })
.attr("height", function(d) { return height - y(d.length); })
.style("fill", "steelblue")
.attr("id", "sleep-dist-histo");
svg.selectAll("label")
.data(bins)
.enter()
.append("text")
.text(function(d){ return d.length })
.attr("x", function(d){ return (x(d.x0) + x(d.x1)) / 2; })
.attr("y", function(d){ return y(d.length) - 15; })
.attr("class", "label")
.attr("dy", ".71em")
// .style("text-anchor", "end")
;
}
</script>

How can I apply a clipPath in SVG with multiple paths and NOT clip out the area between the paths?

I have an SVG clipPath made of multiple path elements, which I need to apply to a group of elements. I only want to clip out the area under the path stroke itself, not in between the paths. (example that doesn't do what I want follows)
var lineData = [ { "x": 1, "y": 5}, { "x": 100, "y": 400},
{ "x": 300, "y": 100}, { "x": 600, "y": 600},
{ "x": 700, "y": 50} ];
var lineData2 = [ { "x": 1, "y": 500}, { "x": 100, "y": 100},
{ "x": 300, "y": 700}, { "x": 600, "y": 60},
{ "x": 700, "y": 700} ];
var lineFunction = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveBundle);
var svg = d3.select('body')
.append('svg')
.attr('id', 'svg')
.attr('width', 660)
.attr('height', 660)
.style('outline', '1px solid red')
.append('g')
.attr('clip-path', 'url(#clippy)');
var polygon = svg.append('polygon')
.attr('points', '230 10, 660 330, 230 650')
.attr('fill', '#c99');
var circle = svg.append('circle')
.attr('cx', 230)
.attr('cy', 330)
.attr('r', 200)
.attr('fill', '#9c6')
var clippy = d3.select('#svg')
.append('defs')
.append('clipPath')
.attr('id', 'clippy');
clippy.append("path")
.attr("d", lineFunction(lineData))
.attr("stroke", "blue")
.attr("stroke-width", 18)
.attr("fill", "none");
clippy.append("path")
.attr("d", lineFunction(lineData2))
.attr("stroke", "blue")
.attr("stroke-width", 18)
.attr("fill", "none");
Basically I want to accomplish something similar to what you get using letters, but instead using lines/paths.
var lineData = [ { "x": 1, "y": 5}, { "x": 100, "y": 400},
{ "x": 300, "y": 100}, { "x": 600, "y": 600},
{ "x": 700, "y": 50} ];
var lineData2 = [ { "x": 1, "y": 500}, { "x": 100, "y": 100},
{ "x": 300, "y": 700}, { "x": 600, "y": 60},
{ "x": 700, "y": 700} ];
var lineFunction = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveBundle);
var svg = d3.select('body')
.append('svg')
.attr('id', 'svg')
.attr('width', 660)
.attr('height', 660)
.style('outline', '1px solid red')
.append('g')
.attr('clip-path', 'url(#clippy)');
var polygon = svg.append('polygon')
.attr('points', '230 10, 660 330, 230 650')
.attr('fill', '#c99');
var circle = svg.append('circle')
.attr('cx', 230)
.attr('cy', 330)
.attr('r', 200)
.attr('fill', '#9c6')
var clippy = d3.select('#svg')
.append('defs')
.append('clipPath')
.attr('id', 'clippy');
clippy.append('text')
.attr('x', 120)
.attr('y', 320)
.attr('font-size', '4em')
.attr('font-weight', 'bold')
.attr('font-family', 'Georgia')
.text('This is a clip');
clippy.append('text')
.attr('x', 120)
.attr('y', 420)
.attr('font-size', '4em')
.attr('font-weight', 'bold')
.attr('font-family', 'Georgia')
.text('Also a clip')
Please help!
EDIT: Here's a Codepen with both examples.
From my reading of the SVG spec, it's not possible to use only a <path>'s stroke as the clipping path:
The raw geometry of each child element exclusive of rendering properties such as ‘fill’, ‘stroke’, ‘stroke-width’ within a ‘clipPath’ conceptually defines a 1-bit mask (with the possible exception of anti-aliasing along the edge of the geometry) which represents the silhouette of the graphics associated with that element. Anything outside the outline of the object is masked out.
However, converting the <clipPath> to a <mask> element produces what I think is your desired effect.
Here's a forked CodePen to demonstrate — the paths have have their strokes set to white, and the <text> elements were also given a fill of white to match the clip-path effect.

D3 drawing step-before paths between other elements

I have data in a multidimensional array in the form of:
data = [
[
{
"x": 329, "y": 484.8333333333333
},
{
"x": 439, "y": 484.8333333333333
},
{
"x": 439, "y": 484.8333333333333
},
{
"x": 549, "y": 484.8333333333333
}
], [
{
"x": 559, "y": 484.8333333333333
},
{
"x": 669, "y": 484.8333333333333
},
{
"x": 669, "y": 484.8333333333333
},
{
"x": 779, "y": 484.8333333333333
}
], [
{
"x": 329, "y": 313.8333333333333
},
{
"x": 439, "y": 313.8333333333333
},
{
"x": 439, "y": 253.83333333333331
},
{
"x": 549, "y": 253.83333333333331
}
], [
{
"x": 559, "y": 313.8333333333333
},
{
"x": 669, "y": 313.8333333333333
},
{
"x": 669, "y": 253.83333333333331
},
{
"x": 779, "y": 253.83333333333331
}
], etc.
]
Each array is the coordinates for one step-before path connecting two svg elements. I've defined a function for generating a path:
stepFuction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("step-before");
I am attempting to instantiate the paths like so:
step = svg.selectAll("path")
.data(_.each(stepData, (d) => { return d; }))
.enter()
.append("path")
.attr("class", "line")
.attr("d", stepFuction);
Rather than a step-before path, I'm getting a bowtie looking shape (see attachment). Obviously, I am doing something wrong, I'm assuming it has something to do with using _.each inside the data method.
1) What is the correct approach to creating one path per array in my data array?
I would like to be able to drag the elements around and have these paths update. I am using d3.on("tick") for nodes, labels and groups with something like:
node.attr("x", function (d) { return d.x - d.width / 2 + pad; })
.attr("y", function (d) { return d.y - d.height / 2 + pad; });
and it is working correctly but I'm not sure how to update paths as there are multiple values that need to be recalculated on every tick.
2) What is the correct approach to updating these step-before paths on each tick?
I created a fiddle if anything wasn't clear in my description:
d3 step-before fiddle
I think this is actually to do with stylings.
Fiddle
Added this css code:
path {
stroke-width: 1px;
fill: none;
stroke: black;
}

How to draw circles at different times with D3js?

Using d3js, I need to draw(append) circles, not all together but with less then one second of distance. So one circle in x position, another one in y position after 0.5 second.
Use setTimeout. Here is the working code snippet.
var nodes = [{
"name": "6",
"x": 207,
"y": 305
}, {
"name": "7",
"x": 404,
"y": 310
}, {
"name": "8",
"x": 420,
"y": 510
}, {
"name": "9",
"x": 540,
"y": 126
}, {
"name": "10",
"x": 350,
"y": 150
}, {
"name": "11",
"x": 177,
"y": 320
}, {
"name": "12",
"x": 200,
"y": 190
}, {
"name": "13",
"x": 170,
"y": 150
}, {
"name": "14",
"x": 107,
"y": 510
}, {
"name": "15",
"x": 104,
"y": 150
}, {
"name": "16",
"x": 104,
"y": 150
}, {
"name": "17",
"x": 310,
"y": 160
}, {
"name": "18",
"x": 120,
"y": 110
}, {
"name": "19",
"x": 619,
"y": 145
}, {
"name": "20",
"x": 148,
"y": 107
}, {
"name": "21",
"x": 575,
"y": 107
}];
var width = 500,
height = 400;
var color = d3.scale.category20();
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
nodes.forEach(function(d, i) {
setTimeout(function() {
svg.append("circle")
.datum(d)
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", "10")
.style("fill", function(d) {
return color(i);
});
}, 500 * i);
});
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.overlay {
fill: none;
pointer-events: all;
}
#map{
border: 2px #555 dashed;
width:500px;
height:400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<body>
<div id="map"></div>
</body>
You can use the standard javascript methods setTimeout or setInterval to append the circles one by one with a variable delay depending on the circle index.
Or, you could create all the circles on enter normally using the standard d3 syntax but with opacity set to 0 and just add a .transition() with delay dependent on the index that sets opacity to 1
Here's a working jsfiddle of the latter option: http://jsfiddle.net/pg5m3m3n/5/
Extract:
canvas.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr({
'cx': function(d) { return d.x; },
'cy': function(d) { return d.y; },
'r': 10,
'opacity': 0
})
.transition().delay(function(d,i) { return i*50; })
.attr('opacity',1);
The pros of this is that it uses d3 syntax and it's just 2 lines of code more than the normal append, the con is that the circles are actually added immediately and only become visible one by one, which may give performance issues if the number of circles to append is huge.

d3.js path doesn't respect domain and range

There must be something obvious I'm missing here.
I'm trying to draw a simple line and this is my javascript:
// CRASH DATA
var lineData = [
{ "x": 0, "y": 0.5},
{ "x": 2, "y": 0.1},
{ "x": 4, "y": -0.5},
{ "x": 6, "y": -0.8},
{ "x": 8, "y": -0.9},
{ "x": 10, "y": -0.10},
{ "x": 12, "y": -0.10},
{ "x": 14, "y": -0.11},
{ "x": 16, "y": -0.10},
{ "x": 18, "y": -0.9},
{ "x": 20, "y": -0.7},
{ "x": 22, "y": -0.6},
{ "x": 24, "y": -0.5},
{ "x": 26, "y": -0.3},
{ "x": 28, "y": -0.1},
{ "x": 30, "y": 0.2},
{ "x": 32, "y": 0.4},
{ "x": 34, "y": 0.8},
{ "x": 36, "y": 0.8},
{ "x": 38, "y": 0.7},
{ "x": 40, "y": 0.4},
{ "x": 42, "y": 0.4},
{ "x": 44, "y": 0.4},
{ "x": 46, "y": 0.2},
{ "x": 48, "y": 0.1},
{ "x": 50, "y": 0}
];
//DRAW TRAJECTORY
function draw(data){
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
var x = d3.scale.linear().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
//This is the accessor function we talked about above
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
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 + ")");
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.x; }));
y.domain(d3.extent(data, function(d) { return d.y; }));
svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g") // Add the Y Axis
.attr("class", "y axis")
.call(yAxis);
svg.append("path") // Add the lineFunction path.
.attr("class", "line")
.attr("d", lineFunction(data));
};
//PUT EVERYTHING ON SCREEN
$( document ).ready(function() {
draw(lineData);
});
And here is the outcome:
You aren't actually using the scales you define. Your line function should be
var lineFunction = d3.svg.line()
.x(function(d) { return x(d.x); })
.y(function(d) { return y(d.y); })
.interpolate("linear");
Complete example here.

Resources