diff --git a/src/components/ColorScatterPlot.svelte b/src/components/ColorScatterPlot.svelte index fff6d716..6de30189 100644 --- a/src/components/ColorScatterPlot.svelte +++ b/src/components/ColorScatterPlot.svelte @@ -10,6 +10,7 @@ makeScales, dragEventToColorZ, dragEventToColorXY, + screenSpaceAvg, } from "../lib/utils"; import configStore from "../stores/config-store"; import { scaleLinear } from "d3-scale"; @@ -100,6 +101,9 @@ $: x = scales.x; $: y = scales.y; $: z = scales.z; + // screen -> color space + $: xInv = scales.xInv; + $: yInv = scales.yInv; $: CircleProps = (color: Color, i: number) => ({ cx: x(color), @@ -137,7 +141,13 @@ dragging: interactionMode === "drag", }; - let interactionMode: "idle" | "drag" | "select" | "point-touch" = "idle"; + let interactionMode: + | "idle" + | "drag" + | "select" + | "point-touch" + | "stretch" + | "rotate" = "idle"; let dragTargetPos = { x: 0, y: 0 }; let dragDelta = { x: 0, y: 0 }; function dragStart(e: any) { @@ -258,6 +268,126 @@ "fill-opacity": "0.5", class: "pointer-events-none", }; + + $: initialRotationColors = [] as Color[]; + $: initialRotatePos = { x: 0, y: 0 }; + $: rotatePivotCenter = { x: 0, y: 0 }; + $: rotatePosition = { x: 0, y: 0 }; + + function rotateStart(e: any) { + // console.log("rotate start"); + startDragging(); + interactionMode = "rotate"; + initialRotationColors = [...colors]; + // const pos = toXY(e); + initialRotatePos = { + x: e.target.getBBox().x, + y: e.target.getBBox().y, + }; + rotatePivotCenter = screenSpaceAvg( + focusedColors + .map((x) => initialRotationColors[x]) + .map((el) => ({ + x: x(el), + y: y(el), + })) + ); + rotateUpdate(e); + } + + function rotateUpdate(e: any) { + // console.log("rotate update"); + let currentPos = toXY(e); + currentPos = { + x: currentPos.x - svgContainer.getBoundingClientRect().x, + y: currentPos.y - svgContainer.getBoundingClientRect().y, + }; + rotatePosition = currentPos; + let newAngle = Math.atan2( + currentPos.y - rotatePivotCenter.y, + currentPos.x - rotatePivotCenter.x + ); + const initialAngle = Math.atan2( + initialRotatePos.y - rotatePivotCenter.y, + initialRotatePos.x - rotatePivotCenter.x + ); + let angle = newAngle - initialAngle; + angle = angle < 0 ? angle + 2 * Math.PI : angle; + const xc = rotatePivotCenter.x; + const yc = rotatePivotCenter.y; + const newColors = focusedColors + .map((x) => initialRotationColors[x]) + .map((color) => { + const x1 = x(color); + const y1 = y(color); + const x3 = + Math.cos(angle) * (x1 - xc) - Math.sin(angle) * (y1 - yc) + xc; + const y3 = + Math.sin(angle) * (x1 - xc) + Math.cos(angle) * (y1 - yc) + yc; + const newChannels = [...color.toChannels()] as [number, number, number]; + newChannels[config.xChannelIndex] = xInv(x3, y3); + newChannels[config.yChannelIndex] = yInv(x3, y3); + return color.fromChannels(newChannels); + }); + onColorsChange(newColors); + } + function rotateEnd() { + // console.log("rotate end"); + interactionMode = "idle"; + stopDragging(); + } + + $: handles = [ + // { + // type: "stretch", + // label: "left", + // x: pos.xPos - 15, + // y: pos.yPos + pos.selectionHeight / 2, + // }, + // { + // type: "stretch", + // label: "right", + // x: pos.xPos + pos.selectionWidth + 5, + // y: pos.yPos + pos.selectionHeight / 2, + // }, + // { + // type: "stretch", + // label: "top", + // x: pos.xPos + pos.selectionWidth / 2, + // y: pos.yPos - 15, + // }, + // { + // type: "stretch", + // label: "bottom", + // x: pos.xPos + pos.selectionWidth / 2, + // y: pos.yPos + pos.selectionHeight + 5, + // }, + { + type: "rotate", + label: "", + x: pos.xPos, + y: pos.yPos, + }, + { + type: "rotate", + label: "", + x: pos.xPos, + y: pos.yPos + pos.selectionHeight, + }, + { + type: "rotate", + label: "", + x: pos.xPos + pos.selectionWidth, + y: pos.yPos, + }, + { + type: "rotate", + label: "", + x: pos.xPos + pos.selectionWidth, + y: pos.yPos + pos.selectionHeight, + }, + ]; + let svgContainer: any; @@ -269,7 +399,7 @@
- + {#if config.isPolar} @@ -283,10 +413,14 @@ on:touchstart|preventDefault={selectionStart(false)} on:touchmove|preventDefault={interactionMode === "drag" ? dragUpdate(false) - : selectionUpdate(false)} + : interactionMode === "rotate" + ? rotateUpdate + : selectionUpdate(false)} on:touchend|preventDefault={interactionMode === "drag" ? dragEnd(false) - : selectionEnd(false)} + : interactionMode === "rotate" + ? rotateEnd + : selectionEnd(false)} /> dragStart(e)} /> {/if} + {#if focusedColors.length > 1} + {#each handles as handle} + {#if handle.type === "stretch"} + + {/if} + {#if handle.type === "rotate"} + + {/if} + {/each} + {#if interactionMode === "rotate"} + + + {/if} + {/if} {#if interactionMode === "select"} @@ -400,6 +579,15 @@ on:touchend|preventDefault={dragEnd(false)} /> {/if} + {#if interactionMode === "rotate"} + + {/if}
diff --git a/src/content-modules/Controls.svelte b/src/content-modules/Controls.svelte index 996f0616..07204775 100644 --- a/src/content-modules/Controls.svelte +++ b/src/content-modules/Controls.svelte @@ -8,7 +8,6 @@ import DistributePoints from "./contextual-tools/DistributePoints.svelte"; import AdjustColor from "./contextual-tools/AdjustColor.svelte"; import AddColor from "./contextual-tools/AddColor.svelte"; - import Rotate from "./contextual-tools/Rotate.svelte"; import ColorChannelPicker from "../components/ColorChannelPicker.svelte"; $: currentPal = $colorStore.palettes[$colorStore.currentPal]; @@ -31,7 +30,6 @@ {/if} - diff --git a/src/content-modules/contextual-tools/Rotate.svelte b/src/content-modules/contextual-tools/Rotate.svelte deleted file mode 100644 index deb77bbb..00000000 --- a/src/content-modules/contextual-tools/Rotate.svelte +++ /dev/null @@ -1,168 +0,0 @@ - - -{#if focusedColors.length > 0} -
-
-
Rotate
- - -
-
-
Around which point?
-
- - - - {#each focusedColors as colorIdx} - - {/each} -
-
- Rotate about the - - Axis -
-
-
-
-
-
-
- - -
-
-{/if} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 7cd1803b..64962281 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -384,3 +384,9 @@ export const dragEventToColorXY: dragEventToColor = ( coords[config.yChannelIndex] = newVal[1]; return Color.colorFromChannels(coords, colorSpace); }; + +export const screenSpaceAvg = (colors: { x: number; y: number }[]) => { + const xAvg = colors.reduce((acc, x) => acc + x.x, 0) / colors.length; + const yAvg = colors.reduce((acc, x) => acc + x.y, 0) / colors.length; + return { x: xAvg, y: yAvg }; +};