react-occult
Version:
Layered Information Visualization based on React and D3
1,155 lines (1,014 loc) • 32.4 kB
JavaScript
import React from 'react';
import { scaleLinear, scaleOrdinal } from 'd3-scale';
import keyAndObjectifyBarData from './keyAndObjectifyBarData';
import { getAdjustedPositionSize } from '../utils';
import { extent, max, min, sum } from 'd3-array';
import { nest } from 'd3-collection';
import { arc } from 'd3-shape';
import calculateMargin from '../utils/calculateMargin';
import { objectifyType, stringToArrayFn, stringToFn } from '../../utils';
import pointOnArcAtAngle from '../../utils/pointOnArcAtAngle';
import toPipeline from '../../pipeline/ordinal/toPipeline';
import drawSummaries from '../../axis/drawSummaries';
import orFrameAxisGenerator from '../../axis/orFrameAxisGenerator';
const genericFunction = value => () => value;
const midMod = d => (d.middle ? d.middle : 0);
const zeroFunction = genericFunction(0);
const twoPI = Math.PI * 2;
const defaultOverflow = { top: 0, bottom: 0, left: 0, right: 0 };
const BAR_PERCENT = 'barpercent';
const TIMELINE = 'timeline';
const CLUSTER_BAR = 'clusterbar';
const BAR = 'bar';
const NONE = 'none';
const ORDINAL_POINT = 'ordinalpoint';
const PROJECTION_VERTICAL = 'vertical';
const PROJECTION_RADIAL = 'radial';
const PROJECTION_HORIZONTAL = 'horizontal';
const naturalLanguageTypes = {
bar: { items: 'bar', chart: 'bar chart' },
clusterbar: { items: 'bar', chart: 'grouped bar chart' },
swarm: { items: 'point', chart: 'swarm plot' },
ordinalpoint: { items: 'point', chart: 'point plot' },
timeline: { items: 'bar', chart: 'timeline' }
};
const computeOrdinalFrameData = props => {
const {
width,
height,
title: baseTitle,
margin: baseMargin,
customClickBehavior,
customHoverBehavior,
customDoubleClickBehavior,
hoverAnnotation,
axesDefs: baseAxis,
plotChildren
} = props;
const singleOrdinalPlot = plotChildren[0];
const {
// ordinal plot
data,
oScaleType,
rScaleType,
dynamicColumnWidth,
pixelColumnWidth,
oLabel,
projection,
multiAxis,
renderMode,
oPadding: padding = 0,
oAccessor: baseOAccessor,
rAccessor: baseRAccessor,
oExtent: baseOExtent,
pieceIDAccessor: basePieceIDAccessor,
pieceHoverAnnotation,
baseMarkProps = {},
style: baseStyle,
rExtent: baseRExtent,
renderKey: baseRenderKey,
oSort,
pieceClass: basePieceClass,
pieceUseCanvas,
connectorStyle: baseConnectorStyle,
connectorType: baseConnectorType,
connectorUseCanvas,
connectorClass,
connectorRenderMode,
summaryStyle: baseSummaryStyle,
summaryClass: baseSummaryClass,
summaryPosition: baseSummaryPosition,
summaryRenderMode,
summaryHoverAnnotation,
summaryType: baseSummaryType,
summaryUseCanvas
} = singleOrdinalPlot.props;
let pieceType = singleOrdinalPlot.type.name.toLowerCase();
const _mappedMiddles = (oScale, middleMax, padding) => {
const oScaleDomainValues = oScale.domain();
const mappedMiddles = {};
oScaleDomainValues.forEach((p, q) => {
const base = oScale(p) - padding;
const next = oScaleDomainValues[q + 1]
? oScale(oScaleDomainValues[q + 1])
: middleMax;
const diff = (next - base) / 2;
mappedMiddles[p] = base + diff;
});
return mappedMiddles;
};
const connectorStyle = stringToFn(baseConnectorStyle, () => ({}), true);
const summaryStyle = stringToFn(baseSummaryStyle, () => ({}), true);
const pieceStyle = stringToFn(baseStyle, () => ({}), true);
const pieceClass = stringToFn(basePieceClass, () => '', true);
const summaryClass = stringToFn(baseSummaryClass, () => '', true);
const summaryPosition = baseSummaryPosition || (position => position);
const title =
typeof baseTitle === 'object' &&
!React.isValidElement(baseTitle) &&
baseTitle !== null
? baseTitle
: { title: baseTitle, orient: 'top' };
const pieceIDAccessor = stringToFn(basePieceIDAccessor, () => '');
// Paper variables
const summaryType = objectifyType(baseSummaryType);
const oAccessor = stringToArrayFn(baseOAccessor, d => d.renderKey);
const rAccessor = stringToArrayFn(baseRAccessor, d => d.value || 1);
const renderKey = stringToFn(baseRenderKey, (d, i) => i);
const eventListenersGenerator = () => ({});
let oLabels;
const projectedColumns = {};
// --------------- same as xy - start
const size = [width, height];
// --------------- same as xy - close
const originalRAccessor = Array.isArray(baseRAccessor)
? baseRAccessor
: [baseRAccessor];
const originalOAccessor = Array.isArray(baseOAccessor)
? baseOAccessor
: [baseOAccessor];
const { allData, multiExtents } = keyAndObjectifyBarData({
data,
renderKey,
oAccessor,
rAccessor,
originalRAccessor,
originalOAccessor,
multiAxis
});
let arrayWrappedAxis;
if (Array.isArray(baseAxis)) {
arrayWrappedAxis = baseAxis.map(axisFnOrObject =>
typeof axisFnOrObject === 'function'
? axisFnOrObject({ size })
: axisFnOrObject
);
} else if (baseAxis) {
arrayWrappedAxis = [baseAxis].map(axisFnOrObject =>
typeof axisFnOrObject === 'function'
? axisFnOrObject({ size })
: axisFnOrObject
);
}
if (multiExtents && baseAxis) {
arrayWrappedAxis.forEach((d, i) => {
d.extentOverride = multiExtents[i];
});
}
const margin = calculateMargin({
margin: baseMargin,
axes: arrayWrappedAxis,
title,
oLabel,
projection,
size
});
const { adjustedPosition, adjustedSize } = getAdjustedPositionSize({
size: [width, height],
margin
});
const oExtentSettings =
baseOExtent === undefined || Array.isArray(baseOExtent)
? { extent: baseOExtent }
: baseOExtent;
const calculatedOExtent = allData.reduce((p, c) => {
const baseOValue = c.column;
const oValue = baseOValue !== undefined ? String(baseOValue) : baseOValue;
if (p.indexOf(oValue) === -1) {
p.push(oValue);
}
return p;
}, []);
let oExtent = oExtentSettings.extent || calculatedOExtent;
if (pieceType === BAR_PERCENT) {
const oExtentSums = oExtent
.map(d =>
allData
.filter(p => String(p.column) === d)
.reduce((p, c) => p + c.value, 0)
)
.reduce((p, c, i) => {
p[oExtent[i]] = c;
return p;
}, {});
allData.forEach(d => {
d.value = (oExtentSums[d.column] && d.value / oExtentSums[d.column]) || 0;
});
pieceType = BAR;
}
if (pixelColumnWidth) {
if (projection === PROJECTION_RADIAL) {
console.error('pixelColumnWidth is not honored in radial mode');
} else if (projection === PROJECTION_VERTICAL) {
adjustedSize[0] = oExtent.length * pixelColumnWidth;
} else {
adjustedSize[1] = oExtent.length * pixelColumnWidth;
}
}
const oDomain = (projection === PROJECTION_VERTICAL && [
0,
adjustedSize[0]
]) || [0, adjustedSize[1]];
const cwHash = oExtent.reduce(
(p, c) => {
p[c] = (1 / oExtent.length) * oDomain[1];
p.total += p[c];
return p;
},
{ total: 0 }
);
const oScale = dynamicColumnWidth ? scaleOrdinal() : oScaleType;
oScale.domain(oExtent);
let maxColumnValues;
if (dynamicColumnWidth) {
let columnValueCreator;
if (typeof dynamicColumnWidth === 'string') {
columnValueCreator = d => sum(d.map(p => p.data[dynamicColumnWidth]));
} else {
columnValueCreator = d => dynamicColumnWidth(d.map(p => p.data));
}
const thresholdDomain = [0];
maxColumnValues = 0;
const columnValues = [];
oExtent.forEach(d => {
const oValues = allData.filter(p => p.column === d);
const columnValue = columnValueCreator(oValues);
columnValues.push(columnValue);
maxColumnValues += columnValue;
});
cwHash.total = 0;
oExtent.forEach((d, i) => {
const oValue = columnValues[i];
const stepValue = (oValue / maxColumnValues) * (oDomain[1] - oDomain[0]);
cwHash[d] = stepValue;
cwHash.total += stepValue;
if (i !== oExtent.length - 1) {
thresholdDomain.push(stepValue + thresholdDomain[i]);
}
});
oScale.range(thresholdDomain);
} else {
oScale.range(oDomain);
}
const rExtentSettings =
baseRExtent === undefined || Array.isArray(baseRExtent)
? { extent: baseRExtent, onChange: undefined, includeAnnotations: false }
: baseRExtent;
let rExtent = rExtentSettings.extent;
let subZeroRExtent = [0, 0];
if (pieceType === BAR && summaryType.type && summaryType.type !== NONE) {
pieceType = NONE;
}
const annotationsForExtent = [];
// todo: annotations
if (rExtentSettings.includeAnnotations && annotations) {
rAccessor.forEach(actualRAccessor => {
annotations.forEach((annotation, annotationIndex) => {
const r = actualRAccessor(annotation, annotationIndex);
if (isFinite(r)) {
annotationsForExtent.push(r);
}
});
});
}
if (pieceType === TIMELINE) {
const rData = allData.map(d => d.value);
const leftExtent = extent(rData.map(d => d[0]));
const rightExtent = extent(rData.map(d => d[1]));
rExtent = extent([...leftExtent, ...rightExtent, ...annotationsForExtent]);
} else if (pieceType !== BAR) {
rExtent = extent([...allData.map(d => d.value), ...annotationsForExtent]);
} else {
const positiveData = allData.filter(d => d.value >= 0);
const negativeData = allData.filter(d => d.value < 0);
const nestedPositiveData = nest()
.key(d => d.column)
.rollup(leaves => sum(leaves.map(d => d.value)))
.entries(positiveData);
const nestedNegativeData = nest()
.key(d => d.column)
.rollup(leaves => sum(leaves.map(d => d.value)))
.entries(negativeData);
const positiveAnnotations = annotationsForExtent.filter(d => d > 0);
rExtent = [
0,
nestedPositiveData.length === 0 && positiveAnnotations.length === 0
? 0
: Math.max(
max([
...nestedPositiveData.map(d => d.value),
...positiveAnnotations
]),
0
)
];
const negativeAnnotations = annotationsForExtent.filter(d => d < 0);
subZeroRExtent = [
0,
nestedNegativeData.length === 0
? 0
: Math.min(
min([
...nestedNegativeData.map(d => d.value),
...negativeAnnotations
]),
0
)
];
rExtent = [subZeroRExtent[1], rExtent[1]];
}
if ((pieceType === CLUSTER_BAR || multiAxis) && rExtent[0] > 0) {
rExtent[0] = 0;
}
const calculatedRExtent = rExtent;
if (
rExtentSettings.extent &&
rExtentSettings.extent[0] !== undefined &&
rExtentSettings.extent[1] !== undefined
) {
rExtent = rExtentSettings.extent;
} else {
if (
rExtentSettings.extent &&
rExtentSettings.extent[1] !== undefined &&
rExtentSettings.extent[0] === undefined
) {
rExtent[1] = rExtentSettings.extent[1];
}
if (
rExtentSettings.extent &&
rExtentSettings.extent[0] !== undefined &&
rExtentSettings.extent[1] === undefined
) {
rExtent[0] = rExtentSettings.extent[0];
}
}
if (
props.invertR ||
(rExtentSettings.extent &&
rExtentSettings.extent[0] > rExtentSettings.extent[1])
) {
rExtent = [rExtent[1], rExtent[0]];
}
const nestedPieces = {};
nest()
.key(d => d.column)
.entries(allData)
.forEach(d => {
nestedPieces[d.key] = d.values;
});
if (oSort !== undefined) {
oExtent = oExtent.sort((a, b) =>
oSort(
a,
b,
nestedPieces[a].map(d => d.data),
nestedPieces[b].map(d => d.data)
)
);
oScale.domain(oExtent);
}
const rDomain = (projection === PROJECTION_VERTICAL && [
0,
adjustedSize[1]
]) || [0, adjustedSize[0]];
const castRScaleType = rScaleType;
const instantiatedRScaleType = rScaleType.domain
? rScaleType
: castRScaleType();
const rScale = instantiatedRScaleType
.copy()
.domain(rExtent)
.range(rDomain);
const rScaleReverse = instantiatedRScaleType
.copy()
.domain(rDomain)
.range(rDomain.reverse());
const rScaleVertical = instantiatedRScaleType
.copy()
.domain(rExtent)
.range(rDomain);
const columnWidth = cwHash ? 0 : oScale.bandwidth();
let pieceData = [];
let mappedMiddleSize = adjustedSize[1];
if (projection === PROJECTION_VERTICAL) {
mappedMiddleSize = adjustedSize[0];
}
const mappedMiddles = _mappedMiddles(oScale, mappedMiddleSize, padding);
pieceData = oExtent.map(d => (nestedPieces[d] ? nestedPieces[d] : []));
const zeroValue =
projection === PROJECTION_VERTICAL ? rScaleReverse(rScale(0)) : rScale(0);
oExtent.forEach((o, i) => {
projectedColumns[o] = {
name: o,
padding,
pieceData: pieceData[i],
pieces: pieceData[i]
};
projectedColumns[o].x = oScale(o) + padding / 2;
projectedColumns[o].y = 0;
projectedColumns[o].middle = mappedMiddles[o] + padding / 2;
let negativeOffset = zeroValue;
let positiveOffset = zeroValue;
let negativeBaseValue = 0;
let positiveBaseValue = 0;
projectedColumns[o].pieceData.forEach(piece => {
let valPosition;
if (pieceType === TIMELINE) {
piece.scaledValue = rScale(piece.value[0]);
piece.scaledEndValue = rScale(piece.value[1]);
piece.scaledVerticalValue = rScaleVertical(piece.value[0]);
} else if (pieceType !== BAR && pieceType !== CLUSTER_BAR) {
piece.scaledValue = rScale(piece.value);
piece.scaledVerticalValue = rScaleVertical(piece.value);
} else if (pieceType === CLUSTER_BAR) {
valPosition =
projection === PROJECTION_VERTICAL
? rScaleReverse(rScale(piece.value))
: rScale(piece.value);
piece.scaledValue = Math.abs(zeroValue - valPosition);
}
piece.x = projectedColumns[o].x;
if (piece.value >= 0) {
if (pieceType === BAR) {
piece.scaledValue =
projection === PROJECTION_VERTICAL
? positiveOffset -
rScaleReverse(rScale(positiveBaseValue + piece.value))
: rScale(positiveBaseValue + piece.value) - positiveOffset;
positiveBaseValue += piece.value;
}
piece.base = zeroValue;
piece.bottom = pieceType === BAR ? positiveOffset : 0;
piece.middle = piece.scaledValue / 2 + positiveOffset;
positiveOffset =
projection === PROJECTION_VERTICAL
? positiveOffset - piece.scaledValue
: positiveOffset + piece.scaledValue;
piece.negative = false;
} else {
if (pieceType === BAR) {
piece.scaledValue =
projection === PROJECTION_VERTICAL
? Math.abs(rScale(piece.value) - rScale(0))
: Math.abs(rScale(piece.value) - zeroValue);
negativeBaseValue += piece.value;
}
piece.base = zeroValue;
piece.bottom = pieceType === BAR ? negativeOffset : 0;
piece.middle = negativeOffset - piece.scaledValue / 2;
negativeOffset =
projection === PROJECTION_VERTICAL
? negativeOffset + piece.scaledValue
: negativeOffset - piece.scaledValue;
piece.negative = true;
}
});
if (cwHash) {
projectedColumns[o].width = cwHash[o] - padding;
if (props.ordinalAlign === 'center') {
if (i === 0) {
projectedColumns[o].x =
projectedColumns[o].x - projectedColumns[o].width / 2;
projectedColumns[o].middle =
projectedColumns[o].middle - projectedColumns[o].width / 2;
} else {
projectedColumns[o].x =
projectedColumns[oExtent[i - 1]].x +
projectedColumns[oExtent[i - 1]].width;
projectedColumns[o].middle =
projectedColumns[o].x + projectedColumns[o].width / 2;
}
}
projectedColumns[o].pct = cwHash[o] / cwHash.total;
projectedColumns[o].pct_start =
(projectedColumns[o].x - oDomain[0]) / cwHash.total;
projectedColumns[o].pct_padding = padding / cwHash.total;
projectedColumns[o].pct_middle =
(projectedColumns[o].middle - oDomain[0]) / cwHash.total;
} else {
projectedColumns[o].width = columnWidth - padding;
if (props.ordinalAlign === 'center') {
projectedColumns[o].x =
projectedColumns[o].x - projectedColumns[o].width / 2;
projectedColumns[o].middle =
projectedColumns[o].middle - projectedColumns[o].width / 2;
}
projectedColumns[o].pct = columnWidth / adjustedSize[1];
projectedColumns[o].pct_start =
(projectedColumns[o].x - oDomain[0]) / adjustedSize[1];
projectedColumns[o].pct_padding = padding / adjustedSize[1];
projectedColumns[o].pct_middle =
(projectedColumns[o].middle - oDomain[0]) / adjustedSize[1];
}
});
const labelArray = [];
const pieArcs = [];
const labelSettings =
typeof oLabel === 'object'
? Object.assign({ label: true, padding: 5 }, oLabel)
: { orient: 'default', label: oLabel, padding: 5 };
if (oLabel || hoverAnnotation) {
const { offsetAngle, angleRange } = singleOrdinalPlot.props;
const offsetPct = (offsetAngle && offsetAngle / 360) || 0;
const rangePct = (angleRange && angleRange.map(d => d / 360)) || [0, 1];
const rangeMod = rangePct[1] - rangePct[0];
const adjustedPct =
rangeMod < 1
? scaleLinear()
.domain([0, 1])
.range(rangePct)
: d => d;
oExtent.forEach(d => {
const arcGenerator = arc()
.innerRadius(0)
.outerRadius(rScale.range()[1] / 2);
const angle = projectedColumns[d].pct * rangeMod;
const startAngle = adjustedPct(projectedColumns[d].pct_start + offsetPct);
const endAngle = startAngle + angle;
const midAngle = startAngle + angle / 2;
const markD = arcGenerator({
startAngle: startAngle * twoPI,
endAngle: endAngle * twoPI
});
const translate = [adjustedSize[0] / 2, adjustedSize[1] / 2];
const centroid = arcGenerator.centroid({
startAngle: startAngle * twoPI,
endAngle: endAngle * twoPI
});
const addedPadding =
centroid[1] > 0 &&
(!labelSettings.orient ||
labelSettings.orient === 'default' ||
labelSettings.orient === 'edge')
? 8
: 0;
const outerPoint = pointOnArcAtAngle(
[0, 0],
midAngle,
rScale.range()[1] / 2 + labelSettings.padding + addedPadding
);
pieArcs.push({
startAngle,
endAngle,
midAngle,
markD,
translate,
centroid,
outerPoint
});
});
}
if (oLabel) {
let labelingFn;
if (labelSettings.label === true) {
const labelStyle = {
textAnchor: 'middle'
};
if (
projection === PROJECTION_HORIZONTAL &&
labelSettings.orient === 'right'
) {
labelStyle.textAnchor = 'start';
} else if (projection === PROJECTION_HORIZONTAL) {
labelStyle.textAnchor = 'end';
}
labelingFn = (d, p, i) => {
const additionalStyle = {};
let transformRotate;
if (
projection === PROJECTION_RADIAL &&
labelSettings.orient === 'stem'
) {
transformRotate = `rotate(${
pieArcs[i].outerPoint[0] < 0
? pieArcs[i].midAngle * 360 + 90
: pieArcs[i].midAngle * 360 - 90
})`;
} else if (
projection === PROJECTION_RADIAL &&
labelSettings.orient !== 'center'
) {
transformRotate = `rotate(${
pieArcs[i].outerPoint[1] < 0
? pieArcs[i].midAngle * 360
: pieArcs[i].midAngle * 360 + 180
})`;
}
if (
projection === PROJECTION_RADIAL &&
labelSettings.orient === 'stem' &&
((pieArcs[i].outerPoint[0] > 0 && labelSettings.padding < 0) ||
(pieArcs[i].outerPoint[0] < 0 && labelSettings.padding >= 0))
) {
additionalStyle.textAnchor = 'end';
} else if (
projection === PROJECTION_RADIAL &&
labelSettings.orient === 'stem'
) {
additionalStyle.textAnchor = 'start';
}
return (
<text
{...labelStyle}
{...additionalStyle}
transform={transformRotate}
>
{d}
</text>
);
};
} else if (typeof labelSettings.label === 'function') {
labelingFn = labelSettings.label;
}
oExtent.forEach((d, i) => {
let xPosition = projectedColumns[d].middle;
let yPosition = 0;
if (projection === PROJECTION_HORIZONTAL) {
yPosition = projectedColumns[d].middle;
if (labelSettings.orient === 'right') {
xPosition = adjustedSize[0] + 3;
} else {
xPosition = -3;
}
} else if (projection === PROJECTION_RADIAL) {
if (labelSettings.orient === 'center') {
xPosition = pieArcs[i].centroid[0] + pieArcs[i].translate[0];
yPosition = pieArcs[i].centroid[1] + pieArcs[i].translate[1];
} else {
xPosition = pieArcs[i].outerPoint[0] + pieArcs[i].translate[0];
yPosition = pieArcs[i].outerPoint[1] + pieArcs[i].translate[1];
}
}
const label = labelingFn(
d,
projectedColumns[d].pieceData.map(d => d.data),
i
// ,{ arc: pieArcs[i], data: projectedColumns[d] }
);
labelArray.push(
<g
key={`olabel-${i}`}
transform={`translate(${xPosition},${yPosition})`}
>
{label}
</g>
);
});
if (projection === PROJECTION_VERTICAL) {
let labelY;
if (labelSettings.orient === 'top') {
labelY = -15;
} else {
labelY = 15 + rScale.range()[1];
}
oLabels = (
<g
key="ordinalframe-labels-container"
className="ordinal-labels"
transform={`translate(${margin.left},${labelY + margin.top})`}
>
{labelArray}
</g>
);
} else if (projection === PROJECTION_HORIZONTAL) {
oLabels = (
<g
key="ordinalframe-labels-container"
className="ordinal-labels"
transform={`translate(${margin.left},${margin.top})`}
>
{labelArray}
</g>
);
} else if (projection === PROJECTION_RADIAL) {
oLabels = (
<g
key="ordinalframe-labels-container"
className="ordinal-labels"
transform={`translate(${margin.left},${margin.top})`}
>
{labelArray}
</g>
);
}
}
let columnOverlays;
if (props.hoverAnnotation) {
columnOverlays = oExtent.map((d, i) => {
const barColumnWidth = projectedColumns[d].width;
let xPosition = projectedColumns[d].x;
let yPosition = 0;
let height = rScale.range()[1];
let width = barColumnWidth + padding;
if (projection === PROJECTION_HORIZONTAL) {
yPosition = projectedColumns[d].x;
xPosition = 0;
width = rScale.range()[1];
height = barColumnWidth;
}
if (projection === PROJECTION_RADIAL) {
const { markD, centroid, translate, midAngle } = pieArcs[i];
const radialMousePackage = {
type: 'column-hover',
column: projectedColumns[d],
pieces: projectedColumns[d].pieceData,
summary: projectedColumns[d].pieceData,
arcAngles: {
centroid,
translate,
midAngle,
length: rScale.range()[1] / 2
}
};
return {
markType: 'path',
key: `hover${d}`,
d: markD,
transform: `translate(${translate.join(',')})`,
style: { opacity: 0, fill: 'pink' },
overlayData: radialMousePackage,
onDoubleClick:
customDoubleClickBehavior &&
(() => {
customDoubleClickBehavior(radialMousePackage);
}),
onClick:
customClickBehavior &&
(() => {
customClickBehavior(radialMousePackage);
}),
onMouseEnter:
customHoverBehavior &&
(() => {
customHoverBehavior(radialMousePackage);
}),
onMouseLeave:
customHoverBehavior &&
(() => {
customHoverBehavior();
})
};
}
const baseMousePackage = {
type: 'column-hover',
column: projectedColumns[d],
pieces: projectedColumns[d].pieceData,
summary: projectedColumns[d].pieceData
};
return {
markType: 'rect',
key: `hover-${d}`,
x: xPosition,
y: yPosition,
height: height,
width: width,
style: { opacity: 0, stroke: 'black', fill: 'pink' },
onDoubleClick:
customDoubleClickBehavior &&
(() => {
customDoubleClickBehavior(baseMousePackage);
}),
onClick:
customClickBehavior &&
(() => {
customClickBehavior(baseMousePackage);
}),
onMouseEnter:
customHoverBehavior &&
(() => {
customHoverBehavior(baseMousePackage);
}),
onMouseLeave: () => ({}),
overlayData: baseMousePackage
};
});
}
let screenCoordinates;
const pieceTypeForXY =
pieceType && pieceType !== NONE ? pieceType : ORDINAL_POINT;
const pieceTypeLayout =
typeof pieceTypeForXY === 'function'
? pieceTypeForXY
: singleOrdinalPlot.type.layout;
const calculatedPieceData = pieceTypeLayout({
...singleOrdinalPlot.props,
data: projectedColumns,
renderMode: stringToFn(renderMode, undefined, true),
eventListenersGenerator,
styleFn: pieceStyle,
projection,
classFn: pieceClass,
adjustedSize,
chartSize: size,
margin,
rScale,
baseMarkProps
});
const keyedData = calculatedPieceData.reduce((p, c) => {
if (c.o) {
if (!p[c.o]) {
p[c.o] = [];
}
p[c.o].push(c);
}
return p;
}, {});
Object.keys(projectedColumns).forEach(d => {
projectedColumns[d].xyData = keyedData[d] || [];
});
let calculatedSummaries = {};
if (summaryType.type && summaryType.type !== NONE) {
calculatedSummaries = drawSummaries({
data: projectedColumns,
type: summaryType,
renderMode: stringToFn(summaryRenderMode, undefined, true),
styleFn: stringToFn(summaryStyle, () => ({}), true),
classFn: stringToFn(summaryClass, () => '', true),
// canvasRender: stringToFn<boolean>(summaryUseCanvas, undefined, true),
positionFn: summaryPosition,
projection,
eventListenersGenerator,
adjustedSize,
baseMarkProps,
// chartSize: size,
margin
});
calculatedSummaries.originalData = projectedColumns;
}
const yMod = projection === PROJECTION_HORIZONTAL ? midMod : zeroFunction;
const xMod = projection === PROJECTION_VERTICAL ? midMod : zeroFunction;
const basePieceData = calculatedPieceData
.map(d => {
if (d.piece && d.xy) {
return {
...d.piece,
type: 'frame-hover',
x: d.xy.x + xMod(d.xy),
y: d.xy.y + yMod(d.xy)
};
}
return null;
})
.filter(d => d);
if (
(pieceHoverAnnotation &&
[BAR, CLUSTER_BAR, TIMELINE].indexOf(pieceType) === -1) ||
summaryHoverAnnotation
) {
if (summaryHoverAnnotation && calculatedSummaries.xyPoints) {
screenCoordinates = calculatedSummaries.xyPoints.map(d =>
Object.assign({}, d, {
type: 'frame-hover',
isSummaryData: true,
x: d.x,
y: d.y
})
);
} else if (pieceHoverAnnotation && calculatedPieceData) {
screenCoordinates = basePieceData;
}
}
const { innerRadius = 0 } = singleOrdinalPlot.props;
// todo: move to toAxes
const { axis, axesTickLines } = orFrameAxisGenerator({
axis: arrayWrappedAxis,
data: allData,
projection,
adjustedSize,
size,
rScale,
rScaleType: instantiatedRScaleType.copy(),
innerRadius,
rExtent,
maxColumnValues,
xyData: basePieceData,
margin
});
if (
pieceHoverAnnotation &&
[BAR, CLUSTER_BAR, TIMELINE].indexOf(pieceType) !== -1
) {
const yMod = projection === PROJECTION_HORIZONTAL ? midMod : zeroFunction;
const xMod = projection === PROJECTION_VERTICAL ? midMod : zeroFunction;
columnOverlays = calculatedPieceData.map((d, i) => {
const mousePackage = {
...d.piece,
x: d.xy.x + xMod(d.xy),
y: d.xy.y + yMod(d.xy)
};
if (React.isValidElement(d.renderElement)) {
return {
renderElement: d.renderElement,
overlayData: mousePackage
};
}
return {
...d.renderElement,
key: `hover-${i}`,
type: 'frame-hover',
style: { opacity: 0, stroke: 'black', fill: 'pink' },
overlayData: mousePackage,
onClick:
customClickBehavior &&
(() => {
customClickBehavior(mousePackage.data);
}),
onDoubleClick:
customDoubleClickBehavior &&
(() => {
customDoubleClickBehavior(mousePackage.data);
}),
onMouseEnter:
customHoverBehavior &&
(() => {
customHoverBehavior(mousePackage.data);
}),
onMouseLeave:
customHoverBehavior &&
(() => {
customHoverBehavior();
})
};
});
}
const typeAriaLabel = naturalLanguageTypes[pieceType] || {
items: 'piece',
chart: 'ordinal chart'
};
const { svgPipe, canvasPipe } = toPipeline({
shouldRender: pieceType !== NONE,
isOrdinalPoint: pieceType === 'ordinalpoint',
pieceData: calculatedPieceData,
pieceUseCanvas,
pieceStyle: stringToFn(pieceStyle, () => ({}), true),
pieceClass: stringToFn(pieceClass, () => '', true),
connectorType: objectifyType(baseConnectorType),
connectorData: keyedData,
connectorStyle: stringToFn(connectorStyle, () => ({}), true),
connectorClass: stringToFn(connectorClass, () => '', true),
connectorUseCanvas,
connectorRenderMode: stringToFn(connectorRenderMode, undefined, true),
projection,
ariaLabel: typeAriaLabel,
axis: arrayWrappedAxis
});
const svgPipeline = [...svgPipe, ...(calculatedSummaries.marks || [])];
const canvasPipeline = canvasPipe.slice();
if (
rExtentSettings.onChange &&
(calculatedRExtent || []).join(',') !== (calculatedRExtent || []).join(',')
) {
rExtentSettings.onChange(calculatedRExtent);
}
if (
oExtentSettings.onChange &&
(calculatedOExtent || []).join(',') !== (calculatedOExtent || []).join(',')
) {
oExtentSettings.onChange(calculatedOExtent);
}
let interactionOverflow;
if (summaryType && summaryType.amplitude) {
if (projection === PROJECTION_HORIZONTAL) {
interactionOverflow = {
top: summaryType.amplitude,
bottom: 0,
left: 0,
right: 0
};
} else if (projection === PROJECTION_RADIAL) {
interactionOverflow = defaultOverflow;
} else {
interactionOverflow = {
top: 0,
bottom: 0,
left: summaryType.amplitude,
right: 0
};
}
}
return {
projectedColumns,
pieceIDAccessor,
oColumn: null,
canvasPipeline,
svgPipeline,
screenCoordinates,
xyPoints: [],
adjustedPosition,
adjustedSize,
axes: axis,
axesTickLines,
overlay: columnOverlays,
interactionOverflow,
projection,
oAccessor,
rAccessor,
rScale,
rScaleType,
summaryType,
type: pieceType,
oLabels
};
};
export default computeOrdinalFrameData;