diff --git a/src/rendering/HorizontalLayout/index.js b/src/rendering/HorizontalLayout/index.js
new file mode 100644
index 0000000..9336c22
--- /dev/null
+++ b/src/rendering/HorizontalLayout/index.js
@@ -0,0 +1,90 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import * as style from 'rendering/style';
+
+const HorizontalLayout = ({ connectorPath, transforms, children }) => {
+ return <>
+
+ { React.Children.map(children, (child, i) => (
+ { child }
+ )) }
+ >;
+};
+
+HorizontalLayout.defaultProps = {
+ withConnectors: false,
+ spacing: 10
+};
+
+HorizontalLayout.propTypes = {
+ spacing: PropTypes.number,
+ withConnectors: PropTypes.bool,
+ connectorPath: PropTypes.string,
+ transforms: PropTypes.arrayOf(PropTypes.string),
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node
+ ]).isRequired
+};
+
+const generateOffsetBoxes = (boxes, axisY, spacing) => {
+ let offset = 0;
+ return boxes.map(box => {
+ try {
+ return {
+ ...box,
+ offsetX: offset,
+ offsetY: axisY - box.axisY
+ };
+ }
+ finally {
+ offset += box.width + spacing;
+ }
+ });
+};
+const calculateChildTransforms = boxes =>
+ boxes.map(box => `translate(${ box.offsetX } ${ box.offsetY })`);
+const calculateConnectorPath = (boxes, axisY) => {
+ let last = boxes[0];
+ return boxes.slice(1).map(box => {
+ try {
+ return `
+ M${ last.offsetX + last.axisX2 },${ axisY }
+ H${ box.offsetX + box.axisX1 }
+ `;
+ }
+ finally {
+ last = box;
+ }
+ }).join('');
+};
+const layout = data => {
+ const { withConnectors, spacing } = {
+ ...HorizontalLayout.defaultProps,
+ ...data.props
+ };
+ const childBoxes = data.children.map(child => child.box);
+
+ data.box = {
+ width: childBoxes.reduce((width, box) => width + box.width, 0) +
+ (childBoxes.length - 1) * spacing,
+ height: Math.max(...(childBoxes.map(box => box.axisY))) +
+ Math.max(...(childBoxes.map(box => box.height - box.axisY))),
+ axisY: Math.max(...(childBoxes.map(box => box.axisY)))
+ };
+
+ const offsetBoxes = generateOffsetBoxes(childBoxes, data.box.axisY, spacing);
+ data.props = {
+ ...data.props,
+ transforms: calculateChildTransforms(offsetBoxes),
+ connectorPath: withConnectors
+ ? calculateConnectorPath(offsetBoxes, data.box.axisY)
+ : undefined
+ };
+
+ return data;
+};
+
+export default HorizontalLayout;
+export { layout };
diff --git a/src/rendering/style.js b/src/rendering/style.js
index 2f896a0..4b5e474 100644
--- a/src/rendering/style.js
+++ b/src/rendering/style.js
@@ -18,3 +18,8 @@ export const strokeBase = {
strokeWidth: '2px',
stroke: black
};
+
+export const connectors = {
+ fillOpacity: 0,
+ ...strokeBase
+};
diff --git a/src/rendering/types.js b/src/rendering/types.js
index da7e963..763d888 100644
--- a/src/rendering/types.js
+++ b/src/rendering/types.js
@@ -1,9 +1,11 @@
import * as SVG from 'rendering/SVG';
import * as Text from 'rendering/Text';
import * as Box from 'rendering/Box';
+import * as HorizontalLayout from 'rendering/HorizontalLayout';
export default {
SVG,
Text,
- Box
+ Box,
+ HorizontalLayout
};
diff --git a/src/syntax/js.js b/src/syntax/js.js
index 02e0478..83a7eb4 100644
--- a/src/syntax/js.js
+++ b/src/syntax/js.js
@@ -6,18 +6,37 @@ const parse = expr => {
type: 'SVG',
children: [
{
- type: 'Box',
+ type: 'HorizontalLayout',
props: {
- theme: 'literal'
+ withConnectors: true
},
children: [
{
- type: 'Text',
+ type: 'Box',
props: {
- quoted: true
+ theme: 'literal'
},
children: [
- `JS => ${ expr }`
+ {
+ type: 'Text',
+ children: [
+ 'JS'
+ ]
+ }
+ ]
+ },
+ {
+ type: 'Box',
+ props: {
+ theme: 'literal'
+ },
+ children: [
+ {
+ type: 'Text',
+ children: [
+ expr
+ ]
+ }
]
}
]
diff --git a/src/syntax/pcre.js b/src/syntax/pcre.js
index 12b2fa4..6877c50 100644
--- a/src/syntax/pcre.js
+++ b/src/syntax/pcre.js
@@ -6,18 +6,37 @@ const parse = expr => {
type: 'SVG',
children: [
{
- type: 'Box',
+ type: 'HorizontalLayout',
props: {
- theme: 'literal'
+ withConnectors: true
},
children: [
{
- type: 'Text',
+ type: 'Box',
props: {
- quoted: true
+ theme: 'literal'
},
children: [
- `PCRE => ${ expr }`
+ {
+ type: 'Text',
+ children: [
+ 'PCRE'
+ ]
+ }
+ ]
+ },
+ {
+ type: 'Box',
+ props: {
+ theme: 'literal'
+ },
+ children: [
+ {
+ type: 'Text',
+ children: [
+ expr
+ ]
+ }
]
}
]