diff --git a/libs/shared/lib/components/colorComponents/colorPicker/colorPicker.stories.tsx b/libs/shared/lib/components/colorComponents/colorPicker/colorPicker.stories.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2a1a4eaa2e95c7245a45ea01213ca700bfe5b2ce --- /dev/null +++ b/libs/shared/lib/components/colorComponents/colorPicker/colorPicker.stories.tsx @@ -0,0 +1,22 @@ +import React, { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { ColorPicker } from '.'; + +const Component: Meta<typeof ColorPicker> = { + title: 'ColorManager/Legend/ColorPicker', + component: ColorPicker, + argTypes: { onChange: { action: 'changed' } }, + decorators: [(Story) => <div className="w-52 m-5">{Story()}</div>], +}; + +export default Component; + +type Story = StoryObj<typeof Component>; + +export const ColorPickerStory: Story = (args: any) => { + const [value, setValue] = useState<[number, number, number]>([251, 150, 55]); + + return <ColorPicker value={value} onChange={setValue} />; +}; + +ColorPickerStory.args = {}; diff --git a/libs/shared/lib/components/colorComponents/colorPicker/index.tsx b/libs/shared/lib/components/colorComponents/colorPicker/index.tsx index f323841e79b5ee02b707a354904cd8c4532ca0a1..15030ba933555ae5e3ec9eb4f0a660f0a4f1cab1 100644 --- a/libs/shared/lib/components/colorComponents/colorPicker/index.tsx +++ b/libs/shared/lib/components/colorComponents/colorPicker/index.tsx @@ -1,70 +1,54 @@ import React from 'react'; -import { TwitterPicker } from 'react-color'; -import { useFloating, autoUpdate, offset, flip, shift, useInteractions, useClick, FloatingPortal } from '@floating-ui/react'; +import { visualizationColors } from 'config'; +import { Popover, PopoverTrigger, PopoverContent } from '@graphpolaris/shared/lib/components/layout/Popover'; -type Props = { - value: any; - updateValue: (val: [number, number, number]) => void; +const hexToRgb = (hex: string): [number, number, number] => { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return [r, g, b]; }; -export default function ColorPicker({ value, updateValue }: Props) { - const [open, setOpen] = React.useState(false); - - const { x, y, strategy, context, refs, floatingStyles } = useFloating({ - placement: 'bottom', - open, - onOpenChange: setOpen, - whileElementsMounted: autoUpdate, - middleware: [offset(5), flip(), shift({ padding: 5 })], - }); - - const { getReferenceProps, getFloatingProps } = useInteractions([useClick(context)]); +type Props = { + value: [number, number, number]; + onChange: (val: [number, number, number]) => void; +}; +export function ColorPicker({ value, onChange }: Props) { return ( - <> - <div - className="p-1 inline-block cursor-pointer" - ref={refs.setReference} - {...getReferenceProps({ - onClick: (e) => { - setOpen(!open); + <div> + <Popover> + <PopoverTrigger + onClick={(e) => { e.stopPropagation(); - }, - })} - > - <div - className="w-5 h-5" - style={{ - backgroundColor: `rgb(${value?.[0] ?? 0}, ${value?.[1] ?? 0}, ${value?.[2] ?? 0})`, }} - /> - </div> - {open && ( - <FloatingPortal> + > + <div className="w-4 h-4 rounded-sm" style={{ backgroundColor: `rgb(${value[0]}, ${value[1]}, ${value[2]})` }} /> + </PopoverTrigger> + <PopoverContent> <div - ref={refs.setFloating} - style={{ - position: strategy, - top: y ?? 0, - left: x ?? 0, - ...floatingStyles, + className="grid grid-cols-4 gap-2 p-2" + onClick={(e) => { + e.stopPropagation(); }} - className="z-10" - {...getFloatingProps()} > - <TwitterPicker - triangle="top-right" - color={{ r: value[0], g: value[1], b: value[2] }} - onChangeComplete={(color) => { - const rgb = color.rgb; - const newValue: [number, number, number] = [rgb.r, rgb.g, rgb.b]; - updateValue(newValue); - setOpen(false); - }} - /> + {visualizationColors.GPCat.colors[14].map((hexColor) => { + const [r, g, b] = hexToRgb(hexColor); + return ( + <div + key={hexColor} + className="w-4 h-4 rounded-sm cursor-pointer" + style={{ backgroundColor: hexColor }} + onClick={(e) => { + e.stopPropagation(); + onChange([r, g, b]); + }} + /> + ); + })} </div> - </FloatingPortal> - )} - </> + </PopoverContent> + </Popover> + </div> ); } diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethOptions.tsx index b077728f8136973be96eef4c9465a8f72b31cd13..d86e37b70212ec7461d002a80cbd603d447997fc 100644 --- a/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethOptions.tsx +++ b/libs/shared/lib/vis/visualizations/mapvis/layers/choropleth-layer/ChoroplethOptions.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import ColorPicker from '@graphpolaris/shared/lib/components/colorComponents/colorPicker'; +import { ColorPicker } from '@graphpolaris/shared/lib/components/colorComponents/colorPicker'; import { MapProps } from '../../mapvis'; import { DropdownColorLegend, EntityPill, Input, RelationPill } from '@graphpolaris/shared/lib/components'; import { LayerSettingsComponentType } from '../../mapvis.types'; @@ -133,7 +133,7 @@ export function ChoroplethOptions({ <span className="font-bold">Color</span> <ColorPicker value={edgeSettings.color} - updateValue={(val) => + onChange={(val) => updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, color: val } } }) } /> diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx index a853da41aeda3c90a57fcf4e8737b4eb2efb28c3..2d214c0e3cc28ad9d3a36a518bc868bf35a68eea 100644 --- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx +++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkLayer.tsx @@ -31,21 +31,28 @@ export class NodeLinkLayer extends CompositeLayer<CompositeLayerType> { // TODO: Remove any here if (changeFlags.propsOrDataChanged) { const colorScales: ColorScales = {}; - Object.keys(props.settings[NodeLinkLayer.type].nodes).map((label) => { - const nodeSettings = props.settings[NodeLinkLayer.type].nodes[label]; - const nodeDistribution = props.graphMetadata.nodes.types[label].attributes; - if (nodeSettings.colorByAttribute) { - if (nodeSettings.colorAttributeType === 'numerical') { - colorScales[label] = this.setNumericalColor( - nodeDistribution[nodeSettings?.colorAttribute]?.min, - nodeDistribution[nodeSettings?.colorAttribute]?.max, - nodeSettings.colorScale, - ); + const nodesSettings = props.settings?.[NodeLinkLayer.type]?.nodes; + const nodesMetadata = props.graphMetadata?.nodes?.types; + + if (nodesSettings && nodesMetadata) { + Object.keys(nodesSettings).forEach((label) => { + const nodeSettings = nodesSettings[label]; + const nodeDistribution = nodesMetadata[label]?.attributes; + + if (nodeSettings?.colorByAttribute && nodeDistribution) { + const colorAttribute = nodeSettings.colorAttribute; + const attributeData = nodeDistribution[colorAttribute]; + + if (nodeSettings.colorAttributeType === 'numerical' && attributeData) { + colorScales[label] = this.setNumericalColor(attributeData.min, attributeData.max, nodeSettings.colorScale); + } } - } - }); + }); + } + this.setState({ colorScales }); } + return changeFlags.propsOrDataChanged || changeFlags.somethingChanged; } diff --git a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx index fee3d3670707f3689670eeeea847301818a02d0d..cb73469aab406eabbb87e597e568ba289e763cce 100644 --- a/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx +++ b/libs/shared/lib/vis/visualizations/mapvis/layers/nodelink-layer/NodeLinkOptions.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; -import ColorPicker from '@graphpolaris/shared/lib/components/colorComponents/colorPicker'; -import { DropdownColorLegend, EntityPill, Icon, Input, RelationPill } from '@graphpolaris/shared/lib/components'; +import { ColorPicker } from '@graphpolaris/shared/lib/components/colorComponents/colorPicker'; +import { DropdownColorLegend, EntityPill, Input, RelationPill } from '@graphpolaris/shared/lib/components'; import { MapProps } from '../../mapvis'; import { LayerSettingsComponentType } from '../../mapvis.types'; import { nodeColorRGB } from '../../utils'; @@ -123,7 +123,7 @@ export function NodeLinkOptions({ {!nodeSettings.colorByAttribute && ( <ColorPicker value={nodeSettings.color} - updateValue={(val) => { + onChange={(val) => { updateLayerSettings({ nodes: { ...layerSettings.nodes, [nodeType]: { ...nodeSettings, color: val } } }); }} /> @@ -185,7 +185,7 @@ export function NodeLinkOptions({ <p className="truncate w-18">{attr.length > 0 ? attr : 'Empty val'}</p> <ColorPicker value={(nodeSettings?.colorMapping ?? {})[attr] ?? [0, 0, 0]} - updateValue={(val) => { + onChange={(val) => { updateLayerSettings({ nodes: { ...layerSettings.nodes, @@ -281,7 +281,7 @@ export function NodeLinkOptions({ <AccordionBody> <ColorPicker value={edgeSettings.color} - updateValue={(val) => + onChange={(val) => updateLayerSettings({ edges: { ...settings.edges, [edgeType]: { ...edgeSettings, color: val } } }) } /> diff --git a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx index cd5809a7db70fbca6b0db98531915e264b189591..fc8db75b1256acf27cbce14b3e39d52afb4471c1 100644 --- a/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx +++ b/libs/shared/lib/vis/visualizations/mapvis/mapvis.tsx @@ -10,7 +10,7 @@ import { Attribution, ActionBar, MapTooltip, MapSettings } from './components'; import { useSelectionLayer, useCoordinateLookup } from './hooks'; import { VisualizationTooltip } from '@graphpolaris/shared/lib/components/VisualizationTooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip'; -import { isGeoJsonType } from './utils'; +import { isGeoJsonType, rgbToHex } from './utils'; import { NodeType } from '../nodelinkvis/types'; export type MapProps = { @@ -270,7 +270,11 @@ export const MapVis = forwardRef((props: VisualizationPropTypes<MapProps>, refEx ? (node as SearchResultType)?.name : 'N/A' } - colorHeader="#FB9637" + colorHeader={rgbToHex( + props?.settings?.nodelink?.nodes?.[node.label]?.color?.[0] ?? 0, + props?.settings?.nodelink?.nodes?.[node.label]?.color?.[1] ?? 0, + props?.settings?.nodelink?.nodes?.[node.label]?.color?.[2] ?? 0, + )} > <MapTooltip type={node.selectedType} data={{ ...node }} key={node._id} /> </VisualizationTooltip> diff --git a/libs/shared/lib/vis/visualizations/mapvis/utils.ts b/libs/shared/lib/vis/visualizations/mapvis/utils.ts index 9cd704f2abfebb238587147992a41bf14851076e..02e12e01fcb8213fb030ae38f8290c38728adb02 100644 --- a/libs/shared/lib/vis/visualizations/mapvis/utils.ts +++ b/libs/shared/lib/vis/visualizations/mapvis/utils.ts @@ -15,3 +15,8 @@ export function nodeColorRGB(num: number) { export const isGeoJsonType = (data: NodeType | GeoJsonType | SearchResultType): data is GeoJsonType => { return (data as GeoJsonType).properties !== undefined; }; + +export const rgbToHex = (r: number, g: number, b: number): string => { + const toHex = (n: number) => n.toString(16).padStart(2, '0'); + return `#${toHex(r)}${toHex(g)}${toHex(b)}`; +}; diff --git a/libs/shared/package.json b/libs/shared/package.json index d5afe985efed3e7e54c803c028bf616544301e37..57573d868e6c87ab3ff7f1e6d8ee8fcbf7049456 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -54,7 +54,6 @@ "pixi-actions": "^1.1.10", "pixi-viewport": "5.0.2", "pixi.js": "^7.4.2", - "react-color": "^2.19.3", "react-cookie": "^7.1.0", "react-draggable": "^4.4.6", "react-grid-layout": "^1.4.4", @@ -85,7 +84,6 @@ "@types/lodash-es": "^4.17.12", "@types/node": "20.11.27", "@types/react": "^18.2.65", - "@types/react-color": "^3.0.12", "@types/react-dom": "^18.2.22", "@types/react-window": "^1.8.8", "@typescript-eslint/eslint-plugin": "~7.2.0",