diff --git a/content/index.md b/content/index.md index 737fd7d..1632384 100644 --- a/content/index.md +++ b/content/index.md @@ -7,4 +7,4 @@ date: 2025-01-10 --- Hello World! -[heres the todo](https://quartz.jzhao.xyz/authoring-content) +[Here's the TODO](https://quartz.jzhao.xyz/authoring-content) diff --git a/docs/features/graph view.md b/docs/features/graph view.md index 4f905c7..19f0862 100644 --- a/docs/features/graph view.md +++ b/docs/features/graph view.md @@ -36,6 +36,7 @@ Component.Graph({ opacityScale: 1, // how quickly do we fade out the labels when zooming out? removeTags: [], // what tags to remove from the graph showTags: true, // whether to show tags in the graph + enableRadial: false, // whether to constrain the graph, similar to Obsidian }, globalGraph: { drag: true, @@ -49,6 +50,7 @@ Component.Graph({ opacityScale: 1, removeTags: [], // what tags to remove from the graph showTags: true, // whether to show tags in the graph + enableRadial: true, // whether to constrain the graph, similar to Obsidian }, }) ``` diff --git a/flake.nix b/flake.nix index 6c03087..a4d7888 100644 --- a/flake.nix +++ b/flake.nix @@ -29,7 +29,7 @@ packages.quartz = pkgs.buildNpmPackage { name = "quartz"; src = ./.; - npmDepsHash = "sha256-hawMRXs2VvIeZ7hP8NZDBU8yqg/f2cTzmGEvn+VzjE4="; + npmDepsHash = "sha256-qzFBy4KCGvAhO+4eDk0ZXUT4TFwBbHu7lY1pr0bOlHQ="; dontNpmBuild = true; }; }) diff --git a/package-lock.json b/package-lock.json index 30d740c..d84ab5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "mdast-util-to-hast": "^13.2.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", - "pixi.js": "^8.6.6", + "pixi.js": "^8.7.3", "preact": "^10.25.4", "preact-render-to-string": "^6.5.13", "pretty-bytes": "^6.1.1", @@ -79,10 +79,10 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^22.10.6", + "@types/node": "^22.12.0", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", - "@types/ws": "^8.5.13", + "@types/ws": "^8.5.14", "@types/yargs": "^17.0.33", "esbuild": "^0.24.2", "prettier": "^3.4.2", @@ -1914,10 +1914,11 @@ } }, "node_modules/@types/node": { - "version": "22.10.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.6.tgz", - "integrity": "sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==", + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz", + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } @@ -1943,10 +1944,11 @@ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" }, "node_modules/@types/ws": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", - "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -5583,9 +5585,10 @@ } }, "node_modules/pixi.js": { - "version": "8.6.6", - "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.6.6.tgz", - "integrity": "sha512-o5pw7G2yuIrnBx0G4npBlmFp+XGNcapI/Ufs62rRj/4XKxc1Zo74YJr/BtEXcXTraTKd+pQvYOLvnfxRjxBMvQ==", + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.7.3.tgz", + "integrity": "sha512-wfWlhJYnGx1s4f2yoouevQjaeacbJ12LTkJGa+n9AIYNIjOnmJylBtZ2mARX7iFk3mr2xv0wuo//XPe2hk5OBw==", + "license": "MIT", "dependencies": { "@pixi/colord": "^2.9.6", "@types/css-font-loading-module": "^0.0.12", diff --git a/package.json b/package.json index 726350b..192a8ab 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "mdast-util-to-hast": "^13.2.0", "mdast-util-to-string": "^4.0.0", "micromorph": "^0.4.5", - "pixi.js": "^8.6.6", + "pixi.js": "^8.7.3", "preact": "^10.25.4", "preact-render-to-string": "^6.5.13", "pretty-bytes": "^6.1.1", @@ -102,10 +102,10 @@ "@types/d3": "^7.4.3", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^22.10.6", + "@types/node": "^22.12.0", "@types/pretty-time": "^1.1.5", "@types/source-map-support": "^0.5.10", - "@types/ws": "^8.5.13", + "@types/ws": "^8.5.14", "@types/yargs": "^17.0.33", "esbuild": "^0.24.2", "prettier": "^3.4.2", diff --git a/quartz/components/Graph.tsx b/quartz/components/Graph.tsx index ec3475d..e8b462d 100644 --- a/quartz/components/Graph.tsx +++ b/quartz/components/Graph.tsx @@ -18,6 +18,7 @@ export interface D3Config { removeTags: string[] showTags: boolean focusOnHover?: boolean + enableRadial?: boolean } interface GraphOptions { @@ -39,6 +40,7 @@ const defaultOptions: GraphOptions = { showTags: true, removeTags: [], focusOnHover: false, + enableRadial: false, }, globalGraph: { drag: true, @@ -53,10 +55,11 @@ const defaultOptions: GraphOptions = { showTags: true, removeTags: [], focusOnHover: true, + enableRadial: true, }, } -export default ((opts?: GraphOptions) => { +export default ((opts?: Partial) => { const Graph: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => { const localGraph = { ...defaultOptions.localGraph, ...opts?.localGraph } const globalGraph = { ...defaultOptions.globalGraph, ...opts?.globalGraph } diff --git a/quartz/components/scripts/graph.inline.ts b/quartz/components/scripts/graph.inline.ts index dbddae9..16ee33f 100644 --- a/quartz/components/scripts/graph.inline.ts +++ b/quartz/components/scripts/graph.inline.ts @@ -8,6 +8,7 @@ import { forceCenter, forceLink, forceCollide, + forceRadial, zoomIdentity, select, drag, @@ -87,6 +88,7 @@ async function renderGraph(container: string, fullSlug: FullSlug) { removeTags, showTags, focusOnHover, + enableRadial, } = JSON.parse(graph.dataset["cfg"]!) as D3Config const data: Map = new Map( @@ -161,15 +163,20 @@ async function renderGraph(container: string, fullSlug: FullSlug) { })), } + const width = graph.offsetWidth + const height = Math.max(graph.offsetHeight, 250) + // we virtualize the simulation and use pixi to actually render it + // Calculate the radius of the container circle + const radius = Math.min(width, height) / 2 - 40 // 40px padding const simulation: Simulation = forceSimulation(graphData.nodes) .force("charge", forceManyBody().strength(-100 * repelForce)) .force("center", forceCenter().strength(centerForce)) .force("link", forceLink(graphData.links).distance(linkDistance)) .force("collide", forceCollide((n) => nodeRadius(n)).iterations(3)) - const width = graph.offsetWidth - const height = Math.max(graph.offsetHeight, 250) + if (enableRadial) + simulation.force("radial", forceRadial(radius * 0.8, width / 2, height / 2).strength(0.3)) // precompute style prop strings as pixi doesn't support css variables const cssVars = [ diff --git a/quartz/styles/base.scss b/quartz/styles/base.scss index 29ddc5a..4389491 100644 --- a/quartz/styles/base.scss +++ b/quartz/styles/base.scss @@ -1,3 +1,5 @@ +@use "sass:map"; + @use "./variables.scss" as *; @use "./syntax.scss"; @use "./callouts.scss"; @@ -121,7 +123,7 @@ a { } .page { - max-width: calc(#{map-get($breakpoints, desktop)} + 300px); + max-width: calc(#{map.get($breakpoints, desktop)} + 300px); margin: 0 auto; & article { & > h1 { @@ -151,24 +153,25 @@ a { & > #quartz-body { display: grid; - grid-template-columns: #{map-get($desktopGrid, templateColumns)}; - grid-template-rows: #{map-get($desktopGrid, templateRows)}; - column-gap: #{map-get($desktopGrid, columnGap)}; - row-gap: #{map-get($desktopGrid, rowGap)}; - grid-template-areas: #{map-get($desktopGrid, templateAreas)}; + grid-template-columns: #{map.get($desktopGrid, templateColumns)}; + grid-template-rows: #{map.get($desktopGrid, templateRows)}; + column-gap: #{map.get($desktopGrid, columnGap)}; + row-gap: #{map.get($desktopGrid, rowGap)}; + grid-template-areas: #{map.get($desktopGrid, templateAreas)}; + @media all and ($tablet) { - grid-template-columns: #{map-get($tabletGrid, templateColumns)}; - grid-template-rows: #{map-get($tabletGrid, templateRows)}; - column-gap: #{map-get($tabletGrid, columnGap)}; - row-gap: #{map-get($tabletGrid, rowGap)}; - grid-template-areas: #{map-get($tabletGrid, templateAreas)}; + grid-template-columns: #{map.get($tabletGrid, templateColumns)}; + grid-template-rows: #{map.get($tabletGrid, templateRows)}; + column-gap: #{map.get($tabletGrid, columnGap)}; + row-gap: #{map.get($tabletGrid, rowGap)}; + grid-template-areas: #{map.get($tabletGrid, templateAreas)}; } @media all and ($mobile) { - grid-template-columns: #{map-get($mobileGrid, templateColumns)}; - grid-template-rows: #{map-get($mobileGrid, templateRows)}; - column-gap: #{map-get($mobileGrid, columnGap)}; - row-gap: #{map-get($mobileGrid, rowGap)}; - grid-template-areas: #{map-get($mobileGrid, templateAreas)}; + grid-template-columns: #{map.get($mobileGrid, templateColumns)}; + grid-template-rows: #{map.get($mobileGrid, templateRows)}; + column-gap: #{map.get($mobileGrid, columnGap)}; + row-gap: #{map.get($mobileGrid, rowGap)}; + grid-template-areas: #{map.get($mobileGrid, templateAreas)}; } @media all and not ($desktop) { diff --git a/quartz/styles/variables.scss b/quartz/styles/variables.scss index 4a5cea5..f61adfc 100644 --- a/quartz/styles/variables.scss +++ b/quartz/styles/variables.scss @@ -1,3 +1,5 @@ +@use "sass:map"; + /** * Layout breakpoints * $mobile: screen width below this value will use mobile styles @@ -10,11 +12,11 @@ $breakpoints: ( desktop: 1200px, ); -$mobile: "(max-width: #{map-get($breakpoints, mobile)})"; -$tablet: "(min-width: #{map-get($breakpoints, mobile)}) and (max-width: #{map-get($breakpoints, desktop)})"; -$desktop: "(min-width: #{map-get($breakpoints, desktop)})"; +$mobile: "(max-width: #{map.get($breakpoints, mobile)})"; +$tablet: "(min-width: #{map.get($breakpoints, mobile)}) and (max-width: #{map.get($breakpoints, desktop)})"; +$desktop: "(min-width: #{map.get($breakpoints, desktop)})"; -$pageWidth: #{map-get($breakpoints, mobile)}; +$pageWidth: #{map.get($breakpoints, mobile)}; $sidePanelWidth: 320px; //380px; $topSpacing: 6rem; $boldWeight: 700;