D3.JS : recover cell value when click in a row - d3.js

I have made a table from a csv with d3.js. When I click in a cell I want to get the value of the last cell in its row .
I tried this :
var container = d3.select("body")
.append("table")
.attr("id","myTable");
//header
var headers=container.append("thead").append("tr")
.selectAll("th")
.data(titles)
.enter().append("th")
.text(function(d) {
return d;
});
var rows=container.append("tbody")
.selectAll("tr").data(dataCSV)
.enter().append("tr")
.on('click',function(d){
if(d3.select(this).classed("highlight")){
d3.selectAll("tr").classed("highlight", false);
}else{
d3.selectAll("tr").classed("highlight", false);
d3.select(this).classed("highlight", true);
}
});
rows.selectAll("td")
.data(function (d) {
return titles.map(function (k) {
return { 'value': d[k], 'name': k};
});
}).enter()
.append('td')
.attr("style","width:"+widthCellule+"%")
.attr('data-th', function (d) {
return d.name;
})
.text(function (d) {
return d.value;
})
/* Here is my mistake , I d'ont know */
.on('click',function(d,i,j){
column=i;
last_column=titles.length;
alert(d3.select('id','myTable').rows[j].cells[last_column].innerHTML);
});

Related

d3 .data() function not working as expected multiple data sources

I have a chart that works off two data sources and essentially the first [updateSetupData(data)] builds the elements the chart needs and the second [update(data)] does not append elements it only updates the html formed by the other function. I use an id as a key to keep things in sync.
function updateSetupData(data) {
var countsByParent = d3.nest()
.key(function (d) { return d.parent + '_tbl'; })
.key(function (d) { return d.SkillGroup + '_grp'; })
//.key(node => node.AgtName)
//.rollup(function(leaves) { return leaves.length;})
.entries(data);
var treeRoot = {
key: "root",
parent: null,
value: "100",
values: countsByParent };
var root = d3.hierarchy(treeRoot, function (d) { return d.values; })
.sum(function (d) { return d.value; });
// .sort(function(a, b) { return b.value - a.value; });
var nodes = pack(root);
//console.log(nodes);
var node = canvas.selectAll(".node")
.data(pack(root).descendants())
.enter().append("g")
.attr("class", function (d) {
return d.data.key == null ? "node " + d.data.AgtName + " agent " :
"node " + d.data.key;
})
.attr("id", function (d) { return d.data.AgtName + "a_" + d.data.AgtId +
"_s" + d.data.skillId + "_g" + d.data.groupId })
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y
+ ")"; })
.attr("fill", "steelblue")
.on("mouseover", function (d) {
highlight(d.label ? d.label : d.data.AgtName);
}).on("mouseout", function (d) { highlight(null); });
function highlight(agtName) {
if (agtName == null) d3.selectAll(".node").classed("active", false);
else d3.selectAll(".node." + agtName).classed("active", true);
}
node.append("circle")
.attr("r", function (d) { return d.r; })
// .attr("fill", "steelblue")
.attr("opacity", 0.25)
.attr("stroke", "#ADADAD")
.attr("stroke-width", "2");
node
.append("svg:title").text(function (d) { return d.data.AgtName; });
var arc = arcGenerator
.outerRadius(function (d, i) { return d.r; })
.startAngle(0)
.endAngle(180);
node.append('defs')
.append('path')
.attr("id", function (d, i) { return "s" + i; })
.attr("d", arc);
//.attr("d", function (d, i) { return getPathData(d.r); } );
node.append("text")
.attr("transform", "rotate(90)")
.attr("text-anchor", function (d) { return d.data.key == null ? "start"
: d.data.key.split("_") [1] === "tbl" ? "end" : "start"; })
.append("textPath")
.attr("startOffset", '50%')
.attr("xlink:href", function (d, i) { return '#s' + i; })
.attr("fill", function (d) { return d.data.key == null ? "none" :
d.data.key.split("_") [1] === "tbl" ? "blue" : "black"; })
.text(function (d) {
return d.data.key == null ? "" :
d.data.key == "root" ? "" : d.data.key.split("_")[0];
});
});
The second function is where I am having the issue. Even though I call .data() and have the new [different] data only used to overlay live calls on the static chart; The classed function just after the .data(data, key) works fine; the (d) there has the new data.
for the var text variable (d) in the data functions is from the other function, so the data to set the text with is wrong.
function update(data) {
var agent = canvas.selectAll(".node.agent")
//sets all elements to false for the class before the update
.classed("newCall", false)
.data(data, function (d) {
// the key is either an element id or an id from the data
var myId = d.id ? d.id : this.id;
// console.log("data key: " + d.id + " element id: " + this.id + "
new: " + d.newCall);
return myId;
}).classed("newCall", function (d) {
var f = d.newCall ? d.newCall : false;
//console.log(this.id + " " + f )
return f;
})
var text = agent.selectAll(".newCall text")
.attr("transform", null)
.attr("startOffset", null)
.attr("xlink:href", null)
.attr("fill", function (d) { return "black"; })
.attr("dx", function (d) { return -4;})
.attr("dy", function (d) { return 4; })
.text(function (d) {
console.log(d);
return "3";
});
Is there something I need to do with the text var to get the right data? I was thinking that because I call .data on the agents var that the text var in the would be OK since it appears that when I class the elements the data is there.
Including the whole fixed function. First the data and classes are cleared then the new data is added. The text elements for agent.newCall are updated when the function runs.
function update(data) {
var agent = canvas.selectAll(".node.agent");
// clear call data
agent.select("text").text("");
// sets all elements to false for the class before the update
agent.classed("newCall", false)
.data(data, function (d) {
// the key is either an element id or an id from the data
var myId = d.id ? d.id : this.id;
// console.log("data key: " + d.id + " element id: " + this.id + " new: " + d.newCall);
return myId;
}).classed("newCall", function (d) {
var f = d.newCall ? d.newCall : false;
//console.log(this.id + " " + f )
return f;
})
agent
.select(".newCall text")
.attr("transform", null)
.style("text-anchor", "middle")
.attr("dy", function (d) { return 4; })
.attr("fill", "black")
.style("font-size", function (d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 24) + "px"; })
.text(function (d) {
var txt = d.Calls ? d.Calls.length > 0 ? d.Calls.length : "" : "";
return txt;
});
agent.enter().append("g")
.classed("newCall", function (d) {
return d.newCall ? d.newCall : false;
});
agent.exit().classed("newCall", function (d) {
// console.log(d);
return false;
});
};

How to avoid multiple svg elements from being created?

I am trying to build a D3 chart in angular2 component. When ever I click on the link to create D3 chart it creates a new instance of it. Please notice the HTML where multiple copies of SVG tags are created. any ideas why is it happening and how to avoid it?
every time i click on the link to create a D3 chart, it should clear/null the existing instance and create a fresh chart component.
Code to create the new instance from the parent component,
import { Component } from '#angular/core';
import { BubbleChart } from '../Charts/BubbleChart';
#Component({
template: `
<div id="divBubbleChart">
<bubble-chart></bubble-chart>
</div>
`,
directives: [BubbleChart]
})
export class CacheVisualization {
constructor() {
console.log("CacheVisualization component being called");
}
}
the child d3 component
import { Component, ViewEncapsulation } from '#angular/core';
import { HTTP_PROVIDERS, Http } from '#angular/http';
import { Configuration } from '../Configuration/Configuration';
declare var d3: any;
#Component({
selector: 'bubble-chart',
styleUrls: ['css/BubbleChart.css'],
providers: [Configuration, HTTP_PROVIDERS],
template: ``,
encapsulation: ViewEncapsulation.None
})
export class BubbleChart {
public resultData: any;
public chartData: any;
margin = 5;
diameter = 660;
constructor(private _Configuration: Configuration) {
console.log("In constructor of BubbleChartComponent");
this.DrawBubbleChart();
}
private DrawBubbleChart(): void {
console.log("Inside DrawBubbleChart in BubbleChartComponent");
//console.log(this.resultData);
var color = d3.scale.linear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
var pack = d3.layout.pack()
.padding(2)
.size([this.diameter - this.margin, this.diameter - this.margin])
.value(function (d) { return d.size; })
var svg = d3.select("body").append("svg")
.attr("width", this.diameter)
.attr("height", this.diameter)
.append("g")
.attr("transform", "translate(" + this.diameter / 2 + "," + this.diameter / 2 + ")");
var chart = d3.json(this._Configuration.BLUESKYDATACACHEAPI_GETEXTRACTORQUEUESLATEST, (error, root) => {
if (error) throw error;
var focus = root,
nodes = pack.nodes(root),
view;
var circle = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function (d) { return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root"; })
.style("fill", (d) => { return d.children ? color(d.depth) : null; })
.on("click", (d) => { if (focus !== d) zoom.call(this, d), d3.event.stopPropagation(); });
var text = svg.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("class", "label")
.style("fill-opacity", function (d) { return d.parent === root ? 1 : 0; })
.style("display", function (d) { return d.parent === root ? "inline" : "none"; })
.text(function (d) { return d.name; });
var node = svg.selectAll("circle,text");
d3.select("body")
.style("background", "white")
//.style("vertical-align", "top")
//.style("background", color(-1))
.on("click", () => { zoom.call(this, root); });
zoomTo.call(this, [root.x, root.y, root.r * 2 + this.margin]);
function zoom(d) {
var focus0 = focus; focus = d;
var transition = d3.transition()
.duration(d3.event.altKey ? 7500 : 750)
.tween("zoom", (d) => {
var i = d3.interpolateZoom(view, [focus.x, focus.y, focus.r * 2 + this.margin]);
return (t) => { zoomTo.call(this, i(t)); };
});
transition.selectAll("text")
.filter(function (d) { return d.parent === focus || this.style.display === "inline"; })
.style("fill-opacity", function (d) { return d.parent === focus ? 1 : 0; })
.each("start", function (d) { if (d.parent === focus) this.style.display = "inline"; })
.each("end", function (d) { if (d.parent !== focus) this.style.display = "none"; });
}
function zoomTo(v) {
var k = this.diameter / v[2]; view = v;
node.attr("transform", function (d) { return "translate(" + (d.x - v[0]) * k + "," + (d.y - v[1]) * k + ")"; });
circle.attr("r", function (d) { return d.r * k; });
}//end zoomTo
});//end chart
}//end DrawBubbleChart
}
After assigning the ID to the component created, it creates ID for the parent html tag and not for the "svg" tag. refer the snapshot below
To remove elements that you are creating, you should remove them when you remove your component. Angular 2 has OnDestory lyfecycle hook. Try to implement it.
Inside it you you remove svg element from body.
ngOnDestroy() {
// save the element on creation and..
// remove element from body here
}
Solution 1: Check if svg element already exists before creating SVG element
d3.select("body").append("svg"). If exists use that instead of appending a new SVG
var svg = d3.select('#mySVG').transition()
Solution 2: Create a new function that should be invoked for chart update 'UpdateDrawBubbleChart()'. In BubbleChart constructor check if instance of the class already exists and call 'UpdateDrawBubbleChart', in this function either remove SVG element or use d3 transition.

D3 How to filter menu based on nested data

I have the following plunk
https://plnkr.co/edit/gDbUE99nNNMtYp9IpR1d?p=info and am struggling to get my dropdown menu working with nested data. Ive generated a menu from a first level nest ('starting point'), which shows a list of nationalities that is generated from the second level nest ('Nat'). I want to use the menu to load the different 'Nat' assosicated with each 'starting point'.
Previously I created my menu like this, but it doesn't seem to be working with 'nested_data'. Is d3.filter() wrong for this?
list.on('change', function() {
var selected = d3.select(this).select("select").property("value")
var cd = nested_data.filter(function(d) {
return (selected == d.key)
});
updateNested_data(cd)
});
...
var filter = nested_data.filter(function(d) {
return ("Berkner Island" == d.key)
});
updateNested_data(filter)
I'm also having a problem with exit() in my update function, I think because I'm binding my data too early? But for this question I'd like to focus on how to get the menu working.
Full code
d3.csv("data.csv", function(CSV) {
var nested_data = d3.nest()
.key(function(d) {
return d['starting point'];
})
.key(function(d) {
return d.Nat;
})
.entries(CSV);
// CREATE DROPDOWN
var list = d3.select("#opts")
list.append("select").selectAll("option")
.data(nested_data)
.enter().append("option")
.attr("value", function(d) {
return d.key;
})
.text(function(d) {
return d.key;
});
// MENU
list.on('change', function() {
var selected = d3.select(this).select("select").property("value")
var cd = nested_data.filter(function(d) {
return (selected == d.key)
});
updateNested_data(cd)
});
// CONFIG
var canvas = d3.select('#explorers').data(nested_data)
// BIND, ENTER, REMOVE
function updateNested_data(nested_data) {
var country = canvas.selectAll(".country")
var countryEnter = country
.data(function(d) {
return d.values
})
.enter().append('div')
.attr('class', 'country')
countryEnter
.append("p")
.attr('class', 'label')
.style('font-weight', 'bold')
.text(function(d) {
return d.key;
})
country.exit().remove();
};
//FILTER
var filter = nested_data.filter(function(d) {
return ("Berkner Island" == d.key)
});
// UPDATE
updateNested_data(filter)
});
There is no problem anywhere except the way you entering and exiting:
function updateNested_data(cd) {
//set the data to the selection
var country = canvas.selectAll(".country").data(cd.values)
var countryEnter = country
.enter().append('div')
.attr('class', 'country')
countryEnter
.append("p")
.attr('class', 'label')
.style('font-weight', 'bold')
.text(function(d) {
return d.key;
})
//exit on selection
country.exit().remove();
};
working code here

D3 Force Directed Map Source and Target Properties

Is it possible to use names other than source and target for a D3 Force Directed Map? I am connecting to an API that provides me with the needed information but they are supplying it as 'src-port' and 'dst-port'. If I change the names in a static JSON file to 'source' and 'target' the links on my map appear. If I leave it as is, I get the following error message:
e.source is undefined
Is there a way I can specify what property names to look for instead of using the defaults, 'source' and 'target'?
Here is the complete code to work with:
function buildMap(node, ids, mode) {
d3.select("svg").remove();
width = 960,
height = 500;
svg = d3.select(node).append("svg")
.attr("width", width)
.attr("height", height)
.attr("id","chart")
.attr("preserveAspectRatio","xMidYMid")
.attr("viewBox","0 0 960 500");
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-220)
.linkDistance(40)
.size([width, height]);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function (d) {
return "<strong>DPID:</strong> <span style='color:red'>" + d.dpid + "</span><br />" + "<strong>Type:</strong> <span style='color:red'>" + d.type + "</span>";
})
svg.call(tip);
//d3.json("http://192.168.1.82:9000/wm/onos/topology", function(error, graph) {
d3.json("http://localhost:9001/data/nodes.json", function(error, graph) {
force
.nodes(graph.switches)
.links(graph.links.forEach(function(l) {
l.source = l["src-port"];
l.target = l["dst-port"];
})
)
.on("tick", tick)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.selectAll(".node")
.data(graph.switches)
.enter().append("circle")
.attr("class", function(d) {
//if(d.type == undefined) {
return "node";
//} else {
// return d.type;
//}
})
.attr("r", function(d) {
//if(d.type == undefined) {
return 5;
//} else {
// switch(d.type) {
// case "core":
// return 10;
// break;
// case "agg":
// return 8;
// break;
// default:
// return 5;
// }
//}
})
.style("fill", function(d) {
//var count = ids.length;
//if(count <= 0)
// return d.color;
var color = "#15a9ff";
//if(d3.select(this).style("fill") == color){
// for (i = 0; i <= count; i++) {
// if(ids[i] != undefined) {
// if(ids[i].attributes.id == d.instance_id) {
// color = d.color;
// }
// }
// }
return color;
// }
}
)
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.call(force.drag)
.on("click", function (d) {
enyo.Signals.send("onNodeSelected", d);
});
//node.append("title")
// .text(function(d) { return d.name; });
function tick(e) {
//if(mode == "tree") {
// var k = 6 * e.alpha;
// graph.links.forEach(function(d, i) {
// d.source.y -= k;
// d.target.y += k;
// });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
//} else {
// link.attr("x1", function(d) { return d.source.x; })
// .attr("y1", function(d) { return d.source.y; })
// .attr("x2", function(d) { return d.target.x; })
// .attr("y2", function(d) { return d.target.y; });
// node.attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; });
}
//}
});
}
There is no way to do this through the API; you would have to modify the source. One easy way of dealing with this is to simply copy the values in src-port and dst-port into source and target:
links.forEach(function(l) {
l.source = l.src-port;
l.target = l.dst-port;
});

d3 join on object key within array

My data looks like this:
[
{
name: "Joel Spolsky",
values: [
{
timestamp: 1380432214730,
value: 55
},
{
timestamp: 1380432215730,
value: 32
},
{
timestamp: 1380432216730,
value: 2
},
{
timestamp: 1380432217730,
value: 37
},
// etc
]
},
{
name: "Soul Jalopy",
values: [
{
timestamp: 1380432214730,
value: 35
},
{
timestamp: 1380432215730,
value: 72
},
{
timestamp: 1380432216730,
value: 23
},
{
timestamp: 1380432217730,
value: 3
},
// etc
]
},
// and so on
]
I pass this data into d3.layout.stack so a y and y0 get added. I then draw this stacked layout.
When the data changes, I join the new data to the old.
I can join the groups on name like this:
var nameGroups = this.chartBody.selectAll(".nameGroup")
.data(this.layers, function (d) {
return d.name;
});
But I'm having trouble joining the rectangles (or "bars") on timestamp. The best I can do (so far) is join them on the values key:
var rects = nameGroups.selectAll("rect")
.data(function (d) {
return d.values;
});
How do I join this "inner data" on the timestamp key?
I've tried including the array index:
var rects = nameGroups.selectAll("rect")
.data(function (d, i) {
return d.values[i].timestamp;
});
But that doesn't work because (I think) the timestamp is matched per array index. That is, the join isn't looking at all timestamp values for a match, just the one at that index.
UPDATE
Here is my complete update function:
updateChart: function (data) {
var that = this,
histogramContainer = d3.select(".histogram-container"),
histogramContainerWidth = parseInt(histogramContainer.style('width'), 10),
histogramContainerHeight = parseInt(histogramContainer.style('height'), 10),
width = histogramContainerWidth,
height = histogramContainerHeight,
nameGroups, rects;
/*
FWIW, here's my stack function created within my
init function:
this.stack = d3.layout.stack()
.values(function (d) { return d.values; })
.x(function (dd) { return dd.timestamp; })
.y(function (dd) { return dd.value; });
*/
// save the new data
this.layers = this.stack(data);
// join the new data to the old via the "name" key
nameGroups = this.chartBody.selectAll(".nameGroup")
.data(this.layers, function (d, i) {
return d.name;
});
// UPDATE
nameGroups.transition()
.duration(750);
// ENTER
nameGroups.enter().append("svg:g")
.attr("class", "nameGroup")
.style("fill", function(d,i) {
//console.log("entering a namegroup: ", d.name);
var color = (that.colors[d.name]) ?
that.colors[d.name].value :
Moonshadow.helpers.rw5(d.name);
return "#" + color;
});
// EXIT
nameGroups.exit()
.transition()
.duration(750)
.style("fill-opacity", 1e-6)
.remove();
rects = nameGroups.selectAll("rect")
.data(function (d) {
// I think that this is where the change needs to happen
return d.values;
});
// UPDATE
rects.transition()
.duration(750)
.attr("x", function (d) {
return that.xScale(d.timestamp);
})
.attr("y", function(d) {
return -that.yScale(d.y0) - that.yScale(d.y);
})
.attr("width", this.barWidth)
.attr("height", function(d) {
return +that.yScale(d.y);
});
// ENTER
rects.enter().append("svg:rect")
.attr("class", "stackedBar")
.attr("x", function (d) {
return that.xScale(d.timestamp); })
.attr("y", function (d) {
return -that.yScale(d.y0) - that.yScale(d.y); })
.attr("width", this.barWidth)
.attr("height",function (d) {
return +that.yScale(d.y); })
.style("fill-opacity", 1e-6)
.transition()
.duration(1250)
.style("fill-opacity", 1);
// EXIT
rects.exit()
.transition()
.duration(750)
.style("fill-opacity", 1e-6)
.transition()
.duration(750)
.remove();
}
You're not actually passing a key function in your code. The key function is the optional second argument to .data() (see the documentation). So in your case, the code should be
.data(function(d) { return d.values; },
function(d) { return d.timestamp; })
Here the first function tells D3 how to extract the values from the upper level of the nesting and the second how, for each item in the array extracted in the first argument, get the key.

Resources