Skip to content

Commit

Permalink
Merge pull request #707 from maxwroc/UnleashColorRanges
Browse files Browse the repository at this point in the history
Removed restrictions for color thresholds
  • Loading branch information
maxwroc authored Feb 22, 2024
2 parents 4d50880 + 1b9cbb3 commit ac1d1e8
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 13 deletions.
56 changes: 43 additions & 13 deletions src/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -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;
}
Expand All @@ -62,20 +62,28 @@ 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);
}
}
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(simpleList, level);
return getColorInterpolationForPercentage(colorList, level);
}

/**
Expand Down Expand Up @@ -153,7 +161,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) {
Expand All @@ -164,4 +172,26 @@ 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 = <T>(collelction: T[]): T => collelction && collelction.length > 0 ? collelction[collelction.length - 1] : <T>{};
4 changes: 4 additions & 0 deletions src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
57 changes: 57 additions & 0 deletions test/other/colors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -89,4 +98,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 = <IBatteryEntityConfig>{
entity: "",
colors: {
steps,
non_percent_values,
gradient
}
}

const result = getColorForBatteryLevel(config, value, false);

expect(result).toBe(expectedResult);
})
})

0 comments on commit ac1d1e8

Please sign in to comment.