Home > Software engineering >  D3js group zoom
D3js group zoom

Time:03-15

I want the marker to stay the same size when the map zooms in (40px). As I understand when I scale a group the marker size scales too, so I need to update the marker size after group zoom. But when I try to use

 g.selectAll(".mark")
                .transition()
                .duration(750)
                .style("stroke-width", 1.5 / scale   "px")
                .attr("transform", "translate("   translate   ")scale("   1 / scale   ")")

the marker flies away (changes it xy coordinates) Here is my code https://jsfiddle.net/6L4yu1kc/1/

Is it possible to not scale the marker size? Thanks

CodePudding user response:

Basically, this marker has to be treated exactly like any other svg object with dimensions specified by an absolute quantity. For the state borders you do

.attr("stroke-width", 1.5 / scale   "px")

and for the marker you need to scale every attribute that is specified in terms of image.width or image.height, respectively. You can position the marker using

.attr('width', image.width)
.attr('height', image.height)
.attr("x", d => d[0] - image.width / 2)
.attr("y", d => d[1] - image.height)

Therefore, on zoom set these to

.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)

In principle, you could also do this using a transform with scale 1/scale but the translate then needs to take into account, that the x and y coordinates of the marker are scaled likewise. This would look something like that

.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})`);

I would scale the coordinates directly instead of this obscure transform ;)

Note: Since it really looks like the marker is changing its size, I added an additional circle outside the group that does not move and scale. The change of size seems to be an optical illusion.

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 ></div>

  • Related