This commit is contained in:
Nikita Miloserdov 2026-01-29 05:38:36 +01:00 committed by GitHub
commit 71e42c52aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 132 additions and 22 deletions

View File

@ -41,7 +41,11 @@ export const defaultContentPageLayout: PageLayout = {
Component.Explorer(),
],
right: [
Component.Graph(),
Component.Graph({
localGraph: {
defaultZoom: 2,
},
}),
Component.DesktopOnly(Component.TableOfContents()),
Component.Backlinks(),
],

View File

@ -19,6 +19,7 @@ export interface D3Config {
showTags: boolean
focusOnHover?: boolean
enableRadial?: boolean
defaultZoom?: number
}
interface GraphOptions {
@ -41,6 +42,7 @@ const defaultOptions: GraphOptions = {
removeTags: [],
focusOnHover: false,
enableRadial: false,
defaultZoom: 1,
},
globalGraph: {
drag: true,
@ -56,6 +58,7 @@ const defaultOptions: GraphOptions = {
removeTags: [],
focusOnHover: true,
enableRadial: true,
defaultZoom: 1,
},
}

View File

@ -87,6 +87,7 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
showTags,
focusOnHover,
enableRadial,
defaultZoom,
} = JSON.parse(graph.dataset["cfg"]!) as D3Config
const data: Map<SimpleSlug, ContentDetails> = new Map(
@ -497,30 +498,41 @@ async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) {
}
if (enableZoom) {
select<HTMLCanvasElement, NodeData>(app.canvas).call(
zoom<HTMLCanvasElement, NodeData>()
.extent([
[0, 0],
[width, height],
])
.scaleExtent([0.25, 4])
.on("zoom", ({ transform }) => {
currentTransform = transform
stage.scale.set(transform.k, transform.k)
stage.position.set(transform.x, transform.y)
const zoomBehavior = zoom<HTMLCanvasElement, NodeData>()
.extent([
[0, 0],
[width, height],
])
.scaleExtent([0.25, 4])
.on("zoom", ({ transform }) => {
currentTransform = transform
stage.scale.set(transform.k, transform.k)
stage.position.set(transform.x, transform.y)
// zoom adjusts opacity of labels too
const scale = transform.k * opacityScale
let scaleOpacity = Math.max((scale - 1) / 3.75, 0)
const activeNodes = nodeRenderData.filter((n) => n.active).flatMap((n) => n.label)
// zoom adjusts opacity of labels too
const scale = transform.k * opacityScale
let scaleOpacity = Math.max((scale - 1) / 3.75, 0)
const activeNodes = nodeRenderData.filter((n) => n.active).flatMap((n) => n.label)
for (const label of labelsContainer.children) {
if (!activeNodes.includes(label)) {
label.alpha = scaleOpacity
}
for (const label of labelsContainer.children) {
if (!activeNodes.includes(label)) {
label.alpha = scaleOpacity
}
}),
)
}
})
const canvas = select<HTMLCanvasElement, NodeData>(app.canvas)
canvas.call(zoomBehavior)
// Apply default zoom (defaults to 1 if not provided)
const zoomLevel = defaultZoom ?? 1
if (zoomLevel !== 1) {
const defaultTransform = zoomIdentity
.translate(width / 2, height / 2)
.scale(zoomLevel)
.translate(-width / 2, -height / 2)
canvas.call(zoomBehavior.transform, defaultTransform)
}
}
let stopAnimation = false

View File

@ -0,0 +1,91 @@
import test, { describe } from "node:test"
import assert from "node:assert"
import { zoomIdentity } from "d3"
describe("graph", () => {
describe("defaultZoom", () => {
test("should create identity transform when not specified", () => {
const transform = zoomIdentity
assert.strictEqual(transform.k, 1)
assert.strictEqual(transform.x, 0)
assert.strictEqual(transform.y, 0)
})
test("should scale correctly when defaultZoom is 2", () => {
const width = 400
const height = 300
const defaultZoom = 2
const transform = zoomIdentity
.translate(width / 2, height / 2)
.scale(defaultZoom)
.translate(-width / 2, -height / 2)
assert.strictEqual(transform.k, defaultZoom)
assert.strictEqual(transform.x, -200)
assert.strictEqual(transform.y, -150)
})
test("should produce identity-like transform when defaultZoom is 1", () => {
const width = 400
const height = 300
const defaultZoom = 1
const transform = zoomIdentity
.translate(width / 2, height / 2)
.scale(defaultZoom)
.translate(-width / 2, -height / 2)
assert.strictEqual(transform.k, 1)
assert.strictEqual(transform.x, 0)
assert.strictEqual(transform.y, 0)
})
test("should zoom out when defaultZoom is 0.5", () => {
const width = 400
const height = 300
const defaultZoom = 0.5
const transform = zoomIdentity
.translate(width / 2, height / 2)
.scale(defaultZoom)
.translate(-width / 2, -height / 2)
assert.strictEqual(transform.k, 0.5)
assert.strictEqual(transform.x, 100)
assert.strictEqual(transform.y, 75)
})
test("should keep center point stationary after zoom", () => {
const width = 400
const height = 300
const defaultZoom = 2
const transform = zoomIdentity
.translate(width / 2, height / 2)
.scale(defaultZoom)
.translate(-width / 2, -height / 2)
const centerX = width / 2
const centerY = height / 2
const [newX, newY] = transform.apply([centerX, centerY])
assert.strictEqual(newX, centerX)
assert.strictEqual(newY, centerY)
})
test("should default to 1 when defaultZoom is undefined", () => {
const defaultZoom: number | undefined = undefined
const zoomLevel = defaultZoom ?? 1
assert.strictEqual(zoomLevel, 1)
})
test("should use provided value when defaultZoom is defined", () => {
const defaultZoom: number | undefined = 2.5
const zoomLevel = defaultZoom ?? 1
assert.strictEqual(zoomLevel, 2.5)
})
})
})