我一直在看简单的地图演示。
http://bl.ocks.org/mbostock/2206340
热衷于使用迷你地图或画笔创建更复杂的缩放/画笔方法,而不是依赖缩放事件。
http://bl.ocks.org/mbostock/4343214
如何修改示例代码 - 创建一个迷你地图,其中包含一个用作缩放/平移控制的小画笔区域?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.background {
fill: none;
pointer-events: all;
}
#states {
fill: #aaa;
}
#state-borders {
fill: none;
stroke: #fff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 500;
var projection = d3.geo.albersUsa()
.scale(1070)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.scaleExtent([height, 8 * height])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.call(zoom);
g.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
d3.json("/mbostock/raw/4090846/us.json", function(error, us) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.on("click", clicked);
g.append("path")
.datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
.attr("id", "state-borders")
.attr("d", path);
});
function clicked(d) {
var centroid = path.centroid(d),
translate = projection.translate();
projection.translate([
translate[0] - centroid[0] + width / 2,
translate[1] - centroid[1] + height / 2
]);
zoom.translate(projection.translate());
g.selectAll("path").transition()
.duration(700)
.attr("d", path);
}
function zoomed() {
projection.translate(d3.event.translate).scale(d3.event.scale);
g.selectAll("path").attr("d", path);
}
</script>
我当前的代码看起来像这样,但我不确定如何开始创建一个控制较大地图的小地图 - 从哪里开始?
var worldMapper = {
initPie: function(id, color, w, h, r, irw, ir){
var specs = {
color: color,
w: w,
h: h,
r: r,
irw: irw,
ir: ir
}
goPie.initChart(id, specs);
},
updatePie: function(id, localPieData){
var newJson = new Array();
$.each(localPieData, function(index, value) {
var localData = {
title: value.label,
octetTotalCount: value.value
}
newJson.push(localData);
});
goPie.updateCharts(id, newJson);
},
init: function(){
var that = this;
var width = 600,
height = 400,
rotate = 60, // so that [-60, 0] becomes initial center of projection
maxlat = 83; // clip northern and southern poles (infinite in mercator)
var projection = d3.geo.mercator()
.rotate([rotate,0])
.scale(1) // we'll scale up to match viewport shortly.
.translate([width/2, height/2]);
// find the top left and bottom right of current projection
function mercatorBounds(projection, maxlat) {
var yaw = projection.rotate()[0],
xymax = projection([-yaw+180-1e-6,-maxlat]),
xymin = projection([-yaw-180+1e-6, maxlat]);
return [xymin,xymax];
}
// set up the scale extent and initial scale for the projection
var b = mercatorBounds(projection, maxlat),
s = width/(b[1][0]-b[0][0]),
scaleExtent = [s, 10*s];
projection
.scale(scaleExtent[0]);
var zoom = d3.behavior.zoom()
.scaleExtent(scaleExtent)
.scale(projection.scale())
.translate([0,0]) // not linked directly to projection
.on("zoom", redraw);
var path = d3.geo.path()
.projection(projection);
var svg = d3.selectAll('#map')
.append('svg')
.attr('width',width)
.attr('height',height)
.call(zoom);
var worldMap = svg.append('g')
.attr('class', 'worldmap');
var coordinateItems = svg.append('g')
.attr('class', 'coordinateItems');
var dataCharts = [
{
"coordinates": [
10,
20
],
"pieData": [
{"label":"Blade Workstation", "value":0.0001},
{"label":"GenX", "value":4},
{"label":"GenY", "value":0.0001},
{"label":"Laptop", "value":34},
{"label":"Physical Desktop", "value":13}
]
},
{
"coordinates": [
-0.134153,
51.509757
],
"pieData": [
{"label":"Blade Workstation", "value":33},
{"label":"GenX", "value":12},
{"label":"GenY", "value":23},
{"label":"Laptop", "value":23},
{"label":"Physical Desktop", "value":322}
]
},
{
"coordinates": [
-0.134153,
21.509757
],
"pieData": [
{"label":"Blade Workstation", "value":0.0001},
{"label":"GenX", "value":0.0001},
{"label":"GenY", "value":0.0001},
{"label":"Laptop", "value":54},
{"label":"Physical Desktop", "value":45}
]
},
{
"coordinates": [
-82.134153,
29.509757
],
"pieData": [
{"label":"Blade Workstation", "value":34},
{"label":"GenX", "value":34},
{"label":"GenY", "value":0.0001},
{"label":"Laptop", "value":0.0001},
{"label":"Physical Desktop", "value":57}
]
},
{
"coordinates": [
-82.134153,
49.509757
],
"pieData": [
{"label":"Blade Workstation", "value":0.0001},
{"label":"GenX", "value":0.0001},
{"label":"GenY", "value":789},
{"label":"Laptop", "value":34},
{"label":"Physical Desktop", "value":0.0001}
]
},
{
"coordinates": [
-82.134153,
49.509757
],
"pieData": [
{"label":"Blade Workstation", "value":23},
{"label":"GenX", "value":23},
{"label":"GenY", "value":43},
{"label":"Laptop", "value":0.0001},
{"label":"Physical Desktop", "value":0.0001}
]
}
];
d3.json("readme.json", function(collection) {
that.initPie("#easy-as-pie-chart", "Spectrum", 400, 400, 90, 70, 70);
worldMap
.selectAll("path")
.data(collection.features)
.enter().append("path")
.attr("d", d3.geo.path().projection(d3.geo.mercator()));
redraw(); // update path data
})
/*
d3.json("world-50m.json", function ready(error, world) {
console.log("world", world);
svg.selectAll('path')
.data(topojson.feature(world, world.objects.countries).features)
.enter().append('path')
redraw(); // update path data
});
*/
// track last translation and scale event we processed
var tlast = [0,0],
slast = null;
function plotPoints(){
var that = this;
var points = coordinateItems.selectAll("g")
var projectedCoordinates = [];
points.remove();
for (var i=0; i<dataCharts.length; i++) {
projectedCoordinates[i] = projection(dataCharts[i]["coordinates"]);
coordinateItems.append("g")
.attr("transform", "translate("+projectedCoordinates[i][0]+", "+projectedCoordinates[i][1]+")")
.attr("id", "minipie"+i)
.on("click", function() {
var id = $(this).attr("id");
idNumber = parseInt(id.replace(/[^\d.]/g, ''));
var localPieData = dataCharts[idNumber]["pieData"];
$("#easy-as-pie-chart").empty();
worldMapper.initPie("#easy-as-pie-chart", "Spectrum", 400, 400, 90, 70, 70);
worldMapper.updatePie("#easy-as-pie-chart", localPieData);
});
worldMapper.initPie("#minipie"+i, "Spectrum", 25, 25, 10, 5, 0);
worldMapper.updatePie("#minipie"+i, dataCharts[i]["pieData"]);
}
}
function redraw() {
if (d3.event) {
var scale = d3.event.scale,
t = d3.event.translate;
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != slast) {
projection.scale(scale);
} else {
var dx = t[0]-tlast[0],
dy = t[1]-tlast[1],
yaw = projection.rotate()[0],
tp = projection.translate();
// use x translation to rotate based on current scale
projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = mercatorBounds(projection, maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < height) dy = height-b[1][1];
projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
slast = scale;
tlast = t;
}
worldMap.selectAll('path') // re-project path data
.attr('d', path);
plotPoints();
}
}
}
$( document ).ready(function() {
worldMapper.init();
});
我设法在迷你地图上创建了一个画笔。但是将它连接起来控制较大地图的缩放/平移是很棘手的。
我试图修复画笔的高度/宽度以确保音阶保持一致......
这是我当前的代码
var worldMapper = {
initPie: function(id, color, w, h, r, irw, ir){
var specs = {
color: color,
w: w,
h: h,
r: r,
irw: irw,
ir: ir
}
goPie.initChart(id, specs);
},
updatePie: function(id, localPieData){
var newJson = new Array();
$.each(localPieData, function(index, value) {
var localData = {
title: value.label,
octetTotalCount: value.value
}
newJson.push(localData);
});
goPie.updateCharts(id, newJson);
},
createMiniWorldMap: function(collection){
var that = this;
var width = 120,
height = 80,
rotate = 60, // so that [-60, 0] becomes initial center of projection
maxlat = 83; // clip northern and southern poles (infinite in mercator)
var projection = d3.geo.mercator()
.rotate([rotate,0])
.scale(1) // we'll scale up to match viewport shortly.
.translate([width/2, height/2]);
// find the top left and bottom right of current projection
function mercatorBounds(projection, maxlat) {
var yaw = projection.rotate()[0],
xymax = projection([-yaw+180-1e-6,-maxlat]),
xymin = projection([-yaw-180+1e-6, maxlat]);
return [xymin,xymax];
}
// set up the scale extent and initial scale for the projection
var b = mercatorBounds(projection, maxlat),
s = width/(b[1][0]-b[0][0]),
scaleExtent = [s, 10*s];
projection
.scale(scaleExtent[0]);
var path = d3.geo.path()
.projection(projection);
var svg = d3.selectAll('#minimap')
.append('svg')
.attr('width',width)
.attr('height',height)
.attr('class', 'miniworldmap');
var worldMap = svg.append('g')
.attr('class', 'worldmap');
worldMap
.selectAll("path")
.data(collection.features)
.enter().append("path")
.attr("d", d3.geo.path().projection(d3.geo.mercator()));
worldMap.selectAll('path') // re-project path data
.attr('d', path);
this.brush = d3.svg.brush()
.extent([0, 30])
.x(d3.scale.identity().domain([0, width]))
.y(d3.scale.identity().domain([0, height]))
.on("brush", brushed);
this.gBrush = svg.append("g")
.attr("class", "brush")
.call(this.brush);
/*
this.gBrush.selectAll("rect")
.attr("height", 30)
.attr("width", 30);
*/
function brushed() {
var extent = d3.event.target.extent();
var extent0 = extent;
var extent1;
var smallMapWidth = 120;
var smallMapHeight = 80;
var gWidth = 30;
var gHeight = 30;
// if dragging, preserve the width of the extent
if (d3.event.mode === "move") {
var xLimit = extent0[0][0];
var YLimit = extent0[0][1];
if(xLimit+gWidth+1 > smallMapWidth){
xLimit = 91;
}
if(YLimit+gHeight+1 > smallMapHeight){
YLimit = 51;
}
extent1 = [xLimit, YLimit];
}
// otherwise, if resizing, round both dates
else {
extent1 = extent0;
}
worldMap.classed("selected", function(d) {
console.log("extent[0]", extent[0]);
console.log("extent[1]", extent[1]);
var boxWidth = extent[1][0] - extent[0][0];
var boxHeight = extent[1][1] - extent[0][1];
console.log("boxWidth", boxWidth);
console.log("boxHeight", boxHeight);
var smallMapWidth = 120;
var smallMapHeight = 80;
var bigMapWidth = 600;
var bigMapHeight = 400;
var xRatio = extent[0][0]/smallMapWidth;
var yRatio = extent[1][1]/smallMapHeight;
var newX = bigMapWidth*xRatio;
var newY = bigMapHeight*yRatio;
worldMapper.newT = new Array(newX, newY);
worldMapper.newScale = 100;
worldMapper.redraw()
})
/*
worldMapper.gBrush.selectAll("rect")
.attr("height", 30)
.attr("width", 30);*/
//console.log(worldMapper.brush.extent(extent1));
//d3.select(this).call(worldMapper.brush.extent(extent1));
/*
var extent = d3.event.target.extent();
worldMap.classed("selected", function(d) {
worldMapper.redraw()
console.log("extent[0]", extent[0]);
console.log("extent[1]", extent[1]);
var boxWidth = extent[1][0] - extent[0][0];
var boxHeight = extent[1][1] - extent[0][1];
console.log("boxWidth", boxWidth);
console.log("boxHeight", boxHeight);
var smallMapWidth = 120;
var smallMapHeight = 80;
var bigMapWidth = 600;
var bigMapHeight = 400;
var xRatio = extent[0][0]/smallMapWidth;
var yRatio = extent[1][1]/smallMapHeight;
var newX = bigMapWidth*xRatio;
var newY = bigMapHeight*yRatio;
worldMapper.newT = new Array(newX, newY);
console.log("worldMapper.newT", worldMapper.newT);
})
*/
}
},
majorWorld: {},
tlast: [0,0],
newScale: 200,
redraw: function(){
var that = this;
if (d3.event) {
var scale = d3.event.scale;
if(!scale){
scale = worldMapper.newScale;
}
var t = d3.event.translate;
if(!t){
t = worldMapper.newT;
}
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != worldMapper.slast) {
worldMapper.majorWorld.projection.scale(scale);
} else {
var dx = t[0]-worldMapper.tlast[0],
dy = t[1]-worldMapper.tlast[1],
yaw = worldMapper.majorWorld.projection.rotate()[0],
tp = worldMapper.majorWorld.projection.translate();
// use x translation to rotate based on current scale
worldMapper.majorWorld.projection.rotate([yaw+360.*dx/worldMapper.majorWorld.width*worldMapper.majorWorld.scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = worldMapper.mercatorBounds(worldMapper.majorWorld.projection, worldMapper.majorWorld.maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < worldMapper.majorWorld.height) dy = worldMapper.majorWorld.height-b[1][1];
worldMapper.majorWorld.projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
worldMapper.slast = scale;
if(t!=undefined){
worldMapper.tlast = t;
}
}
worldMapper.MajorMap.selectAll('path') // re-project path data
.attr('d', worldMapper.path);
worldMapper.plotPoints();
},
plotPoints: function(){
var that = this;
var points = that.coordinateItems.selectAll("g")
var projectedCoordinates = [];
points.remove();
for (var i=0; i < that.dataCharts.length; i++) {
projectedCoordinates[i] = that.majorWorld.projection(that.dataCharts[i]["coordinates"]);
that.coordinateItems.append("g")
.attr("transform", "translate("+projectedCoordinates[i][0]+", "+projectedCoordinates[i][1]+")")
.attr("id", "minipie"+i)
.on("click", function() {
var id = $(this).attr("id");
idNumber = parseInt(id.replace(/[^\d.]/g, ''));
var localPieData = that.dataCharts[idNumber]["pieData"];
$("#detailPie").empty();
worldMapper.initPie("#detailPie", "Spectrum", 400, 400, 90, 70, 70);
worldMapper.updatePie("#detailPie", localPieData);
});
worldMapper.initPie("#minipie"+i, "Spectrum", 25, 25, 10, 5, 0);
worldMapper.updatePie("#minipie"+i, that.dataCharts[i]["pieData"]);
}
},
mercatorBounds: function(projection, maxlat) {
// find the top left and bottom right of current projection
var yaw = projection.rotate()[0],
xymax = projection([-yaw+180-1e-6,-maxlat]),
xymin = projection([-yaw-180+1e-6, maxlat]);
return [xymin,xymax];
},
createWorldMap: function(collection){
var that = this;
var width = 600,
height = 400,
rotate = 20, // so that [-60, 0] becomes initial center of projection
maxlat = 80; // clip northern and southern poles (infinite in mercator)
var projection = d3.geo.mercator()
.rotate([rotate,0])
.scale(1) // we'll scale up to match viewport shortly.
.translate([width/2, height/2]);
// set up the scale extent and initial scale for the projection
var b = this.mercatorBounds(projection, maxlat),
s = width/(b[1][0]-b[0][0]),
scaleExtent = [s, 10*s];
projection
.scale(scaleExtent[0]);
/*
var zoom = d3.behavior.zoom()
.scaleExtent(scaleExtent)
.scale(projection.scale())
.translate([0,0]) // not linked directly to projection
.on("zoom", that.redraw);
*/
that.path = d3.geo.path()
.projection(projection);
var svg = d3.selectAll('#majormap')
.append('svg')
.attr('width',width)
.attr('height',height)
.attr('class', 'majorworldmap')
//.call(zoom);
this.MajorMap = svg.append('g')
.attr('class', 'worldmap');
that.coordinateItems = svg.append('g')
.attr('class', 'coordinateItems');
this.majorWorld["projection"] = projection;
this.majorWorld["width"] = width;
this.majorWorld["height"] = height;
this.majorWorld["rotate"] = rotate;
this.majorWorld["scaleExtent"] = scaleExtent;
this.majorWorld["maxlat"] = maxlat;
//d3.json("readme.json", function(collection) {
that.initPie("#detailPie", "Spectrum", 400, 400, 90, 70, 70);
that.MajorMap
.selectAll("path")
.data(collection.features)
.enter().append("path")
.attr("d", d3.geo.path().projection(d3.geo.mercator()));
that.redraw(); // update path data
//})
/*
d3.json("world-50m.json", function ready(error, world) {
console.log("world", world);
svg.selectAll('path')
.data(topojson.feature(world, world.objects.countries).features)
.enter().append('path')
redraw(); // update path data
});
*/
// track last translation and scale event we processed
this.tlast = [0,0];
this.slast = null;
that.plotPoints();
/*
function redraw() {
if (d3.event) {
var scale = d3.event.scale,
t = d3.event.translate;
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != slast) {
projection.scale(scale);
} else {
var dx = t[0]-tlast[0],
dy = t[1]-tlast[1],
yaw = projection.rotate()[0],
tp = projection.translate();
// use x translation to rotate based on current scale
projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = mercatorBounds(projection, maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < height) dy = height-b[1][1];
projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
slast = scale;
tlast = t;
}
worldMap.selectAll('path') // re-project path data
.attr('d', path);
plotPoints();
}
*/
},
init: function(){
this.dataCharts = [
{
"coordinates": [
10,
20
],
"pieData": [
{"label":"Blade Workstation", "value":0.0001},
{"label":"GenX", "value":4},
{"label":"GenY", "value":0.0001},
{"label":"Laptop", "value":34},
{"label":"Physical Desktop", "value":13}
]
},
{
"coordinates": [
-0.134153,
51.509757
],
"pieData": [
{"label":"Blade Workstation", "value":33},
{"label":"GenX", "value":12},
{"label":"GenY", "value":23},
{"label":"Laptop", "value":23},
{"label":"Physical Desktop", "value":322}
]
},
{
"coordinates": [
-0.134153,
21.509757
],
"pieData": [
{"label":"Blade Workstation", "value":0.0001},
{"label":"GenX", "value":0.0001},
{"label":"GenY", "value":0.0001},
{"label":"Laptop", "value":54},
{"label":"Physical Desktop", "value":45}
]
},
{
"coordinates": [
-82.134153,
29.509757
],
"pieData": [
{"label":"Blade Workstation", "value":34},
{"label":"GenX", "value":34},
{"label":"GenY", "value":0.0001},
{"label":"Laptop", "value":0.0001},
{"label":"Physical Desktop", "value":57}
]
},
{
"coordinates": [
-82.134153,
49.509757
],
"pieData": [
{"label":"Blade Workstation", "value":0.0001},
{"label":"GenX", "value":0.0001},
{"label":"GenY", "value":789},
{"label":"Laptop", "value":34},
{"label":"Physical Desktop", "value":0.0001}
]
},
{
"coordinates": [
-82.134153,
49.509757
],
"pieData": [
{"label":"Blade Workstation", "value":23},
{"label":"GenX", "value":23},
{"label":"GenY", "value":43},
{"label":"Laptop", "value":0.0001},
{"label":"Physical Desktop", "value":0.0001}
]
}
];
var that = this;
d3.json("readme.json", function(collection) {
that.createWorldMap(collection);
that.createMiniWorldMap(collection);
})
}
}
$( document ).ready(function() {
worldMapper.init();
});