Equivalent of chord.groups in D3.js v7 - javascript

I am trying to recreate Nadia Bremer's chord diagram for storytelling with data from https://gist.github.com/nbremer/94db779237655907b907
She accesses the chord.groups element when creating the g element:
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", function(d) {return "group " + NameProvider[d.index];});
This works fine in v3 of D3. Here's a jsfiddle that works
However, when I try the same thing in D3 v7, chord.groups becomes undefined. Here is a jsfiddle with the v7 variant which gives an error when accessing chord.groups
The d3.chord() reference states that "The chords array also defines a secondary array of length n, chords.groups, where each group represents the combined outflow for node i..."
I've also found several examples on Observable which access chord.groups and they run fine. What am I doing wrong?

The error is very subtle, and here I'd blame the docs for lack of clarity, not you.
The problem is that you passed the data to the chord generator like this:
var chord = d3.chord(matrix)
But d3.chord() doesn't accept arguments. Instead, it returns a function which accepts the arguments. Thus, it should be:
var chord = d3.chord()(matrix)
Alternatively, defining the generator and then passing the data:
const chord = d3.chord();
const chordLayout = chord(matrix);
As you can see, it's a bit different. The confusion is maybe worsened by the fact that some D3 methods, like scales, accept data arguments like that, e. g. d3.scaleLinear(domain, range).
Most of D3 generators are like this. For instance, using a common line generator...
const lineGenerator = d3.line();
You get the path d attribute using:
lineGenerator(data);
Which is the same of d3.line()(data), but it's not the same of d3.line(data).
Here's your working v7 version:
var NameProvider = ["Apple", "HTC", "Huawei", "LG", "Nokia", "Samsung", "Sony", "Other"];
var matrix = [
[9.6899, 0.8859, 0.0554, 0.443, 2.5471, 2.4363, 0.5537, 2.5471], /*Apple 19.1584*/
[0.1107, 1.8272, 0, 0.4983, 1.1074, 1.052, 0.2215, 0.4983], /*HTC 5.3154*/
[0.0554, 0.2769, 0.2215, 0.2215, 0.3876, 0.8306, 0.0554, 0.3322], /*Huawei 2.3811*/
[0.0554, 0.1107, 0.0554, 1.2182, 1.1628, 0.6645, 0.4983, 1.052], /*LG 4.8173*/
[0.2215, 0.443, 0, 0.2769, 10.4097, 1.2182, 0.4983, 2.8239], /*Nokia 15.8915*/
[1.1628, 2.6024, 0, 1.3843, 8.7486, 16.8328, 1.7165, 5.5925], /*Samsung 38.0399*/
[0.0554, 0.4983, 0, 0.3322, 0.443, 0.8859, 1.7719, 0.443], /*Sony 4.4297*/
[0.2215, 0.7198, 0, 0.3322, 1.6611, 1.495, 0.1107, 5.4264] /*Other 9.9667*/
];
/*Sums up to exactly 100*/
var colors = ["#C4C4C4", "#69B40F", "#EC1D25", "#C8125C", "#008FC8", "#10218B", "#134B24", "#737373"];
/*Initiate the color scale*/
var fill = d3.scaleOrdinal()
.domain(d3.range(NameProvider.length))
.range(colors);
/*//////////////////////////////////////////////////////////
/////////////// Initiate Chord Diagram /////////////////////
//////////////////////////////////////////////////////////*/
var margin = {
top: 30,
right: 25,
bottom: 20,
left: 25
},
width = 650 - margin.left - margin.right,
height = 600 - margin.top - margin.bottom,
innerRadius = Math.min(width, height) * .39,
outerRadius = innerRadius * 1.04;
/*Initiate the SVG*/
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform", "translate(" + (margin.left + width / 2) + "," + (margin.top + height / 2) + ")");
var chord = d3.chord()
.sortSubgroups(d3.descending) /*sort the chords inside an arc from high to low*/
.sortChords(d3.descending)(matrix);
/*//////////////////////////////////////////////////////////
////////////////// Draw outer Arcs /////////////////////////
//////////////////////////////////////////////////////////*/
var arc = d3.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", function(d) {
return "group " + NameProvider[d.index];
});
g.append("svg:path")
.attr("class", "arc")
.style("stroke", function(d) {
return fill(d.index);
})
.style("fill", function(d) {
return fill(d.index);
})
.attr("d", arc)
.style("opacity", 0)
.transition().duration(1000)
.style("opacity", 0.4);
#chart rect {
fill: steelblue;
}
#chart text {
fill: white;
font: 10px Helvetica;
text-anchor: end;
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<svg id="chart"></svg>

Related

My code for piechart (d3) is not working, how to fix?

For a class in data visualization i need to make a piechart in d3. I wrote the code, but after running on local server it does not work, but also does not show any errors.
I tried to code the chart according to some online examples and studied the elements of a pie chart. I cannot discover the bug in my code.
function draw_d3_piechart(input_data){
// putting the datapoints in an array
var data = {math: input_data["VALUE_MATH"], reading:
input_data["VALUE_READ"], science:input_data["VALUE_SCIENCE"]};
console.log(data);
// the output of this is :
// {math: 387, reading: 398, science: 397}
// determining the width, height
var margin = {top: 50, right: 100, bottom: 0, left: 50},
svgWidth = 500,
svgHeight = 400,
plotWidth = width - margin.left - margin.right,
plotHeight = height - margin.top - margin.bottom,
radius = Math.min(svgWidth, svgHeight) / 2;
// creating svg element in a div that is in the html file
var svg = d3.select("#piechart_area"),
width = svgWidth,
height = svgHeight;
// creating g element for the chart to be written in
var g = svg.append("g")
.attr("transform", "translate(" + svgWidth / 2 + "," +
svgHeight / 2 + ")");
// creating colorscale
var color = d3.scaleOrdinal()
.domain(data)
.range(["#98abc5", "#8a89a6", "#7b6888"]);
// determining size of wedges
var pie = d3.pie()
.sort(null)
.value(function(d) {return d.value; });
var data_ready = pie(d3.entries(data));
g
.selectAll('g')
.data(data_ready)
.enter()
.append('path')
.attr('d', d3.arc()
.innerRadius(0)
.outerRadius(radius)
)
.attr('fill', function(d){ return(color(d.data.key)) })
.attr("stroke", "black")
.style("stroke-width", "2px")
.style("opacity", 0.7);
};
The data looks like this for this specific example:
{"LOCATION":"PER","SUBJECT":"TOT","TIME":2015,"VALUE_READ":398.0,"VALUE_MATH":387.0,"VALUE_SCIENCE":397.0,"TOTAL_VALUE":1182.0}
After calling this function, there is no output at all (except for the console.log which I used to test whether the data was in the right format). No errors either. I would appreciate if someone could send me in the right direction of what I'm doing wrong.

How better organize grid lines?

There are grid lines from points.
Is there another solution with better performance, because if I add many svg elements(etc. rects, circles, paths) and increase the dimension of the grid I will see the freeze effect when I use zoom, move element...
The size of the grid is changed.
Also, how can I create endless grid lines, instead limited (gridCountX, gridCountY)?
Thanks
var svg = d3.select("body").append("svg");
var svgG = svg.append("g");
var gridLines = svgG.append("g").classed("grid-lines-container", true).data(["gridLines"]);
var gridCountX = _.range(100);
var gridCountY = _.range(100);
var size = 10;
gridLines.selectAll("g").data(gridCountY)
.enter()
.append("g")
.each(function(d) {
d3.select(this).selectAll("circle").data(gridCountX).enter()
.append("circle")
.attr("cx", function(_d) {return _d*size;})
.attr("cy", function(_d) {return d*size;})
.attr("r", 0.5)
.attr("style", function() {
return "stroke: black;";
});
});
var zoomSvg = d3.zoom()
.scaleExtent([1, 10])
.on("zoom", function(){
svgG.attr("transform", d3.event.transform);
});
svg.call(zoomSvg);
svg {
width: 100%;
height: 100%;
border: 1px solid #a1a1a1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
As you note, this approach is not really scalable and has a larger impact on performance. I have found the approach of utilizing d3 axes for grids to have minimal performance impact while also being relatively straightforward to incorporate with zoom such that you can have infinite zoom with the grid lines updating in a sensible manner due to the "magic" of automatic generation of sensible tick locations in d3.
To implement something similar in d3 v4, you can do something along these lines:
var svg = d3.select("svg"),
margin = {top: 20, right: 140, bottom: 50, left: 70},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"),
innerSvg = g.append("svg").attr("width", width).attr("height", height);
// Calculate domain for x and y from data and store in x0, y0 (not shown here)
x.domain(x0);
y.domain(y0);
xGridAxis = d3.axisBottom(x).ticks(10);
yGridAxis = d3.axisLeft(y).ticks(10 * height / width);
// Create grouping and additional set of axes for displaying grid
innerSvg.append("g")
.attr("class", "grid x-grid")
.attr("transform", "translate (0," + height + ")")
.call(xGridAxis
.tickSize(-height, 0, 0)
.tickFormat("")
)
.selectAll(".tick");
innerSvg.append("g")
.attr("class", "grid y-grid")
.attr("transform", "translate (" + width + ", 0)")
.call(yGridAxis
.tickSize(width)
.tickFormat("")
);
// Add element to capture mouse events for drag and pan of plots
var zoom = d3.zoom()
.on("zoom", zoomed);
var scrollZoom = innerSvg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("pointer-events", "all") // Defaults to panning with mouse
.call(zoom);
// Mouse panning and scroll-zoom implementation using d3.zoom
// Modification of : http://bl.ocks.org/lorenzopub/013c0c41f9ffab4d27f860127f79c5f5
function zoomed() {
lastEventTransform = d3.event.transform;
// Rescale the grid using the new transform associated with zoom/pan action
svg.select(".x-grid").call(xGridAxis.scale(lastEventTransform.rescaleX(x)));
svg.select(".y-grid").call(yGridAxis.scale(lastEventTransform.rescaleY(y)));
// Calculate transformed x and y locations which are used to redraw all plot elements
var xt = lastEventTransform.rescaleX(x),
yt = lastEventTransform.rescaleY(y);
// Code below just shows how you might do it. Will need to tweak based on your plot
var line = d3.line()
.x(function(d) { return xt(d.x); })
.y(function(d) { return yt(d.y); });
innerSvg.selectAll(".line")
.attr("d", function(d) { return line(d.values); });
innerSvg.selectAll(".dot")
.attr("cx", function(d) {return xt(d.x); })
.attr("cy", function(d) {return yt(d.y); });
}
Here is a worked out example in d3 v4 that inspired my version above:
http://bl.ocks.org/lorenzopub/013c0c41f9ffab4d27f860127f79c5f5

Not able to center D3 pie chart

I'm trying to draw a circle with different data values as angles but for some reason, it's only the last data point that gets the color and display. I've tried to translate the svg but it seems not to budge.
I'm fairly new to D3 so I'm sure I've done something less intelligent without realizing it. As far I could tell, the angles in the g and path elements are as supposed to.
var height = 400, width = 600, radius = Math.min(height, width) / 2;
var colors = ["#red", "pink", "green", "yellow", "blue","magent","brown","olive","orange"];
var data = [1,2,1,2,1,2,1,3,1];
var chart = d3.select("#chart").append("svg")
.attr("width", width).attr("height", height);
chart.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var pie = d3.layout.pie().sort(null).value(function (d) { return d; });
var arc = d3.svg.arc().startAngle(0).innerRadius(0).outerRadius(radius);
var grx = chart.selectAll(".sector").data(pie(data))
.enter().append("g").attr("class", "sector");
grx.append("path")
.attr("d", arc)
.style("fill", function (d, i) {
console.log(d);
return colors[i];
});
The problem is that you're appending all the sectors of the pie to the svg node when they should be appended to the translated g node, you have two options to solve this problem
make chart equal to the translated g node
select g before all the .sectors and store that in grx
The first solution is simpler e.g.
var chart = d3.select("#chart").append("svg")
.attr("width", width).attr("height", height);
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
demo

Wrong point of origin reference

I'm facing problem with wrong point of origin when multiple elements are added to an SVG.
JSFiddle: https://jsfiddle.net/sbc6ejeu/2/
I've added an SVG and associated path and couple of circles to it. They seem to correspond to the correct origin. However when I move the slider, I expect the circle of id=movingCicle (as mentioned in the code) to move along the green curve (line). I'm unable to start the initial
position of the circle to the same origin as other svg elements.
Also I observe that the range of the red circle is not same as the other elements or the SVG to which it is appended. For the 2nd and 3rd drop down options, the red cicle moves out of the graph when the slider is increased. I feel I'm missing out on something.
Appretiate any help on this.Thanks!
function initialize() {
// Add circle data
jsonCircles = [{
"xAxis": 50,
"yAxis": 154
}, {
"xAxis": 150,
"yAxis": 154
}];
// Set the dimensions of the canvas / graph
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
};
width = 600 - margin.left - margin.right;
height = 270 - margin.top - margin.bottom;
// Set the ranges
x = d3.scale.linear().range([0, width]);
y = d3.scale.linear().range([height, 0]);
// Define the axes
xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(10);
yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(7);
valueLine = d3.svg.line()
.x(function(d, i) {
return x(i);
})
.y(function(d) {
return y(d);
});
// Adds the svg canvas
svg = d3.select("#graph")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "svg1")
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
}
function updateCirclePosition(i) {
d3.select("#movingCircle").remove();
svg.append("circle")
.attr("cx", +i)
.attr("cy", yValues[i])
.attr("r", 5)
.attr("id", "movingCircle")
.style("fill", "red");
}
function addSvgElements() {
// Add the valueline path.
var path = svg.append("path")
.attr("class", "line")
.attr("id", "lineId")
.attr("d", valueLine(yValues));
}
Inside the function updateCirclePosition, the variable i contains the value of the budget, and yValues[i] is the corresponding revenue.
The corresponding coordinates in the chart can be found using x and y functions, therefore x(i) and y(yValues[i]) should be used to set the correct cx and cy:
svg.append("circle")
.attr("cx", x(i))
.attr("cy", y(yValues[i]))
updated fiddle: https://jsfiddle.net/sbc6ejeu/5/

D3, plotting to map, possible data formatting

I am trying to create a map and plot some points to it using d3, I found a few good examples to build off of but I believe i am stuck. My guess is I am not handling the plot points correctly according to how I have the data structured. I could use a bit of help - this is my first attempt. Here's what I have so far :
var m_width = document.getElementById("map").offsetWidth,
width = 938,
height = 500;
var projection = d3.geo.mercator()
.scale(150)
.translate([width / 2, height / 1.5]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#map").append("svg")
.attr("preserveAspectRatio", "xMidYMid")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("width", m_width)
.attr("height", m_width * height / width);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height)
var g = svg.append("g");
d3.json("scripts/world-110m2.json", function(error, us) {
g.append("g")
.attr("id", "countries")
.selectAll("path")
.data(topojson.feature(us, us.objects.countries).features)
.enter()
.append("path")
.attr("id", function(d) { return d.id; })
.attr("d", path)
});
svg.selectAll(".pin")
.data(places)
.enter().append("circle", ".pin")
.attr("r", 5)
.attr("transform", function(d) {
return "translate(" + projection([
d.earthquakes.lon,
d.earthquakes.lat
]) + ")"
});
window.addEventListener('resize', function(event){
var w = document.getElementById("map").offsetWidth;
svg.attr("width", w);
svg.attr("height", w * height / width);
});
And the "places" data is structured like so
var places = {"count":"392","earthquakes":[{"src":"us","eqid":"2010sdbk","timedate":"2010-01-31 15:18:44","lat":"-18.7507","lon":"169.3940","magnitude":"5.1","depth":"231.50","region":"Vanuatu"}
Where all the place are inside an object array "earthquakes" inside places. (lon and lat specifically inside of that).
The world map shows up fine, I am just having trouble getting these plot points to work. Would appreciate any help greatly. Thanks for reading!!
You almost had it, but couple problems here:
1.) The data you pass to .data should be an array (of where to add your circles).
2.) In your places object, you lat/lon are strings and need to be converted to numbers.
Try:
var places = {
"count": "392",
"earthquakes": [{
"src": "us",
"eqid": "2010sdbk",
"timedate": "2010-01-31 15:18:44",
"lat": "-18.7507",
"lon": "169.3940",
"magnitude": "5.1",
"depth": "231.50",
"region": "Vanuatu"
}]
};
svg.selectAll(".pin")
.data(places.earthquakes) //<-- pass array
.enter()
.append("circle")
.attr("class","pin")
.attr("r", 5)
.attr("transform", function(d) {
return "translate(" + projection([
+d.lon, //<-- coerce to number
+d.lat
]) + ")";
});
Example here.

Categories

Resources