d3.js brush with multiline chart - d3.js

I'm making a multi-line chart and using a brush to select time periods. It's broadly based on Mike Bostock's example at http://bl.ocks.org/mbostock/1667367
My chart is at http://lowercasen.com/dev/d3/general/piezobrush.html
My problem is in selecting the multiple lines in my 'focus' area to apply the brush to. I've nested the data based on a key, so the data is within a function. Because the function that calls my brush is outside that function, it can't access the data and I'm getting a TypeError: undefined is not an object (evaluating 'data.length')
Here's the code that nests the data:
dataNest.forEach(function(d, i) {
focus.append("path")
.attr("class", "line")
.attr("id", d.key.replace(/\s+/g, '')) //the replace stuff is getting rid of spaces
.attr("d", levelFocus(d.values));
context.append("path")
.attr("class", "line")
.attr("id", d.key.replace(/\s+/g, '')) //the replace stuff is getting rid of spaces
.attr("d", levelContext(d.values));
and at the bottom I have the function for the brush:
function brushed() {
xFocus.domain(brush.empty() ? xContext.domain() : brush.extent());
focus.selectAll(".line").attr("d", levelFocus(d.values));
focus.select(".x.axis").call(xAxisFocus);
}
It works fine for the x axis (if I comment out the line where I'm trying to select the lines) but I don't know how to select the lines correctly.
Apologies for any garbled syntax or confusing language, my coding skills are basic at best.
Any help is greatly appreciated, I've searched for hours for a solution.
Here's the full code as requested by Lars
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Multiline with brush</title>
<script src="http://d3js.org/d3.v3.js"></script>
<script src="d3/tooltip.js"></script>
<link href="styles/evidentlySoCharts.css" rel="stylesheet">
<meta name="viewport" content="initial-scale=1">
<style>
svg {
font: 10px sans-serif;
}
path {
stroke-width: 1;
fill: none;
}
#Stream1, #Nebo1D {
stroke: #009390;
}
#Stream1Legend, #Nebo1DLegend {
fill: #009390;
}
#Stream2, #Nebo2D {
stroke: #8dc63f;
}
#Stream2Legend, #Nebo2DLegend {
fill: #8dc63f;
}
#Stream3, #Nebo1S {
stroke: #132d46;
}
#Stream3Legend, #Nebo1SLegend {
fill: #132d46;
}
#Stream4, #Nebo2S {
stroke: #aaa813;
}
#Stream4Legend, #Nebo2SLegend {
fill: #aaa813;
}
#Stream5, #Nebo3 {
stroke: #619dd4;
}
#Stream5Legend, #Nebo3Legend {
fill: #619dd4;
}
.pn1d, .pn2d {
fill: none;
clip-path: url(#clip);
}
.pn1d {
stroke: #009390;
}
.pn2d {
stroke: #1b4164;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script>
var marginFocus = {top: 10, right: 10, bottom: 250, left: 40},
marginContext = {top: 430, right: 10, bottom: 170, left: 40},
width = 960 - marginFocus.left - marginFocus.right,
heightFocus = 650 - marginFocus.top - marginFocus.bottom,
heightContext = 650 - marginContext.top - marginContext.bottom;
legendOffset = 550;
var parseDate = d3.time.format("%d/%m/%y %H:%M").parse;
var xFocus = d3.time.scale().range([0, width]),
xContext = d3.time.scale().range([0, width]),
yFocus = d3.scale.linear().range([heightFocus, 0]),
yContext = d3.scale.linear().range([heightContext, 0]);
var xAxisFocus = d3.svg.axis().scale(xFocus).orient("bottom"),
xAxisContext = d3.svg.axis().scale(xContext).orient("bottom"),
yAxisFocus = d3.svg.axis().scale(yFocus).orient("left");
var levelFocus = d3.svg.line()
.interpolate("linear")
.x(function(d) { return xFocus(d.date); })
.y(function(d) { return yFocus(d.level); });
var levelContext = d3.svg.line()
.interpolate("linear")
.x(function(d) { return xContext(d.date); })
.y(function(d) { return yContext(d.level); });
var svg = d3.select("body").append("svg")
.attr("width", width + marginFocus.left + marginFocus.right)
.attr("height", heightFocus + marginFocus.top + marginFocus.bottom);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", heightFocus);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + marginFocus.left + "," + marginFocus.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + marginContext.left + "," + marginContext.top + ")");
d3.csv("data/PiezoNeboNestSimple.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.level = +d.level;
});
xFocus.domain(d3.extent(data.map(function(d) { return d.date; })));
yFocus.domain([d3.min(data.map(function(d) { return d.level; })) -2,0]);
xContext.domain(xFocus.domain());
yContext.domain(yFocus.domain());
// Nest the entries by piezo
var dataNest = d3.nest()
.key(function(d) {return d.piezo;})
.entries(data);
legendSpace = width/dataNest.length; // spacing for legend // ******
var brush = d3.svg.brush()
.x(xContext)
.on("brush", brushed);
focus.selectAll("g").data(dataNest)
.enter()
.append("g")
.attr("class", "line")
.attr("id", function(d) { return d.key.replace(/\s+/g, '') }) //the replace stuff is getting rid of spaces
.append("path")
.attr("d", function(d) { return levelFocus(d.values); });
context.selectAll("g").data(dataNest)
.enter()
.append("g")
.attr("class", "line")
.attr("id", function(d) { return d.key.replace(/\s+/g, '') }) //the replace stuff is getting rid of spaces
.append("path")
.attr("d", function(d) { return levelContext(d.values); });
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + heightFocus + ")")
.call(xAxisFocus);
focus.append("g")
.attr("class", "y axis")
.call(yAxisFocus);
context.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + heightContext + ")")
.call(xAxisContext);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", heightContext + 7);
function brushed() {
xFocus.domain(brush.empty() ? xContext.domain() : brush.extent());
focus.selectAll(".line").attr("d", levelFocus(dataNest.values));
focus.select(".x.axis").call(xAxisFocus);
}
});
</script>
</body>
</html>

It mostly boils down to two things as far as I can see. First, the elements you're selecting to be updated are the g and not the path elements and second, you need to reference the data bound to the elements in order to set d. Both are easily fixed and the brushed function looks something like this.
function brushed() {
xFocus.domain(brush.empty() ? xContext.domain() : brush.extent());
focus.selectAll("path").attr("d", function(d) { return levelFocus(d.values); });
focus.select(".x.axis").call(xAxisFocus);
}
Complete demo here. Note that some bits are still missing, in particular the clip path to restrict the lines to the chart area. This can be copied and pasted directly from the example you've referenced though.

Related

exit().remove() is not removing the first bar

Whenever the slider is dragged and a new date is selected it does not redraw the first bar, it redraws all the other bars. For example, try date 09/06 and then 09/17. The first bar for id 54042 will not redraw. This is the link to my bl.ocks http://blockbuilder.org/fall16mis/87a39bc00b1b356f78dfd0954f345444
This is the code:
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
.tooltip {
opacity: 0;
background-color: #ffe047;
position: absolute;
}
.grid line {
stroke: lightgrey;
shape-rendering: crispEdges;
}
.ticks {
font-size: 11px;
}
.track,
.track-inset,
.track-overlay {
stroke-linecap: round;
}
.track {
stroke: #000;
stroke-opacity: 0.3;
stroke-width: 10px;
}
.track-inset {
stroke: #ddd;
stroke-width: 8px;
}
.track-overlay {
pointer-events: stroke;
stroke-width: 50px;
stroke: transparent;
cursor: crosshair;
}
.handle {
fill: #fff;
stroke: #000;
stroke-opacity: 0.5;
stroke-width: 1.25px;
}
.bar:hover{
fill:#0f9fff;
}
.legend{
color:#005ebc;
z-index:0;
}
</style>
</head>
<body>
<div id="slider"></div>
<script>
var json_data = "id,date,start_time,end_time\n\
54042,2017/09/06,5.50,5.53\n\
54042,2017/09/06,7.55,9.19\n\
54042,2017/09/16,11.12,12.28\n\
54042,2017/09/23,13.56,15.03\n\
54042,2017/09/07,16.29,17.33\n\
54042,2017/09/06,19.56,20.53\n\
54042,2017/09/20,21.3,22.14\n\
98765,2017/09/06,5.1,6.51\n\
98765,2017/09/06,11.4,11.53\n\
98765,2017/09/06,12.2,12.42\n\
98765,2017/09/06,12.55,14.2\n\
98765,2017/09/16,21.42,21.59\n\
98765,2017/09/16,22.01,23.13\n\
98765,2017/09/16,23.16,23.51\n\
98765,2017/09/23,13.41,14.03\n\
65299,2017/09/06,7.23,8.21\n\
65299,2017/09/06,9.37,10.23\n\
65299,2017/09/06,11.46,13.29\n\
65299,2017/09/06,18.07,19.57\n\
65299,2017/09/17,14.41,16.22\n\
65299,2017/09/17,21.39,23.39\n\
79408,2017/09/06,9.37,10.17\n\
79408,2017/09/06,11.03,12.08\n\
79408,2017/09/06,13.14,15.53\n\
79408,2017/09/06,16.05,17.48\n\
79408,2017/09/06,19.47,20.23\n\
38338,2017/09/06,8.22,9.28\n\
38338,2017/09/06,11.34,12.17\n\
38338,2017/09/07,12.43,13.35\n\
38338,2017/09/07,14.12,15.48\n\
38338,2017/09/07,16.09,17.23\n\
38338,2017/09/07,18.31,19.19\n\
38338,2017/09/07,21.49,23.26\n\
81757,2017/09/06,6.31,7.41\n\
81757,2017/09/06,8.18,9.39\n\
81757,2017/09/06,10.18,11.23\n\
81757,2017/09/06,13.02,14.04\n\
81757,2017/09/07,15.22,17.23\n\
81757,2017/09/07,20.32,22.01\n\
68077,2017/09/06,11.1,12.45\n\
68077,2017/09/06,15.23,16.54\n\
68077,2017/09/06,17.31,19.05\n\
68077,2017/09/06,20.39,21.3\n\
68077,2017/09/06,21.41,22.37\n\
58381,2017/09/06,16.51,17.55\n\
58381,2017/09/06,19.34,20.55\n\
58381,2017/09/06,21.33,22.51\n\
58381,2017/09/07,14.46,16.15\n\
37500,2017/09/06,8.2,10.18\n\
37500,2017/09/06,11.37,13.34\n\
37500,2017/09/06,19.22,20.16\n\
37500,2017/09/06,21.55,22.09\n\
37500,2017/09/16,14.16,16.26\n\
37500,2017/09/16,16.58,17.48\n\
39146,2017/09/06,19.47,20.21\n\
39146,2017/09/06,20.35,21.29\n\
39146,2017/09/06,22.01,23.25\n\
39146,2017/09/16,8.03,9.56\n\
39146,2017/09/16,10.23,12.52\n\
39146,2017/09/16,13.25,14.28";
window.data = d3.csvParse(json_data, function(d){ return {
id:d.id,
date:d.date,
start_time:+d.start_time,
end_time:+d.end_time}; });
window.minDate = d3.min(window.data,function(d){ return d.date; });
window.maxDate = d3.max(window.data,function(d){ return d.date; });
window.names = window.data.map(function(d){
return d.id; });
var parseDate = d3.timeParse("%Y/%m/%d");
var displayDate = d3.timeFormat("%m/%d");
var forChartDate = d3.timeFormat("%Y/%m/%d");
var height = 500;
var width = 800;
var margin = {left: 50, right: 20, bottom: 0, top: 70};
var tooltip = d3.select("body").append("div").attr("class", "tooltip")
var svg = d3.select("body").append("svg").attr("height","1000px").attr("width","100%");
var chartGroup = svg.append("g").attr("transform","translate("+margin.left+","+(margin.top+10)+")");
var legend = svg.append("g").attr("transform","translate("+margin.left+","+(margin.top+10)+")");
var chartDate = window.minDate;
var displaySlider = function(data){
window.x = d3.scaleLinear()
.domain([0, 24])
.range([0, width]);
window.y = d3.scaleBand()
.domain(window.names)
.rangeRound([height, 0])
.paddingInner(0.3);
function make_y_gridlines() {
return d3.axisLeft(y)
};
function make_x_gridlines() {
return d3.axisBottom(x)
};
chartGroup.append("g")
.attr("class","axis y")
.call(d3.axisLeft(y))
chartGroup.append("g")
.attr("class","axis x")
.call(d3.axisBottom(x))
.attr("transform","translate(0,"+height+")")
.call(d3.axisBottom(x)
.ticks(24));
chartGroup.append("g")
.attr("class", "grid")
.call(make_y_gridlines()
.tickSize(-width)
.tickFormat("")
);
chartGroup.append("g")
.attr("class", "grid")
.call(make_x_gridlines()
.tickSize(height)
.tickFormat("")
)
var newData = data.filter(function(d){
return d.date==chartDate;
})
displayBar(newData);
var x1 = d3.scaleTime()
.range([0, width])
.domain([parseDate(minDate), parseDate(maxDate)])
.clamp(true);
var slider = svg.append("g")
.attr("class", "slider")
.attr("transform", "translate(" + margin.left + ",30)");
slider.append("line")
.attr("class", "track")
.attr("x1", x1.range()[0])
.attr("x2", x1.range()[1])
.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("drag end", function() { sliderFunc(x1.invert(d3.event.x)); }));
slider.insert("g", ".track-overlay")
.attr("class", "ticks")
.attr("transform", "translate(0," + 10 + ")")
.selectAll("text")
.data(x1.ticks(15))
.enter()
.append("text")
.attr("x", x1)
.attr("y", 10)
.attr("text-anchor", "middle")
.text(function(d) { return displayDate(d); });
var label = slider.append("text")
.attr("class", "label")
.attr("text-anchor", "middle")
.text(minDate)
.attr("transform", "translate(0," + (-10) + ")");
var handle = slider.insert("circle", ".track-overlay")
.attr("class", "handle")
.attr("r", 7);
function sliderFunc(h) {
handle.attr("cx", x1(h));
label.attr("x", x1(h))
.text(displayDate(h));
chartDate = forChartDate(h);
console.log(chartDate);
newData = data.filter(function(d){
return d.date==chartDate;
})
//displayText(newData);
displayBar(newData);
}
};
var displayBar = function(data){
var bars = chartGroup.selectAll(".bar")
.data(data, function(d){
console.log(d);
return d;
});
bars.exit().remove();
bar_enter = bars.enter().append("rect")
bar_enter.attr("class", "bar")
.attr("x", function(d) {
console.log(d.start_time);
return window.x(d.start_time); })
.attr("y", function(d) { return window.y(d.id); })
.attr("height", window.y.bandwidth())
.attr("fill", "green")
.transition()
.duration(600)
.attr("width", function(d) { return window.x(d.end_time-d.start_time); });
bar_enter.on('mousemove', function(d,i){
tooltip.style("opacity","1")
.style("left",(d3.event.pageX+10)+"px")
.style("top",d3.event.pageY+"px");
tooltip.html(" Start Time:"+d.start_time+" End Time:"+d.end_time);
})
bar_enter.on('mouseout', function(){
tooltip.style("opacity","0")
});
};
displaySlider(window.data);
</script>
</body>
You cannot use the whole object in the key function:
var bars = chartGroup.selectAll(".bar")
.data(data, function(d){
return d;
});
The API explains it:
key function may be specified to control which datum is assigned to which element, replacing the default join-by-index, by computing a string identifier for each datum and element. (emphasis mine)
Therefore, instead of using the whole object, use a property (like the id):
var bars = chartGroup.selectAll(".bar")
.data(data, function(d){
return d.id;
});
Here is the updated bl.ocks: https://bl.ocks.org/GerardoFurtado/57d014aa5124bdbe5774e1457816ff43/07a233d46aa3ae91bcffb8682de9ed8376c99b9a

Multiple line chart issue from json

Following is my code. I have been trying to plot humidity and dew point in basis of months in the axis. But I am getting an error of data undefined and also the month in the axis comes in number.
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<title> Data Visualization - Binding Dataset to Shapes Using D3 </title>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<style> /* set the CSS */
body { font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.legend {
font-size: 16px;
font-weight: bold;
text-anchor: middle;
}
</style>
<body>
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 70, left: 50},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%b").parse;
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var priceline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.dew); });
// Adds the svg canvas
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 + ")");
// Get the data
d3.json("weatherdata.json", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.history.date.mon);
d.dew = +d.history.dailysummary[0].meandewptm;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.dew; }));
// Nest the entries by symbol
var dataNest = d3.nest()
.key(function(d) {return d.dew;})
.entries(data);
var color = d3.scale.category10(); // set the colour scale
legendSpace = width/dataNest.length; // spacing for legend
// Loop through each symbol / key
dataNest.forEach(function(d,i) {
svg.append("path")
.attr("class", "line")
.style("stroke", function() { // Add the colours dynamically
return d.color = color(d.key); })
.attr("d", priceline(d.values));
// Add the Legend
svg.append("text")
.attr("x", (legendSpace/2)+i*legendSpace) // spacing
.attr("y", height + (margin.bottom/2)+ 5)
.attr("class", "legend") // style the legend
.style("fill", function() { // dynamic colours
return d.color = color(d.key); })
.text(d.key);
});
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
});
</script>
</body>
</html>
I am not sure how will i solve it. Can anyone please help. I am sharing the data file with the current issue I am facing.
I have attached the model data the way it should look like:
Thanks in advance.
For your question on Date format for the X axis , sorry i put an answer because i can't comment (need more reputation).
I think you need to do something like this
chart.xAxis
.tickFormat(function(d) {
return d3.time.format('%b')(format(d));
});
That will display only the month.

d3 line graph using csv

I have a data in csv like this:
date,partner,units
2012-05-01,team1,34.12
2012-04-30,team1,45.56
2012-04-27,team2,67.89
2012-04-26,team1,78.54
2012-04-25,team2,89.23
2012-04-24,team2,99.23
2012-04-23,team2,101.34
I want to plot two lines (one for team1, one for team2 using this data), but I am just getting a scatterplot using the following complete d3 code, is my filtering wrong?
<!DOCTYPE html>
<meta charset="utf-8">
<style> /* set the CSS */
#line1 {
fill: none;
stroke: steelblue;
stroke-width: 2px;
}
#line2 {
fill: none;
stroke: red;
stroke-width: 2px;
}
</style>
<input type="button" onclick="hideLine()">
<input type="button" onclick="showLine()">
<!-- load the d3.js library -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the graph
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// parse the date / time
var parseTime = d3.timeParse("%Y-%m-%d");
// set the ranges
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
// define the 1st line
var valueline = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.units); });
// define the 2nd line
var valueline2 = d3.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.units); });
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 + ")");
// Get the data
d3.csv("data1.csv", function(error, data) {
if (error) throw error;
// format the data
data.forEach(function(d) {
d.date = parseTime(d.date);
d.units = +d.units;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
m = d3.max(data, function(d) {
var m = d.units;
return m;
});
console.log("Max:", m);
y.domain([0,m]);
// Add the valueline path.
svg.append("path")
.data([data])
.filter(function(d) { return d.partner == 'team1'; })
.attr("id", "line1")
.attr("d", valueline);
console.log("DATA", data)
svg.selectAll(".point")
.data(data)
.enter()
.append("circle")
.attr("class", "point")
.attr("cx", function (d, i) {
return x(d.date);
})
.attr("cy", function(d) {
return y(d.units);
})
.attr("r", 4)
.on("mouseover", function(d) { console.log(d.units) });
// Add the valueline2 path.
svg.append("path")
.data([data])
.filter(function(d) { return d.partner == 'team2'; })
.attr("id", "line2")
.style("stroke", "red")
.attr("d", valueline2)
.on("mouseover", function(d) {console.log(d)});
// Add the X Axis
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg.append("g")
.call(d3.axisLeft(y));
});
function hideLine() {
console.log("Hideline");
d3.select("#line2").attr("style", "opacity:0");
}
function showLine() {
console.log("ShowLine");
d3.select("#line2").attr("style", "opacity:1");
}
</script>
</body>
Yes, your filter is wrong. You are wrapping your data in another [] which means the filter is only operating on the outer array.
Do this instead:
svg.append("path")
.datum(data.filter(function(d) { return d.partner == 'team1'; }))
.attr("id", "line1")
.attr("d", valueline);
Working example here.

Removing the D3 brush on button click

I'm wrestling with a problem of a brush not being removed correctly on a bar chart. You can see the Bl.ock here and see what's not working correctly.
In short, the brush highlights the bars that have been selected by the brush, as well as snaps to the edge of the rect to make selecting spans of time easier (there's a secondary bug here where the brush snapping isn't quite mapping correctly to the dates -- you'll see this if you try to draw the brush up to the edge of the barchart). Somewhere along the way (maybe with the rect snapping?) the background click-to-remove-brush feature stopped working (it now selects a single year span, although doesn't highlight the rect correctly). To make it easier for users, I wanted to add a button that a user can click to remove the brush when they're done (the resetBrush() function below).
My understanding was the brush selection can be cleared with brush.extent(), but when you clear the extent you then have to redraw the brush. I thought I was doing that correctly, but alas, I'm running into some problem somewhere that I can't seem to track down. Any pointers on where I'm tripping up would be greatly appreciated!
Code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: sans-serif;
color: #000;
text-rendering: optimizeLegibility;
}
.barchart {
z-index: 30;
display: block;
visibility: visible;
position: relative;
padding-top: 15px;
margin-top: 15px;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.resize path {
fill: #666;
fill-opacity: .8;
stroke: #000;
stroke-width: 1.5px;
}
.brush .extent {
stroke: #fff;
stroke-opacity: .6;
stroke-width: 2px;
fill-opacity: .1;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
brushYearStart = 1848;
brushYearEnd = 1905;
// Scales
var x = d3.scale.ordinal().rangeRoundBands([0, width - 60], .1);
var y = d3.scale.linear().range([height, 0]);
// Prepare the barchart canvas
var barchart = d3.select("body").append("svg")
.attr("class", "barchart")
.attr("width", "100%")
.attr("height", height + margin.top + margin.bottom)
.attr("y", height - height - 100)
.append("g");
var z = d3.scale.ordinal().range(["steelblue", "indianred"]);
var brushYears = barchart.append("g")
brushYears.append("text")
.attr("id", "brushYears")
.classed("yearText", true)
.text(brushYearStart + " - " + brushYearEnd)
.attr("x", 35)
.attr("y", 12);
d3.csv("years_count.csv", function (error, post) {
// Coercion since CSV is untyped
post.forEach(function (d) {
d["frequency"] = +d["frequency"];
d["frequency_discontinued"] = +d["frequency_discontinued"];
d["year"] = d3.time.format("%Y").parse(d["year"]).getFullYear();
});
var freqs = d3.layout.stack()(["frequency", "frequency_discontinued"].map(function (type) {
return post.map(function (d) {
return {
x: d["year"],
y: +d[type]
};
});
}));
x.domain(freqs[0].map(function (d) {
return d.x;
}));
y.domain([0, d3.max(freqs[freqs.length - 1], function (d) {
return d.y0 + d.y;
})]);
// Axis variables for the bar chart
x_axis = d3.svg.axis().scale(x).tickValues([1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900]).orient("bottom");
y_axis = d3.svg.axis().scale(y).orient("right");
// x axis
barchart.append("g")
.attr("class", "x axis")
.style("fill", "#000")
.attr("transform", "translate(0," + height + ")")
.call(x_axis);
// y axis
barchart.append("g")
.attr("class", "y axis")
.style("fill", "#000")
.attr("transform", "translate(" + (width - 85) + ",0)")
.call(y_axis);
// Add a group for each cause.
var freq = barchart.selectAll("g.freq")
.data(freqs)
.enter().append("g")
.attr("class", "freq")
.style("fill", function (d, i) {
return z(i);
})
.style("stroke", "#CCE5E5");
// Add a rect for each date.
rect = freq.selectAll("rect")
.data(Object)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.x);
})
.attr("y", function (d) {
return y(d.y0) + y(d.y) - height;
})
.attr("height", function (d) {
return height - y(d.y);
})
.attr("width", x.rangeBand())
.attr("id", function (d) {
return d["year"];
});
// Draw the brush
brush = d3.svg.brush()
.x(x)
.on("brush", brushmove)
.on("brushend", brushend);
var arc = d3.svg.arc()
.outerRadius(height / 15)
.startAngle(0)
.endAngle(function(d, i) { return i ? -Math.PI : Math.PI; });
brushg = barchart.append("g")
.attr("class", "brush")
.call(brush);
brushg.selectAll(".resize").append("path")
.attr("transform", "translate(0," + height / 2 + ")")
.attr("d", arc);
brushg.selectAll("rect")
.attr("height", height);
});
// ****************************************
// Brush functions
// ****************************************
function brushmove() {
y.domain(x.range()).range(x.domain()).clamp(true);
b = brush.extent();
brushYearStart = Math.ceil(y(b[0]));
brushYearEnd = Math.ceil(y(b[1]));
// Snap to rect edge
d3.select("g.brush").call(brush.extent([y.invert(brushYearStart), y.invert(brushYearEnd)]));
// Fade all years in the histogram not within the brush
d3.selectAll("rect.bar").style("opacity", function (d, i) {
return d.x >= brushYearStart && d.x < brushYearEnd ? "1" : ".4"
});
}
function brushend() {
// Additional calculations happen here...
// filterPoints();
// colorPoints();
// styleOpacity();
// Update start and end years in upper right-hand corner of the map
d3.select("#brushYears").text(brushYearStart + " - " + brushYearEnd);
}
function resetBrush() {
d3.selectAll(".brush").remove();
d3.selectAll("brushg.resize").remove();
brush.clear();
brushg.call(brush);
}
</script>
<div id="resetMap">
<button
id="returnBrush"
class="btn btn-default"
onclick="resetBrush()"/>Remove Brush
</div>
</body>
</html>
If you execute d3.selectAll(".brush").remove(); you remove <g class="brush"></g> and his childs.
This d3.selectAll("brushg.resize").remove(); is a bug. Must to be brushg.selectAll(".resize").remove(); but is the same case that d3.selectAll(".brush").remove();.
You have to do this:
For reset the brush.extent() and fire the brush event.
function resetBrush() {
brush
.clear()
.event(d3.select(".brush"));
}
For reset #brushYears to the initial state
function brushend() {
var localBrushYearStart = (brush.empty()) ? brushYearStart : Math.ceil(y(b[0])),
localBrushYearEnd = (brush.empty()) ? brushYearEnd : Math.ceil(y(b[1]));
// Update start and end years in upper right-hand corner of the map
d3.select("#brushYears").text(localBrushYearStart + " - " + localBrushYearEnd);
}
For reset to initial values on brush event
function brushmove() {
y.domain(x.range()).range(x.domain()).clamp(true);
b = brush.extent();
3.1. To set the localBrushYearStart and localBrushYearEnd variables to initial state on brush.empty() or set to Math.ceil(brush.extent()))
var localBrushYearStart = (brush.empty()) ? brushYearStart : Math.ceil(y(b[0])),
localBrushYearEnd = (brush.empty()) ? brushYearEnd : Math.ceil(y(b[1]));
3.2. To execute brush.extent() on selection, or brush.clear() on brush.empty()
// Snap to rect edge
d3.select("g.brush").call((brush.empty()) ? brush.clear() : brush.extent([y.invert(localBrushYearStart), y.invert(localBrushYearEnd)]));
3.3. To set opacity=1 years on brush.empty() or selection, and opacity=.4 on not selected years
// Fade all years in the histogram not within the brush
d3.selectAll("rect.bar").style("opacity", function(d, i) {
return d.x >= localBrushYearStart && d.x < localBrushYearEnd || brush.empty() ? "1" : ".4";
});
}
Check the corrections on my BL.OCKS
Just do this
function resetBrush() {
d3.select("g.brush").call(brush.extent([0, 0]))
d3.selectAll("rect.bar").style("opacity", "0.4");
//reset year labels at top
}

D3.JS - how do I add gridlines to my pie chart

I have extended the pie-chart example at:
with pies that vary in radius depending on a percentage. I would like to add gridlines (circles) every 20 percent, but I can't figure out how.
here is the updated csv:
age,population,percent
<5,2704659,67
5-13,4499890,38
14-17,2159981,91
18-24,3853788,49
25-44,14106543,71
45-64,8819342,88
=65,612463,64
and here is the updated code with pie-parts of different radius:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
background: #333;
}
.arc path {
stroke: #fff;
stroke-width: 2px;
}
.arc grid {
stroke: #fff;
stroke-width: 1;
stroke-dasharray: 5,5;
}
.arc text {
fill:#fff;
font-size:12px;
font-weight:bold;
}
.arc line {
stroke: #fff;
}
</style>
<body>
<script src="d3.js"></script>
<script>
var width = 960,
height = 500,
radius = Math.min(width, height) / 2 - 10;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(function(d) { return 50 + (radius - 50) * d.data.percent / 100; })
.innerRadius(20);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
var grid = d3.svg.area.radial()
.radius(150);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.population = +d.population;
d.percent = d.percent;
});
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); });
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });
});
</script>
First set the number of ticks:
var numTicks = 5; // Each tick is 20%
Then create the data to create the gridlines:
var sdat = [];
for (i=0; i<=numTicks; i++) {
sdat[i] = (radius/numTicks) * i;
}
And then you can use a function to create the radial gridlines, and you can call it from within the d3.csv block:
addCircleAxes = function() {
var circleAxes, i;
svg.selectAll('.circle-ticks').remove();
circleAxes = svg.selectAll('.circle-ticks')
.data(sdat)
.enter().append('svg:g')
.attr("class", "circle-ticks");
// radial tick lines
circleAxes.append("svg:circle")
.attr("r", String)
.attr("class", "circle")
.style("stroke", "#CCC")
.style("opacity", 0.5)
.style("fill", "none");
// Labels for each circle
circleAxes.append("svg:text")
.attr("text-anchor", "center")
.attr("dy", function(d) { return d - 5 })
.style("fill", "#fff")
.text(function(d,i) { return i * (100/numTicks) });
};
An example is here: http://bl.ocks.org/3994129
(Borrowed from: http://kreese.net/blog/2012/08/26/d3-js-creating-a-polar-area-diagram-radial-bar-chart/)

Resources