react-occult
Version:
Layered Information Visualization based on React and D3
145 lines (133 loc) • 3.81 kB
JavaScript
import { Mark } from 'semiotic-mark';
import * as React from 'react';
import { d as glyphD } from 'd3-glyphedge';
import { linkHorizontal, linkVertical } from 'd3-shape';
const sigmoidLinks = {
horizontal: linkHorizontal()
.x(d => d.x)
.y(d => d.y),
vertical: linkVertical()
.x(d => d.x)
.y(d => d.y),
radial: glyphD.lineArc
};
const genericLineGenerator = d =>
`M${d.source.x},${d.source.y}L${d.target.x},${d.target.y}`;
function sankeyEdgeSort(a, b, direction) {
if (a.circular && !b.circular) return -1;
if (b.circular && !a.circular) return 1;
const first = direction === 'down' ? 'y' : 'x';
const second = direction === 'down' ? 'x' : 'y';
return a.source[first] === b.source[first]
? a.sankeyWidth === b.sankeyWidth
? a.source[second] - b.source[second]
: b.sankeyWidth - a.sankeyWidth
: a.source[first] - b.source[first];
}
const customEdgeHashD = {
curve: (d, projection = 'vertical') => sigmoidLinks[projection](d),
linearc: d => glyphD.lineArc(d),
ribbon: d => glyphD.ribbon(d, d.width),
arrowhead: d =>
glyphD.arrowHead(d, d.target.nodeSize, d.width, d.width * 1.5),
halfarrow: d =>
glyphD.halfArrow(d, d.target.nodeSize, d.width, d.width * 1.5),
nail: d => glyphD.nail(d, d.source.nodeSize),
comet: d => glyphD.comet(d, d.target.nodeSize),
taffy: d =>
glyphD.taffy(
d,
d.source.nodeSize / 2,
d.target.nodeSize / 2,
(d.source.nodeSize + d.target.nodeSize) / 4
)
};
const drawEdges = ({
data: baseData,
renderKeyFn,
customMark,
styleFn,
classFn,
renderMode,
useCanvas,
type,
baseMarkProps,
networkType,
direction,
projection
}) => {
const data =
networkType === 'sankey'
? baseData.sort((a, b) => sankeyEdgeSort(a, b, direction))
: baseData;
let dGenerator = genericLineGenerator;
const svgPipeline = [];
const canvasPipeline = [];
if (customMark) {
// CUSTOM MARK IMPLEMENTATION
data.forEach((d, i) => {
const renderedCustomMark = customMark({
d,
i,
renderKeyFn,
styleFn,
classFn,
renderMode,
key: renderKeyFn ? renderKeyFn(d, i) : `edge-${i}`,
className: `${classFn(d, i)} edge`,
transform: `translate(${d.x},${d.y})`,
baseMarkProps
});
if (
renderedCustomMark &&
renderedCustomMark.props &&
(renderedCustomMark.props.markType !== 'path' ||
renderedCustomMark.props.d)
) {
svgPipeline.push(renderedCustomMark);
}
});
} else {
if (type) {
if (typeof type === 'function') {
dGenerator = type;
} else if (customEdgeHashD[type]) {
dGenerator = d => customEdgeHashD[type](d, projection);
}
}
data.forEach((d, i) => {
const renderedD = dGenerator(d);
if (renderedD && useCanvas === true) {
const canvasEdge = {
baseClass: 'frame-piece',
tx: d.x,
ty: d.y,
d,
i,
markProps: { markType: 'path', d: renderedD },
styleFn,
renderFn: renderMode,
classFn
};
canvasPipeline.push(canvasEdge);
} else if (renderedD) {
svgPipeline.push(
<Mark
{...baseMarkProps}
key={renderKeyFn ? renderKeyFn(d, i) : `edge-${i}`}
markType="path"
renderMode={renderMode ? renderMode(d, i) : undefined}
className={`${classFn(d)} edge`}
d={renderedD}
style={styleFn(d, i)}
tabIndex={-1}
role="img"
aria-label={`connection from ${d.source.id} to ${d.target.id}`}
/>
);
}
});
}
return { svgPipeline, canvasPipeline };
};
export default drawEdges;