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:
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!
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With