diff --git a/src/rendering/Box/style.js b/src/rendering/Box/style.js index 96c563f..6357b44 100644 --- a/src/rendering/Box/style.js +++ b/src/rendering/Box/style.js @@ -4,11 +4,11 @@ import { tan, grey, blue, - fontFamily, - fontSizeSmall, - strokeBase + strokeBase, + infoText } from 'rendering/style'; +export { infoText }; export const literal = { fill: blue, strokeWidth: '1px', @@ -31,7 +31,3 @@ export const capture = { export const anchor = { fill: brown }; -export const infoText = { - fontSize: fontSizeSmall, - fontFamily -}; diff --git a/src/rendering/Loop/index.js b/src/rendering/Loop/index.js new file mode 100644 index 0000000..e11e9d3 --- /dev/null +++ b/src/rendering/Loop/index.js @@ -0,0 +1,133 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { getBBox } from 'layout'; + +import * as style from 'rendering/style'; + +const radius = 10; +const arrowSize = 5; + +const Loop = ({ + label, + labelTransform, + contentTransform, + loopPaths, + children +}) => { + const labelProps = { + transform: labelTransform, + style: style.infoText + }; + + return <> + + { label && { label } } + + { children } + + ; +}; + +Loop.propTypes = { + label: PropTypes.string, + greedy: PropTypes.bool, + skip: PropTypes.bool, + repeat: PropTypes.bool, + labelTransform: PropTypes.string, + contentTransform: PropTypes.string, + loopPaths: PropTypes.string, + children: PropTypes.node.isRequired +}; + +const generateOffsetBox = (box, { skip, repeat }) => { + let x = 0; + let y = 0; + + if (skip) { + x = 1.5 * radius; + y = radius; + } else if (repeat) { + x = radius; + } + + return { + ...box, + x, y, + axisX1: x + box.axisX1, + axisX2: x + box.axisX2, + axisY: y + box.axisY + }; +}; +const skipPath = (box, greedy) => { + const vert = Math.max(0, box.axisY - box.y - radius); + const horiz = box.width - radius; + + return ` + M 0,${ box.axisY } + q ${ radius },0 ${ radius },${ -radius } + v ${ -vert } + q 0,${ -radius } ${ radius },${ -radius } + h ${ horiz } + q ${ radius },0 ${ radius },${ radius } + v ${ vert } + q 0,${ radius } ${ radius },${ radius } + ` + (greedy ? '' : ` + M ${ radius },${ box.axisY - 1.5 * radius } + l ${ arrowSize },${ arrowSize } + z + l ${ -arrowSize },${ arrowSize } + `); +}; +const repeatPath = (box, greedy) => { + const vert = box.y + box.height - box.axisY - radius; + + return ` + M ${ box.x },${ box.axisY } + q ${ -radius },0 ${ -radius },${ radius } + v ${ vert } + q 0,${ radius } ${ radius },${ radius } + h ${ box.width } + q ${ radius },0 ${ radius },${ -radius } + v ${ -vert } + q 0,${ -radius } ${ -radius },${ -radius } + ` + (greedy ? ` + m ${ radius },${ 1.5 * radius } + l ${ arrowSize },${ -arrowSize } + z + l ${ -arrowSize },${ -arrowSize } + ` : ''); +}; +const layout = data => { + const { label, greedy, skip, repeat } = data.props || {}; + const childBox = generateOffsetBox(data.children[0].box, { skip, repeat }); + const labelBox = label ? + getBBox({ label }) : + { width: 0, height: 0 }; + const width = childBox.width + childBox.x * 2; + const height = childBox.height + labelBox.height + + (skip ? 10 : 0) + (repeat ? 10 : 0); + + data.box = { + width, + height, + axisY: childBox.axisY, + axisX1: childBox.axisX1, + axisX2: childBox.axisX2 + }; + + data.props = { + ...data.props, + labelTransform: `translate(${ width - labelBox.width } ${ height })`, + contentTransform: `translate(${ childBox.x } ${ childBox.y })`, + loopPaths: [ + skip && skipPath(childBox, greedy), + repeat && repeatPath(childBox, greedy) + ].filter(Boolean).join('') + }; + + return data; +}; + +export default Loop; +export { layout }; diff --git a/src/rendering/style.js b/src/rendering/style.js index 4b5e474..85048fe 100644 --- a/src/rendering/style.js +++ b/src/rendering/style.js @@ -23,3 +23,8 @@ export const connectors = { fillOpacity: 0, ...strokeBase }; + +export const infoText = { + fontSize: fontSizeSmall, + fontFamily +}; diff --git a/src/rendering/types.js b/src/rendering/types.js index 6fa6d19..9907124 100644 --- a/src/rendering/types.js +++ b/src/rendering/types.js @@ -2,6 +2,7 @@ import * as SVG from 'rendering/SVG'; import * as Pin from 'rendering/Pin'; import * as Text from 'rendering/Text'; import * as Box from 'rendering/Box'; +import * as Loop from 'rendering/Loop'; import * as HorizontalLayout from 'rendering/HorizontalLayout'; import * as VerticalLayout from 'rendering/VerticalLayout'; @@ -10,6 +11,7 @@ export default { Pin, Text, Box, + Loop, HorizontalLayout, VerticalLayout };