This is similar to a problem that I asked before. However instead of a bar chart, I'm trying to display the totals for a row chart.
I've tried to adjust the code accordingly, but my familiarity with JS is pretty low. Below is my attempt, any help would be greatly appreciated.
genderChart.on('renderlet', function (chart) {
var rowData = [];
var rows = chart.selectAll('.row').each(function (d) {
rowsData.push(d);
});
//Remove old values (if found)
d3.select(rows[0][0].parentNode).select('#inline-labels').remove();
//Create group for labels
var gLabels = d3.select(bars[0][0].parentNode).append('g').attr('id', 'inline-labels');
for (var i = rows[0].length - 1; i >= 0; i--) {
var b = rows[0][i];
gLabels.append("text")
.text(d3.format(",f")(rowsData[i].data.value))
.attr('x', +b.getAttribute('x') + (b.getAttribute('width') + 20)
.attr('y', +b.getAttribute('y'))
.attr('text-anchor', 'middle')
.attr('fill', 'black');
}
});
Right now there's no error in the console, so it's being render correctly...somwhere. So far the text does not show up anywhere near my row chart. Thanks!
As an alternative to your approach, you could try using renderTitleLabel.
Something like this:
genderChart
.title(function(d) {
return d.value; // or your custom value accessor
})
.renderTitleLabel(true)
.titleLabelOffsetX(10); // optional offset from the right side of the chart
(This is available in the 2.0 betas, I am not sure about previous versions.)
Related
I am working on a widget that shows several D3 bar charts with different values, one after the other, in a sliding carousel.
When the page loads, the bar chart animate as it should, but when the page goes on to the next chart - whether it be on click or by itself - I would like it to restart the animation again each time.
I have tried calling animateChart() in the console but this doesn't work.
I am looking for a function that I can call from the console or from another function, like animateChart(), that will reload the D3 bar chart animation.
Here is a link to my widget:
http://jsfiddle.net/alocdk/oa5tg1qu/1/
I've found where you could enhance your animateChart function.
In fact you were modifying only data that were enterring your graph.
By calling :
d3.select(svg)
.selectAll("rect")
.data(data)
.enter().append("rect")
[...]
Everything following this, will only apply on the new data.
You may want to read these to understand the pattern to follow with data update in D3.
General Update Pattern, I
General Update Pattern, II
General Update Pattern, III
Here is my shot now http://jsfiddle.net/uknynmqa/1/
I've removed the loop you were doing on all your svg, because I assumed you wanted to only animate the current one.
And your function is updating all of the data, and not only those enterring thanks to :
// Update the data for all
var join = d3.select(svg)
.selectAll("rect")
.data(data);
// Append new data.
join.enter().append("rect")
.attr("class", function (d, i) {
var low = ""
i == minIndex ? low = " low" : "";
return "bar" + " " + "index_" + i + low;
})
// Update everyone.
join.attr("width", barWidth)
.attr("x", function (d, i) {
return barWidth * i + barSpace * i;
})
.attr("y", chartHeight)
.attr("height", 0)
.transition()
.delay(function (d, i) {
return i * 100;
})
.attr("y", function (d, i) {
return chartHeight - y(d);
})
.attr("height", function (d) {
return y(d);
});
D3 is following a really specific data update pattern.
Depending on what you want to do, you can follow this. It's up to you what you want to animate or not.
// Link data to your graph and get the join
var join = svg.selectAll('rect')
.data(data);
// Update data already there
join.attr('x', 0);
// Append the new data
join.enter().append('rect')
.attr('x', 0);
// Remove exiting elements not linked to any data anymore
join.exit().remove();
// Update all resulting elements
join.attr('x', 0);
I'm trying to understand how to use with dimplejs but the result is not what i ment.
JSFiddleCode
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://dimplejs.org/dist/dimple.v2.0.0.min.js"></script>
<script type="text/javascript">
var svg = dimple.newSvg("#chartContainer", 590, 400);
d3.csv("carsData.csv", function (data) {
// change string (from CSV) into number format
data.forEach(function(d) {
if(d["Sports Car"]==1)
d.Category = "Sports Car";
else if(d["SUV"]==1)
d.Category = "SUV";
else
d.Category = "Other";
d.HP = +d.HP;
d["Engine Size (l)"] = +d["Engine Size (l)"];
});
// Latest period only
//dimple.filterData(data, "Date", "01/12/2012");
// Create the chart
var myChart = new dimple.chart(svg, data);
myChart.setBounds(60, 30, 420, 330)
// Create a standard bubble of SKUs by Price and Sales Value
// We are coloring by Owner as that will be the key in the legend
myChart.addMeasureAxis("x", "HP");
myChart.addMeasureAxis("y", "Engine Size (l)");
myChart.addSeries("Category", dimple.plot.bubble);
var myLegend = myChart.addLegend(530, 100, 60, 300, "Right");
myChart.draw();
// This is a critical step. By doing this we orphan the legend. This
// means it will not respond to graph updates. Without this the legend
// will redraw when the chart refreshes removing the unchecked item and
// also dropping the events we define below.
myChart.legends = [];
// This block simply adds the legend title. I put it into a d3 data
// object to split it onto 2 lines. This technique works with any
// number of lines, it isn't dimple specific.
svg.selectAll("title_text")
.data(["Click legend to","show/hide owners:"])
.enter()
.append("text")
.attr("x", 499)
.attr("y", function (d, i) { return 90 + i * 14; })
.style("font-family", "sans-serif")
.style("font-size", "10px")
.style("color", "Black")
.text(function (d) { return d; });
// Get a unique list of Owner values to use when filtering
var filterValues = dimple.getUniqueValues(data, "Category");
// Get all the rectangles from our now orphaned legend
myLegend.shapes.selectAll("rect")
// Add a click event to each rectangle
.on("click", function (e) {
// This indicates whether the item is already visible or not
var hide = false;
var newFilters = [];
// If the filters contain the clicked shape hide it
filterValues.forEach(function (f) {
if (f === e.aggField.slice(-1)[0]) {
hide = true;
} else {
newFilters.push(f);
}
});
// Hide the shape or show it
if (hide) {
d3.select(this).style("opacity", 0.2);
} else {
newFilters.push(e.aggField.slice(-1)[0]);
d3.select(this).style("opacity", 0.8);
}
// Update the filters
filterValues = newFilters;
// Filter the data
myChart.data = dimple.filterData(data, "Category", filterValues);
// Passing a duration parameter makes the chart animate. Without
// it there is no transition
myChart.draw(800);
});
});
the scatterplot result is only 3 and i dont know why.
the x is the HP and the y is horse power.
more questions:
1. how can i change the axis unit.
2. how can i control the size of each bubble.
3. how to fix the wrong results.
heres the result picture:
The csv file has 480 rows.
maybe the addseries is wrong (i dont know what it is)?
Dimple aggregates the data for you based on the first parameter of the addSeries method. You have passed "Category" which has 3 values and therefore creates 3 bubbles with summed values. If instead you want a bubble per vehicle coloured by category you could try:
myChart.addSeries(["Vehicle Name", "Category"], dimple.plot.bubble);
To change the axis unit you can use axis.tickFormat though the change above will reduce scale so you might find you don't need to.
To control bubble size based on values in your data you need to add a "z" axis. See this example.
If you want to just set a different marker size for your scatter plot you can do so after the draw method has been called with the following:
var mySeries = myChart.addSeries("Category", dimple.plot.bubble);
var myLegend = myChart.addLegend(530, 100, 60, 300, "Right");
myChart.draw();
// Set the bubble to 3 pixel radius
mySeries.shapes.selectAll("circle").attr("r", 3);
NB. A built in property for this is going to be included in the next release.
I have a parameter that ranges from (0-1.0). I am trying to create a dc.js bar chart, so that there are 10 bars representing (0-0.1) (0.1-0.2) and so on.
I am using crossfilter.js to create dimension and group data, but it does not seem to create groups as required.
I tried the following code
var fluctuation = ndx.dimension(function (d) {
return d.value;
});
var fluctuationGroup = fluctuation.group(function(d){
return Math.round(d*10)/10;
});
I also tried doing it another way.
var fluctuation = ndx.dimension(function (d) {
return Math.round(d.value*10)/10;
});
var fluctuationGroup = fluctuation.group(function(d){
return d;
});
Output:
Sorry folks, it was not a crossfilter problem. It was a rendering problem.
I was not using
xUnits(d3.scale.linear().domain([0, 1])) with the chart.
Please suggest if I should delete this question ? Or update it ??
I have been following the guide for choropleth using D3 from this link.
http://synthesis.sbecker.net/articles/2012/07/18/learning-d3-part-7-choropleth-maps
instead of unemployment, I have a json file that lists the number of automobile crashes per county per state. The format of this json file is
{
"id":1001,
"crashTotal":2
},
And this is for each of the elements in the json file; one for each county. The ID is the State+County FIPS Code and the crashTotal is its namesake.
I have been following the example code closely and have come upon the quantize function
// quantize function takes a data point and returns a number
// between 0 and 8, to indicate intensity, the prepends a 'q'
// and appends '-9'
function quantize(d) {
return "q" + Math.min(8, ~~(data[d.id] * 9 / 12)) + "-9";
}
For me, data is a variable set equal to the crashes.json file. I'm confused as to why I cannot use the crashTotal values from my data to use according to the quantize function.
When I try to use the following code
~~data[d.id] or +data[d.id]
I get 0 or NaN. Why is this? I'm fairly new to using d3 so I'm not sure how this is meant to work. Thanks.
My code is quite close to the example code, but with my own US country and state JSON files converted from the census shapefiles. Can someone help?
EDIT: I'd figure I explain the issue a little bit more. Its not a problem between using a quantize function or d3 scale quantize, but rather how to access my data to color each county. As stated, my data file is a JSON in the format above. The following is how I set the data and how I call quantize
d3.json("Crashes.json", function(crashes) {
max = +crashes[0].crashTotal;
min = +crashes[0].crashTotal;
maxFIPS = +crashes[0].id;
minFIPS = +crashes[0].id;
for(i = 0; i < crashes.length; i++) {
if(+crashes[i].crashTotal > max) {
maxFIPS = +crashes[i].id;
max = +crashes[i].crashTotal;
}
if(+crashes[i].crashTotal < min) {
minFIPS = +crashes[i].id;
min = +crashes[i].crashTotal;
}
}
data=crashes;
//for(i = 0; i < data.length; i++) {
// document.writeln(data[i].id + " " + data[i].crashTotal);
// }
counties.selectAll("path")
.attr("class", quantize);
//.text(function (d){return "" + d.value;});
//console.log("maxFIPS:" + maxFIPS + " minFIPS:" + minFIPS + "\n" + "max:" + max + " min:" + min);
});
function quantize(d) {
return "q" + Math.min(8, ~~data[d.id]) + "-9";
}
If I were to replace data[d.id] in the quantize function above, it would actually color based on the color scheme specified in the bracket or CSS document. How would I get this to use the CrashTotal numbers from my data?
EDIT[3-6-2014]
Following the answer from Amelia, I now have the following code bracket.
d3.json("Crashes.json", function(crashes) {
crashDataMap = d3.map();
crashes.forEach(function(d) {crashDataMap.set(d.id, d);});
data = crashDataMap.values();
quantize = d3.scale.quantize()
.domain(d3.extent(data, function(d) {return d.crashTotal;}))
.range(d3.range(9).map(function(i) {return "q" + i + "-9"}));
//min = d3.min(crashDataMap.values(), function(d) {return d.crashTotal;});
//max = d3.max(crashDataMap.values(), function(d) {return d.crashTotal;});
//console.log(quantize(crashDataMap.get(6037).crashTotal));
counties.selectAll("path")
.attr("class", function(d) {return quantize(crashDataMap.get(d.id).crashTotal);});
});
This should get me the correct coloring for my map, but my map stays white. I can confirm that by testing out quantize, I get the correct class name from my CSS file.
console.log(quantize(crashDataMap.get(1001).crashTotal)); //returns q0-9
More help is appreciated. Thanks.
EDIT2[3-6-2014] I decided to just post the entire code I have here, hoping someone could make sense out of the madness of why this doesn't work
//CSS or <style></style> bracket
svg {
background: white;
}
path {
fill: none;
stroke: #000;
stroke-width: 0.1px;
}
#counties path{
stroke: #000;
stroke-width: 0.25px;
}
#states path{
fill: none;
stroke: #000;
stroke-width: 0.5px;
}
.Blues .q0-9{fill:rgb(247,251,255)}
.Blues .q1-9{fill:rgb(222,235,247)}
.Blues .q2-9{fill:rgb(198,219,239)}
.Blues .q3-9{fill:rgb(158,202,225)}
.Blues .q4-9{fill:rgb(107,174,214)}
.Blues .q5-9{fill:rgb(66,146,198)}
.Blues .q6-9{fill:rgb(33,113,181)}
.Blues .q7-9{fill:rgb(8,81,156)}
.Blues .q8-9{fill:rgb(8,48,107)}
//Crashes.js file
var width = 960
var height = 500;
var data;
var crashDataMap;
var quantize;
var path = d3.geo.path();
var zoom = d3.behavior.zoom()
.on("zoom", zoomed);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom)
.append("g");
var counties = svg.append("g")
.attr("id", "counties")
.attr("class", "Blues");
var states = svg.append("g")
.attr("id", "states");
d3.json("county.json", function(county) {
var countyFeatures = topojson.feature(county, county.objects.county);
counties.selectAll("path")
.data(countyFeatures.features)
.enter().append("path")
.attr("d", path);
});
d3.json("state.json", function(state) {
var stateFeatures = topojson.feature(state, state.objects.state);
states.selectAll("path")
.data(stateFeatures.features)
.enter().append("path")
.attr("d", path);
});
d3.json("Crashes.json", function(crashes) {
crashDataMap = d3.map();
crashes.forEach(function(d) {crashDataMap.set(d.id, d);});
data = crashDataMap.values();
quantize = d3.scale.quantize()
.domain(d3.extent(data, function(d) {return d.crashTotal;}))
.range(d3.range(9).map(function(i) {return "q" + i + "-9"}));
/*
for(i = 0; i < data.length; i++) {
console.log(quantize(crashDataMap.get(data[i].id).crashTotal));
}
*/
counties.selectAll("path")
.attr("class", function(d) {return quantize(crashDataMap.get(d.id).crashTotal);});
});
function zoomed() {
svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
};
Take a look at where I generated the paths for counties. After .enter().append("path")
statement, if I were to enter the code .attr("class", "q8-9) It would color every county to the scheme defined as q8-9.
If I were to call counties.selectAll("path").attr("class", "q8-9") anywhere outside of the code bracket, nothing happens; the map stays white. This is bugging me as I clearly have no idea why this can happen. I can verify that the path elements are there for both county and state.
To explain what's going on in the original code:
The tutorial you linked to uses two data files, one for the maps and one for the data values. Unfortunately, it doesn't seem to include links to the actual data files used, but their both JSON. The counties have an 'id' property and that property seems to be used as the keys in the second JSON data file. I.e., that second file (data) must be of the form:
{
"1001": ".097",
"1003": ".091",
"1005": ".134",
/*...*/
}
This is different from the data structure used in the very similar Mike Bostock example, which uses a .tsv file for the unemployment data, which is then used to generate a d3.map hashmap data dictionary.
var rateById = d3.map();
queue.defer(d3.tsv, "unemployment.tsv", function(d) { rateById.set(d.id, +d.rate); })
//this is equivalent to
/*
d3.tsv("unemployment.tsv",
function(d) { rateById.set(d.id, +d.rate); },
readyFunction );
*/
//except that the readyFunction is only run when *both* data files are loaded.
//When two functions are given as parameters to d3.tsv,
//the first one is called on each row of the data.
//In this case, it adds the id and rate as a key:value pair to the hashmap
Both of these examples end up with a data structure where the id values are keys that can be used to grab the appropriate data value. In contrast, your data are in an unkeyed array, with your id values as just another property, not as a key. That is why data[d.id] was returning an error for you -- instead of grabbing a data number that matches that id, it's grabbing an element of your array at the index equivalent the id number. That either returns an object, which becomes NaN when converted to a number, or undefined, which becomes zero.
In either example, once they have the number, they then want to convert it to an integer from 0 to 8 in order to assign one of the ColorBrewer class names to the path. The Scott Becker tutorial uses a somewhat arbitrary calculation for this, the Mike Bostock example uses a quantize scale with a hard-coded domain. You say you want to figure out a domain based on your data.
To help you figure out what you need to do:
Your first step is to get your crash data into a structure where you can easily grab a data element based on its id value.
One option would be to create a d3.map object (var crashDataMap = d3.map();) and then use a forEach call on your existing data array to add each object to the map using map.set(key, value) with its id as the key.
crashDataArray.forEach( function(d){ crashDataMap.set( d.id, d) });
Then when you are setting the class on your shapes, you can use crashDataMap.get(d.id) to grab the crash data that matches the shape's id, and you can extract the correct number from that.
For dividing your data into categories, you probably want to use a quantize scale similar to Mike Bostock's example. On your original data array, you can use d3.extent with an appropriate accessor function to grab the crash totals from each entry and find the max and min for setting the domain.
I want the actual value of each bar displayed on top in the way it's shown here
I am trying this on multi bar chart.
Can't find reference anywhere.
Duplicate of How to display values in Stacked Multi-bar chart - nvd3 Graphs
There is a fix you can implement yourself at https://gist.github.com/topicus/217444acb4204f364e46
EDIT: Copied the code if the github link gets removed:
// You need to apply this once all the animations are already finished. Otherwise labels will be placed wrongly.
d3.selectAll('.nv-multibar .nv-group').each(function(group){
var g = d3.select(this);
// Remove previous labels if there is any
g.selectAll('text').remove();
g.selectAll('.nv-bar').each(function(bar){
var b = d3.select(this);
var barWidth = b.attr('width');
var barHeight = b.attr('height');
g.append('text')
// Transforms shift the origin point then the x and y of the bar
// is altered by this transform. In order to align the labels
// we need to apply this transform to those.
.attr('transform', b.attr('transform'))
.text(function(){
// Two decimals format
return parseFloat(bar.y).toFixed(2);
})
.attr('y', function(){
// Center label vertically
var height = this.getBBox().height;
return parseFloat(b.attr('y')) - 10; // 10 is the label's magin from the bar
})
.attr('x', function(){
// Center label horizontally
var width = this.getBBox().width;
return parseFloat(b.attr('x')) + (parseFloat(barWidth) / 2) - (width / 2);
})
.attr('class', 'bar-values');
});
});
Apparently this doesn't exist yet. There is an issue (https://github.com/novus/nvd3/issues/150) that was closed because this is (apparently) hard to implement.
I am not sure what you have tried so fat, but the example in here is pretty straight forward.
.showValues(true) pretty much does the trick.
Hope it helps.