d3 zoom: fix jagged edges and rotate axis labels?

Viewed 16

Consider the following example, test.html, which is my simplified modification of https://observablehq.com/@d3/pan-zoom-axes?collection=@d3/d3-zoom tested on latest Firefox 104 on both Windows and Linux:

<!doctype html>
<html>
<head>
  <script src="https://d3js.org/d3.v7.min.js" charset="utf-8"></script>
  <style type="text/css">
  </style>
  <!-- script type module raises CORS Cross-Origin Request Blocked,
      when used locally (implied file://..js)
    <script type="module">
    import {zoom} from "https://cdn.skypack.dev/d3-zoom@3";
    console.log(zoom);
    //const handler = zoom();
    import {svg} from "https://cdn.skypack.dev/htl@0.3.1";
    console.log(svg);
  </script> -->
  <script src="https://cdn.jsdelivr.net/npm/d3-zoom@3"></script>
  <script src="https://cdn.jsdelivr.net/npm/htl@0.3.1"></script>
</head>
<body>
  <div>
    <h3>Test</h3>
    <div id="viewer"></div>
  </div>
  <script type="text/javascript" charset="utf-8">
var vbWidth = 400;
var vbHeight = 100;

const svg = d3.select("#viewer")
    .style("background-color", "green")
    .append("svg")
    .attr("viewBox", [0, 0, vbWidth, vbHeight]);
defs = () => htl.svg`<defs>
  <style>
    /*.axis .domain { display: none; }*/
    .axis line { stroke-opacity: 0.3; shape-rendering: crispEdges; stroke-width: 0.4px; }
    /*.view { fill: url(#gradient); stroke: #000; }*/
    .view { fill: white; stroke: #000; shape-rendering: crispEdges; }
    .axis text { font-size: 4px; }
  </style>
   `
svg.append(defs);

const view = svg.append("rect")
  .attr("class", "view")
  .attr("x", 0) // 0.5)
  .attr("y", 0) // 0.5)
  .attr("width", vbWidth)
  .attr("height", vbHeight-30);

x = d3.scaleLinear()
    .domain([0, 100])      // "[0, 100]   This is what is written on the Axis: from 0 to 100"
    .range([0, vbWidth]);  // "[100, 800] This is where the axis is placed: from 100px to 800px"
xAxis = d3.axisBottom(x)
    .ticks(((vbWidth + 2) / (vbHeight + 2)) * 10)
    .tickSize(vbHeight)
    .tickPadding(-28)
    ;
y = d3.scaleLinear()
    .domain([-1, vbHeight + 1])
    .range([-1, vbHeight + 1]);
yAxis = d3.axisRight(y)
    .ticks(10)
    .tickSize(vbWidth)
    .tickPadding(8 - vbWidth)
    ;
const gX = svg.append("g").attr("class", "axis axis--x").call(xAxis);
const gY = svg.append("g").attr("class", "axis axis--y").call(yAxis);

function zoomed({ transform }) {
  view.attr("transform", transform);
  const newsw = ((1.0/transform.k));
  view.attr("stroke-width", newsw+"px") // https://bl.ocks.org/rgdonohue/51c43bb749689e696b8a
  console.log("k", transform.k, "n", newsw);
  gX.call(xAxis.scale(transform.rescaleX(x)));
  gY.call(yAxis.scale(transform.rescaleY(y)));

  //svg.selectAll(".tick text")
  gX.selectAll("text")
    .attr("transform-origin", "top left")
    .style("text-anchor", "end")
    //.attr("dx", "-1.8em")
    //.attr("dy", "1.15em")
    .attr("transform", function(d) {
      //console.log(d); // numbers - coords
      //return d3.select(this).attr("transform") + " rotate(10)"; // SO:17791926
      //console.log(d3.select(this).attr("transform")); // null! or "rotate(45)" if already set once
      return "rotate(45)";
    })
  ;
} // end function zoomed

const zoom = d3.zoom()
  .scaleExtent([1, 40]) // min and max zoom level
  .translateExtent([[-100, -100], [vbWidth + 90, vbHeight + 100]])
  .filter(filter)
  .on("zoom", zoomed);

Object.assign(svg.call(zoom).node(), {reset});

function reset() {
  svg.transition()
    .duration(750)
    .call(zoom.transform, d3.zoomIdentity);
}

// prevent scrolling then apply the default filter
function filter(event) {
  console.log("filter", event);
  event.preventDefault();
  return (!event.ctrlKey || event.type === 'wheel') && !event.button;
}
  </script>
</body>
</html>

I have two problems here:

  1. Note that I have attempted to keep the stroke width the same across zoom levels with the const newsw = ((1.0/transform.k)); however, if I zoom in with the mousewheel to a larger zoom level, I randomly get jagged edges (or edges disappear) of the white rectangle, upon repeated zoom-in/zoom-out:
    rect-jagged-edges-zoom-in-out

  2. If I do not rotate the tick labels (the line return "rotate(45)"; is commented), then labels keep their position fine upon dragging (as the above animated image shows). however, if I enable it - the labels are off their intended location:
    rotated-tick-labels-off
    So, with this problem, the origin of the white rectangle can be read to be (14,0) from the tick labels - instead of the correct (0,0).

So, what needs to be changed in the code, so I both have correctly rendered constant stroke width (here, of the white rectangle), regardless of zoom level and drag position - and I have rotated tick labels, which are correctly positioned, even when dragging and zooming?

0 Answers
Related