Ad

Issues With PreserveAspectRatio, ViewBox And Layout.size()

- 1 answer

I have written the following code designed to display a d3js tree layout but encountered some difficulty when trying to get the generated svg to resize according to its aspect ratio. I was able (in the attached demo) to get the svg to scale the way I had desired but the code I have written is constrained by const ASPECT_RATIO, as seen here:

canvas.attr("viewBox", " 0 0 " + window.innerWidth + " " + (window.innerWidth * ASPECT_RATIO));

and again, further down, here:

layout.size([(window.innerWidth * ASPECT_RATIO), window.innerWidth - 128]);

Is there a way to circumvent this? I would prefer not having to change this value by hand every time the aspect ratio of the svg changes (that is, any time new content is added).

Regards,

Brian


tl;dr: Help me eliminate const ASPECT_RATIO.


Code:

/// <reference path="d3.d.ts" />

"use strict";

/* (c) brianjenkins94 | brianjenkins94.me | MIT licensed */

// Get JSON, impose synchronicity
d3.json("js/data.json", function(error, treeData) {
  if (!error) {
// Instantiate canvas
var canvas = d3.select("#canvas");

// Aspect ratio nonsense
const ASPECT_RATIO = 1.89260808926;

canvas.attr("viewBox", " 0 0 " + window.innerWidth + " " + (window.innerWidth * ASPECT_RATIO));
canvas.attr("preserveAspectRatio", "xMinYMin slice");

// Update
update();

function update() {

  // Add an SVG group element
  canvas.append("g");

  // Instantiate group
  var group = canvas.select("g");

  // Translate group right
  group.attr("transform", "translate(64, 0)");

  // Instantiate layout tree
  var layout = d3.layout.tree();

  // Initialize layout dimensions
  layout.size([(window.innerWidth * ASPECT_RATIO), window.innerWidth - 128]);

  // Instantiate rotation diagonal
  var diagonal = d3.svg.diagonal();

  // Rotate projection 90 degrees about the diagonal
  diagonal.projection(function(d) { return [d.y, d.x]; });

  // Initialize node array
  var nodes = layout.nodes(treeData);

  // Initialize link array
  var links = layout.links(nodes);

  // Select all paths in group
  group.selectAll("path")
    // For each link, create a path
    .data(links).enter().append("path")
    // Provide the specific diagonal
    .attr("d", diagonal);

  // Select all groups in group
  var node = group.selectAll("g")
    // For each node, create a group
    .data(nodes).enter().append("g")
    // Translate accordingly
    .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

  // Add a circle at every node
  node.append("circle")
    .attr("r", 3);

  // Add label
  node.append("text")
    // To the left if the node has children, otherwise right
    .attr("dx", function(d) { return d.children ? -8 : 8; })
    .attr("dy", 0)
    // Branch if the node has children
    .attr("text-anchor", function(d) { return d.children ? "end" : "start"; })
    .text(function(d) { return d.name; });
    }
  } else {
    console.log("There was a connection error of some sort.");
  }
});

Demo:

Ad

Answer

Here's what I learned:

  • While an svg can be "dimensionless" it can't be "aspect-ratio-less." There must be some core aspect ratio to govern the resize.
  • The tree.size() and tree.nodeSize() functions create an "arbitrary coordinate system" that "is not limited screen coordinates."
  • What the aspect ratio is, is up to the developer and can be expressed as "arbitrary coordinates" or, in accordance with responsive design, can be expressed as relativistic measurements.

My solution is as follows:

// Viewbox & preserveAspectRatio
canvas.attr("viewBox", " 0 0 " + window.innerWidth + " " + (2 * window.innerWidth));
canvas.attr("preserveAspectRatio", "xMinYMin slice");

...

// Initialize layout dimensions
layout.size([(2 * window.innerWidth), (window.innerWidth - 128)]);

Thus eliminating the dependence on const ASPECT_RATIO in favor of relative measurements based off of browser dimensions.

This can potentially (and almost certainly will) cause rendering inconsistency across multiple viewports, but can be handled accordingly by querying the viewport prior to rendering and employing cosmetic adjustments.

Ad
source: stackoverflow.com
Ad