upend/webui/src/views/Surface.svelte

175 lines
4.0 KiB
Svelte

<script lang="ts">
import UpObject from "../components/display/UpObject.svelte";
import { queryOnce } from "../lib/api";
import Selector from "../components/utils/Selector.svelte";
import * as d3 from "d3";
import { onMount } from "svelte";
import type { D3ZoomEvent, ZoomTransform } from "d3";
const urlSearch = window.location.href.substring(
window.location.href.indexOf("?")
);
const urlParams = new URLSearchParams(urlSearch);
let x: string = urlParams.get("x");
let y: string = urlParams.get("y");
interface IPoint {
address: string;
x: number;
y: number;
}
let points: IPoint[] = [];
$: {
if (x && y) {
points = [];
queryOnce(`(matches ? (in "${x}" "${y}") ?)`).then((result) => {
points = Object.entries(result.objects)
.map(([address, obj]) => {
let objX = parseInt(String(obj.get(x)));
let objY = parseInt(String(obj.get(y)));
if (objX && objY) {
return {
address,
x: objX,
y: objY,
};
}
})
.filter(Boolean);
});
}
}
onMount(() => {
const view = d3.select(".view");
const { width, height } = (
view.node() as HTMLElement
).getBoundingClientRect();
const svg = view.append("svg");
const xScale = d3.scaleLinear().domain([0, width]).range([0, width]);
const xAxis = d3
.axisBottom(xScale)
.ticks(15)
.tickSize(height)
.tickPadding(5 - height);
const yScale = d3.scaleLinear().domain([0, height]).range([0, height]);
const yAxis = d3
.axisRight(yScale)
.ticks(height / (width / 15))
.tickSize(width)
.tickPadding(5 - width);
const gX = svg.append("g").attr("class", "axis axis--x").call(xAxis);
const gY = svg.append("g").attr("class", "axis axis--y").call(yAxis);
const zoom = d3
.zoom()
// .scaleExtent([1, 40])
// .translateExtent([
// [-100, -100],
// [width + 90, height + 100],
// ])
.on("zoom", zoomed);
function zoomed({ transform }: { transform: ZoomTransform }) {
const points = d3.select(".points");
points.style(
"transform",
`translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`
);
const allPoints = d3.selectAll(".point");
allPoints.style("transform", `scale(${1 / transform.k})`);
gX.call(xAxis.scale(transform.rescaleX(xScale)));
gY.call(yAxis.scale(transform.rescaleY(yScale)));
}
function reset() {
svg.transition().duration(750).call(zoom.transform, d3.zoomIdentity);
}
d3.select(".view").call(zoom);
});
</script>
<div class="surface">
<div class="header">
<div class="axis-selector">
X: <Selector type="attribute" bind:attribute={x} />
</div>
<div class="axis-selector">
Y: <Selector type="attribute" bind:attribute={y} />
</div>
</div>
<div class="view">
<div class="points">
{#each points as point}
<div class="point" style="left: {point.x}px; top: {point.y}px">
<div class="inner">
<UpObject point link address={point.address} />
</div>
</div>
{/each}
</div>
</div>
</div>
<style lang="scss">
.surface {
display: flex;
flex-direction: column;
height: 100%;
}
.header {
display: flex;
gap: 2em;
padding: 1em;
border-bottom: 1px solid var(--foreground);
.axis-selector {
display: flex;
gap: 1em;
align-items: center;
}
}
.view {
flex-grow: 1;
position: relative;
overflow: hidden;
:global(svg) {
width: 100%;
height: 100%;
}
:global(.tick text) {
color: var(--foreground-lightest);
font-size: 1rem;
}
.points {
transform-origin: 0 0;
}
.point {
position: absolute;
transform-origin: 0 0;
.inner {
transform: translate(-50%, -50%);
}
}
}
</style>