How to display property text on mouseover in d3 map - javascript

I am new to d3 and trying to figure out how to get a property ("NAME") to show up when hovering over a polygon in a map. I am aware that I should be doing something along the lines of .on("mouseover", function(d,i) { some function that returns properties.NAME } but can't figure out where to go from there. Here is the js as written, which just statically places the NAME property on each polygon:
<script>
var width = 950,
height = 650;
var projection = d3.geo.albers()
.scale(120000)
.center([22.85, 40.038]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("newnabes.json", function(error, topology) {
var nabes = topojson.object(topology, topology.objects.temp);
svg.selectAll("path")
.data(nabes.geometries)
.enter().append("path")
.attr("d", path);
svg.selectAll(".subunit-label")
.data(nabes.geometries)
.enter().append("text")
.attr("class", function(d) { return "subunit-label " + d.id; })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.NAME; });
});
</script>
Here is a small chunck of the json
{"type":"Topology",
"transform":{
"scale":[0.00003242681758896625,0.000024882264664420337],
"translate":[-75.28010087738252,39.889167081829875]},
"objects":{
"temp":{
"type":"GeometryCollection",
"geometries":[{
"type":"Polygon",
"id":1,
"arcs":[[0,1,2,3,4,5,6]],
"properties":{"NAME":"Haddington"}
},{
"type":"Polygon",
"id":2,
"arcs":[[7,8,9,10,-3,11]],
"properties":{"NAME":"Carroll Park"}
}...
Thanks

So I figured it out, courtesy of: Show data on mouseover of circle
The simplest solution is to just append the names to the svg title attribute:
svg.selectAll("path")
.data(nabes.geometries)
.append("svg:title")
.attr("class", function(d) { return "path " + d.id; })
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.properties.NAME; });
Still investigating a more stylish solution to the problem (eg tipsy).

Related

D3.js Appending line name to it

I have this chart:
The symbol names, '$TSLA' and '$AAPL' are not appended to the line, they are simply placed there with the appropriate x and y values. I would like to append them to the line, such that if the line ended at a different position, the symbol name would appear next to it. Like in this example.
Here is the code:
var svg = d3.select('#graph')
.append('svg')
.attr('width', w)
.attr('height', h);
svg.append('path')
.datum(data)
.attr('class', 'stock')
.attr('stroke', '#157145') ....
//I have included the above code because it is what's different from the link-
//my lines are appended to the variable 'svg'. I am, however, selecting the correct class
//in the below code:
....
var sec = d3.selectAll(".stock")
.data(kvals) //this data follows the same pattern as the examples'
.enter().append("g")
.attr("class", "stock");
sec.append("text")
.datum(function(d){
return {
name: d.name,
value: d.values[d.values.length-1]
};
})
.attr("transform", function(d){
console.log(xScale(d.value.date)); //not displayed in console.
return "translate(" + xScale(d.value.date) + "," + yScale(d.value.stock) +")"
})
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d){
return d.name;
});
Thank you for your help. If you need all the code, I can paste it.
Here's the code I used to add the labels. First part (knames) is what I used to plot the line too. (there might be an easier way of doing this):
var knames = d3.scaleOrdinal();
knames.domain(Object.keys(data[0]).filter(function(key){ //only get non-sma values
return !key.match(/([_])|(d)|(band)\w+/g)
}));
var kvals = knames.domain().map(function(name){
return {
name: name,
values: data.map(function(d){
return {
date: d.date,
stock: d[name]
};
})
};
});
var sec = svg.selectAll("stock")
.data(kvals)
.enter().append("g")
.attr("class", "stock");
sec.append("text")
.datum(function(d){
return {
name: d.name,
value: d.values[d.values.length-1]
};
})
.attr("transform", function(d){
return "translate(" + xScale(d.value.date) + "," + yScale(d.value.stock) +")"
})
.attr("x", 7)
.attr("dy", ".3em")
.style("fill", function(d) {
return accent(d.name);
})
.style('font-family', 'Helvetica')
.style('font-size', '11px')
.style('letter-spacing', '1px')
.style('text-transform', 'uppercase')
.text(function(d){
return d.name;
});

Add path but also text above the path - D3js v3?

Trying to add the textual data above the path in this Zoomable Sunburst graph: Zoomable Sunburst
I have this code:
d3.json(jsonUrl, function(error, data) {
if (error) {
throw error;
}
svg.selectAll("g")
.data(partition.nodes(data))
.enter()
.append("g")
.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", self.click)
.append("text")
.text(function(d) {
//console.dir(d);
return d.name + "\n" + formatNumber(d.value);
});
});
But it doesn't work. No text is shown. I know it is something like that but I make something wrong. I am using D3js version 3 at the moment.
Some ideas?
i just found this Donut Chart example and changed some parts in my code.
Though it looks cluttered it does the job. Maybe with some polishing I could get decent looks:
Here is my code:
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")"),
g = null;
d3.json(dataFile, function(error, data) {
if (error) {
throw error;
}
g = svg.selectAll(".arc")
.data(partition.nodes(data))
.enter()
.append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.on("click", self.click);
g.append("text")
.attr("transform", function (d) {
return "translate(" + arc.centroid(d) + ")";
})
.attr("dy", ".35em")
.text(function (d) {
return d.name + "\n" + formatNumber(d.value);
});
});

D3.js How to identify a single bar uniquely within a group bar chart

I have created group bar chart by using D3.js. Each group has 2 bars. When any bar is clicked it must show some data using custom alert box. Now the bar can click and it shows data.
var state = svg.selectAll(".TestSuite")
.data(data)
.enter().append("g")
.attr("class", "TestSuite")
.on("click", function(d,i) {
if(i==0){
Alert.render(d3.select(this).data()[0].FalseStatements);
}else{
Alert.render(d3.select(this).data()[0].TrueStatements);
}
})
.attr("transform", function (d) {
return "translate(" + x0(d.TestSuite) + ",0)";
});
But data is vary according to clicked bars. So how to identify each single bar within a single group uniquely.
Here "if condition" that I used does not do the thing I want.How do I correct it?
Thank you.
(Suppose one group of bar consists two bars, one shows true count and other shows false count for a particular scenario. When we click the bar which shows true count then it should appear "TrueStatements" which is already have in data.using d3.select(this).data()[0].TrueStatements can do this. And also when someone click the bar which shows false count then it should appear "FalseStatements" which is already have in data.using d3.select(this).data()[0].FalseStatements can do this. My question is how do we identify the bar which shows true count and the bar which shows false count uniquely for do this task.)
EDITED:
How I get the data for bar chart(This is inside a for loop)
originalDataSetForBarChart.push({
TestSuite: "TS"+treeIndex,
Pass: trueAppear,
Fail: falseAppear,
FalseStatements : falseStatement,
TrueStatements : trueStatement
});
Bar chart code
var margin = {
top: 20,
right: 10,
bottom: 30,
left: 40
},
width = 890 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .5);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#4169E1", "#800080"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(""));
var w = width + margin.left + margin.right;
var h = height + margin.top + margin.bottom;
var svg = d3.select(".chart1").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//svg.call(tip);
var xg = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
var yg = svg.append("g")
.attr("class", "y axis");
yg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Count");
I append bars to this chart inside a setInterval function using following method.
function update() {
startTime_barChart = new Date().getTime();
for (var i = 0; i < data.length; i++) {
var testSuite = d3.keys(data[i]).filter(function (key) {
return key !== "TestSuite";
});
}
data.forEach(function (d) {
d.trueFalseCount = testSuite.map(function (name) {
return {
name: name,
value: +d[name]
};
});
});
x0.domain(data.map(function (d) {
return d.TestSuite;
}));
x1.domain(testSuite).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function (d) {
return d3.max(d.trueFalseCount, function (d) {
return d.value;
});
})]);
//making the x axis/y axis
xg.call(xAxis);
yg.call(yAxis);
//removing all the rectangles
svg.selectAll(".TestSuite").remove();
var tip_word;
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
tip_word= "<strong style='color:white'>"+
"Pass count :"+
"</strong>"+
" <span style='color:white'>" + d.True +
"</span></br>"+
"<strong style='color:white'>"+
"Fail count :"+
"</strong>"+
" <span style='color:white'>" + d.False +
"</span>";
return word;
});
var state = svg.selectAll(".TestSuite")
.data(data)
.enter().append("g")
.attr("class", "TestSuite")
.on("click", function(d,i) {
if(i%2 == 0){//How to set this condition
Alert.render(d3.select(this).data()[0].FalseStatements);
}else{
Alert.render(d3.select(this).data()[0].TrueStatements);
}
})
.attr("transform", function (d) {
return "translate(" + x0(d.TestSuite) + ",0)";
});
svg.call(tip);
state.selectAll("rect")
.data(function (d) {
return d.trueFalseCount;})
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function (d) {
return x1(d.name);
})
.attr("y", function (d) {
return y(d.value);
})
.attr("height", function (d) {
return height - y(d.value);
})
.style("fill", function (d) {
return color(d.name);
});
if(barChartLegentController==1){
var legend = svg.selectAll(".legend")
.data(testSuite.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
barChartLegentController=2;
}
endTime_barChart = new Date().getTime();
var totalbar = (endTime_barChart-startTime_barChart)/1000;
//alert('Total bar time : '+ totalbar+' seconds');
}
I'm not sure I fully understand what you are asking yet but the best way to identifying any element/entity is with an id, something like the following:
d3.select(this).attr(id, function(d, i) {return 'bar_' + i});
Add this inside the iterative function where you are creating your bars. In this way you will be able to select them from anywhere in your code with a d3.select('#bar_1).
If you only want to identify each bar it would be something like this:
var state = svg.selectAll(".TestSuite")
.data(data)
.enter().append("g")
.attr("id", function(d,i) {return 'bar_' + i})
.attr("class", "TestSuite")
.on("click", function(d,i) {
if(i==0){
Alert.render(d3.select(this).data()[0].FalseStatements);
}else{
Alert.render(d3.select(this).data()[0].TrueStatements);
}
})
.attr("transform", function (d) {
return "translate(" + x0(d.TestSuite) + ",0)";
});
In the case that you would like to identify each bar with an Id related to its contents (true or false statements) I would suggest something like the following:
var state = svg.selectAll(".TestSuite")
.data(data)
.enter().append("g")
.attr("class", "TestSuite")
.on("click", function(d,i) {
var barId;
if(i==0){
barId = 'falseBar_' + i;
Alert.render(d3.select(this).data()[0].FalseStatements);
}else{
barId = 'trueBar_' + i;
Alert.render(d3.select(this).data()[0].TrueStatements);
}
d3.select(this).attr('id', barId);
})
.attr("transform", function (d) {
return "translate(" + x0(d.TestSuite) + ",0)";
});
In any case, this will assign an unique Id to every bar (i.e. "bar_25" or "falseBar_14") to each bar, giving you an ideal way to identify each bar.
EDIT: After OP showed me the actual code they are working with, the following are my suggestions for a solution (which are actually on the same lines as the code above).
The code you should actually be tinkering with is the one below the code you posted. It is where the actual bars are rendered:
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
My suggestion to add an id attribute to each bar would be the following:
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("id", function(d, i) {return 'bar_' + i}) // <-- Edited line
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
It is important that you understand why this, and not the code block you provided initially, is the pertinent one. As you well said, the first block renders each group of bars (hence the append("g") which stands for svg group). The second block starts with a append("rect") which means svg rectangle. This and other lines (i.e. style("fill")..., attr("x")... and attr("y")...) clearly give away that this block is the one dealing with the actual bars and not the groups.

d3.js stacked chart animations

I'm developing a stacked chart application.
http://jsfiddle.net/NYEaX/174/
I've placed it inside a jquery plugin to create multiple instances etc... different properties and eventually different data sources.
For now I am having problems animating the chart bars and the axis.
Animate bar code
animateBars: function(selector, data){
var w = $(selector).data("width");
var h = $(selector).data("height");
var margin = methods.getMargin(h);
methods.setDimensions(w, h, margin);
//methods.setX();
//methods.setY();
//methods.setDomain(data);
var initialHeight = 0;
//var svg = d3.select(selector + " .stackedchart");
var barholder = d3.select(selector + " .barholder");
var state = barholder.selectAll(".state")
.data(data)
.enter()
.append("g")
.attr("class", "g")
.attr("x", function(d) {
return methods.x(d.Label);
})
.attr("transform", function(d) {
return "translate(" + methods.x(d.Label) + ",0)";
});
var bar = state.selectAll("rect")
.data(function(d) {
return d.blocks;
});
// Enter
bar.enter()
.append("rect")
.attr("width", methods.x.rangeBand())
.attr("y", function(d) {
return methods.y(d.y1);
})
.attr("height", function(d) {
return methods.y(d.y0) - methods.y(d.y1);
})
.style("fill", function(d) {
return methods.color(d.name);
});
// Update
bar
.attr("y", function(d) {
return methods.y(d.y1);
})
.attr("height", function(d) {
return methods.y(d.y0) - methods.y(d.y1);
})
.transition()
.duration(500)
.attr("x", function(d) {
return methods.x(d.Label);
})
.attr("width", methods.x.rangeBand())
.attr("y", function(d) {
return methods.y(d.y1);
})
.attr("height", function(d) {
return methods.y(d.y0) - methods.y(d.y1);
});
// Exit
bar.exit()
.transition()
.duration(250)
.attr("y", function(d) {
return methods.y(d.y1);
})
.attr("height", function(d) {
return methods.y(d.y0) - methods.y(d.y1);
})
.remove();
}
One problem is that "state" is generated from the "enter()" method, so all your "bar" calls are only being executed when your "g.class" is being generated, not on update. Change this:
var state = barholder.selectAll(".state")
.data(data)
.enter()
.append("g")...
to this:
var state = barholder.selectAll(".state")
.data(data);
state.enter().append("g")...
See if that helps a bit. It doesn't seem to affect your fiddle, but you might be having problems other than d3. Try simplifying your fiddle and get the d3 stuff working by itself first.

D3: Substituting d3.svg.diagonal() with d3.svg.line()

I have implemented the following graph with the edges rendered with d3.svg.diagonal(). However, when I try substituting the diagonal with d3.svg.line(), it doesn't appear to pull the target and source data. What am I missing? Is there something I don't understand about d3.svg.line?
The following is the code I am referring to, followed by the full code:
var line = d3.svg.line()
.x(function(d) { return d.lx; })
.y(function(d) { return d.ly; });
...
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",d3.svg.diagonal())
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
The entire code:
var margin = {top: 20, right: 20, bottom: 20, left: 20},
width =1500,
height = 1500,
diameter = Math.min(width, height),
radius = diameter / 2;
var balloon = d3.layout.balloon()
.size([width, height])
.value(function(d) { return d.size; })
.gap(50)
var line = d3.svg.line()
.x(function(d) { return d.lx; })
.y(function(d) { return d.ly; });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + (margin.left + radius) + "," + (margin.top + radius) + ")")
root = "flare.json";
root.y0 = height / 2;
root.x0 = width / 2;
d3.json("flare.json", function(root) {
var nodes = balloon.nodes(root),
links = balloon.links(nodes);
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",d3.svg.diagonal())
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
var node = svg.selectAll("g.node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node");
node.append("circle")
.attr("r", function(d) { return d.r; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
node.append("text")
.attr("dx", function(d) { return d.x })
.attr("dy", function(d) { return d.y })
.attr("font-size", "5px")
.attr("fill", "white")
.style("text-anchor", function(d) { return d.children ? "middle" : "middle"; })
.text(function(d) { return d.name; })
});
A comparison of how the d attribute of the svg disappears when using "line."
Question is quite dated, but since I don't see an answer and someone might face the same problem, here it is.
The reason why simple replacement of diagonal with line is not working is because d3.svg.line and d3.svg.diagonal return different results:
d3.svg.diagonal returns function that accepts datum and its index and transforms it to path using projection. In other words diagonal.projection determines how the function will get points' coordinates from supplied datum.
d3.svg.line returns function that accepts an array of points of the line and transforms it to path. Methods line.x and line.y determine how coordinates of the point retreived from the single element of supplied array
D3 SVG-Shapes reference
SVG Paths and D3.js
So you can not use result of the d3.svg.line directly in d3 selections (at least when you want to draw multiple lines).
You need to wrap it in another function like this:
var line = d3.svg.line()
.x( function(point) { return point.lx; })
.y( function(point) { return point.ly; });
function lineData(d){
// i'm assuming here that supplied datum
// is a link between 'source' and 'target'
var points = [
{lx: d.source.x, ly: d.source.y},
{lx: d.target.x, ly: d.target.y}
];
return line(points);
}
// usage:
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",lineData)
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
Here's working version of jsFiddle mobeets posted: jsFiddle
I had the same problem...There's a jsFiddle here.
Note that changing line to diagonal will make it work.
Perhaps encapsulating the diagonal function and editing its parameters could work for you:
var diagonal = d3.svg.diagonal();
var new_diagonal = function (obj, a, b) {
//Here you may change the reference a bit.
var nobj = {
source : {
x: obj.source.x,
y: obj.source.y
},
target : {
x: obj.target.x,
y: obj.target.y
}
}
return diagonal.apply(this, [nobj, a, b]);
}
var link= svg.selectAll("path")
.data(links)
.enter().append("path")
.attr("d",new_diagonal)
.attr("class", ".link")
.attr("stroke", "black")
.attr("stroke-width", "2px")
.attr("shape-rendering", "auto")
.attr("fill", "none");
Just set the d attribute of link to line:
.attr("d", line)

Categories

Resources