D3 Set labels on Legend - javascript

I need help setting the labels on a D3 stacked bar chart. I'm not sure how to map the color in the legend with a reference to the name property in the data object.
I have a JSFiddle here:
http://jsfiddle.net/Lhs3e7xk/1/
The code in particular I need help with is the legend function:
function updateLegend(dt) {
var legend = svg.selectAll(".legend")
.data(color.domain()) // I tried dt as well.
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d, i) {
console.log(d)
return color(d.name)
});
}
The output should be the value for the name property in the data set and the color associated with that group.
Fixed MBS [Color01]
Floating MBS [Color02]
CMO [Color03]
Thank you!

Instead of this
.text(function(d, i) {
console.log(d)
return color(d.name)
})
do the text function like this:
.text(function(d, i) {
if(i==0)
return "Fixed MBS"
if(i==1)
return "Floating MBS"
if(i==2)
return "CMO"
});
Working example here
EDIT
For setting legends using data
//your dynamic legend data set
var legends = ["Fixed MBS", "Floating MBS", "CMO"];
function updateLegend(dt) {
var legend = svg.selectAll(".legend")
.data(legends)//pass the lgend data
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d, i){return color(i)});//color based on index
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d, i) {
return d;//return the array data
});
}
Working code here

Related

d3 bar chart labels not getting updated on updating the chart with the new data

Below is the code getting called everytime when I click on a button or an option in the drop down menu in order to update my d3 bar chart graph with the new data.
The bars in the chart are properly updated according to the new data, but the labels of the bar chart are not getting replaced with the new ones, instead both old and new labels are displayed.
Please suggest something so that old labels are not shown on bars.
function updateGraph(data) {
// scale the range of the data
x.domain(data.map(function(d) { return d.Country; }));
y.domain([0, d3.max(data, function(d) { return d.Value; })]);
var bars = svg.selectAll(".bar").data(data);
bars.enter().append("rect").attr("class", "bar");
bars.transition().duration(200).attr("x", function(d) { return x(d.Country); })
.attr("width", x.rangeBand())
.attr("y", function(d) {return y(d.Value); })
.attr("height", function(d) { return height - y(d.Value); });
var texts = svg.selectAll(".text").data(data);
texts.enter().append("text").attr("class","label").attr("x", (function(d) { return x(d.Country) + x.rangeBand() / 2 ; } ))
.attr("y", function(d) { return y(d.Value) + 1; })
.attr("dy", ".75em")
.text(function(d) { return d.Value; });
texts.transition().duration(200).attr("x", (function(d) { return x(d.Country) + x.rangeBand() / 2 ; } ))
.attr("y", function(d) { return y(d.Value) + 1; })
.attr("dy", ".75em")
.text(function(d) { return d.Value; });
bars.exit().remove();
svg.selectAll(".axis").remove();
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value");
}
You are not adding class to the text
This will select all DOM with class text
var texts = svg.selectAll(".text").data(data);
texts
.enter()
.append("text")
.attr("class","label text") <-- add text class here
.attr("x", (function(d) { return x(d.Country) + x.rangeBand() / 2 ; } ))
.attr("y", function(d) { return y(d.Value) + 1; })
.attr("dy", ".75em")
.text(function(d) { return d.Value; });
Now to remove all text on update
bars.exit().remove();
texts.exit().remove(); <--- add this to remove exited text

How to make a drop-down filter in my visualisation

I would love to implement a dropdown filter in my visualisation that allows me to filter by endorsed president. However, being very new to d3, I am really struggling. I have tried to implement it using code I have found elsewhere but to no avail.
var dataPath = "data/p.csv";
var dataPath2 = "data/e.csv";
var field1=[];
var field2=[];
d3.csv(dataPath2,function(data){
data.map(function(d){
field1.push(d.year);
field2.push(d.publication);
})
//called after the AJAX is success
console.log("field1",field1);
console.log("field2",field2);
console.log("field1",field1[0]);
var myData = data;
var margin = 150,
width = 1000 - margin,
height = 2000 - margin;
/*
* value accessor - returns the value to encode for a given data object.
* scale - maps value to a visual display encoding, such as a pixel position.
* map function - maps from data value to display value
* axis - sets up axis
*/
// setup x
var yValue = function(d) { return d.publication;}, // data -> value
yScale = d3.scale.ordinal().domain(field2).rangePoints([height, margin]); // value -> display
yMap = function(d) { return yScale(yValue(d));}, // data -> display
yAxis = d3.svg.axis().scale(yScale).orient("left");
// setup y
var xValue = function(d) { return d.year;}, // data -> value
xExtent = d3.extent(data, function(d) {
return d.year;
});
xScale = d3.scale.linear().domain(xExtent).range([0,width-200]), // value -> display
xMap = function(d) { return xScale(xValue(d));}, // data -> display
xAxis = d3.svg.axis().scale(xScale).orient("bottom");
//
//
// // setup fill color
var cValue = function(d) { return d.endorsed;},
color = d3.scale.category10();
//
// // add the graph canvas to the body of the webpage
var svg = d3.select("body").append("svg")
.attr("width", width + margin)
.attr("height", height + margin)
.append("g")
.attr("transform", "translate(150)");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// x-axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis
.ticks(12))
.append("text")
.attr("class", "label")
.attr("x", width-200)
.attr("y", -6)
.style("text-anchor", "end")
.text("Year");
//
// // y-axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis
.ticks(50))
.append("text")
.attr("class", "label")
// .attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Publication");
// draw dots
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 4)
.attr("x", width - 10)
.attr("y", 95)
.attr("cx", xMap )
.attr("cy", yMap)
.style("fill", function(d) { return color(cValue(d));})
// if (d.endorsed == "Clinton") { return "red"}
// else {return "black"}; })
.on("mouseover", function(d) {
tooltip.transition()
.duration(1000)
.style("opacity", .9);
tooltip.html( d.endorsed
)
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(1000)
.style("opacity", 0);
});
// draw legend
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", width - 10)
.attr("y", 95)
.attr("width", 10)
.attr("height", 10)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", width - 24)
.attr("y", 100)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
});
I had a similar requirement that I was able to solve with help of others, but you are going to need more than just d3. Take a look at using JQuery to create dropdown options, and you can filter your data based on selections.

How to add numbers to bars with D3.js?

I've created a chart and it works fine, but I can't find out how to add numbers to columns. Numbers appear only if I hover the columns.
Tried different variants:
svg.selectAll("text").
data(data).
enter().
append("svg:text").
attr("x", function(datum, index) { return x(index) + barWidth; }).
attr("y", function(datum) { return height - y(datum.days); }).
attr("dx", -barWidth/2).
attr("dy", "1.2em").
attr("text-anchor", "middle").
text(function(datum) { return datum.days;}).
attr("fill", "white");
A link to my example: https://jsfiddle.net/rinatoptimus/db98bzyk/5/
Alternate idea to #gerardofurtado is to instead of a rect append a g, then group the text and rect together. This prevents the need for double-data binding.
var bars = svg.selectAll(".bar")
.data(newData)
.enter().append("g")
.attr("class", "bar")
// this might be affected:
.attr("transform", function(d, i) {
return "translate(" + i * barWidth + ",0)";
});
bars.append("rect")
.attr("y", function(d) {
return y(d.days);
})
.attr("height", function(d) {
return height - y(d.days) + 1;
})
.style({
fill: randomColor
}) // color bars
.attr("width", barWidth - 1)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
bars.append("text")
.text(function(d) {
return d.days;
})
.attr("y", function(d) {
return y(d.days);
})
.attr("x", barWidth / 2)
.style("text-anchor", "middle");
Updated fiddle.
When you do this:
svg.selectAll("text")
you are selecting text elements that already exist in your SVG. In an enter selection, it's important selecting something that doesn't exist:
svg.selectAll(".text")
.data(newData)
.enter()
.append("svg:text")
.attr("x", function(datum) {
return x(datum.name) + x.rangeBand()/2
})
.attr("y", function(datum) {
return y(datum.days) - 10;
})
.attr("text-anchor", "middle")
.text(function(datum) {
return datum.days;
})
.attr("fill", "white");
Here is your fiddle: https://jsfiddle.net/c210osht/
You could try using the d3fc bar series component, which allows you to add data-labels via the decorate pattern.
Here's what the code would look like:
var svgBar = fc.seriesSvgBar()
.xScale(xScale)
.yScale(yScale)
.crossValue(function(_, i) { return i; })
.mainValue(function(d) { return d; })
.decorate(function(selection) {
selection.enter()
.append("text")
.style("text-anchor", "middle")
.attr("transform", "translate(0, -10)")
.text(function(d) { return d3.format(".2f")(d); })
.attr("fill", "black");
});
Here's a codepen with the complete example.
Disclosure: I am a core contributor to the d3fc project!

How to position the legend in a d3 chart

How do I position the legend above and out of the chart?
I am working in this d3 example Grouped Bar Chart
Here is my PLUNKER but the legend can overlap the graph. Ideally I would like the legend above and out of the chart.
This is my code that I have to change. I don't understand why the 0 refers to the current position.
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
I can move the legend as follows: PLUNKER
.attr("transform", function(d, i) { return "translate(" + "-500," + i * 20 + ")"; }); which moves imore to the center.
I can then have the legend read from left to right as follows:
.attr("transform", function(d, i) { return "translate(" + (-700+i*100) + "," + 0 + ")"; }); I would be great if I could move this above and outside the chart as it still overlaps some of the graph.
EDIT1 PLUNKER
tks to an answer belwo. This is my attempt, which is above the chart as I would expect, but I would like the different series in the legend to appear closer together (there is too much white space). So how do I have the coloured rect and then the text beside it, but without the whitespace?
## the below is close but I am just guessing
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (-width) + "," + (-margin.top) + ")")
.attr('class','legendHolder')
var legend = legendHolder.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
legend.append("rect")
.attr("x", function(d,i){return (width +(150*i))})
.attr("width", 36)
.attr("height", 18)
//.style("text-anchor", "end")
.style("fill", color);
legend.append("text")
//.attr("x", width - 24)
.attr("x", function(d,i){return (width +(140*i))})
.attr("y", 9)
.attr("dy", ".35em")
//.style("text-anchor", "end")
.text(function(d) { return d; });
EDIT2 PLUNKER
This is the best I can do, but I fell I am jsut guessing, maybe I will revisit but in the meant time if anyone can beautifully explain it to me that would be greatly appreciated
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (-width) + "," + (-margin.top) + ")")
.attr('class','legendHolder')
var legend = legendHolder.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr('transform', function(d, i) { return "translate(" + -40*i + "," + 0 + ")"; })
.attr("width", 36)
legend.append("rect")
.attr("x", function(d,i){return (width +(150*i))})
.attr("width", 18)
.attr("height", 18)
//.style("text-anchor", "end") //"startOffset"="100%
//.style("startOffset","100%") //"startOffset"="100%
.style("fill", color);
legend.append("text")
//.attr("x", width - 24)
.attr("x", function(d,i){return (width +(150*i)+20)})
.attr("y", 9)
.attr("dy", ".35em")
//.style("text-anchor", "end")
.text(function(d) { return d; });
If you want the legend to be located outside of the graph, you just need to increase the size of the margin where you want it to be placed and translate it into position.
Right now you are positioning the individual parts of your legend based on the size of the <svg>. You can simplify this by creating a <g> that contains all of your legend elements and translating that to its desired position in the graph.
You'll need to play around with the values to get exactly what you want, but below are the values that would allow you to place the legend in the right margin.
var margin = {top: 20, right: 100, bottom: 30, left: 40};
var legendHolder = svg.append('g')
// translate the holder to the right side of the graph
.attr('transform', "translate(" + (margin.left + width) + ",0)")
var legend = legendHolder.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", 0)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", 0)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
The legend in the example appears on the right hand side, despite a transform of zero because the elements in the group have an x attribute of nearly the width of the frame (minus a small offset), pushing them to the right:
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
So an x transform of -500, about half your width, pulls it to the middle, as noted. Using a smaller x attribute for the legend elements might help make it clearer for setting up your legend (this is seen in the other answer), though as your comment notes, it isn't too hard to make it work as it is (just more confusing than needed).

Legends with paging in D3js charts

I have a large number of legends, but I want to display only 5 legends at a time. I want to have a button that displays the next 5 legends. Can anyone help me in implementing this?
I have implemented the pagination but in the end I am having two repeating legends.
My working code is here js fiddle.
var data=[
{
"age":"<5",
"population":2704659
},
{
"age":"5-10",
"population":4499890
},
{
"age":"10-13",
"population":6736433
},
{
"age":"14-16",
"population":2159981
},
{
"age":"16-18",
"population":3853788
},
{
"age":"18-22",
"population":8848383
},
{
"age":"22-30",
"population":8384390
},
{
"age":"30-44",
"population":14106543
},
{
"age":"45-64",
"population":8819342
},
{
"age":"≥65",
"population":800000
}
]
var width = 1060,
height = 600,
radius = 175,
color = d3.scale.category10(),
legendNo=4, // number of legends to display at a time
legendCount=0; //To store number of legends
//creating svg element and appending to body
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); //transform it to center of body
//creating start and end angle for each arc
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.population; });
//creating the arcs based on pie layout
var arc = d3.svg.arc() //inner arc with color
.outerRadius(radius)
.innerRadius(radius-70);
//calculate the total to display in hole
var total=0;
data.forEach(function(d) {
d.population;
total +=parseInt(d.population);
legendCount++;
});
//creating svg element for center text
var center_group = svg.append("svg:g")
.attr("class", "center_group")
.attr("transform", "translate(" + (width/2) + "," + (height/2) + ")");
//selecting all inner arcs and appending data
var g = svg.selectAll("arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc")
//giving colour to each inner arc and execute onClick function
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.age); })
//display text in the inner arcs
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.style("font-size", "14px")
.text(function(d) { return d.data.age; });
count = 0;
var p=0;
var viewdata = data.slice(p,p+legendNo);
var hidedata;
var temp;
//selecting all legend elements
var legend = svg.selectAll(".legend")
.data(viewdata).enter()
.append("g").attr("class", "legend")
//.attr("width", )
.attr("id", function() {
return count++;
})
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
//appending coloured rectangles to legend
svg.selectAll("rect")
.data(viewdata)
.enter().append("rect")
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
var prev=svg.append("svg:text")
.attr("id","prev")
.attr("x", width-1260)
.attr("y",height-385)
.attr("dy", "2.90em")
.attr("dx", "1.75em")
//.attr("stroke", "black")
//.style("fill","white")
// .style("text-anchor", "middle")
.style("font-size", "20px")
//.attr("width", 45).attr("height", 25)
.text("<|")
.on("click",onPrevClick)
var next=svg.append("svg:text")
.attr("id","next")
.attr("x", width-950)
.attr("y",height-385)
.attr("dy", "2.90em")
.attr("dx", "1.75em")
.style("font-size", "20px")
//.attr("stroke", "black")
//.style("fill","white")
//.attr("width", 45).attr("height", 25)
.text("|>")
.on("click",onNextClick)
function onNextClick()
{p+=legendNo;
if(p>=legendCount){
p-=legendNo;
viewdata = data.slice(p,legendCount);
//temp=legendNo-(legendCount-p);
//hidedata =data.slice(p-temp-2,p-2);
}
else{
viewdata = data.slice(p,p+legendNo);
//hidedata =data.slice(0,0);
}
svg.selectAll("rect")
.data(viewdata)
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
legend.select("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
}
function onPrevClick(){
p-=legendNo;
if(p<=0){
p=0;
}
viewdata = data.slice(p,p+legendNo);
svg.selectAll("rect")
.data(viewdata)
.attr("x", width/2-150)
.attr("y",5)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
.attr("width", 23).attr("height", 23)
.attr("transform", function(d, i) {
return "translate("+(-(width/2-30)+ i * 50)+"," + (height/2-50)+ ")";
})
.style("fill", function(d) {
return color(d.age);
});
legend.select("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
}
// giving text to legends
legend.append("text").attr("x", width/2-150)
.data(viewdata)
.attr("y", 15)
.attr("dy", "3.00em")
.attr("dx", "1.75em")
//.attr("transform", function(d, i) {return "rotate("+45*i+","+d.age+",200)";})
.attr("text-anchor", "middle").text(function(d) {
return d.age;
});
//displaying legend title
var legendTitle = svg.append("svg:text")
.attr("x", -(width/2-200))
.attr("y", height/2-25)
.style("font-size", "14px")
.style("text-decoration", "underline")
.text("Age Group");

Categories

Resources