diff --git a/src/components/NetworkFrame.tsx b/src/components/NetworkFrame.tsx index ab19a04b..bbc276e0 100644 --- a/src/components/NetworkFrame.tsx +++ b/src/components/NetworkFrame.tsx @@ -1,4 +1,5 @@ import * as React from "react" +import { useMemo } from "react" import Frame from "./Frame" @@ -44,119 +45,208 @@ import { NetworkFrameState } from "./types/networkTypes" import { AnnotationLayerProps } from "./AnnotationLayer" +import { useDerivedStateFromProps } from "./useDerivedStateFromProps" +import { useLegacyUnmountCallback } from "./useLegacyUnmountCallback" + +const defaultProps = { + annotations: [], + foregroundGraphics: [], + size: [500, 500], + className: "", + name: "networkframe", + networkType: { type: "force", iterations: 500 }, + filterRenderedNodes: (d: NodeType) => d.id !== "root-generated" +} -class NetworkFrame extends React.Component< - NetworkFrameProps, - NetworkFrameState -> { - static defaultProps = { - annotations: [], - foregroundGraphics: [], - annotationSettings: {}, - size: [500, 500], - className: "", - name: "networkframe", - networkType: { type: "force", iterations: 500 }, - filterRenderedNodes: (d: NodeType) => d.id !== "root-generated" +export default React.memo(function NetworkFrame(allProps: NetworkFrameProps) { + const props: NetworkFrameProps = { ...defaultProps, ...allProps } + const baseState = { + dataVersion: undefined, + nodeData: [], + edgeData: [], + adjustedPosition: [], + adjustedSize: [], + backgroundGraphics: null, + foregroundGraphics: null, + projectedNodes: [], + projectedEdges: [], + renderNumber: 0, + nodeLabelAnnotations: [], + graphSettings: { + type: "empty-start", + nodes: [], + edges: [], + nodeHash: new Map(), + edgeHash: new Map(), + hierarchicalNetwork: false + }, + edgeWidthAccessor: stringToFn("weight"), + legendSettings: undefined, + margin: { top: 0, left: 0, right: 0, bottom: 0 }, + networkFrameRender: {}, + nodeIDAccessor: stringToFn("id"), + nodeSizeAccessor: genericFunction(5), + overlay: [], + projectedXYPoints: [], + sourceAccessor: stringToFn("source"), + targetAccessor: stringToFn("target"), + title: { title: undefined }, + props } - static displayName: string = "NetworkFrame" - - constructor(props: NetworkFrameProps) { - super(props) - - const baseState = { - dataVersion: undefined, - nodeData: [], - edgeData: [], - adjustedPosition: [], - adjustedSize: [], - backgroundGraphics: null, - foregroundGraphics: null, - projectedNodes: [], - projectedEdges: [], - renderNumber: 0, - nodeLabelAnnotations: [], - graphSettings: { - type: "empty-start", - nodes: [], - edges: [], - nodeHash: new Map(), - edgeHash: new Map(), - hierarchicalNetwork: false - }, - edgeWidthAccessor: stringToFn("weight"), - legendSettings: undefined, - margin: { top: 0, left: 0, right: 0, bottom: 0 }, - networkFrameRender: {}, - nodeIDAccessor: stringToFn("id"), - nodeSizeAccessor: genericFunction(5), - overlay: [], - projectedXYPoints: [], - sourceAccessor: stringToFn("source"), - targetAccessor: stringToFn("target"), - title: { title: undefined }, - props - } - this.state = { + const initialState = useMemo( + () => ({ ...baseState, ...calculateNetworkFrame(props, baseState) - } - } - - componentWillUnmount() { - const { onUnmount } = this.props - if (onUnmount) { - onUnmount(this.props, this.state) - } + }), + [] + ) + + const state = useDerivedStateFromProps( + deriveNetworkFrameState, + props, + initialState + ) + + useLegacyUnmountCallback(props, state) + + const { + annotations, + annotationSettings, + className, + customClickBehavior, + customDoubleClickBehavior, + customHoverBehavior, + size, + matte, + hoverAnnotation, + beforeElements, + afterElements, + interaction, + disableContext, + canvasPostProcess, + baseMarkProps, + useSpans, + canvasNodes, + canvasEdges, + additionalDefs, + renderOrder = state.graphSettings && state.graphSettings.type === "matrix" + ? matrixRenderOrder + : generalRenderOrder, + sketchyRenderingEngine, + frameRenderOrder, + disableCanvasInteraction, + interactionSettings, + disableProgressiveRendering + } = props + const { + backgroundGraphics, + foregroundGraphics, + projectedXYPoints, + margin, + legendSettings, + adjustedPosition, + adjustedSize, + networkFrameRender, + nodeLabelAnnotations, + overlay, + title + } = state + + let formattedOverlay + + if (overlay && overlay.length > 0) { + formattedOverlay = overlay } - static getDerivedStateFromProps( - nextProps: NetworkFrameProps, - prevState: NetworkFrameState + let activeHoverAnnotation + if (Array.isArray(hoverAnnotation)) { + activeHoverAnnotation = hoverAnnotation + } else if ( + customClickBehavior || + customDoubleClickBehavior || + customHoverBehavior ) { - const { props } = prevState - if ( - (prevState.dataVersion && - prevState.dataVersion !== nextProps.dataVersion) || - (!prevState.projectedNodes && !prevState.projectedEdges) || - props.size[0] !== nextProps.size[0] || - props.size[1] !== nextProps.size[1] || - (!prevState.dataVersion && - networkFrameChangeProps.find((d) => { - return props[d] !== nextProps[d] - })) - ) { - return { - ...calculateNetworkFrame(nextProps, prevState), - props: nextProps - } - } - return { props: nextProps } - } - - onNodeClick(d: Object, i: number) { - const { onNodeClick } = this.props - if (onNodeClick) { - onNodeClick(d, i) - } - } - - onNodeEnter(d: Object, i: number) { - const { onNodeEnter } = this.props - if (onNodeEnter) { - onNodeEnter(d, i) - } + activeHoverAnnotation = blankArray + } else { + activeHoverAnnotation = !!hoverAnnotation } - onNodeOut(d: Object, i: number) { - const { onNodeOut } = this.props - if (onNodeOut) { - onNodeOut(d, i) + return ( + defaultNetworkSVGRule(props, state, args)} + defaultHTMLRule={(args) => defaultNetworkHTMLRule(props, state, args)} + hoverAnnotation={activeHoverAnnotation} + annotations={[...annotations, ...nodeLabelAnnotations]} + annotationSettings={annotationSettings} + legendSettings={legendSettings} + interaction={interaction} + customClickBehavior={customClickBehavior} + customHoverBehavior={customHoverBehavior} + customDoubleClickBehavior={customDoubleClickBehavior} + points={projectedXYPoints} + margin={margin} + overlay={formattedOverlay} + backgroundGraphics={backgroundGraphics} + foregroundGraphics={foregroundGraphics} + beforeElements={beforeElements} + afterElements={afterElements} + disableContext={disableContext} + canvasPostProcess={canvasPostProcess} + baseMarkProps={baseMarkProps} + useSpans={!!useSpans} + canvasRendering={!!(canvasNodes || canvasEdges)} + renderOrder={renderOrder} + disableCanvasInteraction={disableCanvasInteraction} + sketchyRenderingEngine={sketchyRenderingEngine} + frameRenderOrder={frameRenderOrder} + interactionSettings={interactionSettings} + disableProgressiveRendering={disableProgressiveRendering} + /> + ) +}) + +function deriveNetworkFrameState( + nextProps: NetworkFrameProps, + prevState: NetworkFrameState +) { + const { props } = prevState + if ( + (prevState.dataVersion && + prevState.dataVersion !== nextProps.dataVersion) || + (!prevState.projectedNodes && !prevState.projectedEdges) || + props.size[0] !== nextProps.size[0] || + props.size[1] !== nextProps.size[1] || + (!prevState.dataVersion && + networkFrameChangeProps.find((d) => { + return props[d] !== nextProps[d] + })) + ) { + return { + ...calculateNetworkFrame(nextProps, prevState), + props: nextProps } } + return { props: nextProps } +} - defaultNetworkSVGRule = ({ +function defaultNetworkSVGRule( + props: NetworkFrameProps, + state: NetworkFrameState, + { d: baseD, i, annotationLayer @@ -164,117 +254,121 @@ class NetworkFrame extends React.Component< d: AnnotationType i: number annotationLayer: AnnotationLayerProps - }) => { - const { - projectedNodes, - projectedEdges, - nodeIDAccessor, - nodeSizeAccessor, - networkFrameRender, - adjustedSize, - adjustedPosition - } = this.state - //TODO PASS FRAME STYLE FNs TO HIGHLIGHT - const { svgAnnotationRules } = this.props - - const d = baseD.ids - ? baseD - : baseD.edge - ? { - ...(projectedEdges.find((p) => { - return ( - nodeIDAccessor(p.source) === nodeIDAccessor(baseD.edge.source) && - nodeIDAccessor(p.target) === nodeIDAccessor(baseD.edge.target) - ) - }) || {}), - ...baseD - } - : { - ...(projectedNodes.find((p) => nodeIDAccessor(p) === baseD.id) || {}), - ...baseD - } - - const { voronoiHover } = annotationLayer - - if (svgAnnotationRules) { - const customAnnotation = svgAnnotationRules({ - d, - i, - networkFrameProps: this.props, - networkFrameState: this.state, - nodes: projectedNodes, - edges: projectedEdges, - voronoiHover, - screenCoordinates: [d.x, d.y], - adjustedPosition, - adjustedSize, - annotationLayer - }) - if (customAnnotation !== null) { - return customAnnotation + } +) { + const { + projectedNodes, + projectedEdges, + nodeIDAccessor, + nodeSizeAccessor, + networkFrameRender, + adjustedSize, + adjustedPosition + } = state + //TODO PASS FRAME STYLE FNs TO HIGHLIGHT + const { svgAnnotationRules } = props + + const d = baseD.ids + ? baseD + : baseD.edge + ? { + ...(projectedEdges.find((p) => { + return ( + nodeIDAccessor(p.source) === nodeIDAccessor(baseD.edge.source) && + nodeIDAccessor(p.target) === nodeIDAccessor(baseD.edge.target) + ) + }) || {}), + ...baseD } + : { + ...(projectedNodes.find((p) => nodeIDAccessor(p) === baseD.id) || {}), + ...baseD + } + + const { voronoiHover } = annotationLayer + + if (svgAnnotationRules) { + const customAnnotation = svgAnnotationRules({ + d, + i, + networkFrameProps: props, + networkFrameState: state, + nodes: projectedNodes, + edges: projectedEdges, + voronoiHover, + screenCoordinates: [d.x, d.y], + adjustedPosition, + adjustedSize, + annotationLayer + }) + if (customAnnotation !== null) { + return customAnnotation } - if (d.type === "node") { - return svgNodeRule({ - d, - i, - nodeSizeAccessor - }) - } else if (d.type === "desaturation-layer") { - return desaturationLayer({ - style: d.style instanceof Function ? d.style(d, i) : d.style, - size: adjustedSize, - i, - key: d.key - }) - } else if (d.type === "basic-node-label") { - return ( - - {baseD.element || baseD.label} - - ) - } else if (d.type === "react-annotation" || typeof d.type === "function") { - return svgReactAnnotationRule({ - d, - i, - projectedNodes, - nodeIDAccessor - }) - } else if (d.type === "enclose") { - return svgEncloseRule({ - d, - i, - projectedNodes, - nodeIDAccessor, - nodeSizeAccessor - }) - } else if (d.type === "enclose-rect") { - return svgRectEncloseRule({ - d, - i, - projectedNodes, - nodeIDAccessor, - nodeSizeAccessor - }) - } else if (d.type === "enclose-hull") { - return svgHullEncloseRule({ - d, - i, - projectedNodes, - nodeIDAccessor, - nodeSizeAccessor - }) - } else if (d.type === "highlight") { - return svgHighlightRule({ - d, - i, - networkFrameRender - }) - } - return null } + if (d.type === "node") { + return svgNodeRule({ + d, + i, + nodeSizeAccessor + }) + } else if (d.type === "desaturation-layer") { + return desaturationLayer({ + style: d.style instanceof Function ? d.style(d, i) : d.style, + size: adjustedSize, + i, + key: d.key + }) + } else if (d.type === "basic-node-label") { + return ( + + {baseD.element || baseD.label} + + ) + } else if (d.type === "react-annotation" || typeof d.type === "function") { + return svgReactAnnotationRule({ + d, + i, + projectedNodes, + nodeIDAccessor + }) + } else if (d.type === "enclose") { + return svgEncloseRule({ + d, + i, + projectedNodes, + nodeIDAccessor, + nodeSizeAccessor + }) + } else if (d.type === "enclose-rect") { + return svgRectEncloseRule({ + d, + i, + projectedNodes, + nodeIDAccessor, + nodeSizeAccessor + }) + } else if (d.type === "enclose-hull") { + return svgHullEncloseRule({ + d, + i, + projectedNodes, + nodeIDAccessor, + nodeSizeAccessor + }) + } else if (d.type === "highlight") { + return svgHighlightRule({ + d, + i, + networkFrameRender + }) + } + return null +} - defaultNetworkHTMLRule = ({ +function defaultNetworkHTMLRule( + props: NetworkFrameProps, + state: NetworkFrameState, + { d: baseD, i, annotationLayer @@ -282,182 +376,69 @@ class NetworkFrame extends React.Component< d: AnnotationType i: number annotationLayer: AnnotationLayerProps - }) => { - const { - tooltipContent, - optimizeCustomTooltipPosition, - htmlAnnotationRules, - useSpans - } = this.props - const { - projectedNodes, - projectedEdges, - nodeIDAccessor, - adjustedSize, - adjustedPosition - } = this.state - - const { voronoiHover } = annotationLayer - - const d = baseD.ids - ? baseD - : baseD.edge - ? { - ...(projectedEdges.find( - (p) => - nodeIDAccessor(p.source) === nodeIDAccessor(baseD.source) && - nodeIDAccessor(p.target) === nodeIDAccessor(baseD.target) - ) || {}), - ...baseD - } - : { - ...(projectedNodes.find((p) => nodeIDAccessor(p) === baseD.id) || {}), - ...baseD - } - - if (htmlAnnotationRules) { - const customAnnotation = htmlAnnotationRules({ - d, - i, - networkFrameProps: this.props, - networkFrameState: this.state, - nodes: projectedNodes, - edges: projectedEdges, - voronoiHover, - screenCoordinates: [d.x, d.y], - adjustedPosition, - adjustedSize, - annotationLayer - }) - if (customAnnotation !== null) { - return customAnnotation - } - } - if (d.type === "frame-hover") { - return htmlFrameHoverRule({ - d, - i, - tooltipContent, - optimizeCustomTooltipPosition, - useSpans, - nodes: projectedNodes, - edges: projectedEdges, - nodeIDAccessor - }) - } - return null } +) { + const { + tooltipContent, + optimizeCustomTooltipPosition, + htmlAnnotationRules, + useSpans + } = props + const { + projectedNodes, + projectedEdges, + nodeIDAccessor, + adjustedSize, + adjustedPosition + } = state + + const { voronoiHover } = annotationLayer + + const d = baseD.ids + ? baseD + : baseD.edge + ? { + ...(projectedEdges.find( + (p) => + nodeIDAccessor(p.source) === nodeIDAccessor(baseD.source) && + nodeIDAccessor(p.target) === nodeIDAccessor(baseD.target) + ) || {}), + ...baseD + } + : { + ...(projectedNodes.find((p) => nodeIDAccessor(p) === baseD.id) || {}), + ...baseD + } - render() { - const { - annotations, - annotationSettings, - className, - customClickBehavior, - customDoubleClickBehavior, - customHoverBehavior, - size, - matte, - hoverAnnotation, - beforeElements, - afterElements, - interaction, - disableContext, - canvasPostProcess, - baseMarkProps, - useSpans, - canvasNodes, - canvasEdges, - additionalDefs, - renderOrder = this.state.graphSettings && - this.state.graphSettings.type === "matrix" - ? matrixRenderOrder - : generalRenderOrder, - sketchyRenderingEngine, - frameRenderOrder, - disableCanvasInteraction, - interactionSettings, - disableProgressiveRendering - } = this.props - const { - backgroundGraphics, - foregroundGraphics, - projectedXYPoints, - margin, - legendSettings, + if (htmlAnnotationRules) { + const customAnnotation = htmlAnnotationRules({ + d, + i, + networkFrameProps: props, + networkFrameState: state, + nodes: projectedNodes, + edges: projectedEdges, + voronoiHover, + screenCoordinates: [d.x, d.y], adjustedPosition, adjustedSize, - networkFrameRender, - nodeLabelAnnotations, - overlay, - title - } = this.state - - let formattedOverlay - - if (overlay && overlay.length > 0) { - formattedOverlay = overlay - } - - let activeHoverAnnotation - if (Array.isArray(hoverAnnotation)) { - activeHoverAnnotation = hoverAnnotation - } else if ( - customClickBehavior || - customDoubleClickBehavior || - customHoverBehavior - ) { - activeHoverAnnotation = blankArray - } else { - activeHoverAnnotation = !!hoverAnnotation + annotationLayer + }) + if (customAnnotation !== null) { + return customAnnotation } - - return ( - - ) } + if (d.type === "frame-hover") { + return htmlFrameHoverRule({ + d, + i, + tooltipContent, + optimizeCustomTooltipPosition, + useSpans, + nodes: projectedNodes, + edges: projectedEdges, + nodeIDAccessor + }) + } + return null } - -export default NetworkFrame diff --git a/src/components/OrdinalFrame.tsx b/src/components/OrdinalFrame.tsx index 256be0d8..4ab6fbcb 100644 --- a/src/components/OrdinalFrame.tsx +++ b/src/components/OrdinalFrame.tsx @@ -1,4 +1,5 @@ import * as React from "react" +import { useMemo } from "react" import { scaleBand, scaleLinear } from "d3-scale" @@ -34,6 +35,8 @@ import { AnnotationType } from "./types/annotationTypes" import { AnnotationLayerProps } from "./AnnotationLayer" import { OrdinalFrameProps, OrdinalFrameState } from "./types/ordinalTypes" +import { useDerivedStateFromProps } from "./useDerivedStateFromProps" +import { useLegacyUnmountCallback } from "./useLegacyUnmountCallback" const xScale = scaleLinear() const yScale = scaleLinear() @@ -42,105 +45,244 @@ const projectedCoordinatesObject = { y: "y", x: "x" } const defaultOverflow = { top: 0, bottom: 0, left: 0, right: 0 } -class OrdinalFrame extends React.Component< - OrdinalFrameProps, - OrdinalFrameState -> { - static defaultProps = { - annotations: [], - foregroundGraphics: [], - annotationSettings: {}, - projection: "vertical", - size: [500, 500], - className: "", - data: [], - oScaleType: scaleBand, - rScaleType: scaleLinear, - type: "none", - summaryType: "none", - useSpans: false, - optimizeCustomTooltipPosition: false - } +const defaultProps: Partial = { + annotations: [], + foregroundGraphics: [], + projection: "vertical", + size: [500, 500], + className: "", + data: [], + oScaleType: scaleBand(), + rScaleType: scaleLinear, + type: "none", + useSpans: false, + optimizeCustomTooltipPosition: false +} - static displayName: string = "OrdinalFrame" - - constructor(props: OrdinalFrameProps) { - super(props) - - const baseState = { - adjustedPosition: [], - adjustedSize: [], - backgroundGraphics: undefined, - foregroundGraphics: undefined, - axisData: undefined, - renderNumber: 0, - oLabels: { labels: [] }, - oAccessor: stringToArrayFn("renderKey"), - rAccessor: stringToArrayFn("value"), - oScale: scaleBand(), - rScale: scaleLinear(), - axes: undefined, - calculatedOExtent: [], - calculatedRExtent: [0, 1], - columnOverlays: [], - dataVersion: undefined, - legendSettings: undefined, - margin: { top: 0, bottom: 0, left: 0, right: 0 }, - oExtent: [], - oScaleType: scaleBand(), - orFrameRender: {}, - pieceDataXY: [], - pieceIDAccessor: stringToFn("semioticPieceID"), - projectedColumns: {}, - rExtent: [], - rScaleType: scaleLinear(), - summaryType: { type: "none" }, - title: {}, - type: { type: "none" }, - props - } +export default React.memo(function OrdinalFrame(allProps: OrdinalFrameProps) { + const props: OrdinalFrameProps = { ...defaultProps, ...allProps } + const baseState = { + adjustedPosition: [], + adjustedSize: [], + backgroundGraphics: undefined, + foregroundGraphics: undefined, + axisData: undefined, + renderNumber: 0, + oLabels: { labels: [] }, + oAccessor: stringToArrayFn("renderKey"), + rAccessor: stringToArrayFn("value"), + oScale: scaleBand(), + rScale: scaleLinear(), + axes: undefined, + calculatedOExtent: [], + calculatedRExtent: [0, 1], + columnOverlays: [], + dataVersion: undefined, + legendSettings: undefined, + margin: { top: 0, bottom: 0, left: 0, right: 0 }, + oExtent: [], + oScaleType: scaleBand(), + orFrameRender: {}, + pieceDataXY: [], + pieceIDAccessor: stringToFn("semioticPieceID"), + projectedColumns: {}, + rExtent: [], + rScaleType: scaleLinear(), + summaryType: { type: "none" }, + title: {}, + type: { type: "none" }, + props + } - this.state = { + const initialState = useMemo( + () => ({ ...baseState, ...calculateOrdinalFrame(props, baseState) + }), + [] + ) + const state = useDerivedStateFromProps( + deriveOrdinalFrameState, + props, + initialState + ) + + useLegacyUnmountCallback(props, state) + + const { + className, + annotationSettings, + annotations, + matte, + renderKey, + interaction, + customClickBehavior, + customHoverBehavior, + customDoubleClickBehavior, + projection, + backgroundGraphics, + foregroundGraphics, + afterElements, + beforeElements, + disableContext, + summaryType, + summaryHoverAnnotation, + pieceHoverAnnotation, + hoverAnnotation, + canvasPostProcess, + baseMarkProps, + useSpans, + canvasPieces, + canvasSummaries, + canvasConnectors, + renderOrder, + additionalDefs, + sketchyRenderingEngine, + frameRenderOrder, + disableCanvasInteraction, + disableProgressiveRendering + } = props + + const { + orFrameRender, + projectedColumns, + adjustedPosition, + adjustedSize, + legendSettings, + columnOverlays, + axesTickLines, + axes, + margin, + pieceDataXY, + oLabels, + title + } = state + + const size = [ + adjustedSize[0] + margin.left + margin.right, + adjustedSize[1] + margin.top + margin.bottom + ] + + let interactionOverflow + + if (summaryType && summaryType.amplitude) { + if (projection === "horizontal") { + interactionOverflow = { + top: summaryType.amplitude, + bottom: 0, + left: 0, + right: 0 + } + } else if (projection === "radial") { + interactionOverflow = defaultOverflow + } else { + interactionOverflow = { + top: 0, + bottom: 0, + left: summaryType.amplitude, + right: 0 + } } } - componentWillUnmount() { - const { onUnmount } = this.props - if (onUnmount) { - onUnmount(this.props, this.state) - } - - } - - static getDerivedStateFromProps( - nextProps: OrdinalFrameProps, - prevState: OrdinalFrameState - ) { - const { props } = prevState - - if ( - (prevState.dataVersion && - prevState.dataVersion !== nextProps.dataVersion) || - !prevState.projectedColumns || - props.size[0] !== nextProps.size[0] || - props.size[1] !== nextProps.size[1] || - (!prevState.dataVersion && - orFrameChangeProps.find((d) => { - return props[d] !== nextProps[d] - })) - ) { - return { - ...calculateOrdinalFrame(nextProps, prevState), - props: nextProps + const renderedForegroundGraphics = + typeof foregroundGraphics === "function" + ? foregroundGraphics({ size, margin }) + : foregroundGraphics + + return ( + defaultORSVGRule(props, state, args)} + defaultHTMLRule={(args) => defaultORHTMLRule(props, state, args)} + hoverAnnotation={ + summaryHoverAnnotation || pieceHoverAnnotation || hoverAnnotation } - } else { - return { props: nextProps } + annotations={annotations} + annotationSettings={annotationSettings} + legendSettings={legendSettings} + interaction={ + interaction && { + ...interaction, + brush: interaction.columnsBrush !== true && "oBrush", + projection, + projectedColumns + } + } + customClickBehavior={customClickBehavior} + customHoverBehavior={customHoverBehavior} + customDoubleClickBehavior={customDoubleClickBehavior} + points={pieceDataXY} + margin={margin} + columns={projectedColumns} + backgroundGraphics={backgroundGraphics} + foregroundGraphics={renderedForegroundGraphics} + beforeElements={beforeElements} + afterElements={afterElements} + overlay={columnOverlays} + rScale={state.rScale} + projection={projection} + disableContext={disableContext} + interactionOverflow={interactionOverflow} + canvasPostProcess={canvasPostProcess} + baseMarkProps={baseMarkProps} + canvasRendering={!!(canvasPieces || canvasSummaries || canvasConnectors)} + renderOrder={renderOrder} + disableCanvasInteraction={disableCanvasInteraction} + sketchyRenderingEngine={sketchyRenderingEngine} + frameRenderOrder={frameRenderOrder} + additionalVizElements={oLabels} + disableProgressiveRendering={disableProgressiveRendering} + /> + ) +}) + +function deriveOrdinalFrameState( + nextProps: OrdinalFrameProps, + prevState: OrdinalFrameState +) { + const { props } = prevState + + if ( + (prevState.dataVersion && + prevState.dataVersion !== nextProps.dataVersion) || + !prevState.projectedColumns || + props.size[0] !== nextProps.size[0] || + props.size[1] !== nextProps.size[1] || + (!prevState.dataVersion && + orFrameChangeProps.find((d) => { + return props[d] !== nextProps[d] + })) + ) { + return { + ...calculateOrdinalFrame(nextProps, prevState), + props: nextProps } + } else { + return { props: nextProps } } +} - defaultORSVGRule = ({ +function defaultORSVGRule( + props: OrdinalFrameProps, + state: OrdinalFrameState, + { d, i, annotationLayer @@ -148,149 +290,153 @@ class OrdinalFrame extends React.Component< d: AnnotationType i: number annotationLayer: AnnotationLayerProps - }) => { - const { projection, svgAnnotationRules } = this.props + } +) { + const { projection, svgAnnotationRules } = props + + const { + adjustedPosition, + adjustedSize, + oAccessor, + rAccessor, + oScale, + rScale, + projectedColumns, + orFrameRender, + pieceIDAccessor, + rScaleType, + summaryType, + type + } = state + + let screenCoordinates: number[] | number[][] = [0, 0] + + getColumnScreenCoordinates + if (d.isColumnAnnotation) { const { + coordinates: [xPosition, yPosition] + } = getColumnScreenCoordinates({ + d, + projectedColumns, + oAccessor, + summaryType, + type, + projection, adjustedPosition, + adjustedSize + }) + screenCoordinates = [xPosition, yPosition] + } else if (d.coordinates || (d.type === "enclose" && d.neighbors)) { + screenCoordinates = (d.coordinates || d.neighbors).map( + (p: { column?: string }) => { + const pO = findFirstAccessorValue(oAccessor, p) || p.column + const oColumn = projectedColumns[pO] + const idPiece = findIDPiece(pieceIDAccessor, oColumn, p) + + return screenProject({ + p, + adjustedSize, + rScale, + rAccessor, + idPiece, + projection, + oColumn, + rScaleType + }) + } + ) + } else { + const pO = findFirstAccessorValue(oAccessor, d) || d.column + const oColumn = projectedColumns[pO] + const idPiece = findIDPiece(pieceIDAccessor, oColumn, d) + + screenCoordinates = screenProject({ + p: d, adjustedSize, - oAccessor, + rScale, rAccessor, + idPiece, + projection, + oColumn, + rScaleType + }) + } + + const { voronoiHover } = annotationLayer + + //TODO: Process your rules first + const customAnnotation = + svgAnnotationRules && + svgAnnotationRules({ + d, + i, oScale, rScale, - projectedColumns, - orFrameRender, + oAccessor, + rAccessor, + orFrameProps: props, + orFrameState: state, + screenCoordinates, + adjustedPosition, + adjustedSize, + annotationLayer, + categories: projectedColumns, + voronoiHover + }) + if (svgAnnotationRules && customAnnotation !== null) { + return customAnnotation + } else if (d.type === "desaturation-layer") { + return desaturationLayer({ + style: d.style instanceof Function ? d.style(d, i) : d.style, + size: adjustedSize, + i, + key: d.key + }) + } else if (d.type === "ordinal-line") { + return svgOrdinalLine({ d, screenCoordinates, voronoiHover }) + } else if (d.type === "or") { + return svgORRule({ d, i, screenCoordinates, projection }) + } else if (d.type === "highlight") { + return svgHighlightRule({ + d, pieceIDAccessor, - rScaleType, - summaryType, - type - } = this.state - - let screenCoordinates: number[] | number[][] = [0, 0] - - getColumnScreenCoordinates - - if (d.isColumnAnnotation) { - const { - coordinates: [xPosition, yPosition] - } = getColumnScreenCoordinates({ - d, - projectedColumns, - oAccessor, - summaryType, - type, - projection, - adjustedPosition, - adjustedSize - }) - screenCoordinates = [xPosition, yPosition] - } else if (d.coordinates || (d.type === "enclose" && d.neighbors)) { - screenCoordinates = (d.coordinates || d.neighbors).map( - (p: { column?: string }) => { - const pO = findFirstAccessorValue(oAccessor, p) || p.column - const oColumn = projectedColumns[pO] - const idPiece = findIDPiece(pieceIDAccessor, oColumn, p) - - return screenProject({ - p, - adjustedSize, - rScale, - rAccessor, - idPiece, - projection, - oColumn, - rScaleType - }) - } - ) - } else { - const pO = findFirstAccessorValue(oAccessor, d) || d.column - const oColumn = projectedColumns[pO] - const idPiece = findIDPiece(pieceIDAccessor, oColumn, d) - - screenCoordinates = screenProject({ - p: d, - adjustedSize, - rScale, - rAccessor, - idPiece, - projection, - oColumn, - rScaleType - }) - } - - const { voronoiHover } = annotationLayer - - //TODO: Process your rules first - const customAnnotation = - svgAnnotationRules && - svgAnnotationRules({ - d, - i, - oScale, - rScale, - oAccessor, - rAccessor, - orFrameProps: this.props, - orFrameState: this.state, - screenCoordinates, - adjustedPosition, - adjustedSize, - annotationLayer, - categories: projectedColumns, - voronoiHover - }) - if (svgAnnotationRules && customAnnotation !== null) { - return customAnnotation - } else if (d.type === "desaturation-layer") { - return desaturationLayer({ - style: d.style instanceof Function ? d.style(d, i) : d.style, - size: adjustedSize, - i, - key: d.key - }) - } else if (d.type === "ordinal-line") { - return svgOrdinalLine({ d, screenCoordinates, voronoiHover }) - } else if (d.type === "or") { - return svgORRule({ d, i, screenCoordinates, projection }) - } else if (d.type === "highlight") { - return svgHighlightRule({ - d, - pieceIDAccessor, - orFrameRender, - oAccessor - }) - } else if (d.type === "react-annotation" || typeof d.type === "function") { - return basicReactAnnotationRule({ d, i, screenCoordinates }) - } else if (d.type === "enclose") { - return svgEncloseRule({ d, i, screenCoordinates }) - } else if (d.type === "enclose-rect") { - return svgRectEncloseRule({ d, screenCoordinates, i }) - } else if (d.type === "r") { - return svgRRule({ - d, - i, - screenCoordinates, - rScale, - rAccessor, - projection, - adjustedSize, - adjustedPosition - }) - } else if (d.type === "category") { - return svgCategoryRule({ - projection, - d, - i, - categories: this.state.projectedColumns, - adjustedSize - }) - } - return null + orFrameRender, + oAccessor + }) + } else if (d.type === "react-annotation" || typeof d.type === "function") { + return basicReactAnnotationRule({ d, i, screenCoordinates }) + } else if (d.type === "enclose") { + return svgEncloseRule({ d, i, screenCoordinates }) + } else if (d.type === "enclose-rect") { + return svgRectEncloseRule({ d, screenCoordinates, i }) + } else if (d.type === "r") { + return svgRRule({ + d, + i, + screenCoordinates, + rScale, + rAccessor, + projection, + adjustedSize, + adjustedPosition + }) + } else if (d.type === "category") { + return svgCategoryRule({ + projection, + d, + i, + categories: state.projectedColumns, + adjustedSize + }) } + return null +} - defaultORHTMLRule = ({ +function defaultORHTMLRule( + props: OrdinalFrameProps, + state: OrdinalFrameState, + { d, i, annotationLayer @@ -298,292 +444,143 @@ class OrdinalFrame extends React.Component< d: AnnotationType i: number annotationLayer: AnnotationLayerProps - }) => { + } +) { + const { + adjustedPosition, + adjustedSize, + oAccessor, + rAccessor, + oScale, + rScale, + projectedColumns, + summaryType, + type, + pieceIDAccessor, + rScaleType + } = state + const { + htmlAnnotationRules, + tooltipContent, + optimizeCustomTooltipPosition, + projection, + size, + useSpans + } = props + let screenCoordinates: number[] | number[][] = [0, 0] + + const { voronoiHover } = annotationLayer + + if (d.coordinates || (d.type === "enclose" && d.neighbors)) { + screenCoordinates = (d.coordinates || d.neighbors).map( + (p: { column?: string }) => { + const pO = findFirstAccessorValue(oAccessor, p) || p.column + const oColumn = projectedColumns[pO] + const idPiece = findIDPiece(pieceIDAccessor, oColumn, p) + + return screenProject({ + p, + adjustedSize, + rScale, + rAccessor, + idPiece, + projection, + oColumn, + rScaleType + }) + } + ) + } else if (d.type === "column-hover") { const { - adjustedPosition, - adjustedSize, - oAccessor, - rAccessor, - oScale, - rScale, + coordinates: [xPosition, yPosition] + } = getColumnScreenCoordinates({ + d, projectedColumns, + oAccessor, summaryType, type, - pieceIDAccessor, - rScaleType - } = this.state - const { - htmlAnnotationRules, - tooltipContent, - optimizeCustomTooltipPosition, projection, - size, - useSpans - } = this.props - let screenCoordinates: number[] | number[][] = [0, 0] - - const { voronoiHover } = annotationLayer - - if (d.coordinates || (d.type === "enclose" && d.neighbors)) { - screenCoordinates = (d.coordinates || d.neighbors).map( - (p: { column?: string }) => { - const pO = findFirstAccessorValue(oAccessor, p) || p.column - const oColumn = projectedColumns[pO] - const idPiece = findIDPiece(pieceIDAccessor, oColumn, p) - - return screenProject({ - p, - adjustedSize, - rScale, - rAccessor, - idPiece, - projection, - oColumn, - rScaleType - }) - } - ) - } else if (d.type === "column-hover") { - const { - coordinates: [xPosition, yPosition] - } = getColumnScreenCoordinates({ - d, - projectedColumns, - oAccessor, - summaryType, - type, - projection, - adjustedPosition, - adjustedSize - }) - screenCoordinates = [xPosition, yPosition] - } else { - const pO = findFirstAccessorValue(oAccessor, d) || d.column - const oColumn = projectedColumns[pO] - const idPiece = findIDPiece(pieceIDAccessor, oColumn, d) - - screenCoordinates = screenProject({ - p: d, - adjustedSize, - rScale, - rAccessor, - idPiece, - projection, - oColumn, - rScaleType - }) - } - - const flippedRScale = - projection === "vertical" - ? rScaleType.domain(rScale.domain()).range(rScale.range().reverse()) - : rScale - //TODO: Process your rules first - const customAnnotation = - htmlAnnotationRules && - htmlAnnotationRules({ - d, - i, - oScale, - rScale: flippedRScale, - oAccessor, - rAccessor, - orFrameProps: this.props, - screenCoordinates, - adjustedPosition, - adjustedSize, - annotationLayer, - orFrameState: this.state, - categories: this.state.projectedColumns, - voronoiHover - }) - - if (htmlAnnotationRules && customAnnotation !== null) { - return customAnnotation - } + adjustedPosition, + adjustedSize + }) + screenCoordinates = [xPosition, yPosition] + } else { + const pO = findFirstAccessorValue(oAccessor, d) || d.column + const oColumn = projectedColumns[pO] + const idPiece = findIDPiece(pieceIDAccessor, oColumn, d) + + screenCoordinates = screenProject({ + p: d, + adjustedSize, + rScale, + rAccessor, + idPiece, + projection, + oColumn, + rScaleType + }) + } - if (d.type === "frame-hover") { - return htmlFrameHoverRule({ - d, - i, - rAccessor, - oAccessor, - projection, - tooltipContent, - optimizeCustomTooltipPosition, - projectedColumns, - useSpans, - pieceIDAccessor, - adjustedSize, - rScale, - type, - rScaleType - }) - } else if (d.type === "column-hover") { - return htmlColumnHoverRule({ - d, - i, - summaryType, - oAccessor, - projectedColumns, - type, - adjustedPosition, - adjustedSize, - projection, - tooltipContent, - optimizeCustomTooltipPosition, - useSpans - }) - } - return null + const flippedRScale = + projection === "vertical" + ? rScaleType.domain(rScale.domain()).range(rScale.range().reverse()) + : rScale + //TODO: Process your rules first + const customAnnotation = + htmlAnnotationRules && + htmlAnnotationRules({ + d, + i, + oScale, + rScale: flippedRScale, + oAccessor, + rAccessor, + orFrameProps: props, + screenCoordinates, + adjustedPosition, + adjustedSize, + annotationLayer, + orFrameState: state, + categories: state.projectedColumns, + voronoiHover + }) + + if (htmlAnnotationRules && customAnnotation !== null) { + return customAnnotation } - render() { - const { - className, - annotationSettings, - annotations, - matte, - renderKey, - interaction, - customClickBehavior, - customHoverBehavior, - customDoubleClickBehavior, + if (d.type === "frame-hover") { + return htmlFrameHoverRule({ + d, + i, + rAccessor, + oAccessor, projection, - backgroundGraphics, - foregroundGraphics, - afterElements, - beforeElements, - disableContext, - summaryType, - summaryHoverAnnotation, - pieceHoverAnnotation, - hoverAnnotation, - canvasPostProcess, - baseMarkProps, + tooltipContent, + optimizeCustomTooltipPosition, + projectedColumns, useSpans, - canvasPieces, - canvasSummaries, - canvasConnectors, - renderOrder, - additionalDefs, - sketchyRenderingEngine, - frameRenderOrder, - disableCanvasInteraction, - disableProgressiveRendering - } = this.props - - const { - orFrameRender, + pieceIDAccessor, + adjustedSize, + rScale, + type, + rScaleType + }) + } else if (d.type === "column-hover") { + return htmlColumnHoverRule({ + d, + i, + summaryType, + oAccessor, projectedColumns, + type, adjustedPosition, adjustedSize, - legendSettings, - columnOverlays, - axesTickLines, - axes, - margin, - pieceDataXY, - oLabels, - title - } = this.state - - const size = [ - adjustedSize[0] + margin.left + margin.right, - adjustedSize[1] + margin.top + margin.bottom - ] - - let interactionOverflow - - if (summaryType && summaryType.amplitude) { - if (projection === "horizontal") { - interactionOverflow = { - top: summaryType.amplitude, - bottom: 0, - left: 0, - right: 0 - } - } else if (projection === "radial") { - interactionOverflow = defaultOverflow - } else { - interactionOverflow = { - top: 0, - bottom: 0, - left: summaryType.amplitude, - right: 0 - } - } - } - - const renderedForegroundGraphics = - typeof foregroundGraphics === "function" - ? foregroundGraphics({ size, margin }) - : foregroundGraphics - - return ( - - ) + projection, + tooltipContent, + optimizeCustomTooltipPosition, + useSpans + }) } + return null } - -export default OrdinalFrame diff --git a/src/components/processing/ordinal.tsx b/src/components/processing/ordinal.tsx index 3f567254..f77494e5 100644 --- a/src/components/processing/ordinal.tsx +++ b/src/components/processing/ordinal.tsx @@ -303,9 +303,13 @@ export const calculateOrdinalFrame = ( { total: 0 } ) - const castOScaleType = oScaleType as unknown as Function + const castOScaleType = oScaleType as unknown as any - const oScale = dynamicColumnWidth ? scaleOrdinal() : castOScaleType() + const oScale = dynamicColumnWidth + ? scaleOrdinal() + : castOScaleType?.domain + ? castOScaleType + : castOScaleType() oScale.domain(oExtent) @@ -495,10 +499,11 @@ export const calculateOrdinalFrame = ( adjustedSize[0] ] - const castRScaleType = rScaleType as unknown as Function + const castRScaleType = rScaleType as unknown as any - const instantiatedRScaleType = rScaleType.domain - ? rScaleType + // if rScaleType has a domain that means it's instantiated, otherwise, it needs to be instantiated + const instantiatedRScaleType = castRScaleType.domain + ? castRScaleType : castRScaleType() const zeroCheck = instantiatedRScaleType(0) diff --git a/src/components/types/networkTypes.ts b/src/components/types/networkTypes.ts index 932854ec..51d59dbd 100644 --- a/src/components/types/networkTypes.ts +++ b/src/components/types/networkTypes.ts @@ -151,9 +151,6 @@ export interface NetworkFrameProps extends GeneralFrameProps { edgeType?: string | Function customNodeIcon?: Function customEdgeIcon?: Function - onNodeOut?: Function - onNodeClick?: Function - onNodeEnter?: Function renderOrder?: ReadonlyArray<"edges" | "nodes"> filterRenderedNodes: ( value?: NodeType, diff --git a/src/components/types/ordinalTypes.ts b/src/components/types/ordinalTypes.ts index 68516659..525288e7 100644 --- a/src/components/types/ordinalTypes.ts +++ b/src/components/types/ordinalTypes.ts @@ -80,8 +80,8 @@ export interface OrdinalFrameProps extends GeneralFrameProps { renderMode?: object | string | accessorType summaryRenderMode?: object | string | accessorType pixelColumnWidth?: number - oScaleType?: ScaleBand - rScaleType?: ScaleLinear + oScaleType?: any + rScaleType?: () => ScaleLinear data: Array oPadding?: number axes?: diff --git a/src/components/useLegacyUnmountCallback.ts b/src/components/useLegacyUnmountCallback.ts index 20fe5af8..7c29e162 100644 --- a/src/components/useLegacyUnmountCallback.ts +++ b/src/components/useLegacyUnmountCallback.ts @@ -7,7 +7,7 @@ export function useLegacyUnmountCallback(props: any, state: any) { let ref = useRef<[any, any]>() ref.current = [props, state] useEffect(() => { - ;() => { + return () => { const [props, state] = ref.current const onUnmount = props.onUnmount if (onUnmount) {