如何在两个组件之间画线
How to draw line between 2 components
我有一些组件(语义-ui-react 中的分段组件),我正在尝试创建一个树状组件,它将由下图中给出的线组成。
我发现了一个名为 react-lineto 的库,但我无法获得我需要的东西。这是我试过的代码。
import React, { Component } from "react";
import { SteppedLineTo } from "react-lineto";
import { Segment, Grid, GridColumn, GridRow } from "semantic-ui-react";
const style = {
delay: true,
borderColor: "#ddd",
borderStyle: "solid",
borderWidth: 3
};
class App extends Component {
render() {
return (
<Grid>
<GridRow>
<GridColumn width={1} />
<GridColumn width={14}>
<Segment raised compact className="A">
Comapny A
</Segment>
<Segment raised compact className="B" style={{ margin: "20px" }}>
Comapny B
</Segment>
<Segment raised compact className="C" style={{ margin: "40px" }}>
Comapny C
</Segment>
<Segment raised compact className="D" style={{ margin: "20px" }}>
Comapny D
</Segment>
<Segment raised compact className="E" style={{ margin: "0px" }}>
Company E
</Segment>
<SteppedLineTo
from="A"
to="B"
fromAnchor="left"
toAnchor="0 50%"
orientation="h"
{...style}
/>
<SteppedLineTo
from="A"
to="C"
fromAnchor="left"
toAnchor="0 50%"
orientation="h"
{...style}
/>
</GridColumn>
<GridColumn width={1} />
</GridRow>
</Grid>
);
}
}
export default App;
这呈现了这样的东西
我怎样才能做到这一点?除了使用这个库还有其他选择吗?也许是一个普通的 css 把戏?
很酷的挑战!
这是使用纯 React & CSS 的方法。此解决方案仅适用于没有循环的树,这似乎是您的用例。
我们的想法是从一棵树开始,用渲染框和 link 所需的所有信息丰富和展平节点,然后我们通过 CSS 和 [=12] 放置这些信息=].
希望对您有所帮助。
这里是最终的输出,框高20px,框间距10px,link偏移5px(框边和框边之间的space附上 link).
<div style="position: relative;"><div style="position: absolute; left: 0px; top: 0px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; top: 20px; left: 4px; height: 105px; border-left: 1px solid grey;"></div>Parent</div><div style="position: absolute; left: 10px; top: 30px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div><div style="position: absolute; top: 20px; left: 4px; height: 15px; border-left: 1px solid grey;"></div>Child 1</div><div style="position: absolute; left: 20px; top: 60px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 1</div><div style="position: absolute; left: 10px; top: 90px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Child 2</div><div style="position: absolute; left: 10px; top: 120px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div><div style="position: absolute; top: 20px; left: 4px; height: 45px; border-left: 1px solid grey;"></div>Child 3</div><div style="position: absolute; left: 20px; top: 150px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 2</div><div style="position: absolute; left: 20px; top: 180px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 3</div></div>
下面的代码
import React from 'react'
// Example:
//
// const topLevelNode = {
// text: 'Parent',
// children: [
// {
// text: 'Child 1',
// children: [
// {
// text: 'Grandchild 1',
// },
// ],
// },
// {
// text: 'Child 2',
// },
// ],
// }
//
// flatten(enrich(topLevelNode))
//
// [
// { text: 'Parent', depth: 0, descendentsCount: 3, heightDiffWithLastDirectChild: 3 },
// { text: 'Child 1', depth: 1, descendentsCount: 1, heightDiffWithLastDirectChild: 1 },
// { text: 'Grandchild 1', depth: 2, descendentsCount: 0, heightDiffWithLastDirectChild: 0 },
// { text: 'Child 2', depth: 1, descendentsCount: 0, heightDiffWithLastDirectChild: 0 },
// ]
// Enrich nodes with information needed for render
const enrich = (node, depthOffset = 0) => {
if (!node.children) {
return {
...node,
depth: depthOffset,
descendentsCount: 0,
heightDiffWithLastDirectChild: 0,
}
}
const enrichedChildren = node.children.map((child) => enrich(child, depthOffset + 1))
const descendentsCount = node.children.length + enrichedChildren.reduce(
(acc, enrichedChild) => acc + enrichedChild.descendentsCount,
0,
)
const heightDiffWithLastDirectChild = descendentsCount - enrichedChildren[node.children.length - 1].descendentsCount
return {
...node,
children: enrichedChildren,
depth: depthOffset,
descendentsCount,
heightDiffWithLastDirectChild,
}
}
// Flatten nodes with a depth first search
const flatten = (node) => {
const { children = [], ...nodeWithoutChildren } = node
return [
{ ...nodeWithoutChildren },
...children.map((childNode) => flatten(childNode)).flat(),
]
}
const boxHeight = 20
const boxGap = 10
const linkPositionOffset = 5
const LinkedBox = ({ node, order }) => (
<div
style={{
position: 'absolute',
left: `${node.depth * boxGap}px`,
top: `${order * (boxHeight + boxGap)}px`,
height: `${boxHeight}px`,
border: '1px solid grey',
borderRadius: '2px',
padding: '10px',
boxSizing: 'border-box',
display: 'flex',
alignItems: 'center',
}}
>
{node.depth > 0 && (
<div
style={{
position: 'absolute',
left: `-${boxGap - linkPositionOffset}px`,
top: `${linkPositionOffset - 1}px`,
width: `${boxGap - linkPositionOffset}px`,
borderTop: 'solid 1px grey',
}}
/>
)}
{node.heightDiffWithLastDirectChild > 0 && (
<div
style={{
position: 'absolute',
top: `${boxHeight}px`,
left: `${linkPositionOffset - 1}px`,
height: `${boxGap + (node.heightDiffWithLastDirectChild - 1) * (boxGap + boxHeight) + linkPositionOffset}px`,
borderLeft: 'solid 1px grey',
}}
/>
)}
{node.text}
</div>
)
const Diagram = ({ topLevelNode }) => (
<div style={{ position: 'relative' }}>
{flatten(enrich(topLevelNode)).map((enrichedNode, order) => (
<LinkedBox node={enrichedNode} order={order} key={JSON.stringify(enrichedNode)} />
))}
</div>
)
export default () => (
<Diagram
topLevelNode={{
text: 'Parent',
children: [
{
text: 'Child 1',
children: [
{ text: 'Grandchild 1' },
],
},
{ text: 'Child 2' },
{
text: 'Child 3',
children: [
{ text: 'Grandchild 2' },
{ text: 'Grandchild 3' },
],
},
],
}}
/>
)
你可以使用 react-xarrows。
import React, {useRef} from "react";
import Xarrow from "react-xarrows";
const boxStyle = {
border: '1px #999 solid',
borderRadius: '10px',
textAlign: 'center',
width: '100px',
height: '30px',
color: 'black',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
};
export const Test = () => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '500px',
position: 'absolute',
}}>
<div id="elem1" style={boxStyle}>
elem1
</div>
<div id="elem2" style={{ ...boxStyle, position: 'relative', left: '100px' }}>
elem2
</div>
<div id="elem3" style={{ ...boxStyle, position: 'relative', left: '100px' }}>
elem2
</div>
<Xarrow start="elem1" end="elem2" showHead={false} startAnchor={'bottom'} path={'grid'} />
<Xarrow start="elem1" end="elem3" showHead={false} startAnchor={'bottom'} path={'grid'} />
</div>
);
};
会给你结果
如果您希望线路从左侧连接,您可以使用自定义偏移它 startAnchor
属性:
export const Test = () => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '500px',
position: 'absolute',
}}>
<div id="elem1" style={boxStyle}>
elem1
</div>
<div id="elem2" style={{ ...boxStyle, position: 'relative', left: '100px' }}>
elem2
</div>
<div id="elem3" style={{ ...boxStyle, position: 'relative', left: '100px' }}>
elem2
</div>
<Xarrow
start="elem1"
end="elem2"
showHead={false}
startAnchor={{ position: 'bottom', offset: { rightness: -30 } }}
path={'grid'}
/>
<Xarrow
start="elem1"
end="elem3"
showHead={false}
startAnchor={{ position: 'bottom', offset: { rightness: -30 } }}
path={'grid'}
/>
</div>
);
};
会给你结果:
我有一些组件(语义-ui-react 中的分段组件),我正在尝试创建一个树状组件,它将由下图中给出的线组成。
我发现了一个名为 react-lineto 的库,但我无法获得我需要的东西。这是我试过的代码。
import React, { Component } from "react";
import { SteppedLineTo } from "react-lineto";
import { Segment, Grid, GridColumn, GridRow } from "semantic-ui-react";
const style = {
delay: true,
borderColor: "#ddd",
borderStyle: "solid",
borderWidth: 3
};
class App extends Component {
render() {
return (
<Grid>
<GridRow>
<GridColumn width={1} />
<GridColumn width={14}>
<Segment raised compact className="A">
Comapny A
</Segment>
<Segment raised compact className="B" style={{ margin: "20px" }}>
Comapny B
</Segment>
<Segment raised compact className="C" style={{ margin: "40px" }}>
Comapny C
</Segment>
<Segment raised compact className="D" style={{ margin: "20px" }}>
Comapny D
</Segment>
<Segment raised compact className="E" style={{ margin: "0px" }}>
Company E
</Segment>
<SteppedLineTo
from="A"
to="B"
fromAnchor="left"
toAnchor="0 50%"
orientation="h"
{...style}
/>
<SteppedLineTo
from="A"
to="C"
fromAnchor="left"
toAnchor="0 50%"
orientation="h"
{...style}
/>
</GridColumn>
<GridColumn width={1} />
</GridRow>
</Grid>
);
}
}
export default App;
这呈现了这样的东西
我怎样才能做到这一点?除了使用这个库还有其他选择吗?也许是一个普通的 css 把戏?
很酷的挑战!
这是使用纯 React & CSS 的方法。此解决方案仅适用于没有循环的树,这似乎是您的用例。
我们的想法是从一棵树开始,用渲染框和 link 所需的所有信息丰富和展平节点,然后我们通过 CSS 和 [=12] 放置这些信息=].
希望对您有所帮助。
这里是最终的输出,框高20px,框间距10px,link偏移5px(框边和框边之间的space附上 link).
<div style="position: relative;"><div style="position: absolute; left: 0px; top: 0px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; top: 20px; left: 4px; height: 105px; border-left: 1px solid grey;"></div>Parent</div><div style="position: absolute; left: 10px; top: 30px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div><div style="position: absolute; top: 20px; left: 4px; height: 15px; border-left: 1px solid grey;"></div>Child 1</div><div style="position: absolute; left: 20px; top: 60px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 1</div><div style="position: absolute; left: 10px; top: 90px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Child 2</div><div style="position: absolute; left: 10px; top: 120px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div><div style="position: absolute; top: 20px; left: 4px; height: 45px; border-left: 1px solid grey;"></div>Child 3</div><div style="position: absolute; left: 20px; top: 150px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 2</div><div style="position: absolute; left: 20px; top: 180px; height: 20px; border: 1px solid grey; border-radius: 2px; padding: 10px; box-sizing: border-box; display: flex; align-items: center;"><div style="position: absolute; left: -5px; top: 4px; width: 5px; border-top: 1px solid grey;"></div>Grandchild 3</div></div>
下面的代码
import React from 'react'
// Example:
//
// const topLevelNode = {
// text: 'Parent',
// children: [
// {
// text: 'Child 1',
// children: [
// {
// text: 'Grandchild 1',
// },
// ],
// },
// {
// text: 'Child 2',
// },
// ],
// }
//
// flatten(enrich(topLevelNode))
//
// [
// { text: 'Parent', depth: 0, descendentsCount: 3, heightDiffWithLastDirectChild: 3 },
// { text: 'Child 1', depth: 1, descendentsCount: 1, heightDiffWithLastDirectChild: 1 },
// { text: 'Grandchild 1', depth: 2, descendentsCount: 0, heightDiffWithLastDirectChild: 0 },
// { text: 'Child 2', depth: 1, descendentsCount: 0, heightDiffWithLastDirectChild: 0 },
// ]
// Enrich nodes with information needed for render
const enrich = (node, depthOffset = 0) => {
if (!node.children) {
return {
...node,
depth: depthOffset,
descendentsCount: 0,
heightDiffWithLastDirectChild: 0,
}
}
const enrichedChildren = node.children.map((child) => enrich(child, depthOffset + 1))
const descendentsCount = node.children.length + enrichedChildren.reduce(
(acc, enrichedChild) => acc + enrichedChild.descendentsCount,
0,
)
const heightDiffWithLastDirectChild = descendentsCount - enrichedChildren[node.children.length - 1].descendentsCount
return {
...node,
children: enrichedChildren,
depth: depthOffset,
descendentsCount,
heightDiffWithLastDirectChild,
}
}
// Flatten nodes with a depth first search
const flatten = (node) => {
const { children = [], ...nodeWithoutChildren } = node
return [
{ ...nodeWithoutChildren },
...children.map((childNode) => flatten(childNode)).flat(),
]
}
const boxHeight = 20
const boxGap = 10
const linkPositionOffset = 5
const LinkedBox = ({ node, order }) => (
<div
style={{
position: 'absolute',
left: `${node.depth * boxGap}px`,
top: `${order * (boxHeight + boxGap)}px`,
height: `${boxHeight}px`,
border: '1px solid grey',
borderRadius: '2px',
padding: '10px',
boxSizing: 'border-box',
display: 'flex',
alignItems: 'center',
}}
>
{node.depth > 0 && (
<div
style={{
position: 'absolute',
left: `-${boxGap - linkPositionOffset}px`,
top: `${linkPositionOffset - 1}px`,
width: `${boxGap - linkPositionOffset}px`,
borderTop: 'solid 1px grey',
}}
/>
)}
{node.heightDiffWithLastDirectChild > 0 && (
<div
style={{
position: 'absolute',
top: `${boxHeight}px`,
left: `${linkPositionOffset - 1}px`,
height: `${boxGap + (node.heightDiffWithLastDirectChild - 1) * (boxGap + boxHeight) + linkPositionOffset}px`,
borderLeft: 'solid 1px grey',
}}
/>
)}
{node.text}
</div>
)
const Diagram = ({ topLevelNode }) => (
<div style={{ position: 'relative' }}>
{flatten(enrich(topLevelNode)).map((enrichedNode, order) => (
<LinkedBox node={enrichedNode} order={order} key={JSON.stringify(enrichedNode)} />
))}
</div>
)
export default () => (
<Diagram
topLevelNode={{
text: 'Parent',
children: [
{
text: 'Child 1',
children: [
{ text: 'Grandchild 1' },
],
},
{ text: 'Child 2' },
{
text: 'Child 3',
children: [
{ text: 'Grandchild 2' },
{ text: 'Grandchild 3' },
],
},
],
}}
/>
)
你可以使用 react-xarrows。
import React, {useRef} from "react";
import Xarrow from "react-xarrows";
const boxStyle = {
border: '1px #999 solid',
borderRadius: '10px',
textAlign: 'center',
width: '100px',
height: '30px',
color: 'black',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
};
export const Test = () => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '500px',
position: 'absolute',
}}>
<div id="elem1" style={boxStyle}>
elem1
</div>
<div id="elem2" style={{ ...boxStyle, position: 'relative', left: '100px' }}>
elem2
</div>
<div id="elem3" style={{ ...boxStyle, position: 'relative', left: '100px' }}>
elem2
</div>
<Xarrow start="elem1" end="elem2" showHead={false} startAnchor={'bottom'} path={'grid'} />
<Xarrow start="elem1" end="elem3" showHead={false} startAnchor={'bottom'} path={'grid'} />
</div>
);
};
会给你结果
如果您希望线路从左侧连接,您可以使用自定义偏移它 startAnchor
属性:
export const Test = () => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '500px',
position: 'absolute',
}}>
<div id="elem1" style={boxStyle}>
elem1
</div>
<div id="elem2" style={{ ...boxStyle, position: 'relative', left: '100px' }}>
elem2
</div>
<div id="elem3" style={{ ...boxStyle, position: 'relative', left: '100px' }}>
elem2
</div>
<Xarrow
start="elem1"
end="elem2"
showHead={false}
startAnchor={{ position: 'bottom', offset: { rightness: -30 } }}
path={'grid'}
/>
<Xarrow
start="elem1"
end="elem3"
showHead={false}
startAnchor={{ position: 'bottom', offset: { rightness: -30 } }}
path={'grid'}
/>
</div>
);
};
会给你结果: