d3 Pie chart with one object key /value - javascript

Hello I am quite new with d3 and javascript. I am trying to display a pie chart with one object filled with mutiple keys and values.
Here my object :
dataset Object { $Allied Health Professions, Dentistry, Nursing and Pharmacy: 4, $Psychology, Psychiatry and Neuroscience: 4, $Biological Sciences: 4, $General Engineering: 4, $Architecture, Built Environment and Planning: 4, $Geography, Environmental Studies and Archaeology: 4, $Business and Management Studies: 4, $Law: 4, $Social Work and Social Policy: 4, $Education: 4, 5 de plus… }
My code is quite long and in mutiple files so I don't think it's relevant to link it.
I succeed to load a pie chart with a simple array but I do not know how to access the values here.

D3 data method accepts 3 things:
An array;
A function;
Nothing;
Therefore, you have to convert that object in an array of objects, with a specific property for the category and a property for the value. For instance:
var obj = {
"Allied Health Professions, Dentistry, Nursing and Pharmacy": 4,
"Psychology, Psychiatry and Neuroscience": 4,
"Biological Sciences": 4,
"General Engineering": 4,
"Architecture, Built Environment and Planning": 4
};
var data = [];
for (var key in obj) {
data.push({
name: key,
value: obj[key]
})
};
console.log(data)
Here is a basic demo with a portion of your object:
var obj = {
"Allied Health Professions, Dentistry, Nursing and Pharmacy": 4,
"Psychology, Psychiatry and Neuroscience": 4,
"Biological Sciences": 4,
"General Engineering": 4,
"Architecture, Built Environment and Planning": 4
};
var data = [];
for (var key in obj) {
data.push({
name: key,
value: obj[key]
})
};
var arc = d3.arc().outerRadius(100).innerRadius(0);
var pie = d3.pie().value(function(d) {
return d.value
});
var colors = d3.scaleOrdinal(d3.schemeCategory10)
var svg = d3.select("svg")
.append("g")
.attr("transform", "translate(100,100)")
svg.selectAll(null)
.data(pie(data))
.enter()
.append("path")
.attr("d", arc)
.style("fill", function(d, i) {
return colors(i)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="200" height="200"></svg>

Related

Retrieving selection's data

I got some trouble understanding the way .data works in D3.js, even by reading and re-reading the documentation. Let's imagine the following code:
const chart = selection => {
const svg = selection.selectAll('svg').data(d => d);
console.log(svg.data());
}
d3.select('#root').data([
[{
name: 'Foo',
values: [1, 2, 3]
},
{
name: 'Bar',
values: [4, 5, 6]
},
]
]).call(chart);
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="root">
</div>
I would expect to get the array with Foo and Bar objects to be logged in this code. Instead, I get an empty array.
According to documentation:
The data is specified for each group in the selection. If the
selection has multiple groups (such as d3.selectAll followed by
selection.selectAll), then data should typically be specified as a
function. This function will be evaluated for each group in order,
being passed the group’s parent datum
I probably misunderstand something here. Can an expert enlighten me? :)
Related JSFiddle here: https://jsfiddle.net/tmq4h8w2/
What you have here...
const svg = selection.selectAll('svg').data(d => d);
... is what we call an "update" selection in a D3 code. What it does is:
Select all <svg> elements;
Binds the data to them;
Since you don't have any <svg> element here, your update selection is empty. Check the console in the demo below, which uses your code exactly as it is:
const chart = selection => {
const svg = selection.selectAll('svg').data(d => d);
console.log("The size of the update selection is: " + svg.size());
}
d3.select('#root').data([
[{
name: 'Foo',
values: [1, 2, 3]
},
{
name: 'Bar',
values: [4, 5, 6]
},
]
]).call(chart);
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="root">
</div>
That's why, for appending elements, we don't need to select anything:
selection.selectAll(null)//see the 'null' here
.data(data)
.enter()
.append("svg");
On the other hand, if you already had <svg> elements in your HTML, your "update" selection wouldn't be empty: it would bind the data to the selected elements and you could clearly log the bound data. The demo below just adds two SVG elements in the HTML, your code is the same:
const chart = selection => {
const svg = selection.selectAll('svg').data(d => d);
console.log(svg.data());
}
d3.select('#root').data([
[{
name: 'Foo',
values: [1, 2, 3]
},
{
name: 'Bar',
values: [4, 5, 6]
},
]
]).call(chart);
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="root">
<svg></svg>
<svg></svg>
</div>
The issue is that your selection doesn't bind that data to svg on its own. You need to enter the data join (and append the object that uses the data) so that it properly comes into being:
const chart = selection => {
const svg = selection.selectAll('svg').data(d => d)
.enter()
.append('svg');
console.log(svg.data());
}
d3.select('#root').data([[
{ name: 'Foo', values: [1, 2, 3] },
{ name: 'Bar', values: [4, 5, 6] },
]]).call(chart);
Fiddle here: https://jsfiddle.net/dfzc0ub7/
After you call it into being, you should be able to select and update without the need for enter().

In D3, how can I create multiple elements for each data element based on value in data?

I have a simple array with objects for my data and I generate divs from it. Instead of creating only one div for each data element, I would like to create several divs, depends on the number that appears in the data (as one of the object's properties).
For example in case that the "num" for a data element is 4, it will generate 4 divs.
Here is the code I have for this part:
data = [
{num: 4, category: 'A'},
{num: 3, category: 'B'},
{num: 2, category: 'D'},
{num: 5, category: 'A'}
]
d3.select('body')
.selectAll('div')
.data(data)
.enter()
.append('div')
.text((d)=> d.num)
.style('width', (d) => d.num * 20 + "px")
I've tried to solve it with a for loop but I'm not sure how to loop in the middle of a d3 selection, while still having access to the data.
Any idea would be very much appreciated!
Here's how I'd do it:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
data = [{
num: 4,
category: 'A'
}, {
num: 3,
category: 'B'
}, {
num: 2,
category: 'D'
}, {
num: 5,
category: 'A'
}]
d3.select('body')
.selectAll('div')
.data(data) // bind our data
.enter()
// inner selection
.selectAll('div')
// inner selection data binding
// creates array of repeating datum that is length of num
.data((d) =>
d3.range(d.num).map(() => d)
)
.enter()
.append('div')
.text((d) => d.num)
.style('width', (d) => d.num * 20 + "px");
</script>
</body>
</html>

How to get a D3.js pie chart to render data dynamically from the DOM

I'm trying to make a pie chart using D3.js that changes once the submit button has been clicked on in the HTML form (asking for total count of females and males).
This is what I have so far for the JavaScript file:
// Data
function getTotalFemales() {
let total = document.getElementById('totalFemales').value;
totals.push(parseFloat(total));
}
function getTotalMales() {
let total = document.getElementById('totalMales').value;
totals.push(parseFloat(total));
}
let totals = [];
// Dataset
var dataset = [
{label: 'Female Count', count: 1},
{label: 'Male Count', count: 1}
];
function updateChart() {
dataset.count.forEach(function(num)
dataset.count = totals[num]
});
console.log(totals);
}
and this is the d3 part:
var path = pieChart.selectAll('path')
.data(pie(dataset))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.label);
});
Any idea on how I can get the count property in the dataset array to change depending on the user's form values?
Take the input values and directly put them into the dataset. I don't see the need for the totals array.
And set the properties of the dataset array of objects,
var dataset = [
{label: 'Female Count', count: 1},
{label: 'Male Count', count: 1}
];
function getTotals() {
dataset[0].count = document.getElementById('totalFemales').value;
dataset[1].count = document.getElementById('totalMales').value;
}

Can I update DOM elements in a D3 selector based on a data join to a different data object?

tl;dr
Can I create elements using data that looks like:
[{"id": 1, ...}, {"id: 2, ...}, ..]
and update attributes of the elements using data that looks like:
[2, 4, 7]
(where the elements of the array are a subset of the ids of the initial data set).
Long version
I have data that looks like this:
[
{
"id": 1,
"latitude": 38.314552,
"longitude": -88.9025347755102,
"name": "JEFFERSON COUNTY JAIL ",
"official_name": "Jefferson County Justice Center",
"url": "https://www.ice.gov/detention-facilities/facilities/jeffeil.htm"
},
{
"id": 2,
"latitude": 41.875702,
"longitude": -87.63072,
"name": "CHICAGO HOLD ROOM ",
"official_name": "Chicago Hold Room",
"url": ""
},
{
"id": 3,
"latitude": 43.407029,
"longitude": -88.704349,
"name": "DODGE COUNTY JAIL, JUNEAU ",
"official_name": "Dodge Detention Facility",
"url": "https://www.ice.gov/detention-facilities/facilities/dodgewi.htm"
},
...
]
I put it on an SVG map like this:
var projection = d3.geo.albersUsa()
.scale(scale)
.translate([width / 2, height / 2]);
var facilityCircles = svg.append("svg:g")
.attr("id", "facilities");
d3.json("data/facilities.json", function(facilities) {
var positions = [];
var positionsByFacilityId = {};
var pointSize = 5;
facilities.forEach(function(facility) {
var loc = [facility.longitude, facility.latitude];
var pos = projection(loc);
positions.push(pos);
positionsByFacilityId[facility.id] = pos;
});
facilityCircles.selectAll("circle")
.data(facilities, function(d) { return d.id; })
.enter().append("svg:circle")
.attr("data-facility-id", function(d, i) { return d.id; })
.attr("cx", function(d, i) { return positionsByFacilityId[d.id][0]; })
.attr("cy", function(d, i) { return positionsByFacilityId[d.id][1]; })
.attr("r", function(d, i) { return pointSize; });
}
However, later on, I want to update attributes of the circles based on another data object, which would just be an array of ids representing a subset of the id properties from the initial data, e.g. [2, 4, 5]
Can I do something like this to update the attributes of only selected elements mapped to data objects with the given ids?
facilitiesSubset = [2, 4, 5];
facilityCircles.selectAll("circle")
.data(facilitiesSubset)
.attr("fill", "green");
or, should I just extract the ids from the initial data and use those in the call to data() that is used to create the elements?
To make this a little easier, I'd suggest changing the naming convention a little bit:
var facilityCircleContainer = svg.append("svg:g")
.attr("id", "facilities");
Since the svg:g object isn't actually the circles, it is just holding them. So then:
var facilityCircles = facilityCircleContainer.selectAll("circle")
.data(facilities, function(d) { return d.id; })
.enter().append("svg:circle")
.attr("data-facility-id", function(d, i) { return d.id; })
.ect(...)
facilityCircles now refers to the circles with their attached data now. Updating the fill based on the array is pretty simple:
facilityCircles.attr("fill", function(d){
facilitiesSubset.indexOf(d.id) != -1 ? "green" : "red"});

simple multi line graph with d3js

I have the following array
var data = [
[{"time": 1, "value": 2.1}, {"time": 2, "value": 1.1}],{"time": 3, "value": 3.1}],
[{"time": 1, "value": 5.3}, {"time": 2, "value": 0.1}, {"time": 3, "value": 6.1}]
];
and I need to find the maximum time and value out of the entire array. the code that doesn't quite work is
var x = d3.scale.linear()
.domain([1, d3.max(data, function(d,i){ return d[i].time;})])
.range([0, width]);
for some reason I get a maximum time of 2, and not 3. even if I use a larger dataset with more point I still don't get the actual maximum value.
Any help is appreciated.
Your data is an array or arrays. If you want the "max of the maxes", you'll need to account for the nesting. One way to do it:
.domain([1, d3.max(data, function(arrayOfDs, i) {
return d3.max(arrayOfDs, function(d, i) { return d.time; });
})])

Categories