i have started with d3, on this moment i tray to edit a given script to meed the new requirments. I tray to reach a variabel but i can not access it. can someone help me out?
The problem is on line 5, i can not get "this.parentNode" but i need it.
Can someone point me in the right drection?
Thanks for your time
Greathings
function lines(x, yl, yr, n, svgElement, colors, Line) {
Line = Line || d3.line()
.x(function(d) { return x(d.time) + x.bandwidth() /2; })
.y(function(d) {
if(parseInt(d3.select(this.parentNode).attr("data-index")) !== scope.dataset.labels.indexOf(scope.rightas) && scope.rightas !== null) {
return yl(d.y);
}
return yr(d.y);
});
svgElement.selectAll('.line')
.data(function(d){ return [d]; })
.enter()
.append("path")
.attr("d", Line)
.attr("class", "line")
.style("fill", "none" )
.style("stroke", function(d, i, j) {
return d3.rgb(colors[d3.select(this.parentNode).attr("data-index")]).darker();
})
.style("stroke-width", "2")
}
You cannot get this DOM element from inside the line generator (with or without Angular). The line generator has no access to the element you're appending using it.
The API is clear about that:
When a line is generated, the x accessor will be invoked for each defined element in the input data array, being passed the element d, the index i, and the array data as three arguments. (emphasis mine)
The same, obviously, goes for the y accessor. So, as you can see, only the data is passed.
Therefore, this in this case will be simply the window, and there is no parentNode here.
Look at this demo (as the stack snippet takes a long time to console.log the window object, I'm using only this.name):
var line = d3.line()
.x(function(d) {
console.log(this.name);
return d;
})
.y(function(d) {
return d;
});
line([1]);
<script src="https://d3js.org/d3.v5.min.js"></script>
Related
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.
I'm working on a d3 chart (Multiple line chart).
I'm trying to represent a stock prediction, so basically the chart contains two lines: stock values line and an other one for my prediction.
The prediction is monthly, all days of month are represented in the chart.
In order to choose the month I have added a dropdown menu.
I appended a circle on each daily data, and works well for the first time. When user tries to change the month, the old circles are not updated, but the new ones are added.
Follow the code about circles:
topicEnter.append("g").selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 5)
.attr("cx", function(dd){return x(dd.date)})
.attr("cy", function(dd){return y(dd.probability)})
.attr("fill", "none")
.attr("stroke", "black");
I have done a fiddle to understand better the situation and in order to show code.
What am I missing here? Why don't the circles update themself with the lines?
To solve the issue about circles not updating you can do the following:
function update(topics) {
// Calculate min and max values with arrow functions
const minValue = d3.min(topics, t => d3.min(t.values, v => v.probability));
const maxValue = d3.max(topics, t => d3.max(t.values, v => v.probability));
y.domain([minValue, maxValue]);
x2.domain(x.domain());
y2.domain(y.domain());
// update axes
d3.transition(svg).select('.y.axis').call(yAxis);
d3.transition(svg).select('.x.axis').call(xAxis);
// Update context
var contextUpdate = context.selectAll(".topic").data(topics);
contextUpdate.exit().remove();
contextUpdate.select('path')
.transition().duration(600)
.call(drawCtxPath);
contextUpdate.enter().append('g') // append new topics
.attr('class', 'topic')
.append('path').call(drawCtxPath);
// New data join
var focusUpdate = focus.selectAll('.topic').data(topics);
// Remove extra topics not found in data
focusUpdate.exit().remove(); //remove topics
// Update paths
focusUpdate.select('path')
.transition().duration(600)
.call(drawPath)
// Update circles
var circlesUpdate = focusUpdate
.selectAll('.topic-circle')
.data(d => d.values);
circlesUpdate.exit().remove();
circlesUpdate.transition().duration(600).call(drawCircle);
circlesUpdate.enter().append('circle').call(drawCircle);
// Add new topics
var newTopics = focusUpdate.enter().append('g') // append new topics
.attr('class', 'topic');
// Add new paths
newTopics.append('path').call(drawPath)
// Add new circles
newTopics.selectAll('.topic-circle')
.data(d => d.values)
.enter()
.append('circle')
.call(drawCircle);
}
With these helper functions to reduce code duplication:
function drawCtxPath(path) {
path.attr("d", d => line2(d.values))
.style("stroke", d => color(d.name));
}
function drawPath(path) {
path.attr("d", d => line(d.values))
.attr('clip-path', 'url(#clip)')
.style("stroke", d => color(d.name));
}
function drawCircle(circle) {
circle.attr('class', 'topic-circle')
.attr('clip-path', 'url(#clip)')
.attr("r", d => 5)
.attr("cx", d => x(d.date))
.attr("cy", d => y(d.probability))
.attr("fill", "none")
.attr("stroke", "black");
}
I think there are some additional issues in your code, when you select the same month twice you get an error, we can fix that by doing the following:
d3.select('#month_chart').on("change", function() {
// Get selected value of the select
var month = this.options[this.selectedIndex].value;
// Since you have hardcoded data we need to return a new array
// This is why if you select the same month twice your code breaks
// since parseDate will fail since the data will be already parsed
// the second time
var monthData = get_monthly_data(month).map(d => {
return {
date: parseDate(d.date),
predicted_bool: d.predicted_bool,
target: d.target
};
});
// Lets use arrow functions!
var keys = d3.keys(monthData[0]).filter(k => k !== 'date');
color.domain(keys);
// More arrow functions!
var topics = keys.map(key => {
return {
name: key,
values: monthData.map(d => {
return {
date: d.date,
probability: +d[key]
};
})
};
});
x.domain(d3.extent(monthData, d => d.date));
update(topics);
});
// A good ol' switch
function get_monthly_data(month) {
switch (month) {
case 'gennaio':
return data_1;
case 'febbraio':
return data_2;
case 'marzo':
return data_3;
default:
return data_1;
}
}
Working jsfiddle:
https://jsfiddle.net/g699scgt/37/
The problem is your update cycle, but there are a good number of examples of the enter, update, exit process in d3.
But essentially:
You append a new g element for each batch of circles, which means you have an empty selection (no circles are in that g yet) each time and each data point is appended (and none are removed). You don't need this extra append. Take a look at the DOM structure on each append in your existing code.
Your enter() selection returns new elements - not modified elements. So if your total number of elements remains the same you will have an empty enter() selection. You'll want to update existing elements separately (alternatively, remove them all and append them all every time).
You'll want something closer to this:
// set the data
circles = topic.selectAll("circle")
.data(function(d){return d.values});
// update existing circles
circles.attr("cx", function(dd){return x(dd.date)})
.attr("cy", function(dd){return y(dd.probability)});
// add new circles
circles.enter()
.append("circle")
.attr("r", 5)
.attr("cx", function(dd){return x(dd.date)})
.attr("cy", function(dd){return y(dd.probability)})
.attr("fill", "none")
.attr("stroke", "black");
// remove excess circles
circles.exit().remove();
You'll likely also want to revise the lines that append the lines to reflect the enter, update, exit cycle in d3.
I hava a multi line chart with D3.js.
Initial rendering works just fine. When I try to update, only new lines are added instead of the old ones update/removed.
Example: http://jsfiddle.net/ty192n93/6/
This is the part where the line is rendered:
var node = svg.selectAll(".g.city")
.data(data, function(d) { return d.name; });
var enter = node.enter().append("g")
.attr("class", "city");
enter.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.data);
})
.style("stroke", function(d) {
return color(d.name);
});
// Text element left out
var remove = node.exit().remove();
How is it possible to update the old values (key function is specified) or remove them completely?
Your first selection is wrong. The selector .g.city selects all elements having both classes g and city. Your selection is valid, though, but will always return an empty selection putting all your data in the enter selection and leaving the exit selection empty. Instead, you are interested in group elements g having class city. Removing the first dot from the selector should do the trick:
var node = svg.selectAll("g.city") // <-- remove the dot
.data(data, function(d) { return d.name; });
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!
I am trying to add an element to a quadtree in D3 but I am getting a "RangeError: Maximum stack size exceeded' error in the console whenever I add more than one element. My quadtree declaration looks like this (in CoffeeScript):
quadtree = d3.geom.quadtree()
.x((d) -> x_scale(d[dim_1]))
.y(0)
quadroot = quadtree([])
... later in another function (quadtree and quadroot are both scoped globally) ...
quadtree.extent([[x_scale.range()[0], 0],[x_scale.range()[1], 0]])
datapoints = datapoints_g.selectAll("circle")
.data(vis_data)
datapoints.exit()
.remove()
# now add / transition datapoints
datapoints.enter()
.append("circle")
.attr("cx", x_scale.domain()[0])
.attr("cy", y_scale.domain()[0])
.on("mouseover", showDetails)
.on("mouseout", hideDetails)
datapoints
.transition()
.duration(1000)
.attr("r", (d) ->
if sizeBy == 'none'
return datapoint_size
else
return size_scale(d[sizeBy]))
.attr("cx", (d) -> x_scale(d[dim_1]))
.attr("cy", (d) ->
if dim_2 != 'none'
y_scale(d[dim_2])
else
quadroot.add(d)
# calculateOffset(maxR)
)
I am attempting to modify the example from http://fiddle.jshell.net/6cW9u/8/, just FYI; however, I still get the infinite recursion error upon adding the second element to the quadroot even when I comment out all of the other code from the example.
Any guidance would be appreciated greatly!
Thanks in advance.