I am working on a pie chart today in D3 (fun!).
Alright, the issue I am having is the colours aren't being assigned properly.
Here's the jsfiddle:
https://jsfiddle.net/zh34ud25/5/
Most(?) relevant code:
var color = d3.scale.ordinal()
.range(["#71b2b9", "#dcdcdc"]);
color.domain(d3.keys(dataUnbilledRevenue[0].values[0]).filter(function(key) {
if (key === 'Unbilled_Revenue'
|| key === 'Billed_Revenue') {
return key
}
}));
// This returns the data into two separate objects which can be graphed.
// In this case, Amount and Quantity.
var datasets = color.domain().map(function(name) {
return {
name: name,
values: dataUnbilledRevenue.map(function(d) {
return {
Value: +d.values[0][name]
};
})
};
});
This is simple, doing this will give the colors to pie:
pieValues.append("path")
.attr("d", arc)
.attr('class', 'pie-point')
.style("fill", function(d) {
return color(d.data.name)
})
working code here
Related
Looking to hide label from legend of a Pie Chart when return value is equal to 0. Can anyone point me in the right direction in NVD3.js?
nv.addGraph(function () {
var donutChart = nv.models.pieChart()
.x(function (d) {
return d.label
})
.y(function (d) {
return d.value
})
d3.select("#chart-devices svg")
.datum(data)
.transition().duration(1200)
.call(donutChart);
nv.utils.windowResize(donutChart.update);
return donutChart;
});
A possible answer is to remove items with value=0 on renderEnd event:
chart.dispatch.on('renderEnd', function () {
console.log("renderEnd");
d3.selectAll(".nv-legend .nv-series")[0].forEach(function (d) {
//get the data
var t = d3.select(d).data()[0];
// remove item from legend
if (t.value == 0)
d3.select(d).remove();
});
});
Another possibility is to remove items after a timeout:
setTimeout(function () {
d3.selectAll(".nv-legend .nv-series")[0].forEach(function (d) {
//get the data
var t = d3.select(d).data()[0];
// remove item from legend
if (t.value == 0)
d3.select(d).remove();
});
}, 1);
In both cases there are gaps among remaining items.
Here is a fiddle (first option): https://jsfiddle.net/beaver71/yt7vrohk/
Having some massive trouble getting my D3 scatter plot visualization running. Didnt know how to reference the data, so its available from a dropbox link here.
There are a few problems.
I am a bit confused about loading my data.
I cannot seem to get the data loaded. I have been successful before, but I am trying to load the data without having to reference a function (i.e., global). However, as you will see - I am getting nothing - [].
Do I need to load it at the bottom of my script and then reference the function within the d3.csv(function(d) {...}, FUNCTION);? Why cant I simple load it to a variable (as I am trying to) at the top of my script. Such that its the first object available?
I also felt like I had a good handle on the Mike Bostock tutorial about .enter(), update(), .exit(). But I know that I have an issue in the comment section of "//ENTER + UPDATE" where I have circle.circle(function(d) {return d;});. I dont understand this.
Overall, I am looking to create a scatter plot with fare as my x-axis, age as my y-axis, then loop through the options of "Female Only", "Male Only", "Children Only" and "All" (starting and ending with All).
I plan to add more to this as I get a better understanding of where I am currently stumbling.
d3.csv("titanic_full.csv", function(d) {
return {
fare: +d.fare,
age: d.age == '' ? NaN : +d.age,
sibsp: +d.sibsp,
pclass: +d.pclass,
sex: d.sex,
name: d.name,
survived: d.survived
};
}, function(error, d) {
//Filter out erroneous age values (263 rows are removed)
var dataset = d.filter(function(d) {
if (d['age'] >= 0) {
return d;
}
});
//*Main Elements Setup*
//Width and height
var w = 650;
var h = 650;
var padding = 20;
//Create scale functions
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d.fare;
})])
.range([padding, w - padding * 2]); // introduced this to make sure values are not cut off
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d.age;
})])
.range([h - padding, padding]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Show Axes
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + (h - padding) + ')')
.call(xAxis);
svg.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + padding + ',0)')
.call(yAxis);
function update(dataset) {
//DATA JOIN
//Join new data with old elements, if any.
var circle = svg.selectAll('circle')
.data(dataset);
//UPDATE
//Update old elements as needed.
circle.attr('class', 'update');
//ENTER
//Create new elements as needed.
circle.enter().append('circle')
.attr('class', 'enter')
.transition()
.duration(1000)
.attr("cx", function(d) {
return xScale(d.fare);
})
.attr("cy", function(d) {
return yScale(d.age);
})
.attr("r", 6)
.attr('fill', function(d) {
if (d.survived === '0') {
return 'green';
} else {
return 'red';
}
})
//ENTER + UPDATE
//Appending to the enter selection expands the update selection to include
//entering elements; so, operations on the update selection after appending to
//the enter selection will apply to both entering and updating nodes.
circle.circle(function(d) {
return d;
});
//EXIT
//Remove old elements as needed.
circle.exit().remove();
};
//The initial display.
update(dataset);
//Work through each selection
var options = ['Female Only', 'Male Only', 'Children Only', 'All']
var option_idx = 0;
console.log('next')
var option_interval = setInterval(function(options) {
if (options == 'Female Only') {
var filteredData = dataset.filter(function(d) {
if (d['sex'] == 'female') {
return d;
}
})
} else if (options == 'Male Only') {
var filteredData = dataset.filter(function(d) {
if (d['sex'] == 'male') {
return d;
}
})
} else if (options == 'Children Only') {
var filteredData = dataset.filter(function(d) {
if (d['age'] <= 13) {
return d;
}
})
} else if (options == 'All') {
var filteredData = dataset.filter(function(d) {
return d;
})
};
console.log('interval')
option_idx++; // increment by one
update(filteredData);
if (option_idx >= options.length) {
clearInterval(option_interval);
};
}, 1500);
});
.axis path,
.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
.axis text {
font-family: sans-serif;
font-size: 8px;
}
<title>Titanic Visualization - Fare and Age Survival</title>
You should write the whole code inside the d3.csv callback function. Try this way.
d3.csv("titanic_full.csv", function(d) {
return {
fare: +d[fare],
age: d.age == '' ? NaN : +d.age,
sibsp: +d.sibsp,
pclass: +d.pclass
};
}, function(error, dataset) {
//Filter out erroneous age values (263 rows are removed)
var dataset = dataset.filter(function(d) {
if (d['age'] >= 0) {
return d;
}
});
//Remaining code
});
Refer here for more details.
Okay, so I have an answer, but I probably will not explain this as well.
1. I am a bit confused about loading my data.
With the help of #Gilsha, I was able to reconfigure my script to load the data first, with the rest of my script being the 'callback' portion of the d3.csv function. That worked smoothly. I also was able to filter my data to remove the blanks right away. Here is that first part:
d3.csv("titanic_full.csv", function(d) {
return {
fare: +d.fare,
age: d.age == '' ? NaN : +d.age,
sibsp: +d.sibsp,
pclass: +d.pclass,
sex: d.sex,
name: d.name,
survived: d.survived
};
}, function(error, d) {
//Filter out erroneous age values (263 rows are removed)
var dataset = d.filter(function(d) {
if (d['age'] >= 0) {
return d;
}
});
//Rest of script here.
**2. I also felt like I had a good handle on the Mike Bostock tutorial about .enter(), update(), .exit(). Link to Bostock Tutorial I was following **
Couple things that I did wrong here that was holding me up. The main item that I was stuck on was this portion:
//ENTER + UPDATE
//Appending to the enter selection expands the update selection to include
//entering elements; so, operations on the update selection after appending to
//the enter selection will apply to both entering and updating nodes.
circle.circle(function(d) {return d;});
Basically, I was following the tutorial too closely and didnt realize that when he was using "text.text(function(d) {return d;});", he was setting the text attribute (?) to something. This is where I believe I should be setting any changes to my ENTER and UPDATE selections (all the items that I expect to be in the DOM). So, when Mike was doing text.text and I replicated with circle.circle, I should have had circle.text(.....). Or whatever I want there. Anyone care to comment or explain that better??
PS - I had many other errors... throughout, especially in the section of establishing my interval!
I am using the D3 library to create a Zoomable Treemap for my application data using Javascript and JSON. I see online that many times d3 category for Color is being used to determine the colors of each section. However, I wish to color the sections of treemap using my application logic. Like below:
If conditionA
color = red
If conditionB
color=green
....
Is there any way to achieve this.. Check the values of my JSON Object and set the color of a section only on the basis of some conditions; and have all other sections set to a default color?
Your question is a little vague (next time include some code!), but in general, say you have data like this:
var data = [{
conditionA: true,
conditionB: false
}, {
conditionA: false,
conditionB: true
}, {
conditionA: false,
conditionB: false
}];
then it's as simple as:
svg.selectAll('.SomeCircles')
.data(data)
.enter()
.append('circle')
.attr('r', 20)
.attr('cx', function(d, i) {
return i * 25 + 25;
})
.attr('cy', function(d, i) {
return i * 25 + 25;
})
.attr('class', 'SomeCircles')
.style('fill', function(d) { //<-- filling based on an attribute of my data
if (d.conditionA) {
return 'red';
} else if (d.conditionB) {
return 'green';
} else {
return 'blue';
}
});
Here is an example.
I am trying to create a scatterplot of hundreds of datapoints, each with about 5 different attributes.
The data is loaded from a .csv as an array of objects, each of which looks like this:
{hour: "02",yval: "63",foo: "33", goo:"0", bar:"1"},
I want to display the scatterplot with the following attributes:
Shape for bar:
-circle to represent all points where bar=0, and a triangle-down to represent those where bar=1 (this is a dummy variable).
Color for foo and goo:
All points start as grey. goo is categorical with values [0,1,2] while foo is quantitative with a range from 0-50. foo and goo are mutually exclusive, so only one of them has a value. In other words, for each data point either foo=0 or goo=0.
Points with goo=1 should be orange; points with goo=2 should be red.
foo should be mapped onto a linear color scale from light blue to dark blue, ie d3.scale.linear().domain([0, 50]).range(["#87CEFF", "#0000FF"]);
I can do each of these individually, but defining everything together is creating issues for me.
My code with reproducible data is here: http://jsfiddle.net/qy5ohw0x/3/
Issues
For the symbol, i tried
.append("svg:path")
.attr("d", d3.svg.symbol())
which did not work. I tried a different approach altogether, but this did not map the values correctly:
var series = svg.selectAll("g.series")
.data(dataSet, function(d, i) { return d.bar; })
.enter()
.append("svg:g")
series.selectAll("g.point")
.data(dataSet)
.enter()
.append("svg:path")
.attr("transform", function(d, i) { return "translate(" + d.hour + "," + d.yval + ")"; })
.attr("d", function(d,i, j) { return d3.svg.symbol().type(symbolType[j])(); })
.attr("r", 2);
For the goo colors (grey/orange/red), i mapped the values to the 3 colors manually:
First define var colors = ["grey", "orange", "red"];
Then while drawing the data points chain
.style("fill", function (d) { return colors[d.type]; })
This worked alone, but not with the different symbols.
Finally, can i chain a second color .attr for foo? d3.scale.linear().domain([0, 50]).range(["#87CEFF", "#0000FF"]); would probably work if this is possible.
Again, the jsfiddle is here: http://jsfiddle.net/qy5ohw0x/3/
Thanks!!
Just do all the logic and comparisons in a function(d) for each attribute.
First set up some helpers:
// symbol generators
var symbolTypes = {
"triangleDown": d3.svg.symbol().type("triangle-down"),
"circle": d3.svg.symbol().type("circle")
};
// colors for foo
var fooColors = d3.scale
.linear()
.domain([0, 50])
.range(["#87CEFF", "#0000FF"]);
Then append a path for each symbol:
svg.selectAll("path")
.data(dataSet)
.enter().append("path")
.attr("class", "dot")
// position it, can't use x/y on path, so translate it
.attr("transform", function(d) {
return "translate(" + (x(d.hour) + (Math.random() * 12 - 6)) + "," + y(d.yval) + ")";
})
// assign d from our symbols
.attr("d", function(d,i){
if (d.bar === "0") // circle if bar === 0
return symbolTypes.circle();
else
return symbolTypes.triangleDown();
})
// fill based on goo and foo
.style("fill", function(d,i){
if (d.goo !== "0"){
if (d.goo === "1")
return "red";
else
return "orange";
}else{
return fooColors(d.foo);
}
});
Updated fiddle.
On a side note, I actually think straight d3 is way more intuitive than nvd3 for this situation.
It's much simplier with nvd3.js
function prepareData (data) {
return [{
key: 'Group 1',
values: data.map(function (item) {
item.shape = item.bar == "0" ? 'circle' : 'triangle-down';
item.x = Number(item.hour);
item.y = Number(item.yval);
item.size = 0.1;
item.disabled = Math.random() > 0.4;
return item;
})
}]
}
nv.addGraph(function() {
var chart = nv.models.scatterChart()
.showDistX(false)
.showDistY(true)
.showLegend(false)
//Axis settings
chart.xAxis.tickFormat(d3.format('3.0f'));
chart.yAxis.tickFormat(d3.format('3.0f'));
d3.select('#chart svg')
.datum(prepareData(dataSet))
.call(chart)
// A bit hacky but works
var fooscale = d3.scale.linear().domain([0, 50]).range(["#87CEFF", "#0000FF"]);
function colorer(d) {
if (d.goo == '1')
return 'orange';
else if (d.goo == '2')
return 'red';
else if (d.goo == '0')
return fooscale(d.foo);
return 'gray';
}
d3.selectAll('.nv-point')
.attr({
'stroke': colorer,
'fill': colorer
})
nv.utils.windowResize(chart.update);
return chart;
});
See https://jsfiddle.net/qy5ohw0x/4/
PS Unfortunately Nvd3 lacks docs, so use it's github instead
The code below requires the JSON object to specify "value" and "label". I want to make this so that a pie chart can be created with any key names.
//Regular pie chart example
nv.addGraph(function() {
var chart = nv.models.pieChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
.showLabels(true);
d3.select("#chart svg")
.datum(exampleData())
.transition().duration(350)
.call(chart);
return chart;
});
The above function can be modified to return d.fruit and d.number to create a pie chart of a JSON object [{"fruit": "apple", "number": 1}], but I would like this to work for any JSON object, regardless of the key names.
#chart svg {
height: 400px;
}
</style>
<div id="chart">
<svg></svg>
</div>
</head>
<body>
<script>
//Regular pie chart example
nv.addGraph(function() {
var chart = nv.models.pieChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
.showLabels(true);
d3.select("#chart svg")
.datum(exampleData())
.transition().duration(350)
.call(chart);
return chart;
});
//Donut chart example
nv.addGraph(function() {
var chart = nv.models.pieChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
.showLabels(true) //Display pie labels
.labelThreshold(.05) //Configure the minimum slice size for labels to show up
.labelType("percent") //Configure what type of data to show in the label. Can be "key", "value" or "percent"
.donut(true) //Turn on Donut mode. Makes pie chart look tasty!
.donutRatio(0.35) //Configure how big you want the donut hole size to be.
;
d3.select("#chart2 svg")
.datum(exampleData())
.transition().duration(350)
.call(chart);
return chart;
});
//Pie chart example data. Note how there is only a single array of key-value pairs.
function exampleData() {
return [
{"value":"1","label":"apples"},{"value":"2","label":"bananas"}
];
}
</script>
</body>
</html>
var chart = nv.models.pieChart()
.x(function(d) { //always spits out first memeber of object
var val, x;
val = 0;
x = 0;
for (i in d) {
if (x++ === val)
{
return d[i];
}
}
})
.y(function(d) { //always spits out second member of object
var val, x;
val = 1;
x = 0;
for (i in d) {
if (x++ === val)
{
return d[i];
}
}
})
The following lines define what properties should be used by the chart:
var chart = nv.models.pieChart()
.x(function(d) { return d.label })
.y(function(d) { return d.value })
So, you can change d.label to d.whatever, and if you have a whatever property, it will use that for x.
You could run your data through something before you pass it into the chart. Something along the lines of:
d3.map(data, function(item) {
return {
label: item.car,
value: item.speed
};
}).values();
You could easily wrap that in a function something like:
function transform(data, x, y) {
x = x || "label";
y = y || "value";
return d3.map(data, function(item) {
return {
label: item[x],
value: item[y]
};
}).values();
}
d3.select("#chart2 svg")
.datum(transform(exampleData(), "car", "speed"));
There is no reliable way other than transforming your data or changing your x and y accessors to guarantee you will see the data you're expecting to see. The chart has no way to understand your data without you expressing what it means.