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().
Related
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>
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>
I have a bunch of data that can be nested into a few categories, e.g.:
{id: 1, type: A},
{id: 2, type: A},
{id: 1, type: B},
{id: 1, type: A},
{id: 2, type: B}
Nesting this data on id gives me nested data with id as the key and a values array which contains all original values. What I need is a stacked chart showing that id 1 has two type A and one type B occurrences, id 2 has one of each, etc. This is the code I'm using:
var nested = d3.nest()
.key( function(d) {
return d.Id
})
.entries(data);
var stack = d3.stack()
.keys(['A','B'])
.offset(d3.stackOffsetExpand);
I want these as percentages hence the stackOffsetExpand. However this is giving me null stack values, presumably because the type field that I want the stack function to use is hidden inside the values array. Using the .value function of the stack call I can see that the data it's seeing is indeed the whole chunk of data for each nested array (i.e. key: 1, values: [{all objects with id 1 here}]). I just don't know how to use that function to tell it to start counting on the type property...
This is one way to calculate the percentage from your data. I'm setting the keys first and then rolling up the values in the nest function using rollup. Then, I'm calculating the percentage for each key after that.
var raw_data = [{id: 1, type: "A"},
{id: 2, type: "A"},
{id: 1, type: "B"},
{id: 1, type: "A"},
{id: 2, type: "B"}];
var data = d3.nest()
.key(function(d) {return d.id;})
.rollup(function(d) { return d.length; })
.entries(raw_data);
data.forEach(function(d) {
d.percentage = d.value / raw_data.length;
console.log(d.key + ": " + d.percentage*100);
});
JSFiddle - https://jsfiddle.net/do4ym1L2/
I need the x-axis values, when hovering over my plotly chart.
According to the plotly docs (https://plot.ly/javascript/hover-events/) the hover event callback should contain the field "points" from which I should get the x value.
But if you look at this basic example you can see that the callback does not contain the field "points". Also other fields like "data" are undefined:
HTML
<div id="tester" style="width:600px;height:250px;"></div>
JS
$(document).ready(function(){
var tester = $('#tester');
tester.on('plotly_hover', function(data){
console.log(data)
});
Plotly.plot( 'tester', [{
x: [1, 2, 3, 4, 5],
y: [1, 2, 4, 8, 16] }], {
margin: { t: 0 } } );
})
See this fiddle in order to try it yourself:
https://jsfiddle.net/c1kt3r82/158/
What am I doing wrong?
plotly-basic does not seem to support hover events, use plotly-latest instead
when using jQuery to select the element, it returns a different object than doing it via document.getElementById
the hover events need to be defined after calling plot
$(document).ready(function() {
var tester = document.getElementById('tester');
Plotly.plot(tester, [{
x: [1, 2, 3, 4, 5],
y: [1, 2, 4, 8, 16]
}], {
margin: {
t: 0
}
});
tester.on('plotly_hover', function(data) {
console.log(data)
});
});
<div id="tester" style="width:600px;height:250px;"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
I am trying to highlight a single set of values in a c3.js stacked bar chart. Following this example I can change the color of a single bar in a non-stacked bar, but I can't figure out how to identify the indexes of a single stack.
My JS is:
var chart = c3.generate({
bindto: '#chart1',
data: {
x: 'x',
columns: [
['x', "1ST", "2ND", "3RD", "4TH"],
['A', 6, 8, 2, 9],
['B', 3, 4, 1, 6],
['C', 4, 4, 2, 4]
],
type: 'bar',
groups: [
['A', 'B', 'C']
],
colors: {
A: '#666666',
B: '#cccccc',
C: '#428bca',
},
order: 'asc'
},
axis: {
x: {
type: 'category'
}
}
});
And the approach of the example is to do:
color: function (color, f) {
return f.index === 4 ? "#dd0" : "#ddd";
}
Work in progress JSFIDDLE.
How can I get the indexes of the 3 values of a single stack and then use them to change their colors? For example, if I want to highlight "2ND" by changing the colours to a set of reds?
Adapted from the example here -->
http://c3js.org/samples/data_color.html
color : function (color, d) {
return d.index && d.index === 2 ? "#dd0" : color;
},
https://jsfiddle.net/kmjpg30c/3/
d.index gives you the index of the stack - however, in your example there is no index 4 as the indexing starts at 0, so you have [0,1,2,3] as a possible range
So test against the index and return a new color if it matches and if it doesn't return the color that is originally passed in.
(the d.index test is present as the color function gets used by other routines that pass in just a string indicating the dataset ("A", "B", "C" in your example), so you need to test for the existence of the fields you need in d first.
if you want to highlight just one part of the stack use the .id field as well e.g. d.index === 2 && d.id === "A"