react-occult
Version:
Layered Information Visualization based on React and D3
140 lines (122 loc) • 3.75 kB
JavaScript
import { forceCollide, forceSimulation, forceX, forceY } from 'd3-force';
import * as React from 'react';
import pointOnArcAtAngle from '../../../utils/pointOnArcAtAngle';
const swarmLayout = ({
iterations = 120,
r,
strength,
customMark,
data,
renderMode,
eventListenersGenerator,
styleFn,
projection,
classFn,
adjustedSize,
chartSize,
margin,
baseMarkProps,
rScale
}) => {
let allCalculatedPieces = [];
const columnKeys = Object.keys(data);
columnKeys.forEach((key, ordsetI) => {
const oColumn = data[key];
const anglePiece = 1 / columnKeys.length;
const oData = oColumn.pieceData;
const adjustedColumnWidth = oColumn.width;
const circleRadius =
r || Math.max(2, Math.min(5, (4 * adjustedColumnWidth) / oData.length));
const simulation = forceSimulation(oData)
.force('y', forceY(d => d.scaledValue).strength(strength || 2))
.force('x', forceX(oColumn.middle))
.force('collide', forceCollide(circleRadius))
.stop();
if (projection === 'vertical') {
simulation.force(
'y',
forceY(d => d.scaledVerticalValue).strength(strength || 2)
);
}
for (let i = 0; i < iterations; ++i) simulation.tick();
const calculatedPieces = oData.map((piece, i) => {
const renderValue = renderMode && renderMode(piece.data, i);
let xPosition = piece.x;
let yPosition = piece.y;
if (projection === 'horizontal') {
yPosition = piece.x;
xPosition = piece.y;
} else if (projection === 'radial') {
const angle = oColumn.pct_middle;
xPosition =
((piece.x - oColumn.middle) / adjustedColumnWidth) * anglePiece;
const rPosition = piece.scaledValue / 2;
const xAngle = angle + xPosition;
const baseCentroid = pointOnArcAtAngle(
[adjustedSize[0] / 2, adjustedSize[1] / 2],
xAngle,
rPosition
);
xPosition = baseCentroid[0];
yPosition = baseCentroid[1];
}
const actualCircleRadius =
typeof circleRadius === 'function'
? circleRadius(piece, i)
: circleRadius;
const eventListeners = eventListenersGenerator(piece, i);
const renderElementObject = customMark ? (
<g
key={`piece-${piece.renderKey}`}
transform={`translate(${xPosition},${yPosition})`}
>
{customMark(
{ ...piece.data, ...piece, x: xPosition, y: yPosition },
i,
{
x: xPosition,
y: yPosition,
r: circleRadius,
baseMarkProps,
renderMode,
styleFn,
classFn,
adjustedSize,
chartSize,
margin,
rScale
}
)}
</g>
) : (
{
className: classFn({ ...piece, ...piece.data }, i),
markType: 'rect',
renderMode: renderValue,
key: `piece-${piece.renderKey}`,
height: actualCircleRadius * 2,
width: actualCircleRadius * 2,
x: xPosition - actualCircleRadius,
y: yPosition - actualCircleRadius,
rx: actualCircleRadius,
ry: actualCircleRadius,
style: styleFn({ ...piece, ...piece.data }, ordsetI),
...eventListeners
}
);
const calculatedPiece = {
o: key,
xy: {
x: xPosition,
y: yPosition
},
piece,
renderElement: renderElementObject
};
return calculatedPiece;
});
allCalculatedPieces = [...allCalculatedPieces, ...calculatedPieces];
});
return allCalculatedPieces;
};
export default swarmLayout;