From 3e631c1fd6e03cc9d4b37f0a4d9c608f4148c69b Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:28:49 +0300 Subject: [PATCH 1/4] chore: update Chart component to support static legend and enhance color handling --- @codexteam/ui/dev/pages/components/Chart.vue | 227 +++++++++-- @codexteam/ui/package.json | 2 +- .../src/vue/components/chart/Chart.colors.ts | 94 ++++- .../src/vue/components/chart/Chart.types.ts | 14 +- .../ui/src/vue/components/chart/Chart.vue | 374 ++++++++++++------ .../ui/src/vue/components/chart/ChartLine.vue | 16 +- 6 files changed, 557 insertions(+), 170 deletions(-) diff --git a/@codexteam/ui/dev/pages/components/Chart.vue b/@codexteam/ui/dev/pages/components/Chart.vue index 03e0de6d..73e550bd 100644 --- a/@codexteam/ui/dev/pages/components/Chart.vue +++ b/@codexteam/ui/dev/pages/components/Chart.vue @@ -12,22 +12,37 @@ lines

- An array of line objects to display on the chart. + An array of line objects to display on the chart. Each line has + label, data and optional color.

+
+

+ lines[].color +

+

+ ChartLineColor.Red / ChartLineColor.LightGrey, + or any CSS color string ('#3F88FF', 'rgb(...)'). + Omit it and the line stays red. +

+ +
+

detalization

- Controls how timestamps are formatted on the X-axis legend and tooltip. - Does not affect data aggregation — only the display format. + Grain of the X-axis and tooltip timestamps. Preview data follows the selected step.

Try it: @@ -39,6 +54,23 @@ />
+ +
+

+ legend +

+

+ Static series legend under the chart: color dot + label for each line. + Off by default so existing layouts stay the same. +

+
+ Try it: + +
+
@@ -49,6 +81,7 @@ @@ -61,6 +94,23 @@ + + + + + Many Series + +

+ Extra series via hex strings. Legend below, tooltip grows down from the axis. +

+
+
+
@@ -69,7 +119,7 @@ diff --git a/@codexteam/ui/package.json b/@codexteam/ui/package.json index 2550ce0f..d54b572f 100644 --- a/@codexteam/ui/package.json +++ b/@codexteam/ui/package.json @@ -1,6 +1,6 @@ { "name": "@codexteam/ui", - "version": "0.2.5", + "version": "0.2.6", "type": "module", "sideEffects": [ "*.css", diff --git a/@codexteam/ui/src/vue/components/chart/Chart.colors.ts b/@codexteam/ui/src/vue/components/chart/Chart.colors.ts index 3dd1a96e..d498217a 100644 --- a/@codexteam/ui/src/vue/components/chart/Chart.colors.ts +++ b/@codexteam/ui/src/vue/components/chart/Chart.colors.ts @@ -1,5 +1,5 @@ import { ChartLineColor } from './Chart.types'; -import type { ChartLineColors } from './Chart.types'; +import type { ChartLineColorToken, ChartLineColors } from './Chart.types'; /** * Colors for dark color scheme. @@ -44,3 +44,95 @@ export const chartColorsLight: ChartLineColors[] = [ pointerColor: '#FF4A68', }, ]; + +/** + * Clamp a color channel to 0–255 + * + * @param value - parsed channel + */ +function clampChannel(value: number): number { + if (!Number.isFinite(value)) { + return 0; + } + + return Math.max(0, Math.min(255, Math.round(value))); +} + +/** + * Parse #RGB or #RRGGBB only. Anything else is rejected — never pass raw CSS through. + * + * @param color - CSS color string + */ +function parseHexColor(color: string): { r: number; g: number; b: number } | null { + const value = color.trim(); + const shortHex = /^#([0-9a-fA-F]{3})$/.exec(value); + + if (shortHex) { + const [r, g, b] = shortHex[1].split(''); + + return { + r: parseInt(r + r, 16), + g: parseInt(g + g, 16), + b: parseInt(b + b, 16), + }; + } + + const longHex = /^#([0-9a-fA-F]{6})$/.exec(value); + + if (longHex) { + return { + r: parseInt(longHex[1].slice(0, 2), 16), + g: parseInt(longHex[1].slice(2, 4), 16), + b: parseInt(longHex[1].slice(4, 6), 16), + }; + } + + return null; +} + +/** + * Build a gradient set from a hex color. Returns null if the string is not a hex. + * + * @param color - CSS color string + */ +export function createChartLineColorsFromCss(color: string): ChartLineColors | null { + const parsed = parseHexColor(color); + + if (!parsed) { + return null; + } + + const r = clampChannel(parsed.r); + const g = clampChannel(parsed.g); + const b = clampChannel(parsed.b); + + return { + name: `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`, + strokeStart: `rgb(${r}, ${g}, ${b})`, + strokeEnd: `rgba(${r}, ${g}, ${b}, 0.22)`, + fillStart: `rgba(${r}, ${g}, ${b}, 0.26)`, + fillEnd: `rgba(${r}, ${g}, ${b}, 0)`, + pointerColor: `rgb(${r}, ${g}, ${b})`, + }; +} + +/** + * Resolve a line color: palette token or hex. Invalid values fall back to red. + * + * @param color - palette token or hex + * @param palette - dark/light chart palette + */ +export function resolveChartLineColor( + color: ChartLineColorToken | undefined, + palette: ChartLineColors[] +): ChartLineColors { + const fallback = palette.find(c => c.name === ChartLineColor.Red) ?? palette[0]; + const colorName = color ?? ChartLineColor.Red; + const fromPalette = palette.find(c => c.name === colorName); + + if (fromPalette) { + return fromPalette; + } + + return createChartLineColorsFromCss(String(colorName)) ?? fallback; +} diff --git a/@codexteam/ui/src/vue/components/chart/Chart.types.ts b/@codexteam/ui/src/vue/components/chart/Chart.types.ts index d29562e1..7ab3b964 100644 --- a/@codexteam/ui/src/vue/components/chart/Chart.types.ts +++ b/@codexteam/ui/src/vue/components/chart/Chart.types.ts @@ -13,6 +13,11 @@ export enum ChartLineColor { LightGrey = 'light-grey' } +/** + * Palette token (`red` / `light-grey`) or a CSS hex/rgb color string. + */ +export type ChartLineColorToken = ChartLineColor | (string & {}); + /** * Chart element in common case */ @@ -43,9 +48,10 @@ export interface ChartLine { data: ChartItem[]; /** - * Name of the color for the line stroke and fill. + * Line color: a ChartLineColor token, or any CSS color string. + * Defaults to red when omitted. */ - color?: ChartLineColor; + color?: ChartLineColorToken; } /** @@ -53,9 +59,9 @@ export interface ChartLine { */ export interface ChartLineColors { /** - * Name of the color + * Name of the color (palette token or the original CSS color) */ - name: ChartLineColor; + name: string; /** * Starting color for stroke gradient (top) */ diff --git a/@codexteam/ui/src/vue/components/chart/Chart.vue b/@codexteam/ui/src/vue/components/chart/Chart.vue index fe592be8..5275c0a2 100644 --- a/@codexteam/ui/src/vue/components/chart/Chart.vue +++ b/@codexteam/ui/src/vue/components/chart/Chart.vue @@ -1,87 +1,101 @@ + + + +
+
+ + {{ preparedLine.line.label }}
@@ -90,7 +104,7 @@ - diff --git a/@codexteam/ui/src/vue/components/chart/Chart.colors.ts b/@codexteam/ui/src/vue/components/chart/Chart.colors.ts index 365b54d6..756b377e 100644 --- a/@codexteam/ui/src/vue/components/chart/Chart.colors.ts +++ b/@codexteam/ui/src/vue/components/chart/Chart.colors.ts @@ -56,6 +56,62 @@ export const chartColorsDark: ChartLineColors[] = [ fillEnd: 'rgba(66, 69, 101, 0)', pointerColor: '#FF2E51', }, + { + name: ChartLineColor.Blue, + strokeStart: '#3F88FF', + strokeEnd: '#424565', + fillStart: 'rgba(63, 136, 255, 0.22)', + fillEnd: 'rgba(66, 69, 101, 0)', + pointerColor: '#3F88FF', + }, + { + name: ChartLineColor.Green, + strokeStart: '#00C853', + strokeEnd: '#424565', + fillStart: 'rgba(0, 200, 83, 0.22)', + fillEnd: 'rgba(66, 69, 101, 0)', + pointerColor: '#00C853', + }, + { + name: ChartLineColor.Orange, + strokeStart: '#FF8A3D', + strokeEnd: '#424565', + fillStart: 'rgba(255, 138, 61, 0.22)', + fillEnd: 'rgba(66, 69, 101, 0)', + pointerColor: '#FF8A3D', + }, + { + name: ChartLineColor.Violet, + strokeStart: '#913BE6', + strokeEnd: '#424565', + fillStart: 'rgba(145, 59, 230, 0.22)', + fillEnd: 'rgba(66, 69, 101, 0)', + pointerColor: '#913BE6', + }, + { + name: ChartLineColor.Yellow, + strokeStart: '#F5C542', + strokeEnd: '#424565', + fillStart: 'rgba(245, 197, 66, 0.22)', + fillEnd: 'rgba(66, 69, 101, 0)', + pointerColor: '#F5C542', + }, + { + name: ChartLineColor.Cyan, + strokeStart: '#2EC4B6', + strokeEnd: '#424565', + fillStart: 'rgba(46, 196, 182, 0.22)', + fillEnd: 'rgba(66, 69, 101, 0)', + pointerColor: '#2EC4B6', + }, + { + name: ChartLineColor.Pink, + strokeStart: '#FF5CA8', + strokeEnd: '#424565', + fillStart: 'rgba(255, 92, 168, 0.22)', + fillEnd: 'rgba(66, 69, 101, 0)', + pointerColor: '#FF5CA8', + }, ]; /** @@ -78,6 +134,62 @@ export const chartColorsLight: ChartLineColors[] = [ fillEnd: 'rgba(255, 190, 198, 0)', pointerColor: '#FF4A68', }, + { + name: ChartLineColor.Blue, + strokeStart: '#4F94FF', + strokeEnd: 'rgba(119, 136, 198, 0.4)', + fillStart: 'rgba(79, 148, 255, 0.28)', + fillEnd: 'rgba(190, 214, 255, 0)', + pointerColor: '#4F94FF', + }, + { + name: ChartLineColor.Green, + strokeStart: '#26A15F', + strokeEnd: 'rgba(119, 136, 198, 0.4)', + fillStart: 'rgba(38, 161, 95, 0.28)', + fillEnd: 'rgba(168, 232, 198, 0)', + pointerColor: '#26A15F', + }, + { + name: ChartLineColor.Orange, + strokeStart: '#F07828', + strokeEnd: 'rgba(119, 136, 198, 0.4)', + fillStart: 'rgba(240, 120, 40, 0.28)', + fillEnd: 'rgba(255, 210, 176, 0)', + pointerColor: '#F07828', + }, + { + name: ChartLineColor.Violet, + strokeStart: '#8B4BCB', + strokeEnd: 'rgba(119, 136, 198, 0.4)', + fillStart: 'rgba(139, 75, 203, 0.28)', + fillEnd: 'rgba(234, 212, 255, 0)', + pointerColor: '#8B4BCB', + }, + { + name: ChartLineColor.Yellow, + strokeStart: '#D9A82E', + strokeEnd: 'rgba(119, 136, 198, 0.4)', + fillStart: 'rgba(217, 168, 46, 0.28)', + fillEnd: 'rgba(255, 232, 176, 0)', + pointerColor: '#D9A82E', + }, + { + name: ChartLineColor.Cyan, + strokeStart: '#1AA89C', + strokeEnd: 'rgba(119, 136, 198, 0.4)', + fillStart: 'rgba(26, 168, 156, 0.28)', + fillEnd: 'rgba(176, 232, 226, 0)', + pointerColor: '#1AA89C', + }, + { + name: ChartLineColor.Pink, + strokeStart: '#E94B93', + strokeEnd: 'rgba(119, 136, 198, 0.4)', + fillStart: 'rgba(233, 75, 147, 0.28)', + fillEnd: 'rgba(255, 198, 222, 0)', + pointerColor: '#E94B93', + }, ]; /** @@ -94,7 +206,7 @@ function clampChannel(value: number): number { /** * Parse #RGB or #RRGGBB only. Anything else is rejected — never pass raw CSS through. - * @param color - CSS color string + * @param color - hex color string */ function parseHexColor(color: string): { r: number; g: number; b: number } | null { const value = color.trim(); @@ -136,7 +248,7 @@ function channelToHex(channel: number): string { /** * Build a gradient set from a hex color. Returns null if the string is not a hex. - * @param color - CSS color string + * @param color - hex color string */ export function createChartLineColorsFromCss(color: string): ChartLineColors | null { const parsed = parseHexColor(color); diff --git a/@codexteam/ui/src/vue/components/chart/Chart.types.ts b/@codexteam/ui/src/vue/components/chart/Chart.types.ts index db765db4..5c45ec83 100644 --- a/@codexteam/ui/src/vue/components/chart/Chart.types.ts +++ b/@codexteam/ui/src/vue/components/chart/Chart.types.ts @@ -10,11 +10,46 @@ export enum ChartLineColor { /** * Accent color for secondary line */ - LightGrey = 'light-grey' + LightGrey = 'light-grey', + + /** + * Blue series + */ + Blue = 'blue', + + /** + * Green series + */ + Green = 'green', + + /** + * Orange series + */ + Orange = 'orange', + + /** + * Violet series + */ + Violet = 'violet', + + /** + * Yellow series + */ + Yellow = 'yellow', + + /** + * Cyan series + */ + Cyan = 'cyan', + + /** + * Pink series + */ + Pink = 'pink' } /** - * Palette token (`red` / `light-grey`) or a hex color (`#RGB` / `#RRGGBB`). + * Palette token or a hex color (`#RGB` / `#RRGGBB`). */ export type ChartLineColorToken = ChartLineColor | `#${string}`; @@ -59,7 +94,7 @@ export interface ChartLine { */ export interface ChartLineColors { /** - * Name of the color (palette token or the original CSS color) + * Name of the color (palette token or hex) */ name: string; /** diff --git a/@codexteam/ui/src/vue/components/chart/Chart.vue b/@codexteam/ui/src/vue/components/chart/Chart.vue index 0a82eb31..295e75e2 100644 --- a/@codexteam/ui/src/vue/components/chart/Chart.vue +++ b/@codexteam/ui/src/vue/components/chart/Chart.vue @@ -1,9 +1,10 @@