d3.scale.quantize producing unexpected result, how to debug? - json

I have a working example of a choropleth that takes data values from .csv file and calculates the color ranges.
There are four possible colors that a region can take, depending on the population.
For application I am trying to develop, I need data to be loaded by server from a database. As a dummy example, I made a route that returns json.
json.features contains the geojson information, and json.features.properties.value - population value.
Here is the function I use. It draws a map correctly, and the population values on tooltips are correct as well. The color domain is [98, 629725]. However there is just one region that takes the very dark color, only four regions that take a little brighter one, and all other regions are taking the other color. If you look at the link I provide at the top - there the distribution of color is more uniform. But in the map made with this code - I have the same color for place with 5000 population and 90000 population.
I'm stuck right now because I don't really see where could the problem come from, and I don't have a good idea on how to investigate it. Can you suggest what would be the first things to check in this situation? Maybe you already see something wrong with this code?
function draw_map (dataroute) {
var w = 500;
var h = 800;
var projection = d3.geo.transverseMercator()
.rotate([-27,-65,0])
.translate([w/2, h/2])
.scale([3500])
var path = d3.geo.path()
.projection(projection);
var color = d3.scale.quantize()
.range(["#c6dbef","#6baed6","#2171b5","#084594"])
.domain([0, 100]);
var svg = d3.select("#map")
.attr("width", w)
.attr("height", h);
svg.append("rect")
.attr("class", "background")
.attr("width", w)
.attr("height", h);
var g = svg.append("g")
queue()
.defer(d3.json, dataroute)
.await(ready);
function ready(error, json) {
if (error) throw error;
color.domain([
d3.min(json.features, function(d) { return d.properties.value; }),
d3.max(json.features, function(d) { return d.properties.value; })
]);
console.log([
d3.min(json.features, function(d) { return d.properties.value; }),
d3.max(json.features, function(d) { return d.properties.value; })
]);
// returns [98, 629725]
g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d",path)
.style("fill", colormap)
.style("stroke", "#08306b")
.attr("class", "feature")
.on("mouseover", function(d) {
d3.select(this)
.style("fill", "#08306b");
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var target = d3.select("#tooltip")
.style("left", coordinates[0] + "px")
.style("top", coordinates[1]-80 + "px");
target.select("#name")
.text(d.properties.text);
target.select("#stat")
.text(json.statdata_name + ": " + d.properties.value);
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function(d){
// If this is active node, keep the color on mouseout
d3.select(this)
.style("fill", colormap(d));
d3.select("#tooltip").classed("hidden", true);
});
}
//Return color for path
var colormap = function(d) {
var value = d.properties.value;
//console.log(value);
// If value exists, map to color
if (value) {
return color(value);
//console.log(color(value));
// if not, set color red
} else {
console.log("Undefined: " + d.properties.text + " " + value)
return "red";
}
}
}
In the original version that uses .csv file, the code is like this:
//Width and height
var w = 800;
var h = 800;
active = d3.select(null);
previous = d3.select(null);
var projection = d3.geo.transverseMercator()
.rotate([-27,-65,0])
.translate([w/2, h/2])
.scale([3500])
var path = d3.geo.path()
.projection(projection);
var color = d3.scale.quantize()
.range(["#c6dbef","#6baed6","#2171b5","#084594"])
.domain([0, 100]);
var svg = d3.select("#map")
.attr("width", w)
.attr("height", h);
svg.append("rect")
.attr("class", "background")
.attr("width", w)
.attr("height", h)
.on("click", reset);
var g = svg.append("g")
// Click to zoom
function clicked(d) {
// If this node is zoomed, unzoom
if (active.node() === this) {
d3.select(this).style("fill",colormap)
return reset();
} else {
// Otherwise recolor previous one, to avoid contamination
previous.style("fill",colormap)
}
active.classed("active", false);
active = d3.select(this).classed("active", true);
// store previous to uncolor it after clicking new one
previous = d3.select(this)
d3.select(this).style("fill","#08306b")
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .3 / Math.max(dx / w, dy / h),
translate = [w / 2 - scale * x, h / 2 - scale * y];
g.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function reset() {
active.classed("active", false);
active = d3.select(null);
g.transition()
.duration(750)
.style("stroke-width", "1px")
.attr("transform", "");
}
queue()
.defer(d3.json, "/static/finland.geojson")
.defer(d3.csv, "/static/kuntavakiluku.csv")
.await(ready);
var finland_geojson_data
var csv_data
function ready(error, json, data) {
if (error) throw error;
finland_geojson_data = json;
csv_data = data;
color.domain([
d3.min(data, function(d) { return d.Vakiluku; }),
d3.max(data, function(d) { return d.Vakiluku; })
]);
console.log(data.length);
for (var i = 0; i < data.length; i++) {
var dataState = data[i].Kunta;
var dataValue = parseInt(data[i].Vakiluku);
//Find the corresponding state inside the GeoJSON
for (var j = 0; j < json.features.length; j++) {
var jsonState = json.features[j].properties.text;
//console.log(jsonState)
if (dataState == jsonState ) {
//Copy the data value into the JSON
json.features[j].properties.value = dataValue;
//Stop looking through the JSON
break;
}
}
}
g.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d",path)
.style("fill", colormap)
.style("stroke", "#08306b")
.attr("class", "feature")
.on("mouseover", function(d) {
d3.select(this)
.style("fill", "#08306b");
var coordinates = [0, 0];
coordinates = d3.mouse(this);
var target = d3.select("#tooltip")
.style("left", coordinates[0] + "px")
.style("top", coordinates[1]-80 + "px");
target.select("#kunta")
.text(d.properties.text);
target.select("#vakiluku")
.text("Väestö: " + d.properties.value);
d3.select("#tooltip").classed("hidden", false);
})
.on("mouseout", function(d){
// If this is active node, keep the color on mouseout
if (active.node() === this) {
d3.select(this).style("fill","#08306b")
} else {
d3.select(this)
.style("fill", colormap(d));
}
d3.select("#tooltip").classed("hidden", true);
})
.on("click",clicked);
}
//Return color for path
var colormap = function(d) {
var value = d.properties.value;
// If value exists, map to color
if (value) {
return color(value);
// if not, set color red
} else {
console.log("Undefined: " + d.properties.text + " " + value)
return "red";
}
}

Quantized scales are linear scales with discreet ranges (as you can see in the link that #seb provided in the comments).
So in your case, if you provide 4 colors, the domain, figuratively speaking, will be split up in 4 sections (and your scale is a "map" for that).
Thus, the first quarter of the domain will be the first color, etc.
In your expl, the domain is [98, 629725], so the first color will be starting at 98 and end at (629725-98)/4+98 = 157'504.75
In code:
var scale = d3.scale.quantize()
.range(["#c6dbef", "#6baed6", "#2171b5", "#084594"])
.domain([98, 629725]);
So you can test that, e.g. here (check console output!)
You can see that 157504 yields col1, 157505 the second.
So it it no surprise that 5000 and 90000 are in the same "section".

Related

d3js dont drawing path

Axises drawn well done, but path is invisible.And no error messages in the console. Please help understand where is the problem.
function Chart(id, data) {
var chartData = JSON.parse(data);
const height = 800;
const width = 900;
var svg = d3.select(id)
.append("svg")
.attr("width", width)
.attr("height", height);
var parseDate = d3.timeParse("%d.%m.%Y %H:%M:%S");
var lineLvl = d3.line()
.x(function (d) {
return Date.parse(parseDate(d.timeData));
})
.y(function (d)
{
return d.lvlData;
});
var xscale =
d3.scaleTime()
.domain([Date.parse(parseDate(chartData[0].timeData))-60000,
Date.parse(parseDate(chartData[chartData.length - 1].timeData)) + 60000])
.range([0, width*0.95]);
var yscale = d3.scaleLinear()
.domain([0, d3.max(chartData, function (d) { return d.lvlData*1.1; } )] )
.range([height / 2, 0]);
var x_axis = d3.axisBottom().scale(xscale)
.tickFormat(d3.timeFormat("%H:%M"));
svg.append("path")
.attr("stroke", "blue")
.attr("stroke-width", 2)
.attr("fill", "none")
.attr("d", lineLvl(chartData));
var y_axis = d3.axisLeft().scale(yscale);
svg.append("g")
.attr("transform", "translate(50, 10)")
.call(y_axis);
var xAxisTranslate = height / 2 + 10;
svg.append("g")
.attr("transform", "translate(50, " + xAxisTranslate + ")")
.call(x_axis);
}
Data represented by array of structures like this
{"timeData":"17.07.2020 14:38:10","lvlData":"146575","temperatureData":"24","volumeData":"6155"}
You need to apply the scales to your data when plotting it:
var lineLvl = d3.line()
.x(function (d) {
return xscale(Date.parse(parseDate(d.timeData))); // run the x value through the x scale
})
.y(function (d)
{
return yscale(d.lvlData); // run the y value through the y scale
});
The reason you see no error is because the line is drawn, but with unscaled values it is drawn a long ways off the SVG. If you inspect the SVG, you'll see the path, it's drawn, just that its coordinates reflect the raw data, not the scaled data.

Adding transition Image

I am trying to make a d3 visual through block builder.
I want the picture of tourdefrance to appear first in the whole container and then fade away and then the bars to appear.
I know I have made it a little complicated by copying from various sources.
Would be great if someone could help out and also explain the problem.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: gold;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
// Mike Bostock "margin conventions"
var margin = {top: 10, right: 20, bottom: -500, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// D3 scales = just math
// x is a function that transforms from "domain" (data) into "range" (usual pixels)
// domain gets set after the data loads
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
// D3 Axis - renders a d3 scale in SVG
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, "%");
// create an SVG element (appended to body)
// set size
// add a "g" element (think "group")
// annoying d3 gotcha - the 'svg' variable here is a 'g' element
// the final line sets the transform on <g>, not on <svg>
body = d3.select("body");
show_image("https://upload.wikimedia.org/wikipedia/en/e/eb/Tour_de_France_logo.svg")
function show_image(source) {
var img = body.append("img").attr("src", source).style("opacity", 0)
; img.transition().duration(5000).ease(d3.easeLinear).style("opacity", 1)
}
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 + "," + margin.top + ")");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
svg.append("g")
.attr("class", "y axis")
.append("text") // just for the title (ticks are automatic)
.attr("transform", "rotate(-90)") // rotate the text!
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
// d3.tsv is a wrapper around XMLHTTPRequest, returns array of arrays (?) for a TSV file
// type function transforms strings to numbers, dates, etc.
d3.csv("https://gist.githubusercontent.com/vaibhavjaitly/1418c9beb1c71f6acffdaf21669e1956/raw/45af8ebcc87d6641eee8b4a60b7bafb8326607a0/data.csv", type, function(error, data) {
replay(data);
});
function type(d) {
// + coerces to a Number from a String (or anything)
d.Frequency = +d.Frequency;
return d;
}
function replay(data) {
var slices = [];
for (var i = 0; i < data.length; i++) {
slices.push(data.slice(0, i+1));
}
slices.forEach(function(slice, index){
setTimeout(function(){
draw(slice);
}, index * 300);
});
}
function draw(data) {
// measure the domain (for x, unique letters) (for y [0,maxFrequency])
// now the scales are finished and usable
x.domain(data.map(function(d) { return d.Letter; }));
y.domain([0, d3.max(data, function(d) { return d.Frequency; })]);
// another g element, this time to move the origin to the bottom of the svg element
// someSelection.call(thing) is roughly equivalent to thing(someSelection[i])
// for everything in the selection\
// the end result is g populated with text and lines!
svg.select(".x.axis").transition().duration(300).call(xAxis);
// same for yAxis but with more transform and a title
svg.select(".y.axis").transition().duration(300).call(yAxis)
// THIS IS THE ACTUAL WORK!
var bars = svg.selectAll(".bar").data(data, function(d) { return d.Letter; }) // (data) is an array/iterable thing, second argument is an ID generator function
bars.exit()
.transition()
.duration(300)
.attr("y", y(0))
.attr("height", height - y(0))
.style('fill-opacity', 0.00001024)
.remove();
// data that needs DOM = enter() (a set/selection, not an event!)
bars.enter().append("rect")
.attr("class", "bar")
.attr("y", y(0))
.attr("height", height - y(0));
// the "UPDATE" set:
bars.transition().duration(300).attr("x", function(d) { return x(d.Letter); }) // (d) is one item from the data array, x is the scale object from above
.attr("width", x.rangeBand()) // constant, so no callback function(d) here
.attr("y", function(d) { return y(d.Frequency); })
.attr("height", function(d) { return height - y(d.Frequency); }); // flip the height, because y's domain is bottom up, but SVG renders top down
}

D3v4 zoom coordinates of visible area

I'm trying to calculate the coordinates of the visible area after zooming. Is there a way to calculate it using d3.event.transform?
I tried a lot but couldn't make it work, at least not for d3v4. Using v3 is no opinion since everything else of the project is using v4.
Isolated Code: https://jsfiddle.net/qyvnhvmj/
All you need to do is grab the inverse of the points from the transform,
var width = 800,
height = 400,
Radius = 20;
var Circles, Data = [];
for (var i = 0; i < 3; i++) {
Data.push({
x: 100 + i * Radius * 8,
y: 100,
});
}
var zoom = d3.zoom().scaleExtent([1, 128]).on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.call(zoom);
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.style("fill", d3.color("gray"))
.attr("opacity", 0.1);
Circles = svg.selectAll("circle")
.data(Data)
.enter().append("circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", Radius);
function getVisibleArea(t) {
var l = t.invert([0, 0]),
r = t.invert([width, height]);
return Math.trunc(l[0]) + " x " + Math.trunc(l[1]) + " - " + Math.trunc(r[0]) + " x " + Math.trunc(r[1]);
}
function zoomed(d) {
Circles.attr("transform", d3.event.transform);
console.log("zoom transform: ", d3.event.transform);
d3.select("#area span").text(getVisibleArea(d3.event.transform));
}
svg {
position: absolute;
top: 50;
}
<svg width="800" height="400"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<a id="area">visible area: <span>?</span></a>
<br>
<br>

D3.js mouseovers on html5 canvas scatter plot

Let's say we draw our scatter plot not with SVG but with canvas.
So something like:
var scale = 1 + Math.floor(Math.random() * 10);
// Redraw axes
x.domain([0, scale]);
y.domain([0, scale]);
xg.call(xAxis);
yg.call(yAxis);
var points = randomPoints(scale),
colors = {};
// Update canvas
context.clearRect(0, 0, width, height);
picker.clearRect(0, 0, width, height);
points.forEach(function(p,i){
// Space out the colors a bit
var color = getColor(i * 1000 + 1);
colors[color] = p;
picker.fillStyle = "rgb(" + color + ")";
context.beginPath();
picker.beginPath();
context.arc(x(p[0]), y(p[1]), 10, 0, 2 * Math.PI);
picker.arc(x(p[0]), y(p[1]), 5, 0, 2 * Math.PI);
context.fill();
picker.fill();
});
Now how would we pass data into a mouseover event, say, to draw a tooltip?
The examples I've seen for tooltips all take for granted that you are handling an event with data bound to an element moused-over.
But what about for canvas?
I assume you'll need to use d3.bisector or something similar based on the x y coordinates of the mouse event.
One way is to iterate trough all the points and check if the x and y matches the vicinity of the click. This will definitely be slow when the scatter chart points are too many.{I think in your case you are making scatter chart in canvas just to over come that issue}
Other way is to make the use of quad tree.
First I am making some random 10000 points.
sampleData = d3.range(1000).map(function(d) {
var datapoint = {};
datapoint.id = "Sample Node " + d;
datapoint.x = Math.random() * 500;
datapoint.y = Math.random() * 500;
return datapoint;
})
Store all the points in the scatter chart in quad tree like this.
quadtree = d3.geom.quadtree()
.extent([[0,0], [500,500]]) //here 500 is the width and height of the canvas or the max x/y range of the points in scatter chart.
.x(function(d) {return d.x})
.y(function(d) {return d.y});
Pass all the points into quadtree:
quadData = quadtree(sampleData);
Now on click find the associated node data:
quadData = quadtree(sampleData);
d3.select("canvas").on("click", function(){
found = [];
//find in the vicinity of 10 pixel around the click.
search(quadData, d3.event.pageX -10, d3.event.pageY-10, d3.event.pageX +10, d3.event.pageY+10);
var message = "";
//iterate the found and make the message
found.forEach(function(d){
message += d.id + " ";
});
alert("selected Node" + message);
var data
})
Finally my search function to check for the nodes in the quadtree rectangle:
function search(quadtree, x0, y0, x3, y3) {
quadtree.visit(function(node, x1, y1, x2, y2) {
var p = node.point;
if (p) {
p.selected = (p.x >= x0) && (p.x < x3) && (p.y >= y0) && (p.y < y3);
if(p.selected){
found.push(p);
}
}
return x1 >= x3 || y1 >= y3 || x2 < x0 || y2 < y0;
});
}
Click on any of the circle and you will be alerted with the data it holds
working code here
I ended up using a solution suggested by Noah Veltman, as follows:
var margin = {top: 20, right: 10, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
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 + " " + margin.top + ")");
var factory = d3.geom.quadtree()
.extent([
[0, 0],
[width, height]
]);
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var xg = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")");
var yg = svg.append("g")
.attr("class", "y axis");
var chartArea = d3.select("body").append("div")
.style("left", margin.left + "px")
.style("top", margin.top + "px");
var canvas = chartArea.append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
context.fillStyle = "#f0f";
// Layer on top of canvas, example of selection details
var highlight = chartArea.append("svg")
.attr("width", width)
.attr("height", height)
.append("circle")
.attr("r", 7)
.classed("hidden", true);
redraw();
function redraw() {
// Randomize the scale
var scale = 1 + Math.floor(Math.random() * 10);
// Redraw axes
x.domain([0, scale]);
y.domain([0, scale]);
xg.call(xAxis);
yg.call(yAxis);
var points = randomPoints(scale);
var tree = factory(points);
// Update canvas
context.clearRect(0, 0, width, height);
points.forEach(function(p,i){
context.beginPath();
context.arc(x(p[0]), y(p[1]), 5, 0, 2 * Math.PI);
context.fill();
});
canvas.on("mousemove",function(){
var mouse = d3.mouse(this),
closest = tree.find([x.invert(mouse[0]), y.invert(mouse[1])]);
highlight.attr("cx", x(closest[0]))
.attr("cy", y(closest[1]));
});
canvas.on("mouseover",function(){
highlight.classed("hidden", false);
});
canvas.on("mouseout",function(){
highlight.classed("hidden", true);
});
}
function randomPoints(scale) {
// Get points
return d3.range(1000).map(function(d){
return [
Math.random() * scale,
Math.random() * scale
];
});
}
The key is the use of
var factory = d3.geom.quadtree()
.extent([
[0, 0],
[width, height]
]);
Which has a getter setter function based on the x, y value pair of each point.

Duplicate row in CSV with D3.js

Here is my problem: I have a CSV file that have the following colums
Item type (car, moto, bus,...)
Owner
City/State
Number (how many cars/moto/bus a specific owner owns)
I want to make a dynamic bubble D3.js chart. Each bubble should represent an item and I want that the user can sort them by type, owner or state. I found a script that is working well except that it takes each line from the CSV to make a bubble. I'm trying to find a way to "duplicate" the bubble when number < 1 but until now I didn't find...
What is your advice? Should I use D3.js to do that? jQuery? Other?
Here is the code:
d3.csv('data/test.csv', function (error, data) {
var width = 600, height = 600;
var fill = d3.scale.ordinal().range(['#827d92','#827354','#523536','#72856a','#2a3285'])
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height);
_.each(data, function (elem) {
elem.radius = 5;
elem.x = _.random(0, width);
elem.y = _.random(0, height);
});
var padding = 2;
var maxRadius = d3.max(_.pluck(data, 'radius'));
function getCenters(vname, w, h) {
var nodes = [], c =[], result = {};
var v = _.uniq(_.pluck(data, vname));
var l = d3.layout.treemap().size([w, h]).ratio(1/1);
_.each(v, function (k, i) {
c.push({name: k, value: 1});
});
nodes = l.nodes({children: c})[0].children;
for (var i = 0; i < nodes.length; i++) {
result[nodes[i].name] = nodes[i];
}
return result;
}
var nodes = svg.selectAll("circle")
.data(data);
nodes.enter().append("circle")
.attr("class", "node")
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", 2)
.style("fill", function (d) { return fill(d.cat_int); })
.on("mouseover", function (d) { showPopover.call(this, d); })
.on("mouseout", function (d) { removePopovers(); })
nodes.transition().duration(1000)
.attr("r", function (d) { return d.radius; })
var force = d3.layout.force()
.charge(0)
.gravity(0)
.size([width, height])
draw('cat_int');
$( ".btn" ).click(function() {
draw(this.id);
});
function draw (varname) {
var foci = getCenters(varname, 600, 600);
force.on("tick", tick(foci, varname, .45));
labels(foci)
force.start();
}
function tick (foci, varname, k) {
return function (e) {
data.forEach(function(o, i) {
var f = foci[o[varname]];
o.y += ((f.y + (f.dy / 2)) - o.y) * k * e.alpha;
o.x += ((f.x + (f.dx / 2)) - o.x) * k * e.alpha;
});
nodes
.each(collide(.1))
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; });
}
}
function labels (foci) {
svg.selectAll(".label").remove();
svg.selectAll(".label")
.data(_.toArray(foci)).enter().append("text")
.attr("class", "label")
.text(function (d) { return d.name })
.attr("transform", function (d) {
return "translate(" + (d.x + (d.dx / 2)) + ", " + (d.y + 20) + ")";
});
}
function removePopovers () {
$('.popover').each(function() {
$(this).remove();
});
}
function showPopover (d) {
$(this).popover({
placement: 'auto top',
container: 'body',
trigger: 'manual',
html : true,
content: function() {
return "Category: " + d.cat_int + "<br/> Owner: " + d.owner + "<br/>Sate: " + d.state; }
});
$(this).popover('show')
}
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
return function(d) {
var r = d.radius + maxRadius + padding,
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + quad.point.radius + padding;
if (l < r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
}
});