D3 binding data to SVG tooltip - d3.js

Id like to use this tooltip from this answer however I can't figure out how to bind data to it.
In this example I'd like to have two circles, that on hover show their size value as text.
watch.csv
circle,size
green,5
yellow,10
code
d3.csv("watch.csv", function(error, watch) {
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text(function(d) { return d.size; }) //TRYING THIS
var sampleSVG = d3.select(".example_div")
.append("svg:svg")
.attr("class", "sample")
.attr("width", 300)
.attr("height", 300);
d3.select(".example_div svg")
.data(watch) //AND THIS
.enter()
.append("svg:circle")
.attr("stroke", "black")
.attr("fill", "aliceblue")
.attr("r", 30)
.attr("cx", 52)
.attr("cy", 52)
.on("mouseover", function() {
return tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 10) + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
});
Plunker

Change your tooltip var for this:
var tooltip = d3.select("body")
.append("div")
.attr("class", "someTooltip")
.style("opacity", 0);
Style your tooltip in the CSS, using the selector .someTooltip.
And change all your "mouseove", "mousemove" and "mouseout" for this:
.on("mousemove", function(d) {
tooltip.html("The size is: " + d.size)
.style("top", (d3.event.pageY - 10) + "px")
.style("left", (d3.event.pageX + 10) + "px")
.style("opacity", 1);
}).on("mouseout", function(){
tooltip.style("opacity", 0);
});

First you need to add jquery and tipsy which is a jquery plugin.
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tipsy/1.0.3/jquery.tipsy.js"></script>
tipsy css
<link href="https://cdnjs.cloudflare.com/ajax/libs/jquery.tipsy/1.0.3/jquery.tipsy.css" rel="stylesheet" type="text/css" />
Next you need to bind the data to the tipsy jquery way like this.
$('svg circle').tipsy({ //select all circles
gravity: 'w',
html: true,
title: function() {
var d = this.__data__;//get the data from the dom
return 'Hi there! My color is <span style="color:' + d.circle + '">' + d.size + '</span>'; //make the tipsy tool tip using the data
}
});
Remove all the tooltip code like this as that is not needed:
.on("mouseover", function() {
return tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 10) + "px");
})
.on("mouseout", function() {
return tooltip.style("visibility", "hidden");
});
Remove this
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text(function(d) { return d.size; }) //TRYING THIS
So your code will look like this here

Related

D3.JS bar graph in Firefox cshtml

I am trying to put together an interactive bar chart in cshtml.
The good news is it works on every browser except for Firefox.
That being said I'd very much like to know why it is failing on Firefox when it even works on Internet Explorer.. I mean come on, the internet doesn't even work on Internet Explorer.
I have added in what I believe to be the relevant patch of code here:
function buildVisualization(dataSet) {
var barWidth = (chartWidth / dataSet.Items.length - 1) - 1;
var bars = svg.selectAll("rect")
.data(dataSet.Items);
// Build bars for each item
// Example "rect" element: <rect x="200" y="400" width="300" height="100" style="" class="" />
bars.enter()
.append("rect")
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) } )
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate)})
.attr("width", function (item) { return barWidth})
.attr("height", function (item) { return yScale(item.Rate)})
.attr("fill", "teal");
bars.exit().remove();
bars.transition()
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked))} )
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate)})
.attr("width", function (item) { return barWidth})
.attr("height", function (item) { return yScale(item.Rate)})
.attr("fill", "teal");
}
That being said I can provide any information required if requested.
I should point out that when run the chart itself is put into the right place however the bars (an important bit of a bar chart) are all pushed off to the left and stacked on top of each other though they do change height when different options are selected so it seems to be something wrong with the positioning rather than with how they are created. Any advice would be quite welcome.
Entire Snippet:
#{
ViewBag.Title = "Bar Chart";
var choices = new List<SelectListItem>
(){
new SelectListItem(){Text= "C#", Value="c#", Selected=true },
new SelectListItem(){Text= ".Net", Value=".net" },
new SelectListItem(){Text= "ASP.Net", Value="asp.net" },
new SelectListItem(){Text= "ASP.Net MVC", Value="asp.net-mvc" },
new SelectListItem(){Text= "C", Value="c" },
new SelectListItem(){Text= "C++", Value="c++" },
new SelectListItem(){Text= "JavaScript", Value="javascript" },
new SelectListItem(){Text= "Objective C", Value="objective-c" },
new SelectListItem(){Text= "PHP", Value="php" },
new SelectListItem(){Text= "Ruby", Value="ruby" },
new SelectListItem(){Text= "Python", Value="python" }
};
}
<style type="text/css">
svg g.axis {
font-size: .75em;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
svg g.axis text.label {
font-size: 2em;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
svg g.axis path,
svg g.axis line {
fill: none;
stroke: black;
shape-rendering: crispEdges;
}
</style>
<h2>#ViewBag.Title</h2>
<div class="row">
<div class="col-md-8">
<p>This demo takes tag information from data.stackexchange.com and projects it below.</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
#Html.Label("TagChoice", "Tag")
#Html.DropDownList("TagChoice", choices)
</div>
</div>
<div class="row">
<div id="chartContainer">
</div>
</div>
#Scripts.Render("~/bundles/d3")
#section Scripts{
<script type="text/javascript">
$(document).ready(function () {
$("#TagChoice").on("change", function () {
var tag = $(this).val();
var url = "/api/tags?tag=";
url += encodeURIComponent(tag);
$.getJSON(url, function (data) {
buildVisualization(data);
});
});
$("#TagChoice").change();
});
// Overall dimensions of the SVG
var height = 400;
var width = 900;
// Padding...
var leftPadding = 75;
var bottomPadding = 50;
// Actual space for the bars
var chartWidth = width - leftPadding;
var chartHeight = height - bottomPadding;
//Building the scale for the heights
var yScale = d3.scale
.linear()
.range([0, chartHeight])
.domain([0, 21000]);
var yAxisScale = d3.scale
.linear()
.range([chartHeight, 0])
.domain([0, 21000]);
//Building the scale for the bar locations
var xScale = d3.time.scale()
.domain([new Date("5-1-2008"), new Date("2-1-2014")])
.range([leftPadding, width - 10]);
//Building a Y axis
var yAxis = d3.svg.axis()
.scale(yAxisScale)
.orient("left");
// Building an X Axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(d3.time.format("%m/%d/%Y"));
// Build the overall SVG container
var svg = d3.select("#chartContainer")
.append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "chart");
// Adding the Axes
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + leftPadding + ",0)")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("dy", "-55px")
.attr("dx", "-50px")
.attr("class", "label")
.style("text-anchor", "end")
.text("Number of Questions Asked");
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + chartHeight + ")")
.call(xAxis)
.append("text")
.attr("dy", "40px")
.attr("dx", "475px")
.attr("class", "label")
.style("text-anchor", "end")
.text("Month Asked");
function buildVisualization(dataSet) {
var barWidth = (chartWidth / dataSet.Items.length - 1) - 1;
var bars = svg.selectAll("rect")
.data(dataSet.Items);
// Build bars for each item
// Example "rect" element: <rect x="200" y="400" width="300" height="100" style="" class="" />
bars.enter()
.append("rect")
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) })
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate) })
.attr("width", function (item) { return barWidth })
.attr("height", function (item) { return yScale(item.Rate) })
.attr("fill", "teal");
bars.exit().remove();
bars.transition()
.attr("x", function (item, i) { return xScale(new Date(item.DateAsked)) })
.attr("y", function (item, i) { return chartHeight - yScale(item.Rate) })
.attr("width", function (item) { return barWidth })
.attr("height", function (item) { return yScale(item.Rate) })
.attr("fill", "teal");
}
</script>
}
Perhaps the reason of your problem is that in Chrome
>> new Date("5-1-2008")
Thu May 01 2008 ...
while in Firefox:
>> new Date("5-1-2008")
Invalid Date
(this is relevant to lines, where you construct xScale)

D3.js: Is this good practise for data-DOM-binding?

I'm trying to grasp the D3 basics. I understand that it's crucial to get the binding right.
So I did a simple web page with update, enter and exit operations. Every time the data changes (modified, added, removed) I call a function where the data is bound to svg-objects in the update, enter and exit ways.
I wonder if my approach is a good one?
<html>
<head>
<style>
.drawarea { border-style: solid; }
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data, drawArea;
function init(){
data = [{"px" : 25, "py" : 25, "s" : 5}, {"px" : 25, "py" : 50, "s" : 5}, {"px" : 25, "py" : 75, "s" : 10} ];
drawArea = d3.select("#drawareadiv").append("svg:svg")
.attr("class", "drawarea")
.attr("width", 500)
.attr("height", 250);
draw();
}
function draw(){
var domRects = drawArea.selectAll("rect");
// update
domRects
.data(data)
.attr("x", function(d) { return d.px; })
.attr("y", function(d) { return d.py; })
.attr("width", function(d) { return d.s; })
.attr("height", function(d) { return d.s; });
// enter
domRects
.data(data)
.enter()
.append("svg:rect")
.attr("x", function(d) { return d.px; })
.attr("y", function(d) { return d.py; })
.attr("width", function(d) { return d.s; })
.attr("height", function(d) { return d.s; });
// exit
domRects
.data(data)
.exit()
.remove();
}
function updateDataAndBinding(){
for(i=0; i<data.length; i++)
data[i].px += 10;
draw();
}
function enterDataAndBinding(){
var px = 25;
var py = 25 * (data.length+1);
var s = Math.floor(data.length/2)*5+5;
data.push({"px" : px, "py" : py, "s" : s});
draw();
}
function exitDataAndBinding(){
data.splice(data.length-1, 1);
draw();
}
</script>
</head>
<body onload="init()">
<div id="drawareadiv"></div>
<button onclick="updateDataAndBinding()">updateDataAndBinding</button>
<button onclick="enterDataAndBinding()">enterDataAndBinding</button>
<button onclick="exitDataAndBinding()">exitDataAndBinding</button>
</body>
</html>
I'd suggest two things:
Don't bind the data again and again. Do this just one time:
var domRects = drawArea.selectAll("rect").data(data);
As this is D3 v4.x, use merge to deal with the "enter" and "update" selections together:
// enter + update
domRects.enter()
.append("svg:rect")
.attr("x", function(d) { return d.px; })
.attr("y", function(d) { return d.py; })
.attr("width", function(d) { return d.s; })
.attr("height", function(d) { return d.s; })
.merge(domRects)
.attr("x", function(d) { return d.px; })
.attr("y", function(d) { return d.py; })
.attr("width", function(d) { return d.s; })
.attr("height", function(d) { return d.s; });
Here is the demo (click "run code snippet"):
<style>
.drawarea { border-style: solid; }
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var data, drawArea;
function init(){
data = [{"px" : 25, "py" : 25, "s" : 5}, {"px" : 25, "py" : 50, "s" : 5}, {"px" : 25, "py" : 75, "s" : 10} ];
drawArea = d3.select("#drawareadiv").append("svg:svg")
.attr("class", "drawarea")
.attr("width", 500)
.attr("height", 200);
draw();
}
function draw(){
var domRects = drawArea.selectAll("rect").data(data);
// enter
domRects.enter()
.append("svg:rect")
.attr("x", function(d) { return d.px; })
.attr("y", function(d) { return d.py; })
.attr("width", function(d) { return d.s; })
.attr("height", function(d) { return d.s; })
.merge(domRects)
.attr("x", function(d) { return d.px; })
.attr("y", function(d) { return d.py; })
.attr("width", function(d) { return d.s; })
.attr("height", function(d) { return d.s; });
// exit
domRects.exit()
.remove();
}
function updateDataAndBinding(){
for(i=0; i<data.length; i++)
data[i].px += 10;
draw();
}
function enterDataAndBinding(){
var px = 25;
var py = 25 * (data.length+1);
var s = Math.floor(data.length/2)*5+5;
data.push({"px" : px, "py" : py, "s" : s});
draw();
}
function exitDataAndBinding(){
data.splice(data.length-1, 1);
draw();
}
</script>
</head>
<body onload="init()">
<div id="drawareadiv"></div>
<button onclick="updateDataAndBinding()">updateDataAndBinding</button>
<button onclick="enterDataAndBinding()">enterDataAndBinding</button>
<button onclick="exitDataAndBinding()">exitDataAndBinding</button>
</body>
Other than that, your approach seems to be a good (and standard) one.

Text tag not displaying properly in D3 SVG

I have the following code to generate a simple graph in D3...
var width = 800, height = 800;
// force layout setup
var force = d3.layout.force()
.charge(-200).linkDistance(30).size([width, height]);
// setup svg div
var svg = d3.select("#graph").append("svg")
.attr("width", "100%").attr("height", "100%")
.attr("pointer-events", "all");
// load graph (nodes,links) json from /graph endpoint
d3.json("/graph", function(error, graph) {
if (error) return;
force.nodes(graph.nodes).links(graph.links).start();
// render relationships as lines
var link = svg.selectAll(".link")
.data(graph.links).enter()
.append("line")
.attr("class", "link")
.attr("stroke", "black");
// render nodes as circles, css-class from label
var node = svg.selectAll(".node")
.data(graph.nodes).enter()
.append("circle")
.attr("r", 10)
.call(force.drag);
// html title attribute for title node-attribute
node.append("title")
.text(function (d) { return d.title; })
.attr("font-family", "sans-serif")
.attr("font-size", "20px")
.attr("fill", "black");
// force feed algo ticks for coordinate computation
force.on("tick", function() {
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; });
});
});
Everything looks great except I cannot see the title. If I look at the DOM I see the following....
<circle r="10" cx="384.5368115283669" cy="390.4516626058579"><title font-family="sans-serif" font-size="20px" fill="black">My NAme</title></circle>
However no matter what I try I cannot seem to see the title. What am I missing here?
This is nothing to blame on d3. As per the SVG 1.1 spec the title is a description string which will not be rendered when the graphic gets rendered. Most browsers, however, will display the title as a tooltip when the mouse pointer is over the element.
I have set up an updated snippet based on your code. Placing the mouse over the red circle will bring up the tooltip "My Name".
<svg width="200" height="200">
<circle r="10" cx="38" cy="39" fill="red">
<title>My Name</title>
</circle>
</svg>
To add text to your svg which will get rendered, use the <text> element instead.
<svg width="200" height="200">
<text x="50" y="100">My Text</text>
</svg>

Render multiple svg in d3js

Im trying to render circles in d3js. On multiply svg:s. In my HTML i have all the svg:s likte this.
<section id="a" class="render">
<svg></svg>
</section>
<section id="b" class="render">
<svg></svg>
</section>
<section id="c" class="renderr">
<svg></svg>
</section>
<section id="d" class="render">
<svg></svg>
</section>
What im trying to do is this. Depending on some values i get back from a json-call i want to render and animate the circles in different svgs.
My javascript looks like this.
for(i = 0; i<json.answers[0].length; i++) {
personal.color = json.answers[0][i].color;
personal.answer = json.answers[0][i].alternative_id;
if(json.answers[0][i].alternative_id == 1) {
drawSvg("#a")
}
if(json.answers[0][i].alternative_id == 2) {
drawSvg("#b")
}
if(json.answers[0][i].alternative_id == 3) {
drawSvg("#c")
}
if(json.answers[0][i].alternative_id == 4) {
drawSvg("#d")
}
createCircle(personal);
}
drawSvg = function(el) {
svg = d3.select(el+ " svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
force.on("tick", function(e) {
svg.selectAll("path")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
}
);
});
}
createCircle = function(p) {
people.push({
type: "circle",
size: Math.random() * 1200 + 700
});
force.start();
svg.selectAll("path")
.data(people)
.enter().append("path")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("d", d3.svg.symbol()
.size(function(d) { return d.size; })
.type(function(d) { return d.type; }))
.style("fill", p.color)
.attr("class","person")
.attr("id","p_"+index)
index++;
}

Create a Graduated Symbol Map using D3

I'm trying to create a graduated symbol map and am struggling to find a way to make this happen. I can create pie charts and I can create a symbol map, but how to place pie charts at specific coordinates on a map?
I've successfully placed proportional symbols at the proper coordinates, but I can't figure out how to replace the symbols with pie charts. Every attempt leaves me with an empty map.
I've tried to merge Mike Bostock's Pie Multiples example with his Symbol Map example but have instead only managed to expose my lack of understanding of d3's data and event functions.
Index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Graduated Symbol Map</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://d3js.org/topojson.v1.min.js"></script>
<script type="text/javascript" src="http://d3js.org/queue.v1.min.js"></script>
<style type="text/css">
body {
text-align: center;
}
</style>
</head>
<body>
<script type="text/javascript">
var width = 400,
height = 500;
var radius = d3.scale.sqrt()
.domain([0, 5e5])
.range([0, 40]);
// Define map projection
var projection = d3.geo.transverseMercator()
.rotate([72.57, -44.20])
.translate([175,190])
.scale([12000]);
// Define path generator
var path = d3.geo.path()
.projection(projection);
// Create SVG Element
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
queue()
.defer(d3.json, "vermont.json")
.defer(d3.json, "fed.json")
.await(ready)
function ready(error, vt, centroid) {
svg.append("path")
.attr("class", "towns")
.datum(topojson.feature(vt, vt.objects.vt_towns))
.attr("d", path)
.style("stroke", "#ddd")
.style("stroke-width", "1px")
.style("fill", "#ccc");
svg.append("path")
.datum(topojson.feature(vt, vt.objects.lake))
.attr("d", path)
.style("stroke", "#89b6ef")
.style("stroke-width", "1px")
.style("fill", "#b6d2f5");
svg.selectAll(".symbol")
.data(centroid.features.sort(function(a,b) {
return b.properties.dollars - a.properties.dollars; }))
.enter().append("path")
.attr("class", "symbol")
.attr("d", path.pointRadius(function(d) {
return radius(d.properties.dollars); })
)
.style("fill", "#509e2f")
.style("stroke", "#ddd")
.style("fill-opacity", 0.7);
}
</script>
</body>
</html>
fed.json (there are 14 points, all with the same format)
'dollars' are the total dollars spent by the four organizations, the size of the pie chart should relate to this value.
{
"type": "Feature",
"id": "53",
"geometry": {
"type": "Point",
"coordinates": [-73.1349605, 43.0278745]
},
"properties": {
"name": "Bennington County",
"dollars": 79730,
"unit": "county",
"ECP": 49608,
"LIP": 3451,
"NAP": 0,
"SURE": 26671
}
},
vermont.json
Large file, map is not the issue.
References I've used
http://mbostock.github.io/protovis/ex/symbol.html
http://bl.ocks.org/mbostock/1305111
http://bl.ocks.org/mbostock/4342045
Here's my solution, using #LarsKotthoff's answer from this question to solve the projection issue.
I've scaled the pie charts in a rather hackish way.
index.html
Below is just the ready function. Everything else has remained unchanged.
function ready(error, vt, centroid) {
svg.append("path")
.attr("class", "towns")
.datum(topojson.feature(vt, vt.objects.vt_towns))
.attr("d", path)
.style("stroke", "#ddd")
.style("stroke-width", "1px")
.style("fill", "#ccc");
svg.append("path")
.datum(topojson.feature(vt, vt.objects.lake))
.attr("d", path)
.style("stroke", "#89b6ef")
.style("stroke-width", "1px")
.style("fill", "#b6d2f5");
var pieArray = [],
pieMeta = [];
function pieData() {
for (var i=0; i<centroid.features.length; i++) {
pieArray.push([
parseInt(centroid.features[i].properties.ECP),
parseInt(centroid.features[i].properties.LIP),
parseInt(centroid.features[i].properties.NAP),
parseInt(centroid.features[i].properties.SURE)
]);
pieMeta.push([
projection(centroid.features[i].geometry.coordinates),
radius(parseInt(centroid.features[i].properties.dollars))
]);
}
return [pieArray, pieMeta];
};
var svgSvg = d3.select("body").select("svg").selectAll("g")
.data(pieData()[0])
.enter().append("svg:svg")
.attr("width", width)
.attr("height", height)
.append("svg:g")
.style("opacity", 0.8)
.attr("property", function (d,i) {
return pieData()[1][i][1];
})
.attr("transform", function (d,i) {
var coordinates = pieData()[1][i][0];
return ("translate(" + (coordinates[0]) + "," +
(coordinates[1]) + ")");
});
svgSvg.selectAll("path")
.data(d3.layout.pie())
.enter().append("svg:path")
.attr("d", d3.svg.arc()
.innerRadius(0)
.outerRadius(function (d) {
var chartList = d3.select(this.parentNode).attr("property");
return chartList;
}))
.style("fill", function(d, i) { return color[i]; });
}

Resources