Issue with D3 Smooth Zoom in of globe on click of plot - animation

I have created D3 globe.
I am stuck in issue, right now on click on plot, Map zoom in but it is not smooth zoom in.
I need to zoom in map with smooth transition.
http://projectsdemo.net/globe/v4/
globe.focus = function(d, k) { d3.selectAll('.globe').transition()
.duration(2000)
.tween("transform", function() {
var centroid = d3.geo.centroid(d);
var r = d3.interpolate(projection.rotate(), [-centroid[0], -centroid[1], 0]);
return function(t) {
//projection.rotate(r(t));
pathG.selectAll("path").attr("d", path);
var clipExtent = projection.clipExtent();
//projection.scale(1).translate([0, 0]).clipExtent(null);
//var b = path.bounds(d);
var minScale = 270,
maxScale = minScale * 5;
projection.rotate(r(t)).scale(Math.max(minScale, Math.min(maxScale, k)))
.translate([width / 2, height / 2])
.clipExtent(clipExtent);
}
});

Your rotate is transistioning because of this:
.rotate(r(t))
Where r is an interpolate function and t is the current step in the transition. It looks like your scale though:
.scale(Math.max(minScale, Math.min(maxScale, k)))
is just set to the same value at every step in the transition.
You need to set up a separate interpolate function for the scale:
var r = d3.interpolate(projection.rotate(), [-centroid[0], -centroid[1], 0]),
r2 = d3.interpolate(project.scale(), Math.max(minScale, Math.min(maxScale, k)));
Then, use this in your transition:
projection.rotate(r(t))
.scale(r2(t))
...

Related

d3: How to calculate delta-y correctly in a drag and drop?

Looking at the following d3 snippet and if you try dragging any circle up or down and thus, resizing the stacked bar elements you will notice that the cursor goes either faster or slower than the edge being resized actually the bars to the left-most are slower than the bars to the right-most... why is that? and how can be fixed?
The drag behavior is defined from line #110
var drag2 = d3.behavior.drag()
.on("drag", function(d) {
var datum = data[d.group];
var dy = d3.event.dy * 0.3; // <<<<<<<<<<<<<<<<< here is scaled down
if(d.i == 0) { // but no matter what weight I put here
datum.bars[0].y1 -= dy; // it will always be too fast or too slow ..
datum.bars[1].y0 -= dy;
} else {
datum.bars[1].y1 -= dy;
datum.bars[2].y0 -= dy;
}
d.y -= dy;
render(rects)
})
Basically the affecting line is 113 var dy = d3.event.dy * 0.3; here the event.dy is scaled down by 0.3 but why? I have played with that weight but no value seem to keep the edge being dragged and the cursor equal.
UDPATE This other example How can I click to add or drag in D3? shows that a correct way to calculate is using transform/translate but these are attributes of an element and in my OP the affected target is the data itself and not a visual element.
You have to use an inverse scale to calculate the dy, like so:
var drag2 = d3.behavior.drag()
.on("drag", function(d) {
var datum = data[d.group];
y.domain([d.total, 0]);
var dy = y.invert(d3.event.dy);
// ...
});
Since this is just a linear scale you could also do it like this:
var dy = d3.event.dy * d.total / height;

dc.js geochoropleth map scaling

I have a geo map. Everything is running just fine but the the map that is drawn is incredibly tiny. I have checked the GEOJSON for errors and it works fine. In the JS Box there is a proper working Demo that is commented out to see a working example.
How Do I get my map to Scale up to fill my svg?
http://codepen.io/MichaelArledge/pen/VeeVmY?editors=011
$.getJSON("https://googledrive.com/host/0B9jw0MX1C_D_N1plZFhjTlZwY3c", function(json){
var max = community_per_capita_totals.top(1)[0].value;
// create a first guess for the projection
var center = d3.geo.centroid(json)
var scale = 100;
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);
chart.dimension(community_dim)
.group(community_per_capita_totals)
.width(width)
.height(height)
.colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
.colorDomain([0, max])
.projection(d3.geo.mercator()
.center(center)
.scale(scale)
.translate(offset))
.overlayGeoJson(json["features"], "Community")
dc.renderAll();
});
The issue is with your logic for determining the projection.scale(). I am not sure how to modify your logic to give you a custom scale for your map, but here is an example of scale logic I have used in maps before. The factors I am multiplying width and height by are based on the overall aspect ratio of the albersUsa projection, so you would need to tweak those to get a good fit for the mercator projection.
var scale = Math.min(width * 1.2, height * 2.1);
var projection = albersUsaPr()
.scale(scale)
.translate([width / 2, height / 2]);

How do I draw horizontal bars with a label using either ChartJS or D3?

What's the best way of drawing multiple horizontal lines and labels for a simple line graph in either ChartJS or D3? I know that I could draw these as individual lines and then do a text overlay but I'm wondering if there is a simpler solution. Ideally I'd be able to create each of the labels below as one unit and move it anywhere.
If this is simpler in another JS graph library then feel free suggest.
Example below
To do it with Chart.js you have to extend the line chart
Chart.types.Line.extend({
name: "LineAlt",
initialize: function (data) {
// it's easier to programmatically update if you store the raw data in the object (vs. storing the geometric data)
this.marks = data.marks;
this.marks.xStart = Number(data.labels[0]);
this.marks.xStep = data.labels[1] - data.labels[0];
// make sure all our x labels are uniformly apart
if (!data.labels.every(function (e, i, arr) { return !i || ((e - arr[i - 1]) === this.marks.xStep); }, this))
throw "labels must be uniformly spaced";
Chart.types.Line.prototype.initialize.apply(this, arguments);
},
draw: function () {
Chart.types.Line.prototype.draw.apply(this, arguments);
// save existing context properties
var self = this;
var ctx = self.chart.ctx;
var scale = self.scale;
ctx.save();
// line properties
ctx.lineWidth = 1;
ctx.fillStyle = "#666";
ctx.strokeStyle = "#666";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.font = scale.font;
// draw marks
self.marks.forEach(function (mark) {
// assuming that the marks are always within the data range
var y = scale.calculateY(mark.y);
var x1 = scale.calculateX((mark.x1 - self.marks.xStart) / self.marks.xStep);
var x2 = scale.calculateX((mark.x2 - self.marks.xStart) / self.marks.xStep);
// draw line
ctx.beginPath();
ctx.moveTo(x1, y);
ctx.lineTo(x2, y);
// draw edges
ctx.moveTo(x1, y + 10);
ctx.lineTo(x1, y - 10);
ctx.moveTo(x2, y + 10);
ctx.lineTo(x2, y - 10);
ctx.stroke();
// draw text
ctx.fillText(mark.label, (x1 + x2) / 2, y + scale.fontSize * 1.5);
})
ctx.restore();
},
});
You pass in the data for drawing the lines like so
var data = {
...
marks: [
{
x1: 1.5,
x2: 3.5,
y: 50,
label: 'Label1'
},
{
x1: 5,
x2: 7,
y: 60,
label: 'Label2'
}
]
};
and you create the chart using this extended chart type
var myLineChart = new Chart(ctx).LineAlt(data);
You can update the lines like this
myLineChart.marks[0].y = 80;
myLineChart.marks[0].x1 = 9;
myLineChart.marks[0].x2 = 10;
and then call
myLineChart.update();
to reflect those changes on the canvas
Caveats
The (x axis) labels should be numeric and uniformly spaced.
The lines should be within the scale range of the y axis (alternatively you can do a scaleOverride to set the scale parameters so that the lines are within the y scale range)
Fiddle - http://jsfiddle.net/en92k763/2/

D3: What projection am I using? / How to simplify with a null projection?

I am attempting to simplify a d3 map on zoom, and I am using this example as a starting point. However, when I replace the json file in the example with my own (http://weather-bell.com/res/nws_regions.topojson), I get a tiny upside-down little map.
Here is my jsfiddle: http://jsfiddle.net/8ejmH
code:
var width = 900,
height = 500;
var chesapeake = [-75.959, 38.250];
var scale,
translate,
visibleArea, // minimum area threshold for points inside viewport
invisibleArea; // minimum area threshold for points outside viewport
var simplify = d3.geo.transform({
point: function (x, y, z) {
if (z < visibleArea) return;
x = x * scale + translate[0];
y = y * scale + translate[1];
if (x >= 0 && x <= width && y >= 0 && y <= height || z >= invisibleArea) this.stream.point(x, y);
}
});
var zoom = d3.behavior.zoom()
.size([width, height])
.on("zoom", zoomed);
// This projection is baked into the TopoJSON file,
// but is used here to compute the desired zoom translate.
var projection = d3.geo.mercator().translate([0, 0])
var canvas = d3.select("#map").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection(simplify)
.context(context);
d3.json("http://weather-bell.com/res/nws_regions.topojson", function (error, json) {
canvas.datum(topojson.mesh(topojson.presimplify(json)))
.call(zoomTo(chesapeake, 0.05).event)
.transition()
.duration(5000)
.each(jump);
});
function zoomTo(location, scale) {
var point = projection(location);
return zoom.translate([width / 2 - point[0] * scale, height / 2 - point[1] * scale])
.scale(scale);
}
function zoomed(d) {
translate = zoom.translate();
scale = zoom.scale();
visibleArea = 1 / scale / scale;
invisibleArea = 200 * visibleArea;
context.clearRect(0, 0, width, height);
context.beginPath();
path(d);
context.stroke();
}
function jump() {
var t = d3.select(this);
(function repeat() {
t = t.transition()
.call(zoomTo(chesapeake, 100).event)
.transition()
.call(zoomTo(chesapeake, 0.05).event)
.each("end", repeat);
})();
}
My guess is that the topojson file I am using already has the projection built in, so I should be using a null projection in d3.
The map renders properly if I do not use a projection at all: (http://jsfiddle.net/KQfrK/1/) - but then I cannot simplify on zoom.
I feel like I am missing something basic... perhaps I just need to somehow rotate and zoom into the map in my first fiddle.
Either way, I'd appreciate some help. Been struggling with this one.
Edit: I used QGIS to save the geojson file with a "EPSG:3857 - WGS 84 / Pseudo Mercator" projection.
However, when I convert this to topojson with the topojson command-line utility and then display it with D3 using the same code as above I get a blank screen.
Should I specify the projection within the topojson command-line utility? I tried to do that but I got an error message:
topojson --projection EPSG:3857 E:\gitstore\public\res\nws.geojson -o E:\gitstore\public\res\nws.topojson --id-property NAME
[SyntaxError: Unexpected token :]
The TopoJSON file doesn't have a projection built-in, you're simply using the default projection when you don't specify one (which is albersUsa, see the documentation). You can retrieve this projection by calling d3.geo.projection() without an argument. Then you can modify this projection in the usual way for zoom etc.
I set up this fiddle using the Mercator projection and I took a different approach to zooming in and out based on this block, which to me was a simpler approach. I have a feeling that there was an issue in the zoomTo function in the translate bit, but I could exactly what it was. So I replaced with the code below and included a recursive call:
function clicked(k) {
if (typeof k === 'undefined') k = 8;
g.transition()
.duration(5000)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -projection(chesapeake)[0] + "," + -projection(chesapeake)[1] + ")")
.each("end", function () {
(k === 8) ? k = 1 : k = 8;
clicked(k);
});

d3 pie chart transition with attrtween

i'm trying to somehow sweep in a half-donut-chart, meaning starting with a blank screen the chart starts drawing at -90 degree (or 270) and performs a halfcircle until reaching 90 degree. the code looks like:
var width = 800;
var height = 400;
var radius = 300;
var grad=Math.PI/180;
var data = [30, 14, 4, 4, 5];
var color = d3.scale.category20();
var svg = d3.select("body").append("svg").attr("width", width).attr("height",
`height).append("g").attr("transform", "translate(" + radius*1.5 + "," + radius*1.5 +
")");
var arc = d3.svg.arc().innerRadius(radius - 100).outerRadius(radius - 20);
var pie = d3.layout.pie().sort(null);
svg.selectAll("path").data(pie(data)).enter().append("path").attr("d",
arc).attr("fill",
function(d, i) { return color(i); }).transition().duration(500).attrTween("d", sweep);
function sweep(a) {
var i = d3.interpolate({startAngle: -90*grad, endAngle: -90*grad},{startAngle: -90*grad, endAngle: 90*grad});
return function(t) {
return arc(i(t));
};
}
looking at several examples i managed to get the animation, however, i fail at binding (or converting) the data to the arc. my feeling is that there is only one path drawn and then it stops.
if i change the interpolation to start/end -90/90 and a, i get different colors but not all of them. adding the start/end-angle to the pie-var gives me a transition where a one-colored-arc is shown at the beginning and then the other parts slide in (which would be correct if there was no arc at the beginning - the proportions also seem a bit wrong). setting the initial color to white does not help because then everything stays white.
i'm afraid i'm missing an obvious point, but so far i'm stuck, maybe someone can point me in the right direction.
after quite some variations and tests it somehow started to work, using these to lines of code:
var pie = d3.layout.pie().sort(null).startAngle(-90*grad).endAngle(90*grad);
var i = d3.interpolate({startAngle: -90*grad, endAngle: -90*grad},a);
one final "problem" was that the height of the svg was too small and so some segments got cut off, so changing it to
var height = 800;
ended my search. thanks for any considerations.
A small typo on the
var svg = d3.select("body").append("svg").attr("width", width).attr("height", `height)
should be:
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height)

Resources