kripke: fix arrow tip issue in Safari
This commit is contained in:
@@ -9,7 +9,15 @@ import {
|
|||||||
worlds,
|
worlds,
|
||||||
} from "@cannorin/kripke";
|
} from "@cannorin/kripke";
|
||||||
import type { SVGAttributes } from "svelte/elements";
|
import type { SVGAttributes } from "svelte/elements";
|
||||||
import { type Vector, add, degree, rotate, sub, theta } from "../lib/vector";
|
import {
|
||||||
|
type Radian,
|
||||||
|
type Vector,
|
||||||
|
add,
|
||||||
|
degree,
|
||||||
|
rotate,
|
||||||
|
sub,
|
||||||
|
theta,
|
||||||
|
} from "../lib/vector";
|
||||||
|
|
||||||
export interface FrameInputProps extends SVGAttributes<SVGElement> {
|
export interface FrameInputProps extends SVGAttributes<SVGElement> {
|
||||||
frame?: Frame | undefined;
|
frame?: Frame | undefined;
|
||||||
@@ -82,23 +90,32 @@ const center: Vector = { x: 125, y: 125 };
|
|||||||
|
|
||||||
const radius: Vector = { x: 20, y: 0 };
|
const radius: Vector = { x: 20, y: 0 };
|
||||||
|
|
||||||
function getSelfPath(w: World) {
|
function tip(position: Vector, angle: Radian) {
|
||||||
|
const length = 8;
|
||||||
|
const offset = degree(20);
|
||||||
|
const lb = add(position, rotate({ x: length, y: 0 }, angle - offset));
|
||||||
|
const rb = add(position, rotate({ x: length, y: 0 }, angle + offset));
|
||||||
|
return { lb, rb };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelfArrow(w: World) {
|
||||||
const angle = theta(sub(center, positions[w])) + Math.PI;
|
const angle = theta(sub(center, positions[w])) + Math.PI;
|
||||||
const offset = degree(45);
|
const offset = degree(45);
|
||||||
const loopRadius = 20;
|
const loopRadius = 20;
|
||||||
const start = add(positions[w], rotate(radius, angle - offset));
|
const start = add(positions[w], rotate(radius, angle - offset));
|
||||||
const end = add(positions[w], rotate(radius, angle + offset));
|
const end = add(positions[w], rotate(radius, angle + offset));
|
||||||
|
const { lb, rb } = tip(end, angle + offset - degree(10));
|
||||||
|
|
||||||
return `
|
return {
|
||||||
M ${start.x} ${start.y}
|
path: `M ${start.x} ${start.y} A ${loopRadius} ${loopRadius} 0 1 1 ${end.x} ${end.y}`,
|
||||||
A ${loopRadius} ${loopRadius} 0 1 1 ${end.x} ${end.y}
|
tipPolygon: `${end.x} ${end.y} ${lb.x} ${lb.y} ${rb.x} ${rb.y}`,
|
||||||
`;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPath(rel: Relation) {
|
function getArrow(rel: Relation) {
|
||||||
const l = left(rel);
|
const l = left(rel);
|
||||||
const r = right(rel);
|
const r = right(rel);
|
||||||
if (l === r) return getSelfPath(l);
|
if (l === r) return getSelfArrow(l);
|
||||||
|
|
||||||
const angle = theta(sub(positions[r], positions[l]));
|
const angle = theta(sub(positions[r], positions[l]));
|
||||||
|
|
||||||
@@ -106,62 +123,61 @@ function getPath(rel: Relation) {
|
|||||||
|
|
||||||
const dl = rotate(radius, angle + offset);
|
const dl = rotate(radius, angle + offset);
|
||||||
const dr = rotate(radius, angle + Math.PI - offset);
|
const dr = rotate(radius, angle + Math.PI - offset);
|
||||||
|
|
||||||
const from = add(positions[l], dl);
|
const from = add(positions[l], dl);
|
||||||
const to = add(positions[r], dr);
|
const to = add(positions[r], dr);
|
||||||
|
const { lb, rb } = tip(to, angle + Math.PI - offset);
|
||||||
|
|
||||||
if (!frame.relations.has(reverse(rel)))
|
if (!frame.relations.has(reverse(rel)))
|
||||||
return `M ${from.x} ${from.y} L ${to.x} ${to.y}`;
|
return {
|
||||||
|
path: `M ${from.x} ${from.y} L ${to.x} ${to.y}`,
|
||||||
|
tipPolygon: `${to.x} ${to.y} ${lb.x} ${lb.y} ${rb.x} ${rb.y}`,
|
||||||
|
};
|
||||||
|
|
||||||
return `M ${from.x} ${from.y} C ${from.x + dl.x * 2} ${from.y + dl.y * 2}, ${to.x + dr.x * 2} ${to.y + dr.y * 2}, ${to.x} ${to.y}`;
|
return {
|
||||||
|
path: `M ${from.x} ${from.y} C ${from.x + dl.x * 2} ${from.y + dl.y * 2}, ${to.x + dr.x * 2} ${to.y + dr.y * 2}, ${to.x} ${to.y}`,
|
||||||
|
tipPolygon: `${to.x} ${to.y} ${lb.x} ${lb.y} ${rb.x} ${rb.y}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.node {
|
||||||
|
fill: rgb(var(--background));
|
||||||
|
stroke: rgb(var(--foreground));
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
.node.selected {
|
||||||
|
stroke: rgb(var(--primary));
|
||||||
|
stroke-width: 3;
|
||||||
|
}
|
||||||
|
.edge {
|
||||||
|
stroke: rgb(var(--foreground));
|
||||||
|
stroke-width: 1;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
.tip {
|
||||||
|
stroke: rgb(var(--foreground));
|
||||||
|
stroke-width: 0.5;
|
||||||
|
fill: rgb(var(--foreground));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<svg width={width ?? 250} height={height ?? 250} {...rest} viewBox="0,0,250,250" onclick={handleSvgClick} xmlns="http://www.w3.org/2000/svg">
|
<svg width={width ?? 250} height={height ?? 250} {...rest} viewBox="0,0,250,250" onclick={handleSvgClick} xmlns="http://www.w3.org/2000/svg">
|
||||||
<style>
|
|
||||||
.node {
|
|
||||||
fill: rgb(var(--background));
|
|
||||||
stroke: rgb(var(--foreground));
|
|
||||||
stroke-width: 1;
|
|
||||||
}
|
|
||||||
.node.selected {
|
|
||||||
stroke: rgb(var(--primary));
|
|
||||||
stroke-width: 3;
|
|
||||||
}
|
|
||||||
.edge {
|
|
||||||
stroke: rgb(var(--foreground));
|
|
||||||
stroke-width: 1;
|
|
||||||
fill: none;
|
|
||||||
marker-end: url(#arrowhead);
|
|
||||||
}
|
|
||||||
.arrowhead {
|
|
||||||
fill: rgb(var(--foreground));
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<defs>
|
|
||||||
<marker
|
|
||||||
id="arrowhead"
|
|
||||||
viewBox="0 0 10 10"
|
|
||||||
refX="8"
|
|
||||||
refY="5"
|
|
||||||
markerWidth="6"
|
|
||||||
markerHeight="6"
|
|
||||||
orient="auto"
|
|
||||||
markerUnits="strokeWidth"
|
|
||||||
>
|
|
||||||
<path class="arrowhead" d="M0,0 L0,10 L10,5 z" />
|
|
||||||
</marker>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
{#each Array.from(frame.relations) as rel}
|
{#each Array.from(frame.relations) as rel}
|
||||||
{#key rel}
|
{@const { path, tipPolygon } = getArrow(rel)}
|
||||||
<path
|
<path
|
||||||
d={getPath(rel)}
|
d={path}
|
||||||
class={["edge", !disabled && "cursor-pointer"]}
|
class={["edge", !disabled && "cursor-pointer"]}
|
||||||
onclick={(e) => !disabled && handleEdgeClick(rel, e)}
|
onclick={(e) => !disabled && handleEdgeClick(rel, e)}
|
||||||
/>
|
/>
|
||||||
{/key}
|
<polygon
|
||||||
|
class="tip"
|
||||||
|
points={tipPolygon}
|
||||||
|
onclick={(e) => !disabled && handleEdgeClick(rel, e)}
|
||||||
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#each worlds as w}
|
{#each worlds as w}
|
||||||
|
|||||||
@@ -107,21 +107,8 @@ const colors: Record<number, string> = {
|
|||||||
|
|
||||||
{#snippet sampleArrow()}
|
{#snippet sampleArrow()}
|
||||||
<svg width="50" height="10" class="mt-[2px]">
|
<svg width="50" height="10" class="mt-[2px]">
|
||||||
<defs>
|
<path d="M 0 5 L 45 5" stroke="rgb(var(--foreground))" stroke-width="1" />
|
||||||
<marker
|
<polygon points="46 5 38.4824590337 7.73616114661 38.4824590337 2.26383885339" stroke="rgb(var(--foreground))" stroke-width="0.5" fill="rgb(var(--foreground))" />"
|
||||||
id="arrowhead"
|
|
||||||
viewBox="0 0 10 10"
|
|
||||||
refX="8"
|
|
||||||
refY="5"
|
|
||||||
markerWidth="6"
|
|
||||||
markerHeight="6"
|
|
||||||
orient="auto"
|
|
||||||
markerUnits="strokeWidth"
|
|
||||||
>
|
|
||||||
<path d="M0,0 L0,10 L10,5 z" fill="rgb(var(--foreground))" />
|
|
||||||
</marker>
|
|
||||||
</defs>
|
|
||||||
<path d="M 5 5 L 45 5" stroke="rgb(var(--foreground))" stroke-width="1" marker-end="url(#arrowhead)" />
|
|
||||||
</svg>
|
</svg>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user