From c0491fc09cc6123845507d908c99a99de4b3f8b0 Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Thu, 22 Feb 2024 18:24:12 +0000 Subject: [PATCH 1/3] Removed restrictions for color thresholds --- src/colors.ts | 40 ++++++++++++++++++++++---------- src/typings.d.ts | 4 ++++ test/other/colors.test.ts | 48 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/colors.ts b/src/colors.ts index d2b98fdb..603845d4 100644 --- a/src/colors.ts +++ b/src/colors.ts @@ -13,26 +13,26 @@ import { log, safeGetConfigArrayOfObjects } from "./utils"; return config.charging_state.color; } - if (batteryLevel === undefined || isNaN(batteryLevel) || batteryLevel > 100 || batteryLevel < 0) { + if (batteryLevel === undefined || isNaN(batteryLevel)) { return defaultColor; } const colorSteps = safeGetConfigArrayOfObjects(config.colors?.steps, "color"); if (config.colors?.gradient) { - return getGradientColors(colorSteps, batteryLevel); + return getGradientColors(colorSteps, batteryLevel, config.colors?.non_percent_values); } let thresholds: IColorSteps[] = defaultColorSteps; if (config.colors?.steps) { // making sure the value is always set thresholds = colorSteps.map(s => { - s.value = s.value === undefined || s.value > 100 ? 100 : s.value; + s.value = s.value === undefined ? 100 : s.value; return s; }); } - return thresholds.find(th => batteryLevel <= th.value!)?.color || defaultColor; + return thresholds.find(th => batteryLevel <= th.value!)?.color || lastObject(thresholds).color || defaultColor; } /** @@ -41,15 +41,15 @@ import { log, safeGetConfigArrayOfObjects } from "./utils"; * @param level Battery level * @returns Hex HTML color */ -const getGradientColors = (config: IColorSteps[], level: number): string => { +const getGradientColors = (config: IColorSteps[], level: number, nonPercentValues?: boolean): string => { - let simpleList = config.map(s => s.color); - if (!isColorGradientValid(simpleList)) { + let colorList = config.map(s => s.color); + if (!isColorGradientValid(colorList)) { log("For gradient colors you need to use hex HTML colors. E.g. '#FF00FF'", "error"); return defaultColor; } - if (simpleList.length < 2) { + if (colorList.length < 2) { log("For gradient colors you need to specify at least two steps/colors", "error"); return defaultColor; } @@ -62,20 +62,34 @@ const getGradientColors = (config: IColorSteps[], level: number): string => { return first.color; } - const last = config[config.length - 1]; + const last = lastObject(config); if (level >= last.value!) { return last.color; } const index = config.findIndex(s => level <= s.value!); if (index != -1) { - simpleList = [ config[index - 1].color, config[index].color ]; + colorList = [ config[index - 1].color, config[index].color ]; // calculate percentage level = (level - config[index - 1].value!) * 100 / (config[index].value! - config[index - 1].value!); } + // checking whether we should convert the level to the percentage + else if ((nonPercentValues == undefined && config.some(s => s.value! < 0 || s.value! > 100)) || nonPercentValues === true) { + level = convertToPercentage(config, level); + } } - return getColorInterpolationForPercentage(simpleList, level); + return getColorInterpolationForPercentage(colorList, level); +} + +const convertToPercentage = (colorSteps: IColorSteps[], value: number) => { + const values = colorSteps.map((s, i) => s.value === undefined ? i : s.value) + + const dist = values[values.length - 1] - values[0]; + const valueAdjusted = value - values[0]; + console.log(dist, valueAdjusted) + + return Math.round(valueAdjusted / dist * 100); } /** @@ -164,4 +178,6 @@ const getColorInterpolationForPercentage = function (colors: string[], pct: numb } return true; -} \ No newline at end of file +} + +const lastObject = (collelction: T[]): T => collelction && collelction.length > 0 ? collelction[collelction.length - 1] : {}; \ No newline at end of file diff --git a/src/typings.d.ts b/src/typings.d.ts index 864b6e22..874486e8 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -27,6 +27,10 @@ interface IColorSettings { * Whether to enable smooth color transition between steps */ gradient?: boolean; + /** + * Whether the values are not percentages + */ + non_percent_values?: boolean; } /** diff --git a/test/other/colors.test.ts b/test/other/colors.test.ts index 8f857617..15256b1e 100644 --- a/test/other/colors.test.ts +++ b/test/other/colors.test.ts @@ -89,4 +89,52 @@ describe("Colors", () => { expect(result).toBe("inherit"); }) + + test.each([ + // gradient, non-percent + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, true, -20, "#ff0000"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, true, 0, "#ff0000"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, true, 75, "#ff7f00"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, true, 150, "#ffff00"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, true, 200, "#7fff00"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, true, 250, "#00ff00"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, true, 260, "#00ff00"], + // gradient, non-percent, negative step values + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, true, -200, "#ff0000"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, true, -150, "#ff0000"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, true, -125, "#ff7f00"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, true, -100, "#ffff00"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, true, -75, "#7fff00"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, true, -50, "#00ff00"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, true, 0, "#00ff00"], + // steps, non-percent + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, false, -20, "#ff0000"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, false, 0, "#ff0000"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, false, 75, "#ffff00"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, false, 150, "#ffff00"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, false, 200, "#00ff00"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, false, 250, "#00ff00"], + [[{color: "#ff0000", value: 0}, {color: "#ffff00", value: 150}, {color: "#00ff00", value: 250}], true, false, 260, "#00ff00"], + // steps, non-percent, negative step values + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, false, -200, "#ff0000"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, false, -150, "#ff0000"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, false, -125, "#ffff00"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, false, -100, "#ffff00"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, false, -75, "#00ff00"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, false, -50, "#00ff00"], + [[{color: "#ff0000", value: -150}, {color: "#ffff00", value: -100}, {color: "#00ff00", value: -50}], true, false, 0, "#00ff00"], + ])("non percentage state values and gradient", (steps: IColorSteps[], non_percent_values: boolean | undefined, gradient: boolean | undefined, value: number, expectedResult: string) => { + const config = { + entity: "", + colors: { + steps, + non_percent_values, + gradient + } + } + + const result = getColorForBatteryLevel(config, value, false); + + expect(result).toBe(expectedResult); + }) }) \ No newline at end of file From 2baea4881708635abd2e4be62fa757de3d5a7508 Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Thu, 22 Feb 2024 19:27:27 +0000 Subject: [PATCH 2/3] Default color when level out of range and gradient on --- src/colors.ts | 7 +++++-- test/other/colors.test.ts | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/colors.ts b/src/colors.ts index 603845d4..893c18f6 100644 --- a/src/colors.ts +++ b/src/colors.ts @@ -78,6 +78,10 @@ const getGradientColors = (config: IColorSteps[], level: number, nonPercentValue level = convertToPercentage(config, level); } } + else if (level < 0 || level > 100) { + log("Entity state value seems to be outside of 0-100 range and color step values are not defined"); + return defaultColor; + } return getColorInterpolationForPercentage(colorList, level); } @@ -87,7 +91,6 @@ const convertToPercentage = (colorSteps: IColorSteps[], value: number) => { const dist = values[values.length - 1] - values[0]; const valueAdjusted = value - values[0]; - console.log(dist, valueAdjusted) return Math.round(valueAdjusted / dist * 100); } @@ -167,7 +170,7 @@ const getColorInterpolationForPercentage = function (colors: string[], pct: numb const isColorGradientValid = (gradientColors: string[]) => { if (gradientColors.length < 2) { log("Value for 'color_gradient' should be an array with at least 2 colors."); - return; + return false; } for (const color of gradientColors) { diff --git a/test/other/colors.test.ts b/test/other/colors.test.ts index 15256b1e..3501eb3c 100644 --- a/test/other/colors.test.ts +++ b/test/other/colors.test.ts @@ -14,6 +14,15 @@ describe("Colors", () => { expect(result).toBe(expectedColor); }) + + test.each([ + [-5], + [120], + ])("default color retuned when level outisde of range, steps have no values and gradient turned on", (batteryLevel: number) => { + const result = getColorForBatteryLevel({ entity: "", colors: { gradient: true, steps: [ { color: "#ff0000" }, { color: "#00ff00" } ] } }, batteryLevel, false); + + expect(result).toBe("inherit"); + }) test.each([ [0, "red"], From 09c764836795507a867d25f66408c18604002032 Mon Sep 17 00:00:00 2001 From: Max Chodorowski Date: Thu, 22 Feb 2024 19:43:29 +0000 Subject: [PATCH 3/3] Added comments --- src/colors.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/colors.ts b/src/colors.ts index 893c18f6..7d1d6ce1 100644 --- a/src/colors.ts +++ b/src/colors.ts @@ -86,15 +86,6 @@ const getGradientColors = (config: IColorSteps[], level: number, nonPercentValue return getColorInterpolationForPercentage(colorList, level); } -const convertToPercentage = (colorSteps: IColorSteps[], value: number) => { - const values = colorSteps.map((s, i) => s.value === undefined ? i : s.value) - - const dist = values[values.length - 1] - values[0]; - const valueAdjusted = value - values[0]; - - return Math.round(valueAdjusted / dist * 100); -} - /** * Default color (inherited color) */ @@ -183,4 +174,24 @@ const getColorInterpolationForPercentage = function (colors: string[], pct: numb return true; } +/** + * Convert given value to percentage (position between min/max step value) + * @param colorSteps Configured steps + * @param value Value to convert + * @returns Percentage + */ +const convertToPercentage = (colorSteps: IColorSteps[], value: number) => { + const values = colorSteps.map((s, i) => s.value === undefined ? i : s.value).sort((a, b) => a - b); + + const range = values[values.length - 1] - values[0]; + const valueAdjusted = value - values[0]; + + return Math.round(valueAdjusted / range * 100); +} + +/** + * Returns last object in the collection or default + * @param collelction Array of objects + * @returns Last object in the collection or default + */ const lastObject = (collelction: T[]): T => collelction && collelction.length > 0 ? collelction[collelction.length - 1] : {}; \ No newline at end of file