Getting graph coordinates from mouse in nvd3 - nvd3.js

I'm trying to see where in the chart the user clicked. The below code almost works but it is offset by some amount. I suspect I need to handle the click relative to the charting area and not take the axis into account. What is the proper way to do this?
d3.select('#chart1 svg')
.datum(chartData)
.on("click", mouseClick )
.call(chart);
...
function mouseClick()
{
var coordinates = d3.mouse(this);
var x = chart.lines.xScale().invert(coordinates[0]);
var y = chart.lines.yScale().invert(coordinates[1]);
console.log(x+','+y);
}

You just have to subtract the margin:
function mouseClick()
{
var coordinates = d3.mouse(this);
var x = chart.lines.xScale().invert(coordinates[0]-chart.margin().left);
var y = chart.lines.yScale().invert(coordinates[1]-chart.margin().top);
}

Related

d3 v4 voronoi find nearest neighbours in a scatterplot svg filled with (dots / circles)

I am trying to find the nearest neighbors in a scatterplot using the data attached below, with the help of this snippet -
const voronoiDiagram = d3.voronoi()
.x(d => d.x)
.y(d => d.y)(data);
data.forEach(function(d){
console.log(d, voronoiDiagram.find(d.x, d.y, 50));
});
Now the dataset I am using is the standard iris sepal, petal lengths data in
format -
{"sepalLength":7.7,"sepalWidth":3,"petalLength":"6.1","petalWidth":"2.3","species":"virginica","index":135,"x":374.99999999999994,"y":33.75,"vy":0,"vx":0},
{"sepalLength":6.3,"sepalWidth":3.4,"petalLength":"5.6","petalWidth":"2.4","species":"virginica","index":136,"x":524.9999999999999,"y":191.25,"vy":0,"vx":0},
{"sepalLength":6.4,"sepalWidth":3.1,"petalLength":"5.5","petalWidth":"1.8","species":"virginica","index":137,"x":412.5,"y":179.99999999999994,"vy":0,"vx":0},
{"sepalLength":6,"sepalWidth":3,"petalLength":"4.8","petalWidth":"1.8","species":"virginica","index":138,"x":374.99999999999994,"y":225,"vy":0,"vx":0},
....
So, essentially it is in the form of
{d: {x, y, sepal length, width, petal length, width}.
Now, I am trying to find the nearest neighbors with d3 voronoi from reference.
But, all I get is this in results -
Let point d in my dataset =
{"sepalLength":5.9,"sepalWidth":3,"petalLength":"5.1","petalWidth":"1.8","species":"virginica","index":149,"x":374.99999999999994,"y":236.24999999999997,"vy":0,"vx":0}
Now, the voronoiDiagram.find(d.x, d.y, 50) for this is resulting in -
"[375,236.25]"
That is, the same point with coordinates rounded off instead of another point.
So, how do I exclude current point being scanned in this case from the voronoi diagram.
Also, If I exclude that point & re-calculate everything would this be good from the performance perspective ?
Can anyone help me with finding nearest neighbors from a set of points
with d3 voronoi / quadtrees (I have tried a couple of examples already from Mike Bostock but couldn't get them to work in my case because of some errors,
so will post them if d3 voronoi does not help).
voronoiDiagram.find(y, x, r) will only ever return, at most, once cell. From the API documentation:
Returns the nearest site to point [x, y]. If radius is specified, only sites within radius distance are considered. (link)
I've previously read that as being plural, apparently I've never looked closely (and I think there is a large amount of utility in being able to find all points within a given radius).
What we can do instead is create a function fairly easily that will:
start with voronoiDiagram.find() to find the cell the point falls in
find the neighbors of the found cell
for each neighbor, see if its point is within the specified radius
if a neighbors point is within the specified radius:
add the neighbor to a list of cells with points within the specified radius,
use the neighbor to repeat steps 2 through 4
stop when no more neighbors have been found within the specified radius, (keep a list of already checked cells to ensure none are checked twice).
The snippet below uses the above process (in the function findAll(x,y,r)) to show points within the specified distance as orange, the closest point will be red (I've set the function to differentiate between the two).
var width = 500;
var height = 300;
var data = d3.range(200).map(function(d) {
var x = Math.random()*width;
var y = Math.random()*height;
var index = d;
return {x:x,y:y,index:index}
});
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
var circles = svg.selectAll()
.data(data, function(d,i) { return d.index; });
circles = circles.enter()
.append("circle")
.attr("cx",function(d) { return d.x; })
.attr("cy",function(d) { return d.y; })
.attr("r",3)
.attr("fill","steelblue")
.merge(circles);
var voronoi = d3.voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.size([width,height])(data);
var results = findAll(width/2,height/2,30);
circles.data(results.nearest,function(d) { return d.index; })
.attr("fill","orange");
circles.data([results.center],function(d) { return d.index; })
.attr("fill","crimson");
var circle = svg.append("circle")
.attr("cx",width/2)
.attr("cy",height/2)
.attr("r",30)
.attr("fill","none")
.attr("stroke","black")
.attr("stroke-width",1);
circle.transition()
.attrTween("r", function() {
var node = this;
return function(t) {
var r = d3.interpolate(30,148)(t);
var results = findAll(width/2,height/2,r);
circles.data(results.nearest,function(d) { return d.index; })
.attr("fill","orange");
return r;
}
})
.duration(2000)
.delay(1000);
function findAll(x,y,r) {
var start = voronoi.find(x,y,r);
if(!start) return {center:[],nearest:[]} ; // no results.
var queue = [start];
var checked = [];
var results = [];
for(i = 0; i < queue.length; i++) {
checked.push(queue[i].index); // don't check cells twice
var edges = voronoi.cells[queue[i].index].halfedges;
// use edges to find neighbors
var neighbors = edges.map(function(e) {
if(voronoi.edges[e].left == queue[i]) return voronoi.edges[e].right;
else return voronoi.edges[e].left;
})
// for each neighbor, see if its point is within the radius:
neighbors.forEach(function(n) {
if (n && checked.indexOf(n.index) == -1) {
var dx = n[0] - x;
var dy = n[1] - y;
var d = Math.sqrt(dx*dx+dy*dy);
if(d>r) checked.push(n.index) // don't check cells twice
else {
queue.push(n); // add to queue
results.push(n); // add to results
}
}
})
}
// center: the point/cell that is closest/overlapping, and within the specified radius, of point x,y
// nearest: all other cells within the specified radius of point x,y
return {center:start,nearest:results};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

geoJSON projection with d3.js and dc.js for South Africa and provinces

I have been struggling for a few days trying to use dc.js with d3.js projection to draw a map of South Africa and the provinces. I have exhausted my search as most results incorporate the path used for SVG when not using dc.js and I can't seem to find a suitable example for correcting a projection in dc.js.
I can't seem to find the map thats being drawing and I don't know how to correct my projection.
I really really don't know what i'm missing, and anyone that can assist will be appreciated.
UPDATE: I have geoJson that ive tested in mapshaper and it works so the geojson is fine. I am just struggling with the projection.
zaMap = zaMapString
//new array
var zaMapData = [];
for(var p in zaMap["features"])
{
console.log("ndx2 province data " + zaMap["features"][p]["properties"]["name"]);
zaMapData.push({
province: zaMap["features"][p]["properties"]["name"],
donation: 1000
})
};
//crossfilter instance
var ndx2 = crossfilter(zaMapData);
//dimensions and group for dc/d3
var provinceDim = ndx2.dimension(function(d) {console.log("province d " + d["province"]); return d["province"];});
var donationsByProvince = provinceDim.group().reduceSum(function(d) {
return d["donation"];
});
//geoChoroplethChart
var zaChart = dc.geoChoroplethChart("#map");
//start of chart code using d3 and dc
zaChart.dimension(provinceDim)
.group(donationsByProvince)
.width(1000)
.height(330)
.colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
.projection(d3.geo.mercator()
.scale(26778)
.translate([8227, 3207]))
.overlayGeoJson(zaMap["features"], "name", function (d) {
return d.properties.name;
});
dc.renderAll();
$("#progress").css({"display": "none"});
})
UPDATE 2: I switched from fiddle to codepen so I could upload the geoJson file as a asset. The geoJson takes a while to load but using code from an existing stackoverflow question, I have gotten the map to draw and projection to correct itself automatically. The d3.js function is not wrapping the dc.js to tie in with crossfilter.js as yet but I am working on that. But this is progress :)
In http://jsfiddle.net/Jimmypoo/f67xo5ry/1/, you are trying to use JSON.parse to parse an zaMapString, which is already a JS object. You don't need to parse it, it's not a string.
Secondly, d3.json is meant for passing in a remote URL, which d3 grabs for you. You are trying to pass in a JS object, which already exists. So you can remove that function, and simply use .overlayGeoJson(zaMap["features"], "name", function (d) { inside.
You also forgot to include jQuery, yet you use it in $("#progress").css({"display": "none"});. You'll need to wrap the entire JS section in a $(document).ready as well.
Also, you are including the scripts multiple times, in both minified and unminified forms.You only need one instance of each library.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.min.js"></script>
You are also trying to include dc's CSS as JavaScript.
<script src="http://cdnjs.cloudflare.com/ajax/libs/dc/1.7.0/dc.css"></script>
It should be added in JsFiddle's left-hand side resource panel instead.
I also don't think assigning #map directly to the body of your document is going to make things easier for you either..would recommend including something interior of that like <div id="map" style="width:100%;height:300px"></div>
These suggestions don't solve all your problems but get you most of the way along.You still have projection issues. Here is an forked fiddle to move from - http://jsfiddle.net/uggtjem6/
I have gotten the geoJson to work with d3.js, dc.js and crossfiler.
var width = 300;
var height = 400;
var zaMapData = [];
//geoChoroplethChart
var zaChart = dc.geoChoroplethChart("#map");
d3.json("https://s3-us-west-2.amazonaws.com/s.cdpn.io/384835/layer1.json", function(json) {
var zaMap = JSON.stringify(json);
console.log(zaMap);
for (var i = 0; i < json.features.length; i++) {
console.log("ndx2 province data " + json["features"][i]["properties"]["PROVINCE"]);
zaMapData.push({
province: json["features"][i]["properties"]["PROVINCE"],
donation: i*1000
})
};
//crossfilter instance
var ndx2 = crossfilter(zaMapData);
//dimensions and group for dc/d3
var provinceDim = ndx2.dimension(function(d) {console.log("province d " + d["province"]); return d["province"];});
var donationsByProvince = provinceDim.group().reduceSum(function(d) {
return d["donation"];
});
var max_province = donationsByProvince.top(1)[0].value;
// create a first guess for the projection
var center = d3.geo.centroid(json)
var scale = 150;
var offset = [width/2, height/2];
var projection = d3.geo.mercator().scale(scale).center(center)
.translate(offset);
// create the path
var path = d3.geo.path().projection(projection);
// using the path determine the bounds of the current map and use
// these to determine better values for the scale and translation
var bounds = path.bounds(json);
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
var scale = (hscale < vscale) ? hscale : vscale;
var offset = [width - (bounds[0][0] + bounds[1][0])/2,
height - (bounds[0][1] + bounds[1][1])/2];
// new projection
projection = d3.geo.mercator().center(center)
.scale(scale).translate(offset);
path = path.projection(projection);
//create dc.js chart
zaChart.dimension(provinceDim)
.group(donationsByProvince)
.width(width)
.height(height)
.colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
.colorDomain([0, max_province])
.projection(d3.geo.mercator()
.center(center)
.scale(scale)
.translate(offset))
.overlayGeoJson(json["features"], "PROVINCE", function (d) {
return d.properties.PROVINCE;
})
.title(function (p) {
return "Province: " + p["key"]
+ "\n"
+ "Total Donations: R " + Math.round(p["value"])
});
dc.renderAll();
});
My codepen here.

change axis color dimple

I have a very simple line chart in dimple. I want to change the x and y axis colour to white.
var svg = dimple.newSvg(".line_chart_container", 400, 300),dataset;
var chart = new dimple.chart(svg, dataset);
var x = chart.addCategoryAxis("x", "Attempts");
//x.style ("fill","red")
var y = chart.addMeasureAxis("y", "Value");
y.showGridlines = true;
x.showGridlines = true;
var s = chart.addSeries(["Metric", "Value"], dimple.plot.bubble);
var lines = chart.addSeries("Metric", dimple.plot.line);
lines.lineWeight = 2;
lines.lineMarkers = true;
chart.assignColor("Metric", "#30D630");
chart.draw();
s.shapes.style("opacity", function (d) {
return (d.yValue === 0 ? 0 : 0.8);
});
I've checked dimple.axis documentation in GitHub but couldn't find any thing. There is a dimple.axis.colors attribute, but it changes the color of the data and not the axis. Does dimple even support this?
I've also tried to add style attribute(like in D3):
x.style ("fill","red")
but caught an error: Uncaught TypeError: x.style is not a function
Any idea?
x is not a d3 selection, it is a dimple.axis. You can access the inner d3 selection with the shapes property (which is standard for any dimple object). There is an example of that here.
Depending on if you want to change the line color, text color, or everything, you would do
x.shapes.selectAll("*").style("fill", "white")
where * could also be "text" or "line".
One note : the individual tick marks are <line> nodes, and to change their color you need to use 'stroke', not 'fill'.
Also, the actual axis line itself is not a <line> element, it's a <path> element. So to change that color would be :
x.shapes.select("path.dimple-custom-axis-line").style("stroke", "white");
You can also just manually write a css rule to override the style for a chart :
g.dimple-axis > g.tick > line, g.dimple-axis path.dimple-custom-axis-line {
stroke:white;
}
For X-axis
d3.selectAll("path.domain")[0][0].style.stroke = "red";
For Y-axis
d3.selectAll("path.domain")[0][1].style.stroke = "yellow";

Semantic zoom on map with circle showing capital

I wanted to implement semantic zoom on map of d3.js. I have developed a example of map and Major cities of particular country, which is working fine but sometime circle got overlap due to near places in maps so if i implement semantic zoom which will solve my circle overlapping problem.
I don't understand how to transform only graph not circle in my map.
My zooming function code is :
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("d", path.projection(projection));
g.selectAll("path")
.attr("d", path.projection(projection));
});
My jsfiddle link
Anybody help me please!
Are you asking how to not scale the circles according to the zoom? The way you have it you are scaling the g element and the circles are in it. The way I'd do it is to "shrink" the circles when zoomed.
// zoom and pan
var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("circle")
.attr("r", function(){
var self = d3.select(this);
var r = 8 / d3.event.scale; // set radius according to scale
self.style("stroke-width", r < 4 ? (r < 2 ? 0.5 : 1) : 2); // scale stroke-width
return r;
});
});
Update fiddle.
I know this is an old post and it already has an accepted answer but as the original post suggests, d3's Semantic Zoom is a cleaner way of doing this. I implemented the same thing you did (circles on cities on a map) by following this demo https://bl.ocks.org/mbostock/368095. The only thing I had to change was I had to subtract the initial position of the circles in the transform function in order to correct their initial position.
function transform(t) {
return function(d) {
const point = [d.x, d.y] // this is the initial point
const tPoint = t.apply(point) // after translation
const finalPoint = [tPoint[0] - d.x, tPoint[1] - d.y]// subtract initial x & y to correct
return `translate(${finalPoint})`;
};
}

Normalize the Graph axes in NVD3 Line with focus chart

I am having two lines in th graph like in the picture having different X axis points. I just want to compare the lines. So I need to bring the second line under the first line (Normalize the graph like the second picture)
I tried to use nvd3 domain and range option and some others too. But its not working. Could you guys tell me how to get those normalized graph. I dont worry about the Tick format as far as the lines are comparable.
PICTURE 1 (the graph I am having)
https://www.dropbox.com/s/sycwppnlachju2s/Screenshot%20from%202014-06-19%2014%3A21%3A40.png
PICTURE 2 (Graph I need)
https://www.dropbox.com/s/eoajf0yyk96w6y7/Screenshot%20from%202014-06-19%2014%3A20%3A21.png
nv.addGraph(function() {
var chart = nv.models.lineWithFocusChart();
chart.xAxis
.tickFormat(d3.format(',f'));
chart.yAxis
.tickFormat(d3.format(',.2f'));
chart.y2Axis
.tickFormat(d3.format(',.2f'));
d3.select('#chart svg')
.datum(data)
.transition().duration(500)
.call(chart)
;
nv.utils.windowResize(chart.update);
return chart;
});
This is my data file. data.json
[{"key":"Memory_FREEmyses1","values":[{"x":"1395426430","y":"200028"},
{"x":"1395426431","y":"199904"},{"x":"1395426432","y":"187620"},
{"x":"1395426434","y":"187504"},{"x":"1395426435","y":"187380"},
{"x":"1395426436","y":"187008"},{"x":"1395426437","y":"186760"},
{"x":"1395426438","y":"186512"},{"x":"1395426439","y":"186388"},
{"x":"1395426440","y":"186264"},{"x":"1395426441","y":"181804"},
{"x":"1395426443","y":"181084"},{"x":"1395426444","y":"181084"}]},
{"key":"Memory_FREEmyses2","values":[{"x":"1395426455","y":"178604"},
{"x":"1395426456","y":"178348"},{"x":"1395426457","y":"178356"},
{"x":"1395426458","y":"178232"},{"x":"1395426460","y":"178108"},
{"x":"1395426461","y":"177860"},{"x":"1395426462","y":"177480"},
{"x":"1395426463","y":"176992"},{"x":"1395426464","y":"176868"},
{"x":"1395426465","y":"176620"},{"x":"1395426466","y":"176612"},
{"x":"1395426467","y":"176620"}]}]
Try to map your data like:
data = data.map(function(series) {
var d0 = series.values[0].x;
var dN = series.values[series.values.length -1].x;
series.values = series.values.map(function(d) {
return {
x: 10*(d.x-d0)/(dN-d0),
y: d.y
}
});
return series;
})
See demo.

Resources