From 44f28c31bf28e283e32b01cc616cc94766805738 Mon Sep 17 00:00:00 2001 From: Ross Date: Thu, 31 Oct 2024 11:23:01 +0000 Subject: [PATCH] feat(data-warehouse): Select color of series on a query visualization (#25909) Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- frontend/src/lib/colors.ts | 5 ++ .../Components/Charts/LineGraph.tsx | 2 +- .../Components/ColorPickerButton.tsx | 74 ++++++++++++++++++ .../ConditionalFormattingTab.tsx | 75 +------------------ .../Components/SeriesTab.tsx | 22 ++++-- .../Components/ySeriesLogic.ts | 3 + frontend/src/queries/schema.json | 3 + frontend/src/queries/schema.ts | 1 + posthog/schema.py | 1 + 9 files changed, 109 insertions(+), 77 deletions(-) create mode 100644 frontend/src/queries/nodes/DataVisualization/Components/ColorPickerButton.tsx diff --git a/frontend/src/lib/colors.ts b/frontend/src/lib/colors.ts index cc9530b5bc524..9646b6dbc953d 100644 --- a/frontend/src/lib/colors.ts +++ b/frontend/src/lib/colors.ts @@ -60,6 +60,11 @@ export function getSeriesColor(index: number = 0): string { return getColorVar(`data-${dataColorVars[adjustedIndex]}`) } +/** Returns all color options for series */ +export function getSeriesColorPalette(): string[] { + return dataColorVars.map((colorVar) => getColorVar(`data-${colorVar}`)) +} + /** Return the background color for the given series index. */ export function getSeriesBackgroundColor(index: number): string { return `${getSeriesColor(index)}30` diff --git a/frontend/src/queries/nodes/DataVisualization/Components/Charts/LineGraph.tsx b/frontend/src/queries/nodes/DataVisualization/Components/Charts/LineGraph.tsx index 2cf06e6917144..06b79cd0e1ce3 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/Charts/LineGraph.tsx +++ b/frontend/src/queries/nodes/DataVisualization/Components/Charts/LineGraph.tsx @@ -114,7 +114,7 @@ export const LineGraph = (): JSX.Element => { const data: ChartData = { labels: xData.data, datasets: yData.map(({ data, settings }, index) => { - const color = getSeriesColor(index) + const color = settings?.display?.color ?? getSeriesColor(index) const backgroundColor = isAreaChart ? hexToRGBA(color, 0.5) : color const graphType = getGraphType(visualizationType, settings) diff --git a/frontend/src/queries/nodes/DataVisualization/Components/ColorPickerButton.tsx b/frontend/src/queries/nodes/DataVisualization/Components/ColorPickerButton.tsx new file mode 100644 index 0000000000000..a9fdac3aedf91 --- /dev/null +++ b/frontend/src/queries/nodes/DataVisualization/Components/ColorPickerButton.tsx @@ -0,0 +1,74 @@ +import { LemonButton, Popover } from '@posthog/lemon-ui' +import { useValues } from 'kea' +import { SeriesGlyph } from 'lib/components/SeriesGlyph' +import { hexToRGBA, lightenDarkenColor, RGBToHex, RGBToRGBA } from 'lib/utils' +import { useState } from 'react' +import { ColorResult, TwitterPicker } from 'react-color' + +import { themeLogic } from '~/layout/navigation-3000/themeLogic' + +const DEFAULT_PICKER_COLORS = [ + '#FFADAD', // Current default + '#E8A598', + '#FFD6A5', + '#FFCFD2', + '#FDFFB6', + '#C1FBA4', + '#9BF6FF', + '#A0C4FF', + '#BDB2FF', + '#FFC6FF', +] + +export const ColorPickerButton = ({ + color, + onColorSelect: propOnColorSelect, + colorChoices = DEFAULT_PICKER_COLORS, +}: { + color: string + onColorSelect?: (color: string) => void + colorChoices?: string[] +}): JSX.Element => { + const [pickerOpen, setPickerOpen] = useState(false) + const { isDarkModeOn } = useValues(themeLogic) + + const onColorSelect = (colorResult: ColorResult): void => { + if (propOnColorSelect) { + propOnColorSelect(colorResult.hex) + } + + if (colorChoices.includes(colorResult.hex)) { + setPickerOpen(false) + } + } + + const colors = isDarkModeOn ? colorChoices.map((n) => RGBToHex(lightenDarkenColor(n, -30))) : colorChoices + + return ( + } + onClickOutside={() => setPickerOpen(false)} + padded={false} + > + setPickerOpen(!pickerOpen)} + sideIcon={<>} + className="ConditionalFormattingTab__ColorPicker" + > + + <> + + + + ) +} diff --git a/frontend/src/queries/nodes/DataVisualization/Components/ConditionalFormatting/ConditionalFormattingTab.tsx b/frontend/src/queries/nodes/DataVisualization/Components/ConditionalFormatting/ConditionalFormattingTab.tsx index 15009af8c17d8..30cd92628f817 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/ConditionalFormatting/ConditionalFormattingTab.tsx +++ b/frontend/src/queries/nodes/DataVisualization/Components/ConditionalFormatting/ConditionalFormattingTab.tsx @@ -1,14 +1,13 @@ import './ConditionalFormattingTab.scss' import { IconPlusSmall, IconTrash } from '@posthog/icons' -import { LemonButton, LemonCollapse, LemonInput, LemonSelect, LemonTag, Popover } from '@posthog/lemon-ui' +import { LemonButton, LemonCollapse, LemonInput, LemonSelect, LemonTag } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' import { SeriesGlyph } from 'lib/components/SeriesGlyph' -import { hexToRGBA, lightenDarkenColor, RGBToHex, RGBToRGBA } from 'lib/utils' -import { useState } from 'react' -import { ColorResult, TwitterPicker } from 'react-color' +import { hexToRGBA, lightenDarkenColor, RGBToRGBA } from 'lib/utils' import { themeLogic } from '~/layout/navigation-3000/themeLogic' +import { ColorPickerButton } from '~/queries/nodes/DataVisualization/Components/ColorPickerButton' import { ConditionalFormattingRule } from '~/queries/schema' import { dataVisualizationLogic } from '../../dataVisualizationLogic' @@ -86,72 +85,6 @@ export const ConditionalFormattingTab = (): JSX.Element => { ) } -const DEFAULT_PICKER_COLORS = [ - '#FFADAD', // Current default - '#E8A598', - '#FFD6A5', - '#FFCFD2', - '#FDFFB6', - '#C1FBA4', - '#9BF6FF', - '#A0C4FF', - '#BDB2FF', - '#FFC6FF', -] - -const ColourPickerButton = ({ - color, - onColorSelect: propOnColorSelect, -}: { - color: string - onColorSelect?: (color: string) => void -}): JSX.Element => { - const [pickerOpen, setPickerOpen] = useState(false) - const { isDarkModeOn } = useValues(themeLogic) - - const onColorSelect = (colorResult: ColorResult): void => { - if (propOnColorSelect) { - propOnColorSelect(colorResult.hex) - } - - if (DEFAULT_PICKER_COLORS.includes(colorResult.hex)) { - setPickerOpen(false) - } - } - - const colors = isDarkModeOn - ? DEFAULT_PICKER_COLORS.map((n) => RGBToHex(lightenDarkenColor(n, -30))) - : DEFAULT_PICKER_COLORS - - return ( - } - onClickOutside={() => setPickerOpen(false)} - padded={false} - > - setPickerOpen(!pickerOpen)} - sideIcon={<>} - className="ConditionalFormattingTab__ColorPicker" - > - - <> - - - - ) -} - const RuleItem = ({ rule: propsRule }: { rule: ConditionalFormattingRule }): JSX.Element => { const { columns, responseLoading, dataVisualizationProps } = useValues(dataVisualizationLogic) @@ -197,7 +130,7 @@ const RuleItem = ({ rule: propsRule }: { rule: ConditionalFormattingRule }): JSX />
- + { @@ -98,7 +99,7 @@ const YSeries = ({ series, index }: { series: AxisSeries; index: number const { setSettingsOpen, submitFormatting, submitDisplay, setSettingsTab } = useActions(seriesLogic) const { isDarkModeOn } = useValues(themeLogic) - const seriesColor = getSeriesColor(index) + const seriesColor = series.settings?.display?.color ?? getSeriesColor(index) const columnsInOptions = showTableSettings ? columns : numericalColumns const options = columnsInOptions.map(({ name, type }) => ({ @@ -219,9 +220,20 @@ const YSeriesDisplayTab = ({ ySeriesLogicProps }: { ySeriesLogicProps: YSeriesLo return (
- - - +
+ + {({ value, onChange }) => ( + + )} + + + + +
{!showTableSettings && ( <> diff --git a/frontend/src/queries/nodes/DataVisualization/Components/ySeriesLogic.ts b/frontend/src/queries/nodes/DataVisualization/Components/ySeriesLogic.ts index d4f992add1859..e938965aad3c1 100644 --- a/frontend/src/queries/nodes/DataVisualization/Components/ySeriesLogic.ts +++ b/frontend/src/queries/nodes/DataVisualization/Components/ySeriesLogic.ts @@ -1,5 +1,6 @@ import { actions, connect, kea, key, path, props, reducers, selectors } from 'kea' import { forms } from 'kea-forms' +import { getSeriesColor } from 'lib/colors' import { AxisSeries, @@ -75,6 +76,7 @@ export const ySeriesLogic = kea([ }, display: { defaults: { + color: props.series?.settings?.display?.color ?? getSeriesColor(props.seriesIndex), label: props.series?.settings?.display?.label ?? '', trendLine: props.series?.settings?.display?.trendLine ?? false, yAxisPosition: props.series?.settings?.display?.yAxisPosition ?? 'left', @@ -83,6 +85,7 @@ export const ySeriesLogic = kea([ submit: async (display) => { actions.updateSeriesIndex(props.seriesIndex, props.series.column.name, { display: { + color: display.color, label: display.label, trendLine: display.trendLine, yAxisPosition: display.yAxisPosition, diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index e8baa13682fd7..7d8fdd6481cb4 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -3006,6 +3006,9 @@ "ChartSettingsDisplay": { "additionalProperties": false, "properties": { + "color": { + "type": "string" + }, "displayType": { "enum": ["auto", "line", "bar"], "type": "string" diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index a437aa9cea660..e52a5b6dc7eec 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -670,6 +670,7 @@ export interface ChartSettingsFormatting { } export interface ChartSettingsDisplay { + color?: string label?: string trendLine?: boolean yAxisPosition?: 'left' | 'right' diff --git a/posthog/schema.py b/posthog/schema.py index de42df84b396e..26c16457f8eea 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -206,6 +206,7 @@ class ChartSettingsDisplay(BaseModel): model_config = ConfigDict( extra="forbid", ) + color: Optional[str] = None displayType: Optional[DisplayType] = None label: Optional[str] = None trendLine: Optional[bool] = None