Transition chord diagram when one chord has zero value - d3.js

I'm trying to make the chord diagram in a reusable way. My problem is when transitioning between 2 data-sets, such one data-set contains zero value (means that one chord will be removed).
So here is the transition code that I have called to transition:
changeDataFunction = function(data,selection) { // thisfunction will be called to determine the next drawing function to be called: render() function for the first time of drawing or transition function for the second one.
if (!chord.matrix()) {
chord.matrix(data);
self.render();
} else {
var old = { //save the old value of the diagram for interpolate
groups: chord.groups(),
chords: chord.chords()
};
console.log(old);
chord.matrix(data);
transition(old,selection); //call the transition function
}
};
function transition(old,selection){
svg = d3.select(selection).selectAll("svg").data([chord]);
svg.enter().append("svg")
.classed("chorddiagram",true);
arcGroup = svg.select(".arcpath-group").selectAll(".arcpath")
.data(function(d){ return d.groups(); })
.enter().append("path")
.classed("arcpath",true)
.style("fill",function(d){ return color(d.index);})
.style("stroke",function(d) { return color (d.index);})
.attr("d",arc_svg)
.on("mouseover",fade(0.1))
.on("mouseout",fade(1));
arcGroup
.transition().duration(1500)
.attrTween("d",arcTween(arc_svg,old));
arcGroup.exit().remove();
chordGroup = svg.select(".chordpath-group").selectAll(".chordpath")
.data(function(d){ return d.chords(); })
.enter().append("path")
.classed("chordpath",true)
.attr("d",chord_svg)
.style("fill", function(d){ return color(d.target.index);});
chordGroup
.transition().duration(1500)
.style("fill",function(d){
return color(d.target.index);
})
.attrTween("d", chordTween(chord_svg, old));
chordGroup.exit().remove();
}
// Interpolate the arcs
function arcTween(arc_svg, old) {
return function(d,i) {
if (d) {
var i = d3.interpolate(old.groups[i], d);
};
return function(t) {
return arc_svg(i(t));
}
}
}
// Interpolate the chords
function chordTween(chord_svg, old) {
return function(d,i) { //I think the problem from inside this block of code. The chord doesn't exist then the bug occurred. But I'm not good at interpolate so I don't know how to fix this.
var i = d3.interpolate(old.chords[i], d);
return function(t) {
return chord_svg(i(t));
}
}
}
Thank you for your attention and helping!

So I have solved my own question above by using the arcTween and chordTween function of #AmeliaBR to replace my old one:
https://stackoverflow.com/a/21923560/3128209
Thank you so much!

Related

Display multiple bar on barChart from a custom reducer

I have a group with custom reducer calculating various total and average values. The goal is to show them all on the same barChart. But I can only get the first bar to show. Here is the JSFiddler
https://jsfiddle.net/71k0guxe/15/
Is it possible to show all the value on the barChart?
Thanks in advance!
Data
ID,SurveySent,ResponseReceived
1,Yes,No
2,No,No
3,Yes,Yes
4,No,No
5,Yes,Yes
6,No,No
7,Yes,No
8,No,No
9,Yes,No
10,No,No
Code
var chart = dc.barChart("#test");
//d3.csv("morley.csv", function(error, experiments) {
var experiments = d3.csvParse(d3.select('pre#data').text());
var ndx = crossfilter(experiments),
dimStat = ndx.dimension(function(d) {return "Statistics";}),
groupStat = dimStat.group().reduce(reduceAdd, reduceRemove, reduceInitial);
function reduceAdd(p, v) {
++p.count;
if (v.SurveySent === "Yes") p.sent++;
if (v.ResponseReceived === "Yes") p.received++;
return p;
}
function reduceRemove(p, v) {
--p.count;
if (v.SurveySent === "Yes") p.sent--;
if (v.ResponseReceived === "Yes") p.received--;
return p;
}
function reduceInitial() {
return {count: 0, sent: 0, received: 0};
}
chart
.width(400)
.height(400)
.xUnits(dc.units.ordinal)
.label(function(d) { return d.data.value })
.elasticY(true)
.x(d3.scaleOrdinal().domain(["Total", "Sent", "Received"]))
.brushOn(false)
.yAxisLabel("This is the Y Axis!")
.dimension(dimStat)
.group(groupStat)
.valueAccessor(function (d) {
//Is it possible to return count sent and received all from here?
return d.value.count;
})
.on('renderlet', function(chart) {
chart.selectAll('rect').on("click", function(d) {
console.log("click!", d);
});
});
chart.render();
Just got some idea from the FAQ section of dc.js/wiki/FAQ
Fake Groups
"dc.js uses a very limited part of the crossfilter API - in fact, it really only uses dimension.filter() and group.all()."
I don't care about filtering, so i just need to mark up my own group.all. Basically transpose it from one row to multiple row. Works my purpose.
/* solution */
var groupStatTranposed = group_transpose(groupStat);
function group_transpose(source_group, f) {
return {
all:function () {
return [
{key: "Total", value: source_group.all()[0].value.count},
{key: "Sent", value: source_group.all()[0].value.sent},
{key: "Received", value: source_group.all()[0].value.received}
];
}
};
}
//use groupStatTranposed in the chart.
/** solution */

dc.js bubble chart - multidimension grouping issue and unable to get custom reducer to work

I'm currently trying to produce a dashboard in dc.js for my master's thesis and I have hit a real roadblock today if anyone could please help it would be much appreciated. I'm new to Javascript and dc so I'll try my best to explain...
My data format (Probe Request with visible SSID):
{"vendor":"Huawei Technologies Co.Ltd","SSID":"eduroam","timestamp":"2018-07-10 12:25:26","longitude":-1.9361,"mac":"dc:d9:16:##:##:##","packet":"PR-REQ","latitude":52.4505,"identifier":"Client"}
My data format (Probe Request with Broadcast / protected SSID):
{"vendor":"Nokia","SSID":"Broadcast","timestamp":"2018-07-10 12:25:26","longitude":-1.9361,"mac":"dc:d9:16:##:##:##","packet":"PR-REQ","latitude":52.4505,"identifier":"Client"}
I'm trying to produce a bubble chart which will display vendors as a bubble (size denoted by volume of packets collected for that vendor) then plot the bubble against X axis unprotected (any SSID != broadcast) & Y axis protected (packets where "Broadcast" is in the data)
Sketch of what I mean
What I've managed to get so far
I've managed to get a bar/ row/pie charts to work as they only require me to use one dimension and run them through a group. But I think I'm fundamentally misunderstanding how to pass multiple dimensions to a group.
for each at the top adds a new value of 0 / 1 to triple if Broadcast is present in the data.
Then I'm using a custom reducer to count 0 / 1 related to unpro & pro which will be used to plot the X / Y
I've tried reworking the nasdaq example and I'm getting nowhere
Code:
queue()
.defer(d3.json, "/uniquedevices")
.await(plotVendor);
function plotVendor(error, packetsJson) {
var packets = packetsJson;
packets.forEach(function (d) {
if(d["SSID"] == "Broadcast") {
d.unpro = 0;
d.pro = 1;
} else {
d.unpro = 1;
d.pro = 0;
}
});
var ndx = crossfilter(packets);
var vendorDimension = ndx.dimension(function(d) {
return [ d.vendor, d.unpro, d.pro ];
});
var vendorGroup = vendorDimension.group().reduce(
function (p, v) {
++p.count;
p.numun += v.unpro;
p.numpr += v.pro;
return p;
},
function (p, v) {
--p.count;
p.numun -= v.unpro;
p.numpr -= v.pro;
return p;
},
function () {
return {
numun: 0,
numpr: 0
};
}
);
var vendorBubble = dc.bubbleChart("#vendorBubble");
vendorBubble
.width(990)
.height(250)
.transitionDuration(1500)
.margins({top: 10, right: 50, bottom: 30, left: 40})
.dimension(vendorDimension)
.group(vendorGroup)
.yAxisPadding(100)
.xAxisPadding(500)
.keyAccessor(function (p) {
return p.key[1];
})
.valueAccessor(function (p) {
return p.key[2];
})
.radiusValueAccessor(function (d) { return Object.keys(d).length;
})
.maxBubbleRelativeSize(0.3)
.x(d3.scale.linear().domain([0, 10]))
.y(d3.scale.linear().domain([0, 10]))
.r(d3.scale.linear().domain([0, 20]))
dc.renderAll();
};
Here is a fiddle: http://jsfiddle.net/adamistheanswer/tm9fzc4r/1/
I think you are aggregating the data right and the missing bits are
your accessors should look inside of value (that's where crossfilter aggregates)
.keyAccessor(function (p) {
return p.value.numpr;
})
.valueAccessor(function (p) {
return p.value.numun;
})
.radiusValueAccessor(function (d) {
return d.value.count;
})
your key should just be the vendor - crossfilter dimensions aren't geometric dimensions, they are what you filter and bin on:
var vendorDimension = ndx.dimension(function(d) {
return d.vendor;
});
you probably need to initialize count because ++undefined is NaN:
function () { // reduce-init
return {
count: 0,
numun: 0,
numpr: 0
};
}
Fork of your fiddle, with all the dependencies added, wrapping function disabled, and elasticX/elasticY (probably not what you want but easier to debug):
https://jsfiddle.net/gordonwoodhull/spw5oxkj/16/

Set different diameters to graph nodes in javascript d3

How can I set different diameters to graph nodes, depend on their 'grade' (by grade I mean root or children)?
For example, I have one source node and I want to set the diameter to a value. Its children will have another value.
This is what I've tried by now:
Here I build the links array:
reply.forEach(function (targetNode) {
links.push({
source: sourceNode, // the source is a string
target: targetNode // the target is an array of strings
});
});
And here I tried to give different diameters:
.attr("r", function (d) {
links.forEach(function (link) {
if (d === link.source) {
return 15;
} else return 6;
})
})
The result was a graph only with links, all the nodes disappeared.
Any ideas how can I resolve this?
I fixed it. If it will help anyone, I leave the code here:
function setDiameter() {
links.forEach(function (link) {
svg.selectAll("circle")
.attr("r", function (d) {
if (d === link.source) {
return 15;
} else return 6;
})
});
}

In d3 js how to append the additional text as tool tip on the circles

I am working on a d3 sample http://bost.ocks.org/mike/nations/:
I am trying to add title for circles with with the name as well as checkin details.
following is the modified code for the display year function (rest of the code almost no change,,,):
// Updates the display to show the specified year.
function displayYear(year) {
dot.data(interpolateData(year), key)
.call(position)
.sort(order);
dot.append("title").text(function(d) { return d.name});
dot.text(function(d) { return d.name + "~"+ d.checkins + d.Checkintimes; });
label.text(Math.round(year));
}
// Interpolates the dataset for the given (fractional) year.
function interpolateData(year) {
return nations.map(function(d) {
return {
name: d.name,
region: d.region,
checkins: interpolateValues(d.checkins, year),
teamsize: interpolateValues(d.teamsize, year),
Checkintimes: interpolateValues(d.Checkintimes, year)
};
});
}
However the same is not appearing as title in the circles. I just want to append the checkin detail with the circle.
My json file contains the following:
[
{
"name":"Search&Navigator",
"region":"IPScience",
"checkins":[[2000,100],[2001,200],[2002,300],[2003,275],[2004,222],[2005,280],[2006,281],[2007,400],[2008,55],[2009,300]],
"teamsize":[[2000,10],[2001,7],[2002,7],[2003,12],[2004,5],[2005,3],[2006,10],[2007,12],[2008,12],[2009,10]],
"Checkintimes":[[2000,40],[2001,50],[2002,60],[2003,50],[2004,40],[2005,30],[2006,30],[2007,35],[2008,30],[2009,30]]
}
]
Your variable dot doesn't contain a reference to the title element. Simply change the function that appends it to do what you want:
dot.append("title")
.text(function(d) { return d.name + "~"+ d.checkins + d.Checkintimes; });

d3 adding data attribute conditionally

I'm creating a table with d3 to be used by the FooTable jquery plugin and this requires having some data- attributes in the header row. But not all columns have all the data attributes and wondering if there is a way to do this.
This approach sort of works, by adding all the possible data attributes and leaving some blank, but I'm sure it's not good practise.
var th = d3.select(selection).select("thead").selectAll("th")
.data(colspec)
.enter().append("th")
.text(function(d) { return d["data-name"]; })
.attr("data-class", function(d) {
if ("data-class" in d) {
return d["data-class"];
} else {
return "";
}
})
.attr("data-hide", function(d) {
if ("data-hide" in d) {
return d["data-hide"];
} else {
return "";
}
})
.attr("data-ignore", function(d) {
if ("data-ignore" in d) {
return d["data-ignore"];
} else {
return "";
}
})
etc.
colspec example:
[{"data-name": "username"}, {"data-name": "Date Joined", "data-hide": "true"}]
Currently getting:
<th data-class="" data-hide="true" data-ignore="" data-type="">Joined</th>
Want
<th data-hide="true" >Joined</th>
Any suggestions?
You don't need to call each() or filter()... The attr() function will do this for you internally. Just call it with a function instead of a value, and have that function return the desired value for each datum, or null if the attribute is not desired for a particular datum, like so:
...
.attr('data-class', function(d) {
return 'data-class' in d ? d['data-class'] : null;
});
If your function returns null, the attribute is not added. You can even combine several attributes into one call by providing a map of attr names to functions like so:
...
.attr({
'data-class': function(d) {
return 'data-class' in d ? d['data-class'] : null;
},
'data-hide': function(d) {
return 'data-hide' in d ? d['data-hide'] : null;
},
'data-ignore': function(d) {
return 'data-ignore' in d ? d['data-ignore'] : null;
}
});
or if you're like me and would rather not type so much, you can reduce the list of attribute names into the appropriate map:
...
.attr(['data-class', 'data-hide', 'data-ignore'].reduce(function(result, attr) {
result[attr] = function(d) {
return attr in d ? d[attr] : null;
}
return result;
}, {}));
Seems like a good candidate for .each():
var th = d3.select(selection).select("thead").selectAll("th")
.data(colspec)
.enter().append("th")
.text(function(d) { return d["data-name"]; })
// now address each item individually
.each(function(d) {
var header = d3.select(this);
// loop through the keys - this assumes no extra data
d3.keys(d).forEach(function(key) {
if (key != "data-name")
header.attr(key, d[key]);
});
});
I often use .each when having a per-item scope makes more sense than trying to figure out a bunch of attributes for each item.
For a short list of attributes, especially if you're worried about extra data in the objects, it's probably easier to loop through the desired keys instead of everything:
.each(function(d) {
var header = d3.select(this);
['data-class', 'data-hide', 'data-ignore'].forEach(function(key) {
if (key in d)
header.attr(key, d[key]);
});
});
You can use the .filter() function to only operate on the subset of the selection that you need to set attributes for, e.g.
var th = d3.select(selection).select("thead").selectAll("th")
.data(colspec)
.enter().append("th")
.text(function(d) { return d["data-name"]; });
th.filter(function(d) { return ("data-class" in d); })
.attr("data-class", function(d) {
return d["data-class"];
});
The most voted solution is perfect because .attr(a,b) works as conditional when b is null,
d3chain.attr('data-class', d=>'data-class' in d ? d['data-class'] : null );
but this solution is not geral, is not valid for other chaining methods, except using .each(), .filter or .call(). In general the most simple is call().
.call(condFunc,param)
Suppose that param is an global variable used as parameter in the condition, and that g is a global object used to return a value.
// inconditional
d3chain.attr(param, g[param])
// conditional case using globals
d3chain.call( s => { if (g[param]) s.attr(param,g[param]) })
// conditional case passing the parameter
d3chain.call( (s,p) => {
if (g[p]) s.attr(p, g[p])
}, param)
.each(d => condFunc)
Typical use:
d3chain.each( d=> {
if (param && d) d3.select(this).attr(d, g[param])
})
See #nrabinowitz answer for detailed example.
.filter(d=>condFunc).etc
Typical use:
d3chain.filter( d=> param in d ).attr(param, d=> g[param])
See #LarsKotthoff answer for detailed example.
A cleaner is to use filter
.filter(d => !!d["data-class"]) // filter only data with the "data-class" property
.attr("data-class", d => d["data-class"])

Resources