My initial array contains quarterly data, needed to build a boxplot, faceted by ProspRating. I created number of complex g objects (one for each ProspRating) based on the first quarter data. Each g incapsulates simple boxplot elements - lines, rectangles, etc.)
To create the objects I used the following code (excerpt for one simple boxplot element (line.range) only):
d3.select("svg").selectAll("g.box")
.data(data10Q1)
.enter()
.append("g")
.attr("class", "box")
.attr("transform", function(d) {return "translate(" + xScale(d.ProspRating) +"," + yScale(d.v_mdn) + ")"})
.each(function(d,i) {
d3.select(this)
.append('line')
.attr("class", "range")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", yScale(d.v_max) - yScale(d.v_mdn))
.attr("y2", yScale(d.v_min) - yScale(d.v_mdn))
.style("stroke", "black")
.style("stroke-width", "4px");
The initial boxplot based on the code works well. Currently am trying to write a function to update the element attributes based on data for another quarter.
Excerpt from the code (for one boxplot's element (line.range) only):
function update (quarter) {
var filtered = data.filter(function(d) {
return d.quarter === quarter})
var boxes = d3.select("svg").selectAll("g.box")
.data(filtered, function(d){return d.ProspRating})
.attr("transform", function(d) {return "translate(" + xScale(d.ProspRating) +"," + yScale(d.v_mdn) + ")"})
.each(function(d,i) {
d3.select(this)
.select("line.range")
.attr("y1", yScale(d.v_max) - yScale(d.v_mdn))
.attr("y2", yScale(d.v_min) - yScale(d.v_mdn))
Looks like d3.select(this).select("line.range") does not work properly. What would be the best way to access the line.range and other similar elements to update their attributes?
I guess you missed the .enter() after var boxes = d3.select [...] .data [...]
Related
I have a data that is filtered by year then nested by group. The first few years there is one group, while the remaining years have seven to eight groups for which the lines are plotted. Along with lines I also plot points of actual data.
here is the plot for the year 2007 that has only one group:
[2007 line plot][1]
Notice the y-axis is missing and only the points are showing but no lines.
Here is the plot for the year 2010 that has eight groups:
[2010 lines plot][2]
Notice the y-axis, all points and lines are showing.
Here is the associated code:
Defining line
var line = d3.svg.line()
.x(function(d) { return LoanAmount_scale(d['amount_mean']); })
.y(function(d) { return Investor_scale(d['Investors_mean']); });
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right )
.attr("height", height + margin.top + margin.bottom);
var plot = svg.append("g")
.attr("class", "chart")
.attr("width" , width- margin.left)
.attr("height", height)
.attr("transform","translate ("+ margin.left + "," + margin.top + ")");
menu selection
// setting stage for dropdown menu
// years
var years = ['Select Year'];
var i= 2006;
for( i = 2006; i<= 2014; i++){
years.push(i);
}
// selecting an option
var select = d3.select('body')
.append('select')
.attr('class','select')
.attr('id','select_id')
.on('change',change);
// available options on dropdown menu
var option = select.selectAll('option')
.data(years)
.enter()
.append('option')
.text(function(d){ return d});
// menu selection of year
function change(){
var select_year = d3.select('select')
.property('value')
// reset chart before updating year
update(data,'Select Year');
// update year
update(data,select_year);
}
Update function filters year and nests by group, then plots lines and points
function update(data, year){
var Filtered = data.filter(function(d){ return d["LoanOrig_Yr"] == year});
var nested_by_rating = d3.nest()
.key(function(d) {
return d['ProsperRating']
}).sortKeys(d3.ascending)
.entries(Filtered);
// add lines for each rating
var lines = plot.selectAll("path")
.data(nested_by_rating)
// remove old lines
lines.exit().remove();
plot.selectAll("circle")
.data(nested_by_rating)
.exit().remove();
nested_by_rating.forEach( function(d,i){
var prosperR = d.key
// entering data
lines.enter()
.append("path")
.attr("class", "line")
.attr("d", line(d.values))
.attr("stroke", RatingColor(prosperR))
.attr("stroke-width", 3)
.attr("stroke-opacity",0.3)
.attr("fill","none");
debugger;
plot.selectAll("path.dot")
.data(d.values)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", line.x())
.attr("cy", line.y())
.attr("r", 3.5)
.attr("fill","Black");
debugger;
// dynamic legend
svg.append("text")
.attr("x", 1300)
.attr("y", 100 + i*20)
.attr("class", "legend") // style the legend
.style("fill", function() { // Add the colours dynamically
return d.color = RatingColor(prosperR); })
.text(prosperR);
Reset chart between year selection
// reseting chart between years
if (year == 'Select Year'){
gX.call(xAxis);
gY.call(yAxis);
svg.append("text")
.attr("x", 1300)
.attr("y", 100)
.attr("class", "legend") // style the legend
.style("fill", "white")
.text(["NR"]);
svg.selectAll("legend_text")
.data(["1.AA","2.A","3.B","4.C","5.D","6.E","7.HR","NR"])
.enter()
.append("text")
.attr("class","legend")
.attr("x", 1300)
.attr("y", function(d,i){return 100 + i*20;
})
.attr("class", "legend") // style the legend
.style("fill", "white")
.style("stroke","white")
.text(function(d,i){return d;});
} \\ end if statement
} \\end .forEach statement
When tracking data entry and exit using the browser inspector, I find the data in both cases move in and out as expected. Observe the points data are passed using line.x() and line.y(), so it is very strange that the line does not show in the year 2007 and other similar years with one group.
I would appreciate any help fixing this bug. Thank you.
I am trying to make a stacked bar graph through d3js and have it update when new data is passed through an update function. I call this update function to initially call the graph and it works fine. However, when I change the data and call it again, it erases all the "rect" elements from the graph (When I console log the data, it appears to be passing through). How can I make the graph be redrawn appropriately? I have tried experimenting with the .remove() statement at the beginning, but without it the data doesn't pass through when the bars are redrawn.
function update(my_data) {
svg.selectAll(".year").remove();
var year = svg.selectAll(".year")
.data(my_data)
.enter().append("g")
.attr("class", "year")
.attr("transform", function(d) { return "translate(" + x0(d.Year) + ",0)"; });
var bar = year.selectAll(".bar")
.data( function(d){ return d.locations; });
bar
.enter().append("rect")
.attr("class", "bar")
.attr("width", x0.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
}
update(data);
It's hard to tell exactly what you're doing cause your question doesn't include the data or the DOM. It would help if you included a link to a work-in-progress jsFiddle or something.
If I had to guess what's going wrong, it looks like you're doing a nested join where each year gets bound to a g element and then each location gets bound to a rect inside each g element.
The issue is likely you are only specifying the enter behavior, but not the update behavior or the exit behavior. As a result, when you try to redraw, nothing updates and nothing exits - but new data elements will get added.
It would seem that is why you have to add the selectAll().remove() to get anything to redraw. By removing everything, all the data elements will trigger the enter condition and get added again.
Take a look at these tutorials to better understand how the enter/update/exit pattern works and how nested joins work.
General Update Pattern: https://bl.ocks.org/mbostock/3808218
Nested Selections: https://bost.ocks.org/mike/nest/
Also, here is a jsFiddle I wrote some time ago to demonstrate how to use nested selections and the general update pattern together:
https://jsfiddle.net/reblace/bWp8L/
var series = svg.selectAll("g.row").data(data, function(d) { return d.key; });
/*
* This section handles the "enter" for each row
*/
// Adding a g element to wrap the svg elements of each row
var seriesEnter = series.enter().append("g");
seriesEnter
.attr("class", "row")
.attr("transform", function(d, i){
return "translate(" + margin.left + "," + (margin.top + (span*i)) + ")";
})
.attr("opacity", 0).transition().duration(200).attr("opacity", 1);
// Adding a text label for each series
seriesEnter.append("text")
.style("text-anchor", "end")
.attr("x", -6)
.attr("y", boxMargin + (boxDim/2))
.attr("dy", ".32em")
.text(function(d){ return d.key; });
// nested selection for the rects associated with each row
var seriesEnterRect = seriesEnter.selectAll("rect").data(function(d){ return d.values; });
// rect enter. don't need to worry about updates/exit when a row is added
seriesEnterRect.enter().append("rect")
.attr("fill", function(d){ return colorScale(d)})
.attr("x", function(d, i){ return i*span + boxMargin; })
.attr("y", boxMargin)
.attr("height", boxDim)
.attr("width", boxDim);
/*
* This section handles updates to each row
*/
var seriesUpdateRect = series.selectAll("rect").data(function(d){ return d.values});
// rect update (Will handle updates after enter)
// rect enter
seriesUpdateRect.enter().append("rect")
.attr("x", function(d, i){ return i*span + boxMargin; })
.attr("y", boxMargin)
.attr("height", boxDim)
.attr("width", boxDim);
// rect enter + update
seriesUpdateRect
.attr("fill", function(d){ return colorScale(d)});
// Exit
seriesUpdateRect.exit();
/*
* This section handles row exit
*/
series.exit()
.attr("opacity", 1)
.transition().duration(200).attr("opacity", 0)
.remove();
I am working with d3.js and need some help regarding data usage.
I have a data array that I bind with d3 and render elements in my plot.
However, each plot item has multiple section, like, starting dot, line and ending dot, something like as below:
O-----------O O---------O
O----------O
Now currently, I am binding the data multiple times for each of the items, the steps are,
Bind data array
Render all starting dots
Bind data array
Render all Line dots
Bind data array
Render all ending dots
the problem in this method is order of rendering. if one plot item rendered over another plot item, the ending dot of the first item will be rendering over the line of 2nd plot item. Another issue is if I want to manipulate a plot item, it is problematic to trace all bits and pieces of a single plot item as they are not related in the plot grammatically.
Now my question is is there any way to bind the data once and render all elements (start dot, line and end dot) together? so that the order of rendering is correct? (in this case all elements of 2nd plot item will render over the 1st plot item).
var lineSelection = PlotGroup.selectAll(".Line")
.data(EventList);
lineSelection .enter()
.append("line")
.attr("class", "Line gline")
.attr("x1", function (d) {
return XScale(d.startTime);
})
.attr("y1", (_Position))
.attr("x2", function (d) {
return XScale(d.endTime);
})
.attr("y2", (_Position));
var RectInSelection = PlotGroup.selectAll(".RectIn")
.data(EventList);
RectInSelection.enter()
.append("rect")
.attr("class", "RectIn")
.attr("x", function (d) {
return XScale(d.startTime);
})
.attr("y", function (d) {
return _Position;
})
.attr("width", 16)
.attr("height", 16);
var RectOutSelection = PlotGroup.selectAll(".RectOut")
.data(EventList);
RectOutSelection.enter()
.append("rect")
.attr("class", "RectOut")
.attr("x", function (d) {
return XScale(d.endTime);
})
.attr("y", function (d) {
return _Position;
})
.attr("width", 16)
.attr("height", 16);
I would have made as many groups as data, and to each group i would have made the line and rectangles. This will fix the overlapping issue and multiple data binding issue.
var groups = PlotGroup.selectAll(".mygroup")
.data(EventList)
.enter()
.append("g")
.classed("mygroup", true);
//make line in the group
groups.append("line")
.attr("class", "Line gline")
.attr("x1", function (d) {
return XScale(d.startTime);
})
.attr("y1", (_Position))
.attr("x2", function (d) {
return XScale(d.endTime);
})
.attr("y2", (_Position))
//make in rectangle
groups.append("rect")
.attr("class", "RectIn")
.attr("x", function (d) {
return XScale(d.startTime);
})
.attr("y", function (d) {
return _Position;
})
.attr("width", 16)
.attr("height", 16);
//make out rectangle
groups.append("rect")
.attr("class", "RectOut")
.attr("x", function (d) {
return XScale(d.endTime);
})
.attr("y", function (d) {
return _Position;
})
.attr("width", 16)
.attr("height", 16)
Hope this helps!
I'm using a range slider to add/remove points from a line which follows an histogram.
Without updating the data, the text labels are shown correctly, when I update the values with the slider I'd like that the text-labels follow the line, so they show the values of the points correctly.
The update for the line works, but I am not able to update the text labels correctly.
Right now the laberls are plotted like so outside of the on-change function:
var SerifText = svg.append("g")
.attr("id", "serif-labels")
.selectAll("text")
.data(histogSerif)
.enter()
.append("text")
.text(function (d) { return d.y >= 1 ? d.y : null; })
.attr("text-anchor", "middle")
.attr("font-size", 10 + "px")
.attr("fill", "#f0027f")
.attr("x", function (d) { return x(d.x)})
.attr("y", function (d) { return y(d.y) - padding; })
.attr("visibility", "hidden"); //the hidden & visible is managed by an external checkbox
And then on the on-change function I put this code:
SerifText.selectAll("text").remove();
/* If I do SerifText.remove() it does remove the text but also the group of course,
but as it is it doesn't work */
SerifText.selectAll("text")
.data(histogSerif)
.enter()
.append("text")
.text(function (d) { return d.y >= 1 ? d.y : null; })
.attr("text-anchor", "middle")
.attr("font-size", 10 + "px")
.attr("fill", "#f0027f")
.attr("x", function (d) { return x(d.x)})
.attr("y", function (d) { return y(d.y) - padding; });
But this doesn't work unfortunately. I'd like also to be able to retain the manage of the visible/hidden status by the external checkbox if possible.
Full code is here
Instead of trying to save a reference to the labels that you are going to delete, and then re-selecting them, how about just use a variable that references the SerifText element selection, e.g.:
var SerifTextGroup = svg.append("g")
.attr("id", "serif-labels");
SerifTextGroup.selectAll("text")
.data(histogSerif)
.enter()
//... and so on
Then the rest of your code should work as written (just change SerifText to SerifTextGroup).
I am trying to create a bar graph with a time scale where its possible to zoom into any time period. I was able to create the zooming functionality for my x-axis(time scale) however my data (in this case rects) doesn't zoom along with my axis.
Here is a simplified version of my graph: http://jsfiddle.net/gorkem/Mf457/5/embedded/result/
As you can see I can zoom in to my x-axis but the bars do not zoom along.
and here is the jfiddle link: http://jsfiddle.net/gorkem/Mf457/6/
var y = d3.scale.linear().domain([0, max_val]).range([graph_height, 0]);
var x = d3.time.scale().domain([minDate, maxDate]).range([0, graph_width]);
var chart = d3.select(location).append("svg")
.attr("class", "chart")
.attr("width", graph_width+20)
.attr("height", graph_height+20)
.call(d3.behavior.zoom().x(x).scaleExtent([1, 8]).on("zoom", zoom));
var lines = chart.selectAll("line");
var lines_y = lines
.data(x.ticks(5))
.enter().append("line")
.attr("x1", x)
.attr("x2", x)
.attr("y1", function (d) {return graph_height - 20 - d;})
.attr("y2", graph_height)
.style("stroke", "#ccc");
var lines_x = lines
.data(y.ticks(10))
.enter().append("line")
.attr("x1", 0)
.attr("x2", graph_width)
.attr("y1", y)
.attr("y2", y)
.style("stroke", "#ccc");
xAxis = d3.svg.axis().scale(x);
yAxis = d3.svg.axis().scale(y).orient("left");
chart.append("svg:g")
.attr("class", "xaxis")
.attr("transform","translate(0,300)")
.call(xAxis);
chart.append("svg:g")
.attr("class", "yaxis")
.attr("transform", "translate(25,0)")
.call(yAxis);
var rect = chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function (d,i) {return x(new Date(d["date"]))+20; })
.attr("y", function (d,i) { return graph_height - (d["num"] *v_scale);})
.attr("width", x(new Date(data[1]["date"])))
.attr("height", function (d,i) {return d["num"]*v_scale;});
rect.call(d3.behavior.zoom().x(x).scaleExtent([1, 8]).on("zoom", zoom));
function zoom() {
chart.select(".xaxis").call(xAxis);
chart.select(".yaxis").call(yAxis);
}
}
I feel like I should be adding more functionality to my zoom function for the function to be effective on bars(rects). I would really appreciate any help.
Using 'selectAll', I've applied the scaling and translation to each bar respectively, restricting both scale and translation to the x-axis only.
function zoom() {
chart.select(".xaxis").call(xAxis);
chart.select(".yaxis").call(yAxis);
chart.selectAll(".chart rect").attr("transform", "translate(" + d3.event.translate[0] + ",0)scale(" + d3.event.scale + ", 1)");
}
This works equally well with a single path element, which is what I was trying to figure out when I stumbled upon your question.
I'm wondering the same thing, so at least know you're in good company. Here's my current approach. First of all, I can't generate a plot from your code and the links don't send me to anything useful -- no chart renders. So I can't help with your specific problem. However, there is an example that has zoomable rectangles here: http://bl.ocks.org/1962173. I'm currently going through it and deleting unnecessary elements and seeing if I can make it work. Hopefully this helps!