UNPKG

terriajs

Version:

Geospatial data visualization platform.

140 lines (128 loc) 4.06 kB
import { scaleLinear } from "@visx/scale"; import { interpolateNumber as d3InterpolateNumber } from "d3-interpolate"; import { computed } from "mobx"; import { observer } from "mobx-react"; import PropTypes from "prop-types"; import React from "react"; import Glyphs from "./Glyphs"; import { GlyphCircle } from "@visx/glyph"; @observer class MomentPointsChart extends React.Component { static propTypes = { id: PropTypes.string.isRequired, chartItem: PropTypes.object.isRequired, basisItem: PropTypes.object, basisItemScales: PropTypes.object, scales: PropTypes.object.isRequired, glyph: PropTypes.string }; static defaultProps = { glyph: "circle" }; @computed get points() { const { chartItem, basisItem, basisItemScales, scales } = this.props; if (basisItem) { // We want to stick the chartItem points to the basis item, to do this we // interpolate the chart item points to match the basis item points. This // interpolation should not affect the scale of the chart item points. const basisToSourceScale = scaleLinear({ domain: basisItemScales.y.domain(), range: scales.y.domain() }); const interpolatedPoints = chartItem.points.map((p) => ({ ...p, ...interpolate(p, basisItem.points, basisToSourceScale) })); return interpolatedPoints; } return chartItem.points; } doZoom(scales) { const points = this.points; if (points.length === 0) { return; } const glyphs = document.querySelectorAll( `g#${this.props.id} > g.visx-glyph` ); glyphs.forEach((glyph, i) => { const point = points[i]; if (point) { const left = scales.x(point.x); const top = scales.y(point.y); const scale = point.isSelected ? "scale(1.4, 1.4)" : ""; glyph.setAttribute("transform", `translate(${left}, ${top}) ${scale}`); glyph.setAttribute("fill-opacity", point.isSelected ? 1.0 : 0.3); } }); } render() { const { id, chartItem, scales, glyph } = this.props; const baseKey = `moment-point-${chartItem.categoryName}-${chartItem.name}`; const fillColor = chartItem.getColor(); const isClickable = chartItem.onClick !== undefined; const clickProps = (point) => { if (isClickable) { return { pointerEvents: "all", cursor: "pointer", onClick: () => chartItem.onClick(point) }; } return {}; }; const Glyph = Glyphs[glyph] ?? GlyphCircle; return ( <g id={id}> <For each="p" index="i" of={this.points}> <Glyph key={`${baseKey}-${i}`} left={scales.x(p.x)} top={scales.y(p.y)} size={100} fill={fillColor} fillOpacity={p.isSelected ? 1.0 : 0.3} {...clickProps(p)} /> </For> </g> ); } } /** Interpolates the given source point {x, y} to the closet point in the `sortedPoints` array. * * The source point and `sortedBasisPoints` may be of different scale, so we use `basisToSourceScale` * to generate a point in the original source items scale. */ function interpolate({ x, y }, sortedBasisPoints, basisToSourceScale) { const closest = closestPointIndex(x, sortedBasisPoints); if (closest === undefined) { return { x, y }; } const a = sortedBasisPoints[closest]; const b = sortedBasisPoints[closest + 1]; if (a === undefined || b === undefined) { return { x, y }; } const xAsPercentage = (x.getTime() - a.x.getTime()) / (b.x.getTime() - a.x.getTime()); const interpolated = { x, y: d3InterpolateNumber( basisToSourceScale(a.y), basisToSourceScale(b.y) )(xAsPercentage) }; return interpolated; } function closestPointIndex(x, sortedPoints) { for (let i = 0; i < sortedPoints.length; i++) { if (sortedPoints[i].x.getTime() >= x.getTime()) { if (i === 0) return 0; return i - 1; } } return; } export default MomentPointsChart;