swizec.com

#### Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Learn more

# Dancing tree fractal with React

That damn Pythagorean fractal from last month wouldn't leave me alone, so I fixed it. Timing may or may not coincide with a commenter giving a solution to the wonky triangle problem.

Here’s the code on Github. For the story and explanation, keep reading. :)

It took somebody doing the math instead of me to kick my arse into gear. Here's Vinicius Ribeiro schooling me on high school trigonometry:

You are not applying the Law of Sines correctly. The variable 'd' is the diameter of the triangle's circumcircle, not its perimeter.

Tinkering with your code on github, I've managed to accomplish what you were trying to do by doing the following calculations:

```.css-1yb0ye3{font-family:monospace;color:#728fcb;background-color:#faf8f5;font-size:0.9em;padding-left:0;padding-right:0;}.css-1yb0ye3 .comment,.css-1yb0ye3 .prolog,.css-1yb0ye3 .doctype,.css-1yb0ye3 .cdata,.css-1yb0ye3 .punctuation{color:#b6ad9a;}.css-1yb0ye3 .namespace{opacity:0.7;}.css-1yb0ye3 .tag,.css-1yb0ye3 .operator,.css-1yb0ye3 .number{color:#063289;}.css-1yb0ye3 .property,.css-1yb0ye3 .function{color:#b29762;}.css-1yb0ye3 .tag-id,.css-1yb0ye3 .selector,.css-1yb0ye3 .atrule-id{color:#2d2006;}.css-1yb0ye3 .attr-name{color:#896724;}.css-1yb0ye3 .boolean,.css-1yb0ye3 .string,.css-1yb0ye3 .entity,.css-1yb0ye3 .url,.css-1yb0ye3 .attr-value,.css-1yb0ye3 .keyword,.css-1yb0ye3 .control,.css-1yb0ye3 .directive,.css-1yb0ye3 .unit,.css-1yb0ye3 .statement,.css-1yb0ye3 .regex,.css-1yb0ye3 .at-rule{color:#728fcb;}.css-1yb0ye3 .placeholder,.css-1yb0ye3 .variable{color:#93abdc;}.css-1yb0ye3 .deleted{text-decoration-line:line-through;}.css-1yb0ye3 .inserted{text-decoration-line:underline;}.css-1yb0ye3 .italic{font-style:italic;}.css-1yb0ye3 .important,.css-1yb0ye3 .bold{font-weight:700;}.css-1yb0ye3 .important{color:#896724;}.css-1yb0ye3 .highlight{background:hsla(0, 0%, 70%, .5);}.css-o6ar0x{font-family:monospace;color:#728fcb;background-color:#faf8f5;font-size:0.9em;padding-left:0;padding-right:0;font-family:monospace;color:#728fcb;background-color:#faf8f5;font-size:0.9em;padding-left:0;padding-right:0;}.css-o6ar0x .comment,.css-o6ar0x .prolog,.css-o6ar0x .doctype,.css-o6ar0x .cdata,.css-o6ar0x .punctuation{color:#b6ad9a;}.css-o6ar0x .namespace{opacity:0.7;}.css-o6ar0x .tag,.css-o6ar0x .operator,.css-o6ar0x .number{color:#063289;}.css-o6ar0x .property,.css-o6ar0x .function{color:#b29762;}.css-o6ar0x .tag-id,.css-o6ar0x .selector,.css-o6ar0x .atrule-id{color:#2d2006;}.css-o6ar0x .attr-name{color:#896724;}.css-o6ar0x .boolean,.css-o6ar0x .string,.css-o6ar0x .entity,.css-o6ar0x .url,.css-o6ar0x .attr-value,.css-o6ar0x .keyword,.css-o6ar0x .control,.css-o6ar0x .directive,.css-o6ar0x .unit,.css-o6ar0x .statement,.css-o6ar0x .regex,.css-o6ar0x .at-rule{color:#728fcb;}.css-o6ar0x .placeholder,.css-o6ar0x .variable{color:#93abdc;}.css-o6ar0x .deleted{text-decoration-line:line-through;}.css-o6ar0x .inserted{text-decoration-line:underline;}.css-o6ar0x .italic{font-style:italic;}.css-o6ar0x .important,.css-o6ar0x .bold{font-weight:700;}.css-o6ar0x .important{color:#896724;}.css-o6ar0x .highlight{background:hsla(0, 0%, 70%, .5);}.css-o6ar0x .comment,.css-o6ar0x .prolog,.css-o6ar0x .doctype,.css-o6ar0x .cdata,.css-o6ar0x .punctuation{color:#b6ad9a;}.css-o6ar0x .namespace{opacity:0.7;}.css-o6ar0x .tag,.css-o6ar0x .operator,.css-o6ar0x .number{color:#063289;}.css-o6ar0x .property,.css-o6ar0x .function{color:#b29762;}.css-o6ar0x .tag-id,.css-o6ar0x .selector,.css-o6ar0x .atrule-id{color:#2d2006;}.css-o6ar0x .attr-name{color:#896724;}.css-o6ar0x .boolean,.css-o6ar0x .string,.css-o6ar0x .entity,.css-o6ar0x .url,.css-o6ar0x .attr-value,.css-o6ar0x .keyword,.css-o6ar0x .control,.css-o6ar0x .directive,.css-o6ar0x .unit,.css-o6ar0x .statement,.css-o6ar0x .regex,.css-o6ar0x .at-rule{color:#728fcb;}.css-o6ar0x .placeholder,.css-o6ar0x .variable{color:#93abdc;}.css-o6ar0x .deleted{text-decoration-line:line-through;}.css-o6ar0x .inserted{text-decoration-line:underline;}.css-o6ar0x .italic{font-style:italic;}.css-o6ar0x .important,.css-o6ar0x .bold{font-weight:700;}.css-o6ar0x .important{color:#896724;}.css-o6ar0x .highlight{background:hsla(0, 0%, 70%, .5);}```const currentH = 0.2 * w,
nextLeft = Math.sqrt(currentH * currentH + 0.7 * w * 0.7 * w),
nextRight = Math.sqrt(currentH * currentH + 0.3 * w * 0.3 * w),
A = Math.deg(Math.atan(currentH / (0.3 * w))),
B = Math.deg(Math.atan(currentH / (0.7 * w)));
``````

The height of the inner triangle is a fraction of the current 'w'. By doing that, we can infer nextLeft and nextRight using the Pythagorean theorem. The angles can then be calculated using the inverse tangent (atan) and the triangle height.

Hope this helps!

Help it did! Thanks, Vinicius.

## How you too can build a dancing tree fractal

Equipped with basic trigonometry, you need 3 ingredients to build a dancing tree:

• a recursive `<Pythagoras>` component
• a mousemove listener
• a memoized next-step-props calculation function

We'll use the `<Pythagoras>` component from November, add a D3 mouse listener, and put Vinicus's math with some tweaks into a memoized function. We need D3 because its mouse listeners automatically calculate mouse position relative to SVG coordinates, and memoization helps us keep our code faster.

The improved `<Pythagoras>` component takes a few more arguments than before, and it uses a function to calculate future props. Like this:

``````const Pythagoras = ({ w,x, y, heightFactor, lean, left, right, lvl, maxlvl }) => {
if (lvl >= maxlvl || w < 1) {
return null;
}

const { nextRight, nextLeft, A, B } = memoizedCalc({
w: w,
heightFactor: heightFactor,
lean: lean
});

let rotate = '';

if (left) {
rotate = `rotate(\${-A} 0 \${w})`;
}else if (right) {
rotate = `rotate(\${B} \${w} \${w})`;
}

return (
<g transform={`translate(\${x} \${y})="" \${rotate}`}="">
<rect width={w} height={w} x={0} y={0} style="{{fill:" interpolateviridis(lvl="" maxlvl)}}="">

<pythagoras w={nextLeft} x={0} y={-nextLeft} lvl={lvl+1} maxlvl={maxlvl} heightfactor={heightFactor} lean={lean} left="">

<pythagoras w={nextRight} x={w-nextRight} y={-nextRight} lvl={lvl+1} maxlvl={maxlvl} heightfactor={heightFactor} lean={lean} right="">

</pythagoras></pythagoras></rect></g>
);
};
``````

We break recursion whenever we try to draw an invisible square or have reached too deep into the tree. Then we:

• use `memoizedCalc` to do the mathematics
• define different `rotate()` transforms for the `left` and `right` branches
• and return an SVG `<rect>` for the current rectangle, and two `<Pythagoras>` elements for each branch.

Most of this code deals with passing arguments onwards to children. It’s not the most elegant approach, but it works. The rest is about positioning branches so corners match up.

## The maths

I don't really understand this math, but I sort of know where it's coming from. It's the sine law applied correctly. You know, the part I failed at miserably last time ?

``````const memoizedCalc = (function () {
const memo = {};

const key = ({ w, heightFactor, lean }) => [w, heightFactor, lean].join("-");

return (args) => {
const memoKey = key(args);

if (memo[memoKey]) {
return memo[memoKey];
} else {
const { w, heightFactor, lean } = args;

const trigH = heightFactor * w;

const result = {
nextRight: Math.sqrt(trigH ** 2 + (w * (0.5 + lean)) ** 2),
nextLeft: Math.sqrt(trigH ** 2 + (w * (0.5 - lean)) ** 2),
A: Math.deg(Math.atan(trigH / ((0.5 - lean) * w))),
B: Math.deg(Math.atan(trigH / ((0.5 + lean) * w))),
};

memo[memoKey] = result;
return result;
}
};
})();
``````

We added to Vinicius's maths a dynamic `heightFactor` and `lean` adjustment. We'll control those with mouse movement.

To improve performance, maybe, our `memoizedCalc` function has an internal data store that maintains a hash of every argument tuple and its result. This lets us avoid computation and read from memory instead.

At 11 levels of depth, `memoizedCalc` gets called 2,048 times and only returns 11 different results. You can't find a better candidate for memoization.

Of course, a benchmark would be great here. Maybe `sqrt`, `atan`, and `**` aren't that slow, and our real bottleneck is redrawing all those nodes on every mouse move. Hint: it totally is.

Now that I spell it out… what the hell was I thinking? I'm impressed it works as well as it does.

## The mouse listener

Inside `App.js`, we add a mouse event listener. We use D3's because it gives us the SVG-relative position calculation out of the box. With React’s, we'd have to do the hard work ourselves.

``````// App.js
state = {
currentMax: 0,
baseW: 80,
heightFactor: 0,
lean: 0
};

componentDidMount() {
d3select(this.refs.svg)
.on("mousemove", this.onMouseMove.bind(this));
}

onMouseMove(event) {
const [x, y] = d3mouse(this.refs.svg),

scaleFactor = scaleLinear().domain([this.svg.height, 0])
.range([0, .8]),

scaleLean = scaleLinear().domain([0, this.svg.width/2, this.svg.width])
.range([.5, 0, -.5]);

this.setState({
heightFactor: scaleFactor(y),
lean: scaleLean(x)
});
}

// ...

render() {
// ...
<svg ref="svg"> //...
<pythagoras w={this.state.baseW} h={this.state.baseW} heightfactor={this.state.heightFactor} lean={this.state.lean} x={this.svg.width/2-40} y={this.svg.height-this.state.baseW} lvl={0} maxlvl="{this.state.currentMax}/">
}
</pythagoras></svg>
``````

A couple of things happen here:

• we set initial `lean` and `heightFactor` to `0`
• in `componentDidMount`, we use `d3.select` and `.on` to add a mouse listener
• we define an `onMouseMove` method as the listener
• we render the first `<Pythagoras>` using values from `state`

The `lean` parameter tells us which way the tree is leaning and by how much; the `heightFactor` tells us how high those triangles should be. We control both with the mouse position.

That happens in `onMouseMove`:

``````onMouseMove(event) {
const [x, y] = d3mouse(this.refs.svg),

scaleFactor = scaleLinear().domain([this.svg.height, 0])
.range([0, .8]),

scaleLean = scaleLinear().domain([0, this.svg.width/2, this.svg.width])
.range([.5, 0, -.5]);

this.setState({
heightFactor: scaleFactor(y),
lean: scaleLean(x)
});
}
``````

`d3mouse` – which is an imported `mouse` function from `d3-selection` – gives us cursor position relative to the SVG element. Two linear scales give us `scaleFactor` and `scalelean` values, which we put into component state.

If you're not used to D3 scales, this reads as:

• map vertical coordinates between `height` and `0` evenly to somewhere between `0` and `.8`
• map horizontal coordinates between `0` and `width/2` evenly to somewhere between `.5` and `0`, and coordinates between `width/2` and `width` to `0` and `-.5`

When we feed a change to `this.setState`, it triggers a re-render of the entire tree, our `memoizedCalc` function returns new values, and the final result is a dancing tree.

Beautious. ?

PS: last time, I mentioned that recursion stops working when you make a React build optimized for production. That doesn't happen. I don't know what was wrong with the specific case where I saw that behavior. ¯\(ツ)

Published on December 13th, 2016 in Front End, react, Technical

#### Continue reading about Dancing tree fractal with React

Semantically similar articles hand-picked by GPT-4

### Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Learn more

Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

Created by Swizec with ❤️