summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md22
-rw-r--r--components/Graph/drawLabels.ts17
-rw-r--r--components/Sidebar/Backlinks.tsx2
-rw-r--r--components/Sidebar/Link.tsx17
-rw-r--r--components/Sidebar/Note.tsx2
-rw-r--r--components/Sidebar/OrgImage.tsx4
-rw-r--r--components/Sidebar/Section.tsx16
-rw-r--r--components/Sidebar/Title.tsx1
-rw-r--r--components/Sidebar/Toolbar.tsx12
-rw-r--r--org-roam-ui.el24
-rw-r--r--pages/index.tsx278
-rw-r--r--util/findNthNeighbour.ts42
-rw-r--r--util/getLinkColor.ts82
-rw-r--r--util/getLinkNodeColor.ts23
-rw-r--r--util/getNodeColor.ts75
-rw-r--r--util/getNodeColorById.ts25
-rw-r--r--util/getThemeColor.ts3
-rw-r--r--util/hexToRGBA.ts11
-rw-r--r--util/isLinkRelatedToNode.ts7
-rw-r--r--util/nodeSize.ts34
-rw-r--r--util/normalizeLinkEnds.ts12
-rw-r--r--util/numberWithinRange.ts3
-rw-r--r--util/processOrg.tsx4
-rw-r--r--util/uniorg.tsx1
24 files changed, 467 insertions, 250 deletions
diff --git a/README.md b/README.md
index d64bac7..1bdd9a2 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
- [Supporting org-roam-ui](#supporting-org-roam-ui)
- [Feedback](#feedback)
- [Contribute 💪](#contribute-)
- - [Hacktoberfest](#hacktoberfest)
+ - [Hacktoberfest](#hacktoberfest)
- [Donate](#donate)
- [Sponsors](#sponsors)
@@ -110,13 +110,13 @@ NOTE: This is quite janky at the moment and will change in the future. Consider
#### Moving around
```emacs-lisp
-(orui-node-zoom)
+(org-roam-ui-node-zoom)
```
Zooms to the current node in the global view _ignoring local mode_.
```emacs-lisp
-(orui-node-local)
+(org-roam-ui-node-local)
```
Opens the current node in local view.
@@ -129,6 +129,21 @@ You can optionally give these command three parameters:
These options might not work at the moment, please configure them in the UI for the time being.
+#### Manipulating graph
+
+```emacs-lisp
+(org-roam-ui-add-to-local-graph &optional id)
+```
+
+Adds the node with the given id to the local graph. If no id is given, the current node is used.
+If the local graph is not open, it will be opened.
+
+```emacs-lisp
+(org-roam-ui-remove-from-local-graph &optional id)
+```
+
+Removes the node with the given id from the local graph. If no id is given, the current node is used.
+
### Configuration
Org-Roam-UI exposes a few variables, but most of the customization is done in the web app.
@@ -369,7 +384,6 @@ If you are interested in being more closely involved with the project, go [here]
We would ❤️ to have you on board!
-
## Donate
If you really really like org-roam-ui, you can make a [one-time donation or sponsor one of us monthly!](https://github.com/sponsors/ThomasFKJorna/)
diff --git a/components/Graph/drawLabels.ts b/components/Graph/drawLabels.ts
index fa19270..46790e6 100644
--- a/components/Graph/drawLabels.ts
+++ b/components/Graph/drawLabels.ts
@@ -1,8 +1,10 @@
import { OrgRoamNode } from '../../api'
import { NodeObject } from 'force-graph'
import { initialVisuals } from '../config'
-import { hexToRGBA, LinksByNodeId } from '../../pages'
+import { LinksByNodeId } from '../../pages'
import wrap from 'word-wrap'
+import { nodeSize } from '../../util/nodeSize'
+import { hexToRGBA } from '../../util/hexToRGBA'
export interface drawLabelsProps {
labelBackgroundColor: string
@@ -14,7 +16,6 @@ export interface drawLabelsProps {
previouslyHighlightedNodes: { [id: string]: {} }
visuals: typeof initialVisuals
opacity: number
- nodeSize: (node: NodeObject) => number
filteredLinksByNodeId: LinksByNodeId
nodeRel: number
hoverNode: NodeObject | null
@@ -44,7 +45,6 @@ export function drawLabels(props: drawLabelsProps) {
previouslyHighlightedNodes,
visuals,
opacity,
- nodeSize,
filteredLinksByNodeId,
nodeRel,
hoverNode,
@@ -78,7 +78,16 @@ export function drawLabels(props: drawLabelsProps) {
const label = nodeTitle.substring(0, visuals.labelLength)
const nodeS = Math.cbrt(
- (visuals.nodeRel * nodeSize(node)) / Math.pow(globalScale, visuals.nodeZoomSize),
+ (visuals.nodeRel *
+ nodeSize({
+ node,
+ highlightedNodes,
+ linksByNodeId: filteredLinksByNodeId,
+ opacity,
+ previouslyHighlightedNodes,
+ visuals,
+ })) /
+ Math.pow(globalScale, visuals.nodeZoomSize),
)
const fontSize = visuals.labelFontSize / Math.cbrt(Math.pow(globalScale, visuals.nodeZoomSize))
// ? Math.max((visuals.labelFontSize * nodeS) / 2, (visuals.labelFontSize * nodeS) / 3)
diff --git a/components/Sidebar/Backlinks.tsx b/components/Sidebar/Backlinks.tsx
index 0bbf415..5a8f6fc 100644
--- a/components/Sidebar/Backlinks.tsx
+++ b/components/Sidebar/Backlinks.tsx
@@ -2,7 +2,6 @@ import { LinksByNodeId, NodeByCite, NodeById } from '../../pages/index'
import { GraphData, NodeObject, LinkObject } from 'force-graph'
-import { normalizeLinkEnds } from '../../pages/index'
import { VStack, Box, Button, Heading, StackDivider } from '@chakra-ui/react'
import React from 'react'
import { ProcessedOrg } from '../../util/processOrg'
@@ -24,6 +23,7 @@ export interface BacklinksProps {
import { PreviewLink } from './Link'
import { OrgRoamNode } from '../../api'
import { Section } from './Section'
+import { normalizeLinkEnds } from '../../util/normalizeLinkEnds'
export const Backlinks = (props: BacklinksProps) => {
const {
diff --git a/components/Sidebar/Link.tsx b/components/Sidebar/Link.tsx
index 2f1ad64..73830ce 100644
--- a/components/Sidebar/Link.tsx
+++ b/components/Sidebar/Link.tsx
@@ -14,17 +14,17 @@ import {
Text,
useTheme,
} from '@chakra-ui/react'
-import React, { ReactElement, useContext, useEffect, useMemo, useState } from 'react'
+import React, { useContext, useEffect, useMemo, useState } from 'react'
import { ProcessedOrg } from '../../util/processOrg'
-import unified from 'unified'
+// import unified from 'unified'
//import createStream from 'unified-stream'
-import uniorgParse from 'uniorg-parse'
-import uniorg2rehype from 'uniorg-rehype'
+// import uniorgParse from 'uniorg-parse'
+// import uniorg2rehype from 'uniorg-rehype'
//import highlight from 'rehype-highlight'
-import katex from 'rehype-katex'
+// import katex from 'rehype-katex'
import 'katex/dist/katex.css'
-import rehype2react from 'rehype-react'
+// import rehype2react from 'rehype-react'
import { ThemeContext } from '../../util/themecontext'
import { LinksByNodeId, NodeByCite, NodeById } from '../../pages'
@@ -63,13 +63,10 @@ export interface NormalLinkProps {
children: string
}
-import { hexToRGBA, getThemeColor } from '../../pages/index'
import { defaultNoteStyle, viewerNoteStyle, outlineNoteStyle } from './noteStyle'
-import { OrgImage } from './OrgImage'
import { Scrollbars } from 'react-custom-scrollbars-2'
import { ExternalLinkIcon } from '@chakra-ui/icons'
-import { Section } from './Section'
-import { OrgRoamLink } from '../../api'
+import { getThemeColor } from '../../util/getThemeColor'
export const NodeLink = (props: NodeLinkProps) => {
const {
diff --git a/components/Sidebar/Note.tsx b/components/Sidebar/Note.tsx
index 638b43a..3d55816 100644
--- a/components/Sidebar/Note.tsx
+++ b/components/Sidebar/Note.tsx
@@ -1,7 +1,7 @@
import React from 'react'
import { NodeObject } from 'force-graph'
-import { NodeById, NodeByCite, LinksByNodeId, normalizeLinkEnds } from '../../pages'
+import { NodeById, NodeByCite, LinksByNodeId } from '../../pages'
import { Box, Flex } from '@chakra-ui/react'
import { UniOrg } from '../../util/uniorg'
import { Backlinks } from '../../components/Sidebar/Backlinks'
diff --git a/components/Sidebar/OrgImage.tsx b/components/Sidebar/OrgImage.tsx
index 0922834..f371f49 100644
--- a/components/Sidebar/OrgImage.tsx
+++ b/components/Sidebar/OrgImage.tsx
@@ -1,5 +1,5 @@
/* eslint-disable @next/next/no-img-element */
-import React, { useEffect, useState } from 'react'
+import React, { useState } from 'react'
import Image from 'next/image'
import path from 'path'
import { Container } from '@chakra-ui/react'
@@ -13,7 +13,7 @@ export interface OrgImageProps {
export const OrgImage = (props: OrgImageProps) => {
const { src, file } = props
- const [image, setImage] = useState<any>(null)
+ // const [image, setImage] = useState<any>(null)
/* )
* .then((res) => res.blob())
diff --git a/components/Sidebar/Section.tsx b/components/Sidebar/Section.tsx
index adaf00e..7e99f4f 100644
--- a/components/Sidebar/Section.tsx
+++ b/components/Sidebar/Section.tsx
@@ -1,17 +1,7 @@
-import { Box, Collapse, Flex, IconButton } from '@chakra-ui/react'
-import React, {
- JSXElementConstructor,
- ReactChild,
- ReactElement,
- ReactNode,
- useContext,
- useEffect,
- useState,
-} from 'react'
-import { BiCaretDownCircle, BiChevronDownCircle, BiCircle } from 'react-icons/bi'
-import { ComponentLike, ComponentPropsWithoutNode } from 'rehype-react'
+import { Box, Flex, IconButton } from '@chakra-ui/react'
+import React, { ReactChild, useContext, useEffect, useState } from 'react'
import { VscCircleFilled, VscCircleOutline } from 'react-icons/vsc'
-import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon } from '@chakra-ui/icons'
+import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'
import { NoteContext } from '../../util/NoteContext'
export interface SectionProps {
diff --git a/components/Sidebar/Title.tsx b/components/Sidebar/Title.tsx
index 2c8d8b7..791cbb8 100644
--- a/components/Sidebar/Title.tsx
+++ b/components/Sidebar/Title.tsx
@@ -1,6 +1,5 @@
import { Flex, Heading } from '@chakra-ui/react'
import React from 'react'
-import { BiFile } from 'react-icons/bi'
import { OrgRoamNode } from '../../api'
export interface TitleProps {
diff --git a/components/Sidebar/Toolbar.tsx b/components/Sidebar/Toolbar.tsx
index 71f3807..f606837 100644
--- a/components/Sidebar/Toolbar.tsx
+++ b/components/Sidebar/Toolbar.tsx
@@ -1,17 +1,9 @@
import React from 'react'
-import { Text, Flex, IconButton, ButtonGroup, Tooltip } from '@chakra-ui/react'
-import {
- BiAlignJustify,
- BiAlignLeft,
- BiAlignMiddle,
- BiAlignRight,
- BiFont,
- BiRightIndent,
-} from 'react-icons/bi'
+import { Flex, IconButton, ButtonGroup, Tooltip } from '@chakra-ui/react'
+import { BiAlignJustify, BiAlignLeft, BiAlignMiddle, BiAlignRight } from 'react-icons/bi'
import { MdOutlineExpand, MdOutlineCompress } from 'react-icons/md'
import { ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons'
import { IoIosListBox, IoMdListBox } from 'react-icons/io'
-import { NodeObject } from 'force-graph'
export interface ToolbarProps {
setJustification: any
diff --git a/org-roam-ui.el b/org-roam-ui.el
index ce2eeec..e13349b 100644
--- a/org-roam-ui.el
+++ b/org-roam-ui.el
@@ -690,6 +690,30 @@ Optionally with ID (string), SPEED (number, ms) and PADDING (number, px)."
(padding . ,padding))))))
(message "No node found.")))
+
+(defun org-roam-ui-change-local-graph (&optional id manipulation)
+ "Add or remove current node to the local graph. If not in local mode, open local-graph for this node."
+ (interactive)
+ (if-let ((node (or id (org-roam-id-at-point))))
+ (websocket-send-text org-roam-ui-ws-socket
+ (json-encode `((type . "command")
+ (data . ((commandName . "change-local-graph")
+ (id . ,node)
+ (manipulation . ,(or manipulation "add")))))))
+ (message "No node found.")))
+
+;;;###autoload
+(defun org-roam-ui-add-to-local-graph (&optional id)
+ "Add current node to the local graph. If not in local mode, open local-graph for this node."
+ (interactive)
+ (org-roam-ui-change-local-graph id "add"))
+
+;;;###autoload
+(defun org-roam-ui-remove-from-local-graph (&optional id)
+ "Remove current node from the local graph. If not in local mode, open local-graph for this node."
+ (interactive)
+ (org-roam-ui-change-local-graph id "remove"))
+
;;;###autoload
(defun org-roam-ui-sync-theme ()
"Sync your current Emacs theme with org-roam-ui."
diff --git a/pages/index.tsx b/pages/index.tsx
index fdbabc3..8770283 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -17,7 +17,6 @@ import { GraphData, LinkObject, NodeObject } from 'force-graph'
import Head from 'next/head'
import React, {
ComponentPropsWithoutRef,
- forwardRef,
useContext,
useEffect,
useMemo,
@@ -30,12 +29,11 @@ import type {
ForceGraph2D as TForceGraph2D,
ForceGraph3D as TForceGraph3D,
} from 'react-force-graph'
-import { BiChart, BiNetworkChart } from 'react-icons/bi'
+import { BiNetworkChart } from 'react-icons/bi'
import { BsReverseLayoutSidebarInsetReverse } from 'react-icons/bs'
import ReconnectingWebSocket from 'reconnecting-websocket'
import SpriteText from 'three-spritetext'
import useUndo from 'use-undo'
-import wrap from 'word-wrap'
import { OrgRoamGraphReponse, OrgRoamLink, OrgRoamNode } from '../api'
import {
algos,
@@ -57,6 +55,13 @@ import { ThemeContext, ThemeContextProps } from '../util/themecontext'
import { openNodeInEmacs } from '../util/webSocketFunctions'
import { drawLabels } from '../components/Graph/drawLabels'
import { VariablesContext } from '../util/variablesContext'
+import { findNthNeighbors } from '../util/findNthNeighbour'
+import { getThemeColor } from '../util/getThemeColor'
+import { normalizeLinkEnds } from '../util/normalizeLinkEnds'
+import { nodeSize } from '../util/nodeSize'
+import { getNodeColor } from '../util/getNodeColor'
+import { isLinkRelatedToNode } from '../util/isLinkRelatedToNode'
+import { getLinkColor } from '../util/getLinkColor'
const d3promise = import('d3-force-3d')
@@ -79,7 +84,7 @@ export interface EmacsVariables {
dailyDir?: string
katexMacros?: { [key: string]: string }
attachDir?: string
- useInheritance?: boolean,
+ useInheritance?: boolean
subDirs: string[]
}
export type Tags = string[]
@@ -461,6 +466,13 @@ export function GraphPage() {
setEmacsNodeId(message.data.id)
break
}
+ case 'change-local-graph': {
+ const node = nodeByIdRef.current[message.data.id as string]
+ if (!node) break
+ console.log(message)
+ handleLocal(node, message.data.manipulation)
+ break
+ }
default:
return console.error('unknown message type', message.type)
}
@@ -518,7 +530,7 @@ export function GraphPage() {
const handleLocal = (node: OrgRoamNode, command: string) => {
if (command === 'remove') {
setScope((currentScope: Scope) => ({
- ...currentScope,
+ nodeIds: currentScope.nodeIds.filter((id: string) => id !== node.id),
excludedNodeIds: [...currentScope.excludedNodeIds, node.id as string],
}))
return
@@ -531,24 +543,23 @@ export function GraphPage() {
return
}
setScope((currentScope: Scope) => ({
- ...currentScope,
+ excludedNodeIds: currentScope.excludedNodeIds.filter((id: string) => id !== node.id),
nodeIds: [...currentScope.nodeIds, node.id as string],
}))
return
}
- const [mainItem, setMainItem] = useState({
- type: 'Graph',
- title: 'Graph',
- icon: <BiNetworkChart />,
- })
+ // const [mainItem, setMainItem] = useState({
+ // type: 'Graph',
+ // title: 'Graph',
+ // icon: <BiNetworkChart />,
+ // })
const [mainWindowWidth, setMainWindowWidth] = usePersistantState<number>(
'mainWindowWidth',
windowWidth,
)
- console.log(emacsVariables)
return (
<VariablesContext.Provider value={{ ...emacsVariables }}>
<Box
@@ -808,36 +819,6 @@ export const Graph = function (props: GraphProps) {
}
}
- const findNthNeighbors = (ids: string[], excludedIds: string[], n: number) => {
- let queue = [ids[0]]
- let todo: string[] = []
- const completed = [ids[0]]
- Array.from({ length: n }, () => {
- queue.forEach((node) => {
- const links = filteredLinksByNodeIdRef.current[node as string] ?? []
- links.forEach((link) => {
- const [sourceId, targetId] = normalizeLinkEnds(link)
- if (excludedIds.some((id) => [sourceId, targetId].includes(id))) {
- return
- }
- if (!completed.includes(sourceId)) {
- todo.push(sourceId)
- return
- }
- if (!completed.includes(targetId)) {
- todo.push(targetId)
- return
- }
- return
- })
- })
- queue = todo
- todo.forEach((neighbor) => neighbor && completed.push(neighbor))
- todo = []
- })
- return completed
- }
-
const centralHighlightedNode = useRef<NodeObject | null>(null)
useEffect(() => {
@@ -983,7 +964,12 @@ export const Graph = function (props: GraphProps) {
? scopedGraphData.nodes.filter((n) => !scope.excludedNodeIds.includes(n.id as string))
: []
const oldScopedNodeIds = oldScopedNodes.map((node) => node.id as string)
- const neighbs = findNthNeighbors(scope.nodeIds, scope.excludedNodeIds, local.neighbors)
+ const neighbs = findNthNeighbors({
+ ids: scope.nodeIds,
+ excludedIds: scope.excludedNodeIds,
+ n: local.neighbors,
+ linksByNodeId: filteredLinksByNodeIdRef.current,
+ })
const newScopedNodes = filteredGraphData.nodes
.filter((node) => {
if (oldScopedNodes.length) {
@@ -993,7 +979,11 @@ export const Graph = function (props: GraphProps) {
const links = filteredLinksByNodeIdRef.current[node.id as string] ?? []
return links.some((link) => {
const [source, target] = normalizeLinkEnds(link)
- return scope.nodeIds.includes(source) || scope.nodeIds.includes(target)
+ return (
+ !scope.excludedNodeIds.includes(source) &&
+ !scope.excludedNodeIds.includes(target) &&
+ (scope.nodeIds.includes(source) || scope.nodeIds.includes(target))
+ )
})
}
return neighbs.includes(node.id as string)
@@ -1037,8 +1027,10 @@ export const Graph = function (props: GraphProps) {
}, [
local.neighbors,
filter,
- JSON.stringify(scope),
- JSON.stringify(graphData),
+ scope,
+ scope.excludedNodeIds,
+ scope.nodeIds,
+ graphData,
filteredGraphData.links,
filteredGraphData.nodes,
])
@@ -1078,6 +1070,7 @@ export const Graph = function (props: GraphProps) {
// shitty handler to check for doubleClicks
const lastNodeClickRef = useRef(0)
+ // this is for animations, it's a bit hacky and can definitely be optimized
const [opacity, setOpacity] = useState(1)
const [fadeIn, cancel] = useAnimation((x) => setOpacity(x), {
duration: visuals.animationSpeed,
@@ -1106,10 +1099,7 @@ export const Graph = function (props: GraphProps) {
...links.flatMap((link) => [link.source, link.target]),
].map((nodeId) => [nodeId, {}]),
)
- }, [
- JSON.stringify(centralHighlightedNode.current),
- JSON.stringify(filteredLinksByNodeIdRef.current),
- ])
+ }, [centralHighlightedNode.current, filteredLinksByNodeIdRef.current])
useEffect(() => {
if (sidebarHighlightedNode?.id) {
@@ -1120,6 +1110,7 @@ export const Graph = function (props: GraphProps) {
}, [sidebarHighlightedNode])
const lastHoverNode = useRef<OrgRoamNode | null>(null)
+
useEffect(() => {
centralHighlightedNode.current = hoverNode
if (hoverNode) {
@@ -1162,102 +1153,6 @@ export const Graph = function (props: GraphProps) {
)
}, [JSON.stringify(hoverNode), lastHoverNode.current, filteredLinksByNodeIdRef.current])
- const getNodeColorById = (id: string) => {
- const linklen = filteredLinksByNodeIdRef.current[id!]?.length ?? 0
- if (coloring.method === 'degree') {
- return visuals.nodeColorScheme[
- numberWithinRange(linklen, 0, visuals.nodeColorScheme.length - 1)
- ]
- }
- return visuals.nodeColorScheme[
- linklen && clusterRef.current[id] % visuals.nodeColorScheme.length
- ]
- }
-
- const getLinkNodeColor = (sourceId: string, targetId: string) => {
- return filteredLinksByNodeIdRef.current[sourceId]!.length >
- filteredLinksByNodeIdRef.current[targetId]!.length
- ? getNodeColorById(sourceId)
- : getNodeColorById(targetId)
- }
-
- const getLinkColor = (
- sourceId: string,
- targetId: string,
- needsHighlighting: boolean,
- theme: any,
- ) => {
- if (!visuals.linkHighlight && !visuals.linkColorScheme && !needsHighlighting) {
- const nodeColor = getLinkNodeColor(sourceId, targetId)
- return getThemeColor(nodeColor, theme)
- }
-
- if (!needsHighlighting && !visuals.linkColorScheme) {
- const nodeColor = getLinkNodeColor(sourceId, targetId)
- return highlightColors[nodeColor][visuals.backgroundColor](visuals.highlightFade * opacity)
- }
-
- if (!needsHighlighting) {
- return highlightColors[visuals.linkColorScheme][visuals.backgroundColor](
- visuals.highlightFade * opacity,
- )
- }
-
- if (!visuals.linkHighlight && !visuals.linkColorScheme) {
- const nodeColor = getLinkNodeColor(sourceId, targetId)
- return getThemeColor(nodeColor, theme)
- }
-
- if (!visuals.linkHighlight) {
- return getThemeColor(visuals.linkColorScheme, theme)
- }
-
- if (!visuals.linkColorScheme) {
- return highlightColors[getLinkNodeColor(sourceId, targetId)][visuals.linkHighlight](opacity)
- }
-
- return highlightColors[visuals.linkColorScheme][visuals.linkHighlight](opacity)
- }
-
- const getNodeColor = (node: OrgRoamNode, theme: any) => {
- const needsHighlighting = highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
- //const needsHighlighting = hoverNode?.id === node.id! || lastHoverNode?.current?.id === node.id
- // if we are matching the node color and don't have a highlight color
- // or we don't have our own scheme and we're not being highlighted
- if (visuals.emacsNodeColor && node.id === emacsNodeId) {
- return getThemeColor(visuals.emacsNodeColor, theme)
- }
- if (tagColors && node?.tags.some((tag) => tagColors[tag])) {
- const tagColor = tagColors[node?.tags.filter((tag) => tagColors[tag])[0]]
- return needsHighlighting
- ? highlightColors[tagColor][tagColor](visuals.highlightFade * opacity)
- : highlightColors[tagColor][visuals.backgroundColor](visuals.highlightFade * opacity)
- }
- if (visuals.citeNodeColor && node?.properties?.ROAM_REFS && node?.properties?.FILELESS) {
- return needsHighlighting
- ? getThemeColor(visuals.citeNodeColor, theme)
- : highlightColors[visuals.citeNodeColor][visuals.backgroundColor](
- visuals.highlightFade * opacity,
- )
- }
- if (visuals.refNodeColor && node.properties.ROAM_REFS) {
- return needsHighlighting
- ? getThemeColor(visuals.refNodeColor, theme)
- : highlightColors[visuals.refNodeColor][visuals.backgroundColor](
- visuals.highlightFade * opacity,
- )
- }
- if (!needsHighlighting) {
- return highlightColors[getNodeColorById(node.id as string)][visuals.backgroundColor](
- visuals.highlightFade * opacity,
- )
- }
- if (!visuals.nodeHighlight) {
- return getThemeColor(getNodeColorById(node.id as string), theme)
- }
- return highlightColors[getNodeColorById(node.id as string)][visuals.nodeHighlight](opacity)
- }
-
const labelTextColor = useMemo(
() => getThemeColor(visuals.labelTextColor, theme),
[visuals.labelTextColor, emacsTheme],
@@ -1268,21 +1163,6 @@ export const Graph = function (props: GraphProps) {
[visuals.labelBackgroundColor, emacsTheme],
)
- const nodeSize = (node: NodeObject) => {
- const links = filteredLinksByNodeIdRef.current[node.id!] ?? []
- const parentNeighbors = links.length ? links.filter((link) => link.type === 'parent').length : 0
- const basicSize =
- 3 + links.length * visuals.nodeSizeLinks - (!filter.parent ? parentNeighbors : 0)
- if (visuals.highlightNodeSize === 1) {
- return basicSize
- }
- const highlightSize =
- highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
- ? 1 + opacity * (visuals.highlightNodeSize - 1)
- : 1
- return basicSize * highlightSize
- }
-
const [dragging, setDragging] = useState(false)
const scaleRef = useRef(1)
@@ -1294,11 +1174,33 @@ export const Graph = function (props: GraphProps) {
warmupTicks: scope.nodeIds.length === 1 ? 100 : scope.nodeIds.length > 1 ? 20 : 0,
onZoom: ({ k, x, y }) => (scaleRef.current = k),
nodeColor: (node) => {
- return getNodeColor(node as OrgRoamNode, theme)
+ return getNodeColor({
+ node: node as OrgRoamNode,
+ theme,
+ visuals,
+ cluster: clusterRef.current,
+ coloring,
+ emacsNodeId,
+ highlightColors,
+ highlightedNodes,
+ previouslyHighlightedNodes,
+ linksByNodeId: filteredLinksByNodeIdRef.current,
+ opacity,
+ tagColors,
+ })
},
nodeRelSize: visuals.nodeRel,
nodeVal: (node) => {
- return nodeSize(node) / Math.pow(scaleRef.current, visuals.nodeZoomSize)
+ return (
+ nodeSize({
+ node,
+ highlightedNodes,
+ linksByNodeId: filteredLinksByNodeIdRef.current,
+ opacity,
+ previouslyHighlightedNodes,
+ visuals,
+ }) / Math.pow(scaleRef.current, visuals.nodeZoomSize)
+ )
},
nodeCanvasObject: (node, ctx, globalScale) => {
drawLabels({
@@ -1313,7 +1215,6 @@ export const Graph = function (props: GraphProps) {
previouslyHighlightedNodes,
visuals,
opacity,
- nodeSize,
labelTextColor,
labelBackgroundColor,
hoverNode,
@@ -1355,7 +1256,18 @@ export const Graph = function (props: GraphProps) {
)
}
- return getLinkColor(sourceId as string, targetId as string, needsHighlighting, theme)
+ return getLinkColor({
+ sourceId: sourceId as string,
+ targetId: targetId as string,
+ needsHighlighting,
+ theme,
+ cluster: clusterRef.current,
+ coloring,
+ highlightColors,
+ linksByNodeId: filteredLinksByNodeIdRef.current,
+ opacity,
+ visuals,
+ })
},
linkWidth: (link) => {
if (visuals.highlightLinkSize === 1) {
@@ -1480,39 +1392,3 @@ export const Graph = function (props: GraphProps) {
</Box>
)
}
-
-function isLinkRelatedToNode(link: LinkObject, node: NodeObject | null) {
- return (
- (link.source as NodeObject)?.id! === node?.id! || (link.target as NodeObject)?.id! === node?.id!
- )
-}
-
-function numberWithinRange(num: number, min: number, max: number) {
- return Math.min(Math.max(num, min), max)
-}
-
-export function normalizeLinkEnds(link: OrgRoamLink | LinkObject): [string, string] {
- // we need to cover both because force-graph modifies the original data
- // but if we supply the original data on each render, the graph will re-render sporadically
- const sourceId =
- typeof link.source === 'object' ? (link.source.id! as string) : (link.source as string)
- const targetId =
- typeof link.target === 'object' ? (link.target.id! as string) : (link.target as string)
- return [sourceId, targetId]
-}
-
-export function getThemeColor(name: string, theme: any) {
- return name.split('.').reduce((o, i) => o[i], theme.colors)
-}
-
-export function hexToRGBA(hex: string, opacity: number) {
- return (
- 'rgba(' +
- (hex = hex.replace('#', ''))
- .match(new RegExp('(.{' + hex.length / 3 + '})', 'g'))!
- .map((l) => parseInt(hex.length % 2 ? l + l : l, 16))
- .concat(isFinite(opacity) ? opacity : 1)
- .join(',') +
- ')'
- )
-}
diff --git a/util/findNthNeighbour.ts b/util/findNthNeighbour.ts
new file mode 100644
index 0000000..d48adbc
--- /dev/null
+++ b/util/findNthNeighbour.ts
@@ -0,0 +1,42 @@
+import { LinksByNodeId } from '../pages'
+import { normalizeLinkEnds } from './normalizeLinkEnds'
+
+export const findNthNeighbors = ({
+ ids,
+ excludedIds,
+ n,
+ linksByNodeId,
+}: {
+ ids: string[]
+ excludedIds: string[]
+ n: number
+ linksByNodeId: LinksByNodeId
+}) => {
+ let queue = [ids[0]]
+ let todo: string[] = []
+ const completed = [ids[0]]
+ Array.from({ length: n }, () => {
+ queue.forEach((node) => {
+ const links = linksByNodeId[node as string] ?? []
+ links.forEach((link) => {
+ const [sourceId, targetId] = normalizeLinkEnds(link)
+ if (excludedIds.some((id) => [sourceId, targetId].includes(id))) {
+ return
+ }
+ if (!completed.includes(sourceId)) {
+ todo.push(sourceId)
+ return
+ }
+ if (!completed.includes(targetId)) {
+ todo.push(targetId)
+ return
+ }
+ return
+ })
+ })
+ queue = todo
+ todo.forEach((neighbor) => neighbor && completed.push(neighbor))
+ todo = []
+ })
+ return completed
+}
diff --git a/util/getLinkColor.ts b/util/getLinkColor.ts
new file mode 100644
index 0000000..33f7094
--- /dev/null
+++ b/util/getLinkColor.ts
@@ -0,0 +1,82 @@
+import { initialColoring, initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { getLinkNodeColor } from './getLinkNodeColor'
+import { getThemeColor } from './getThemeColor'
+
+export const getLinkColor = ({
+ sourceId,
+ targetId,
+ needsHighlighting,
+ theme,
+ visuals,
+ highlightColors,
+ opacity,
+ linksByNodeId,
+ coloring,
+ cluster,
+}: {
+ sourceId: string
+ targetId: string
+ needsHighlighting: boolean
+ theme: any
+ visuals: typeof initialVisuals
+ highlightColors: Record<string, any>
+ opacity: number
+ linksByNodeId: LinksByNodeId
+ coloring: typeof initialColoring
+ cluster: any
+}) => {
+ if (!visuals.linkHighlight && !visuals.linkColorScheme && !needsHighlighting) {
+ const nodeColor = getLinkNodeColor({
+ sourceId,
+ targetId,
+ linksByNodeId,
+ visuals,
+ coloring,
+ cluster,
+ })
+ return getThemeColor(nodeColor, theme)
+ }
+
+ if (!needsHighlighting && !visuals.linkColorScheme) {
+ const nodeColor = getLinkNodeColor({
+ sourceId,
+ targetId,
+ linksByNodeId,
+ visuals,
+ coloring,
+ cluster,
+ })
+ return highlightColors[nodeColor][visuals.backgroundColor](visuals.highlightFade * opacity)
+ }
+
+ if (!needsHighlighting) {
+ return highlightColors[visuals.linkColorScheme][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
+ }
+
+ if (!visuals.linkHighlight && !visuals.linkColorScheme) {
+ const nodeColor = getLinkNodeColor({
+ sourceId,
+ targetId,
+ linksByNodeId,
+ visuals,
+ coloring,
+ cluster,
+ })
+ return getThemeColor(nodeColor, theme)
+ }
+
+ if (!visuals.linkHighlight) {
+ return getThemeColor(visuals.linkColorScheme, theme)
+ }
+
+ if (!visuals.linkColorScheme) {
+ return highlightColors[
+ getLinkNodeColor({ sourceId, targetId, linksByNodeId, visuals, coloring, cluster })
+ ][visuals.linkHighlight](opacity)
+ }
+
+ return highlightColors[visuals.linkColorScheme][visuals.linkHighlight](opacity)
+}
diff --git a/util/getLinkNodeColor.ts b/util/getLinkNodeColor.ts
new file mode 100644
index 0000000..86cfa4b
--- /dev/null
+++ b/util/getLinkNodeColor.ts
@@ -0,0 +1,23 @@
+import { initialColoring, initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { getNodeColorById } from './getNodeColorById'
+
+export const getLinkNodeColor = ({
+ sourceId,
+ targetId,
+ linksByNodeId,
+ visuals,
+ coloring,
+ cluster,
+}: {
+ sourceId: string
+ targetId: string
+ linksByNodeId: LinksByNodeId
+ visuals: typeof initialVisuals
+ coloring: typeof initialColoring
+ cluster: any
+}) => {
+ return linksByNodeId[sourceId]!.length > linksByNodeId[targetId]!.length
+ ? getNodeColorById({ id: sourceId, linksByNodeId, visuals, cluster, coloring })
+ : getNodeColorById({ id: targetId, visuals, linksByNodeId, cluster, coloring })
+}
diff --git a/util/getNodeColor.ts b/util/getNodeColor.ts
new file mode 100644
index 0000000..f25a15b
--- /dev/null
+++ b/util/getNodeColor.ts
@@ -0,0 +1,75 @@
+import { OrgRoamNode } from '../api'
+import { initialColoring, initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { getNodeColorById } from './getNodeColorById'
+import { getThemeColor } from './getThemeColor'
+
+export const getNodeColor = ({
+ node,
+ theme,
+ highlightedNodes,
+ previouslyHighlightedNodes,
+ visuals,
+ tagColors,
+ highlightColors,
+ opacity,
+ emacsNodeId,
+ linksByNodeId,
+ cluster,
+ coloring,
+}: {
+ node: OrgRoamNode
+ theme: any
+ visuals: typeof initialVisuals
+ highlightedNodes: Record<string, any>
+ previouslyHighlightedNodes: Record<string, any>
+ tagColors: Record<string, any>
+ highlightColors: Record<string, any>
+ opacity: number
+ emacsNodeId: string | null
+ linksByNodeId: LinksByNodeId
+ cluster: any
+ coloring: typeof initialColoring
+}) => {
+ const needsHighlighting = highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
+ //const needsHighlighting = hoverNode?.id === node.id! || lastHoverNode?.current?.id === node.id
+ // if we are matching the node color and don't have a highlight color
+ // or we don't have our own scheme and we're not being highlighted
+ if (visuals.emacsNodeColor && node.id === emacsNodeId) {
+ return getThemeColor(visuals.emacsNodeColor, theme)
+ }
+ if (tagColors && node?.tags.some((tag) => tagColors[tag])) {
+ const tagColor = tagColors[node?.tags.filter((tag) => tagColors[tag])[0]]
+ return needsHighlighting
+ ? highlightColors[tagColor][tagColor](visuals.highlightFade * opacity)
+ : highlightColors[tagColor][visuals.backgroundColor](visuals.highlightFade * opacity)
+ }
+ if (visuals.citeNodeColor && node?.properties?.ROAM_REFS && node?.properties?.FILELESS) {
+ return needsHighlighting
+ ? getThemeColor(visuals.citeNodeColor, theme)
+ : highlightColors[visuals.citeNodeColor][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
+ }
+ if (visuals.refNodeColor && node.properties.ROAM_REFS) {
+ return needsHighlighting
+ ? getThemeColor(visuals.refNodeColor, theme)
+ : highlightColors[visuals.refNodeColor][visuals.backgroundColor](
+ visuals.highlightFade * opacity,
+ )
+ }
+ if (!needsHighlighting) {
+ return highlightColors[
+ getNodeColorById({ id: node.id as string, cluster, coloring, linksByNodeId, visuals })
+ ][visuals.backgroundColor](visuals.highlightFade * opacity)
+ }
+ if (!visuals.nodeHighlight) {
+ return getThemeColor(
+ getNodeColorById({ id: node.id as string, cluster, coloring, linksByNodeId, visuals }),
+ theme,
+ )
+ }
+ return highlightColors[
+ getNodeColorById({ id: node.id as string, cluster, coloring, linksByNodeId, visuals })
+ ][visuals.nodeHighlight](opacity)
+}
diff --git a/util/getNodeColorById.ts b/util/getNodeColorById.ts
new file mode 100644
index 0000000..d2c198b
--- /dev/null
+++ b/util/getNodeColorById.ts
@@ -0,0 +1,25 @@
+import { initialColoring, initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { numberWithinRange } from './numberWithinRange'
+
+export const getNodeColorById = ({
+ id,
+ linksByNodeId,
+ visuals,
+ coloring,
+ cluster,
+}: {
+ id: string
+ linksByNodeId: LinksByNodeId
+ visuals: typeof initialVisuals
+ cluster: any
+ coloring: typeof initialColoring
+}) => {
+ const linklen = linksByNodeId[id!]?.length ?? 0
+ if (coloring.method === 'degree') {
+ return visuals.nodeColorScheme[
+ numberWithinRange(linklen, 0, visuals.nodeColorScheme.length - 1)
+ ]
+ }
+ return visuals.nodeColorScheme[linklen && cluster[id] % visuals.nodeColorScheme.length]
+}
diff --git a/util/getThemeColor.ts b/util/getThemeColor.ts
new file mode 100644
index 0000000..1175a52
--- /dev/null
+++ b/util/getThemeColor.ts
@@ -0,0 +1,3 @@
+export const getThemeColor = (name: string, theme: any) => {
+ return name.split('.').reduce((o, i) => o[i], theme.colors)
+}
diff --git a/util/hexToRGBA.ts b/util/hexToRGBA.ts
new file mode 100644
index 0000000..bacb601
--- /dev/null
+++ b/util/hexToRGBA.ts
@@ -0,0 +1,11 @@
+export function hexToRGBA(hex: string, opacity: number) {
+ return (
+ 'rgba(' +
+ (hex = hex.replace('#', ''))
+ .match(new RegExp('(.{' + hex.length / 3 + '})', 'g'))!
+ .map((l) => parseInt(hex.length % 2 ? l + l : l, 16))
+ .concat(isFinite(opacity) ? opacity : 1)
+ .join(',') +
+ ')'
+ )
+}
diff --git a/util/isLinkRelatedToNode.ts b/util/isLinkRelatedToNode.ts
new file mode 100644
index 0000000..eeacab7
--- /dev/null
+++ b/util/isLinkRelatedToNode.ts
@@ -0,0 +1,7 @@
+import { NodeObject, LinkObject } from 'force-graph'
+
+export const isLinkRelatedToNode = (link: LinkObject, node: NodeObject | null) => {
+ return (
+ (link.source as NodeObject)?.id! === node?.id! || (link.target as NodeObject)?.id! === node?.id!
+ )
+}
diff --git a/util/nodeSize.ts b/util/nodeSize.ts
new file mode 100644
index 0000000..3601c1e
--- /dev/null
+++ b/util/nodeSize.ts
@@ -0,0 +1,34 @@
+import { filter } from '@chakra-ui/react'
+import { initialVisuals } from '../components/config'
+import { LinksByNodeId } from '../pages'
+import { NodeObject } from 'force-graph'
+
+export const nodeSize = ({
+ linksByNodeId,
+ visuals,
+ highlightedNodes,
+ previouslyHighlightedNodes,
+ opacity,
+ node,
+}: {
+ node: NodeObject
+ visuals: typeof initialVisuals
+
+ highlightedNodes: Record<string, any>
+ previouslyHighlightedNodes: Record<string, any>
+ opacity: number
+ linksByNodeId: LinksByNodeId
+}) => {
+ const links = linksByNodeId[node.id!] ?? []
+ const parentNeighbors = links.length ? links.filter((link) => link.type === 'parent').length : 0
+ const basicSize =
+ 3 + links.length * visuals.nodeSizeLinks - (!filter.parent ? parentNeighbors : 0)
+ if (visuals.highlightNodeSize === 1) {
+ return basicSize
+ }
+ const highlightSize =
+ highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
+ ? 1 + opacity * (visuals.highlightNodeSize - 1)
+ : 1
+ return basicSize * highlightSize
+}
diff --git a/util/normalizeLinkEnds.ts b/util/normalizeLinkEnds.ts
new file mode 100644
index 0000000..43eee9c
--- /dev/null
+++ b/util/normalizeLinkEnds.ts
@@ -0,0 +1,12 @@
+import { OrgRoamLink } from '../api'
+import { LinkObject } from 'force-graph'
+
+export function normalizeLinkEnds(link: OrgRoamLink | LinkObject): [string, string] {
+ // we need to cover both because force-graph modifies the original data
+ // but if we supply the original data on each render, the graph will re-render sporadically
+ const sourceId =
+ typeof link.source === 'object' ? (link.source.id! as string) : (link.source as string)
+ const targetId =
+ typeof link.target === 'object' ? (link.target.id! as string) : (link.target as string)
+ return [sourceId, targetId]
+}
diff --git a/util/numberWithinRange.ts b/util/numberWithinRange.ts
new file mode 100644
index 0000000..ae435ce
--- /dev/null
+++ b/util/numberWithinRange.ts
@@ -0,0 +1,3 @@
+export const numberWithinRange = (num: number, min: number, max: number) => {
+ return Math.min(Math.max(num, min), max)
+}
diff --git a/util/processOrg.tsx b/util/processOrg.tsx
index ea868ee..2abc256 100644
--- a/util/processOrg.tsx
+++ b/util/processOrg.tsx
@@ -26,7 +26,7 @@ import remarkSectionize from 'remark-sectionize'
import remarkRehype from 'remark-rehype'
import { PreviewLink } from '../components/Sidebar/Link'
-import { LinksByNodeId, NodeByCite, NodeById, normalizeLinkEnds } from '../pages'
+import { LinksByNodeId, NodeByCite, NodeById } from '../pages'
import React, { createContext, ReactNode, useMemo } from 'react'
import { OrgImage } from '../components/Sidebar/OrgImage'
import { Section } from '../components/Sidebar/Section'
@@ -36,6 +36,7 @@ import { OrgRoamLink, OrgRoamNode } from '../api'
// @ts-expect-error non-ESM unified means no types
import { toString } from 'hast-util-to-string'
import { Box, chakra } from '@chakra-ui/react'
+import { normalizeLinkEnds } from './normalizeLinkEnds'
export interface ProcessedOrgProps {
nodeById: NodeById
@@ -126,7 +127,6 @@ export const ProcessedOrg = (props: ProcessedOrgProps) => {
const isMarkdown = previewNode?.file?.slice(-3) === '.md'
const baseProcessor = isMarkdown ? mdProcessor : orgProcessor
- console.log(macros)
const processor = useMemo(
() =>
baseProcessor
diff --git a/util/uniorg.tsx b/util/uniorg.tsx
index 4172d7a..8e285e6 100644
--- a/util/uniorg.tsx
+++ b/util/uniorg.tsx
@@ -1,5 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react'
-import { OrgRoamLink, OrgRoamNode } from '../api'
import { LinksByNodeId, NodeByCite, NodeById } from '../pages/index'
import { ProcessedOrg } from './processOrg'