Map Function to Selected Elements - d3.js

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.

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.

D3.js: Nested selections?

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();

SVG frame-based animation

Could anyone suggest what is the best way to implement frame-based animation in svg, based on JPEG's?
One example which I've found is this:
<svg version="1.1" baseProfile="tiny" id="svg-root"
width="100%" height="100%" viewBox="0 0 480 360"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="320" height="240" xlink:href="test1.jpg">
<animate id='frame_0' attributeName='display' values='inline;none'
dur='0.5s' fill='freeze' begin="0s" repeatCount="indefinite"/>
</image>
<image width="320" height="240" xlink:href="test2.jpg">
<animate id='frame_1' attributeName='display' values='none;inline'
dur='0.5s' fill='freeze' begin="0.5s" repeatCount="indefinite" />
</image>
</svg>
It works for 2 frames, but doesn't really scale. I would like to have something which can handle 100 frames and more.
It's much easier:
<svg version="1.1" baseProfile="tiny" id="svg-root"
width="100%" height="100%" viewBox="0 0 480 360"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="320" height="240" xlink:href="test1.jpg">
<animate attributeName="xlink:href"
values="test1.jpg;test2.jpg"
begin="0s" repeatCount="indefinite" dur="1s"/>
</image>
</svg>
An alternative approach,
If your animation is working, but it's just a matter of too much production to get the files setup, you can use a template to generate your SVG.
Use something like Grunt.Js to read all the images in a directory and then, have an underscore template build the SVG frames the way you already have them setup from an array of the filepaths.
These code snippets might not work out of the box, but it's pretty close.
Here the grunt file grabs the files in the folder, check if they're images then pushes them to an array.
// gruntfile.js //
var fs = require('fs');
var path = require('path');
var _ = require("underscore");
grunt.registerTask('Build Animated SVG', function () {
var template = grunt.file.read("/path to SVG underscore template/"); //see SVG section below.
var frames = [];
var pathtoframes = "mypath";
var mySVG = "mysvg.svg";
fs.readdirSync(path.resolve(pathtoframes)).forEach(function (file) {
if (filetype == "svg" || filetype == "png" || filetype == "jpg" || filetype == "gif") {
var frame = {};
frame.src = pathtoframes + "/" + file;
frames.push(frame);
}
});
var compiledSVG = _.template(template, {frames:frames});
grunt.file.write(path.resolve(pathtoframes) + "/compiled_" + mySVG, compiledSVG);
});
This template will get read in by the grunt file, and underscore will loop through each file and write that into a big string. Grunt then saves that out as an SVG that you can load.
<!-- SVG underscore template -->
<svg version="1.1" baseProfile="tiny" id="svg-root"
width="100%" height="100%" viewBox="0 0 480 360"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<% _.each(frames, function(frame, index){ %>
<image width="320" height="240" xlink:href="<%= frame.src %>">
<animate
id='frame_<%= index %>'
attributeName='display'
values='none;inline'
dur='0.5s'
fill='freeze'
begin="0.5s"
repeatCount="indefinite"
/>
</image>
<% } %>
</svg>
https://michaelsboost.github.io/SVGAnimFrames/
You can easily use my library SVGAnimFrames for this. Simply by calling 1 line of code...
SVGAnimFrames("#bounce svg", "repeat", "40", "0");
Refer to the Github repo to learn how to use it.
Ideally your best bet is to use a spritesheet and animate frame by frame with that which minimizes unnecessary http requests.

Resources