Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed restrictions for color thresholds #707

Merged
merged 4 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
})
})
Loading