diff --git a/apps/fishing-map/features/map/Map.tsx b/apps/fishing-map/features/map/Map.tsx index f330f06c20..f89b52e06f 100644 --- a/apps/fishing-map/features/map/Map.tsx +++ b/apps/fishing-map/features/map/Map.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from 'react' +import { Fragment, useCallback, useEffect, useRef } from 'react' import { useSelector } from 'react-redux' import { DeckGL, DeckGLRef } from '@deck.gl/react' import dynamic from 'next/dynamic' @@ -135,11 +135,15 @@ const MapWrapper = () => { > - {isMapDrawing && } + {isMapDrawing && ( + + + + + )} - {isWorkspaceLocation && !isReportLocation && ( diff --git a/apps/fishing-map/features/map/map-layers.hooks.ts b/apps/fishing-map/features/map/map-layers.hooks.ts index 428771527d..3b0a34ff4a 100644 --- a/apps/fishing-map/features/map/map-layers.hooks.ts +++ b/apps/fishing-map/features/map/map-layers.hooks.ts @@ -11,6 +11,7 @@ import { } from '@globalfishingwatch/deck-layer-composer' import { GFWAPI } from '@globalfishingwatch/api-client' import { FourwingsLayer, HEATMAP_ID } from '@globalfishingwatch/deck-layers' +import { UrlDataviewInstance } from '@globalfishingwatch/dataviews-client' import { AsyncReducerStatus } from 'utils/async-slice' import { selectWorkspaceStatus, @@ -22,7 +23,11 @@ import { selectIsUserLocation, selectIsWorkspaceLocation, } from 'routes/routes.selectors' -import { selectDataviewInstancesResolvedVisible } from 'features/dataviews/selectors/dataviews.selectors' +import { + selectActivityMergedDataviewId, + selectDataviewInstancesResolvedVisible, + selectDetectionsMergedDataviewId, +} from 'features/dataviews/selectors/dataviews.selectors' import { selectBivariateDataviews, selectActivityVisualizationMode, @@ -48,6 +53,18 @@ import { useDrawLayerInstance } from './overlays/draw/draw.hooks' import { useMapViewState } from './map-viewport.hooks' import { selectClickedEvent } from './map.slice' +export const useActivityDataviewId = (dataview: UrlDataviewInstance) => { + const activityMergedDataviewId = useSelector(selectActivityMergedDataviewId) + const detectionsMergedDataviewId = useSelector(selectDetectionsMergedDataviewId) + const dataviewId = + dataview.category === DataviewCategory.Environment + ? dataview.id + : dataview.category === DataviewCategory.Detections + ? detectionsMergedDataviewId + : activityMergedDataviewId + return dataviewId +} + export const useGlobalConfigConnect = () => { const { start, end } = useTimerangeConnect() const timebarHighlightedTime = useSelector(selectHighlightedTime) diff --git a/apps/fishing-map/features/map/overlays/draw/CoordinateEditOverlay.tsx b/apps/fishing-map/features/map/overlays/draw/CoordinateEditOverlay.tsx index 643aec16d6..7b2782c58b 100644 --- a/apps/fishing-map/features/map/overlays/draw/CoordinateEditOverlay.tsx +++ b/apps/fishing-map/features/map/overlays/draw/CoordinateEditOverlay.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next' -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useSelector } from 'react-redux' import { Feature, Polygon } from 'geojson' import { Button, IconButton, InputText } from '@globalfishingwatch/ui-components' @@ -25,6 +25,13 @@ export const CoordinateEditOverlay = () => { const editingPointLongitude = newPointLongitude !== null ? Number(newPointLongitude) : Number(currentPointCoordinates?.[0]) + useEffect(() => { + if (!currentPointCoordinates?.length) { + setNewPointLatitude(null) + setNewPointLongitude(null) + } + }, [currentPointCoordinates]) + const allowDeletePoint = drawingMode === 'polygons' ? drawData && @@ -65,12 +72,6 @@ export const CoordinateEditOverlay = () => { [drawLayer, editingPointLatitude] ) - const onDeletePoint = useCallback(() => { - if (allowDeletePoint) { - drawLayer?.deleteSelectedPosition() - } - }, [allowDeletePoint, drawLayer]) - const resetEditingPoint = useCallback(() => { // As this is triggered with clickOutside we need to wait to reset // in case before the deleteSelectedPosition is called @@ -81,6 +82,13 @@ export const CoordinateEditOverlay = () => { }, 1) }, [drawLayer]) + const onDeletePoint = useCallback(() => { + if (allowDeletePoint) { + drawLayer?.deleteSelectedPosition() + resetEditingPoint() + } + }, [allowDeletePoint, drawLayer, resetEditingPoint]) + const onConfirm = useCallback(() => { drawLayer?.setCurrentPointCoordinates([editingPointLongitude, editingPointLatitude]) resetEditingPoint() diff --git a/apps/fishing-map/features/map/popups/categories/ContextLayers.hooks.ts b/apps/fishing-map/features/map/popups/categories/ContextLayers.hooks.ts index 4abef55d69..5d4ba0bdc3 100644 --- a/apps/fishing-map/features/map/popups/categories/ContextLayers.hooks.ts +++ b/apps/fishing-map/features/map/popups/categories/ContextLayers.hooks.ts @@ -17,7 +17,6 @@ export const getAreaIdFromFeature = ( ): AreaKeyId => { return ( feature.properties?.gfw_id || - // TODO:deck check if promoteId is covered for every case in the getPickingInfo function feature.properties?.[(feature as any).promoteId as string] || (feature.id as string) ) @@ -63,20 +62,6 @@ export const useContextInteractions = () => { const setReportArea = useCallback( (feature: ContextPickingObject | UserLayerPickingObject) => { const { title, value } = feature - // TODO:deck review this - // const areaId = getAreaIdFromFeature(feature) as string - // Report already does it on page reload but to avoid waiting - // this moves the map to the same position - // const bounds = getFeatureBounds(feature) - // if (bounds) { - // const boundsParams = { - // padding: FIT_BOUNDS_REPORT_PADDING, - // mapWidth: window.innerWidth / 2, - // mapHeight: window.innerHeight - TIMEBAR_HEIGHT - FOOTER_HEIGHT, - // } - // fitMapBounds(bounds, boundsParams) - // } - dispatchClickedEvent(null) trackEvent({ diff --git a/apps/fishing-map/features/timebar/TimebarSettings.tsx b/apps/fishing-map/features/timebar/TimebarSettings.tsx index dab7b4f74f..6e44644517 100644 --- a/apps/fishing-map/features/timebar/TimebarSettings.tsx +++ b/apps/fishing-map/features/timebar/TimebarSettings.tsx @@ -70,8 +70,7 @@ const TimebarSettings = ({ loading = false }: { loading: boolean }) => { const isStandaloneVesselLocation = useSelector(selectIsVesselLocation) const vesselIds = activeTrackDataviews.map((v) => v.id) const vesselLayers = useGetDeckLayers(vesselIds) - // TODO:deck better validation of the layer contains data - const hasTracksData = vesselLayers?.length > 0 + const hasTracksData = vesselLayers?.some((layer) => layer?.instance.getVesselTracksLayersLoaded()) const activeVesselsDataviews = useSelector(selectActiveVesselsDataviews) const { timebarVisualisation, dispatchTimebarVisualisation } = useTimebarVisualisationConnect() const { timebarSelectedEnvId, dispatchTimebarSelectedEnvId } = useTimebarEnvironmentConnect() diff --git a/apps/fishing-map/features/vessel/activity/VesselActivity.tsx b/apps/fishing-map/features/vessel/activity/VesselActivity.tsx index 5a1a76fa8c..48bbdade91 100644 --- a/apps/fishing-map/features/vessel/activity/VesselActivity.tsx +++ b/apps/fishing-map/features/vessel/activity/VesselActivity.tsx @@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next' import { Fragment, useMemo } from 'react' import { useSelector } from 'react-redux' import { Choice, ChoiceOption, Spinner } from '@globalfishingwatch/ui-components' +import { useDebounce } from '@globalfishingwatch/react-hooks' import ActivityByType from 'features/vessel/activity/activity-by-type/ActivityByType' import ActivityByVoyage from 'features/vessel/activity/activity-by-voyage/ActivityByVoyage' import { VesselActivitySummary } from 'features/vessel/activity/VesselActivitySummary' @@ -21,6 +22,7 @@ const VesselActivity = () => { const activityMode = useSelector(selectVesselActivityMode) const hasEventsDataset = useSelector(selectVesselHasEventsDatasets) const eventsLoading = useVesselProfileEventsLoading() + const eventsLoadingDebounce = useDebounce(eventsLoading, 400) const eventsError = useVesselProfileEventsError() const vesselProfileDataview = useSelector(selectVesselProfileDataview) const vesselLayer = useVesselProfileLayer() @@ -49,7 +51,7 @@ const VesselActivity = () => { [t] ) - if (hasVesselEvents && (!vesselLayer?.instance || eventsLoading)) { + if (hasVesselEvents && (!vesselLayer?.instance || eventsLoadingDebounce)) { return (
@@ -87,13 +89,13 @@ const VesselActivity = () => { onSelect={setActivityMode} />
- {eventsLoading && ( + {eventsLoadingDebounce && (
)} - {!eventsLoading && activityMode === 'type' && } - {!eventsLoading && activityMode === 'voyage' && } + {!eventsLoadingDebounce && activityMode === 'type' && } + {!eventsLoadingDebounce && activityMode === 'voyage' && } ) } diff --git a/apps/fishing-map/features/vessel/vessel-events.hooks.ts b/apps/fishing-map/features/vessel/vessel-events.hooks.ts index eed9d20d7f..b8cb4c8f6a 100644 --- a/apps/fishing-map/features/vessel/vessel-events.hooks.ts +++ b/apps/fishing-map/features/vessel/vessel-events.hooks.ts @@ -25,7 +25,6 @@ const useVesselProfileEvents = () => { export const useVesselProfileEventsLoading = () => { const vesselInstance = useVesselProfileLayer() - // TODO:deck review this and try to avoid intermediate loading states while toggled on events load return vesselInstance?.instance && !vesselInstance?.instance?.getVesselEventsLayersLoaded() } diff --git a/apps/fishing-map/features/workspace/activity/ActivityLayerPanel.tsx b/apps/fishing-map/features/workspace/activity/ActivityLayerPanel.tsx index d6ac210863..9726c2c566 100644 --- a/apps/fishing-map/features/workspace/activity/ActivityLayerPanel.tsx +++ b/apps/fishing-map/features/workspace/activity/ActivityLayerPanel.tsx @@ -8,7 +8,8 @@ import { UrlDataviewInstance, } from '@globalfishingwatch/dataviews-client' import { DatasetTypes } from '@globalfishingwatch/api-types' -import { useDeckLayerLoadedState } from '@globalfishingwatch/deck-layer-composer' +import { useGetDeckLayer } from '@globalfishingwatch/deck-layer-composer' +import { FourwingsLayer } from '@globalfishingwatch/deck-layers' import { useDataviewInstancesConnect } from 'features/workspace/workspace.hook' import { selectBivariateDataviews, selectReadOnly } from 'features/app/selectors/app.selectors' import { useLocationConnect } from 'routes/routes.hook' @@ -23,8 +24,10 @@ import { SAR_DATAVIEW_SLUG } from 'data/workspaces' import DatasetNotFound from 'features/workspace/shared/DatasetNotFound' import styles from 'features/workspace/shared/LayerPanel.module.css' import Color from 'features/workspace/common/Color' +import { selectIsGFWUser } from 'features/user/selectors/user.selectors' import { TrackCategory, trackEvent } from 'features/app/analytics.hooks' import MapLegend from 'features/workspace/common/MapLegend' +import { useActivityDataviewId } from 'features/map/map-layers.hooks' import DatasetFilterSource from '../shared/DatasetSourceField' import DatasetFlagField from '../shared/DatasetFlagsField' import DatasetSchemaField from '../shared/DatasetSchemaField' @@ -57,14 +60,15 @@ function ActivityLayerPanel({ const { deleteDataviewInstance, upsertDataviewInstance } = useDataviewInstancesConnect() const { dispatchQueryParams } = useLocationConnect() + const isGFWUser = useSelector(selectIsGFWUser) const bivariateDataviews = useSelector(selectBivariateDataviews) const hintsDismissed = useSelector(selectHintsDismissed) const readOnly = useSelector(selectReadOnly) const layerActive = dataview?.config?.visible ?? true - const layerLoadedState = useDeckLayerLoadedState() - const layerLoaded = Object.entries(layerLoadedState).some( - ([id, state]) => id.split(',').includes(dataview.id) && state.loaded - ) + const dataviewId = useActivityDataviewId(dataview) + const activityLayer = useGetDeckLayer(dataviewId) + const layerLoaded = activityLayer?.loaded + const layerError = activityLayer?.instance?.getError?.() // TODO remove when final decission on stats display is taken // const urlTimeRange = useSelector(selectUrlTimeRange) @@ -256,10 +260,25 @@ function ActivityLayerPanel({ {!readOnly && ( )} + {!readOnly && layerError && ( + + )} { @@ -48,15 +44,7 @@ const MapLegendWrapper = ({ showPlaceholder?: boolean }) => { const { t } = useTranslation() - // TODO: restore useTimeCompareTimeDescription and delete the component in the map folder - const activityMergedDataviewId = useSelector(selectActivityMergedDataviewId) - const detectionsMergedDataviewId = useSelector(selectDetectionsMergedDataviewId) - const dataviewId = - dataview.category === DataviewCategory.Environment - ? dataview.id - : dataview.category === DataviewCategory.Detections - ? detectionsMergedDataviewId - : activityMergedDataviewId + const dataviewId = useActivityDataviewId(dataview) const deckLegend = getLegendLabelTranslated(useGetDeckLayerLegend(dataviewId)) const isBivariate = deckLegend?.type === LegendType.Bivariate const legendSublayerIndex = deckLegend?.sublayers?.findIndex( diff --git a/apps/fishing-map/features/workspace/environmental/EnvironmentalLayerPanel.tsx b/apps/fishing-map/features/workspace/environmental/EnvironmentalLayerPanel.tsx index 7f17d8a749..fe52505eb8 100644 --- a/apps/fishing-map/features/workspace/environmental/EnvironmentalLayerPanel.tsx +++ b/apps/fishing-map/features/workspace/environmental/EnvironmentalLayerPanel.tsx @@ -1,11 +1,13 @@ import { useState, useMemo, useTransition } from 'react' import cx from 'classnames' import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' import { DatasetStatus, DatasetTypes } from '@globalfishingwatch/api-types' import { Tooltip, ColorBarOption, IconButton } from '@globalfishingwatch/ui-components' import { UrlDataviewInstance } from '@globalfishingwatch/dataviews-client' import { getEnvironmentalDatasetRange } from '@globalfishingwatch/datasets-client' -import { useDeckLayerLoadedState } from '@globalfishingwatch/deck-layer-composer' +import { useDeckLayerLoadedState, useGetDeckLayer } from '@globalfishingwatch/deck-layer-composer' +import { FourwingsLayer } from '@globalfishingwatch/deck-layers' import styles from 'features/workspace/shared/LayerPanel.module.css' import { useDataviewInstancesConnect } from 'features/workspace/workspace.hook' import ExpandedContainer from 'features/workspace/shared/ExpandedContainer' @@ -22,6 +24,9 @@ import { getDatasetNameTranslated } from 'features/i18n/utils.datasets' import { isBathymetryDataview } from 'features/dataviews/dataviews.utils' import { showSchemaFilter } from 'features/workspace/common/LayerSchemaFilter' import MapLegend from 'features/workspace/common/MapLegend' +import { useActivityDataviewId } from 'features/map/map-layers.hooks' +import { selectReadOnly } from 'features/app/selectors/app.selectors' +import { selectIsGFWUser } from 'features/user/selectors/user.selectors' import DatasetNotFound from '../shared/DatasetNotFound' import Color from '../common/Color' import LayerSwitch from '../common/LayerSwitch' @@ -41,6 +46,8 @@ function EnvironmentalLayerPanel({ dataview, onToggle }: LayerPanelProps): React const { t } = useTranslation() const { upsertDataviewInstance } = useDataviewInstancesConnect() const [colorOpen, setColorOpen] = useState(false) + const isGFWUser = useSelector(selectIsGFWUser) + const readOnly = useSelector(selectReadOnly) const { items, attributes, @@ -51,6 +58,9 @@ function EnvironmentalLayerPanel({ dataview, onToggle }: LayerPanelProps): React isSorting, activeIndex, } = useLayerPanelDataviewSort(dataview.id) + const dataviewId = useActivityDataviewId(dataview) + const activityLayer = useGetDeckLayer(dataviewId) + const layerError = activityLayer?.instance?.getError?.() const datasetFields: { field: SupportedEnvDatasetSchema; label: string }[] = useMemo( () => [ @@ -139,7 +149,7 @@ function EnvironmentalLayerPanel({ dataview, onToggle }: LayerPanelProps): React : false const hasFilters = dataview.config?.filters && Object.keys(dataview.config?.filters).length > 0 const showVisibleFilterValues = showMinVisibleFilter || showMaxVisibleFilter || hasFilters - const showSortHandler = items.length > 1 + const showSortHandler = items.length > 1 && !readOnly return (
)} - + {!readOnly && ( + + )} {showSortHandler && ( )} + {!readOnly && layerError && ( + + )}
- {/* {datasetGeometryType === 'tracks' && ( - // TODO:deck - // - )} */} )} {layerActive && diff --git a/apps/fishing-map/public/locales/source/translations.json b/apps/fishing-map/public/locales/source/translations.json index c2debac217..60b992bd9e 100644 --- a/apps/fishing-map/public/locales/source/translations.json +++ b/apps/fishing-map/public/locales/source/translations.json @@ -440,6 +440,7 @@ "generic": "Something went wrong, try again or contact:", "genericShort": "Something went wrong", "invalidRange": "Min has to be lower than max value", + "layerLoading": "There was an error loading the layer", "latestCarrierNotFound": "Latest carrier dataset not found", "missingLatLng": "No latitude or longitude fields found", "pageNotFound": "Page not found", diff --git a/libs/deck-layers/src/layers/fourwings/FourwingsLayer.ts b/libs/deck-layers/src/layers/fourwings/FourwingsLayer.ts index eb480bcad1..a0d8c24390 100644 --- a/libs/deck-layers/src/layers/fourwings/FourwingsLayer.ts +++ b/libs/deck-layers/src/layers/fourwings/FourwingsLayer.ts @@ -76,6 +76,10 @@ export class FourwingsLayer extends CompositeLayer { + console.warn(error.message) + this.setState({ error: error.message }) + return true + } + _getColorRanges = () => { - // TODO:deck research if we can use the context to get other layers so we can enable whiteEnd - // TODO:deck remove this and calculate values equal to Compare if (this.props.comparisonMode === FourwingsComparisonMode.Bivariate) { return getBivariateRamp(this.props.sublayers.map((s) => s?.colorRamp) as ColorRampId[]) } @@ -393,8 +402,11 @@ export class FourwingsHeatmapTileLayer extends CompositeLayer= 400 && response.status !== 404) { + throw new Error(response.statusText) } cols = parseInt(response.headers.get('X-columns') as string) rows = parseInt(response.headers.get('X-rows') as string) @@ -406,14 +418,15 @@ export class FourwingsHeatmapTileLayer extends CompositeLayer[] - // TODO:deck decide what to do when a chunk load fails const settledPromises = await Promise.allSettled(promises) + const hasChunkError = settledPromises.some((p) => p.status === 'rejected') + if (hasChunkError) { + throw new Error('chunk load error') + } + const arrayBuffers = settledPromises.flatMap((d) => { return d.status === 'fulfilled' && d.value !== undefined ? d.value : [] }) - if (tile.signal?.aborted) { - throw new Error('tile aborted') - } const data = await parse(arrayBuffers.filter(Boolean) as ArrayBuffer[], FourwingsLoader, { worker: true, @@ -473,9 +486,11 @@ export class FourwingsHeatmapTileLayer extends CompositeLayer= 400 && response.status !== 404) { + throw new Error(response.statusText) } cols = parseInt(response.headers.get('X-columns') as string) rows = parseInt(response.headers.get('X-rows') as string) @@ -499,14 +514,18 @@ export class FourwingsHeatmapTileLayer extends CompositeLayer[] - // TODO:deck decide what to do when a chunk load fails const settledPromises = await Promise.allSettled(promises) + + const hasChunkError = settledPromises.some( + (p) => p.status === 'rejected' && p.reason.status !== 404 + ) + if (hasChunkError) { + throw new Error('chunk load error') + } + const arrayBuffers = settledPromises.flatMap((d) => { return d.status === 'fulfilled' && d.value !== undefined ? d.value : [] }) - if (tile.signal?.aborted) { - throw new Error('tile aborted') - } const data = await parse(arrayBuffers.filter(Boolean) as ArrayBuffer[], FourwingsLoader, { worker: true, fourwings: { @@ -646,6 +665,7 @@ export class FourwingsHeatmapTileLayer extends CompositeLayer export type FourwingsTileLayerState = { + error: string tilesCache: FourwingsHeatmapTilesCache colorDomain: FourwingsTileLayerColorDomain colorRanges: FourwingsTileLayerColorRange diff --git a/libs/deck-layers/src/layers/fourwings/positions/FourwingsPositionsTileLayer.ts b/libs/deck-layers/src/layers/fourwings/positions/FourwingsPositionsTileLayer.ts index ab44cf07f9..0ef80690b4 100644 --- a/libs/deck-layers/src/layers/fourwings/positions/FourwingsPositionsTileLayer.ts +++ b/libs/deck-layers/src/layers/fourwings/positions/FourwingsPositionsTileLayer.ts @@ -55,6 +55,7 @@ import { } from './fourwings-positions.types' type FourwingsPositionsTileLayerState = { + error: string fontLoaded: boolean viewportDirty: boolean viewportLoaded: boolean @@ -90,6 +91,16 @@ export class FourwingsPositionsTileLayer extends CompositeLayer< ) } + getError(): string { + return this.state.error + } + + _onLayerError = (error: Error) => { + console.warn(error.message) + this.setState({ error: error.message }) + return true + } + initializeState(context: LayerContext) { super.initializeState(context) let fontLoaded = true @@ -109,6 +120,7 @@ export class FourwingsPositionsTileLayer extends CompositeLayer< }) } this.state = { + error: '', fontLoaded, viewportDirty: false, viewportLoaded: false, @@ -158,9 +170,14 @@ export class FourwingsPositionsTileLayer extends CompositeLayer< props.sublayers?.map(({ colorRamp }) => colorRamp).join(',') !== oldProps.sublayers?.map(({ colorRamp }) => colorRamp).join(',') ) { - // TODO:deck split this in a separate method to avoid calculate the steps again - // as we only need to re-calculate the colors here - this.setState({ colorScale: this._getColorRamp(this.state.positions) }) + if (this.state.colorScale?.colorDomain?.length) { + const colorRange = this.props.sublayers?.map((sublayer) => + getColorRamp({ rampId: sublayer.colorRamp as any }) + ) + this.setState({ colorScale: { ...this.state.colorScale, colorRange } }) + } else { + this.setState({ colorScale: this._getColorRamp(this.state.positions) }) + } } const highlightedFeatureIds = new Set() if (props.highlightedFeatures?.length) { @@ -387,7 +404,6 @@ export class FourwingsPositionsTileLayer extends CompositeLayer< sublayerProperties?.join(',') ), }), - // TODO:deck make chunks here to filter in the frontend instead of requesting on every change 'date-range': `${getISODateFromTS(start < end ? start : end)},${getISODateFromTS(end)}`, } @@ -412,6 +428,7 @@ export class FourwingsPositionsTileLayer extends CompositeLayer< binary: false, loaders: [GFWMVTLoader], fetch: this._fetch, + onTileError: this._onLayerError, onViewportLoad: this._onViewportLoad, renderSubLayers: () => null, }), diff --git a/libs/deck-layers/src/layers/user/UserTracksLayer.ts b/libs/deck-layers/src/layers/user/UserTracksLayer.ts index 239ac7d223..509ed7a799 100644 --- a/libs/deck-layers/src/layers/user/UserTracksLayer.ts +++ b/libs/deck-layers/src/layers/user/UserTracksLayer.ts @@ -182,18 +182,6 @@ export class UserTracksLayer extends CompositeLayer { - // const featureIndex = this.state.rawDataIndexes.find(({ length }) => index < length)?.index! - // const color = - // this.state.rawData?.features?.[featureIndex]?.properties?.color || this.props.color - // return segment.map((s) => ({ ...s, color })) - // }) } _getColorByLineIndex = (_: any, { index }: { index: number }) => {