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'm trying to create dynamic treemap graphic with lovely d3.js library.
Here are sample of my code
element = d3.select(element[0]);
var margin = 10,
width = parseInt(element.style('width')) - margin * 2,
height = parseInt(element.style('height')) - margin * 2;
var color = d3.scale.category10();
var canvas = d3.select('.treemap').append('svg')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(-.5,-.5)')
.style('margin', margin);
var treemap = d3.layout.treemap()
.size([width, height])
.value(function(d) { return d.value; })
.sticky(false);
function redraw(data) {
d3.selectAll('.cell').remove();
var treemapData = {};
treemapData.children = data.map(function(d) {
return {
name: d.name,
value: d.value
};
});
var leaves = treemap(treemapData);
var cells = canvas.selectAll("g")
.data(leaves);
var cellsEnter = cells.enter()
.append('rect')
.attr('class', 'cell')
.attr('x', function(d) { return d.x; })
.attr('y', function(d) { return d.y; })
.attr('width', function(d) { return d.dx; })
.attr('height', function(d) { return d.dy; })
.attr('fill', function(d) { return d.children ? null : color(d.name); })
.attr('stroke', "#fff")
.style('fill-opacity', 0);
console.log(cells.exit(), cells.enter());
}
And here I have stucked.
console.log() shows that whole new data are enter(), and none are exit() !
Input data presents like
[{value: 590, name:"A1"}, {...}, ...]
without root object field, so that's why I remapped data in treemapData object.
Š¢hanks that you at least spent your time for reading this post so far, hope you have any suggestions.
UDP. you can check working version of my code here: https://jsfiddle.net/qtbfm08k/
The following works:
remove d3.selectAll('.cell').remove();
use the code below
See the fiddle: https://jsfiddle.net/b6meLedn/4/
var cells = canvas.selectAll('.cell') //select all cells
.data(leaves); //map the data
cells.exit().remove(); //remove old extra elements
cells.enter()
.append('rect') //create new rectangles as necessary
.attr('class', 'cell')
cells //take all cells (old cells that have new data+extra new cells)
.attr('x', function(d) { return d.x; })
...
I'm having trouble understanding when and how to use nested data.
In this example I have a CSV with names ('Name') and locations ('starting point'). By assigning keys to the locations I am able to make a dropdown containing them all, and I would like to use this to filter the names associated with each location.
However I am unable to find the data's values, in this case 'd.Name'
Here inside the update function I have tried to access the 'values' on the data join.
var adventurer = canvas
.selectAll(".adventurer")
.data(function(d) {
return d.values;
})
Ive also tried creating an extra data variable but thats not working for me either.
Sorry I can't make a jsfiddle but here is a plunk
DATA
,,Name,First names,s,r,Nat,born,starting point,starting date,arrival date,days,km,Assist,Support,Style,note,arrival date 2
1,1,KAGGE,Erling,,,Nor,1/15/1963,Berkner Island,11/18/1992,1/7/1993,50,appr. 1300,n,n,solo,first solo unassisted,
2,2,ARNESEN,Liv,f,,Nor,6/1/1953,Hercules Inlet,11/4/1994,12/24/1994,50,1130,n,n,solo,first woman unassisted,
3,3,HAUGE,Odd Harald,,,Nor,1956,Berkner Island,11/4/1994,12/27/1994,54,appr. 1300,n,n,,,
HTML
<div id="menu"></div>
<div id="chart"></div>
SCRIPT
d3.csv("data.csv", function(csv_data) {
var data = d3.nest()
.key(function(d) {
return d['starting point'];})
.sortKeys(d3.ascending)
.entries(csv_data)
console.log(data);
//create dropdown select
var list = d3.select("#menu").append("select")
list.selectAll("option")
.data(data)
.enter().append("option")
.attr("value", function(d) {
return d.key;
})
.text(function(d) {
return d.key;
});
//chart config
var w = 375,
h = 1000;
var canvas = d3.select('#chart')
.append('svg')
.attr('width', w)
.attr('height', h)
.append('g')
.attr('transform', 'translate (0,50)');
//function (bind, add, remove, update)
function updateLegend(data) {
var adventurer = canvas
.selectAll(".adventurer")
.data(function(d) {
return d.values;
})
var adventurerEnter = adventurer
.enter().append("g")
.attr('class', 'adventurer');
adventurerEnter
.append("text")
.attr('class', 'name')
.attr('x', 0);
adventurer.select('.name')
.text(function(d, i) {
return d.Name;
})
.attr('y', function(d, i) {
return i * 30;
});
// remove old elements
adventurer.exit().remove();
};
// generate initial legend
updateLegend(data);
});
// handle on click event
d3.select('#menu')
.on('change', function() {
var data = eval(d3.select(this).property('value'));
console.log(data)
updateLegend(data);
});
You need to display both the locations and the names. You have a nest (in the plunk but not in your question) of location/name, but you also need a distinct list of names, or possibly a list of name/location:
var locations = d3.nest()
.key(function(d) {return d['starting point'];})
.sortKeys(function(a,b){ return a > b && 1 || b > a && -1 || 0})
.key(function(d) {return d.Name;})
.entries(csv_data)
var names = d3.nest()
.key(function(d) {return d.Name;})
.sortKeys(function(a,b){ return a > b && 1 || b > a && -1 || 0})
.key(function(d) {return d['starting point'];})
.entries(csv_data)
Then you have to display your names however you want. Then you need an .on('change', function()...) handler (or on click or whatever fits your needs) that actually filters the names wherever those are displayed.
I also fixed your sorting. d3.ascending is for numbers, not strings.
I like dcjs, http://bl.ocks.org/d3noob/6584483 but the problem is I see no labels anywhere for the line chart (Events Per Hour). Is it possible to add a label that shows up just above the data point, or even better, within a circular dot at the tip of each data point?
I attempted to apply the concepts in the pull request and came up with:
function getLayers(chart){
var chartBody = chart.chartBodyG();
var layersList = chartBody.selectAll('g.label-list');
if (layersList.empty()) {
layersList = chartBody.append('g').attr('class', 'label-list');
}
var layers = layersList.data(chart.data());
return layers;
}
function addDataLabelToLineChart(chart){
var LABEL_FONTSIZE = 50;
var LABEL_PADDING = -19;
var layers = getLayers(chart);
layers.each(function (d, layerIndex) {
var layer = d3.select(this);
var labels = layer.selectAll('text.lineLabel')
.data(d.values, dc.pluck('x'));
labels.enter()
.append('text')
.attr('class', 'lineLabel')
.attr('text-anchor', 'middle')
.attr('x', function (d) {
return dc.utils.safeNumber(chart.x()(d.x));
})
.attr('y', function (d) {
var y = chart.y()(d.y + d.y0) - LABEL_PADDING;
return dc.utils.safeNumber(y);
})
.attr('fill', 'white')
.style('font-size', LABEL_FONTSIZE + "px")
.text(function (d) {
return chart.label()(d);
});
dc.transition(labels.exit(), chart.transitionDuration())
.attr('height', 0)
.remove();
});
}
I changed the "layers" to be a new group rather than using the existing "stack-list" group so that it would be added after the data points and therefore render on top of them.
Here is a fiddle of this hack: https://jsfiddle.net/bsx0vmok/
Following the County Bubbles example, it's easy to add a bubble for each county. This is how it is added in the example:
svg.append("g")
.attr("class", "bubble")
.selectAll("circle")
.data(topojson.feature(us, us.objects.counties).features
.sort(function(a, b) { return b.properties.population - a.properties.population; }))
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("r", function(d) { return radius(d.properties.population); })
.append("title")
.text(function(d) {
return d.properties.name
+ "\nPopulation " + formatNumber(d.properties.population);
});
However, rather than using a variable from the json file (population), I need to update the radii according to a variable which dynamically changes (so I cannot put it in the json file beforehand as was done in the example). I call updateRadii() when a county is clicked, which needs access to the FIPS.
var currFIPS,
flowByFIPS;
var g = svg.append("g");
queue()
.defer(d3.json, "us.json")
.defer(d3.csv, "census.csv", function(d) {
return {
work: +d.workplace,
home: +d.residence,
flow: +d.flow
}
})
.await(ready);
function ready(error, us, commute) {
// Counties
g.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("path")
.attr("d", path)
.on("click", function(d) {
// Get FIPS of selected county
currFIPS = d.id;
// Filter on selected county (i.e., grab
// people who work in the selected county)
var data = commute.filter(function(d) {
return d.work == currFIPS;
});
// Create d3.map for where these people live
flowByFIPS = d3.map(data, function(d) {
return d.home;
});
// Update radii at "home" counties to reflect flow
updateRadii();
});
// Bubbles
g.append("g")
.attr("class", "counties")
.selectAll("circle")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("circle")
.attr("id", function(d) { return d.id; })
.attr("transform", function(d) {
return "translate(" + path.centroid(d) + ")";
})
.attr("r", 0); // invisible before a county is clicked
}
function updateRadii() {
svg.selectAll(".counties circle")
.transition()
.duration(300)
.attr("r", function(d) {
return flowByFIPS.get(d.id).flow
});
}
According to the error code, I believe that the circles do not have an id (FIPS code) attached. How do I get them to have an id? (I tried nesting the circle with the path using .each as explained in this answer, but could not get it working.)
Note that the above code works for updating fill on paths (rather than circles). For example, sub updateRadii(); for updateFill(); with the function as:
function updateFill() {
svg.selectAll(".counties path")
.transition()
.duration(300)
.attr("fill", function(d) {
return flowByFIPS.get(d.id).color; // e.g., "#444"
});
}
The problem here is that you don't supply d3 with data in the update function. I will recommend you update the data loaded from the file on the clicks, and from there you update the svg.
var update = function() {
g.selectAll(".country")
.data(data)
.attr("r", function(d) { return d.properties.flow_pct });
};
var data = topojson.feature(us, us.objects.counties).features;
data.forEach(function(x) { x.properties.flow_pct = /* calc the value */; })
g.append("g")
.attr("class", "counties")
.selectAll(".country")
.data(data)
.enter()
.append("circle")
.attr("class", "country")
.attr("transform", function(d) {
return "translate(" + path.centroid(d) + ")";
})
.on("click", function(d) {
// more code
data.forEach(function(x) { x.properties.flow_pct = /* calc the NEW value */; })
update();
});
update();
I've tried to use as much as the same code as before, but still trying to straiten it a bit out. The flow is now more d3-like, since the update function works on the changed data.
Another plus which this approach is both first render and future updates use the same logic for finding the radius.
It turns out to be an obvious solution. I forgot to check for the cases where a flow doesn't exist. The code above works if updateRadii() is changed to:
function updateRadii() {
svg.selectAll(".counties circle")
.transition()
.duration(300)
.attr("r", function(d) {
if (currFIPS == d.id) {
return 0;
}
var county = flowByFIPS.get(d.id);
if (typeof county === "undefined") {
return 0;
} else {
return county.flow;
}
});
}