here is my simple code for d3 scalable map:
winWidth = $(window).width();
winHeight = $(window).height();
projection = d3.geo.mercator()
.translate([0, 0])
.scale(winWidth / 2 / Math.PI);
zoom = d3.behavior.zoom()
.scaleExtent([1, 50])
.on("zoom", manageMap);
path = d3.geo.path()
.projection(projection);
svg = d3.select("#map").append("svg")
.attr("viewBox", "0 0 " + winWidth + " " + winHeight)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + winWidth / 2 + "," + winHeight / 2 + ")")
.call(zoom);
g = svg.append("g");
d3.json("world-50m.json", function(error, world) {
g.append("path")
.datum(topojson.feature(world, world.objects.countries))
.attr("class", "land")
.attr("d", path);
g.append("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
function manageMap() {
var t = d3.event.translate,
s = d3.event.scale;
t[0] = Math.min(winWidth / 2 * (s - 1), Math.max(winWidth / 2 * (1 - s), t[0]));
t[1] = Math.min(winHeight / 2 * (s - 1) + 230 * s, Math.max(winHeight / 2 * (1 - s) - 230 * s, t[1]));
zoom.translate(t);
g.style("stroke-width", 1/s).attr("transform", "translate(" + t + ")scale(" + s + ")");
svg.select("g").selectAll("circle")
.attr("cx", function(d) { return projection([d.lng, d.lat])[0]; })
.attr("cy", function(d) { return projection([d.lng, d.lat])[1]; })
.attr("r", 11/s);
}
Is there any simple way to have current visible area coordinates, when map is scaled and translated? I'm already try to project translation of map, but just got some strange numbers.
This will do it. I've also put it up on http://bl.ocks.org/sfinktah/1d38c8a268d893d769ed
Even if you have found your solution, this may be useful for future googlers.
function getScreenBounds() {
return [getPoint(0,0), getPoint()];
}
function getPoint(x,y) {
if (x == null) x = winWidth;
if (y == null) y = winHeight;
var container = g.node();
var svg = container.ownerSVGElement || container;
var point = svg.createSVGPoint();
point.x = x, point.y = y;
point = point.matrixTransform(container.getScreenCTM().inverse());
return formatLocation(projection.invert([point.x, point.y]), zoom.scale());
}
function formatLocation(p, k) {
var format = d3.format("." + Math.floor(Math.log(k) / 2 - 2) + "f");
return (p[1] < 0 ? format(-p[1]) + "°S" : format(p[1]) + "°N") + " "
+ (p[0] < 0 ? format(-p[0]) + "°W" : format(p[0]) + "°E");
}
Related
My aim is to add a legend to Clustered Bubble Chart based on the color of a cluster. The way that I did has no results.
In my CSV file, I created 5 clustered with different colors. In fact, I want to differentiate each cluster by name and color.
The code does not have any errors but nothing showing up. Can someone take look at it and tell what is wrong with it? Do you have any other suggestions to add a legend to the bubble chart?
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
text {
font: 10px sans-serif;
}
circle {
stroke: #565352;
stroke-width: 1;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var width = 1000,
height = 1000,
padding = 1.5, // separation between same-color nodes
clusterPadding = 6, // separation between different-color nodes
maxRadius = 65;
var color = d3.scale.ordinal()
.range(["#5499C7", "#8E44AD", "#138D75", "#F1C40F", "#D35400"]);
d3.text("word_groups.csv", function(error, text) {
var legendRectSize = 18;
var legendSpacing = 4;
if (error) throw error;
var colNames = "text,size,group\n" + text;
var data = d3.csv.parse(colNames);
data.forEach(function(d) {
d.size = +d.size;
});
//unique cluster/group id's
var cs = [];
data.forEach(function(d){
if(!cs.contains(d.group)) {
cs.push(d.group);
}
});
var n = data.length, // total number of nodes
m = cs.length; // number of distinct clusters
//create clusters and nodes
var clusters = new Array(m);
var nodes = [];
for (var i = 0; i<n; i++){
nodes.push(create_nodes(data,i));
}
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(.02)
.charge(0)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("g").call(force.drag);
var legend = svg.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * color.domain().length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color);
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return "Hans"; });
node.append("circle")
.style("fill", function (d) {
return color(d.cluster);
})
.attr("r", function(d){return d.radius})
node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.text(function(d) { return d.text.substring(0, d.radius / 3); });
function create_nodes(data,node_counter) {
var i = cs.indexOf(data[node_counter].group),
r = Math.sqrt((i + 1) / m * -Math.log(Math.random())) * maxRadius,
d = {
cluster: i,
radius: data[node_counter].size*1.5,
text: data[node_counter].text,
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
};
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
return d;
};
function tick(e) {
node.each(cluster(10 * e.alpha * e.alpha))
.each(collide(.5))
.attr("transform", function (d) {
var k = "translate(" + d.x + "," + d.y + ")";
return k;
})
}
// Move d to be adjacent to the cluster node.
function cluster(alpha) {
return function (d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
// Resolves collisions between d and all other circles.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function (d) {
var r = d.radius + maxRadius + Math.max(padding, clusterPadding),
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 + (d.cluster === quad.point.cluster ? padding : clusterPadding);
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;
});
};
}
});
Array.prototype.contains = function(v) {
for(var i = 0; i < this.length; i++) {
if(this[i] === v) return true;
}
return false;
};
</script>
The color.domain array is empty when you join it with the .legend selection, so no 'g' elements are appended.
The color.domain array is populated later in your code, when you append the circles to your nodes selection.
If you switch the order, then the legend items are created:
var node = svg
.selectAll('circle')
.data(nodes)
.enter()
.append('g')
.call(force.drag)
////MOVED BEFORE THE LEGEND CODE
node
.append('circle')
.style('fill', function (d) {
return color(d.cluster)
})
.attr('r', function (d) {
return d.radius
})
var legend = svg
.selectAll('.legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function (d, i) {
var height = legendRectSize + legendSpacing
var offset = height * color.domain().length / 2
var horz = -2 * legendRectSize
var vert = i * height - offset
return 'translate(' + horz + ',' + vert + ')'
})
legend
.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', color)
.style('stroke', color)
legend
.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function (d) {
return 'Hans'
})
PS: Some of the legend items are currently being translated off the SVG view, so your horz and vert variables need looking at.
I have a d3 radial chart created using some community sample and stack-overflow posts.
Here the two bottom labels and numbers are in mirrored form (A13 and A14). Looking for some snippets to transform only this two in counter-clockwise with numbers top (next to the chart) then label so that it will be in better readable form.
JSFiddle
var data = [
{"name":"A11","value":217,"color":"#fad64b"},
{"name":"A12","value":86,"color":"#f15d5d"},
{"name":"A13","value":79,"color":"#f15d5d"},
{"name":"A14","value":82,"color":"#f15d5d"},
{"name":"A15","value":101,"color":"#fad64b"},
{"name":"A16","value":91,"color":"#fad64b"}
];
var width = 500;
var height = 300;
var barHeight = height / 2 - 15;
var formatNumber = d3.format('s');
var color = d3.scale.ordinal()
.range(['#F15D5D', '#FAD64B']);
var svg = d3.select('#chart').append('svg')
.attr('width', width)
.attr('height', height)
.attr('class', 'radial')
.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
var extent = [0, d3.max(data, function(d) { return d.value; })];
var lastNum = extent[1];
var percentageOne = (lastNum*25)/100;
var percentageTwo = (lastNum*50)/100;
var percentageThree = (lastNum*75)/100;
var tickValues = [percentageOne, percentageTwo, percentageThree, lastNum];
var barScale = d3.scale.linear()
.domain(extent)
.range([0, barHeight]);
var keys = data.map(function(d, i) {
return d.name;
});
var numBars = keys.length;
// X scale
var x = d3.scale.linear()
.domain(extent)
.range([0, -barHeight]);
// X axis
var xAxis = d3.svg.axis()
.scale(x).orient('left')
.tickFormat(formatNumber)
.tickValues(tickValues);
// Inner circles
var circles = svg.selectAll('circle')
.data(tickValues)
.enter().append('circle')
.attr('r', function(d) {
return barScale(d);
})
.style('fill', 'none')
.style('stroke-width', '0.5px');
// Create arcs
var arc = d3.svg.arc()
.startAngle(function(d, i) {
var a = (i * 2 * Math.PI) / numBars;
var b = ((i + 1) * 2 * Math.PI) / numBars;
var d = (b - a) / 4;
var x = a + d;
var y = b - d;
return x; //(i * 2 * Math.PI) / numBars;
})
.endAngle(function(d, i) {
var a = (i * 2 * Math.PI) / numBars;
var b = ((i + 1) * 2 * Math.PI) / numBars;
var d = (b - a) / 4;
var x = a + d;
var y = b - d;
return y; //((i + 1) * 2 * Math.PI) / numBars;
})
.innerRadius(0);
// Render colored arcs
var segments = svg.selectAll('path')
.data(data)
.enter().append('path')
.each(function(d) {
d.outerRadius = 0;
})
.attr('class', 'bar')
.style('fill', function(d) {
return d.color;
})
.attr('d', arc);
// Animate segments
segments.transition().ease('elastic').duration(1000).delay(function(d, i) {
return (25 - i) * 10;
})
.attrTween('d', function(d, index) {
var i = d3.interpolate(d.outerRadius, barScale(+d.value));
return function(t) {
d.outerRadius = i(t);
return arc(d, index);
};
});
// Outer circle
svg.append('circle')
.attr('r', barHeight)
.classed('outer', true)
.style('fill', 'none')
.style('stroke-width', '.5px');
// Apply x axis
svg.append('g')
.attr('class', 'x axis')
.call(xAxis);
// Labels
var labelRadius = barHeight * 1.025;
var labels = svg.selectAll('foo')
.data(data)
.enter()
.append('g')
.classed('labels', true);
labels.append('def')
.append('path')
.attr('id', function(d, i) { return 'label-path' + i; })
.attr('d', function(d) {
return 'm0 ' + -(barScale(d.value) + 4) + ' a' + (barScale(d.value) + 4) + ' ' + (barScale(d.value) + 4) + ' 0 1,1 -0.01 0';
});
labels.append('def')
.append('path')
.attr('id', function(d, i) { return 'label-pathnum' + i; })
.attr('d', function(d){
return 'm0 ' + -(barScale(d.value) + 14) + ' a' + (barScale(d.value) + 14) + ' ' + (barScale(d.value) + 14) + ' 0 1,1 -0.01 0';
});
labels.append('text')
.style('text-anchor', 'middle')
.style('fill', function(d, i) {
return d.color;
})
.append('textPath')
.attr('xlink:href', function(d, i) { return '#label-path' + i; })
.attr('startOffset', function(d, i) {
return i * 100 / numBars + 50 / numBars + '%';
})
.text(function(d) {
return d.name.toUpperCase();
});
labels.append('text')
.style('text-anchor', 'middle')
.style('fill', function(d, i) {
return d.color;
})
.append('textPath')
.attr('xlink:href', function(d, i) { return '#label-pathnum' + i; })
.attr('startOffset', function(d, i) {
return i * 100 / numBars + 50 / numBars + '%';
})
.text(function(d) {
return d.value;
});
You need to modify the path for the specific elements that need to be flipped. To do this, I start by storing the angle in your data object:
.each(function(d,i) {
d.outerRadius = 0;
var angleStart = (i/numBars) * 360;
var angleEnd = ((i+1)/numBars) * 360;
d.angle = (angleStart + angleEnd) / 2;
})
Then I tested the angle when creating the paths for the text and I reverse the path for the flipped text case:
var len = barScale(d.value) + 4;
if(d.angle > 91 && d.angle < 269) {
len += 8; // the flipped text is on the inside of the path so we need to draw it further out
return 'M -0.01 ' + (-len) + ' A ' + len + ' ' + len + ' 0 1,0 0 ' + (-len);
}
else {
return 'M 0 ' + (-len) + ' A' + len + ' ' + len + ' 0 1,1 -0.01 ' + (-len);
}
Then, you need to flip your '% around the path' for the placement of the text along the reversed path:
.attr('startOffset', function(d, i) {
if(d.angle > 91 && d.angle < 269)
return (100 - (i * 100 / numBars + 50 / numBars)) + '%';
else
return i * 100 / numBars + 50 / numBars + '%';
})
Working fiddle can be found here: https://jsfiddle.net/FrancisMacDougall/mnrqokqL/
With this result:
Can someone help me implementing a spiral chart similar to the one below using d3.js?
I've just got the basic spiral plot (a simple one) as of now but not been able to append bars to the plot based on the timeline as shown in the image. I'm trying out a few things (if you see the commented code).
Here's my fiddle, and my code:
var width = 400,
height = 430,
axes = 12,
tick_axis = 9,
start = 0,
end = 2.25;
var theta = function(r) {
return 2 * Math.PI * r;
};
var angle = d3.scale.linear()
.domain([0, axes]).range([0, 360])
var r = d3.min([width, height]) / 2 - 40;
var r2 = r;
var radius = d3.scale.linear()
.domain([start, end])
.range([0, r]);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 8) + ")");
var points = d3.range(start, end + 0.001, (end - start) / 1000);
var spiral = d3.svg.line.radial()
.interpolate("cardinal")
.angle(theta)
.radius(radius);
var path = svg.selectAll(".spiral")
.data([points])
.enter().append("path")
.attr("class", "spiral")
.attr("d", spiral)
var z = d3.scale.category20();
var circles = svg.selectAll('.circle')
.data(points);
/* circles.enter().append('circle')
.attr('r', 5)
.attr('transform', function(d) { return 'translate(' + d + ')'})
.style('fill', function(d) { return z(d); });
*/
var circle = svg.append("circle")
.attr("r", 13)
.attr("transform", "translate(" + points[0] + ")");
var movingCircle = circle.transition().duration(4000)
.attrTween('transform', translateAlongPath(path.node()))
// .attr('cx', function(d) { return radius(d) * Math.cos(theta(d))})
// .attr('cy', function(d) { return radius(d) * Math.sin(theta(d))})
function translateAlongPath(path) {
var l = path.getTotalLength();
return function(d, i, a) {
return function(t) {
var p = path.getPointAtLength(t * l);
//console.log(p)
return "translate(" + p.x + "," + p.y + ")";
};
};
}
function pathXY(path) {
var l = path.getTotalLength();
var start = 0;
/* for(i=start; i<l; i++) {
var point = path.getPointAtLength(i);
svg.append('rect').transition().duration(400).attr('transform', 'translate(' + point.x +','+point.y+')')
.attr('width', 10).attr('height', 30).style('fill', z);
}*/
}
pathXY(path.node());
/*var test = translateAlongPath(path.node())()();
//console.log(test)
var bars = svg.selectAll('.bar')
.data(points).enter().append('rect').transition().duration(2000)
// .attrTween('transform', translateAlongPath(path.node()))
.attr('class', 'bar')
.attr('width', 10)
.attr('height', 20)
.style('fill', function(d) { return z(d)});
*/
var rect = svg.append('rect').attr('width', 10).attr('height', 10);
rect.transition().duration(3400)
.attrTween('transform', translateAlongPath(path.node()));
It'd be great to have a few similar examples (i.e. spiral timeline plot).
Thanks.
Glad you came back and updated your question, because this is an interesting one. Here's a running minimal implementation. I've commented it ok, so let me know if you have any questions...
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
var width = 500,
height = 500,
start = 0,
end = 2.25,
numSpirals = 4;
var theta = function(r) {
return numSpirals * Math.PI * r;
};
var r = d3.min([width, height]) / 2 - 40;
var radius = d3.scaleLinear()
.domain([start, end])
.range([40, r]);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// create the spiral, borrowed from http://bl.ocks.org/syntagmatic/3543186
var points = d3.range(start, end + 0.001, (end - start) / 1000);
var spiral = d3.radialLine()
.curve(d3.curveCardinal)
.angle(theta)
.radius(radius);
var path = svg.append("path")
.datum(points)
.attr("id", "spiral")
.attr("d", spiral)
.style("fill", "none")
.style("stroke", "steelblue");
// fudge some data, 2 years of data starting today
var spiralLength = path.node().getTotalLength(),
N = 730,
barWidth = (spiralLength / N) - 1;
var someData = [];
for (var i = 0; i < N; i++) {
var currentDate = new Date();
currentDate.setDate(currentDate.getDate() + i);
someData.push({
date: currentDate,
value: Math.random()
});
}
// here's our time scale that'll run along the spiral
var timeScale = d3.scaleTime()
.domain(d3.extent(someData, function(d){
return d.date;
}))
.range([0, spiralLength]);
// yScale for the bar height
var yScale = d3.scaleLinear()
.domain([0, d3.max(someData, function(d){
return d.value;
})])
.range([0, (r / numSpirals) - 30]);
// append our rects
svg.selectAll("rect")
.data(someData)
.enter()
.append("rect")
.attr("x", function(d,i){
// placement calculations
var linePer = timeScale(d.date),
posOnLine = path.node().getPointAtLength(linePer),
angleOnLine = path.node().getPointAtLength(linePer - barWidth);
d.linePer = linePer; // % distance are on the spiral
d.x = posOnLine.x; // x postion on the spiral
d.y = posOnLine.y; // y position on the spiral
d.a = (Math.atan2(angleOnLine.y, angleOnLine.x) * 180 / Math.PI) - 90; //angle at the spiral position
return d.x;
})
.attr("y", function(d){
return d.y;
})
.attr("width", function(d){
return barWidth;
})
.attr("height", function(d){
return yScale(d.value);
})
.style("fill", "steelblue")
.style("stroke", "none")
.attr("transform", function(d){
return "rotate(" + d.a + "," + d.x + "," + d.y + ")"; // rotate the bar
});
// add date labels
var tF = d3.timeFormat("%b %Y"),
firstInMonth = {};
svg.selectAll("text")
.data(someData)
.enter()
.append("text")
.attr("dy", 10)
.style("text-anchor", "start")
.style("font", "10px arial")
.append("textPath")
// only add for the first of each month
.filter(function(d){
var sd = tF(d.date);
if (!firstInMonth[sd]){
firstInMonth[sd] = 1;
return true;
}
return false;
})
.text(function(d){
return tF(d.date);
})
// place text along spiral
.attr("xlink:href", "#spiral")
.style("fill", "grey")
.attr("startOffset", function(d){
return ((d.linePer / spiralLength) * 100) + "%";
})
</script>
</body>
</html>
I have a rectangle which I am rotating it to -45 but on drag the rotation is not applied.
Code:
function createPoly(x, y)
{
var width = 41;
var height = 41;
var centreX = width / 2;
var centreY = height / 2;
var shape = d3.select(this);
var poly = svg.append("g")
.classed("bpmnGateway", true)
.append('svg:rect')
.attr("type", "poly")
.attr("x", (x - centreX))
.attr("y", (y - centreY))
.attr("width", 41)
.attr("height", 41)
.attr("transform", "rotate(-45)")
.attr("stroke-width", "1.5px")
.attr("stroke", "#7E7E7E")
.style('cursor', 'move')
.style("fill", "#FFED6B")
.call(drag);
}
function drag()
{
if (shape.attr('type') === 'poly')
{
translate = d3.transform(shape.attr("transform")).translate;
var x = d3.event.dx + translate[0];
var y = d3.event.dy + translate[1];
shape.attr("transform", "rotate(-45)")
shape.attr("transform", "translate(" + x + "," + y + ")");
}
}
I tried with rotate option inside drag function which didn't work. How do I get it to work?
The second attr function overwrites the first. You could just do both in one e.g.
shape.attr("transform", "rotate(-45) translate(" + x + "," + y + ")");
or
shape.attr("transform", "translate(" + x + "," + y + ") rotate(-45)");
Depending on the order you want the transforms to be applied.
I am zooming in on a map upon click but the latitude longitude points do not scale. They are rendered as circles and I would like them to move with the map. I am following the D3 template here: http://bl.ocks.org/mbostock/2206590
var map_width = 960,
map_height = 500,
jsonRoot = '/static/d3/json/',
centered;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([map_width / 2, map_height / 2]); // default projection type for d3.geo.path
var urls = {
counties: jsonRoot + "us-counties.json",
states: jsonRoot + "us-states.json"
}
, margin = { top: 0, right: 0, bottom: 0, left: 0 }
, width = 960 - margin.right - margin.left
, height = 500
, path = d3.geo.path().projection(projection)
, map;
var q = queue()
.defer(d3.json, jsonRoot + "us-counties.json")
.defer(d3.json, jsonRoot + "us-states.json")
.await(ready);
function ready(error, countylines, statelines) {
window.error = error;
window.countylines = countylines;
window.statelines = statelines;
if (error){
throw error;
}
var stateIds = {};
statelines.features.forEach(function(d) {
stateIds[d.id] = d.properties.name;
});
countylines.features.forEach(function(d) {
d.properties.state = stateIds[d.id.slice(0,2)];
})
// remove the loading text
d3.select('.loading').remove();
map = d3.select('#map').append('svg')
.style('width', width)
.style('height', height);
counties = map.append('g')
.attr('class', 'counties')
.selectAll('path')
.data(countylines.features)
.enter().append('path')
.attr('d', path);
counties.on('mouseover', showCaption)
.on('mousemove', showCaption)
.on('mouseout', function() {
caption.html(starter);
})
.on('click', clicked);
states = map.append('g')
.attr('class', 'states')
.selectAll('path')
.data(statelines.features)
.enter().append('path')
.attr('d', path);
// Captions
var caption = d3.select('#caption')
, starter = caption.html();
function showCaption(d, i) {
var name = [d.properties.name, d.properties.state].join(', ');
caption.html(name);
}
var systemSuccess = function(result){
console.log(result);
}
var site = map.append("circle")
.attr("r",5)
.classed("system", true)
.attr("latitude",37.77521)
.attr("longitude",-122.42854)
.attr("transform", function() {
return "translate(" + projection([-122.42854,37.77521]) + ")";
});
});
})
};
function clicked(d) {
var x, y, k;
if (d && centered !== d) {
var centroid = path.centroid(d);
x = centroid[0];
y = centroid[1];
k = 4;
centered = d;
} else {
x = width / 2;
y = height / 2;
k = 1;
centered = null;
}
counties.selectAll("path")
.classed("active", centered && function(d) { return d === centered; });
counties.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
states.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
.style("stroke-width", 1.5 / k + "px");
map.selectAll(".system")
.attr("transform", function(d) { return "translate(" + projection([-122.42854, 37.77521 ]) + ")" });
}
});
The map scales appropriately. But not the points.
All help is appreciated!
As Lars suggested, you could do the following.
//Same projection and transformation as applicable to the path elements.
d3.selectAll("circle")
.transition()
.duration(750)
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
I am not sure if the above code would work correctly...although I have used a similar premise through the "zoom" d3 behavior.
If you want your points to retain their size, but be at the right position; you could try semantic zooming
OR
you could keep the resize the circle's radius based on the scale like this:
d3.selectAll("circle")
.attr("r", 5/k);