Published
Edited
Nov 16, 2020
Importers
9 stars
Insert cell
Insert cell
Insert cell
Insert cell
render(htm`
<${Flex} svg width=${width} justify-items="stretch">
<${Flex} margin-right=10 flex-grow=2 aspect-ratio=1.618>
${coloredRectangle("orange")}
<//>
<${Flex} flex-direction="column" flex-grow=1>
<${Flex} margin-bottom=10 flex-grow=2>
${coloredRectangle("blue")}
<//>
<${Flex} flex-grow=1>
${coloredRectangle("purple")}
<//>
<//>
<//>
`)
Insert cell
render(htm`
<div>
<hr />
<h2>Demo 2</h2>
<svg width=${width} height=165>
<!-- grid lines -->
${d3
.range(15, 165, 30)
.map(
v => htm`<line y1=${v} y2=${v} x2=${width} stroke=#f0f0f0 />`
)}
<!-- bars and labels -->
<g font-size=10 text-anchor=middle fill=#555>
<${Flex} width=${width} height=165 align-items="flex-end">
${d3.range(numberOfBins).map(
i =>
htm`<${Flex} flex-direction=column flex-shrink=1 width=50 margin-right=5>
<${Flex} height=15>
${({ hcenter, vcenter }) =>
htm`<text x=${hcenter} y=${vcenter} alignment-baseline="middle">${i +
1}</text>`}
<//>
<${Flex} height=${d3.randomUniform(
20,
150
)()}>${coloredRectangle("teal", false)}<//>
<//>`
)}
<//>
<//>
</g>
</div>
`)
Insert cell
Insert cell
Insert cell
coloredRectangle = (color, showLayoutParameters = true) => ({
left,
top,
width,
height,
hcenter,
vcenter
}) =>
htm`<g>
<rect x=${left} width=${width} y=${top} height=${height} fill=${color}></rect>
${
showLayoutParameters
? htm`<text x=${hcenter} y=${vcenter} text-anchor="middle" font-size=14 fill="white">
${JSON.stringify({ left, top, width, height })}
</text>`
: undefined
}
</g>`
Insert cell
Insert cell
Flex = {
function Flex(props) {
const { children, svg, left = 0, top = 0, ...otherProps } = props;

const layout = flexLayout({ props });

const flexed = htm`<${FlexResolved}
left=${parseFloat(left) + layout.left}
top=${parseFloat(top) + layout.top}
width=${layout.width}
height=${layout.height}
layout=${layout}
...${otherProps}
>
${children}
<//>`;

if (svg) {
return htm`<svg width=${layout.width +
layout.left +
layout.right} height=${layout.height + layout.top + layout.bottom}>
${flexed}
</svg>`;
} else {
return flexed;
}
}

function FlexResolved(props) {
return htm`<${Preact.Fragment}>${[
...childrenToArray(props.children)
.filter(child => typeof child === "function")
.map((child, i) => {
const { width, height, left, top } = props;
return htm`<${Preact.Fragment}>
${child({
width,
height,
left,
top,
right: left + width,
bottom: top + height,
hcenter: left + width / 2,
vcenter: top + height / 2
})}
<//>`;
}),
...childrenToArray(props.children)
.filter(child => child.type === Flex)
.map((child, i) => {
const layout = props.layout.children[i];
return htm`<${FlexResolved}
key=${`a${i}`}
left=${(props.left || 0) + layout.left}
top=${(props.top || 0) + layout.top}
width=${layout.width}
height=${layout.height}
layout=${layout}
...${child.attributes}
>
${child.props.children}
<//>`;
})
]}<//>`;
}

function childrenToArray(children) {
// This works a bit different from React.Children.toArray
// React.Children.toArray didn't work with a function as a child.
if (children == null) {
return [];
} else if (typeof children === "function") {
return flatten([children]);
} else if (typeof children === "object") {
return flatten([children]);
}
}

function flatten(array) {
const result = [];
for (let entry of array) {
if (Array.isArray(entry)) {
for (let a of flatten(entry)) {
result.push(a);
}
} else {
result.push(entry);
}
}
return result;
}

function flexLayout(reactElement) {
const layoutTree = buildTree(reactElement);
layoutTree.calculateLayout(undefined, undefined, yoga.DIRECTION_LTR);
const computed = getComputedLayout(layoutTree);
layoutTree.freeRecursive();
return computed;
}

function buildTree(reactElement) {
const attr = reactElement.props;
const tree = yoga.Node.create(); // this needs to be freed with node.freeRecursive()

// Apply properties from the dom nodes with defaults from the CSS spec
tree.setAlignContent(
attr.hasOwnProperty("align-content")
? parse.align(attr["align-content"])
: yoga.ALIGN_STRETCH
);
tree.setAlignItems(
attr.hasOwnProperty("align-items")
? parse.align(attr["align-items"])
: yoga.ALIGN_STRETCH
);
tree.setAlignSelf(
attr.hasOwnProperty("align-self")
? parse.align(attr["align-self"])
: yoga.ALIGN_AUTO
);
tree.setDisplay(yoga.DISPLAY_FLEX);
tree.setFlexDirection(
attr.hasOwnProperty("flex-direction")
? parse.direction(attr["flex-direction"])
: yoga.FLEX_DIRECTION_ROW
);
tree.setFlexGrow(
attr.hasOwnProperty("flex-grow") ? parseFloat(attr["flex-grow"]) : 0
);
tree.setFlexShrink(
attr.hasOwnProperty("flex-shrink") ? parseFloat(attr["flex-shrink"]) : 1
);
tree.setFlexWrap(
attr.hasOwnProperty("flex-wrap")
? parse.wrap(attr["flex-wrap"])
: yoga.WRAP_NO_WRAP
);
tree.setHeight(attr.hasOwnProperty("height") ? attr.height : "auto");
tree.setWidth(attr.hasOwnProperty("width") ? attr.width : "auto");
tree.setJustifyContent(
attr.hasOwnProperty("justify-content")
? parse.justifyContent(attr["justify-content"])
: yoga.JUSTIFY_FLEX_START
);
if (attr.hasOwnProperty("aspect-ratio"))
tree.setAspectRatio(parseFloat(attr["aspect-ratio"]));
if (attr.hasOwnProperty("flex-basis"))
tree.setFlexBasis(attr["flex-basis"]);
if (attr.hasOwnProperty("margin"))
tree.setMargin(yoga.EDGE_ALL, attr["margin"]);
if (attr.hasOwnProperty("margin-left"))
tree.setMargin(yoga.EDGE_LEFT, attr["margin-left"]);
if (attr.hasOwnProperty("margin-right"))
tree.setMargin(yoga.EDGE_RIGHT, attr["margin-right"]);
if (attr.hasOwnProperty("margin-top"))
tree.setMargin(yoga.EDGE_TOP, attr["margin-top"]);
if (attr.hasOwnProperty("margin-bottom"))
tree.setMargin(yoga.EDGE_BOTTOM, attr["margin-bottom"]);
if (attr.hasOwnProperty("max-height"))
tree.setMaxHeight(attr["max-height"]);
if (attr.hasOwnProperty("max-width")) tree.setMaxWidth(attr["max-width"]);
if (attr.hasOwnProperty("min-height"))
tree.setMaxHeight(attr["min-height"]);
if (attr.hasOwnProperty("min-width")) tree.setMaxWidth(attr["min-width"]);
if (attr.hasOwnProperty("overflow"))
tree.setOverflow(parse.overflow(attr.overflow));
if (attr.hasOwnProperty("padding"))
tree.setPadding(yoga.EDGE_ALL, attr["padding"]);
if (attr.hasOwnProperty("padding-left"))
tree.setPadding(yoga.EDGE_LEFT, attr["padding-left"]);
if (attr.hasOwnProperty("padding-right"))
tree.setPadding(yoga.EDGE_RIGHT, attr["padding-right"]);
if (attr.hasOwnProperty("padding-top"))
tree.setPadding(yoga.EDGE_TOP, attr["padding-top"]);
if (attr.hasOwnProperty("padding-bottom"))
tree.setPadding(yoga.EDGE_BOTTOM, attr["padding-bottom"]);

// Recurse into the child nodes to build a tree
childrenToArray(reactElement.props.children)
.filter(child => child.type === Flex)
.forEach((child, i) => {
tree.insertChild(buildTree(child), i);
});

return tree;
}

function getComputedLayout(yogaTree) {
const layout = yogaTree.getComputedLayout();
const children = [];
for (let i = 0; i < yogaTree.getChildCount(); ++i) {
children.push(getComputedLayout(yogaTree.getChild(i)));
}
return {
...layout,
children
};
}

return Flex;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more