From 4dab0d9dd40f8dce76bbf4278427ecf4e3bdf293 Mon Sep 17 00:00:00 2001 From: hongfaqiu <951142905@qq.com> Date: Fri, 1 Nov 2024 15:06:34 +0800 Subject: [PATCH] feat(ui): enhance control panel components - Add NumberInput component with slider and input field - Improve ColorTableInput with color scheme flip button - Update single color picker to persist color state --- example/src/components/ColorTableInput.tsx | 228 +++++++++++++++++---- 1 file changed, 191 insertions(+), 37 deletions(-) diff --git a/example/src/components/ColorTableInput.tsx b/example/src/components/ColorTableInput.tsx index ec06d66..c5b90b9 100644 --- a/example/src/components/ColorTableInput.tsx +++ b/example/src/components/ColorTableInput.tsx @@ -1,5 +1,7 @@ -import React, { useState } from 'react'; -import { Select } from 'antd'; +import React, { useState, useMemo } from 'react'; +import { Select, ColorPicker } from 'antd'; +import type { Color } from 'antd/es/color-picker'; +import { SwapOutlined } from '@ant-design/icons'; import { interpolateRainbow, interpolateViridis, @@ -16,9 +18,19 @@ import { } from 'd3-scale-chromatic'; import styled from 'styled-components'; +const Container = styled.div` + display: flex; + gap: 8px; + align-items: flex-start; +`; + +const SelectContainer = styled.div` + flex: 1; +`; + const ColorPreview = styled.div` display: flex; - height: 10px; // Reduced height from 20px to 10px + height: 10px; width: 100%; `; @@ -27,6 +39,56 @@ const ColorSegment = styled.div<{ color: string }>` background-color: ${(props) => props.color}; `; +const ColorSchemeOption = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const ColorSchemeInfo = styled.div` + flex: 1; +`; + +const ColorSchemePreview = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const FlipButton = styled.button` + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 0; + background: transparent; + border: none; + cursor: pointer; + color: rgba(0, 0, 0, 0.45); + transition: all 0.3s; + border-radius: 4px; + margin-top: 4px; + + &:hover { + color: rgba(0, 0, 0, 0.85); + background: rgba(0, 0, 0, 0.04); + } + + &:active { + background: rgba(0, 0, 0, 0.08); + } + + &:focus { + outline: none; + } + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + } +`; + interface ColorTableInputProps { value?: string[]; onChange?: (value: string[]) => void; @@ -46,8 +108,12 @@ const generateColorTable = ( return colors; }; +const generateSingleColorTable = (color: string): string[] => { + return Array(20).fill(color); +}; + export const colorSchemes = [ - { label: 'White', value: 'white', interpolator: () => 'white' }, + { label: 'Single Color', value: 'single', interpolator: () => '#FFFFFF', isSingleColor: true }, { label: 'Rainbow', value: 'rainbow', interpolator: interpolateRainbow, reverse: true }, { label: 'Viridis', value: 'viridis', interpolator: interpolateViridis }, { label: 'Cool', value: 'cool', interpolator: interpolateCool }, @@ -62,7 +128,9 @@ export const colorSchemes = [ { label: 'Purples', value: 'purples', interpolator: interpolatePurples }, ].map((item) => ({ ...item, - colors: generateColorTable(item.interpolator, true), + colors: item.isSingleColor + ? generateSingleColorTable(item.interpolator()) + : generateColorTable(item.interpolator, item.reverse), })); const ColorTableInput: React.FC = ({ @@ -70,56 +138,142 @@ const ColorTableInput: React.FC = ({ onChange, }) => { const [selectedScheme, setSelectedScheme] = useState(() => { - // Find matching color scheme const matchingScheme = colorSchemes.find( (scheme) => JSON.stringify(scheme.colors) === JSON.stringify(value), ); return matchingScheme ? matchingScheme.value : 'rainbow'; }); - const handleChange = (newValue: string) => { + const [singleColor, setSingleColor] = useState(() => { + if (value.length > 0 && value.every(color => color === value[0])) { + return value[0]; + } + return '#FFFFFF'; + }); + + const [reversedSchemes, setReversedSchemes] = useState>({}); + + const currentSchemes = useMemo(() => { + return colorSchemes.map(scheme => { + let colors = scheme.isSingleColor + ? generateSingleColorTable(singleColor) + : scheme.colors; + + if (reversedSchemes[scheme.value]) { + colors = [...colors].reverse(); + } + + return { + ...scheme, + colors + }; + }); + }, [singleColor, reversedSchemes]); + + const handleSchemeChange = (newValue: string) => { setSelectedScheme(newValue); - const scheme = colorSchemes.find((s) => s.value === newValue); + const scheme = currentSchemes.find((s) => s.value === newValue); if (scheme) { - onChange?.(scheme.colors); + if (scheme.isSingleColor) { + const colors = generateSingleColorTable(singleColor); + onChange?.(colors); + } else { + onChange?.(scheme.colors); + } } }; + const handleColorChange = (color: Color) => { + const hexColor = color.toHexString(); + setSingleColor(hexColor); + if (selectedScheme === 'single') { + const colors = generateSingleColorTable(hexColor); + onChange?.(colors); + } + }; + + const handleFlip = (schemeValue: string, e: React.MouseEvent) => { + e.stopPropagation(); + setReversedSchemes(prev => { + const newReversed = { + ...prev, + [schemeValue]: !prev[schemeValue] + }; + + if (schemeValue === selectedScheme) { + const scheme = currentSchemes.find(s => s.value === schemeValue); + if (scheme) { + const newColors = [...scheme.colors].reverse(); + onChange?.(newColors); + } + } + + return newReversed; + }); + }; + const renderColorPreview = (scheme: (typeof colorSchemes)[0]) => { const segments = 20; + const colors = scheme.isSingleColor + ? generateSingleColorTable(singleColor) + : scheme.colors; + return ( - - {Array.from({ length: segments }).map((_, i) => ( - - ))} - + + + {Array.from({ length: segments }).map((_, i) => ( + + ))} + + {!scheme.isSingleColor && ( + handleFlip(scheme.value, e)} + title="Flip Colors" + > + + + )} + ); }; return ( - { + const scheme = currentSchemes.find( + (s) => s.value === selectedValue.value, + ); + return scheme ? renderColorPreview(scheme) : null; + }} + options={currentSchemes.map((scheme) => ({ + value: scheme.value, + label: ( + + +
{scheme.label}
+ {renderColorPreview(scheme)} +
+
+ ), + }))} + /> + + {selectedScheme === 'single' && ( + + )} + ); };