d3 selections not passing data down as expected with .join() - d3.js

I have been using the d3 v5 .join update pattern. It's great, but I have a situation in my code where the data is not being passed down as expected and I am at a loss for what is going wrong. My code looks like this :
let binnedWrap = wrap.selectAll('.attr-wrap').data(sortedBins).join('g').attr('class', d=> d.key + ' attr-wrap');
binnedWrap.attr('transform', (d, i)=> 'translate(0,'+(i * (height + 5))+')');
let label = binnedWrap.append('text').text(d=> d.key).attr('y', 40).attr('x', 80).style('text-anchor', 'end');
let branchGroup = binnedWrap.selectAll('g.branch-bin').data(d=> {
///THIS IS RIGHT
console.log('data before the branch bins',d);
return d.branches}).join('g').classed('branch-bin', true);
branchGroup.attr('transform', (d, i)=> 'translate('+(100 + branchScale(i))+')');
This works as expected. The data that consoles is correct and it creates a group classed 'branch-bin' for each branch element in d.branches
BUT- when I attempt to use the branch data within each of the 'branch-bin' group, I am not getting the expected d.branches data:
let continDist = branchGroup.filter(f=> f.type === 'continuous');
var lineGen = d3.line()
.y((d, i)=> {
console.log('y',d, i)
let y = d3.scaleLinear().domain([0, 16]).range([0, height]);
return y(i);
})
.x(d=> {
let x = d3.scaleLinear().domain([0, 100]).range([0, 100]);
return x(Object.entries(d).length - 2);
});
//THIS IS RIGHT//
console.log(continDist.data())
continDist.append('path').data((d, i)=> {
console.log('supposed to be data in d branch', d, i);
return lineGen(d.bins)});
The output looks the same as the above console. The path is not being passed the branch data.
Any idea what is going on here would be much appreciated!

I made the mistake of using
continDist.append('path').data((d, i)=>lineGen(d.bins));
instead of continDist.append('path').attr('d', (d, i)=>lineGen(d.bins));
after assigning d as an attr of path it worked as expected.

Related

NVD3 chart is getting cut off near axes

I'm working on a new chart type for NVD3 called lineWithFocusPlusSecondary. It has two graphs on top of each other. It's working well except for one problem: if the x values are dates, when you zoom in, the graph gets cut off in an unpleasant manner. This doesn't happen with the default lineChart so I've definitely done something wrong.
I've put my code in this plnkr: https://plnkr.co/edit/9GzI0Jxi5qXZas3ljuBQ?p=preview
Would love some help :) It seems like the issue in the screenshot is that the x-axis domain goes until ~7:05pm but we don't have a data point until 7pm.
It could be something something to do with my onBrush function:
function onBrush(extent) {
var processedData = processData(container.datum()),
dataPrimary = processedData.dataPrimary,
dataSecondary = processedData.dataSecondary,
seriesPrimary = processedData.seriesPrimary,
seriesSecondary = processedData.seriesSecondary;
updateChartData(
getIntegerExtent(extent),
dataPrimary,
dataSecondary,
seriesPrimary,
seriesSecondary
);
}
function getIntegerExtent(extent) {
return [Math.ceil(extent[0]), Math.floor(extent[1])];
}
function updateAxes(extent) {
primaryXAxis.scale(primaryChart.xScale());
primaryXAxis.domain(extent);
g
.select('.nv-primary .nv-x.nv-axis')
.transition()
.duration(transitionDuration)
.call(primaryXAxis);
g
.select('.nv-secondary .nv-ySecondary.nv-axis')
.transition()
.duration(transitionDuration)
.call(yAxisSecondary);
g
.select('.nv-primary .nv-yPrimary.nv-axis')
.transition()
.duration(transitionDuration)
.call(yAxisPrimary);
}
function updateChartData(currentExtent, dataPrimary, dataSecondary) {
updateAxes(currentExtent);
var primaryDatasetsWithinBrushExtent = !dataPrimary.length
? [
{
values: []
}
]
: dataPrimary.map(function(d) {
var restrictedDataset = Object.assign({}, d);
restrictedDataset.values = d.values.filter(function(d, i) {
return (
primaryChart.x()(d, i) >= currentExtent[0] &&
primaryChart.x()(d, i) <= currentExtent[1]
);
});
return restrictedDataset;
});
var primaryChartWrap = g
.select('.nv-primary .nv-linesWrap')
.datum(primaryDatasetsWithinBrushExtent);
var secondaryDatasetsWithinExtent = !dataSecondary.length
? [
{
values: []
}
]
: dataSecondary.map(function(d) {
var restrictedDataset = Object.assign({}, d);
restrictedDataset.values = d.values.filter(function(d, i) {
return (
secondaryChart.x()(d, i) >= currentExtent[0] &&
secondaryChart.x()(d, i) <= currentExtent[1]
);
});
return restrictedDataset;
});
var focusSecondaryChartWrap = g
.select('.nv-secondary .nv-secondaryChartWrap')
.datum(secondaryDatasetsWithinExtent);
primaryChart.xDomain(currentExtent);
secondaryChart.xDomain(currentExtent);
primaryChartWrap
.transition()
.duration(transitionDuration)
.call(primaryChart);
focusSecondaryChartWrap
.transition()
.duration(transitionDuration)
.call(secondaryChart);
}
I discovered the issue was that I was trying to set the xDomain in multiple locations. This seems to mess up NVD3's logic. After I removed all of the .domain/.xDomain it worked perfectly :)
Debugging approach was to carefully read through the lineChart.js code and notice what it didn't have that I had.

D3 json parsing in Scala js

I need some help with d3 in scala js please. Not sure how to go about using d3.json function.
I have this:
val rectXFunVpc = (d: Vpcs,
i: Int) => {
println(s"rectXFunVpc i:$i")
i * 30
}
d3.json(
"json-file.json", callback = (e: Any, json: Any) => {
val jsonAsString: String = JSON.stringify(json.asInstanceOf[js.Any])
println(s"jsonAsString: $jsonAsString")
val pickledJson = read[domain.DescribeVpcsObject](jsonAsString)
println(s"pickledJson:$pickledJson")
val dataArray: js.Array[Vpcs] = pickledJson.Vpcs.asInstanceOf[js.Array[Vpcs]]
println(s"dataArray:$dataArray")
val sel: Update[Vpcs] = svg.selectAll("rect").data(dataArray)
sel.enter()
.append("rect")
.attr("x", rectXFunVpc)
.attr("y", 20)
.attr("width", 20)
.attr("height", 10)
.style("fill", rectColorFun)
print()
}
)
couple of issues:
The rects are not drawn so it looks like the dataArray is not right but when I console.log it I think I am getting a proper js.Array[Vpcs]
The rectXFuncVpc is never called (my printlns in there are not
printed in browser console)
the return type of Unit is forcing me to put the print() as the last statement of the function
Is there a chance someone could provide me an example please?
Hard to figure out without the Vpcs json format.
Here is your json example with a simple json:
https://github.com/spaced/scala-js-d3-example-app/tree/json_example
To enforce a Unit , you can use ()

d3 append invisible if data is not same as previous

d3 newb is trying something else:
I want to add a date label to my bar chart only if the date is not the same as in the previous bar.
bar.append("text")
.attr("visibility", function(d,i){
if(d.Datum == data[i-1].Datum) return "hidden"})
.attr("x", padding)
.attr("y", barHeight-2)
.attr("fill", "Gainsboro")
.text(function(d){ return d.Datum})
So I thought I add a visibility to my text label. however I cannot access the previous date from the data-object... probably this is an easy one for someone not newbie as me...
data example (before CSV import)
Datum,Name,Kategorie,Betrag
01/10/15,,Lohn,1586.7
02/10/15,,lunch,-4.55
So assuming that after d3 parses the text file you are left with data like:
var data = [
{
Datum: "01/10/15",
Name: "",
Kategorie: "Lohn",
Betrag: 1586.7
},{
...
}
];
I'd pre-process the data to contain a bool about whether or not it is the first instance of that date:
data.forEach(function(d,i){
d.isFirstInstanceOfDate = (i === 0 || d.Dataum !== data[i-1].Datum);
});
Then assuming that bar is a selection of gs elements (which already contain a rect), I'd filter them and only append the text on the first instance:
bar
.filter(function(d){
return d.isFirstInstanceOfDate
})
.append('text')
...
The index starts from 0. By subtracting 1 from it the first time you get an error. You need to check whether i > 0 so that you don't do out of bounds.
For example:
.style('visibility', function (d, i) {
if (i > 0) {
if (d.datetime === data[i - 1].datetime) {
return 'hidden';
}
}
return 'visible';
});
Here are two sample fiddles:
http://jsfiddle.net/00drii/y079vw7e/
http://jsfiddle.net/00drii/dmhs0gza/

d3js update and maintain structure of visualization

Is it possible to (and if yes - how to fix render()) to perform properly updating divs after click on [x] with d3?
var id_names = {},
render = function (id) {
var names = id_names[id],
divs = d3.select("#filelist").selectAll("div").data(names),
denter = divs.enter().append("div");
denter.append("div")
.classed("txt", true);
denter.append("div")
.classed("del", true);
divs.select("div .txt")
.text(function (d, i) {return i + " :: " + d});
divs.select("div .del")
.text("[x]")
.on("click", function (d, i) {
// remove element from array
id_names[id].splice(i, 1)
render(id)
});
divs.exit().remove()
};
// {id: [name, nameN]}
id_names[11] = ["aaa", "bbb", "cc"]
render(11)
I think the problem is that you bind the data to divs, and you have more divs in the denters.
So if you add some class (or id) to the containing divs, you get the desired effect (I hope).
See here:
JSFiddle
Or here, the core changes:
divs = d3.select("#filelist").selectAll(".container")
.data(name),
denter = divs.enter().append("div")
.attr('class', 'container');
(A side note: I am not a fan of modifying in a function that is in an outside scope, as you are doing it with id_name here...). This may lead to very nasty side effects...)
Hope this helps!

Append Circles to 1 Line in a D3 Multi-Line chart

I have a multi-line chart representing 8 different series of values, for a given date:
http://bl.ocks.org/eoiny/8548406
I have managed to filter out series1 and append circles for each data-point for series1 only, using:
var filtered = city
.filter(function(d){
return d.name == "series1"
})
filtered
.selectAll('circle')
.data(
function(d){return d.values}
)
.enter().append('circle')
.attr({
cx: function(d,i){
return x(d.date)
},
cy: function(d,i){
return y(d.pindex)
},
r: 5
})
However I am trying to append 4 circles to my series1 line, one for each of the following values only:
min value in series1,
max value in series1,
1st value in series1,
last value in series1.
I approached this problem by looking at the "filtered" array and I tried using something like this to catch the min & max values to start with:
.attr("visibility", function(d) {
if (d.pindex == d3.max(filtered, function(d) { return d.pindex; })) {return "visible"}
if (d.pindex == d3.min(filtered, function(d) { return d.pindex; })) {return "visible"}
else { return "hidden" }
;})
But I'm somehow getting muddled up by the fact that the data I need is in an object within the filtered array. I know that filtered should look like this:
[{
name: "series1",
values: [{date: "2005-01-01",
pindex: "100"},
{date: "2005-02-01"
pindex: "100.4"}, ...etc for all data points i.e. dates
]
}]
So I tried something like this:
d.pindex == d3.max(filtered, function(d) { return d.values.pindex; })
but I'm still getting a bit lost. Does anyone have any ideas?
In general, you probably want to filter your data rather than DOM elements. So instead of using city.filter you might use cities.filter to get the data array you're interested in. More importantly, you probably want to filter the data passed to the new circle selection, rather than creating all circles and then selectively showing or hiding them. I might try:
filtered
.selectAll('circle')
.data(function(d){
var points = d.values;
// create the array of desired points, starting with the easy ones
var circleData = [
// first
points[0],
// last
points[points.length - 1]
];
// now find min and max
function getValue(d) { return d.pindex; }
// d3.max returns the max value, *not* the object that contains it
var maxVal = d3.max(points, getValue);
// Note that you might have multiple points with the max. If you
// don't want them all, just take maxPoints[0]
var maxPoints = points.filter(function(d) { return d.pindex === maxVal; });
// same for min
var minVal = d3.min(points, getValue);
var minPoints = points.filter(function(d) { return d.pindex === minVal; });
// stick them all together
return circleData.concat(maxPoints).concat(minPoints);
})
.enter().append('circle')
// etc
Key points:
Filter your data, not your DOM. It's less expensive processing, easier to debug, and generally much easier to get your head around.
d3.min and d3.max don't return the object with the max value, they return the value itself, hence your TypeError.

Resources