Home > other >  center and scale dynamic graph (<g>) in an <svg> element - d3
center and scale dynamic graph (<g>) in an <svg> element - d3

Time:12-21

What I want is to center and scale a dynamically created graph in an svg element

tl;dr: I have an svg with fixed width and height, 1200x480, and I have a dynamically created graph within a g element. The graph's width and height can be smaller or bigger than the available fixed size of the svg, so I will need to do some scaling as well as centering. i.e. centering a graph with 330x250, is quite straightforward, the issues arise when either the width or height are bigger than the graph's

More context: I have an svg, with a fixed width and height. The first node of the graph is drawn on 0,0 of the svg, and the rest follow a pattern of length * dx and depth * dy, to create the tree structure (you will notice in the images below the graph is in the negatives of the svg, this is because I want the graph to be read from right to left and I dont know the length or depth of the graph, I start from the parent on 0,0 and iterate through their children and create the graph)

The fe code is simple:

<svg id="svg" #svg [attr.viewBox]="0   ', '   0   ', '   options.width   ', '   options.height">
  <g id="decision-tree" [attr.transform]="'translate('   centerX   ','   centerY   ') scale('   toScale   ')'">
    //graph elements go here
  </g>
</svg>

The problem in pictures:

a) If the height of the graph is bigger than the available size of the svg. enter image description here

b) If the width of the graph is bigger than the available size of the svg. enter image description here

c) both! are bigger than the available size of the svg

What I tried:

I tried several ways of calculating the new width or height of the graph but I think am missing something basic. Most along these lines:

widthLimit = 1200; heightLimit = 480;
let ratio = graphWidth / graphHeight;
newHeight = ratio / widthLimit;
scale = newHeight / graphHeight; //no idea if this is correct but it kinda worked
if(newHeight > heightLimit ) {
   newWidth = ratio * heightLimit;
   scale = newWidth / graphWidth; //no idea if this is correct but it kinda worked
}

This kinda worked in the length issue but never in the depth, and completely sucked if both were bigger

What am I missing? Is there a magic function in d3 that just does this? (please say yes ;D) Whats the core logic I dont get?

CodePudding user response:

I managed to come to a solution, I was missing some svg basics

  • you need to set svg's width, height and viewbox
  • before any translation translate the element to 0,0, and set its scale to 1 element.attr('transform', 'scale(1)');

and the steps to center any sized element in an svg

  1. get the width and height with d3's (of both the svg and the element you want to center) getBoundingClientRect(), i.e. for svg: d3.select('#svg').node().getBoundingClientRect().width and .height;
  2. find the scale factor, divide svgWidth/elementWidth and svgHeight/elementHeight, and use the smallest of the two
  3. find x and y using (svgWidth - elementScaledWidth) / 2 and (svgHeight - elementScaledHeight) / 2
  4. do the translation with d3's element.attr('transform', 'translate(${x}, ${y}) scale(${scale})');
  5. reset the zoomIdentity of the svg with d3's d3.select('#svg').call(d3.zoom().transform, d3.zoomIdentity.translate(x, y).scale(scale))
  • Related