UNPKG

react-occult

Version:

Layered Information Visualization based on React and D3

338 lines (309 loc) 9.78 kB
import React, { useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { generateFrameTitle, toMarginGraphic } from './utils'; import { BaseProps, BaseDefaultProps } from './BaseProps'; import FilterDefs from '../widgets/FilterDefs'; import SpanOrDiv from '../widgets/SpanOrDiv'; import VisualizationLayer from '../layers/VisualizationLayer'; import InteractionLayer from '../layers/InteractionLayer'; import AnnotationLayer from '../layers/AnnotationLayer'; const getCanvasScale = context => { const devicePixelRatio = window.devicePixelRatio || 1; const backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; return devicePixelRatio / backingStoreRatio; }; const Frame = props => { const frontCanvasRef = useRef(null); const backCanvasRef = useRef(null); const [frontCanvas, setFrontCanvas] = useState(null); const [backCanvas, setBackCanvas] = useState(null); const [voronoiHover, setVoronoiHover] = useState(null); const updateCanvas = () => { if (frontCanvasRef && frontCanvasRef.current) { const _frontContext = frontCanvasRef.current.getContext('2d'); const canvasScale = getCanvasScale(_frontContext); _frontContext.scale(canvasScale, canvasScale); setFrontCanvas(frontCanvasRef.current); } if (backCanvasRef && backCanvasRef.current) { const _backContext = backCanvasRef.current.getContext('2d'); _backContext.mozImageSmoothingEnabled = false; _backContext.webkitImageSmoothingEnabled = false; _backContext.msImageSmoothingEnabled = false; _backContext.imageSmoothingEnabled = false; const canvasScale = getCanvasScale(_backContext); _backContext.scale(canvasScale, canvasScale); setBackCanvas(backCanvasRef.current); } }; useEffect(() => { updateCanvas(); }, []); const { // routine name, className, frameKey, useSpans, matte, width, height, margin, title, // render as it is foregroundGraphics, backgroundGraphics, additionalDefs, beforeElements, afterElements, canvasPostProcess, // generated frameXScale, frameYScale, canvasPipeline, svgPipeline, screenCoordinates, xyPoints, adjustedPosition, adjustedSize, // interaction overlay, interactionOverflow, disableCanvasInteraction, hoverAnnotation, interaction, customClickBehavior, customHoverBehavior, customDoubleClickBehavior, // children children, //todo: remove oLabels, axes, axesTickLines } = props; const size = [width, height]; const devicePixelRatio = window.devicePixelRatio || 1; const finalBackgroundGraphics = typeof backgroundGraphics === 'function' ? backgroundGraphics({ size, margin }) : backgroundGraphics; const finalForegroundGraphics = typeof foregroundGraphics === 'function' ? foregroundGraphics({ size, margin }) : foregroundGraphics; const userTitle = typeof title === 'object' && !React.isValidElement(title) && title !== null ? title : { title, orient: 'top' }; const generatedTitle = generateFrameTitle({ title: userTitle, size }); //todo: remove const marginGraphic = toMarginGraphic({ matte, size, margin, name }); const annotations = React.Children.toArray(children) .filter(d => d.type.name === 'Annotation') .map(d => d.props); if (voronoiHover) { if (Array.isArray(voronoiHover)) { annotations.push(...voronoiHover); } else { annotations.push(voronoiHover); } } // todo: put here? if (props.nodeLabelAnnotations) { annotations.push(...props.nodeLabelAnnotations); } const { generateSVGAnnotations, generateHTMLAnnotations } = props; const annotationLayer = annotations && annotations.length > 0 && ( <AnnotationLayer annotations={annotations} voronoiHover={voronoiHover} generateSVGAnnotations={generateSVGAnnotations} generateHTMLAnnotations={generateHTMLAnnotations} margin={margin} useSpans={useSpans} size={adjustedSize} position={[ adjustedPosition[0] + margin.left, adjustedPosition[1] + margin.top ]} /> ); return ( <SpanOrDiv span={useSpans} className={`${className} frame ${name}`}> {beforeElements && ( <SpanOrDiv span={useSpans} className={`${name} frame-before-elements`}> {beforeElements} </SpanOrDiv> )} <SpanOrDiv span={useSpans} className="frame-elements" style={{ height: `${height}px`, width: `${width}px` }} > <SpanOrDiv span={useSpans} className="visualization-layer" style={{ position: 'absolute' }} > {(axesTickLines || backgroundGraphics) && ( <svg className="background-graphics" style={{ position: 'absolute' }} width={width} height={height} > {backgroundGraphics && ( <g aria-hidden={true} className="background-graphics"> {finalBackgroundGraphics} </g> )} {axesTickLines && ( <g transform={`translate(${margin.left},${margin.top})`} key="visualization-tick-lines" className={'axis axis-tick-lines'} aria-hidden={true} > {axesTickLines} </g> )} </svg> )} <canvas className="frame-canvas frame-canvas-front" ref={frontCanvasRef} style={{ position: 'absolute', left: `0px`, top: `0px`, width: `${width}px`, height: `${height}px` }} width={width * devicePixelRatio} height={height * devicePixelRatio} /> <canvas className="frame-canvas frame-canvas-hidden" ref={backCanvasRef} style={{ position: 'absolute', left: `0px`, top: `0px`, width: `${width}px`, height: `${height}px` }} width={width * devicePixelRatio} height={height * devicePixelRatio} /> <svg className="visualization-layer" style={{ position: 'absolute' }} width={width} height={height} > <FilterDefs matte={marginGraphic} key={frameKey || name} additionalDefs={additionalDefs} /> <VisualizationLayer title={generatedTitle} frameKey={frameKey} width={width} height={height} size={adjustedSize} position={adjustedPosition} frontCanvas={frontCanvas} backCanvas={backCanvas} matte={marginGraphic} margin={margin} canvasPostProcess={canvasPostProcess} canvasPipeline={canvasPipeline} voronoiHover={setVoronoiHover} > {axes && ( <g key="visualization-axis-labels" className="axis axis-labels"> {axes} </g> )} {svgPipeline} </VisualizationLayer> {generatedTitle && <g className="frame-title">{generatedTitle}</g>} {foregroundGraphics || (oLabels && ( <g aria-hidden={true} className="foreground-graphics"> {finalForegroundGraphics} {oLabels} </g> ))} </svg> </SpanOrDiv> <InteractionLayer useSpans={useSpans} hoverAnnotation={hoverAnnotation} interaction={interaction} voronoiHover={setVoronoiHover} customClickBehavior={customClickBehavior} customHoverBehavior={customHoverBehavior} customDoubleClickBehavior={customDoubleClickBehavior} position={adjustedPosition} margin={margin} size={adjustedSize} svgSize={size} xScale={frameXScale} yScale={frameYScale} data={screenCoordinates} enabled={true} useCanvas={canvasPipeline.length > 0} overlay={overlay} interactionOverflow={interactionOverflow} disableCanvasInteraction={disableCanvasInteraction} /> {annotationLayer} {afterElements && ( <SpanOrDiv span={useSpans} className={`${name} frame-after-elements`}> {afterElements} </SpanOrDiv> )} </SpanOrDiv> </SpanOrDiv> ); }; Frame.propTypes = { ...BaseProps, // generated frameXScale: PropTypes.func, frameYScale: PropTypes.func, annotationLayer: PropTypes.node, canvasPipeline: PropTypes.array, svgPipeline: PropTypes.array, // todo: duplicated? screenCoordinates: PropTypes.array, xyPoints: PropTypes.array, adjustedPosition: PropTypes.array, adjustedSize: PropTypes.array, plotChildren: PropTypes.array, axes: PropTypes.array, axesTickLines: PropTypes.array }; Frame.defaultProps = { ...BaseDefaultProps, adjustedPosition: [0, 0], annotationLayer: null, axes: null, axesTickLines: null, canvasPipeline: [], svgPipeline: [], screenCoordinates: [] }; export default Frame;