diff --git a/netlify/utils.ts b/netlify/utils.ts index c71c302d..60c31070 100644 --- a/netlify/utils.ts +++ b/netlify/utils.ts @@ -22,10 +22,10 @@ const engines = { openai: (prompt: string) => openai.chat.completions.create({ messages: [{ role: "user", content: prompt }], - model: "gpt-3.5-turbo", + // model: "gpt-3.5-turbo", n: 1, temperature: 0.7, - // model: "gpt-4", + model: "gpt-4", // model: "gpt-4-turbo-preview", }), }; diff --git a/public/lang-docs.md b/public/lang-docs.md index 56cf0b84..9d97c1d1 100644 --- a/public/lang-docs.md +++ b/public/lang-docs.md @@ -664,6 +664,113 @@ Program: +### Even Distribution in Hue + +Description: Categorical values should have an even distribution around the hue circle in LCH color space + +Natural Language: (std(speed(sort(colors, x => lch.h(x)))) < 10 OR std(speed(sort(colors, x => lch.h(x) + 180 % 360))) < 10) + +Palettes that will fail this test: + +- #ffb9ba, #67de25, #25d4c3, #724dd6, #6d0e44 with a #fff background + + + +Palettes that will pass this test: + +- #ffc5b8, #00dec1, #006095, #b7d119, #6e0074 with a #fff background + +- #4682b4 with a #fff background + + +Program: + +```json +{ + "$schema": "http://localhost:8888/lint-schema.json", + "or": [ + { + "<": { + "left": { + "std": { + "speed": { "sort": "colors", "func": {"lch.h": "x"}, "varb": "x" } + } + }, + "right": 10 + } + }, + { + "<": { + "left": { + "std": { + "speed": { + "sort": "colors", + "varb": "x", + "func": { + "%": { + "left": { + "+": { "left": {"lch.h": "x"}, "right": 180 } + }, + "right": 360 + } + } + } + } + }, + "right": 10 + } + } + ] +} + +``` + + + + + +### Even Distribution in Lightness + +Description: Values should be space evenly in lightness in LCH color space + +Natural Language: std(speed(sort(colors, x => lch.l(x)))) < 5 + +Palettes that will fail this test: + +- #ffb9ba, #67de25, #25d4c3, #724dd6, #6d0e44 with a #fff background + +- #174c00, #166100, #267500, #78c12d, #b7ff6d with a #fff background + + + +Palettes that will pass this test: + +- #1a4400, #1f6e00, #4a9500, #74bd28, #9ee754 with a #fff background + +- #4682b4 with a #fff background + + +Program: + +```json +{ + "$schema": "http://localhost:8888/lint-schema.json", + "<": { + "left": { + "std": { + "speed": { "sort": "colors", "func": {"lch.l": "x"}, "varb": "x" } + } + }, + "right": 5 + } +} + +``` + + + + + ### Fair Description: Do the colors stand out equally? A color palette is described as fair if both chroma and luminance ranges are below a certain threshold and unfair if one of them is above a certain threshold. @@ -1308,71 +1415,6 @@ Program: -### Even Distribution - -Description: Categorical values should have an even distribution around the hue circle in LCH color space - -Natural Language: (std(speed(sort(colors, x => lch.h(x)))) < 10 OR std(speed(sort(colors, x => lch.h(x) + 180 % 360))) < 10) - -Palettes that will fail this test: - -- #ffb9ba, #67de25, #25d4c3, #724dd6, #6d0e44 with a #fff background - - - -Palettes that will pass this test: - -- #ffc5b8, #00dec1, #006095, #b7d119, #6e0074 with a #fff background - -- #4682b4 with a #fff background - - -Program: - -```json -{ - "$schema": "http://localhost:8888/lint-schema.json", - "or": [ - { - "<": { - "left": { - "std": { - "speed": { "sort": "colors", "func": {"lch.h": "x"}, "varb": "x" } - } - }, - "right": 10 - } - }, - { - "<": { - "left": { - "std": { - "speed": { - "sort": "colors", - "varb": "x", - "func": { - "%": { - "left": { - "+": { "left": {"lch.h": "x"}, "right": 180 } - }, - "right": 360 - } - } - } - } - }, - "right": 10 - } - } - ] -} - -``` - - - - - ### In Gamut Description: Checks if the colors are in the sRGB gamut. This is important to ensure that the colors are visible and can be displayed on most devices. diff --git a/src/controls/DistributePoints.svelte b/src/controls/DistributePoints.svelte index b17a1de6..db8ff871 100644 --- a/src/controls/DistributePoints.svelte +++ b/src/controls/DistributePoints.svelte @@ -1,8 +1,10 @@ @@ -71,7 +25,13 @@
Distribute
{#each directions as direction} - {/each} diff --git a/src/lib/ColorLint.test.ts b/src/lib/ColorLint.test.ts index f7f459b5..21b33ec7 100644 --- a/src/lib/ColorLint.test.ts +++ b/src/lib/ColorLint.test.ts @@ -61,9 +61,11 @@ test("ColorLint - UglyColors", () => { autoTest(UglyColors); }); -test("ColorLint - EvenDistribution", () => { - autoTest(EvenDistribution); -}); +test("ColorLint - EvenDistribution (1) Hue", () => + autoTest(EvenDistribution[0])); + +test("ColorLint - EvenDistribution (2) Lightness", () => + autoTest(EvenDistribution[1])); test("ColorLint - Fair Nominal", () => { autoTest(Fair[0]); diff --git a/src/lib/__snapshots__/ColorLint.test.ts.snap b/src/lib/__snapshots__/ColorLint.test.ts.snap index 5f0afa98..fdca4201 100644 --- a/src/lib/__snapshots__/ColorLint.test.ts.snap +++ b/src/lib/__snapshots__/ColorLint.test.ts.snap @@ -64,11 +64,19 @@ exports[`ColorLint - Contrast (3) contrastTextAAA 1`] = `"These colors () do not exports[`ColorLint - Contrast (3) contrastTextAAA 2`] = `"These colors (#af3b4b) do not have a sufficient contrast ratio with the background and may be hard to discriminate in some contexts."`; -exports[`ColorLint - EvenDistribution 1`] = `"This palette does not evenly distribute the colors around its range correctly. Try making the spacing between the colors more regular to resolve this issue. "`; +exports[`ColorLint - EvenDistribution (1) Hue 1`] = `"This palette does not evenly distribute the colors around its range correctly. Try making the spacing between the colors more regular to resolve this issue. "`; -exports[`ColorLint - EvenDistribution 2`] = `"This palette does not evenly distribute the colors around its range correctly. Try making the spacing between the colors more regular to resolve this issue. "`; +exports[`ColorLint - EvenDistribution (1) Hue 2`] = `"This palette does not evenly distribute the colors around its range correctly. Try making the spacing between the colors more regular to resolve this issue. "`; -exports[`ColorLint - EvenDistribution 3`] = `"This palette does not evenly distribute the colors around its range correctly. Try making the spacing between the colors more regular to resolve this issue. "`; +exports[`ColorLint - EvenDistribution (1) Hue 3`] = `"This palette does not evenly distribute the colors around its range correctly. Try making the spacing between the colors more regular to resolve this issue. "`; + +exports[`ColorLint - EvenDistribution (2) Lightness 1`] = `"This palette does not evenly distribute the colors in lightness (in LCH space) correctly. Try making the spacing between the colors more regular to resolve this issue."`; + +exports[`ColorLint - EvenDistribution (2) Lightness 2`] = `"This palette does not evenly distribute the colors in lightness (in LCH space) correctly. Try making the spacing between the colors more regular to resolve this issue."`; + +exports[`ColorLint - EvenDistribution (2) Lightness 3`] = `"This palette does not evenly distribute the colors in lightness (in LCH space) correctly. Try making the spacing between the colors more regular to resolve this issue."`; + +exports[`ColorLint - EvenDistribution (2) Lightness 4`] = `"This palette does not evenly distribute the colors in lightness (in LCH space) correctly. Try making the spacing between the colors more regular to resolve this issue."`; exports[`ColorLint - Fair Nominal 1`] = `"This palette is unfair (meaning that some values may unduely stand out). Note that this check is naturally at odds with color vision deficiency friendly palettes. Maximum chroma range: 80, maximum luminance range: 50."`; diff --git a/src/lib/__snapshots__/LintDocs.test.ts.snap b/src/lib/__snapshots__/LintDocs.test.ts.snap index ef40a79e..48e24f97 100644 --- a/src/lib/__snapshots__/LintDocs.test.ts.snap +++ b/src/lib/__snapshots__/LintDocs.test.ts.snap @@ -667,6 +667,113 @@ Program: +### Even Distribution in Hue + +Description: Categorical values should have an even distribution around the hue circle in LCH color space + +Natural Language: (std(speed(sort(colors, x => lch.h(x)))) < 10 OR std(speed(sort(colors, x => lch.h(x) + 180 % 360))) < 10) + +Palettes that will fail this test: + +- #ffb9ba, #67de25, #25d4c3, #724dd6, #6d0e44 with a #fff background + + + +Palettes that will pass this test: + +- #ffc5b8, #00dec1, #006095, #b7d119, #6e0074 with a #fff background + +- #4682b4 with a #fff background + + +Program: + +\`\`\`json +{ + "$schema": "http://localhost:8888/lint-schema.json", + "or": [ + { + "<": { + "left": { + "std": { + "speed": { "sort": "colors", "func": {"lch.h": "x"}, "varb": "x" } + } + }, + "right": 10 + } + }, + { + "<": { + "left": { + "std": { + "speed": { + "sort": "colors", + "varb": "x", + "func": { + "%": { + "left": { + "+": { "left": {"lch.h": "x"}, "right": 180 } + }, + "right": 360 + } + } + } + } + }, + "right": 10 + } + } + ] +} + +\`\`\` + + + + + +### Even Distribution in Lightness + +Description: Values should be space evenly in lightness in LCH color space + +Natural Language: std(speed(sort(colors, x => lch.l(x)))) < 5 + +Palettes that will fail this test: + +- #ffb9ba, #67de25, #25d4c3, #724dd6, #6d0e44 with a #fff background + +- #174c00, #166100, #267500, #78c12d, #b7ff6d with a #fff background + + + +Palettes that will pass this test: + +- #1a4400, #1f6e00, #4a9500, #74bd28, #9ee754 with a #fff background + +- #4682b4 with a #fff background + + +Program: + +\`\`\`json +{ + "$schema": "http://localhost:8888/lint-schema.json", + "<": { + "left": { + "std": { + "speed": { "sort": "colors", "func": {"lch.l": "x"}, "varb": "x" } + } + }, + "right": 5 + } +} + +\`\`\` + + + + + ### Fair Description: Do the colors stand out equally? A color palette is described as fair if both chroma and luminance ranges are below a certain threshold and unfair if one of them is above a certain threshold. @@ -1311,71 +1418,6 @@ Program: -### Even Distribution - -Description: Categorical values should have an even distribution around the hue circle in LCH color space - -Natural Language: (std(speed(sort(colors, x => lch.h(x)))) < 10 OR std(speed(sort(colors, x => lch.h(x) + 180 % 360))) < 10) - -Palettes that will fail this test: - -- #ffb9ba, #67de25, #25d4c3, #724dd6, #6d0e44 with a #fff background - - - -Palettes that will pass this test: - -- #ffc5b8, #00dec1, #006095, #b7d119, #6e0074 with a #fff background - -- #4682b4 with a #fff background - - -Program: - -\`\`\`json -{ - "$schema": "http://localhost:8888/lint-schema.json", - "or": [ - { - "<": { - "left": { - "std": { - "speed": { "sort": "colors", "func": {"lch.h": "x"}, "varb": "x" } - } - }, - "right": 10 - } - }, - { - "<": { - "left": { - "std": { - "speed": { - "sort": "colors", - "varb": "x", - "func": { - "%": { - "left": { - "+": { "left": {"lch.h": "x"}, "right": 180 } - }, - "right": 360 - } - } - } - } - }, - "right": 10 - } - } - ] -} - -\`\`\` - - - - - ### In Gamut Description: Checks if the colors are in the sRGB gamut. This is important to ensure that the colors are visible and can be displayed on most devices. diff --git a/src/lib/linter-tools/lint-fixer.ts b/src/lib/linter-tools/lint-fixer.ts index 6e2b650b..f7e9cab3 100644 --- a/src/lib/linter-tools/lint-fixer.ts +++ b/src/lib/linter-tools/lint-fixer.ts @@ -40,11 +40,17 @@ import { fixDivergingOrder } from "../lints/diverging-order"; import { fixGamut } from "../lints/in-gamut"; import { fixMaxColors } from "../lints/max-colors"; import { fixSequentialOrder } from "../lints/sequential-order"; +import { + fixLightnessDistribution, + fixHueDistribution, +} from "../lints/even-distribution"; const fixDirectory: Record = { fixBackgroundDifferentiability, fixColorNameDiscriminability, fixDivergingOrder, fixGamut, + fixHueDistribution, + fixLightnessDistribution, fixMaxColors, fixSequentialOrder, }; diff --git a/src/lib/linter.ts b/src/lib/linter.ts index 224751f9..0f78187e 100644 --- a/src/lib/linter.ts +++ b/src/lib/linter.ts @@ -29,12 +29,12 @@ export const BUILT_INS: CustomLint[] = [ ...Affects, ...CVDCheck, ...ColorTags, + ...EvenDistribution, ...Fair, ...MuthGuidelines, ...SizeDiscrim, AvoidExtremes, CatOrderSimilarity, - EvenDistribution, Gamut, MaxColors, MutuallyDistinct, diff --git a/src/lib/lints/even-distribution.ts b/src/lib/lints/even-distribution.ts index 53163611..d828bbed 100644 --- a/src/lib/lints/even-distribution.ts +++ b/src/lib/lints/even-distribution.ts @@ -1,8 +1,13 @@ -import { JSONToPrettyString, makePalFromString } from "../utils"; +import { + JSONToPrettyString, + makePalFromString, + distributePoints, +} from "../utils"; import type { CustomLint } from "../ColorLint"; +import type { LintFixer } from "../linter-tools/lint-fixer"; -const lint: CustomLint = { - name: "Even Distribution", +const evenHue: CustomLint = { + name: "Even Distribution in Hue", program: JSONToPrettyString({ // @ts-ignore $schema: "http://localhost:8888/lint-schema.json", @@ -54,5 +59,76 @@ const lint: CustomLint = { expectedFailingTests: [ makePalFromString(["#ffb9ba", "#67de25", "#25d4c3", "#724dd6", "#6d0e44"]), ], + subscribedFix: "fixHueDistribution", }; -export default lint; + +export const fixHueDistribution: LintFixer = async (palette) => { + const colors = palette.colors.map((x) => ({ + ...x, + color: x.color.toColorSpace("lch"), + })); + const focusedColors = colors.map((_, idx) => idx); + const newColors = distributePoints( + { direction: "vertical", name: "whatever" }, + focusedColors, + colors, + "lch" + ).map((x) => ({ + ...x, + color: x.color.toColorSpace(palette.colorSpace), + })); + + return [{ ...palette, colors: newColors }] as (typeof palette)[]; +}; + +const evenLightness: CustomLint = { + name: "Even Distribution in Lightness", + program: JSONToPrettyString({ + // @ts-ignore + $schema: "http://localhost:8888/lint-schema.json", + "<": { + left: { + std: { speed: { sort: "colors", func: { "lch.l": "x" }, varb: "x" } }, + }, + right: 5, + }, + }), + taskTypes: ["categorical", "diverging", "sequential"] as const, + level: "warning", + group: "design", + requiredTags: [], + description: "Values should be space evenly in lightness in LCH color space", + failMessage: `This palette does not evenly distribute the colors in lightness (in LCH space) correctly. Try making the spacing between the colors more regular to resolve this issue.`, + id: "even-colors-lightness-built-in", + blameMode: "none", + expectedPassingTests: [ + makePalFromString(["#1a4400", "#1f6e00", "#4a9500", "#74bd28", "#9ee754"]), + makePalFromString(["#4682b4"]), + ], + expectedFailingTests: [ + makePalFromString(["#ffb9ba", "#67de25", "#25d4c3", "#724dd6", "#6d0e44"]), + makePalFromString(["#174c00", "#166100", "#267500", "#78c12d", "#b7ff6d"]), + ], + subscribedFix: "fixLightnessDistribution", +}; + +export const fixLightnessDistribution: LintFixer = async (palette) => { + const colors = palette.colors.map((x) => ({ + ...x, + color: x.color.toColorSpace("lch"), + })); + const focusedColors = colors.map((_, idx) => idx); + const newColors = distributePoints( + { direction: "in z space", name: "whatever" }, + focusedColors, + colors, + "lch" + ).map((x) => ({ + ...x, + color: x.color.toColorSpace(palette.colorSpace), + })); + + return [{ ...palette, colors: newColors }] as (typeof palette)[]; +}; + +export default [evenHue, evenLightness]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 4676a8ed..fa0e8a3b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -665,3 +665,57 @@ export function dealWithFocusEvent( } return itemInFocus ? [clickedItem] : []; } + +export interface Direction { + direction: "horizontal" | "vertical" | "in z space"; + name: string; +} +export function distributePoints( + dir: Direction, + focusedColors: number[], + colors: ColorWrap[], + colorSpace: string +) { + const config = colorPickerConfig[colorSpace]; + let sortedIndexes = focusedColors.sort((a, b) => { + const modeToIdx = { horizontal: 1, vertical: 2, "in z space": 0 }; + const idx = modeToIdx[dir.direction] || 0; + const pointA = colors[a].color.toChannels()[idx]; + const pointB = colors[b].color.toChannels()[idx]; + return pointA - pointB; + }); + type Channels = [number, number, number]; + const minPoint = colors[sortedIndexes[0]].color.toChannels() as Channels; + const maxPoint = colors[ + sortedIndexes[sortedIndexes.length - 1] + ].color.toChannels() as Channels; + + const numPoints = sortedIndexes.length - 1; + let newPoints = sortedIndexes.map((colorIdx, arrIdx) => { + const t = arrIdx / numPoints; + const newPoint = colors.at(colorIdx)!.color.toChannels() as Channels; + const xIdx = config.xChannelIndex; + const yIdx = config.yChannelIndex; + const zIdx = config.zChannelIndex; + if (dir.direction === "horizontal") { + newPoint[xIdx] = minPoint[xIdx] * (1 - t) + maxPoint[xIdx] * t; + } else if (dir.direction === "vertical") { + newPoint[yIdx] = minPoint[yIdx] * (1 - t) + maxPoint[yIdx] * t; + } else { + newPoint[zIdx] = minPoint[zIdx] * (1 - t) + maxPoint[zIdx] * t; + } + return newPoint as Channels; + }); + const zip = (arr1: T[], arr2: U[]) => + arr1.map((k, i) => [k, arr2[i]] as [T, U]); + const pointsByIndex = Object.fromEntries(zip(sortedIndexes, newPoints)); + + const newColors = [...colors].map((color, idx) => { + const point = pointsByIndex[idx]; + return point + ? { ...color, color: Color.colorFromChannels(point, colorSpace as any) } + : color; + }); + return newColors; + // colorStore.setCurrentPalColors(newColors); +} diff --git a/src/linting/Eval.svelte b/src/linting/Eval.svelte index b164f226..e3554c23 100644 --- a/src/linting/Eval.svelte +++ b/src/linting/Eval.svelte @@ -26,7 +26,7 @@ if (!acc[check.group]) { acc[check.group] = []; } - // extremely dumb hack to move wcags to the top + // extremely dumb hack to move WCAGs to the top if (check.name.startsWith("WCAG")) { acc[check.group].push(check); } else { @@ -74,6 +74,10 @@ checks = res; }); } + if (x === "lint-customization") { + // unset the current lint + lintStore.setFocusedLint(false); + } //@ts-ignore configStore.setEvalDisplayMode(x); }} diff --git a/src/linting/LintCustomizationAddTest.svelte b/src/linting/LintCustomizationAddTest.svelte new file mode 100644 index 00000000..9c5b173b --- /dev/null +++ b/src/linting/LintCustomizationAddTest.svelte @@ -0,0 +1,39 @@ + + + +
+ + +
+ +
diff --git a/src/linting/LintCustomizationTab.svelte b/src/linting/LintCustomizationTab.svelte index d2d4f85b..ab8b93e1 100644 --- a/src/linting/LintCustomizationTab.svelte +++ b/src/linting/LintCustomizationTab.svelte @@ -14,6 +14,7 @@ import type { LintResult, CustomLint } from "../lib/ColorLint"; import LintCustomizationPreview from "./LintCustomizationPreview.svelte"; import NewLintSuggestion from "./NewLintSuggestion.svelte"; + import LintCustomizationAddTest from "./LintCustomizationAddTest.svelte"; $: lint = $lintStore.lints.find( (lint) => lint.id === $lintStore.focusedLint @@ -459,44 +460,12 @@
Expected to be passing:
- -
- - -
- -
+ + lintStore.setCurrentLintExpectedPassingTests(tests)} + />
{#each passingTestResults as passing, idx} @@ -528,18 +497,12 @@
Expected to be failing
- + + lintStore.setCurrentLintExpectedFailingTests(tests)} + />
{#each failingTestResults as failing, idx}