Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw imbricated blocks that are rotated and fliped with d3.js

I have to create chart that consists of a hierarchy of blocks (a big block that contains smaller blocks that contain other blocks).

The data is a hierarchy of these blocks

{ 
    element: {name: test,  geometry: [..], orientation: '180'}
    element: {name: test2, geometry: [..], orientation: 'none'}
    element: {name: test3, geometry: [..], orientation: 'flipX'}
    element: { 
        name: test4, 
        geometry: [..], 
        orientation: '90'
        children:
            [ 
                element: {name: test5, geometry: [..], orientation: '180'}
                element: {name: test6, geometry: [..], orientation: 'none'}
            ]
        }
}

Each block has a geometry (array of edges) and an orientation:

  • no orientation
  • flip on X (flip around the center of bounding box on X axis)
  • flip on y (flip around the center of bounding box on Y axis)
  • rotate 90 degrees (rotate around the point of origin 90 degrees)
  • 180 degrees

The coordinates of the edges are relative to the parent block's origin.

So if the main block is rotated, the sub-block's coordinate system will also be rotated.

I need to draw this and then change the fill color of each block based on metrics.

The way i did it now is to recursively parse that hierarchy and append svg elements for each one:

<svg>
    <g><path>
        <g><path></g>
        <g><path></g>
        <g><path>
            <g><path></g>
        </g>
    </g>
</svg>

This helps with all the coordinate inheritance as i draw inside groups that are already rotated.

I am not sure this is the best way as i am not using the .data() append() enter() functions because i don't know how to draw imbricated elements. The blocks also have labels and an indicator of where their origin is but i didn't include this to simplify.

Is there a better way to do this?

Thank you!

like image 574
Daniel Avatar asked Oct 04 '17 15:10

Daniel


1 Answers

As long as you are not using a simulation you really don't need a .data()call. You can use a recurrent function to parse the elements tree and append elements to a specific SVG group. Since you are applying transformations on the DOM such as rotate/scale I think that the best solution is to have the DOM to mimic your data tree (this is necessary for rotations and flips). The flips are achieved by scaling the DOM element negatively as such:

if (orientation === 'flipX') {
    ref.attr('transform', `scale(-1, 1) translate(${-ref.node().getBBox().width}, 0)`);
}

if (orientation === 'flipY') {
    ref.attr('transform', `scale(1, -1) translate(0, ${-ref.node().getBBox().height})`);
}

You will need to measure the bounding box of the group when flipping and apply the transformation in order to flip the box by its middle point.

Here is the code that will allow you to parse the tree and append DOM elements with specific transformations:

const svg = d3.select(svgDOM);
svg.selectAll("*").remove();
const group = svg.append('g');
group.attr('transform', 'translate(200,100)');

const colors = d3.schemeAccent;
let parseDataArr = (parent, arr) => {
  const group = parent.append('g');
  arr.forEach((elem, index) => {
    const {element: {geometry, orientation, children}} = elem;
    const ref = group.append('g');
    ref
      .append('path')
      .attr('fill', colors[index])
      .attr('opacity', 0.4)
      .attr('stroke', '#000')
      .attr('stroke-width', 1)
      .attr('d', `M ${geometry.join('L')} z`);

    if (["none", "flipX", "flipY"].indexOf(orientation) === -1) {
      ref.attr('transform', `rotate(${orientation})`);
    }

    if (children) {
      parseDataArr(ref, children);
    }

    if (orientation === 'flipX') {
      ref.attr('transform', `scale(-1, 1) translate(${-ref.node().getBBox().width}, 0)`);
    }

    if (orientation === 'flipY') {
      ref.attr('transform', `scale(1, -1) translate(0, ${-ref.node().getBBox().height})`);
    }

  });
}

parseDataArr(group, data);

Here is a sample code that I used to test the implementation: https://observablehq.com/@cstefanache/test-svg-transforms

like image 135
Cornel Stefanache Avatar answered Oct 18 '22 11:10

Cornel Stefanache