Skip to content

Commit

Permalink
some lint usablility
Browse files Browse the repository at this point in the history
  • Loading branch information
mcnuttandrew committed Jan 22, 2024
1 parent 29bc24c commit 4715ec2
Show file tree
Hide file tree
Showing 16 changed files with 152 additions and 75 deletions.
11 changes: 6 additions & 5 deletions src/components/KeyboardHooks.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
$: xDomain = config.xDomain;
$: yDomain = config.yDomain;
function onKeyDown(e: any) {
if (e.target.tagName.toLowerCase() === "input") {
const isUIElement =
e.target.type === "number" || e.target.type === "range";
if (!isUIElement) {
const tagName = e.target.tagName.toLowerCase();
const tagType = e.target.type;
if (tagName === "input") {
const isUIElement = tagType === "number" || tagType === "range";
if (isUIElement) {
return;
}
}
if (e.target.tagName.toLowerCase() === "textarea") {
if (tagName === "textarea") {
return;
}
const key = e.key.toLowerCase();
Expand Down
2 changes: 1 addition & 1 deletion src/components/PalPreview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class="flex flex-wrap rounded p-2 grow"
style="background-color: {pal.background.toDisplay()};"
>
{#each pal.colors as color, idx (idx)}
{#each pal.colors as color, idx}
{#if allowModification}
<Tooltip allowDrag={true}>
<div slot="content" class="flex flex-col" let:onClick>
Expand Down
135 changes: 92 additions & 43 deletions src/content-modules/Eval.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import colorStore from "../stores/color-store";
import focusStore from "../stores/focus-store";
import configStore from "../stores/config-store";
import { ColorLint } from "../lib/lints/ColorLint";
import { computeStats } from "../lib/color-stats";
import { runLintChecks } from "../lib/linter";
import { colorNameSimple } from "../lib/lints/name-discrim";
Expand All @@ -25,6 +26,17 @@
x.taskTypes.includes(palType)
);
$: checkGroups = checks.reduce(
(acc, check) => {
if (!acc[check.group]) {
acc[check.group] = [];
}
acc[check.group].push(check);
return acc;
},
{} as Record<string, ColorLint<any, any>[]>
);
$: colorsToIssues = colors.map((x) => {
const hex = `${x.toHex()}`;
return checks.filter(
Expand All @@ -36,6 +48,22 @@
error: "",
warning: "⚠️",
} as any;
// more info symbol: ℹ️
const titleCase = (str: string) =>
str
.split(" ")
.map((x) => x[0].toUpperCase() + x.slice(1))
.join(" ");
const descriptions = {
sequential:
"Sequential palettes are used to represent a range of values. They are often used to represent quantitative data, such as temperature or elevation.",
diverging:
"Diverging palettes are used to represent a range of values around a central point. They are often used to represent quantitative data, such as temperature or elevation.",
categorical:
"Categorical palettes are used to represent a set of discrete values. They are often used to represent qualitative data, such as different types of land cover or different political parties.",
};
</script>

<div class="flex h-full">
Expand Down Expand Up @@ -145,54 +173,75 @@
<option value={type}>{type}</option>
{/each}
</select>
Palette
palette. {descriptions[palType]}
</div>
{#if Object.keys($colorStore.currentPal.evalConfig)}
<button
class={buttonStyle}
on:click={() => {
colorStore.setCurrentPalEvalConfig({});
}}
>
Restore Defaults
</button>
{/if}
<div>Checks</div>

<div class="overflow-auto h-full max-w-md">
{#each checks as check}
{#if evalConfig[check.name]?.ignore}
<div class="text-xs">
"{check.name}" Ignored for this palette
<button
class={buttonStyle}
on:click={() => {
colorStore.setCurrentPalEvalConfig({
...evalConfig,
[check.name]: { ignore: false },
});
}}
>
renable
</button>
</div>
{:else}
<div class="w-full rounded flex flex-col justify-between py-1">
<div class="flex" class:font-bold={!check.passes}>
{#if check.passes}<div class="text-green-500">✅</div>{:else}<div
class="text-red-500"
>
{checkLevelToSymbol[check.level]}
</div>{/if}
{#each Object.entries(checkGroups) as checkGroup}
<div class="flex mt-5">
<div class="font-bold mr-5">{titleCase(checkGroup[0])} Checks</div>
<button
class={buttonStyle}
on:click={() => {
const newEvalConfig = { ...evalConfig };
checkGroup[1].forEach((check) => {
newEvalConfig[check.name] = { ignore: true };
});
colorStore.setCurrentPalEvalConfig(newEvalConfig);
}}
>
ignore all
</button>
</div>
{#each checkGroup[1] as check}
{#if evalConfig[check.name]?.ignore}
<div class="text-xs">
Ignored "{check.name}"
<button
class={buttonStyle}
on:click={() => {
colorStore.setCurrentPalEvalConfig({
...evalConfig,
[check.name]: { ignore: false },
});
}}
>
renable
</button>
</div>
{:else}
<div class="w-full rounded flex flex-col justify-between py-1">
<div class="flex" class:font-bold={!check.passes}>
{#if check.passes}<div class="text-green-500">
</div>{:else}<div class="text-red-500">
{checkLevelToSymbol[check.level]}
</div>{/if}
<Tooltip>
<div slot="content" class="flex flex-col">
<div class="">{check.description}</div>
</div>
<button slot="target" let:toggle on:click={toggle}>ⓘ</button>
</Tooltip>
{#if !check.passes}
<EvalResponse {check} />
{/if}{check.name}
</div>
{#if !check.passes}
<EvalResponse {check} />
{/if}{check.name}
<ExplanationViewer {check} />
{/if}
</div>
{#if !check.passes}
<ExplanationViewer {check} />
{/if}
</div>
{/if}
{/if}
{/each}
{/each}
{#if Object.keys($colorStore.currentPal.evalConfig)}
<button
class={`${buttonStyle} mt-5`}
on:click={() => colorStore.setCurrentPalEvalConfig({})}
>
Restore Defaults
</button>
{/if}
</div>
</div>
</div>
18 changes: 11 additions & 7 deletions src/content-modules/contextual-tools/InterpolatePoints.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,16 @@
{/each}
</select>
</div>
<div class="flex justify-between">
<label for="interpolate-count">Num points to add</label>
<select id="interpolate-count" bind:value={numPoints}>
{#each [1, 2, 3, 4, 5, 6, 7, 8] as numPoints}
<option value={numPoints}>{numPoints}</option>
{/each}
</select>
<div class="flex-col items-center">
<label for="interpolate-count">Number of interpolation steps</label>
<input
id="interpolate-count"
class="h-4 w-full"
type="number"
min="1"
step="1"
bind:value={numPoints}
/>
</div>

<!-- <div class="flex justify-between items-center w-full transition-all">
Expand All @@ -83,6 +86,7 @@
flip points
</button>
</div> -->
<div>Preview</div>
<PalPreview pal={tempPal} />
<button
class="{buttonStyle} mt-5"
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lints/ColorLint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export class ColorLint<CheckData, ParamType> {
hasParam: boolean = false;
config: { val?: ParamType } = {};
defaultParam: ParamType = false as any;
group: string = "";
description: string = "";
paramOptions:
| { type: "number"; min: number; max: number; step: number }
| { type: "enum"; options: string[] }
Expand Down
4 changes: 3 additions & 1 deletion src/lib/lints/avoid-extremes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { TaskType } from "./ColorLint";
import { Color } from "../Color";

const hexJoin = (colors: Color[]) => colors.map((x) => x.toHex()).join(", ");
const bannedColors = ["#000000", "#ffffff"];
const bannedColors = ["#000000", "#ffffff", "#000", "#fff"];
const bannedSet = new Set(bannedColors);
// https://www.sciencedirect.com/science/article/pii/S0167947308005549?casa_token=s8jmZqboaYgAAAAA:7lsAu7YUHVBTQA_eaKJ_3FFGv309684j_NTisGO9mIr3UZNIJ6hlAlxPQo04xzsowG7-dH0vzm4
function findExtremeColors(colors: Color[]) {
Expand All @@ -14,6 +14,8 @@ export default class ExtremeColors extends ColorLint<Color[], false> {
name = "Avoid extreme colors";
taskTypes = ["sequential", "diverging", "categorical"] as TaskType[];
level: "error" | "warning" = "warning";
group = "aesthetics";
description = `Colors at either end of the lightness spectrum can be hard to discriminate in some contexts, and are sometimes advised against.`;

_runCheck() {
const { colors } = this.palette;
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lints/background-differentiability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export default class BackgroundDifferentiability extends ColorLint<
> {
name = "All colors differentiable from background";
taskTypes = ["sequential", "diverging", "categorical"] as TaskType[];
group = "accessibility";
description: string = `All colors in a palette should be differentiable from the background color. This is because if they are not, then they will not be differentiable from each other in some contexts.`;
_runCheck() {
const { colors, background } = this.palette;
const colorsCloseToBackground = colors.reduce((acc, x, idx) => {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lints/blind-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ const checks = blindnessTypes.map((key) => {
return class ColorBlindCheck extends ColorLint<[number, number][], false> {
name = `Colorblind Friendly for ${key}`;
taskTypes = ["sequential", "diverging", "categorical"] as TaskType[];
group: string = "accessibility";
description: string = `All colors in a palette should be differentiable by people with ${blindnessLabels[key]}. This is because if they are not, then they will not be differentiable from each other in some contexts.`;
_runCheck() {
const colors = this.palette.colors;
const { pass, notOKColorIndexes } = checkType(colors, key);
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lints/color-similarity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export default class ColorSimilarity extends ColorLint<number[], number> {
taskTypes = ["sequential", "diverging", "categorical"] as TaskType[];
hasParam = true;
defaultParam: number = 10;
group: string = "usability";
description: string = `Colors in a palette should be differentiable from each other. This is because if they are not, then they will not be differentiable from each other in some contexts.`;
paramOptions: { type: "number"; min: number; max: number; step: number } = {
type: "number",
min: 10,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lints/contrast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default class BackgroundContrast extends ColorLint<Color[], Algorithm> {
options: ["APCA", "WCAG21", "Michelson", "Weber", "Lstar", "DeltaPhi"],
};
level: "error" | "warning" = "error";
group: string = "accessibility";
description: string = `All colors in a palette should have a sufficient contrast ratio with the background color. This is because if they are not, then they will not be differentiable from each other in some contexts.`;

_runCheck() {
const { background } = this.palette;
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lints/diverging-order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default class SequentialOrder extends ColorLint<boolean, false> {
name =
"Diverging Palettes should have a middle color that is the lightest or darkest color";
taskTypes = ["diverging"] as TaskType[];
group = "usability";
description: string = `Diverging palettes should have a middle color that is the lightest or darkest color. This is because if they are not, then they will not be differentiable from each other in some contexts.`;
_runCheck() {
const { colors } = this.palette;
if (colors.length <= 2) {
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lints/max-colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export default class MaxColors extends ColorLint<number, number> {
taskTypes = ["sequential", "diverging", "categorical"] as TaskType[];
hasParam = true;
defaultParam = 10;
group = "aesthetics";
description: string = `Palettes should have a maximum number of colors. Higher numbers of colors can make it hard to identify specific values.`;
paramOptions: { type: "number"; min: number; max: number; step: 1 } = {
type: "number",
min: 2,
Expand Down
3 changes: 3 additions & 0 deletions src/lib/lints/name-discrim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ export default class ColorNameDiscriminability extends ColorLint<
> {
name = "Color Name Discriminability";
taskTypes = ["sequential", "diverging", "categorical"] as TaskType[];
group = "usability";
description: string =
"Being able to identify colors by name is important for usability and for memorability. ";
_runCheck() {
const { colors } = this.palette;
const passCheck = simpleDiscrim(colors);
Expand Down
2 changes: 2 additions & 0 deletions src/lib/lints/sequential-order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const getLightness = (color: Color) => color.toColorIO().to("lab").coords[0];
export default class SequentialOrder extends ColorLint<boolean, false> {
name = "Sequential Palettes should be ordered by lightness";
taskTypes = ["sequential"] as TaskType[];
group = "usability";
description = `Sequential palettes should be ordered by lightness. This is a defining property of a sequential palette and ensures that values are understood as having an increase (or decreasing) value.`;
_runCheck() {
const { colors } = this.palette;
if (colors.length < 2) {
Expand Down
12 changes: 7 additions & 5 deletions src/lib/lints/size-discrim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const nd = (p: number, s: number) => ({
});

const sMap = {
thin: 0.1,
medium: 0.5,
wide: 1.0,
Thin: 0.1,
Medium: 0.5,
Wide: 1.0,
default: 0.1,
};
const pMap = {
Expand Down Expand Up @@ -82,13 +82,15 @@ function uniqueJNDColors(key: string, jnds: ReturnType<typeof checkJNDs>) {
return [...uniqueColors].join(", ");
}

const Discrims = ["thin", "medium", "wide"].map((key) => {
const Discrims = ["Thin", "Medium", "Wide"].map((key) => {
return class SizeDiscrim extends ColorLint<
ReturnType<typeof checkJNDs>,
false
> {
name = `${key} Discriminability`;
taskTypes = ["sequential", "diverging", "categorical"] as TaskType[];
group = "usability";
description: string = `Pairs of colors in a palette should be differentiable from each other in ${key} lines. `;
_runCheck() {
const jnds = checkJNDs(this.palette.colors);
const passCheck = jnds.filter((x) => x[0] === key).length === 0;
Expand All @@ -97,7 +99,7 @@ const Discrims = ["thin", "medium", "wide"].map((key) => {
buildMessage() {
const jnds = this.checkData;
const invalid = uniqueJNDColors(key, jnds);
return `This palette has some colors (${invalid}) that are close to each other in perceptual space and will not be resolvable for ${key} areas`;
return `This palette has some colors (${invalid}) that are close to each other in perceptual space and will not be resolvable for ${key} areas.`;
}
};
});
Expand Down
Loading

0 comments on commit 4715ec2

Please sign in to comment.