UNPKG

@deck.gl-community/layers

Version:

Add-on layers for deck.gl

300 lines (274 loc) 10.3 kB
// deck.gl-community // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {CompositeLayer} from '@deck.gl/core'; import {ArcLayer, LineLayer, PathLayer} from '@deck.gl/layers'; import {Vector3} from '@math.gl/core'; import {createPathMarkers, PathDirection} from './create-path-markers'; import {GeometryLayer} from './geometry-layer'; import type { MarkerPlacementsAccessor, PathDirectionAccessor, PathGeometry, PathMarker } from './create-path-markers'; import type { Accessor, Color, DefaultProps, GetPickingInfoParams, Layer, LayerDataSource, LayerProps, Position, UpdateParameters } from '@deck.gl/core'; import type {ArcLayerProps, LineLayerProps, PathLayerProps} from '@deck.gl/layers'; export {PathDirection}; export type { MarkerPlacementsAccessor, MarkerPlacementsAccessorContext, PathDirectionAccessor, PathGeometry, PathMarker } from './create-path-markers'; /** Properties supported by {@link DependencyArrowLayer}. */ export type DependencyArrowLayerProps<DataT = unknown> = LayerProps & _DependencyArrowLayerProps<DataT>; type _DependencyArrowLayerProps<DataT = unknown> = { /** Dependency data rendered by the layer. */ data: LayerDataSource<DataT>; /** Dependency routing mode. @defaultValue 'path' */ mode?: 'path' | 'line' | 'arc'; /** Accessor returning nested or flat dependency path coordinates. */ getPath?: PathLayerProps<DataT>['getPath']; /** Accessor returning dependency line color. */ getColor?: LineLayerProps<DataT>['getColor']; /** Accessor returning dependency line width. */ getWidth?: LineLayerProps<DataT>['getWidth']; /** Units used by dependency line width. */ widthUnits?: LineLayerProps<DataT>['widthUnits']; /** Scale applied to dependency line width. */ widthScale?: LineLayerProps<DataT>['widthScale']; /** Minimum rendered dependency line width in pixels. */ widthMinPixels?: LineLayerProps<DataT>['widthMinPixels']; /** Maximum rendered dependency line width in pixels. */ widthMaxPixels?: LineLayerProps<DataT>['widthMaxPixels']; /** Arc segment count used when `mode` is `'arc'`. */ arcNumSegments?: ArcLayerProps<DataT>['numSegments']; /** Accessor returning arc height when `mode` is `'arc'`. */ getArcHeight?: ArcLayerProps<DataT>['getHeight']; /** Accessor returning arc tilt when `mode` is `'arc'`. */ getArcTilt?: ArcLayerProps<DataT>['getTilt']; /** Accessor returning marker direction flags. @defaultValue PathDirection.FORWARD */ getDirection?: PathDirectionAccessor<DataT>; /** Marker color accessor; falls back to `getColor` when omitted. */ getMarkerColor?: Accessor<DataT, Color>; /** Accessor returning marker ratios along the path, from 0 at start to 1 at end. */ getMarkerPlacements?: MarkerPlacementsAccessor<DataT>; /** Marker size accessor in marker-local `[width, height]` units. */ getMarkerSize?: Accessor<DataT, [number, number]>; /** Optional point used by callers to identify a highlighted dependency location. */ highlightPoint?: Position | Vector3 | null; /** Optional source datum index used with `highlightPoint`. */ highlightIndex?: number; /** Marker size multiplier. @defaultValue 10 */ markerSizeScale?: number; }; const defaultProps: DefaultProps<_DependencyArrowLayerProps> = { getPath: PathLayer.defaultProps.getPath, getColor: LineLayer.defaultProps.getColor, getWidth: LineLayer.defaultProps.getWidth, widthUnits: LineLayer.defaultProps.widthUnits, widthScale: LineLayer.defaultProps.widthScale, widthMinPixels: LineLayer.defaultProps.widthMinPixels, widthMaxPixels: LineLayer.defaultProps.widthMaxPixels, arcNumSegments: ArcLayer.defaultProps.numSegments, getArcHeight: ArcLayer.defaultProps.getHeight, getArcTilt: ArcLayer.defaultProps.getTilt, mode: 'path', markerSizeScale: 10, highlightIndex: -1, highlightPoint: null, getMarkerColor: {type: 'accessor', value: undefined}, getDirection: {type: 'accessor', value: PathDirection.FORWARD}, getMarkerSize: {type: 'accessor', value: [1, 1]}, getMarkerPlacements: {type: 'accessor', value: [0.5]} }; /** Renders paths, lines, or arcs with directional dependency markers. */ export class DependencyArrowLayer< DataT = any, ExtraPropsT = Record<string, unknown> > extends CompositeLayer<ExtraPropsT & Required<_DependencyArrowLayerProps<DataT>>> { static override layerName = 'DependencyArrowLayer'; static override defaultProps = defaultProps; override state: { markers: PathMarker<DataT>[]; } = { markers: [] }; override updateState({props, oldProps, changeFlags}: UpdateParameters<this>) { const shouldRebuildMarkers = changeFlags.dataChanged || props.mode !== oldProps.mode || props.positionFormat !== oldProps.positionFormat || props.getPath !== oldProps.getPath || props.getDirection !== oldProps.getDirection || props.getMarkerPlacements !== oldProps.getMarkerPlacements || (changeFlags.updateTriggersChanged && (changeFlags.updateTriggersChanged['getPath'] || changeFlags.updateTriggersChanged['getDirection'] || changeFlags.updateTriggersChanged['getMarkerPlacements'])); if (shouldRebuildMarkers) { const {data, mode, getPath, getDirection, getMarkerPlacements} = this.props; this.state.markers = createPathMarkers<DataT>({ data: data as Iterable<DataT>, positionSize: props.positionFormat.length, getPath, getDirection, getMarkerPlacements, mode }); } } override getPickingInfo({info}: GetPickingInfoParams) { const pickedObject = info.object; if (pickedObject && pickedObject.__source) { info.object = (pickedObject as PathMarker<DataT>).__source.object; } return info; } renderLayers() { const { mode, getPath, getColor, getMarkerColor, getMarkerSize, markerSizeScale, updateTriggers = {} } = this.props; let pathLayer: Layer | null = null; if (mode === 'path') { pathLayer = new PathLayer( this.props, this.getSubLayerProps({ id: 'links-path', updateTriggers: { getPath: updateTriggers['getPath'], getColor: updateTriggers['getColor'], getWidth: updateTriggers['getWidth'] } }) ); } else { const positionSize = this.props.positionFormat.length; const sharedProps = { ...this.props, data: this.props.data, getSourcePosition: (datum, info) => { const path = getPath(datum, info); return getFirstPoint(path, positionSize) ?? [NaN, NaN]; }, getTargetPosition: (datum, info) => { const path = getPath(datum, info); return getLastPoint(path, positionSize) ?? [NaN, NaN]; } } satisfies LineLayerProps<DataT>; if (mode === 'arc') { pathLayer = new ArcLayer<DataT>( sharedProps, { getSourceColor: this.props.getColor, getTargetColor: this.props.getColor, numSegments: this.props.arcNumSegments, getHeight: this.props.getArcHeight, getTilt: this.props.getArcTilt }, this.getSubLayerProps({ id: 'links-arc', updateTriggers: { getSourcePosition: updateTriggers['getPath'], getTargetPosition: updateTriggers['getPath'], getSourceColor: updateTriggers['getColor'], getTargetColor: updateTriggers['getColor'], getWidth: updateTriggers['getWidth'], getHeight: updateTriggers['getArcHeight'], getTilt: updateTriggers['getArcTilt'] } }) ); } else { pathLayer = new LineLayer<DataT>( sharedProps, this.getSubLayerProps({ id: 'links-line', updateTriggers: { getSourcePosition: updateTriggers['getPath'], getTargetPosition: updateTriggers['getPath'], getColor: updateTriggers['getColor'], getWidth: updateTriggers['getWidth'] } }) ); } } return [ pathLayer, new GeometryLayer<PathMarker<DataT>>( this.getSubLayerProps({ id: 'arrows', updateTriggers: { getSize: updateTriggers['getMarkerSize'], getColor: getMarkerColor ? updateTriggers['getMarkerColor'] : updateTriggers['getColor'], getArcHeight: updateTriggers['getArcHeight'], getArcTilt: updateTriggers['getArcTilt'] } }), { data: this.state.markers, sizeUnits: 'pixels', sizeScale: markerSizeScale, interpolationMode: mode === 'arc' ? 'arc' : 'line', getSourcePosition: d => d.source, getTargetPosition: d => d.target, getPositionRatio: d => d.percentage, getSize: this.getSubLayerAccessor(getMarkerSize) as Accessor< PathMarker<DataT>, [number, number] >, getColor: this.getSubLayerAccessor(getMarkerColor ?? getColor) as Accessor< PathMarker<DataT>, Color >, getArcHeight: this.getSubLayerAccessor(this.props.getArcHeight) as Accessor< PathMarker<DataT>, number >, getArcTilt: this.getSubLayerAccessor(this.props.getArcTilt) as Accessor< PathMarker<DataT>, number >, getPickingColor: d => this.encodePickingColor(d.__source.index) } ) ]; } } function getFirstPoint(path: PathGeometry, size: number): Position | null { if (!path || path.length === 0) return null; if (Array.isArray(path[0])) { return (path as Position[])[0]!; } return path.slice(0, size) as Position; } function getLastPoint(path: PathGeometry, size: number): Position | null { if (!path || path.length === 0) return null; if (Array.isArray(path[0])) { return (path as Position[])[path.length - 1]!; } const len = Math.floor(path.length / size) * size; return path.slice(len - size, len) as Position; }