Skip to content

Commit 5f0007c

Browse files
committed
[palette] detect colorspace for expansion
1 parent 6f3f395 commit 5f0007c

File tree

2 files changed

+92
-6
lines changed

2 files changed

+92
-6
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { colorSpacePresetsSortedByArea, detectSmallestCompatibleColorSpace } from "./utils";
4+
5+
describe("Color Utils", () => {
6+
it("should properly sort preset by size", () => {
7+
expect(colorSpacePresetsSortedByArea).toEqual([
8+
"ochre-sand",
9+
"indigo-night",
10+
"blue-ocean",
11+
"purple-wine",
12+
"yellow-lime",
13+
"tarnish",
14+
"fancy-light",
15+
"red-roses",
16+
"pastel",
17+
"sensible",
18+
"ice-cube",
19+
"green-mint",
20+
"shades",
21+
"fancy-dark",
22+
"fluo",
23+
"colorblind",
24+
"default",
25+
"pimp",
26+
"intense",
27+
"all",
28+
]);
29+
});
30+
it("should properly detect red roses", () => {
31+
const space = detectSmallestCompatibleColorSpace(["#d746ae", "#c2809b", "#cb4572"]);
32+
expect(space).toEqual("red-roses");
33+
});
34+
it("should properly detect colorblind", () => {
35+
const space = detectSmallestCompatibleColorSpace(["#a09344", "#7f64b9", "#c36785"]);
36+
expect(space).toEqual("colorblind");
37+
});
38+
it("should properly detect green-mint", () => {
39+
const space = detectSmallestCompatibleColorSpace(["#c2ce88", "#97d54c", "#68823f"]);
40+
expect(space).toEqual("green-mint");
41+
});
42+
it("should properly detect fancy-light", () => {
43+
const space = detectSmallestCompatibleColorSpace(["#e6b8c4", "#98d4e4", "#e1c4aa", "#bfbee0", "#b7d7bd"]);
44+
expect(space).toEqual("fancy-light");
45+
});
46+
});

packages/gephi-lite/src/components/GraphAppearance/color/utils.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,62 @@
1-
import iwanthue from "iwanthue";
2-
import { every, values as getValues } from "lodash";
1+
import iwanthue, { ColorSpaceArray, ColorSpacePreset } from "iwanthue";
2+
import { labToHcl, rgbHexToLab } from "iwanthue/helpers";
3+
import presets from "iwanthue/presets";
4+
import { every, values as getValues, reverse, sortBy, toPairs } from "lodash";
35

46
export function isColor(strColor: string): boolean {
57
const s = new Option().style;
68
s.color = strColor;
79
return s.color !== "";
810
}
911

12+
const colorSpacePresetsAreas = sortBy(
13+
(toPairs(presets) as [ColorSpacePreset, ColorSpaceArray][]).map(([presetKey, preset]) => {
14+
// hRange can be expressed as a range from 330 to 360 and from 0 to 20 as [330, 20]
15+
// in that case the range has to be calculated differently
16+
const hRange = preset[1] >= preset[0] ? preset[1] - preset[0] : 360 - preset[0] + preset[1];
17+
return {
18+
key: presetKey,
19+
area: hRange * (preset[3] - preset[2]) * (preset[5] - preset[4]),
20+
};
21+
}),
22+
({ area }) => area,
23+
);
24+
25+
export const colorSpacePresetsSortedByArea = colorSpacePresetsAreas.map(({ key }) => key);
26+
27+
export function detectSmallestCompatibleColorSpace(hexColors: string[]) {
28+
const colorSpace = colorSpacePresetsSortedByArea.find((presetKey) => {
29+
// test that all colors are include din the color area
30+
const areaBounds = presets[presetKey];
31+
return hexColors
32+
.map((c) => labToHcl(rgbHexToLab(c)))
33+
.every(
34+
([h, c, l]) =>
35+
(areaBounds[0] <= areaBounds[1]
36+
? areaBounds[0] <= h && h <= areaBounds[1]
37+
: areaBounds[0] <= h || h <= areaBounds[1]) &&
38+
areaBounds[2] <= c &&
39+
c <= areaBounds[3] &&
40+
areaBounds[4] <= l &&
41+
l <= areaBounds[5],
42+
);
43+
});
44+
45+
return colorSpace;
46+
}
47+
1048
export function getPalette(values: string[], originalPalette?: Record<string, string | null>): Record<string, string> {
1149
if (every(values, (v) => isColor(v))) {
1250
return values.reduce((iter, v) => ({ ...iter, [v]: v }), {});
1351
} else {
1452
const currentColors = getValues(originalPalette).filter((c) => c !== null);
15-
const palette = iwanthue(values.length, { originalColorsToExpand: currentColors, colorSpace: "all" });
16-
const newColors = palette.filter((c) => !currentColors.includes(c));
17-
console.log(currentColors, palette, newColors);
53+
// heuristics to detect colorSpace: find the smallest area which contains all origin colors
54+
const colorSpace = detectSmallestCompatibleColorSpace(currentColors);
55+
console.log(colorSpace);
56+
const palette = iwanthue(values.length, { originalColorsToExpand: currentColors, colorSpace });
57+
const newColors = reverse(palette.filter((c) => !currentColors.includes(c)));
1858
return values.reduce(
19-
(iter, v, i) => ({ ...iter, [v]: originalPalette && originalPalette[v] ? originalPalette[v] : newColors[i] }),
59+
(iter, v) => ({ ...iter, [v]: originalPalette && originalPalette[v] ? originalPalette[v] : newColors.pop() }),
2060
{},
2161
);
2262
}

0 commit comments

Comments
 (0)