From 934c343bf9df3137e3ff8f90a9b5e6e7467bff33 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Wed, 21 Jun 2023 12:40:14 +0100 Subject: [PATCH 1/3] Fix format thousands function to handle negatives --- app/scripts/utils/format.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/scripts/utils/format.ts b/app/scripts/utils/format.ts index 0c667e9a3..614c679c2 100644 --- a/app/scripts/utils/format.ts +++ b/app/scripts/utils/format.ts @@ -66,7 +66,7 @@ interface FormatThousandsOptions { * formatThousands(1/2, { decimals: 5, forceDecimals: true}) 0.50000 * */ -export function formatThousands(num: number, options: FormatThousandsOptions) { +export function formatThousands(num: number, options?: FormatThousandsOptions) { const opts = { decimals: 2, separator: ',', @@ -86,7 +86,10 @@ export function formatThousands(num: number, options: FormatThousandsOptions) { return str; }; - let [int, dec] = Number(round(num, opts.decimals)).toString().split('.'); + const sign = num < 0 ? '-' : ''; + const absNum = Math.abs(num); + + let [int, dec] = Number(round(absNum, opts.decimals)).toString().split('.'); let largeNumUnit = ''; if (opts.shorten) { @@ -105,8 +108,8 @@ export function formatThousands(num: number, options: FormatThousandsOptions) { : dec; return dec !== '' - ? `${int}.${dec} ${largeNumUnit}` - : `${int} ${largeNumUnit}`; + ? `${sign}${int}.${dec} ${largeNumUnit}` + : `${sign}${int} ${largeNumUnit}`; } /** From 72e43c269d07f36a87ba6fe80a2e3518314a5b75 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Wed, 21 Jun 2023 12:41:03 +0100 Subject: [PATCH 2/3] Show hovered values of legend on tooltip Fix #558 --- .../components/common/mapbox/layer-legend.tsx | 74 ++++++++++++++++--- mock/datasets/no2.data.mdx | 6 +- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/app/scripts/components/common/mapbox/layer-legend.tsx b/app/scripts/components/common/mapbox/layer-legend.tsx index 5c4975d6b..496e48fff 100644 --- a/app/scripts/components/common/mapbox/layer-legend.tsx +++ b/app/scripts/components/common/mapbox/layer-legend.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, Fragment } from 'react'; +import React, { ReactNode, Fragment, useState, useCallback } from 'react'; import styled from 'styled-components'; import { LayerLegendCategorical, LayerLegendGradient } from 'veda'; import { AccordionFold, AccordionManager } from '@devseed-ui/accordion'; @@ -11,9 +11,15 @@ import { import { CollecticonCircleInformation } from '@devseed-ui/collecticons'; import { Toolbar, ToolbarIconButton } from '@devseed-ui/toolbar'; import { ShadowScrollbar } from '@devseed-ui/shadow-scrollbar'; +import { followCursor } from 'tippy.js'; +import { scaleLinear } from 'd3'; import { Tip } from '../tip'; -import { formatThousands } from '$utils/format'; +import { + formatAsScientificNotation, + formatThousands, + round +} from '$utils/format'; import { variableBaseType, variableGlsp } from '$styles/variable-utils'; import { WidgetItemBodyInner, @@ -44,8 +50,27 @@ const makeGradient = (stops: string[]) => { return `linear-gradient(to right, ${steps.join(', ')})`; }; -const printLegendVal = (val: string | number) => - typeof val === 'number' ? formatThousands(val, { shorten: true }) : val; +const printLegendVal = (val: string | number) => { + const number = Number(val); + if (isNaN(number)) return val; + + if (Math.abs(number) < 1000) { + return formatThousands(number); + } else { + return formatAsScientificNotation(number, 2); + } +}; + +const formatTooltipValue = (rawVal, unit) => { + let value; + if (Math.abs(rawVal) < 1000) { + value = round(rawVal, 3); + } else { + value = formatAsScientificNotation(rawVal, 2); + } + + return unit?.label ? `${value} ${unit.label}` : value; +}; export const LegendContainer = styled.div` position: absolute; @@ -146,6 +171,9 @@ const LegendList = styled.dl` `; const LegendSwatch = styled.span` + /* position is needed to ensure that the layerX on the event is relative to + this element */ + position: relative; display: block; font-size: 0; height: 0.5rem; @@ -209,6 +237,7 @@ export function LayerLegend( @@ -237,9 +266,7 @@ export function LayerLegend( export function LayerLegendContainer(props: LayerLegendContainerProps) { return ( - - {props.children} - + {props.children} ); } @@ -276,17 +303,40 @@ function LayerCategoricalGraphic(props: LayerLegendCategorical) { } function LayerGradientGraphic(props: LayerLegendGradient) { - const { stops, min, max } = props; + const { stops, min, max, unit } = props; + + const [hoverVal, setHoverVal] = useState(0); + + const moveListener = useCallback( + (e) => { + const width = e.nativeEvent.target.clientWidth; + const scale = scaleLinear() + .domain([0, width]) + .range([Number(min), Number(max)]); + setHoverVal(scale(e.nativeEvent.layerX)); + }, + [min, max] + ); + + const hasNumericLegend = !isNaN(Number(min) + Number(max)); + const tipText = formatTooltipValue(hoverVal, unit); return (
- - {stops[0]} to {stops[stops.length - 1]} - + + + {stops[0]} to {stops[stops.length - 1]} + +
- {printLegendVal(min)} + {printLegendVal(min)} {unit?.label} {printLegendVal(max)}
diff --git a/mock/datasets/no2.data.mdx b/mock/datasets/no2.data.mdx index 968ab93a8..a52c887c3 100644 --- a/mock/datasets/no2.data.mdx +++ b/mock/datasets/no2.data.mdx @@ -112,9 +112,11 @@ layers: return `${dateFns.format(datetime, 'LLL yyyy')} VS ${dateFns.format(compareDatetime, 'LLL yyyy')}`; } legend: + unit: + label: molecules/cm3 type: gradient - min: "< -3" - max: "> 3" + min: "-3934857984753" + max: "3348573489573" stops: - "#3A88BD" - "#C9E0ED" From ad521c5881aa068b60eaa821ec53a0959286d0c7 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Wed, 21 Jun 2023 12:48:43 +0100 Subject: [PATCH 3/3] Handle small numbers --- app/scripts/components/common/mapbox/layer-legend.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scripts/components/common/mapbox/layer-legend.tsx b/app/scripts/components/common/mapbox/layer-legend.tsx index 496e48fff..5b35111d3 100644 --- a/app/scripts/components/common/mapbox/layer-legend.tsx +++ b/app/scripts/components/common/mapbox/layer-legend.tsx @@ -54,8 +54,8 @@ const printLegendVal = (val: string | number) => { const number = Number(val); if (isNaN(number)) return val; - if (Math.abs(number) < 1000) { - return formatThousands(number); + if (Math.abs(number) < 1000 && Math.abs(number) > 0.001) { + return formatThousands(number, { decimals: 3 }); } else { return formatAsScientificNotation(number, 2); } @@ -63,7 +63,7 @@ const printLegendVal = (val: string | number) => { const formatTooltipValue = (rawVal, unit) => { let value; - if (Math.abs(rawVal) < 1000) { + if (Math.abs(rawVal) < 1000 && Math.abs(rawVal) > 0.001) { value = round(rawVal, 3); } else { value = formatAsScientificNotation(rawVal, 2);