Skip to content

Commit

Permalink
abstract out the xyguides
Browse files Browse the repository at this point in the history
  • Loading branch information
mcnuttandrew committed Jan 12, 2024
1 parent b971085 commit b6a8362
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 89 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The basic UX for editing has the following components
The 2D graph displays hue/chroma graph and the 1D graph displays lightness. You can map any of a number of colorspaces onto this pair of graphs. (in the code, the lightness graph is "Z"). I propose the following changes

- [x] Reduce the visual impact of the axes and labels by making them transparent gray. Set the colors, made them adaptive, set the luminance flip to .3 (50% visually)
- [ ] Flip the Y axis (zero at the bottom)
- [x] Flip the Y axis (zero at the bottom)
- [ ] Make the labels integers for CIELAB
- [x] Make the axis scale sliders less visualy prominent.
- [ ] Consider removing the axis sliders, replace them with zoom controls in the same panel as the background color selection. Changing these values is rare, they don't need to take up so much UX space.
Expand Down
99 changes: 11 additions & 88 deletions src/components/ColorScatterPlot.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import DoubleRangeSlider from "../components/DoubleRangeSlider.svelte";
import VerticalDoubleRangeSlider from "../components/VerticalDoubleRangeSlider.svelte";
import simulate_cvd from "../lib/blindness";
import ColorScatterPlotXyGuides from "./ColorScatterPlotXYGuides.svelte";
export let scatterPlotMode: "moving" | "looking";
Expand Down Expand Up @@ -59,14 +60,12 @@
$: xScale = scaleLinear()
.domain([domainXScale(extents.x[0]), domainXScale(extents.x[1])])
.range([0, plotWidth]);
$: xNonDimScale = scaleLinear().domain([0, 1]).range(xScale.domain());
$: yRange = config.yDomain;
$: domainYScale = scaleLinear().domain([0, 1]).range(yRange);
$: yScale = scaleLinear()
.domain([domainYScale(extents.y[0]), domainYScale(extents.y[1])])
.range([0, plotHeight]);
$: yNonDimScale = scaleLinear().domain([0, 1]).range(yScale.domain());
$: zRange = config.zDomain;
$: domainLScale = scaleLinear().domain([0, 1]).range(zRange);
Expand Down Expand Up @@ -224,36 +223,6 @@
onFocusedColorsChange([...newFocusedColors]);
}
$: points = {
centerTop: {
x: (xScale.range()[1] - xScale.range()[0]) / 2,
y: yScale.range()[0],
labelAdjust: { x: -5, y: 15 },
anchor: "end",
label: `${config.yChannel}: ${yScale.domain()[0].toFixed(1)}`,
},
centerBottom: {
x: (xScale.range()[1] - xScale.range()[0]) / 2,
y: yScale.range()[1],
anchor: "start",
labelAdjust: { x: 0, y: -3 },
label: yScale.domain()[1].toFixed(1),
},
centerLeft: {
x: xScale.range()[0],
y: (yScale.range()[1] - yScale.range()[0]) / 2,
anchor: "start",
labelAdjust: { x: 5, y: 15 },
label: xScale.domain()[0].toFixed(1),
},
centerRight: {
x: xScale.range()[1],
y: (yScale.range()[1] - yScale.range()[0]) / 2,
anchor: "end",
labelAdjust: { x: -5, y: 0 },
label: `${config.xChannel}: ${xScale.domain()[1].toFixed(1)}`,
},
};
$: zPoints = {
top: {
y: zScale.range()[0] + 15,
Expand All @@ -276,11 +245,6 @@
$: x = (point: Color) => xScale(point.toChannels()[1]);
$: y = (point: Color) => yScale(point.toChannels()[2]);
$: z = (point: Color) => zScale(point.toChannels()[0]);
const avgNums = (nums: number[]) =>
nums.reduce((acc, x) => acc + x, 0) / nums.length;
const bgResolution = 25;
</script>

<!-- svelte-ignore a11y-no-static-element-interactions -->
Expand All @@ -302,58 +266,17 @@
on:mouseup={stopDrag}
on:touchend={stopDrag}
>
<!-- colorful background select -->
<g transform={`translate(${margin.left}, ${margin.top})`}>
{#each [...new Array(bgResolution)] as _, i}
{#each [...new Array(bgResolution)] as _, j}
<rect
x={xScale(xNonDimScale(i / bgResolution))}
y={yScale(yNonDimScale(j / bgResolution))}
width={plotWidth / bgResolution}
height={plotHeight / bgResolution}
opacity="1"
fill={dragging && focusedColors.length === 1
? colorFromChannels(
[
avgNums(
focusedColors.map((x) => colors[x].toChannels()[0])
),
xNonDimScale(i / bgResolution),
yNonDimScale(j / bgResolution),
],
colorSpace
).toHex()
: bg.toHex()}
/>
{/each}
{/each}

<line
x1={points.centerTop.x}
y1={points.centerTop.y}
x2={points.centerBottom.x}
y2={points.centerBottom.y}
stroke={axisColor}
stroke-width="1"
<g transform={`translate(${margin.left}, ${margin.top}`}>
<ColorScatterPlotXyGuides
{xScale}
{yScale}
{plotHeight}
{plotWidth}
{axisColor}
{textColor}
{colorSpace}
dragging={!!dragging}
/>
<line
x1={points.centerLeft.x}
y1={points.centerLeft.y}
x2={points.centerRight.x}
y2={points.centerRight.y}
stroke={axisColor}
stroke-width="1"
/>
{#each Object.values(points) as point}
<text
text-anchor={point.anchor}
x={point.x + point.labelAdjust.x}
y={point.y + point.labelAdjust.y}
fill={textColor}
>
{point.label}
</text>
{/each}

<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
Expand Down
109 changes: 109 additions & 0 deletions src/components/ColorScatterPlotXYGuides.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script lang="ts">
import { colorPickerConfig, colorFromChannels } from "../lib/Color";
import focusStore from "../stores/focus-store";
import colorStore from "../stores/color-store";
import { scaleLinear } from "d3-scale";
export let xScale: any;
export let yScale: any;
export let plotHeight: number;
export let plotWidth: number;
export let colorSpace: string;
export let dragging: boolean;
export let axisColor: string;
export let textColor: string;
$: config = colorPickerConfig[colorSpace as keyof typeof colorPickerConfig];
$: xNonDimScale = scaleLinear().domain([0, 1]).range(xScale.domain());
$: yNonDimScale = scaleLinear().domain([0, 1]).range(yScale.domain());
$: focusedColors = $focusStore.focusedColors;
$: colors = $colorStore.currentPal.colors;
$: axisFormatter =
colorPickerConfig[colorSpace as keyof typeof colorPickerConfig].axisLabel;
$: points = {
centerTop: {
x: (xScale.range()[1] - xScale.range()[0]) / 2,
y: yScale.range()[0],
labelAdjust: { x: -5, y: 15 },
anchor: "end",
label: `${config.yChannel}: ${axisFormatter(yScale.domain()[0])}`,
},
centerBottom: {
x: (xScale.range()[1] - xScale.range()[0]) / 2,
y: yScale.range()[1],
anchor: "start",
labelAdjust: { x: 0, y: -3 },
label: axisFormatter(yScale.domain()[1]),
},
centerLeft: {
x: xScale.range()[0],
y: (yScale.range()[1] - yScale.range()[0]) / 2,
anchor: "start",
labelAdjust: { x: 5, y: 15 },
label: axisFormatter(xScale.domain()[0]),
},
centerRight: {
x: xScale.range()[1],
y: (yScale.range()[1] - yScale.range()[0]) / 2,
anchor: "end",
labelAdjust: { x: -5, y: 0 },
label: `${config.xChannel}: ${axisFormatter(xScale.domain()[1])}`,
},
};
const bgResolution = 25;
const avgNums = (nums: number[]) =>
nums.reduce((acc, x) => acc + x, 0) / nums.length;
$: bg = $colorStore.currentPal.background;
$: fillColor = (i: number, j: number) => {
if (dragging && focusedColors.length === 1) {
const avgColor = [
avgNums(focusedColors.map((x) => colors[x].toChannels()[0])),
xNonDimScale(i / bgResolution),
yNonDimScale(j / bgResolution),
] as [number, number, number];
return colorFromChannels(avgColor, colorSpace as any).toHex();
}
return bg.toHex();
};
</script>

<!-- colorful background select -->
{#each [...new Array(bgResolution)] as _, i}
{#each [...new Array(bgResolution)] as _, j}
<rect
x={xScale(xNonDimScale(i / bgResolution))}
y={yScale(yNonDimScale(j / bgResolution))}
width={plotWidth / bgResolution}
height={plotHeight / bgResolution}
opacity="1"
fill={fillColor(i, j)}
/>
{/each}
{/each}

<line
x1={points.centerTop.x}
y1={points.centerTop.y}
x2={points.centerBottom.x}
y2={points.centerBottom.y}
stroke={axisColor}
stroke-width="1"
/>
<line
x1={points.centerLeft.x}
y1={points.centerLeft.y}
x2={points.centerRight.x}
y2={points.centerRight.y}
stroke={axisColor}
stroke-width="1"
/>
{#each Object.values(points) as point}
<text
text-anchor={point.anchor}
x={point.x + point.labelAdjust.x}
y={point.y + point.labelAdjust.y}
fill={textColor}
>
{point.label}
</text>
{/each}
5 changes: 5 additions & 0 deletions src/lib/Color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class Color {
xyTitle: string = "";
zTitle: string = "";
dimensionToChannel: Record<"x" | "y" | "z", string> = { x: "", y: "", z: "" };
axisLabel: (num: number) => string = (x) => x.toFixed(1).toString();

constructor() {
this.domains = {};
Expand Down Expand Up @@ -131,6 +132,7 @@ export class CIELAB extends Color {
xyTitle: string = "CIELAB: a* b*";
zTitle: string = "CIELAB: L*";
dimensionToChannel = { x: "a", y: "b", z: "L" };
axisLabel = (num: number) => `${Math.round(num)}`;

toString(): string {
const [L, a, b] = Object.values(this.channels).map((x) => x || 0);
Expand Down Expand Up @@ -164,6 +166,7 @@ export class RGB extends Color {
xyTitle: string = "RGB: Green Blue";
zTitle: string = "RGB: Red";
dimensionToChannel = { x: "g", y: "b", z: "r" };
axisLabel = (num: number) => `${Math.round(num)}`;
}

export class HSL extends Color {
Expand Down Expand Up @@ -192,6 +195,7 @@ export class LCH extends Color {
xyTitle: string = "LCH: Chroma Hue";
zTitle: string = "LCH: Lightness";
dimensionToChannel = { x: "c", y: "h", z: "l" };
axisLabel = (num: number) => `${Math.round(num)}`;
}

export class OKLAB extends Color {
Expand Down Expand Up @@ -312,6 +316,7 @@ export const colorPickerConfig = Object.fromEntries(
xStep: exampleColor.stepSize[1],
yStep: exampleColor.stepSize[2],
zStep: exampleColor.stepSize[0],
axisLabel: exampleColor.axisLabel,
},
];
})
Expand Down

0 comments on commit b6a8362

Please sign in to comment.