I think this is a simple question but I'm not getting it. I have the following code for the x-axis of my bar chart, and I'm trying to access the x-value when the corresponding bar is clicked. I've tried selecting xAxis, x, and .domain, but I'm getting null values.
var x = d3.scale.ordinal()
.domain(['191','192','255','902'])
.rangeRoundBands([margin,w-margin], .1)
var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(3, 0)
svg.selectAll(".series")
.data(ratiodata)
.enter()
.append("g")
.classed("series",true)
.style("fill","url(#gradient)")
.selectAll("rect").data(Object)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x",function(d,i) { return x(x.domain()[i])})
.attr("y",function(d) { return y(d.y0)})
.attr("height",function(d) { return y(0)-y(d.size)})
.attr("width",x.rangeBand())
.on("click", function(d,i) {
//Clicking on the bar currently displays elements from another dataset.//
//Ratiodata is only used for displaying the bars//
});
You can get the value in the same way in that you're setting it to start with:
.on("click", function(d, i) {
console.log(x.domain()[i]);
});
Related
I want to add a toolkit that show the type of the disaster, which is the key of the stack datum, how can i get it?
The format of .csv file is like this: (Forgive me can not take pictures)
AllNaturalDisasters,Drought,Earthquake,ExtremeTemperature,ExtremeWeather,Flood,Impact,Landslide,MassMovementDry,VolcanicActivity,Wildfire,Year
5,2,null,null,1,1,null,null,null,1,null,1900
2,null,2,null,null,null,null,null,null,null,null,1901
Here I create a stack
var stack = d3.stack()
.keys(["Drought", "Earthquake", "ExtremeTemperature", "ExtremeWeather", "Flood", "Impact", "Landslide", "MassMovementDry", "VolcanicActivity", "Wildfire"]);
and then I pass it my data:var series = stack(dataset);. dataset is the all data from the csv file. Then I create a chart using stack-layout, like this:
var groups = svg.selectAll("g")
.data(series)
.enter()
.append("g")
.style("fill", function(d, i) {
return colors(i);
});
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]);
})
.attr("width", xScale.bandwidth())
.append("title")
.text(function (d) {
return d.data.Year;
});
The problem is right here:
.append("title")
.text(function (d) {
return d.data.Year;
});
I want to add a toolkit to show the type of the disaster, which is the key of this datum in series , how can I get it instead of the year?!
Each rectangle contains information on the column (year of disaster), but each g has information on the "row" (type of disaster).
The stack produces a nested array, the parent level (which we use to create the g elements) contains the key, or type of disaster
The child level represents the columns, which contains the year.
The grandchild level just contains individual rectangles.
So, we can get a key by selecting the parent g:
.append("title")
.text(function() {
var rect = this.parentNode; // the rectangle, parent of the title
var g = rect.parentNode; // the g, parent of the rect.
return d3.select(g).datum().key; // now we get the key.
})
Of course this could be simplified a bit, but I broke it out to comment it better.
This allows for more flexible sorting - rather than relying on fixed indexes.
Here it is using your data:
var csv = d3.csvParse(d3.select("pre").text());
var stack = d3.stack().keys(["Drought", "Earthquake", "ExtremeTemperature", "ExtremeWeather", "Flood", "Impact", "Landslide", "MassMovementDry", "VolcanicActivity", "Wildfire"]);
var series = stack(csv);
var colors = d3.scaleOrdinal()
.range(d3.schemeCategory10);
var xScale = d3.scaleBand()
.domain([0,1])
.range([0,300])
var yScale = d3.scaleLinear()
.domain([0,6])
.range([200,0]);
var svg = d3.select("svg");
var groups = svg.selectAll("g")
.data(series)
.enter()
.append("g")
.style("fill", function(d, i) {
return colors(i);
});
var rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]);
})
.attr("width", xScale.bandwidth())
.append("title")
.text(function (d) {
var rect = this.parentNode;
var g = rect.parentNode;
return d3.select(g).datum().key;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="300"></svg>
<pre>AllNaturalDisasters,Drought,Earthquake,ExtremeTemperature,ExtremeWeather,Flood,Impact,Landslide,MassMovementDry,VolcanicActivity,Wildfire,Year
5,2,0,0,1,1,0,0,0,1,0,1900
2,0,2,0,0,0,0,0,0,0,0,1901</pre>
Well, I have fixed this problem by a very 'low' method. I have created a simple function:
function getKeys(d) {
return series[parseInt(groups.selectAll("rect").data().indexOf(d) / series[0].length)].key;
}
Well, it so simple and crude, and I still want to know a more efficient method!!!
I have a plunker here - https://plnkr.co/edit/7Eg34HyhXY6M3UJccAFq?p=preview
Its a simple stacked bar chart
I wanted to be able to click on a color in the legend and update the chart to show just that bar only
Ive found a few examples online but they are a bit too complex - just needed a simple method if thats possible
let legendItems = legend.selectAll('li')
.data(legendKeys)
.enter()
.append('li');
legendItems.append('span')
.attr('class', 'rect')
.style('background-color', (d, i) =>{
return colors[i];
});
legendItems.append('span')
.attr('class', 'label')
.html((d) => {
return d
});
You should bind click event handler this way:
let legendItems = legend.selectAll('li')
.data(legendKeys)
.enter()
.append('li')
.on('click', handleLegendItemClick);
Handler-function should looks like this (pay attention on the comments):
function handleLegendItemClick(d, i) {
// change opacity to show active legend item
legendItems.filter(legendItem => legendItem !== d).style('opacity', 0.5);
legendItems.filter(legendItem => legendItem === d).style('opacity', 1);
// update domain of y scale and update tick on y axis
y.domain([0, stackedSeries[i][0].data[d]]);
yAxis.transition().call(d3.axisLeft(y));
// bind new data
let enter = rects.data([stackedSeries[i]])
.enter()
.append('rect');
// remove old rects
rects.exit();
// draw new rect
rects.merge(enter)
.attr('height', (d) => {
return height - y(d[0].data[d.key]);
})
.attr('y', (d) => {
return 0;
})
.style('fill', (d, i) => {
return colorScale(d);
});
}
Check this fork of your plnkr. (I also add color scale - colorScale for applying a color by legend key name, not by index as you did.)
const colorScale = d3.scaleOrdinal(colors);
colorScale.domain(legendKeys);
I am trying to create horizon chart to populate multi year data all at once to identify problem via visual representation but below code is not working, please help to correct it, similar code is working in section 2 in this post.
[1] How to apply color for path for horizon chart using d3js?
data=[0.2,1.3,1.1,1.3.0.9]
var color =
d3.scale.threshold()
.domain([0,1])
.range(["green", "yellow", "red"]);
path.enter().append("path")
.style("fill", function (d,i) { return color(i);})
.attr("transform", t0)
.attr("d", d0);
d3.transition(path)
.style("fill", function(d,i) { return color(i); })
.attr("transform", t1)
.attr("d", d1);
[2] However same is working for below code:
var color = d3.scale.threshold()
.domain([7, 9])
.range(["green","yellow","red"]);
var svg = d3.select('body')
.append('svg')
.attr('width',500)
.attr('height',200);
svg.selectAll('rect')
.data(d3.range(10))
.enter()
.append('rect')
.attr('x',function(d,i) { return i * 40; })
.attr('y',30)
.attr('width',30)
.attr('height',30)
.attr('fill',function(d,i) { return color(i); });
I am attempting to access the data index of a shape on mouseover so that I can control the behavior of the shape based on the index.
Lets say that this block of code lays out 5 rect in a vertical line based on some data.
var g_box = svg
.selectAll("g")
.data(controls)
.enter()
.append("g")
.attr("transform", function (d,i){
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+ 5))+")"
})
.attr("class", "controls");
g_box
.append("rect")
.attr("class", "control")
.attr("width", 15)
.attr("height", 15)
.style("stroke", "black")
.style("fill", "#b8b9bc");
When we mouseover rect 3, it transitions to double size.
g_box.selectAll("rect")
.on("mouseover", function(d){
d3.select(this)
.transition()
.attr("width", controlBoxSize*2)
.attr("height", controlBoxSize*2);
var additionalOffset = controlBoxSize*2;
g_box
.attr("transform", function (d,i){
if( i > this.index) { // want to do something like this, what to use for "this.index"?
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+5)+additionalOffset)+")"
} else {
return "translate("+(width - 100)+","+((controlBoxSize+5)+i*(controlBoxSize+5))+")"
}
})
})
What I want to do is move rect 4 and 5 on mouseover so they slide out of the way and do not overlap rect 3 which is expanding.
So is there a way to detect the data index "i" of "this" rect in my mouseover event so that I could implement some logic to adjust the translate() of the other rect accordingly?
You can easily get the index of any selection with the second argument of the anonymous function.
The problem here, however, is that you're trying to get the index in an anonymous function which is itself inside the event handler, and this won't work.
Thus, get the index in the event handler...
selection.on("mouseover", function(d, i) {
//index here ---------------------^
... and, inside the inner anonymous function, get the index again, using different parameter name, comparing them:
innerSelection.attr("transform", function(e, j) {
//index here, with a different name -----^
This is a simple demo (full of magic numbers), just to show you how to do it:
var svg = d3.select("svg");
var data = d3.range(5);
var groups = svg.selectAll("foo")
.data(data)
.enter()
.append("g");
var rects = groups.append("rect")
.attr("y", 10)
.attr("x", function(d) {
return 10 + d * 20
})
.attr("width", 10)
.attr("height", 100)
.attr("fill", "teal");
groups.on("mouseover", function(d, i) {
d3.select(this).select("rect").transition()
.attr("width", 50);
groups.transition()
.attr("transform", function(e, j) {
if (i < j) {
return "translate(40,0)"
}
})
}).on("mouseout", function() {
groups.transition().attr("transform", "translate(0,0)");
rects.transition().attr("width", 10);
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: don't do...
g_box.selectAll("rect").on("mouseover", function(d, i){
... because you won't get the correct index that way (which explain your comment). Instead of that, attach the event to the groups, and get the rectangle inside it.
I'm pretty sure d3 passes in the index as well as the data in the event listener.
So try
.on("mouseover", function(d,i)
where i is the index.
Also you can take a look at a fiddle i made a couple months ago, which is related to what you're asking.
https://jsfiddle.net/guanzo/h1hdet8d/1/
You can find the index usign indexOf(). The second argument in the event mouseover it doesn't show the index in numbers, it shows the data info you're working, well, you can pass this info inside indexOf() to find the number of the index that you need.
.on("mouseover", (event, i) => {
let index = data.indexOf(i);
console.log(index); // will show the index number
})
My goal is to remove a data point from my bar chart.
It will then update itself:
Update X and Y axis
Update the actual bar chart
Update the legend
Issues I am having:
When I exit().remove() the rectangles in my graph, the code also gets rid of the rectangles in the legend. When I try to enter() the rectangles in my legend, they do not appear. I am not sure what is happening here, but I am not being successful in adding and removing elements due to data changes. Any help would be appreciated.
Code snippet of where I think I am having issues:
This is the part that also deletes the rectangles in the legend (I am not sure if I should do this here or in the enter/update/delete part of the legend)
The code below is executed right after the user clicks the "delete all" button. My intent here is to only delete one bar (the one named "ALL") and update the chart.
//Select rectangles
var bars = svg.selectAll("rect")
.data(dataset, function(d) { return d.State; });
//Enter rectangles
bars.enter()
.append("rect")
.style("fill", function(d,i) { return color(i) })
.attr("x", function(d) { return xScale(d.State); })
.attr("y", function(d) { return yScale(d.CustomerCount) })
.attr("width", xScale.rangeBand()) //returns rangeRoundBands width
.attr("height", function(d) { return h - yScale(d.CustomerCount) });
//Update rectangles
bars.transition()
.duration(1000)
.style("fill", function(d,i) { return color(i) })
.attr("x", function(d) { return xScale(d.State); })
.attr("y", function(d) { return yScale(d.CustomerCount) })
.attr("width", xScale.rangeBand()) //returns rangeRoundBands width
.attr("height", function(d) { return h - yScale(d.CustomerCount) });
//Exit rectangles
bars.exit()
.transition()
.duration(500)
.attr("x", w)
.remove();
Here is entire the code:
http://plnkr.co/edit/Nue5bocQsI4E6D5wfNSP
Here is the slightly smaller code:
I tried to reduce the code as much as possible but it is still pretty big.
http://jsfiddle.net/aNQWV/
In the part where you update the data you say:
var bars = svg.selectAll("rect")
.data(dataset, function(d) { return d.State; });
The problem is that at this point the svg contains two kind of rects. One kind corresponds to the bars, and the other ones are part of the legend. This means that you are joining the new data to this mix of rects, while actually you wanted to join the new data with the bars only.
So, you need a more specific selector that targets only the bar rects. The typical approach is to add a class to those rects when you create them:
svg.selectAll(".bar")
.data(input)
.enter().append("rect")
.attr("class", "bar");
Then in the part where you update the data you would say:
var bars = svg.selectAll(".bar")
.data(dataset, function(d) { return d.State; });