diff --git a/package.json b/package.json
index e5921290..b35646ba 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@netdata/charts",
- "version": "6.10.5",
+ "version": "6.10.6",
"description": "Netdata frontend SDK and chart utilities",
"main": "dist/index.js",
"module": "dist/es6/index.js",
diff --git a/src/components/groupBoxes/popover/label.js b/src/components/groupBoxes/popover/label.js
index 95b025cc..998fc67f 100644
--- a/src/components/groupBoxes/popover/label.js
+++ b/src/components/groupBoxes/popover/label.js
@@ -10,8 +10,12 @@ const GridRow = styled(Flex).attrs({
const Label = ({ label, value }) => (
- {label}
- {value?.join(", ") || "-"}
+
+ {label}
+
+
+ {value?.join(", ") || "-"}
+
)
diff --git a/src/components/groupBoxes/popover/label.test.js b/src/components/groupBoxes/popover/label.test.js
new file mode 100644
index 00000000..45fbcaaf
--- /dev/null
+++ b/src/components/groupBoxes/popover/label.test.js
@@ -0,0 +1,24 @@
+import React from "react"
+import { screen } from "@testing-library/react"
+import "@testing-library/jest-dom"
+import { renderWithProviders } from "@jest/testUtilities"
+import Label from "./label"
+
+describe("groupBoxes popover Label", () => {
+ it("wraps long label keys and values instead of overflowing", () => {
+ renderWithProviders(
+
+ )
+
+ expect(screen.getByText("wlsx_switch_license_serial_number")).toHaveStyle(
+ "word-break: break-word"
+ )
+ expect(screen.getByText("ethernetCsmacd")).toHaveStyle("word-break: break-word")
+ })
+
+ it("renders a dash for missing values", () => {
+ renderWithProviders()
+
+ expect(screen.getByText("-")).toBeInTheDocument()
+ })
+})
diff --git a/src/components/groupBoxes/popover/labels.js b/src/components/groupBoxes/popover/labels.js
index 0b8bbcbf..2a5bedf3 100644
--- a/src/components/groupBoxes/popover/labels.js
+++ b/src/components/groupBoxes/popover/labels.js
@@ -8,7 +8,7 @@ import Label from "./label"
const Container = styled(Flex).attrs(props => ({
round: true,
border: { side: "all", color: "elementBackground" },
- width: { min: "196px", max: props.maxWidth ? `${props.maxWidth}px` : "80vw" },
+ width: { min: "300px", max: props.maxWidth ? `${props.maxWidth}px` : "80vw" },
background: "dropdown",
column: true,
padding: [4],
@@ -30,7 +30,7 @@ const ColorBackground = styled(BaseColorBar).attrs({
const Grid = styled.div`
display: grid;
width: 100%;
- grid-template-columns: auto 2fr;
+ grid-template-columns: minmax(0, max-content) minmax(0, 2fr);
column-gap: 8px;
align-items: center;
`
diff --git a/src/perDimensionColors.stories.js b/src/perDimensionColors.stories.js
new file mode 100644
index 00000000..bccd1a6a
--- /dev/null
+++ b/src/perDimensionColors.stories.js
@@ -0,0 +1,68 @@
+import React, { useMemo, useState } from "react"
+import { Flex, Text, TextSmall, Button } from "@netdata/netdata-ui"
+import { Color } from "@/components/line/dimensions/color"
+import makeDefaultSDK from "./makeDefaultSDK"
+
+const baseDimensions = ["cpu", "memory", "disk", "network"]
+
+const lateCustomColors = {
+ load: "#FF0000",
+ iops: "#00AA00",
+ latency: "#0000FF",
+ errors: "#FF9900",
+ saturation: "#AA00AA",
+}
+
+const Swatch = ({ chart, id }) => (
+
+
+ {id}
+ {chart.selectDimensionColor(id)}
+
+)
+
+export const LateArrivingCustomColors = ({ theme }) => {
+ const chart = useMemo(() => {
+ const sdk = makeDefaultSDK({ attributes: { theme } })
+ const instance = sdk.makeChart({ attributes: { id: "perDimensionColors", colors: {} } })
+ sdk.appendChild(instance)
+ baseDimensions.forEach(id => instance.selectDimensionColor(id))
+ return instance
+ }, [theme])
+
+ const [dimensions, setDimensions] = useState(baseDimensions)
+
+ const bringInCustomDimensions = () => {
+ chart.updateAttribute("colors", lateCustomColors)
+ setDimensions([...baseDimensions, ...Object.keys(lateCustomColors)])
+ }
+
+ return (
+
+
+ Base dimensions get auto-assigned palette colors. Click the button to bring in new
+ dimensions that each carry their own custom color, keyed by name.
+
+
+
+ {dimensions.map(id => (
+
+ ))}
+
+
+ )
+}
+
+export default {
+ title: "Per-dimension colors",
+ component: LateArrivingCustomColors,
+ args: {
+ theme: "default",
+ },
+ argTypes: {
+ theme: {
+ control: { type: "select" },
+ options: ["default", "dark"],
+ },
+ },
+}
diff --git a/src/sdk/makeChart/makeDimensions.js b/src/sdk/makeChart/makeDimensions.js
index 98b3d0d4..3260f4e6 100644
--- a/src/sdk/makeChart/makeDimensions.js
+++ b/src/sdk/makeChart/makeDimensions.js
@@ -169,16 +169,17 @@ export default (chart, sdk) => {
const getNextColor = () => {
const colorsAttribute = chart.getAttribute("colors", [])
- const index = colorCursor++ % (colorsAttribute.length + dimensionColors.length)
+ const positional = Array.isArray(colorsAttribute) ? colorsAttribute : []
+ const index = colorCursor++ % (positional.length + dimensionColors.length)
const nextColor =
- index < colorsAttribute.length
- ? typeof colorsAttribute[index] === "number"
- ? dimensionColors[colorsAttribute[index]]
- : !colorsAttribute[index]
+ index < positional.length
+ ? typeof positional[index] === "number"
+ ? dimensionColors[positional[index]]
+ : !positional[index]
? dimensionColors[colorCursor % dimensionColors.length]
- : colorsAttribute[index]
- : dimensionColors[index - colorsAttribute.length]
+ : positional[index]
+ : dimensionColors[index - positional.length]
return nextColor
}
@@ -249,15 +250,23 @@ export default (chart, sdk) => {
const sparkline = chart.isSparkline()
if (sparkline && Array.isArray(colorsAttr)) return colorsAttr[0]
+ const keyedColors = colorsAttr && !Array.isArray(colorsAttr) ? colorsAttr : null
+
const isSelected = id === "selected"
id = !id || isSelected ? chart.getAttribute("selectedDimensions")[0] || id : id
if (!isNaN(partIndex)) id = id.split(",")?.[partIndex] || id
- const color =
- isSelected && colorsAttr?.length
- ? colorsAttr[0]
- : sdk.getRoot().getNextColor(getNextColor, key, id)
+ let color
+ if (keyedColors && id in keyedColors) {
+ const value = keyedColors[id]
+ color = typeof value === "number" ? dimensionColors[value] : value
+ } else {
+ color =
+ isSelected && Array.isArray(colorsAttr) && colorsAttr.length
+ ? colorsAttr[0]
+ : sdk.getRoot().getNextColor(getNextColor, key, id)
+ }
const index = chart.getThemeIndex()
return typeof color === "string" ? color : color[index]
@@ -532,7 +541,7 @@ export default (chart, sdk) => {
let dimensionIds = chart.getAttribute("dimensionIds")
if (!dimensionIds.length) return
- const keys = chart.isSparkline() ? sparklineDimensions : dimensionIds
+ const keys = chart.isSparkline() ? sparklineDimensions : [...dimensionIds].sort()
keys.forEach(id => chart.selectDimensionColor(id))
}
diff --git a/src/sdk/makeChart/perDimensionColors.test.js b/src/sdk/makeChart/perDimensionColors.test.js
new file mode 100644
index 00000000..40f16b5e
--- /dev/null
+++ b/src/sdk/makeChart/perDimensionColors.test.js
@@ -0,0 +1,56 @@
+import { makeTestChart } from "@jest/testUtilities"
+
+describe("per-dimension custom colors", () => {
+ it("array form: a late-arriving dimension cannot receive a named custom color", () => {
+ const { chart } = makeTestChart({ attributes: { colors: [] } })
+
+ const initial = ["a", "b", "c", "d"].map(id => chart.selectDimensionColor(id))
+
+ chart.updateAttribute("colors", ["#FF0000"])
+
+ expect(chart.selectDimensionColor("e")).not.toBe("#FF0000")
+ expect(chart.selectDimensionColor("a")).toBe(initial[0])
+ })
+
+ it("object form: late-arriving named dimensions receive their custom colors", () => {
+ const { chart } = makeTestChart({ attributes: { colors: {} } })
+
+ ;["a", "b", "c", "d"].forEach(id => chart.selectDimensionColor(id))
+
+ chart.updateAttribute("colors", {
+ e: "#FF0000",
+ f: "#00FF00",
+ g: "#0000FF",
+ h: "#FFFF00",
+ i: "#FF00FF",
+ })
+
+ expect(chart.selectDimensionColor("e")).toBe("#FF0000")
+ expect(chart.selectDimensionColor("f")).toBe("#00FF00")
+ expect(chart.selectDimensionColor("g")).toBe("#0000FF")
+ expect(chart.selectDimensionColor("h")).toBe("#FFFF00")
+ expect(chart.selectDimensionColor("i")).toBe("#FF00FF")
+ })
+
+ it("object form with numeric palette index resolves to a palette color", () => {
+ const { chart } = makeTestChart({ attributes: { colors: { cpu: 2 } } })
+
+ const color = chart.selectDimensionColor("cpu")
+
+ expect(typeof color).toBe("string")
+ expect(color).not.toBe("")
+ })
+
+ it("auto colors do not depend on arrival order", () => {
+ const a1 = makeTestChart({ attributes: { colors: [] } }).chart
+ const a2 = makeTestChart({ attributes: { colors: [] } }).chart
+
+ a1.setAttribute("dimensionIds", ["b", "a"])
+ a2.setAttribute("dimensionIds", ["a", "b"])
+ a1.updateColors()
+ a2.updateColors()
+
+ expect(a1.selectDimensionColor("a")).toBe(a2.selectDimensionColor("a"))
+ expect(a1.selectDimensionColor("b")).toBe(a2.selectDimensionColor("b"))
+ })
+})