I want to highlight a bar with April (value in x-axis) with a square box. but I am not getting a approach to do the same.
tried getting the co-ordinates of the respected bar, but unable to find a solution for the same
Unable to find the co-ordinates of the respective bar which I need to highlight.
what should be the approach for highlighting a bar with a square box in stacked bar chart d3.js v4
createStackedBarChart(130,300,10,60,20,45,"manager-line-graph-2");
function createStackedBarChart(height,width,top,right,bottom,left,id){
var margin = {top: top, right: right, bottom: bottom, left: left };
//console.log("margin"+margin);
var svg = d3.select("#"+id).append("svg"),
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom,
g = svg.attr("width", "100%")
.attr("height", height + margin.top + margin.bottom)
.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(0.2)
.align(5.0);
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(["#0000FF", "#00FFFF", "#81F781", "#F3F781", "#FE2E2E"]);
var data = [
{"Months": "Feb","Installation": 5,"Product": 10,"Payment": 15,"Billing": 20,"Outage": 25},
{"Months": "March","Installation": 6,"Product": 8,"Payment": 9,"Billing": 15,"Outage": 18},
{"Months": "April","Installation": 9,"Product": 12,"Payment": 24,"Billing": 17,"Outage": 14},
{"Months": "May","Installation": 9,"Product": 12,"Payment": 14,"Billing": 17,"Outage": 14},
{"Months": "June","Installation": 9,"Product": 12,"Payment": 15,"Billing": 11,"Outage": 10}
];
// fix pre-processing
var keys = [];
for (key in data[0]){
if (key != "Months")
keys.push(key);
}
console.log("value of keys are " + keys);
data.forEach(function(d){
d.total = 0;
keys.forEach(function(k){
d.total += d[k];
})
});
//data.sort(function(a, b) {
//return b.total - a.total;
//});
x.domain(data.map(function(d) {
return d.Months;
}));
y.domain([0, d3.max(data, function(d) {
return d.total;
})]).nice();
z.domain(keys);
g.append("g")
.selectAll("g")
.data(d3.stack().keys(keys)(data))
.enter().append("g")
.attr("fill", function(d) {return z(d.key);})
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) {
return x(d.data.Months);
})
.attr("y", function(d) {
return y(d[1]);
})
.attr("height", function(d) {
return y(d[0]) - y(d[1]);
})
.attr("width", x.bandwidth()-5);
g.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5));
g.append("g")
.attr("class", "axis")
.call(d3.axisLeft(y).ticks(5, "s"))
.append("text")
.attr("x", 2)
.attr("y", y(y.ticks(5).pop()))
.attr("dy", "0.32em")
.attr("fill", "#000")
.attr("font-weight", "bold")
.attr("text-anchor", "start")
var legend = g.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "end")
.selectAll("g")
.data(keys.slice().reverse())
.enter().append("g")
.attr("transform", function(d, i) {
return "translate(0," + i * 20 + ")";
});
//legend.append("rect")
//.attr("x", width + 20)
//.attr("width", 10)
//.attr("height", 10)
//.attr("fill", z);
legend.append("circle")
.attr("r",5)
.attr("cx", width+30)
.attr("cy", 0)
.attr("fill",z);
legend.append("text")
.attr("x", width + 88)
.attr("y", 3.5)
.attr("dy", "0.12em")
.text(function(d) {
return`enter code here` d;
});
}
One solution I have used previously is to create an invisible layer of bars for the same data. Then use the .on("mouseover", function...); to make the bars for that data visible again by changing the styles or the fill opacity.
Here is a sample bl.ock with what I mean. It is a grouped bar chart with ordinal scale, but the same could be applied to your data with some tweaking.
https://bl.ocks.org/Coola85/b05339b65a7f9b082ca210d307a3e469
Update May 5, 2018: upon further reading I see your issue is different than the solution I suggested. This link might give you a good starting point of how to use an if statement to selectively highlight particular data.
so you could use the following style for your rect
.style ("fill", function(d) {
if (d.Months === "April") {return "red"} // <== Add these
else { return "black" }
})
I am trying to create a horizontal graph legend in D3.js. I am using a group element (g) as a container for all the legends and the individual legends (text) are also each wrapped inside a "g" element. The result is that the individual legends are stacked on top of each other rather than spaced out.
I have tried changing the x attribute on the legends and also transform/translate. Whilst the DOM shows that the x values are applied the legends don't move. So if the DOM shows the legend / g element is positioned at x = 200 it is still positioned at 0.
I have spent two days trying to solve this and probably looked at over 50 examples including anything I could find on StackExchange.
Below code is my latest attempt. It doesn't through any error and the x values are reflected in the DOM but the elements just won't move.
I have included the code covering the relevant bits (but not all code).
The legend container is added here:
/*<note>Add container to hold legends. */
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", Width)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
There is then a loop through a json array of objects:
var PrevElemLength = 0;
/*<note>Loop through each data series, call the Valueline variable and plot the line. */
Data.forEach(function(Key, i) {
/*<note>Add the metric line(s). */
Svg.append("path")
.attr("class", "line")
.attr("data-legend",function() { return Key.OriginId })
/*<note>Iterates through the data series objects and applies a different color to each line. */
.style("stroke", function () {
return Key.color = Color(Key.UniqueName); })
.attr("d", Valueline(Key.DataValues));
/*<note>Add a g element to the legend container. */
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function (d, i) {
if (i === 0) {
return "translate(0,0)"
} else {
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength) + ",0)"
}
});
/*<note>Adds a rectangle to pre-fix each legend. */
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function () {
return Key.color = Color(Key.UniqueName); });
/*<note>Adds the legend text. */
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
/*.attr("y", NetHeight + (Margin.bottom/2)+ 10) */
.attr("class", "legend text")
.style("fill", function () {
return Key.color = Color(Key.UniqueName); })
.text(Key.UniqueName);
Here is a screen shot of what the output looks like:
enter image description here
Any help on how to create a horizontal legend (without over lapping legends) would be much appreciated. Chris
The problem is you are using local variables d and i as function parameters while setting the transform attribute. Parameter i in local scope overrides the actual variable. The value of local variable i would be always zero as there is no data bind to that element.
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function (d, i) { //Remove i
if (i === 0) {
return "translate(0,0)"
} else {
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength) + ",0)"
}
});
I have also made slight updates to the code for improvements.
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", 500)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
var PrevElemLength = 0;
var Data = [{
OriginId: 1,
UniqueName: "Some Long Text 1"
}, {
OriginId: 2,
UniqueName: "Some Long Text 2"
}];
/*<note>Loop through each data series, call the Valueline variable and plot the line. */
var Color = d3.scale.category10();
Data.forEach(function(Key, i) {
/*<note>Add a g element to the legend container. */
var Legend = LegendCanvas.append("g")
.attr("class", "legend container")
.attr("transform", function() {
if (i === 0) {
return "translate(0,0)"
} else {
var marginLeft = 5;
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength + marginLeft) + ",0)"
}
});
/*<note>Adds a rectangle to pre-fix each legend. */
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function() {
return Key.color = Color(Key.UniqueName);
});
/*<note>Adds the legend text. */
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
.attr("dy", "0.4em")
.attr("class", "legend text")
.style("fill", function() {
return Key.color = Color(Key.UniqueName);
})
.text(Key.UniqueName);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg height=500 width=500></svg>
The d3 way of implementation(Using data binding) would be as follows
var LegendCanvas = d3.select("svg")
.append("g")
.attr("class", "legend canvas")
.attr("x", 0)
.attr("y", 0)
.attr("width", 500)
.style("fill", "#ffcccc")
.attr("transform", "translate(0,15)");
var Data = [{
OriginId: 1,
UniqueName: "Some Long Text 1"
}, {
OriginId: 2,
UniqueName: "Some Long Text 2"
}];
var Color = d3.scale.category10();
var Legend = LegendCanvas.selectAll(".legend")
.data(Data)
.enter()
.append("g")
.attr("class", "legend container");
Legend.append("rect")
.attr("width", 5)
.attr("height", 5)
.style("fill", function(Key) {
return Key.color = Color(Key.UniqueName);
});
Legend.append("text")
.attr("x", function() {
return this.parentNode.getBBox().width + 5;
})
.attr("dy", "0.4em")
.attr("class", "legend text")
.style("fill", function(Key) {
return Key.color = Color(Key.UniqueName);
})
.text(function(Key){ return Key.UniqueName });
var PrevElemLength = 0;
Legend.attr("transform", function(d, i) {
if (i === 0) {
return "translate(0,0)"
} else {
var marginLeft = 5;
PrevElemLength += this.previousElementSibling.getBBox().width;
return "translate(" + (PrevElemLength + marginLeft) + ",0)"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width=500 height=500></svg>
Try this :
//Legend
var legend = vis.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("image")
.attr("x", 890)
.attr("y", 70)
.attr("width", 20)
.attr("height", 18)
.attr("xlink:href",function (d) {
return "../assets/images/dev/"+d+".png";
})
legend.append("text")
.attr("x", 910)
.attr("y", 78)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) {
return d;
});
I am trying to visualize a dataset to see major players in the pangolin trade. I want each country to be part of a categorical scale on the y-axis, and the x-axis is the date, and I want each trade instance to be represented as a circle, with x being the date it happened, y being the position for that particular country, and r being the number of instances accumulated so far (represented in my data as r). Basically my data is from 2010 to 2016, and includes the country name where pangolins are either imported to or exported from, the count (accumulated trade instances up until this date), and the date (yyyy-mm-dd).
Below is part of my data, which I store in the variable trade:
[{"date":"2010-10-22","country":"Thailand","rank":4,"count":1},{"date":"2010-10-28","country":"Malaysia","rank":2,"count":1},{"date":"2010-11-8","country":"Thailand","rank":4,"count":2},{"date":"2010-11-18","country":"Nepal","rank":7,"count":1},{"date":"2010-11-22","country":"China","rank":5,"count":1},{"date":"2010-11-22","country":"China","rank":5,"count":2},{"date":"2010-11-27","country":"India","rank":1,"count":1},{"date":"2010-11-28","country":"India","rank":1,"count":2},{"date":"2010-11-28","country":"India","rank":1,"count":3},{"date":"2010-11-30","country":"India","rank":1,"count":4},{"date":"2010-12-17","country":"Malaysia","rank":2,"count":2},{"date":"2010-12-22","country":"Vietnam","rank":3,"count":1},{"date":"2011-01-3","country":"Nepal","rank":7,"count":2},{"date":"2011-02-12","country":"Myanmar","rank":8,"count":1},{"date":"2011-02-25","country":"Malaysia","rank":2,"count":3},{"date":"2011-02-26","country":"Malaysia","rank":2,"count":4},{"date":"2011-03-2","country":"South Africa","rank":18,"count":1},{"date":"2011-03-2","country":"Rwanda","rank":35,"count":1},{"date":"2011-03-2","country":"Mozambique","rank":22,"count":1},{"date":"2011-03-2","country":"Kenya","rank":12,"count":1},{"date":"2011-03-3","country":"China","rank":5,"count":3},{"date":"2011-02-21","country":"Vietnam","rank":3,"count":2},{"date":"2011-03-24","country":"Malaysia","rank":2,"count":5},{"date":"2011-04-4","country":"Malaysia","rank":2,"count":6},{"date":"2011-03-25","country":"India","rank":1,"count":5},{"date":"2011-03-26","country":"Malaysia","rank":2,"count":7},{"date":"2011-04-2","country":"Nepal","rank":7,"count":3},{"date":"2011-04-20","country":"Thailand","rank":4,"count":3},{"date":"2011-05-11","country":"China","rank":5,"count":4},{"date":"2011-05-11","country":"China","rank":5,"count":5},{"date":"2011-05-26","country":"Indonesia","rank":6,"count":1},{"date":"2011-05-26","country":"India","rank":1,"count":6},{"date":"2011-05-29","country":"Indonesia","rank":6,"count":2},{"date":"2011-06-6","country":"India","rank":1,"count":7},{"date":"2011-06-7","country":"Mozambique","rank":22,"count":2},{"date":"2011-06-5","country":"India","rank":1,"count":8},{"date":"2011-06-12","country":"Malaysia","rank":2,"count":8},{"date":"2011-06-13","country":"Singapore","rank":21,"count":1},{"date":"2011-06-14","country":"Malaysia","rank":2,"count":9},{"date":"2011-06-17","country":"India","rank":1,"count":9},{"date":"2011-06-19","country":"India","rank":1,"count":10},{"date":"2011-06-26","country":"Thailand","rank":4,"count":4},{"date":"2011-06-30","country":"India","rank":1,"count":11},{"date":"2011-07-4","country":"Malaysia","rank":2,"count":10},{"date":"2011-07-5","country":"Zimbabwe","rank":14,"count":1},{"date":"2011-07-12","country":"Indonesia","rank":6,"count":3},{"date":"2011-07-18","country":"Indonesia","rank":6,"count":4},{"date":"2011-07-27","country":"Nepal","rank":7,"count":4},{"date":"2011-08-16","country":"Nepal","rank":7,"count":5},{"date":"2011-08-19","country":"Namibia","rank":33,"count":1},{"date":"2011-08-23","country":"India","rank":1,"count":12},{"date":"2010-09-17","country":"Myanmar","rank":8,"count":2},{"date":"2011-09-1","country":"Zimbabwe","rank":14,"count":2},{"date":"2011-09-13","country":"Indonesia","rank":6,"count":5},{"date":"2011-09-13","country":"Malaysia","rank":2,"count":11},{"date":"2011-09-13","country":"Myanmar","rank":8,"count":3},{"date":"2011-09-21","country":"Malaysia","rank":2,"count":12},{"date":"2011-09-26","country":"Thailand","rank":4,"count":5},{"date":"2011-09-30","country":"Indonesia","rank":6,"count":6},{"date":"2011-10-1","country":"Sri Lanka","rank":19,"count":1},{"date":"2011-10-6","country":"India","rank":1,"count":13},{"date":"2011-10-7","country":"India","rank":1,"count":14},{"date":"2011-10-18","country":"Indonesia","rank":6,"count":7},{"date":"2011-10-18","country":"Indonesia","rank":6,"count":8},{"date":"2011-10-18","country":"Indonesia","rank":6,"count":9},{"date":"2011-10-22","country":"India","rank":1,"count":15},{"date":"2011-10-24","country":"India","rank":1,"count":16},{"date":"2011-11-28","country":"United States","rank":32,"count":1},{"date":"2011-12-15","country":"Vietnam","rank":3,"count":3},{"date":"2011-12-27","country":"Thailand","rank":4,"count":6},{"date":"2012-01-4","country":"Philippines","rank":15,"count":1},{"date":"2012-01-5","country":"Kenya","rank":12,"count":2},{"date":"2012-01-6","country":"Philippines","rank":15,"count":2},{"date":"2012-01-17","country":"Philippines","rank":15,"count":3},{"date":"2012-01-24","country":"China","rank":5,"count":6},{"date":"2012-02-22","country":"Malaysia","rank":2,"count":13},{"date":"2012-03-1","country":"Malaysia","rank":2,"count":14},{"date":"2012-03-19","country":"Pakistan","rank":11,"count":1},{"date":"2012-03-21","country":"Malaysia","rank":2,"count":15},{"date":"2012-03-23","country":"Vietnam","rank":3,"count":4},{"date":"2012-04-27","country":"Vietnam","rank":3,"count":5},{"date":"2012-04-23","country":"Belgium","rank":31,"count":1},{"date":"2012-06-7","country":"Thailand","rank":4,"count":7},{"date":"2012-06-7","country":"Thailand","rank":4,"count":8}];
and here is my code:
var margin = {left:120, top:20, right:0, bottom:50};
var width = 1000;
var height = 800;
var data=trade;
var tradeByCountry = d3.nest()
.key(function(d) { return d.country; })
.entries(trade);
console.log(tradeByCountry);
tradeByCountry.forEach(function(country){
country['number']=country.values.length;
console.log(country);
});
var country_colors = ["#393b79","#5254a3", '#6b6ecf', '#9c9ede', '#637939', '#8ca252','#b5cf6b','#cedb9c',
'#8c6d31','#bd9e39','#e7ba52','#e7cb94','#843c39','#ad494a','#d6616b','#e7969c','#7b4173','#a55194',
'#ce6dbd','#de9ed6', '#9467bd', '#c5b0d5','#3182bd', '#6baed6','#17becf','#9edae5','#e6550d','#fd8d3c','#fdae6b',
'#31a354','#74c476','#a1d99b','#d62728','#ff9896','#7f7f7f','#c7c7c7'];
var colors = d3.scale.ordinal()
.domain(d3.range(tradeByCountry.length))
.range(country_colors);
tradeByCountry.sort(function(x, y){
return d3.descending(x.number, y.number);
})
var countriesArray = [];
tradeByCountry.forEach(function(country){
countriesArray.push(country.key);
});
console.log(countriesArray);
var x = d3.time.scale()
.rangeRound([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var timeFormat = d3.time.format("%Y-%m-%d");
x.domain([timeFormat.parse('2010-10-22'),timeFormat.parse('2016-12-30')]);
y.domain(countriesArray);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("date");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "translate(35,-25)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("countries");
svg.selectAll(".dot")
.data(tradeByCountry)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 7)
.attr("cx", function(d) { return 0; })
.attr("cy", function(d,i) { return (height/tradeByCountry.length)*i; })
.style("fill", function(d,i) { return colors(i); });
svg.selectAll("text.labels")
.data(tradeByCountry)
.enter()
.append("text")
.text(function(d) {return d.key})
.attr("x", 0)
.attr("y", function(d,i) { return (height/tradeByCountry.length)*i; })
.attr("transform", "translate(-120,5)")
.attr("fill",function(d,i) { return colors(i); });
svg.selectAll('.line')
.data(tradeByCountry)
.enter().append('line')
.attr("x1", 0) // x position of the first end of the line
.attr("y1",function(d,i) { return (height/tradeByCountry.length)*i; })
.attr("x2", width) // x position of the second end of the line
.attr("y2", function(d,i) { return (height/tradeByCountry.length)*i; })
.style("stroke",function(d,i) { return colors(i); })
.attr("id", function(d){return d.key});
//PROBLEM HERE!!!!!!!!!!!
var g = svg
.selectAll("g")
.data(tradeByCountry)
.enter().append("g")
.selectAll(".dot")
.data(function(d) {return d.values;})
.enter().append("circle")
.attr("id", function(d){ return d.count;})
.attr("r", function(d){return d.count;})
.attr("cx", function(d,i) { console.log (d.date);return (x(timeFormat.parse(d.date))); })
.attr("cy", function(d,i) { return (height/tradeByCountry.length)*i; })
.style("fill", function(d,i) { return colors(i); });
I am having trouble specifically at the very last part of my code, where I can't get all the circles to draw and not in the correct r, as I would like r to increase as the trade accumulates over time for a country, instead it seems that r is different according to country:
Two things, firstly in your example .selectAll("g") at the point you mark as having the error was picking up g elements in the axes, so the first few countries weren't getting their data displayed. This doesn't appear to be the case in your screenshot but it was happening given the code you posted, so I just qualified those gs with the .country class.
The second thing, and what was causing your specific problem was this line:
.attr("cy", function(d,i) { return (height/tradeByCountry.length)*i; })
I'm guessing you thought the index variable i here was still tied to the tradeByCountry array, which it would have been after selectAll("g.country").data(tradebyCountry) but at this point we've now made a nested selection .selectAll(".dot") on d.values for each country's data so i is now indexing those list of values. So that line of code above will take the values for each country and separate them all vertically, always starting from the top row - when in fact you want them on the same line, just separated by country.
What you wanted (I = India, M = Malaysia, T = Thailand)
I--- I0 I1 I2
M--- M0M1 M2
T--- T0 T1 T2
What you were getting
I--- I0 M0 T0
M--- M1 I1 T1
T--- I2 T2 M2
This was also hidden by the fact .attr("fill") also had the same mistake so the colours on each row were consistent (I've used d.rank instead to fix it).
Solution 1: To make sure the values stay on the same correct line use i before you do the nested selection so it still refers to the countries like this, which will offset each g element by the correct amount:
.attr("transform", function(d,i) {
return "translate(0,"+(height/tradeByCountry.length)*i+")";
})
and simply set cy to be zero for everything you add to this g element and they'll all be in a straight line (and on the right line)
Full code at the PROBLEM HERE! stage:
var g = svg
.selectAll("g.country")
.data(tradeByCountry)
.enter().append("g")
.attr("class", "country")
.attr("transform", function(d,i) {
return "translate(0,"+(height/tradeByCountry.length)*i+")";
})
.selectAll(".dot")
.data(function(d) {return d.values;})
.enter().append("circle")
.attr("class", "dot")
.attr("id", function(d){ return d.country+"_"+d.date+"_"+d.count;})
.attr("r", function(d){ return d.count;})
.attr("cx", function(d,i) { console.log (d.date);return (x(timeFormat.parse(d.date))); })
.attr("cy", function(d,i) { return 0; })
.style("fill", function(d,i) { return colors(d.rank); });
http://jsfiddle.net/d4typ567/1/
Solution 2: It's also the case that d3 maintains a parent index variable for nested selections (usually denoted ii) that can be passed into most d3 .attr and .style functions, so you could do .attr("cy", function(d,i, ii) { return (height/tradeByCountry.length)*ii; }) instead, but offsetting the g element is only 1 operation whereas this is done for every circle. Remember to do the same for the color (fill) function.