D3.js: Nested selections? - d3.js

I'm working in D3.js and trying to understand nested selections and the general update pattern.
My data looks like this:
var data1 = [
{ 'name': 'LHR', 'position': 1, 'flights': [1,10,12,14,3] },
{ 'name': 'LAX', 'position': 2, 'flights': [3,6,12,6,3] },
{ 'name': 'SFO', 'position': 3, 'flights': [2,8,17,18,5] }
];
The rendered SVG is this:
<g class="airport LAX">
<text class="LAX legend" x="250" y="40" dy=".2em">LAX</text>
<circle class="flight" cx="20" cy="16" r="3"></circle>
<circle class="flight" cx="40" cy="22" r="3"></circle>
<circle class="flight" cx="60" cy="34" r="3"></circle>
<circle class="flight" cx="80" cy="22" r="3"></circle>
<circle class="flight" cx="100" cy="16" r="3"></circle>
</g>
And I'd like smooth transitions for text and circle elements that are being added, updated and removed - new elements appearing from the top of the graph, updated elements moving smoothly, and deleted elements falling off the bottom of the graph.
Here is my code so far: http://jsfiddle.net/kqxhj/5/
I can transition new and updated elements. But what I can't do, for some reason, is get the exit selection for these elements to remove them nicely.
How can I get the exit selection for these circle and text elements, so that I can transition them smoothly off the bottom of the graph?

You only need one transition to remove the elements for a particular airport as everything is in a group. I've updated your jsfiddle here with the transition that moves everything to the bottom.
The key changes are that I've split the transition into two to first move the elements and then remove them and second to set the "transform" attribute instead of "y" which does nothing for groups. The relevant code is also below.
g.exit().transition()
.duration(1000)
.attr("transform", "translate(0," + 1.5*h + ")");
g.exit().transition().delay(1000)
.remove();

Related

How can I show percentages in MudCharts?

I have managed to make a basic donut chart with MudBlazor, but I can not figure out how to show the percentages of the data displayed. For instance, in the attached image I want to show 50% on each side and preferably in the middle of each half circle so it's clear which percentage belongs to the corresponding half circle. Example chart
This is what I currently have:
<MudChart ChartType="ChartType.Donut" Width="300px" Height="300px" InputData="#data" InputLabels="#labels">
</MudChart>
#code {
public double[] data = { 77, 77 };
public string[] labels = { "Oil", "Coal" };
}
Any help is much appreciated.
I'm not sure if there is a solution for your question but you can put some information inside of the donut chart. See example below. I found it in the docs.
<MudChart ChartType="ChartType.Donut" Width="300px" Height="300px"
InputData="#data" InputLabels="#labels">
<CustomGraphics>
<text class="donut-inner-text" x="47%" y="35%" dominant-baseline="middle" text-anchor="middle" fill="black" font-size="2">Total</text>
<text class="donut-inner-text" x="47%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="black" font-size="5">#data.Sum().ToString()</text>
</CustomGraphics>
</MudChart>

SVG Logo animation

So I have a client logo and I want to animate it on scroll. Let's say that the logo is DANIEL. As the user scrolls down the page i want the spacing between the letters to expand so it would end up being D A N I E L.
I have seen how to do this with regular text but this will be as I said an SVG logo. I have been searching around buy didn't find anything. Also needs to be mobile friendly. Any tips out there?
This is actually very simple. You just need to watch for scroll events, then update the <text> element based on how far down the page you have scrolled.
I've achieved the letter spacing using the letter-spacing presentation attribute.
window.addEventListener("scroll", function() {
document.getElementById("mytext").setAttribute("letter-spacing", (window.scrollY / window.outerHeight) + "em");
});
body {
height: 2000px;
}
svg {
position: fixed;
}
<svg viewBox="0 0 600 100">
<text id="mytext" x="300" y="70" font-size="50" text-anchor="middle">DANIEL</text>
</svg>
Update
For other non-text elements, like <path> etc, you will need to take a slightly different approah. You'll need to physically move them with a transform.
Here is a quick demo. You'll need to tweak it to get the objects to move where you want them.
var EXPAND_AMOUNT = 40;
window.addEventListener("scroll", function() {
var scrollPercent = window.scrollY / window.outerHeight;
document.getElementById("obj1").setAttribute("transform", "translate("+(scrollPercent * 3 * -EXPAND_AMOUNT)+",0)");
document.getElementById("obj2").setAttribute("transform", "translate("+(scrollPercent * 2 * -EXPAND_AMOUNT)+",0)");
document.getElementById("obj3").setAttribute("transform", "translate("+(scrollPercent * -EXPAND_AMOUNT)+",0)");
document.getElementById("obj4").setAttribute("transform", "translate("+(scrollPercent * EXPAND_AMOUNT)+",0)");
document.getElementById("obj5").setAttribute("transform", "translate("+(scrollPercent * 2 * EXPAND_AMOUNT)+",0)");
document.getElementById("obj6").setAttribute("transform", "translate("+(scrollPercent * 3 * EXPAND_AMOUNT)+",0)");
});
body {
height: 2000px;
}
svg {
position: fixed;
}
<svg viewBox="0 0 600 100">
<rect id="obj1" x="165" y="25" width="40" height="50"/>
<rect id="obj2" x="210" y="25" width="40" height="50"/>
<rect id="obj3" x="255" y="25" width="40" height="50"/>
<rect id="obj4" x="300" y="25" width="40" height="50"/>
<rect id="obj5" x="345" y="25" width="40" height="50"/>
<rect id="obj6" x="390" y="25" width="40" height="50"/>
</svg>

Center D3 Piechart in the center of a div, respecting it's dimensions

I'm having some difficulties with centering a piechart I created in D3 in it's parents div. I've set up a JS-fiddle right here: https://jsfiddle.net/86czgLnu/
The problem is that it overflows it's parents dimensions and I want it to scale to fit and be in the center of the parent. I found documentation about preserveAspectRatio and viewBox but wasn't able to fix it with those.
All the code is in the fiddle
Thanks for having a look.
Unfortunately, you can't use percentages inside translate. The best idea would be using JS (D3, in your case) to get the size of the container and translate the group appropriately, by half its width.
Here is your SVG with absolute values for the translate:
<div class="js-graph ct-chart">
<svg width="100%" height="100%">
<g transform="translate(50,50) scale(0.2)">
<path class="slice" fill="#8c564b" d="M1.3532347130578253e-14,-221A221,221,0,1,1,-119.28791713380294,186.04137396256502L0,0Z" original-title=""></path>
<path class="slice" fill="#c49c94" d="M-119.28791713380294,186.04137396256502A221,221,0,0,1,-210.51725450349846,-67.25686252204494L0,0Z"></path>
<path class="slice" fill="#e377c2" d="M-210.51725450349846,-67.25686252204494A221,221,0,0,1,-4.059704139173476e-14,-221L0,0Z"></path>
</g>
</svg>
</div>
An alternative to simulate a translate by percentage is using an inner SVG, as described in this answer by Robert Longson. However, it won't work, because the pie chart is drawn at the origin:
<div class="js-graph ct-chart">
<svg width="100%" height="100%">
<svg x="50%" y="50%">
<g transform="scale(0.2)">
<path class="slice" fill="#8c564b" d="M1.3532347130578253e-14,-221A221,221,0,1,1,-119.28791713380294,186.04137396256502L0,0Z" original-title=""></path>
<path class="slice" fill="#c49c94" d="M-119.28791713380294,186.04137396256502A221,221,0,0,1,-210.51725450349846,-67.25686252204494L0,0Z"></path>
<path class="slice" fill="#e377c2" d="M-210.51725450349846,-67.25686252204494A221,221,0,0,1,-4.059704139173476e-14,-221L0,0Z"></path>
</g>
</svg>
</svg>
</div>
PS: scale is part of the "transform" attribute.

Map Function to Selected Elements

Let's assume three circles within an svg.
<svg height="400" width="400">
<circle cx="100" cy="110" r="10"></circle>
<circle cx="200" cy="210" r="10"></circle>
<circle cx="300" cy="310" r="10"></circle>
</svg>
Now I would like to use d3.js to compose an array of objects representing the circles positions like so:
[{"x":100,"y":110},{"x":200,"y":210},{"x":300,"y":310}]
My intuition and rather rusty knowledge of d3.js tells me that something similiar to this should work:
d3.selectAll("circle").map(function () {
return {
"x": d3.select(this).attr("cx"),
"y": d3.select(this).attr("cy")
};
});
I map a function to the array of selected circles returning an object.
But it doesn't. Could somebody help me with this?
code on jsfiddle.net
D3 isn't really meant for this kind of thing, but if you absolutely want to, you can get into its internal data structures to extract the list of nodes and iterate over them:
var locations = d3.selectAll("circle")[0].map(function(el) {
return {
"x": d3.select(el).attr("cx"),
"y": d3.select(el).attr("cy")
};
})
Complete demo here.

SVG generated with D3 doesn't scale like seemingly identical static SVG content

I'm generating an svg visualization with d3 that I'd like to have scale with it's container in a responsive way. I can directly create an SVG element that behaves this way, but when I write a d3 script that generates the same SVG markup, it doesn't scale.
Here's the d3 code that generates the SVG element:
var arc = d3.svg.arc()
.startAngle(0)
.innerRadius(36)
.outerRadius(48);
var svg = d3.select('#d3').append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewbox', '0, 0, 100, 100')
.attr('preserveAspectRatio', 'xMidYMid')
.append('g')
.attr('transform', 'translate(50, 50)');
var loop = svg.append('g')
.attr('class', 'percent-wheel');
loop.append('path')
.attr('class', 'background')
.attr('d', arc.endAngle(2 * Math.PI));
And here's the resulting SVG markup, which scales correctly when inserted into the DOM statically:
<div id="d3" class="container">
<svg preserveAspectRatio="xMidYMid" viewbox="0, 0, 100, 100" height="100%" width="100%">
<g transform="translate(50, 50)">
<g class="percent-wheel">
<path d="M0,48A48,48 0 1,1 0,-48A48,48 0 1,1 0,48M0,36A36,36 0 1,0 0,-36A36,36 0 1,0 0,36Z" class="background"></path>
</g>
</g>
</svg>
</div>
You can see a live example here:
http://jsfiddle.net/HMQGq/
Can anyone explain what's different about creating the svg via d3 that causes this to happen?
The problem is a typo in the attribute definition -- it should be viewBox instead of viewbox.

Resources