Adding VerticalLayout rendering component
This commit is contained in:
parent
67ca83ff16
commit
91ab1dbd05
23
src/quadratic-curve.js
Normal file
23
src/quadratic-curve.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
class QuadraticCurve {
|
||||||
|
constructor() {
|
||||||
|
this.points = [];
|
||||||
|
this.controlPoint = { x: 0, y: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
addPoint({ x, y }) {
|
||||||
|
const { x: cx, y: cy } = this.controlPoint;
|
||||||
|
this.points.push({ x: x - cx, y: y - cy });
|
||||||
|
|
||||||
|
if (this.points.length % 2 === 0) {
|
||||||
|
this.controlPoint = { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `q${ this.points.map(({ x, y }) => `${ x },${ y }`).join(' ') }`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default QuadraticCurve;
|
157
src/rendering/VerticalLayout/index.js
Normal file
157
src/rendering/VerticalLayout/index.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import QuadraticCurve from 'quadratic-curve';
|
||||||
|
|
||||||
|
import * as style from 'rendering/style';
|
||||||
|
|
||||||
|
const radius = 10;
|
||||||
|
|
||||||
|
const VerticalLayout = ({
|
||||||
|
withConnectors,
|
||||||
|
connectorPath,
|
||||||
|
transforms,
|
||||||
|
children
|
||||||
|
}) => <>
|
||||||
|
{ withConnectors && <path style={ style.connectors } d={ connectorPath }/> }
|
||||||
|
{ React.Children.map(children, (child, i) => (
|
||||||
|
<g transform={ transforms[i] }>{ child }</g>
|
||||||
|
)) }
|
||||||
|
</>;
|
||||||
|
|
||||||
|
VerticalLayout.defaultProps = {
|
||||||
|
withConnectors: false,
|
||||||
|
spacing: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
VerticalLayout.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, spacing, containerWidth) => {
|
||||||
|
let y = 0;
|
||||||
|
return boxes.map(box => {
|
||||||
|
try {
|
||||||
|
const x = (containerWidth - box.width) / 2;
|
||||||
|
return {
|
||||||
|
...box,
|
||||||
|
x, y,
|
||||||
|
axisX1: x + box.axisX1,
|
||||||
|
axisX2: x + box.axisX2,
|
||||||
|
axisY: y + box.axisY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
y += spacing + box.height;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const calculateChildTransforms = boxes =>
|
||||||
|
boxes.map(box => `translate(${ box.x } ${ box.y })`);
|
||||||
|
const linear = (start, end, amount) => start * (1 - amount) + end * amount;
|
||||||
|
const generateCurve = (start, end) => {
|
||||||
|
const curve = new QuadraticCurve();
|
||||||
|
const width = Math.abs(start.x - end.x);
|
||||||
|
const height = Math.abs(start.y - end.y);
|
||||||
|
const direction = {
|
||||||
|
x: start.x < end.x ? 1 : -1,
|
||||||
|
y: start.y < end.y ? 1 : -1
|
||||||
|
};
|
||||||
|
const compression = Math.max(0, 1 - height / (2 * radius));
|
||||||
|
const curveEnd = linear(radius * 2, width, compression);
|
||||||
|
|
||||||
|
curve.addPoint({
|
||||||
|
x: linear(radius, width * 0.25, compression) * direction.x,
|
||||||
|
y: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
if (compression === 0) {
|
||||||
|
curve.addPoint({
|
||||||
|
x: radius * direction.x,
|
||||||
|
y: radius * direction.y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
curve.addPoint({
|
||||||
|
x: linear(radius, width * 0.5, compression) * direction.x,
|
||||||
|
y: height / 2 * direction.y
|
||||||
|
});
|
||||||
|
|
||||||
|
if (compression === 0) {
|
||||||
|
curve.addPoint({
|
||||||
|
x: radius * direction.x,
|
||||||
|
y: (height - radius) * direction.y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
curve
|
||||||
|
.addPoint({
|
||||||
|
x: linear(radius, width * 0.75, compression) * direction.x,
|
||||||
|
y: height * direction.y
|
||||||
|
})
|
||||||
|
.addPoint({
|
||||||
|
x: curveEnd * direction.x,
|
||||||
|
y: height * direction.y
|
||||||
|
});
|
||||||
|
|
||||||
|
if (width > curveEnd) {
|
||||||
|
curve
|
||||||
|
.addPoint({
|
||||||
|
x: (width + curveEnd) / 2 * direction.x,
|
||||||
|
y: height * direction.y
|
||||||
|
})
|
||||||
|
.addPoint({
|
||||||
|
x: width * direction.x,
|
||||||
|
y: height * direction.y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return `M ${ start.x },${ start.y } ${ curve.toString() }`;
|
||||||
|
};
|
||||||
|
const calculateConnectorPath = (boxes, { width, height }) => {
|
||||||
|
const connectorY = height / 2;
|
||||||
|
const start1 = { x: 0, y: connectorY };
|
||||||
|
const start2 = { x: width, y: connectorY };
|
||||||
|
|
||||||
|
return boxes.map(box => `
|
||||||
|
${ generateCurve(start1, { x: box.axisX1, y: box.axisY }) }
|
||||||
|
${ generateCurve(start2, { x: box.axisX2, y: box.axisY }) }
|
||||||
|
`).join('');
|
||||||
|
};
|
||||||
|
const layout = data => {
|
||||||
|
const { withConnectors, spacing } = {
|
||||||
|
...VerticalLayout.defaultProps,
|
||||||
|
...data.props
|
||||||
|
};
|
||||||
|
|
||||||
|
const childBoxes = data.children.map(child => child.box);
|
||||||
|
const curveAllowance = withConnectors ? radius * 2 : 0;
|
||||||
|
|
||||||
|
data.box = {
|
||||||
|
width: Math.max(...(childBoxes.map(box => box.width))) +
|
||||||
|
2 * curveAllowance,
|
||||||
|
height: childBoxes.reduce((height, box) => height + box.height, 0) +
|
||||||
|
(childBoxes.length - 1) * spacing
|
||||||
|
};
|
||||||
|
|
||||||
|
const offsetBoxes = generateOffsetBoxes(childBoxes, spacing, data.box.width);
|
||||||
|
data.props = {
|
||||||
|
...data.props,
|
||||||
|
transforms: calculateChildTransforms(offsetBoxes),
|
||||||
|
connectorPath: withConnectors
|
||||||
|
? calculateConnectorPath(offsetBoxes, data.box)
|
||||||
|
: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VerticalLayout;
|
||||||
|
export { layout };
|
@ -3,11 +3,13 @@ import * as Pin from 'rendering/Pin';
|
|||||||
import * as Text from 'rendering/Text';
|
import * as Text from 'rendering/Text';
|
||||||
import * as Box from 'rendering/Box';
|
import * as Box from 'rendering/Box';
|
||||||
import * as HorizontalLayout from 'rendering/HorizontalLayout';
|
import * as HorizontalLayout from 'rendering/HorizontalLayout';
|
||||||
|
import * as VerticalLayout from 'rendering/VerticalLayout';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
SVG,
|
SVG,
|
||||||
Pin,
|
Pin,
|
||||||
Text,
|
Text,
|
||||||
Box,
|
Box,
|
||||||
HorizontalLayout
|
HorizontalLayout,
|
||||||
|
VerticalLayout
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import Render from 'components/Render';
|
import Render from 'components/Render';
|
||||||
import layout from 'layout';
|
import layout from 'layout';
|
||||||
|
|
||||||
|
const type = 'JS';
|
||||||
|
const description = 'JavaScript';
|
||||||
|
|
||||||
const parse = expr => {
|
const parse = expr => {
|
||||||
return {
|
return {
|
||||||
type: 'SVG',
|
type: 'SVG',
|
||||||
@ -29,15 +32,39 @@ const parse = expr => {
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: 'Box',
|
type: 'VerticalLayout',
|
||||||
props: {
|
props: {
|
||||||
theme: 'literal'
|
withConnectors: true
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: 'Text',
|
type: 'Box',
|
||||||
|
props: {
|
||||||
|
theme: 'literal',
|
||||||
|
label: 'Type'
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
'JS'
|
{
|
||||||
|
type: 'Text',
|
||||||
|
children: [
|
||||||
|
type
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Box',
|
||||||
|
props: {
|
||||||
|
theme: 'literal',
|
||||||
|
label: 'Description'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'Text',
|
||||||
|
children: [
|
||||||
|
description
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import Render from 'components/Render';
|
import Render from 'components/Render';
|
||||||
import layout from 'layout';
|
import layout from 'layout';
|
||||||
|
|
||||||
|
const type = 'PCRE';
|
||||||
|
const description = 'Perl-compatible Regular Expression';
|
||||||
|
|
||||||
const parse = expr => {
|
const parse = expr => {
|
||||||
return {
|
return {
|
||||||
type: 'SVG',
|
type: 'SVG',
|
||||||
@ -29,15 +32,39 @@ const parse = expr => {
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: 'Box',
|
type: 'VerticalLayout',
|
||||||
props: {
|
props: {
|
||||||
theme: 'literal'
|
withConnectors: true
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: 'Text',
|
type: 'Box',
|
||||||
|
props: {
|
||||||
|
theme: 'literal',
|
||||||
|
label: 'Type'
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
'PCRE'
|
{
|
||||||
|
type: 'Text',
|
||||||
|
children: [
|
||||||
|
type
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Box',
|
||||||
|
props: {
|
||||||
|
theme: 'literal',
|
||||||
|
label: 'Description'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'Text',
|
||||||
|
children: [
|
||||||
|
description
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user