d3.js: Rescale y axis of multiple svgs - javascript

I have multiple SVG created via d3.js, all containing simple line charts.
Their y.domain goes from 0 to the max value:
y.domain([0, d3.max(data, function (d) {
return d.value;
})]);
Now I want to rescale all of them. So I want to use
y.domain(d3.extent(data, function (d) {
return d.value;
}));
on all SVG charts by clicking one button.
I am pretty new to this. It would be awesome to get some advice, if this was understandable enough.

Related

Bubble Map with leaflet and D3.js [problem] : bubbles overlapping

I have a basic map here, with dummy data. Basically a bubble map.
The problem is I have multiple dots (ex:20) with exact same GPS coordinates.
The following image is my csv with dummy data, color blue highlight overlapping dots in this basic example. Thats because many compagny have the same city gps coordinates.
Here is a fiddle with the code I'm working on :
https://jsfiddle.net/MathiasLauber/bckg8es4/45/
Many research later, I found that d3.js add this force simulation fonction, that avoid dots from colliding.
// Avoiding bubbles overlapping
var simulationforce = d3.forceSimulation(data)
.force('x', d3.forceX().x(d => xScale(d.longitude)))
.force('y', d3.forceY().y(d => yScale(d.latitude)))
.force('collide', d3.forceCollide().radius(function(d) {
return d.radius + 10
}))
simulationforce
.nodes(cities)
.on("tick", function(d){
node
.attr("cx", function(d) { return projection.latLngToLayerPoint([d.latitude, d.longitude]).x; })
.attr("cy", function(d) {return projection.latLngToLayerPoint([d.latitude, d.longitude]).y; })
});
The problem is I can't make force layout work and my dots are still on top of each other. (lines: 188-200 in the fiddle).
If you have any tips, suggestions, or if you notice basic errors in my code, just let me know =D
Bunch of code close to what i'm trying to achieve
https://d3-graph-gallery.com/graph/circularpacking_group.html
https://jsbin.com/taqewaw/edit?html,output
There are 3 problems:
For positioning the circles near their original position, the x and y initial positions need to be specified in the data passed to simulation.nodes() call.
When doing a force simulation, you need to provide the selection to be simulated in the on tick callback (see node in the on('tick') callback function).
The simulation needs to use the previous d.x and d.y values as calculated by the simulation
Relevant code snippets below
// 1. Add x and y (cx, cy) to each row (circle) in data
const citiesWithCenter = cities.map(c => ({
...c,
x: projection.latLngToLayerPoint([c.latitude, c.longitude]).x,
y: projection.latLngToLayerPoint([c.latitude, c.longitude]).y,
}))
// citiesWithCenter will be passed to selectAll('circle').data()
// 2. node selection you forgot
const node = selection
.selectAll('circle')
.data(citiesWithcenter)
.enter()
.append('circle')
...
// let used in simulation
simulationforce.nodes(citiesWithcenter).on('tick', function (d) {
node
.attr('cx', function (d) {
// 3. use previously computed x value
// on the first tick run, the values in citiesWithCenter is used
return d.x
})
.attr('cy', function (d) {
// 3. use previously computed y value
// on the first tick run, the values in citiesWithCenter is used
return d.y
})
})
Full working demo here: https://jsfiddle.net/b2Lhfuw5/

How to crossfilter histogram and scatterplot matrix in d3 v4?

I am using this kind of scatterplot matrix and a histogram as two views, in d3. Both of them get the data from the same csv file. This is how the histogram looks like (x axis):
To brush the histogram I use the code below, which is similar to this snippet:
svg.append("g")
.attr("class", "brush")
.call(d3.brushX()
.on("end", brushed));
function brushed() {
if (!d3.event.sourceEvent) return;
if (!d3.event.selection) return;
var d0 = d3.event.selection.map(x.invert),
d1 = [Math.floor(d0[0]*10)/10, Math.ceil(d0[1]*10)/10];
if (d1[0] >= d1[1]) {
d1[0] = Math.floor(d0[0]);
d1[1] = d1[0]+0.1;
}
d3.select(this).transition().call(d3.event.target.move, d1.map(x));
}
How can I link the two views, so that when I brush the histogram, the scatterplot matrix will show the brushed points as colored in red, and the other points as, lets say, grey?
This can get you started:
3 html files:
2 for the visuals (histogram.html and scatter.html)
1 to hold them in iframes (both.html):
Dependency:
jQuery (add to all 3 files)
Create table with 2 cells in both.html:
Add iframes to each cell:
<iframe id='histo_frame' width='100%' height='600px' src='histo.html'></iframe>
<iframe id='scatter_frame' width='100%' height='600px' src='scatter.html'></iframe>
I am using this histogram, and this scatterplot.
Add the linky_dink function to call the function inside your scatter.html (see below...):
function linky_dink(linked_data) {
document.getElementById('scatter_frame').contentWindow.color_by_value(linked_data);
}
In your scatter.html change your cell.selectAll function to this:
cell.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) { return x(d[p.x]); })
.attr("cy", function(d) { return y(d[p.y]); })
.attr("r", 4)
.attr('data-x', function(d) { return d.frequency }) // get x value being plotted
.attr('data-y', function(d) { return d.year }) // get y value being plotted
.attr("class", "all_circles") // add custom class
.style("fill", function(d) { return color(d.species); });
}
Note the added lines in bold:
Now our histogram circle elements retain the x and y values, along with a custom class we can use for targeting.
Create a color_by_value function:
function color_by_value(passed_value) {
$('.all_circles').each(function(d, val) {
if(Number($(this).attr('data-x')) == passed_value) {
$(this).css({ fill: "#ff0000" })
}
});
}
We know from above this function will be called from the linky_dink function of the parent html file. If the passed value matches that of the circle it will be recolored to #ff0000.
Finally, look for the brushend() function inside your histogram.html file. Find where it says: d3.selectAll("rect.bar").style("opacity", function(d, i) { .... and change to:
d3.selectAll("rect.bar").style("opacity", function(d, i) {
if(d.x >= localBrushYearStart && d.x <= localBrushYearEnd || brush.empty()) {
parent.linky_dink(d.y)
return(1)
} else {
return(.4)
}
});
Now, in addition to controlling the rect opacity on brushing, we are also calling our linky_dink function in our both.html file, thus passing any brushed histogram value onto the scatterplot matrix for recoloring.
Result:
Not the greatest solution for obvious reasons. It only recolors the scatterplot when the brushing ends. It targets circles by sweeping over all classes which is horribly inefficient. The colored circles are not uncolored when the brushing leaves those values since this overwhelms the linky_dink function. And I imagine you'd rather not use iframes, let alone 3 independent files. Finally, jQuery isn't really needed as D3 provides the needed functionality. But there was also no posted solution, so perhaps this will help you or someone else come up with a better answer.

D3.js - Updating Bar Chart Data with buttons

So I've been trying to get my d3.js bar chart to transition to different values when a button is pressed. Though, at the moment some elements seem to be adding, but extremely wide and all over the place, as well as the previous elements not being removed.
This is my code:
function updateData(time) {
timeValue = time;
// Get the data again
d3.tsv(timeValue + ".tsv", function(error, data) {
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
// Scale the range of the data again
x.domain(d3.extent(data, function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
//Selecting Data
var bars = svg.selectAll("rect")
.data(data);
//Removing
bars.exit().remove("rect");
//Changes
bars.enter().append('rect')
.attr("class", "bar")
bars.attr('x', function(d) { return x(d.letter); })
.attr('width', x.rangeBand())
.attr('y', function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
//var svg = d3.select("div.innerScreen2").transition();
});
Now I've looked at similar questions asked and tried to apply the solutions, but nothing seems to get removed or change :/ Maybe I have code in the wrong place? Any help would be much appreciated
There are two problems to solve in your question. The first issue is related to the update pattern that some elements are added and some not. Do you have an unique identifier in your data set? If yes, you can use a function within the update process:
// Join new data with old elements, if any.
var text = svg.selectAll("text")
.data(data, function(d) { return d.id; });
Source:http://bl.ocks.org/mbostock/3808234
The second issue is related to your x/y positions and may depend on your underlying data. An data excerpt or debugging information are required to solve this issue.

d3 - how to add color to path object

I'm working on a project where I want a specific section of a line graph to be colored differently based on its value. I am POSITIVE the color function works because I can change the text color of the tooltip to match what I want. However, I cannot get the line itself to change color. I'm pretty sure that I'm missing some insight on the different between d and data...
Here's the relevant code:
var valueline = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.avgs); });
d3.csv("https://gist.githubusercontent.com/emcmahon013/850af022a2d9adc4b82a/raw/f2b553737d3772a206dd3b280366c61d301a141c/temp.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.phase= color(d.phase);
d.avgs = +d.avgs;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([40, d3.max(data, function(d) { return d.sensor; })]);
// Add the valueline path.
lineSvg.append("path") // **********
.attr("class", "line")
.attr("d", valueline(data))
.attr("stroke",function(data){return d.phase;}) //THIS IS THE PROBLEM!
.attr("stroke-width",2);
In addition, the following code does not work:
.attr("stroke",function(data){return data.phase;})
I can place a colorblock function that will work. If the d[i] value (where i= my index) corresponds to red, it will paint red, and if it corresponds to blue will paint blue. I just can't figure out how to get the i values and make this dynamic. Please help!
.attr("stroke",function(data) {return colorblock();}
function colorblock() {
d=data[0]; //will return red line
return d.phase;
}
function colorblock() {
d=data[310]; //will return blue line
return d.phase;
}
You've identified the line with the problem correctly. You're using an undefined variable.
Original:
.attr("stroke",function(data){return d.phase;}) //THIS IS THE PROBLEM!
What you probably wanted:
.attr("stroke",function(data){return data.phase;}) //THIS IS THE SOLUTION!

d3 - xScale not updating?

I'm trying to make a d3 scatterplot with two drop-down menus. The drop-down menus are used to select which datasets to plot against each other. I use two global variables to keep track of which datasets are currently used. "currentX" is the name of the first dataset (on the x-axis) and "currentY" is the name of the second dataset.
My scale functions depend on the values of "currentX" and "currentY". Here is an example of my xScale function:
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d){return d.data[currentX]}), d3.max(dataset, function(d){return d.data[currentX]})
.range([padding, w - padding])
.nice();
My yScale function is the same, but uses currentY instead of currentX. My problem is that when I try to change views of the data, my scale doesn't update. Here is the code for changing between views of the data:
d3.selectAll('select')
.on('change', function() {
// Update currentX and currentY with the currently selected datasets
key = Object.keys(dataset[0].data)[this.selectedIndex];
if (this.getAttribute('id') == 'xSelect') {currentX = key}
if (this.getAttribute('id') == 'ySelect') {currentY = key}
// Change data used in the scatterplot
svg.selectAll('circle')
.data(dataset)
.transition()
.duration(1000)
.attr('cx', function(d) { return xScale(d.data[currentX]) })
.attr('cy', function(d) { return yScale(d.data[currentY]) })
.attr('r', 2)
};
I want the xScale and yScale functions to update, to reflect the new values of currentX and currentY. But for some reason, these functions are not updating. If anyone could help me fix this, I would really appreciate it! Thanks!
UPDATE: Just to clarify, my problem is that my xScale and yScale functions do not change, even though xCurrent and yCurrent (and their minimum and maximum values) have changed. For example, "console.log(xScale(-5))" always produces the same value. This value should change as xCurrent and yCurrent change! Thanks again.
UPDATE 2: The global variables "xCurrent" and "yCurrent" ARE being updated. Furthermore, if I define NEW xScale and yScale functions in the .on('change') function, then my scales are updated. This actually fixes my problem, but I would still like to know why I can't do it the other way. Still trying to learn D3!
You need to update the x scale domain inside your change function. Also, you can use d3.extent instead of d3.min and d3.max. For example:
.on('change', function () {
// Update currentX and currentY with the currently selected datasets
key = Object.keys(dataset[0].data)[this.selectedIndex];
if (this.getAttribute('id') == 'xSelect') {currentX = key}
if (this.getAttribute('id') == 'ySelect') {currentY = key}
xScale.domain(d3.extent(dataset, function(d){return d.data[currentX];}));
// Change data used in the scatterplot
svg.selectAll('circle')
.data(dataset)
.transition()
.duration(1000)
.attr('cx', function(d) { return xScale(d.data[currentX]) })
.attr('cy', function(d) { return yScale(d.data[currentY]) })
.attr('r', 2)
})
This is because the scale generator isn't aware of changes to your data. It's domain method isn't going to be called every time the data changes. Thus, when the data does change, you have to explicitly set the scale's domain before re-drawing any data that depends on it.

Categories