Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make Detail scrollable and zoomable #38

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions src/routes/Brush.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<script>
import * as d3 from 'd3';
import {
DATA_MIN,
DATA_MAX,
BRUSH_WINDOW_DEFAULT_START,
BRUSH_WINDOW_DEFAULT_END
} from './Devilstable_DEFAULTS.json';

let marginTop = 20;
let marginRight = 0;
Expand All @@ -20,12 +26,10 @@
*/
let gBrush = $state();

/** @type {{width?: number, height?: number, DATA_MIN?: number, DATA_MAX?: number, data?: {values: boolean[], label: string}[], brushE: function}} */
/** @type {{width?: number, height?: number, data?: {values: boolean[], label: string}[], selection: {start: number, end: number}}} */
let {
width = 400,
height = 150,
DATA_MIN = 1,
DATA_MAX = 827,
data = [
{
label: 'D',
Expand All @@ -36,7 +40,7 @@
values: []
}
],
brushE
selection = $bindable()
} = $props();

let mobile = $derived(width > height);
Expand Down Expand Up @@ -95,28 +99,28 @@
.on('brush', (/** @type {{ selection: [number, number]; }} */ e) => {
const from = e.selection[0];
const to = e.selection[1];
if (Math.abs(from - to) <= 180) {
const start = Math.round(valuesDim.invert(from));
const end = Math.round(valuesDim.invert(to));

brushE({ start, end });
// Update range in Details
if (Math.abs(from - to) <= DATA_MAX - DATA_MIN) {
selection.start = Math.round(valuesDim.invert(from));
selection.end = Math.round(valuesDim.invert(to));
}
})
.on('end', (/** @type {{ selection: [number, number]; }} */ e) => {
const from = e.selection[0];
const to = e.selection[1];
if (Math.abs(from - to) > 180) {
const start = Math.round(valuesDim.invert(from));
const end = Math.round(valuesDim.invert(to));

brushE({ start, end });
// Update range in Details
if (Math.abs(from - to) > DATA_MAX - DATA_MIN) {
selection.start = Math.round(valuesDim.invert(from));
selection.end = Math.round(valuesDim.invert(to));
}
});
});
$effect(() => {
d3.select(gBrush)
.call(brush)
.call(brush.move, [valuesDim(DATA_MIN), valuesDim(100)]);
.call(brush.move, [valuesDim(selection.start), valuesDim(selection.end)]);
});
let chunkedData = $derived(
data.map((dataObject) => {
Expand Down
92 changes: 69 additions & 23 deletions src/routes/Detail.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@
import { computePosition, shift, flip, offset } from '@floating-ui/dom';
import { base } from '$app/paths';
import { summaryLabel } from '$lib/constants';
import {
DATA_MIN,
DATA_MAX,
SCROLL_SPEED,
ZOOM_INCREMENT,
ZOOM_MINIMUM_WINDOW_SIZE
} from './Devilstable_DEFAULTS.json';

/** @type {{codices: any, width?: number, height?: number, data_start?: number, data?: {values: boolean[], label: string}[]}} */
let { codices, width = 400, height = 400, data_start = 1, data = [] } = $props();
/** @type {{codices: any, width?: number, height?: number,data?: {values: boolean[], label: string}[], selection: {start: number, end: number}}} */
let { codices, width = 400, height = 400, data = [], selection = $bindable() } = $props();
let marginTop = 30;
let marginRight = 0;
let marginBottom = 20;
Expand Down Expand Up @@ -38,6 +45,44 @@
*/
let svgElement = $state();

const handleWheel = (/** @type {{ deltaY: any; }} */ event) => {
event.preventDefault();

// Check if the CTRL key is held down during the scroll
const isZooming = event.ctrlKey;

let delta = selection.end - selection.start;

if (isZooming) {
// Zoom in
if (event.deltaY > 0) {
// Zoom out
selection.start = Math.max(DATA_MIN, selection.start - ZOOM_INCREMENT);
selection.end = Math.min(DATA_MAX, selection.end + ZOOM_INCREMENT);
} else {
// Zoom in
if (delta > ZOOM_MINIMUM_WINDOW_SIZE) {
selection.start = Math.max(DATA_MIN, selection.start + ZOOM_INCREMENT);
selection.end = Math.max(
selection.start + ZOOM_MINIMUM_WINDOW_SIZE,
Math.min(DATA_MAX, selection.end - ZOOM_INCREMENT)
);
}
}
} else {
// Scroll
if (event.deltaY > 0) {
// Scroll down
selection.end = Math.min(DATA_MAX, (selection.end += SCROLL_SPEED));
selection.start = selection.end - delta;
} else {
// Scroll up
selection.start = Math.max(DATA_MIN, (selection.start -= SCROLL_SPEED));
selection.end = selection.start + delta;
}
}
};

const handleMouseMove = (/** @type {{ clientX: any; clientY: any; }} */ event) => {
mousePos = d3.pointer(event, svgElement);
if (mousePos[0] >= marginLeft && mousePos[1] >= marginTop) {
Expand Down Expand Up @@ -117,27 +162,26 @@
};
let contigousData = $derived(
data.map((d) => {
if (d.label === 'fr') {
return d;
}
// skip for label 'fr'
if (d.label === 'fr') return d;

// init
let contiguousRanges = [];
let start = 0;
let start = null;

//
for (let i = 0; i < d.values.length; i++) {
if (d.values[i]) {
if (start === 0) {
// console.log(d.label, i, data_start);
start = i + data_start;
}
} else {
if (start !== 0) {
contiguousRanges.push([start, i + data_start - 1]);
start = 0;
}
if (d.values[i] && start === null) {
// start a new range
start = i + selection.start;
} else if (!d.values[i] && start !== null) {
contiguousRanges.push([start, i + selection.start - 1]);
start = null; // reset start
}
}
if (start !== 0) {
// console.log(d.label, start, d.values.length - 1 + data_start);
contiguousRanges.push([start, d.values.length - 1 + data_start]);
// if range is still open at the end
if (start !== null) {
contiguousRanges.push([start, d.values.length + selection.start - 1]);
}
return {
label: d.label,
Expand All @@ -158,14 +202,14 @@
let y = $derived(
d3.scaleLinear(
// Domain: from bottom to top: at the bottom is the last selected verse, at the top is the first selected verse
[data_start + data[0]?.values.length, data_start],
[selection.start + data[0]?.values.length, selection.start],
[height - marginBottom, marginTop]
)
);
let verse = $derived(Math.floor(y.invert(mousePos[1])));
let manuscript = $derived(
scaleBandInvert(x)(mousePos[0]) === 'fr'
? data.find((d) => d.label === 'fr')?.values[verse - data_start] || 'fr'
? data.find((d) => d.label === 'fr')?.values[verse - selection.start] || 'fr'
: scaleBandInvert(x)(mousePos[0])
);
// $inspect(contigousData);
Expand Down Expand Up @@ -220,6 +264,7 @@
});
}
})

.on('blur', (e) => {
const reference = e.currentTarget;
const popup =
Expand Down Expand Up @@ -263,7 +308,7 @@
{/each}
{#each data.find((d) => d.label === 'fr')?.values || [] as fraction, i}
{#if Array.isArray(fraction)}
{@const verse = i + data_start}
{@const verse = i + selection.start}
<div
class="card p-1 variant-filled-primary top-0 left-0 w-max absolute opacity-0"
bind:this={popupFractions[verse]}
Expand All @@ -282,6 +327,7 @@
{/if}
{/each}
<div
onwheel={handleWheel}
onmousemove={handleMouseMove}
onmouseleave={(_e) => {
floating.style.display = 'none';
Expand All @@ -300,7 +346,7 @@
{#each sigla.values as values, i}
{#if values}
{#if isNaN(values[1])}
{@const verseNumber = i + data_start}
{@const verseNumber = i + selection.start}
{#if values.length === 1}
<a
href={`${base}/textzeugen/${values[0]}/${verseNumber}`}
Expand Down
29 changes: 17 additions & 12 deletions src/routes/Devilstable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import Brush from './Brush.svelte';
import Detail from './Detail.svelte';
import { summaryLabel } from '$lib/constants';

const DATA_MAX = 827;

import {
DATA_MAX,
BRUSH_WINDOW_DEFAULT_START,
BRUSH_WINDOW_DEFAULT_END
} from './Devilstable_DEFAULTS.json';
const brushDimension = 200;
const brushDimensionWithSafetyPixel = brushDimension + 1; // fixes a glitch, where Brush and Detail don't fit next to each other on PageResize.

Expand Down Expand Up @@ -71,14 +73,16 @@
})
);
let selection = $state({
start: 1,
end: 100
start: BRUSH_WINDOW_DEFAULT_START,
end: BRUSH_WINDOW_DEFAULT_END
});
let detailData = $derived(
boolData.map((d) => {
return { label: d.label, values: d.values.slice(selection.start - 1, selection.end) };
})
boolData.map(({ values, ...rest }) => ({
values: values.slice(selection.start - 1, selection.end),
...rest
}))
);
$inspect(selection);
</script>

<div class="card p-4 variant-filled-primary max-w-lg" data-popup="popupChips">
Expand Down Expand Up @@ -139,17 +143,18 @@
width={mobile ? width : brushDimension}
height={mobile ? brushDimension : height}
data={boolData.filter((d) => d.label !== summaryLabel)}
brushE={(/** @type {{ start: number; end: number; }} */ e) => {
bind:selection
/>
<!-- brushE={(/** @type {{ start: number; end: number; }} */ e) => {
selection.start = e.start;
selection.end = e.end;
}}
/>
}} -->
<Detail
{codices}
width={mobile ? width : width - brushDimensionWithSafetyPixel}
height={mobile ? height - brushDimension : height}
data={detailData}
data_start={selection.start}
bind:selection
/>

<style>
Expand Down
9 changes: 9 additions & 0 deletions src/routes/Devilstable_DEFAULTS.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"DATA_MIN": 1,
"DATA_MAX": 827,
"BRUSH_WINDOW_DEFAULT_START": 1,
"BRUSH_WINDOW_DEFAULT_END": 100,
"SCROLL_SPEED": 50,
"ZOOM_INCREMENT": 40,
"ZOOM_MINIMUM_WINDOW_SIZE": 50
}
Loading