I am a beginner in D3 and JavaScript development, i have succeed to generate a multi-line graph ( generated with an JS object) with interactivity. Every control work except that zoom alter line form.
I have tried to change data quantity and i don't know which part is guilty of that behavior. It appears after a given time, the line doesn't change and stay the same.
These pictures show this latter effect :
capture with the right line:
capture with altered line:
If anyone have any idea for resolve that.
[edit] i make another pass on my code and the problem seems linked to path update. i add these blocks of code if anyone see something wrong.
function line_gen(num){
graph['lineFunction'+num] = d3.line()
.defined(function(d) {return !isNaN(d[graph.dict[num]]) })
.x(function(d) { return graph.xScaleTemp(d[graph.dict[0]]);})
.y(function(d) { return graph.yScaleTemp(d[(graph.dict[num])])})
.curve(d3.curveMonotoneX);
}
function updateData() {
var t;
function data_join(num){
if(!(graph.oldFdata.length)){
graph.dataGroup.select(".data-line"+graph.opt.name+num)
.transition()
// .duration(graph.spd_transition!=-1?graph.spd_transition:0)
.attr("d", graph['lineFunction'+num]((graph.Fdata)));
}else{
let update = graph.dataGroup.select(".data-line"+graph.opt.name+num)
.data(graph.Fdata);
update.enter()
.append("path")
.attr("d",graph['lineFunction'+num]);
update.exit().remove();
// graph.dataGroup.select(".data-line"+graph.opt.name+num)
// .transition()
// .duration(graph.spd_transition!=-1?graph.spd_transition:0)
// .attrTween('d',function(d){
// var previous =d3.select(this).attr('d');
// var current =graph['lineFunction'+num]((graph.Fdata));
// return d3.interpolatePath(previous, current);
// });
}
}
for (var i = 1; i < graph["keys_list"].length; i++) {
if(graph[("lineFunction" + i)]==null){
indic_gen(i);
indicGrp(i);
}
data_join(i);
}
for (var i = 1; i < (Object.keys(graph.Fdata[0]).length); i++) {
/* update_tooltip(i); */
set_tooltip(i);
}
}
I have resolved my problem by rerendering it once more. Not the best solution but it's works.
Related
The bounty expires in 2 days. Answers to this question are eligible for a +50 reputation bounty.
Tom Rudge wants to draw more attention to this question.
I've created a D3 force directed graph in an Angular environment using typescript. I've followed this "brushing" example of a selectable force directed graph code for this example here. I've had to convert it into typescript and obviously fit it into my structure which is pretty standard D3. Brushing basically allows the selection of multiple nodes on a graph. This is triggered by keyup/keydown of the shift key.
Clicking on a node selects it and de-selects everything else.
Shift-clicking on a node toggles its selection status and leaves all
other nodes as they are.
Shift-dragging toggles the selection status of all nodes within the
selection area.
Dragging on a selected node drags all selected nodes.
Dragging an unselected node selects and drags it while de-selecting
everything else.
I've struggled to get this fully working.. here are my issues:
Shift toggles brushing on and off fine, but when I begin dragging when shift is active, when I keydown shift(let go) brushing remains on.
The brushing box does not appear until my second drag/selection attempt (as it remains on).
When I do brush/select nodes these don't get the selected class appended to them.
Here is a stackBlitz to my Angular D3 brushing attempt.
I'm relatively new to D3 so any assistance welcomed!
Brushing part of the code, for full version please see demo:
let gBrushHolder = svg.append('g');
let gBrush = null;
svg.style('cursor', 'grab');
let brushMode = false;
let brushing = false;
const brushstarted = () => {
// keep track of whether we're actively brushing so that we
// don't remove the brush on keyup in the middle of a selection
brushing = true;
node.each(function (d) {
d.previouslySelected = this.shiftKey && d.selected;
});
};
const brushed = () => {
if (!_d3.event.sourceEvent) return;
if (!_d3.event.selection) return;
console.log("during")
var extent = _d3.event.selection;
node.classed('selected', function (d) {
return (d.selected =
d.previouslySelected ^
(<any>(
(extent[0][0] <= d.x &&
d.x < extent[1][0] &&
extent[0][1] <= d.y &&
d.y < extent[1][1])
)));
});
};
const brushended = () => {
if (!_d3.event.sourceEvent) return;
if (!_d3.event.selection) return;
if (!gBrush) return;
gBrush.call(brush.move, null);
if (!brushMode) {
// the shift key has been release before we ended our brushing
gBrush.remove();
gBrush = null;
}
brushing = false;
console.log("end")
};
let brush = _d3
.brush()
.on('start', brushstarted)
.on('brush', brushed)
.on('end', brushended);
const keydown = () => {
this.shiftKey = _d3.event.shiftKey;
if (this.shiftKey) {
// if we already have a brush, don't do anything
if (gBrush) return;
brushMode = true;
if (!gBrush) {
gBrush = gBrushHolder.append('g');
gBrush.call(brush);
}
}
};
const keyup = () => {
this.shiftKey = false;
brushMode = false;
if (!gBrush) return;
if (!brushing) {
console.log("NOT BRUSHING")
// only remove the brush if we're not actively brushing
// otherwise it'll be removed when the brushing ends
gBrush.remove();
gBrush = null;
}
};
_d3.select('body').on('keydown', keydown);
_d3.select('body').on('keyup', keyup);
I'm building a D3 treemap visualization, and I've run into a spot of trouble.
I've been able to populate my initial treemap, but I'd like to introduce modifications to it via click interactions. In order to do that, I need to recompute a new layout (and then do some things with it later) every click, using a subset of my previous (currently displayed) data to do that.
I've written a sum function that works as I want it to, and populates the value property of each Node in my new_root object. This is, as I understand it, the sole prerequisite for running d3.treemap on my new_root Here's a snippet of the code:
let treemap = d3.treemap()
.tile(d3.treemapResquarify)
.size([800, 400])
.round(true)
.paddingInner(1);
// later...
function object_is_direct_child(root, obj) {
return (root.children.filter(child => (child.id === obj.name)).length > 0);
}
// ...much later...
// Create and bind the click event.
let click = function(d) {
if (d.data.children.length > 0) {
// Use the hover ID to get the underlying name attr (e.g. "Noise-hover" -> "Noise")
let name = this.getAttribute("id").slice(0,-6);
// Select the new root node.
let new_root = root.children.find(c => c.id === name);
// Sum.
new_root = new_root.sum(node => ((object_is_direct_child(new_root, node)) ? node.n : 0));
// Here is the problem area. This doesn't work as expected.
treemap(new_root);
}
}
hover_rects.on("click", click);
This should populate the computed, liad-out x0, x1, y0, y1 values on new_node and all of its sub-children. Instead, new_node and all of its children are NaN.
I've uploaded what I have so far as a Gist, accessible here.
I was able to debug this using the follow MWE script, run in Node:
'use strict';
// Load data.
const tree = require('../threshold-tree');
const d3 = require('d3');
const assert = require('assert');
// Load in the data file as it would be loaded in the browser.
const fs = require('fs');
const csvString = fs.readFileSync('data/complaint_types.csv').toString();
const raw_data = d3.csvParse(csvString);
let tr = new tree.ThresholdTree(raw_data);
let hr = tr.as_hierarchy();
let root = d3.stratify().id(d => d.name).parentId(d => d.parent)(hr);
function object_is_direct_child(root, obj) {
return (root.children.filter(child => (child.id === obj.name)).length > 0);
}
// Now the sum function itself.
function nodal_summer(node) {
return ((object_is_direct_child(root, node)) ? node.n : 0);
}
root = root.sum(nodal_summer);
// Create our treemap layout factory function.
let treemap = d3.treemap()
.tile(d3.treemapResquarify)
.size([800, 400])
.round(true)
.paddingInner(1);
// Apply our treemap function to our data.
debugger;
treemap(root);
// Again...this is where it fails!
let new_root = root.children[0];
new_root.parent = null;
new_root = new_root.sum(node => ((object_is_direct_child(new_root, node)) ? node.n : 0));
debugger;
new_root = treemap(new_root);
console.log(treemap(new_root));
The culprit is the positionNode callback inside d3:
function positionNode(node) {
var p = paddingStack[node.depth],
...
paddingStack is initialized as a length-1 list, but node.depth is 1, so p is initialized as NaN, which gets propagated from there.
Two working fixes are:
Using node.copy().
new_root.eachBefore(function(node) { node.depth--; }).
I have a lot to learn so could really use some pointers: http://jsfiddle.net/David_Knowles/9kDC3/
QUESTION
I am trying store iteratively the height values of bars in a bar
chart.
Then i set the height of all to zero
I then trigger an animation of the bars where I increase height towards the original values stored in array
Problem
It looks to me that the array is being overwritten instead of
increasingly filled
I don't know what the best way is to share the array values with the
other script I later trigger
//
$(function(){
// get the height of all the bars
var $theBars = $("#v-bars").children();
var BarsHeight = $theBars.each(function(index, element ) {
var origHeight = [];
origHeight.push($(this).attr("height"));
console.log (origHeight);
});
//
$("#svg-skills-expertise").click(function() {
$($theBars).each(function(){
$(this).css("MyH",$(this).attr("height")).animate(
{
MyH:400 // this value needs to be the array values so how
},
{
step:function(v1){
$(this).attr("height",v1)
}
})
})
console.log ("Yeap the clicked it! callback");
});
});
Updated!
Initialized origHeight outside of your function call, added parameter "i" to $($theBars).each function (passes the index to the each call), and set MyH: origHeight[i].
/* SVG ARRAY \\\\\\\\\ */
var origHeight = [];
$(function(){
var $theBars = $("#v-bars").children();
var BarsHeight = $theBars.each(function(index, element ) {
origHeight.push($(this).attr("height"));
console.log (origHeight);
});
$("#svg-skills-expertise").click(function() {
$($theBars).each(function(i){
$(this).css("MyH",$(this).attr("height")).animate(
{
MyH:origHeight[i]
},
{
step:function(v1){
$(this).attr("height",v1)
}
})
})
console.log ("Yeap clicked it!");
});
});
I am trying to make a bubble chart, in that if i click on a bubble, the title of the bubble should appear in the console. I tried some ways, but was not successful.
d3.json("deaths.json",
function (jsondata) {
var deaths = jsondata.map(function(d) { return d.deaths; });
var infections = jsondata.map(function(d) { return d.infections; });
var country = jsondata.map(function(d) { return d.country; });
var death_rate = jsondata.map(function(d) { return d.death_rate; });
console.log(deaths);
console.log(death_rate);
console.log(infections);
console.log(country);
console.log(date);
//Making chart
for (var i=0;i<11;i++)
{
var f;
var countryname=new Array();
var dot = new Array();
dot = svg.append("g").append("circle").attr("class", "dot").attr("id",i)
.style("fill", function(d) { return colorScale(death_rate[i]); }).call(position);
//adding mouse listeners....
dot.on("click", click());
function click()
{
/***********************/
console.log(country); //i need the title of the circle to be printed
/*******************/
}
function position(dot)
{
dot .attr("cx", function(d) { return xScale(deaths[i]); })
.attr("cy", function(d) { return yScale(death_rate[i]); })
.attr("r", function(d) { return radiusScale(infections[i]); });
dot.append("title").text(country[i]);
}
}
});
I need the title of circle to be printed
Please help!!!
You had the good idea by using the on("click", ...) function. However I see two things:
The first problem is that you don't call the function on the click event but its value. So, you write dot.on("click", click()); instead of dot.on("click", click);. To understand the difference, let's imagine that the function click needs one argument, which would for example represent the interesting dot, what would it be? Well, you would write the following:
dot.on("click", function(d){click(d)})
Which is equivalent (and less prone to errors) to writing:
dot.on("click", click)
Now, the second point is that, indeed you want to pass the node as an argument of the function click. Fortunately, with the on event, as I used in my example, the function click is called with the argument d which represents the data of dot. Thus you can now write:
dot.on("click", click);
function click(d)
{
console.log(d.title); //considering dot has a title attribute
}
Note: you can also use another argument by writing function click(d,i) with i representing the index in the array, see the documentation for more details.
If you have a title on your data,
dot.on('click' , function(d){ console.log(d.title); });
I am struggling to understand how this JavaScript code work. I am learning JS, and not exposed to a dynamic, functional language before. So, I visualize function calls in bit procedural, hierarchical order. With d3.js, one can draw svg elements as explained here
var dataset = [ 5, 10, 15, 20, 25 ];
d3.select("body").selectAll("p")
.data(dataset)
.enter()
.append("p")
.text("New paragraph!");
Let’s change the last line:
.text(function(d) { return d; });
Check out what the new code does on this demo page.
Whoa! We used our data to populate the contents of each paragraph, all thanks to the magic of the data() method. You see, when chaining methods together, anytime after you call data(), you can create an anonymous function that accepts d as input. The magical data() method ensures that d is set to the corresponding value in your original data set, given the current element at hand.
This magic, mentioned above is what I fail to understand. "d" is not a global variable, as if I change to another (c) name, it still works. So, the data method may be setting the value for the anonymous fn.
But, typically(with my limited reading) chaining is possible because the current function returns an object, on which the next method can be invoked. In the above case, how data method knows whether a text ("New paragraph!") is passed by the user, otherwise pass the data to the anonymous fn. The doubt is, the text method is down the line and data() is already executed. How the data is passed to the anonymous function?
thanks.
Digging into d3.js internals shows the following result for text function:
d3_selectionPrototype.text = function(value) {
return arguments.length < 1
? this.node().textContent : this.each(typeof value === "function"
? function() { var v = value.apply(this, arguments); this.textContent = v == null ? "" : v; } : value == null
? function() { this.textContent = ""; }
: function() { this.textContent = value; });
};
In case the supplied argument is a function, the following code gets executed:
this.each(function() {
var v = value.apply(this, arguments); // executing function provided
this.textContent = v == null ? "" : v;
});
Function each is declared as:
d3_selectionPrototype.each = function(callback) {
for (var j = -1, m = this.length; ++j < m;) {
for (var group = this[j], i = -1, n = group.length; ++i < n;) {
var node = group[i];
if (node) callback.call(node, node.__data__, i, j); // this is the line you are interested in
}
}
return this;
};
so on each invocation it supplies an element from this. And, getting down to it, this is populated by data function invocation.
Well, I have never used d3 before, but this is what I understand.
d is the data object (I would call it data instead of d had set in the data() method.
So what does the text() method does? Will it will call the function and use it's output, something like this:
function text (callback) {
var theText;
if (typeof callback === "function") {
theText = callback(dataset);
} else {
theText = callback;
}
// does something more
}
So, if callback is a function call it, and use its return value as text.
Then, what I'm guessing, is that if the function is an array, it will call the text method for each element in the array.
Something like this...
function text(callback) {
var theText;
if (typeof callback === "function") {
theText = callback(dataset);
} else {
theText = callback;
}
if (theText instanceof Array) { // this is not the best way to check if an object is an array, I'll come back to this later. I'm sorry.
for (var i=0, len=theText.length; i<len; i++) {
text(theText[i]);
}
} else {
// do something else
}
// do something more
}
please take into account that this would be a really simple version of what really happens.
If it's not clear enough please let me know.