Related
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>
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>
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.
hey guys i am currently trying to change the visibility of 4 lines depending on whether the slider position is at the same "year-tick" as i have in my data.
for example: i would want line 3 which has the value "1994" stored to only be visible as long as the slider is on position "1994"
i havent workder with sliders yet, which is why i am having this much trouble getting this to work.
here is a fiddle of my code: https://fiddle.jshell.net/42jdw2Lt/3/
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js">
</script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<script>
var width = 1500;
var height = 1500;
var margin = {top: 50, left: 50, right: 50, bottom:50},
height = 650-margin.top-margin.bottom,
width = 1200-margin.left-margin.right;
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 strwi = d3.scaleLinear()
.domain([100, 400])
.range([7,35])
var group = svg.append("g")
var series = [
[{"x": 360, "y": 250, "num": 100}, {"x": 520, "y": 400, "num": 100}, {"x":
630, "y": 300, "num": 100, "year": 1991}],
[{"x": 71, "y": 45, "num": 200}, {"x": 32, "y": 39, "num": 200}, {"x": 43,
"y": 70, "num": 200, "year": 1992}],
[{"x": 100, "y": 300, "num": 300}, {"x": 200, "y": 200, "num": 300}, {"x":
300, "y": 200, "num": 300, "year": 1994}],
[{"x": 101, "y": 202, "num": 400}, {"x": 102, "y": 204, "num": 400}, {"x":
103, "y": 215, "num": 400, "year": 1995}]
];
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
group.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
// .attr("visibility", "hidden")
.attr("stroke-width", function(d) {return strwi(d); })
.attr("stroke", "black")
.attr("fill", "none")
.attr("d", line);
var data = [1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
2000, 2001, 2002]
var x = d3.scaleLinear()
.domain(d3.extent(data))
.range([0, width])
.clamp(true);
var slider = svg.append("g")
.attr("class", "slider")
.attr("transform", "translate(" + 0 + "," + 300 + ")");
slider.append("line")
.attr("class", "track")
.attr("x1", x.range()[0])
.attr("x2", x.range()[1])
.attr("stroke", "black")
.attr("stroke-width", "4")
.select(function() { return
this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "track-inset")
.select(function() { return
this.parentNode.appendChild(this.cloneNode(true)); })
.attr("class", "track-overlay")
.call(d3.drag()
.on("start.interrupt", function() { slider.interrupt(); })
.on("start drag", function() { hue(x.invert(d3.event.x));
}));
slider.insert("g", ".track-overlay")
.attr("class", "ticks")
.selectAll("ticks")
.data(x.ticks(data.length))
.enter().append("text")
.attr("x", x)
.attr("text-anchor", "middle")
.attr("transform", "translate(0," + 30 + ")")
.text(function(d) { return d; })
.exit()
.data(x.ticks(data.length * 2))
.enter().append("circle")
.attr("cx", x)
.attr("r", 3)
.attr("fill", "#c1c7cd");
slider.insert("g", ".track-overlay")
.attr("class", "ticks--cirlces")
.selectAll("ticks--ticks");
var handle = slider.insert("circle", ".track-overlay")
.attr("class", "handle")
.attr("r", 9);
slider.transition() // Gratuitous intro!
.duration(750)
.tween("hue", function() {
var i = d3.interpolate(0, 70);
return function(t) { hue(i(t)); };
});
function hue(h) {
handle.attr("cx", x(h));
d3.select(".text")
.text( (Math.round(h*2)/2).toFixed(1) );
}
</script>
I added the prop year to all your elements in the data selecting all the line paths in a variable called paths so that I can change the opacity when you move your slider like this
var paths = group.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
// .attr("visibility", "hidden")
.attr("stroke-width", function(d) {
return strwi(d);
})
.attr("stroke", "black")
.attr("fill", "none")
.attr("d", line);
and then in your hue function, I added this code to change the opacity based on your slider
paths.attr('opacity', function(d) {
if (((Math.round(h * 2) / 2).toFixed(1)) > d[2]['year']) {
return 0;
} else {
return 1;
}
})
Here's a fiddle for you:
var width = 1500;
var height = 1500;
var margin = {
top: 50,
left: 50,
right: 50,
bottom: 50
},
height = 650 - margin.top - margin.bottom,
width = 1200 - margin.left - margin.right;
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 strwi = d3.scaleLinear()
.domain([100, 400])
.range([7, 35])
var group = svg.append("g")
var series = [
[{
"x": 360,
"y": 250,
"num": 100,
"startYear": 1991,
"endYear": 1995
}, {
"x": 520,
"y": 400,
"num": 100,
"startYear": 1991,
"endYear": 1995
}, {
"x": 630,
"y": 300,
"num": 100,
"startYear": 1991,
"endYear": 1995
}],
[{
"x": 71,
"y": 45,
"num": 200,
"startYear": 1992,
"endYear": 1993
}, {
"x": 32,
"y": 39,
"num": 200,
"startYear": 1992,
"endYear": 1993
}, {
"x": 43,
"y": 70,
"num": 200,
"startYear": 1992,
"endYear": 1993
}],
[{
"x": 100,
"y": 300,
"num": 300,
"startYear": 1994,
"endYear": 1996
}, {
"x": 200,
"y": 200,
"num": 300,
"startYear": 1994,
"endYear": 1996
}, {
"x": 300,
"y": 200,
"num": 300,
"startYear": 1994,
"endYear": 1996
}],
[{
"x": 101,
"y": 202,
"num": 400,
"startYear": 1995,
"endYear": 1997
}, {
"x": 102,
"y": 204,
"num": 400,
"startYear": 1995,
"endYear": 1997
}, {
"x": 103,
"y": 215,
"num": 400,
"startYear": 1995,
"endYear": 1997
}]
];
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) {
return d.x;
})
.y(function(d) {
return d.y;
});
var paths = group.selectAll(".line")
.data(series)
.enter().append("path")
.attr("class", "line")
.attr('v1',function(d){
return d[2]['startYear'];
})
.attr('v2',function(d) {
return d[2]['endYear'];
})
.attr('opacity',0)
//.attr("visibility", "hidden")
.attr("stroke-width", function(d) {
return strwi(d);
})
.attr("stroke", "black")
.attr("fill", "none")
.attr("d", line);
var data = [1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002]
var x = d3.scaleLinear()
.domain(d3.extent(data))
.range([0, width])
.clamp(true);
var slider = svg.append("g")
.attr("class", "slider")
.attr("transform", "translate(" + 0 + "," + 300 + ")");
slider.insert("g", ".track-overlay")
.attr("class", "ticks")
.selectAll("ticks")
.data(x.ticks(data.length))
.enter().append("text")
.attr("x", x)
.attr("text-anchor", "middle")
.attr("transform", "translate(0," + 30 + ")")
.text(function(d) {
return d;
})
.exit()
.data(x.ticks(data.length * 2))
.enter().append("circle")
.attr("cx", x)
.attr("r", 3)
.attr("fill", "#c1c7cd");
slider.append("line")
.attr("class", "track")
.attr("x1", x.range()[0])
.attr("x2", x.range()[1])
.attr("stroke", "black")
.attr("stroke-width", "4")
.select(function() {
return this.parentNode.appendChild(this.cloneNode(true));
})
.attr("class", "track-inset")
.select(function() {
return this.parentNode.appendChild(this.cloneNode(true));
})
.attr("class", "track-overlay")
.call(d3.drag()
.on("start.interrupt", function() {
slider.interrupt();
})
.on("start drag", function() {
//console.log(d3.event.x)
hue(x.invert(d3.event.x));
}));
slider.insert("g", ".track-overlay")
.attr("class", "ticks--cirlces")
.selectAll("ticks--ticks");
var handle = slider.insert("circle", ".track-overlay")
.attr("class", "handle")
.attr("r", 9);
slider.transition() // Gratuitous intro!
.duration(750)
.tween("hue", function() {
var i = d3.interpolate(0, 70);
return function(t) {
//nsole.log(t)
hue(i(t));
};
});
function hue(h) {
handle.attr("cx", x(h));
d3.select(".text")
.text((Math.round(h * 2) / 2).toFixed(1));
paths
.transition().duration(300)
.attr('opacity', function(d) {
if (((Math.round(h * 2) / 2).toFixed(1)) >= d[2]['startYear'] && ((Math.round(h * 2) / 2).toFixed(1)) <= d[2]['endYear']) {
return 1;
} else {
return 0;
}
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.2/d3.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
Let me know if this is what you wanted or there's something else.
I would like to get the coordinates of a point on a line by clicking on the line using the following code:
var lineData = [ { "x": 1, "y": 5}, { "x": 20, "y": 20},
{ "x": 40, "y": 10}, { "x": 60, "y": 40},
{ "x": 80, "y": 5}, { "x": 100, "y": 60}];
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
var svgContainer = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
var lineGraph = svgContainer.append("path")
.data([lineData]).attr("d", lineFunction)
//.attr("d", lineFunction(lineData))
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.on('mousedown', function(d) {
console.log({"x":d.x, "y":d.y})
});
(I updated the code to address the comments, but I still get "Object {x: undefined, y: undefined}")
I keep getting an "undefined" when clicking on the line. Am I missing a step?
You can get the coordinates of an event using d3.event:
.on("mousedown", function() {
console.log({"x": d3.event.x, "y": d3.event.y});
});
use mouse event
.on('mousedown', function(d) {
var m = d3.mouse(this);
console.log("x:"+m[0]+" y:"+m[1]);
});
in your function m[0] and m[1] gives you X and Y.