kripke: fix arrow tip issue in Safari

This commit is contained in:
2025-02-22 12:09:53 +09:00
parent adfae65138
commit ca13b55dc0
2 changed files with 70 additions and 67 deletions

View File

@@ -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,20 +123,25 @@ 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>
<!-- svelte-ignore a11y_click_events_have_key_events --> <style>
<!-- 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">
<style>
.node { .node {
fill: rgb(var(--background)); fill: rgb(var(--background));
stroke: rgb(var(--foreground)); stroke: rgb(var(--foreground));
@@ -133,35 +155,29 @@ function getPath(rel: Relation) {
stroke: rgb(var(--foreground)); stroke: rgb(var(--foreground));
stroke-width: 1; stroke-width: 1;
fill: none; fill: none;
marker-end: url(#arrowhead);
} }
.arrowhead { .tip {
stroke: rgb(var(--foreground));
stroke-width: 0.5;
fill: rgb(var(--foreground)); fill: rgb(var(--foreground));
} }
</style> </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>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- 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">
{#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}

View File

@@ -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}