diff --git a/app/scripts/components/analysis/results/analysis-head-actions.tsx b/app/scripts/components/analysis/results/analysis-head-actions.tsx deleted file mode 100644 index 0372793f1..000000000 --- a/app/scripts/components/analysis/results/analysis-head-actions.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import React, { Fragment } from 'react'; -import styled, { useTheme } from 'styled-components'; -import { glsp, media, themeVal } from '@devseed-ui/theme-provider'; -import { Dropdown, DropTitle } from '@devseed-ui/dropdown'; -import { Button } from '@devseed-ui/button'; -import { CollecticonChevronDownSmall } from '@devseed-ui/collecticons'; -import { FormSwitch } from '@devseed-ui/form'; - -import { FoldHeadActions } from '$components/common/fold'; -import { - Legend, - LegendTitle, - LegendList, - LegendSwatch, - LegendLabel -} from '$styles/infographics'; - -export interface DataMetric { - id: string; - label: string; - chartLabel: string; - themeColor: - | 'infographicA' - | 'infographicB' - | 'infographicC' - | 'infographicD' - | 'infographicE'; -} - -export const dataMetrics: DataMetric[] = [ - { - id: 'min', - label: 'Min', - chartLabel: 'Min', - themeColor: 'infographicA' - }, - { - id: 'mean', - label: 'Average', - chartLabel: 'Avg', - themeColor: 'infographicB' - }, - { - id: 'max', - label: 'Max', - chartLabel: 'Max', - themeColor: 'infographicC' - }, - { - id: 'std', - label: 'St Deviation', - chartLabel: 'STD', - themeColor: 'infographicD' - }, - { - id: 'median', - label: 'Median', - chartLabel: 'Median', - themeColor: 'infographicE' - } -]; - -const MetricList = styled.ul` - display: flex; - flex-flow: column; - list-style: none; - margin: 0 -${glsp()}; - padding: 0; - gap: ${glsp(0.5)}; - - > li { - padding: ${glsp(0, 1)}; - } -`; - -const MetricSwitch = styled(FormSwitch)<{ metricThemeColor: string }>` - display: grid; - grid-template-columns: min-content 1fr auto; - - &::before { - content: ''; - width: 0.5rem; - height: 0.5rem; - background: ${({ metricThemeColor }) => - themeVal(`color.${metricThemeColor}` as any)}; - border-radius: ${themeVal('shape.ellipsoid')}; - align-self: center; - } -`; - -const AnalysisFoldHeadActions = styled(FoldHeadActions)` - width: 100%; - - ${media.mediumUp` - width: auto; - `} - - ${Button} { - margin-left: auto; - } -`; - -const AnalysisLegend = styled(Legend)` - flex-flow: column nowrap; - align-items: flex-start; - - ${media.smallUp` - flex-flow: row nowrap; - align-items: center; - `}; -`; - -const AnalysisLegendList = styled(LegendList)` - display: grid; - grid-template-columns: repeat(6, auto); - - ${media.smallUp` - display: flex; - flex-flow: row nowrap; - `}; -`; - -interface AnalysisHeadActionsProps { - activeMetrics: DataMetric[]; - onMetricsChange: (metrics: DataMetric[]) => void; -} - -export default function AnalysisHeadActions(props: AnalysisHeadActionsProps) { - const { activeMetrics, onMetricsChange } = props; - const theme = useTheme(); - - const handleMetricChange = (metric: DataMetric, shouldAdd: boolean) => { - onMetricsChange( - shouldAdd - ? activeMetrics.concat(metric) - : activeMetrics.filter((m) => m.id !== metric.id) - ); - }; - - return ( - - - Legend - - {dataMetrics.map((metric) => { - const active = !!activeMetrics.find((m) => m.id === metric.id); - return ( - - - - {theme.color?.[metric.themeColor]} - - - - {metric.label} - - ); - })} - - - - ( - - )} - > - View options - - {dataMetrics.map((metric) => { - const checked = !!activeMetrics.find((m) => m.id === metric.id); - return ( -
  • - handleMetricChange(metric, !checked)} - > - {metric.label} - -
  • - ); - })} -
    -
    -
    - ); -} diff --git a/app/scripts/components/analysis/results/analysis-head.tsx b/app/scripts/components/analysis/results/analysis-head.tsx new file mode 100644 index 000000000..19f803269 --- /dev/null +++ b/app/scripts/components/analysis/results/analysis-head.tsx @@ -0,0 +1,79 @@ +import React, { Fragment } from 'react'; +import styled, { useTheme } from 'styled-components'; +import { media } from '@devseed-ui/theme-provider'; +import { Button } from '@devseed-ui/button'; + +import { DATA_METRICS } from './analysis-metrics-dropdown'; + +import { FoldHeadActions } from '$components/common/fold'; +import { + Legend, + LegendTitle, + LegendList, + LegendSwatch, + LegendLabel +} from '$styles/infographics'; + +const AnalysisFoldHeadActions = styled(FoldHeadActions)` + width: 100%; + + ${media.mediumUp` + width: auto; + `} + + ${Button} { + margin-left: auto; + } +`; + +const AnalysisLegend = styled(Legend)` + flex-flow: column nowrap; + align-items: flex-start; + + ${media.smallUp` + flex-flow: row nowrap; + align-items: center; + `}; +`; + +const AnalysisLegendList = styled(LegendList)` + display: grid; + grid-template-columns: repeat(6, auto); + + ${media.smallUp` + display: flex; + flex-flow: row nowrap; + `}; +`; + +export default function AnalysisHead() { + const theme = useTheme(); + + return ( + + + Legend + + {DATA_METRICS.map((metric) => { + return ( + + + + {theme.color?.[metric.themeColor]} + + + + {metric.label} + + ); + })} + + + + ); +} diff --git a/app/scripts/components/analysis/results/analysis-metrics-dropdown.tsx b/app/scripts/components/analysis/results/analysis-metrics-dropdown.tsx new file mode 100644 index 000000000..bf2b268ea --- /dev/null +++ b/app/scripts/components/analysis/results/analysis-metrics-dropdown.tsx @@ -0,0 +1,139 @@ +import React from 'react'; +import styled from 'styled-components'; +import { glsp, themeVal } from '@devseed-ui/theme-provider'; +import { Dropdown, DropTitle } from '@devseed-ui/dropdown'; +import { Button } from '@devseed-ui/button'; +import { CollecticonChartLine } from '@devseed-ui/collecticons'; +import { FormSwitch } from '@devseed-ui/form'; + +export interface DataMetric { + id: string; + label: string; + chartLabel: string; + themeColor: + | 'infographicA' + | 'infographicB' + | 'infographicC' + | 'infographicD' + | 'infographicE'; +} + +export const DATA_METRICS: DataMetric[] = [ + { + id: 'min', + label: 'Min', + chartLabel: 'Min', + themeColor: 'infographicA' + }, + { + id: 'mean', + label: 'Average', + chartLabel: 'Avg', + themeColor: 'infographicB' + }, + { + id: 'max', + label: 'Max', + chartLabel: 'Max', + themeColor: 'infographicC' + }, + { + id: 'std', + label: 'St Deviation', + chartLabel: 'STD', + themeColor: 'infographicD' + }, + { + id: 'median', + label: 'Median', + chartLabel: 'Median', + themeColor: 'infographicE' + } +]; + +const MetricList = styled.ul` + display: flex; + flex-flow: column; + list-style: none; + margin: 0 -${glsp()}; + padding: 0; + gap: ${glsp(0.5)}; + + > li { + padding: ${glsp(0, 1)}; + } +`; + +const MetricSwitch = styled(FormSwitch)<{ metricThemeColor: string }>` + display: grid; + grid-template-columns: min-content 1fr auto; + + &::before { + content: ''; + width: 0.5rem; + height: 0.5rem; + background: ${({ metricThemeColor }) => + themeVal(`color.${metricThemeColor}` as any)}; + border-radius: ${themeVal('shape.ellipsoid')}; + align-self: center; + } +`; + +interface AnalysisMetricsDropdownProps { + activeMetrics: DataMetric[]; + onMetricsChange: (metrics: DataMetric[]) => void; + isDisabled: boolean; +} + +export default function AnalysisMetricsDropdown( + props: AnalysisMetricsDropdownProps +) { + const { activeMetrics, onMetricsChange, isDisabled } = props; + + const handleMetricChange = (metric: DataMetric, shouldAdd: boolean) => { + onMetricsChange( + shouldAdd + ? activeMetrics.concat(metric) + : activeMetrics.filter((m) => m.id !== metric.id) + ); + }; + + return ( + ( + + )} + > + View options + + {DATA_METRICS.map((metric) => { + const checked = !!activeMetrics.find((m) => m.id === metric.id); + return ( +
  • + handleMetricChange(metric, !checked)} + > + {metric.label} + +
  • + ); + })} +
    +
    + ); +} diff --git a/app/scripts/components/analysis/results/chart-card.tsx b/app/scripts/components/analysis/results/chart-card.tsx index 0cb54fb75..8eea39dbc 100644 --- a/app/scripts/components/analysis/results/chart-card.tsx +++ b/app/scripts/components/analysis/results/chart-card.tsx @@ -1,4 +1,13 @@ -import React, { useCallback, useRef, useMemo, MouseEvent, ReactNode } from 'react'; +import React, { + useCallback, + useRef, + useMemo, + MouseEvent, + ReactNode, + useState +} from 'react'; +import { DatasetLayer } from 'veda'; +import { get } from 'lodash'; import { reverse } from 'd3'; import styled, { useTheme } from 'styled-components'; import { Link } from 'react-router-dom'; @@ -23,7 +32,11 @@ import { ChartCardNoData, ChartCardNoMetric } from './chart-card-message'; -import { DataMetric } from './analysis-head-actions'; +import AnalysisMetricsDropdown, { + DataMetric, + DATA_METRICS +} from './analysis-metrics-dropdown'; + import { CardSelf, CardHeader, @@ -56,10 +69,25 @@ const InfoTipContent = styled.div` } `; +function getInitialMetrics(data: DatasetLayer): DataMetric[] { + const metricsIds = get(data, 'analysis.metrics', []); + + const foundMetrics = metricsIds + .map((metric: string) => { + return DATA_METRICS.find((m) => m.id === metric); + }) + .filter(Boolean); + + if (!foundMetrics.length) { + return DATA_METRICS; + } + + return foundMetrics; +} + interface ChartCardProps { title: ReactNode; chartData: TimeseriesData; - activeMetrics: DataMetric[]; availableDomain: [Date, Date]; brushRange: [Date, Date]; onBrushRangeChange: (range: [Date, Date]) => void; @@ -93,29 +121,23 @@ const getNoDownloadReason = ({ status, data }: TimeseriesData) => { * * @returns Internal path for Link */ -const getDatasetOverviewPath = ( - layerId: string -) => { +const getDatasetOverviewPath = (layerId: string) => { const dataset = allDatasetsProps.find((d) => d.layers.find((l) => l.id === layerId) ); - return dataset - ? getDatasetPath(dataset) - : '/'; + return dataset ? getDatasetPath(dataset) : '/'; }; export default function ChartCard(props: ChartCardProps) { - const { - title, - chartData, - activeMetrics, - availableDomain, - brushRange, - onBrushRangeChange - } = props; + const { title, chartData, availableDomain, brushRange, onBrushRangeChange } = + props; const { status, meta, data, error, name, id, layer } = chartData; + const [activeMetrics, setActiveMetrics] = useState( + getInitialMetrics(layer) + ); + const chartRef = useRef(null); const noDownloadReason = getNoDownloadReason(chartData); @@ -132,7 +154,10 @@ export default function ChartCard(props: ChartCardProps) { // The indexes expect the data to be ascending, so we have to reverse the // data. const data = reverse(chartData.data.timeseries); - const filename = `chart.${id}.${getDateRangeFormatted(startDate, endDate)}`; + const filename = `chart.${id}.${getDateRangeFormatted( + startDate, + endDate + )}`; if (type === 'image') { chartRef.current?.saveAsImage(filename); @@ -206,7 +231,11 @@ export default function ChartCard(props: ChartCardProps) { - + (dataMetrics); - useEffect(() => { if (!start || !end || !datasetsLayers || !aoi) return; @@ -186,10 +181,7 @@ export default function AnalysisResults() { return ( - + Results - + {!!requestStatus.length && availableDomain && brushRange && ( @@ -230,7 +219,6 @@ export default function AnalysisResults() { setBrushRange(range)} diff --git a/app/scripts/components/common/chart/analysis/utils.ts b/app/scripts/components/common/chart/analysis/utils.ts index 05b9ca28f..357bc1c91 100644 --- a/app/scripts/components/common/chart/analysis/utils.ts +++ b/app/scripts/components/common/chart/analysis/utils.ts @@ -5,7 +5,7 @@ import { chartAspectRatio } from '$components/common/chart/constant'; import { TimeseriesDataUnit } from '$components/analysis/results/timeseries-data'; -import { DataMetric } from '$components/analysis/results/analysis-head-actions'; +import { DataMetric } from '$components/analysis/results/analysis-head'; import { TimeDensity } from '$context/layer-data'; const URL = window.URL;