UNPKG

terriajs

Version:

Geospatial data visualization platform.

151 lines 6.94 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { AxisBottom, AxisLeft } from "@visx/axis"; import { Group } from "@visx/group"; import { useParentSize } from "@visx/responsive"; import { scaleLinear, scaleTime } from "@visx/scale"; import { observer } from "mobx-react"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import styled from "styled-components"; import ChartableMixin from "../../../ModelMixins/ChartableMixin"; import MappableMixin from "../../../ModelMixins/MappableMixin"; import LineChart from "./LineChart"; import Styles from "./chart-preview.scss"; const defaultMargin = { top: 5, left: 5, right: 5, bottom: 5 }; /** * Chart component for feature info panel popup */ const FeatureInfoPanelChart = observer((props) => { const [loadingFailed, setLoadingFailed] = useState(false); const { t } = useTranslation(); const parentSize = useParentSize(); const width = props.width || Math.max(parentSize.width, 300) || 0; const height = props.height || Math.max(parentSize.height, 200) || 0; const catalogItem = props.item; // If a yColumn is specified, use it if it is of line type, otherwise use // the first line type chart item. let chartItem = props.yColumn ? catalogItem.chartItems.find((it) => it.id === props.yColumn) : catalogItem.chartItems.find(isLineType); chartItem = chartItem && isLineType(chartItem) ? chartItem : undefined; const notChartable = !ChartableMixin.isMixedInto(catalogItem); const isLoading = !chartItem && MappableMixin.isMixedInto(catalogItem) && catalogItem.isLoadingMapItems; const noData = !chartItem || chartItem.points.length === 0; // Text to show when chart is not ready or available const chartStatus = notChartable ? "chart.noData" : isLoading ? "chart.loading" : loadingFailed ? "chart.noData" : noData ? "chart.noData" : undefined; const canShowChart = chartStatus === undefined; const margin = { ...defaultMargin, ...props.margin }; const baseColor = props.baseColor ?? "#efefef"; useEffect(() => { if (MappableMixin.isMixedInto(catalogItem)) { catalogItem.loadMapItems().then((result) => { setLoadingFailed(result.error !== undefined); result.logError(); }); } else { setLoadingFailed(false); } }, [catalogItem]); return (_jsxs("div", { className: Styles.previewChart, ref: parentSize.parentRef, children: [!canShowChart && (_jsx(ChartStatusText, { width: width, height: height, children: t(chartStatus) })), canShowChart && chartItem && (_jsx(Chart, { width: width, height: height, margin: margin, chartItem: chartItem, baseColor: baseColor, xAxisLabel: props.xAxisLabel }))] })); }); const isLineType = (chartItem) => chartItem.type === "line" || chartItem.type === "lineAndPoint"; /** * Private Chart component that renders the SVG chart */ const Chart = observer(({ width, height, margin, chartItem, baseColor, xAxisLabel }) => { const xAxisHeight = 30; const yAxisWidth = 10; const plot = useMemo(() => { return { width: width - margin.left - margin.right, height: height - margin.top - margin.bottom - xAxisHeight }; }, [width, height, margin]); const scales = useMemo(() => { const xScaleParams = { domain: chartItem.domain.x, range: [margin.left + yAxisWidth, plot.width] }; const yScaleParams = { domain: chartItem.domain.y, range: [plot.height, 0] }; return { x: chartItem.xAxis.scale === "linear" ? scaleLinear(xScaleParams) : scaleTime(xScaleParams), y: scaleLinear(yScaleParams) }; }, [ chartItem.domain.x, chartItem.domain.y, chartItem.xAxis.scale, margin.left, plot.height, plot.width ]); const textStyle = { fill: baseColor, fontSize: 10, textAnchor: "middle", fontFamily: "Arial" }; const chartLabel = xAxisLabel ?? defaultChartLabel({ xName: chartItem.xAxis.name, xUnits: chartItem.xAxis.units, yName: chartItem.name, yUnits: chartItem.units }); useEffect(() => { chartItem.points = chartItem.points.sort((a, b) => scales.x(a.x) - scales.x(b.x)); }); return (_jsx("svg", { width: width, height: height, children: _jsxs(Group, { top: margin.top, left: margin.left, children: [_jsx(AxisBottom, { top: plot.height, // .nice() rounds the scale so that the aprox beginning and // aprox end labels are shown // See: https://stackoverflow.com/questions/21753126/d3-js-starting-and-ending-tick scale: scales.x.nice(), numTicks: 4, stroke: "#a0a0a0", tickStroke: "#a0a0a0", tickLabelProps: (_value, i, ticks) => { // To prevent the first and last values from getting clipped, // we position the first label text to start at the tick position // and the last label text to finish at the tick position. For all // others, middle of the text will coincide with the tick position. const textAnchor = i === 0 ? "start" : i === ticks.length - 1 ? "end" : "middle"; return { ...textStyle, textAnchor }; }, label: chartLabel, labelOffset: 3, labelProps: { fill: baseColor, fontSize: 10, textAnchor: "middle", fontFamily: "Arial" } }), _jsx(AxisLeft, { scale: scales.y, numTicks: 4, stroke: "none", tickStroke: "none", tickLabelProps: () => ({ ...textStyle, textAnchor: "start", dx: "1em", dy: "0" }) }), _jsx(LineChart, { id: `featureInfoPanelChart-${chartItem.name}`, chartItem: chartItem, scales: scales, color: baseColor })] }) })); }); Chart.displayName = "Chart"; export const ChartStatusText = styled.div ` display: flex; align-items: center; justify-content: center; width: ${(p) => p.width}px; height: ${(p) => p.height}px; `; const defaultChartLabel = (opts) => `${withUnits(opts.yName, opts.yUnits)} x ${withUnits(opts.xName, opts.xUnits)}`; const withUnits = (name, units) => units ? `${name} (${units})` : name; export default FeatureInfoPanelChart; //# sourceMappingURL=FeatureInfoPanelChart.js.map