D3js 组缩放
D3js group zoom
我希望标记在地图放大 (40px) 时保持相同大小。据我了解,当我缩放组时,标记大小也会缩放,因此我需要在组缩放后更新标记大小。但是当我尝试使用
g.selectAll(".mark")
.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + 1 / scale + ")")
标记飞走了(改变它的xy坐标)
这是我的代码 https://jsfiddle.net/6L4yu1kc/1/
是否可以不缩放标记大小?谢谢
基本上,必须像对待任何其他尺寸由绝对数量指定的 svg 对象一样对待此标记。对于州界,你做
.attr("stroke-width", 1.5 / scale + "px")
并且对于标记,您需要分别缩放根据 image.width
或 image.height
指定的每个属性。您可以使用
定位标记
.attr('width', image.width)
.attr('height', image.height)
.attr("x", d => d[0] - image.width / 2)
.attr("y", d => d[1] - image.height)
因此,在缩放时将这些设置为
.attr('width', image.width / scale)
.attr('height', image.height / scale)
.attr("x", d => d[0] - image.width / scale / 2)
.attr("y", d => d[1] - image.height / scale)
原则上,您也可以使用缩放比例为 1/scale
的变换来执行此操作,但需要考虑翻译,即标记的 x
和 y
坐标同样缩放。这看起来像那样
.attr("transform", d => `translate(
${d[0] - image.width / scale / 2 - (d[0] - image.width / 2) / scale},
${d[1] - image.height / scale - (d[1] - image.height) / scale}
) scale(${1 / scale})`);
我会直接缩放坐标而不是这种晦涩的变换 ;)
注意:由于看起来标记确实在改变其大小,因此我在 group
之外添加了一个额外的 circle
,它不会移动和缩放。尺寸的变化似乎是一种错觉。
const markers = [{
address: 'TX',
lat: '29.613',
lng: '-98.293'
}]
const image = {
width: 40,
height: 40
}
var margin = {
top: 10,
bottom: 10,
left: 10,
right: 10
},
width = parseInt(d3.select('.viz').style('width')),
width = width - margin.left - margin.right,
mapRatio = 0.5,
height = width * mapRatio,
active = d3.select(null);
var svg = d3.select('.viz').append('svg')
.attr('class', 'center-container')
.attr('height', height + margin.top + margin.bottom)
.attr('width', width + margin.left + margin.right);
svg.append('rect')
.attr('class', 'background center-container')
.attr('height', height + margin.top + margin.bottom)
.attr('width', width + margin.left + margin.right)
.on('click', clicked);
d3.json('https://raw.githubusercontent.com/benderlidze/d3-usa-click/master/us.topojson')
.then(ready)
var projection = d3.geoAlbersUsa()
.translate([width / 2, height / 2])
.scale(width);
var path = d3.geoPath()
.projection(projection);
var g = svg.append("g")
.attr('class', 'center-container center-items us-state')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
function ready(us) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr("class", "state")
.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);
const markers_proj = markers.map(d => projection([d.lng, d.lat]));
g.selectAll("circle")
.data(markers_proj)
.enter()
.append("circle")
.attr("cx", d => d[0])
.attr("cy", d => d[1])
.attr("r", 5)
.style("fill", "white");
svg.selectAll("circle.test")
.data(markers_proj)
.enter()
.append("circle")
.attr("class", "test")
.attr("cx", d => d[0] - 10)
.attr("cy", d => d[1] + 10)
.attr("r", 5)
.style("fill", "black");
g.selectAll(".mark")
.data(markers_proj)
.enter()
.append("image")
.attr('class', 'mark')
.attr("xlink:href", 'https://benderlidze.github.io/d3-usa-click/icon.png')
.attr('width', image.width)
.attr('height', image.height)
.attr("x", d => d[0] - image.width / 2)
.attr("y", d => d[1] - image.height);
}
function clicked(d) {
if (d3.select('.background').node() === this) return reset();
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
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 = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
const t = d3.transition().duration(750);
g.transition(t)
.attr("transform", `translate(${translate}) scale(${scale})`);
g.select("#state-borders")
.transition(t)
.style("stroke-width", `${1.5 / scale}px`);
g.selectAll("circle")
.transition(t)
.attr("r", 5 / scale);
g.selectAll(".mark")
.transition(t)
.attr('width', image.width / scale)
.attr('height', image.height / scale)
.attr("x", d => d[0] - image.width / scale / 2)
.attr("y", d => d[1] - image.height / scale);
}
function reset() {
active.classed("active", false);
active = d3.select(null);
const t = d3.transition().duration(750);
g.transition(t)
.attr("transform", `translate(${margin.left},${margin.top}) scale(1)`);
g.select("#state-borders")
.transition(t)
.style("stroke-width", "1px");
g.selectAll("circle")
.transition(t)
.attr("r", 5);
g.selectAll(".mark")
.transition(t)
.attr('width', image.width)
.attr('height', image.height)
.attr("x", d => d[0] - image.width / 2)
.attr("y", d => d[1] - image.height);
}
.background {
fill: none;
pointer-events: all;
}
#states {
fill: #3689ff;
}
#states .active {
fill: #0057ce;
}
#state-borders {
fill: none;
stroke: #fff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
.state:hover {
fill: #0057ce;
}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script type="text/javascript" src="https://d3js.org/topojson.v3.min.js"></script>
<div class="viz"></div>
我希望标记在地图放大 (40px) 时保持相同大小。据我了解,当我缩放组时,标记大小也会缩放,因此我需要在组缩放后更新标记大小。但是当我尝试使用
g.selectAll(".mark")
.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + 1 / scale + ")")
标记飞走了(改变它的xy坐标) 这是我的代码 https://jsfiddle.net/6L4yu1kc/1/
是否可以不缩放标记大小?谢谢
基本上,必须像对待任何其他尺寸由绝对数量指定的 svg 对象一样对待此标记。对于州界,你做
.attr("stroke-width", 1.5 / scale + "px")
并且对于标记,您需要分别缩放根据 image.width
或 image.height
指定的每个属性。您可以使用
.attr('width', image.width)
.attr('height', image.height)
.attr("x", d => d[0] - image.width / 2)
.attr("y", d => d[1] - image.height)
因此,在缩放时将这些设置为
.attr('width', image.width / scale)
.attr('height', image.height / scale)
.attr("x", d => d[0] - image.width / scale / 2)
.attr("y", d => d[1] - image.height / scale)
原则上,您也可以使用缩放比例为 1/scale
的变换来执行此操作,但需要考虑翻译,即标记的 x
和 y
坐标同样缩放。这看起来像那样
.attr("transform", d => `translate(
${d[0] - image.width / scale / 2 - (d[0] - image.width / 2) / scale},
${d[1] - image.height / scale - (d[1] - image.height) / scale}
) scale(${1 / scale})`);
我会直接缩放坐标而不是这种晦涩的变换 ;)
注意:由于看起来标记确实在改变其大小,因此我在 group
之外添加了一个额外的 circle
,它不会移动和缩放。尺寸的变化似乎是一种错觉。
const markers = [{
address: 'TX',
lat: '29.613',
lng: '-98.293'
}]
const image = {
width: 40,
height: 40
}
var margin = {
top: 10,
bottom: 10,
left: 10,
right: 10
},
width = parseInt(d3.select('.viz').style('width')),
width = width - margin.left - margin.right,
mapRatio = 0.5,
height = width * mapRatio,
active = d3.select(null);
var svg = d3.select('.viz').append('svg')
.attr('class', 'center-container')
.attr('height', height + margin.top + margin.bottom)
.attr('width', width + margin.left + margin.right);
svg.append('rect')
.attr('class', 'background center-container')
.attr('height', height + margin.top + margin.bottom)
.attr('width', width + margin.left + margin.right)
.on('click', clicked);
d3.json('https://raw.githubusercontent.com/benderlidze/d3-usa-click/master/us.topojson')
.then(ready)
var projection = d3.geoAlbersUsa()
.translate([width / 2, height / 2])
.scale(width);
var path = d3.geoPath()
.projection(projection);
var g = svg.append("g")
.attr('class', 'center-container center-items us-state')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
function ready(us) {
g.append("g")
.attr("id", "states")
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path)
.attr("class", "state")
.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);
const markers_proj = markers.map(d => projection([d.lng, d.lat]));
g.selectAll("circle")
.data(markers_proj)
.enter()
.append("circle")
.attr("cx", d => d[0])
.attr("cy", d => d[1])
.attr("r", 5)
.style("fill", "white");
svg.selectAll("circle.test")
.data(markers_proj)
.enter()
.append("circle")
.attr("class", "test")
.attr("cx", d => d[0] - 10)
.attr("cy", d => d[1] + 10)
.attr("r", 5)
.style("fill", "black");
g.selectAll(".mark")
.data(markers_proj)
.enter()
.append("image")
.attr('class', 'mark')
.attr("xlink:href", 'https://benderlidze.github.io/d3-usa-click/icon.png')
.attr('width', image.width)
.attr('height', image.height)
.attr("x", d => d[0] - image.width / 2)
.attr("y", d => d[1] - image.height);
}
function clicked(d) {
if (d3.select('.background').node() === this) return reset();
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
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 = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
const t = d3.transition().duration(750);
g.transition(t)
.attr("transform", `translate(${translate}) scale(${scale})`);
g.select("#state-borders")
.transition(t)
.style("stroke-width", `${1.5 / scale}px`);
g.selectAll("circle")
.transition(t)
.attr("r", 5 / scale);
g.selectAll(".mark")
.transition(t)
.attr('width', image.width / scale)
.attr('height', image.height / scale)
.attr("x", d => d[0] - image.width / scale / 2)
.attr("y", d => d[1] - image.height / scale);
}
function reset() {
active.classed("active", false);
active = d3.select(null);
const t = d3.transition().duration(750);
g.transition(t)
.attr("transform", `translate(${margin.left},${margin.top}) scale(1)`);
g.select("#state-borders")
.transition(t)
.style("stroke-width", "1px");
g.selectAll("circle")
.transition(t)
.attr("r", 5);
g.selectAll(".mark")
.transition(t)
.attr('width', image.width)
.attr('height', image.height)
.attr("x", d => d[0] - image.width / 2)
.attr("y", d => d[1] - image.height);
}
.background {
fill: none;
pointer-events: all;
}
#states {
fill: #3689ff;
}
#states .active {
fill: #0057ce;
}
#state-borders {
fill: none;
stroke: #fff;
stroke-width: 1.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
.state:hover {
fill: #0057ce;
}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script type="text/javascript" src="https://d3js.org/topojson.v3.min.js"></script>
<div class="viz"></div>