From 90818425db3f53da9fd92b8ed9881071659ec96d Mon Sep 17 00:00:00 2001 From: Thibaut Tiberghien Date: Thu, 12 Sep 2024 13:17:26 +0800 Subject: [PATCH] legends docs --- docs/api-reference/color/legend.md | 209 +++++++++++++++++++++++- docs/api-reference/color/scales.md | 51 +++++- docs/api-reference/space/custom-ux.md | 2 + docs/api-reference/space/data-layers.md | 29 ++++ package.json | 2 +- src/components/ColorSwatches.tsx | 36 ++++ src/components/IconsSwatches.tsx | 33 ++++ src/components/Legend.tsx | 7 +- yarn.lock | 8 +- 9 files changed, 362 insertions(+), 15 deletions(-) create mode 100644 src/components/ColorSwatches.tsx create mode 100644 src/components/IconsSwatches.tsx diff --git a/docs/api-reference/color/legend.md b/docs/api-reference/color/legend.md index 68d52c6..4eee51a 100644 --- a/docs/api-reference/color/legend.md +++ b/docs/api-reference/color/legend.md @@ -1,14 +1,18 @@ --- sidebar_position: 3 -sidebar_label: Legend +sidebar_label: Legends slug: legend --- import Legend from '@site/src/components/Legend'; +import ColorSwatches from '@site/src/components/ColorSwatches'; +import IconsSwatches from '@site/src/components/IconsSwatches'; -# Legend +# Legends -## React component +## Numerical scale legends + +### React component To render the legend of a numerical color scale, you can use the `` component as follow: @@ -49,7 +53,7 @@ interface LegendProps { Note that the Legend will fill the width of its container. [Get in touch](mailto:support@smplrspace.com) if you have ideas on improvements or pragmatic options we may have missed. -## Vanilla javascript +### Vanilla javascript For non-react codebases, we provide a vanilla Javascript function that wraps this component and renders it into a container. @@ -76,9 +80,9 @@ smplr.Color.drawLegend({ ``` - `containerId` is the "id" of the html container where smplr.js should render the legend, something like "smplr-legend" that can be found in your html. Only ids are supported, not classes. -- `...legendProps` represents all other options as per the [react component section](#react-component). +- `...legendProps` represents all other options as per the previous section. -## Example legend +### Example @@ -100,3 +104,196 @@ smplr.Color.drawLegend({ }, }); ``` + +## Categorical scale legends + +### React component + +To render the legend of a numerical color scale, you can use the `` component as follow: + +```tsx +interface ColorSwatchesProps { + swatches: { + color: string + label: string + group?: string + }[] + size?: number + correctColor?: boolean + noLabels?: boolean +} + + +// example usage with Color from smplr + +``` + +- `swatches` defines the colors and labels used for the swatches. Note that an optional group can be provided to create multiple lines with a label ahead of each group. You can refer to the [`ragSwatches`](/api-reference/color/scales#ragscale) and [`categorySwatches`](/api-reference/color/scales#categoryscale) helpers to generate swatches for categorical scales generated with our functions. +- `size` - _optional_ - is the size in pixels of each swatch. _Default value: 10._ +- `correctColor` - _optional_ - lets you choose if the colors of the legend should be corrected to match the ones from the viewer as per the explanation in the [color mapping section](./overview#color-mapping). We correct them by default. _Default value: true._ +- `noLabels` - _optional_ - set this to true to hide labels. _Default value: false._ + + +### Vanilla javascript + +For non-react codebases, we provide a vanilla Javascript function that wraps this component and renders it into a container. + +```ts +smplr.Color.drawColorSwatches({ + containerId: string + ...swatchesProps: ColorSwatchesProps // see above +}) + +// example usage +smplr.Color.drawColorSwatches({ + containerId: 'smplr-legend', + swatches: [ + { + color: 'red', + label: 'Alert', + }, + { + color: 'orange', + label: 'Warning', + }, + { + color: 'green', + label: 'All ok', + }, + ] +}) +``` + +- `containerId` is the "id" of the html container where smplr.js should render the legend, something like "smplr-legend" that can be found in your html. Only ids are supported, not classes. +- `...swatchesProps` represents all other options as per the previous section. + +### Example + + + +was rendered with the code below: + +```ts +smplr.Color.drawColorSwatches({ + containerId: 'smplr-legend', + swatches: [ + { + color: 'red', + label: 'Alert', + }, + { + color: 'orange', + label: 'Warning', + }, + { + color: 'green', + label: 'All ok', + }, + ] +}); +``` + +## Icons legends + +### React component + +To render the legend of a numerical color scale, you can use the `` component as follow: + +```tsx +interface IconsSwatchesProps { + icons: { + url: string + label: string + group?: string + }[] + height?: number + noLabels?: boolean +} + + +// example usage with Color from smplr + +``` + +- `icons` defines the icons and labels used for the swatches. Note that an optional group can be provided to create multiple lines with a label ahead of each group. +- `height` - _optional_ - is the height in pixels of each swatch. _Default value: 16._ +- `noLabels` - _optional_ - set this to true to hide labels. _Default value: false._ + + +### Vanilla javascript + +For non-react codebases, we provide a vanilla Javascript function that wraps this component and renders it into a container. + +```ts +smplr.Color.drawIconsSwatches({ + containerId: string + ...swatchesProps: IconsSwatchesProps // see above +}) + +// example usage +smplr.Color.drawIconsSwatches({ + containerId: 'smplr-legend', + icons: [ + { + url: 'https://retail.smplrspace.io/img/electric.png', + label: 'EV charging', + }, + { + url: 'https://retail.smplrspace.io/img/wheelchair.png', + label: 'Reduced mobility', + } + ] +}) +``` + +- `containerId` is the "id" of the html container where smplr.js should render the legend, something like "smplr-legend" that can be found in your html. Only ids are supported, not classes. +- `...swatchesProps` represents all other options as per the previous section. + +### Example + + + +was rendered with the code below: + +```ts +smplr.Color.drawIconsSwatches({ + containerId: 'smplr-legend', + icons: [ + { + url: 'https://retail.smplrspace.io/img/electric.png', + label: 'EV charging', + }, + { + url: 'https://retail.smplrspace.io/img/wheelchair.png', + label: 'Reduced mobility', + } + ], + height: 20 +}); +``` \ No newline at end of file diff --git a/docs/api-reference/color/scales.md b/docs/api-reference/color/scales.md index 0ccebd2..946a489 100644 --- a/docs/api-reference/color/scales.md +++ b/docs/api-reference/color/scales.md @@ -66,7 +66,7 @@ Configure a numerical scale using our interactive [colors playground](https://co This function lets you quickly map a discrete number of named categories to colors. It comes with fallback color built-in, as well as type safe categories. ```ts -smplr.Color.numericScale({ +smplr.Color.categoryScale({ categories: Record nodata?: string }): ((category: C) => string) @@ -86,6 +86,28 @@ smplr.Color.categoryScale({ Typescript tip: You may disable category typesafety by passing a "loose" type hint to the function: `categoryScale({...})`. +#### Easy swatches for your legend + +When using a category scale, you can use the same arguments provided to create the scale to generate the corresponding swatches. This makes it easy to use a [legend for the scale](/api-reference/color/legend#categorical-scale-legends). + +```ts +smplr.Color.categorySwatches({ + // same as categoryScale + categories: Record + nodata?: string + // additional swatches config + excludeNoData?: boolean + noDataLabel?: string +}): { + color: string + label: string +}[] +``` + +- `categories` and `nodata` - refer to `categoryScale` above. +- `excludeNoData` - _optional_ - lets you opt out of having a swatch for the `nodata` color. _Default value: false._ +- `noDataLabel` - _optional_ - lets you customize the label used for the `nodata` color. _Default value: 'No data'._ + ### ragScale This function is built on top of `categoryScale` and provide a shortcut for red/amber/green scenarios. Here, the scale comes with pre-defined and optimized RAG colors, that can be customized. And the values "red", "amber", and "green" are always valid for quick testing. @@ -116,3 +138,30 @@ smplr.Color.ragScale({ - `nodata` - _optional_ - is the color used when the value passed to the scale is not a known category. _Default value: #6a6c6c._ Typescript tip: You may disable category typesafety by passing a "loose" type hint to the function: `ragScale({...})`. + +#### Easy swatches for your legend + +When using a RAG scale, you can use the same arguments provided to create the scale to generate the corresponding swatches. This makes it easy to use a [legend for the scale](/api-reference/color/legend#categorical-scale-legends). + +```ts +smplr.Color.ragSwatches({ + // same as ragScale + categories?: Partial> + colors?: { + red?: string + amber?: string + green?: string + } + nodata?: string + // additional swatches config + excludeNoData?: boolean + noDataLabel?: string +}): { + color: string + label: string +}[] +``` + +- `categories`, `colors` and `nodata` - refer to `ragScale` above. +- `excludeNoData` - _optional_ - lets you opt out of having a swatch for the `nodata` color. _Default value: false._ +- `noDataLabel` - _optional_ - lets you customize the label used for the `nodata` color. _Default value: 'No data'._ \ No newline at end of file diff --git a/docs/api-reference/space/custom-ux.md b/docs/api-reference/space/custom-ux.md index 4e8bb4a..bbed076 100644 --- a/docs/api-reference/space/custom-ux.md +++ b/docs/api-reference/space/custom-ux.md @@ -81,6 +81,7 @@ space.startViewer({ autoRotate?: boolean, hideNavigationButtons?: boolean hideLevelPicker?: boolean + legendPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' }) => void ``` @@ -94,6 +95,7 @@ space.startViewer({ - `autoRotate` - _optional_ - set this to true to have the viewer spin around the space automatically. You can also start, set the rotation speed, and stop the rotation as described [below](#auto-rotate-the-viewer). _Default value: false_ - `hideNavigationButtons` - _optional_ - set this to true if you want the user to control the camera but want to remove the navigation buttons. Mouse, touch and keyboard inputs will work while the buttons are hidden. _Default value: false_ - `hideLevelPicker` - _optional_ - set this to true if you want to remove the level picker from the viewer. Levels can still be controlled programmatically, so you could use your own buttons or logic. _Default value: false_ +- `legendPosition` - _optional_ - lets you choose where the legend (if any is configured in the data layers) would be rendered. _Default value: 'top-left'_ ## Viewer controls diff --git a/docs/api-reference/space/data-layers.md b/docs/api-reference/space/data-layers.md index e53cbb8..d471863 100644 --- a/docs/api-reference/space/data-layers.md +++ b/docs/api-reference/space/data-layers.md @@ -17,10 +17,35 @@ space.addDataLayer({ tooltipTemplate?: string, tooltipContainerStyle?: string, persistentTooltip?: boolean, + legend?: LegendConfig, // see below onClick?: (dataElement: object, event: PointerEvent) => void, onHover?: (dataElement: object, event: LimitedPointerEvent) => void, onHoverOut?: (dataElement: object, event: LimitedPointerEvent) => void }) => DataLayerController + +type LegendConfig = + | { + type: 'numeric' + colorScale: (n: number | null | undefined) => string + domain?: [number, number] + ticks?: Record + } + | { + type: 'swatches' + swatches: { + color: string + label: string + group?: string + }[] + } + | { + type: 'icons' + icons: { + url: string + label: string + group?: string + }[] + } ``` - `...layerDefinition` - refer to the [overview](./overview#data-layers) page. @@ -35,6 +60,10 @@ space.addDataLayer({ - Without this helper, we use `'-'` as a default value for all fields. - `tooltipContainerStyle` - _optional_ - lets you override the style of the tooltip container with inline CSS. - `persistentTooltip` - _optional_ - set this to `true` to turn tooltips into small cards that are all visible at once instead of on hover. Persistent tooltips are automatically positioned on the center of the data element they're attached to. They disappear when the camera is moving and reappear when it stops. They are only displayed for the top visible level. They only work for data elements with a non null or undefined `id`. _Default value: false_ +- `legend` - _optional_ - lets you configure a legend to be rendered automatically in a collapsible overlay on the viewer. The legend can be positioned using `legendPosition` in [viewer options](/api-reference/space/custom-ux#viewer-options). + - For `numeric` legends, refer to options in [the legend section](/api-reference/color/legend#numerical-scale-legends). + - For `swatches` legends, refer to options in [the legend section](/api-reference/color/legend#categorical-scale-legends). + - For `icons` legends, refer to options in [the legend section](/api-reference/color/legend#icons-legends). - `onClick` - _optional_ - is taking the data element that was clicked as argument, as well as the Javascript [pointer event](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent) that triggered the click. It is called each time a click or tap event happens. - `onHover` - _optional_ - is taking the newly hovered data element as argument, as well as a limited (due to the rendering engine's internals) "pointer event" that triggered the handler. The limited event only includes the coordinates within the viewer of the pointer at the time when the event was triggered. The handler is called once when the pointer starts to hover a data element. - `onHoverOut` - _optional_ - is taking the previously hovered data element as argument, as well as the same limited "pointer event" as for `onHover`. The handler is called once when the pointer stops hovering a data element. diff --git a/package.json b/package.json index 6c3c1f8..90f4038 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@mantine/core": "^3.0.5", "@mantine/hooks": "^3.0.5", "@mdx-js/react": "^1.6.21", - "@smplrspace/smplr-loader": "^2.14.2-beta.4", + "@smplrspace/smplr-loader": "^2.25.2-beta.0", "@stackblitz/sdk": "^1.8.2", "@svgr/webpack": "^5.5.0", "chance": "^1.1.8", diff --git a/src/components/ColorSwatches.tsx b/src/components/ColorSwatches.tsx new file mode 100644 index 0000000..ec3debd --- /dev/null +++ b/src/components/ColorSwatches.tsx @@ -0,0 +1,36 @@ +import React, { useEffect, FC } from 'react' +import { loadSmplrJs } from '@smplrspace/smplr-loader' + +const ColorSwatches: FC = () => { + useEffect(() => { + loadSmplrJs('umd', 'dev') + .then((smplr) => { + smplr.Color.drawColorSwatches({ + containerId: 'example-color-swatches', + swatches: [ + { + color: 'red', + label: 'Alert' + }, + { + color: 'orange', + label: 'Warning' + }, + { + color: 'green', + label: 'All ok' + } + ] + }) + }) + .catch((error) => console.error(error)) + }, []) + + return ( +
+
Loading...
+
+ ) +} + +export default ColorSwatches diff --git a/src/components/IconsSwatches.tsx b/src/components/IconsSwatches.tsx new file mode 100644 index 0000000..5b01d9f --- /dev/null +++ b/src/components/IconsSwatches.tsx @@ -0,0 +1,33 @@ +import React, { useEffect, FC } from 'react' +import { loadSmplrJs } from '@smplrspace/smplr-loader' + +const IconsSwatches: FC = () => { + useEffect(() => { + loadSmplrJs('umd', 'dev') + .then((smplr) => { + smplr.Color.drawIconsSwatches({ + containerId: 'example-icons-swatches', + icons: [ + { + url: 'https://retail.smplrspace.io/img/electric.png', + label: 'EV charging' + }, + { + url: 'https://retail.smplrspace.io/img/wheelchair.png', + label: 'Reduced mobility' + } + ], + height: 20 + }) + }) + .catch((error) => console.error(error)) + }, []) + + return ( +
+
Loading...
+
+ ) +} + +export default IconsSwatches diff --git a/src/components/Legend.tsx b/src/components/Legend.tsx index e763364..14105f8 100644 --- a/src/components/Legend.tsx +++ b/src/components/Legend.tsx @@ -5,7 +5,8 @@ const Legend: FC = () => { useEffect(() => { loadSmplrJs('umd', 'dev') .then((smplr) => { - smplr.Color.drawLegend({ containerId: 'legend', + smplr.Color.drawLegend({ + containerId: 'example-legend', colorScale: smplr.Color.numericScale({ name: smplr.Color.NumericScale.RdYlBu, domain: [10, 30], @@ -23,8 +24,8 @@ const Legend: FC = () => { }, []) return ( -
-
Loading...
+
+
Loading...
) } diff --git a/yarn.lock b/yarn.lock index f9a34bf..346ad3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1765,10 +1765,10 @@ url "^0.11.0" webpack-sources "^1.4.3" -"@smplrspace/smplr-loader@^2.14.2-beta.4": - version "2.14.2-beta.4" - resolved "https://registry.yarnpkg.com/@smplrspace/smplr-loader/-/smplr-loader-2.14.2-beta.4.tgz#75aa1e24a4478777c708bb7e0d9e4491a2eca9ad" - integrity sha512-xxr8mXUTWNuLdvarqFzesf8IDp6UB8UhsakmeGKWqaHTEIdxEDlWLs22TX1w+Tgh7qL+z6NLXsLquiBcOaXCtg== +"@smplrspace/smplr-loader@^2.25.2-beta.0": + version "2.25.2-beta.0" + resolved "https://registry.yarnpkg.com/@smplrspace/smplr-loader/-/smplr-loader-2.25.2-beta.0.tgz#3a7a8ddb463f32220724ba127eb93c20f553e0de" + integrity sha512-VqmRVtAHxbLIweAsUOOxscjr06pnX8KdyfKR4wjYS04NvswDzdyobbWfxQrz8iXnxBSefbVEw8mjTXFXwWAgMA== "@stackblitz/sdk@^1.8.2": version "1.8.2"