Reading in external data in d3 JavaScript - an R r2d3 Use Case - javascript

EDIT: link for all data/code used in example: https://drive.google.com/open?id=16MpDptwV7m4nOkoT3ImlKffl4rYqc5ms
Hello friends and roasters alike,
I'm about as novice as can be with D3 visualization. My background is all in Plotly and integrated R platform plots. I have written very very light js/css for Shiny apps, but I'm trying to branch out into more custom and free visual methods.
So I've been diving through the r2d3 package for d3 integration in R. I've searched through all of the examples and pored through whatever documentation I could find in the master repo and overview pages here: https://rstudio.github.io/r2d3/articles/gallery/calendar/
But, for the life of me I simply can't wrap my head around how the js is actually pulling in the data
An example here: the visual, following by the script that produces it, and finally the csv provided as the data source to visualize
Visual:
calendar.js script:
// !preview r2d3 data = read.csv("dji-latest.csv"), d3_version = 4,
container = "div", options = list(start = 2006, end = 2011)
// Based on https://bl.ocks.org/mbostock/4063318
var height = height / (options.end - options.start),
cellSize = height / 8;
var formatPercent = d3.format(".1%");
var color = d3.scaleQuantize()
.domain([-0.05, 0.05])
.range(["#a50026", "#d73027", "#f46d43", "#fdae61", "#fee08b", "#ffffbf", "#d9ef8b", "#a6d96a", "#66bd63", "#1a9850", "#006837"]);
var svg = div
.style("line-height", "0")
.selectAll("svg")
.data(d3.range(options.start, options.end))
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + cellSize * 3.5 + "," + (height - cellSize * 7 - 1) + ")");
svg.append("text")
.attr("transform", "translate(-" + (6 * height / 60) + "," + cellSize * 3.5 + ")rotate(-90)")
.attr("font-family", "sans-serif")
.attr("font-size", 2 + 8 * height / 60)
.attr("text-anchor", "middle")
.text(function(d) { return d; });
var rect = svg.append("g")
.attr("fill", "none")
.attr("stroke", "#ccc")
.attr("stroke-width", "0.25")
.selectAll("rect")
.data(function(d) { return d3.timeDays(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("rect")
.attr("width", cellSize)
.attr("height", cellSize)
.attr("x", function(d) { return d3.timeWeek.count(d3.timeYear(d), d) * cellSize; })
.attr("y", function(d) { return d.getDay() * cellSize; })
.datum(d3.timeFormat("%Y-%m-%d"));
svg.append("g")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", "0.25")
.selectAll("path")
.data(function(d) { return d3.timeMonths(new Date(d, 0, 1), new Date(d + 1, 0, 1)); })
.enter().append("path")
.attr("d", pathMonth);
r2d3.onRender(function(csv, div, width, height, options) {
var data = d3.nest()
.key(function(d) { return d.Date; })
.rollup(function(d) { return (d[0].Close - d[0].Open) / d[0].Open; })
.object(csv);
rect.filter(function(d) { return d in data; })
.attr("fill", function(d) { return color(data[d]); })
.append("title")
.text(function(d) { return d + ": " + formatPercent(data[d]); });
});
function pathMonth(t0) {
var t1 = new Date(t0.getFullYear(), t0.getMonth() + 1, 0),
d0 = t0.getDay(), w0 = d3.timeWeek.count(d3.timeYear(t0), t0),
d1 = t1.getDay(), w1 = d3.timeWeek.count(d3.timeYear(t1), t1);
return "M" + (w0 + 1) * cellSize + "," + d0 * cellSize
+ "H" + w0 * cellSize + "V" + 7 * cellSize
+ "H" + w1 * cellSize + "V" + (d1 + 1) * cellSize
+ "H" + (w1 + 1) * cellSize + "V" + 0
+ "H" + (w0 + 1) * cellSize + "Z";
}
And this is the .csv fed in
And I know this is completely a source of my own understanding of js function call and data handling, but this is simply stumping me to no end. I can see some .data inits and function calls within, but no where do I find any indication of what this visualization is supposed to catch. How does it know which of the columns denotes the dates? Where is the variable specified to actually visualize?
Any inkling of help here would be immensely appreciated. I've gotten some d3 tutorials on my horizon, but any pointers can at least get me playing with the sandboxes those smarter than I have built :)
Thank you!

I know it is an old post... but I ended up here and I think it may be a good idea to write something for further reference.
How does it know which of the columns denotes the dates? Where is the variable specified to actually visualize?
This example is a bit tricky (or at least misleading) for beginners, but the piece of code specifying the variables/dates is this one:
var data = d3.nest()
.key(function(d) { return d.Date; })
.rollup(function(d) { return (d[0].Close - d[0].Open) / d[0].Open; })
.object(csv);
You can see what exactly d3.nest does here. In a nutshell, R passes the data variable (named csv in the js side) to js by translating the table in dji-latest.csv to a js-friendly object, like (in R syntax):
data <- list(
list(Date = "2010-10-01", Open = 10789.72, High = ...),
list(Date = "2010-09-30", Open = 10789.72, High = ...),
)
The specific variables are then selected via d.Dates, d.Close and d.Open in key and rollup function definitions.
Note that csv in the function above refers to data passed from R, because it is the first argument in the function inside r2d3.onRender by default, and that may be the source of confusion. In the js side csv is fed to nest to produced the nested data object required for this specific visualization.
As others have said, it is hard to give a better explanation than reading the docs and this example is pretty straightforward.

Related

d3 pie: redraw label on path change

I'm using d3 to draw a pie with radio button to change the paths showed. My problem is to recalculate the label position based on the new paths. On load the labels are drawn correctly but on click the position still the same of first load.
I think that the problem is that the g's take only the first data value and i don't know how to say to take the current data values.
The function that draw the labels is
//Labels
d3.selectAll("g").select("text").transition()
.ease("linear")
.duration(500)
.style("opacity", 0).remove();
svg.selectAll("g").append("text")
.attr("transform", function (d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("dy", ".35em")
.style("opacity", 0)
.style("fill", "#000")
.style("font-size", 12)
.attr("text-anchor", function (d) {
// are we past the center?
return (d.endAngle + d.startAngle) / 2 > Math.PI ?
"end" : "start";
})
.text(function (d) { return d.data.label; })
.transition()
.ease("linear")
.delay(1000)
.duration(500)
.style("opacity", 1);
For more info see https://jsfiddle.net/w0ckw4tb/
Thanks
Just apply new data binding before you append new text nodes:
svg.selectAll("g").data(pie) // <== !!!
.append("text")
.attr("transform" ...
If you did not do it, this code:
.attr("transform", function (d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
returns the same value for transform attribute so the labels remain at the same place.
Check working fiddle.

Curved labels on circular diagram (D3.js)

So, I'm basically trying to make a multilevel circular partition (aka sunburst diagram) with D3.js (v4) and a JSON data.
I placed some labels, which must have different angles depending of their levels (circles) on the partition :
- Level < 3 must be curved and "follow" the arc radius.
- level == 3 must be straight and perpendicular of the arc radius.
I didn't use textPath tags, because I'm not really experienced in SVG and it looks overly complicated to me, and I don't really know how to use it.
here's my code (without the JSON but this is a really classical one, I can add a part of it if it is needed):
var width = 800;
var height = 800;
var radius = 400;
var formatNumber = d3.format(",d");
var x = d3.scaleLinear().range([0, 2 * Math.PI]);
var y = d3.scaleSqrt().range([0, radius]);
var arc = d3.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); })
.innerRadius(function(d) { return setRadius("inner", d.data.level); })
.outerRadius(function(d) { return setRadius("outer", d.data.level); });
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + (height/2) + ")");
var hierarchy = d3.hierarchy(dataset)
.sum(function(d) { return d.size; });
var partition = d3.partition();
svg.selectAll("path")
.data(partition(hierarchy).descendants())
.enter().append("path")
.attr("id", function(d, i){ return "path" + i; })
.attr("d", arc)
.attr("stroke", "white")
.attr("stroke-width", "1px")
.style("fill", function(d) { return (d.data.color) ? d.data.color : 'black'; });
svg.selectAll("text")
.data(partition(hierarchy).descendants())
.enter().append("text")
.attr("transform", function(d){ return setLabelPosition(d); })
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-size", "18px")
.attr("fill", function(d){ return d.data.textcolor; })
.text(function(d){ if(parseInt(d.data.level) > 0 && parseInt(d.data.level) < 4){ return (d.data.name).toUpperCase(); }});
d3.select(self.frameElement)
.style("height", height + "px");
function setRadius(side, level){
var result = 0;
var innerValues = [0, 120, 180, 240, 365];
var outerValues = [0, 180, 240, 365, 400];
if(!side){
throw error;
}
if(side === "inner"){
result = innerValues[level];
}
if(side === "outer"){
result = outerValues[level];
}
return result;
};
function setLabelPosition(d){
var result = '';
var angle = 0;
var centroid = arc.centroid(d);
if(parseInt(d.data.level) === 3){
angle = (180/Math.PI * (arc.startAngle()(d) + arc.endAngle()(d))/2 - 90);
if(angle > 90){
angle = angle - 180;
}
result = "translate(" + centroid + ")rotate(" + angle + ")";
} else {
angle = (180/Math.PI * (arc.startAngle()(d) + arc.endAngle()(d))/2);
result = "translate(" + centroid + ")rotate(" + angle + ")";
}
return result;
};
And the result :
My problem is, how to curve these level 1 & 2 labels (like the one which have a red border), but keep my lvl 3 labels as they currently are.
It's really a pain in the head, and I did many search (on Google and SO) but I didn't find any satisfying answer.
A solution without using a textPath will be awesome if possible, but any advice is welcome.
Many thanks guys and sorry for my English (as you can probably see it's not my birth language).
PS : This is D3.js v4.

How to update a d3 visualization without redrawing every frame?

I am using d3 to create a waveform visualization. I have it looking great, but in my efforts to design this waveform I have paid no attention to doing it an optimized way; I just was trying to get it to work.
setInterval(function() {
if (refresh) waveform();
if (palette_refresh) {
palette_refresh = false;
updateColorPalette();
}
}, options.refresh_rate);
function waveform() {
analyser.getByteFrequencyData(fd);
document.getElementById('wf_box').innerHTML = "";
var data = ... irrelevant implementation details ...
var chart = d3.select("#wf_box").append("svg")
.attr("class", "chart")
.attr("width", "" + percent_offset + "%")
.attr("style", "padding-left:" + (100 - percent_offset) + "%;")
.attr("viewBox", "0 0 " + Math.max(w * data.length, 0) + " " + Math.max(h, 0));
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d, i) {
var x_offset = x(i) - Math.max(w * max / 900 - 0.25, 0.1) / 2;
return x_offset;
})
.attr("y", function(d, i) {
var height = y(d * options.bar_height) / h;
return h - Math.pow(Math.max(height, 0.01), 1.5);
})
.attr("width", function(d) {
return '1px';
})
.attr("height", function(d, i) {
var height = y(d * options.bar_height) / h;
return Math.pow(Math.max(height, 0.01), 1.5) + options.bar_y_offset;
});
}
As you can see, I am calling the waveform function repeatedly using options.refresh_rate as my speed. I would imagine that there is a more efficient way of updating the data used in my waveform than deleting the waveform from the DOM and redrawing it every frame. Does anybody have any insight to how I can accomplish this?
Thanks!
Take a look at the enter/exit/update-pattern in d3 https://bl.ocks.org/mbostock/3808218 and see if that can give you a hint.

D3 X-value mouseover - function returns NaN

So I am trying to adapt M Bostock's x-value mouseover example to my own graph, the main difference being that I have multiple series instead of his one. For the moment I'm just trying to get the circles to work. My problem is that when I mouseover the graph (in Firebug) I get the message: Unexpected value translate(<my_x>, NaN) parsing transform attribute. I've tried several different ways to fix it but I get the same response each time. What am I doing wrong?
I have a jsFiddle, and the issue is at the bottom:
var focus = main.append('g')
.attr('class', 'focus')
.style('display', 'none');
var circles = focus.selectAll('circle')
.data(sets) // sets = [{name: ..., values:[{date:..., value:...}, ]}, ]
.enter()
.append('circle')
.attr('class', 'circle')
.attr('r', 4)
.attr('stroke', function (d) {return colour(d.name);});
main.append('rect')
.attr('class', 'overlay')
.attr('width', w)
.attr('height', h)
.on('mouseover', function () {focus.style('dispaly', null);})
.on('mouseout', function () {focus.style('display', 'none');})
.on('mousemove', mousemove);
function mousemove() {
var x0 = x_main.invert(d3.mouse(this)[0]),
i = bisectDate(dataset, x0, 1),
d0 = dataset[i - 1].date,
d1 = dataset[i].date,
c = x0 - d0 > d1 - x0 ? [d1, i] : [d0, i - 1];
circles.attr('transform', 'translate(' +
x_main(c[0]) + ',' +
y_main(function (d) {return d.values[c[1]].value;}) + ')'
);
== EDIT ==
Working jsFiddle
You're passing in a function definition into your y_main scale:
circles.attr('transform', 'translate(' +
x_main(c[0]) + ',' +
y_main(function (d) {return d.values[c[1]].value;}) + ')'
);
selection.attr can take a string value or a callback function but this is trying mixing both of those. You're passing in a string and as the string is constructed it tries to scale the function itself as a value, which will return NaN.
The function version should look like this (returning the entire transform value):
circles.attr('transform', function(d) {
return 'translate(' +
x_main(c[0]) + ',' +
y_main(d.values[c[1]].value) + ')';
});

How can I efficiently convert data from string to int within a d3 method chain?

I'm making an interactive bar chart in d3, and have come across a problem. The bar chart reads data from a form, but it reads the data as a string. When I am using the data to draw bars like this:
var bars = svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d,i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d * 4);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d * 4;
})
.attr("fill", function(d) {
return "rgb(" + (d * redLevel) + ", 0, " + (d * blueLevel) + ")";
});
the data is read as a string. I could use parseInt(d) every time I wanted to use d, but that would be grossly inefficient. It would be easy to do var d = parseInt(d) outside of the method chain, but that wouldn't work with d3. Suggestions?
You could map the data before you bind it:
.data(dataset.map(function(d) { return +d; }))
Then unary + operator converts a numeric string into a number.

Categories

Resources