= {
graphData: scopedGraphData,
width: windowWidth,
height: windowHeight,
backgroundColor: theme.colors.gray[visuals.backgroundColor],
nodeLabel: (node) => (node as OrgRoamNode).title,
nodeColor: (node) => {
return getNodeColor(node as OrgRoamNode)
},
nodeRelSize: visuals.nodeRel,
nodeVal: (node) => {
const links = linksByNodeId[node.id!] ?? []
const parentNeighbors = links.length
? links.filter((link) => link.type === 'parent').length
: 0
const basicSize = 3 + links.length - (!filter.parents ? parentNeighbors : 0)
const highlightSize =
highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
? 1 + opacity * (visuals.highlightNodeSize - 1)
: 1
return basicSize * highlightSize
},
nodeCanvasObject: (node, ctx, globalScale) => {
if (!node) {
return
}
if (!visuals.labels) {
return
}
const wasHighlightedNode = previouslyHighlightedNodes[node.id!]
if (
(globalScale <= visuals.labelScale || visuals.labels === 1) &&
!highlightedNodes[node.id!] &&
!wasHighlightedNode
) {
return
}
const nodeTitle = (node as OrgRoamNode).title!
const label = nodeTitle.substring(0, Math.min(nodeTitle.length, 40))
// const label = 'label'
const fontSize = 12 / globalScale
const textWidth = ctx.measureText(label).width
const bckgDimensions = [textWidth * 1.1, fontSize].map((n) => n + fontSize * 0.5) as [
number,
number,
] // some padding
const fadeFactor = Math.min((3 * (globalScale - visuals.labelScale)) / visuals.labelScale, 1)
// draw label background
const getLabelOpacity = () => {
if (visuals.labels === 1) {
return opacity
}
if (globalScale <= visuals.labelScale) {
return opacity
}
return highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]
? Math.max(fadeFactor, opacity)
: 1 * fadeFactor * (-1 * (0.5 * opacity - 1))
}
if (visuals.labelBackgroundColor && visuals.labelBackgroundOpacity) {
const backgroundOpacity = getLabelOpacity() * visuals.labelBackgroundOpacity
const labelBackground = hexToRGBA(labelBackgroundColor, backgroundOpacity)
ctx.fillStyle = labelBackground
ctx.fillRect(
node.x! - bckgDimensions[0] / 2,
node.y! - bckgDimensions[1] / 2,
...bckgDimensions,
)
}
// draw label text
const textOpacity = getLabelOpacity()
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
const labelText = hexToRGBA(labelTextColor, textOpacity)
ctx.fillStyle = labelText
ctx.font = `${fontSize}px Sans-Serif`
ctx.fillText(label, node.x!, node.y!)
},
nodeCanvasObjectMode: () => 'after',
linkDirectionalParticles: visuals.particles ? visuals.particlesNumber : undefined,
linkDirectionalArrowLength: visuals.arrows ? visuals.arrowsLength : undefined,
linkDirectionalArrowRelPos: visuals.arrowsPos,
linkDirectionalArrowColor: visuals.arrowsColor
? (link) => getThemeColor(visuals.arrowsColor)
: undefined,
linkColor: (link) => {
const sourceId = typeof link.source === 'object' ? link.source.id! : (link.source as string)
const targetId = typeof link.target === 'object' ? link.target.id! : (link.target as string)
const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode.current)
const linkWasHighlighted = isLinkRelatedToNode(link, lastHoverNode.current)
const needsHighlighting = linkIsHighlighted || linkWasHighlighted
const roamLink = link as OrgRoamLink
if (visuals.citeLinkColor && roamLink.type === 'cite') {
return needsHighlighting && (visuals.citeLinkHighlightColor || visuals.linkHighlight)
? highlightColors[visuals.citeLinkColor][
visuals.citeLinkHighlightColor || visuals.linkHighlight
](opacity)
: getThemeColor(visuals.citeLinkColor)
}
return getLinkColor(sourceId as string, targetId as string, needsHighlighting)
},
linkWidth: (link) => {
const linkIsHighlighted = isLinkRelatedToNode(link, centralHighlightedNode.current)
const linkWasHighlighted = isLinkRelatedToNode(link, lastHoverNode.current)
return linkIsHighlighted || linkWasHighlighted
? visuals.linkWidth * (1 + opacity * (visuals.highlightLinkSize - 1))
: visuals.linkWidth
},
linkDirectionalParticleWidth: visuals.particlesWidth,
d3AlphaDecay: physics.alphaDecay,
d3AlphaMin: physics.alphaMin,
d3VelocityDecay: physics.velocityDecay,
onNodeClick: (node: NodeObject, event: any) => {
const isDoubleClick = event.timeStamp - lastNodeClickRef.current < 400
lastNodeClickRef.current = event.timeStamp
if (isDoubleClick) {
return handleClick('double', node)
}
return handleClick('click', node)
},
onBackgroundClick: () => {
setHoverNode(null)
if (scope.nodeIds.length === 0) {
return
}
setScope((currentScope: Scope) => ({
...currentScope,
nodeIds: [],
}))
},
onNodeHover: (node) => {
if (!visuals.highlight) {
return
}
if (!hoverNode) {
fadeOutCancel()
setOpacity(0)
}
setHoverNode(node)
},
onNodeRightClick: (node) => {
handleClick('right', node)
},
}
return (
{threeDim ? (
{
if (!visuals.labels) {
return
}
if (visuals.labels < 3 && !highlightedNodes[node.id!]) {
return
}
const sprite = new SpriteText(node.title.substring(0, 40))
sprite.color = getThemeColor(visuals.labelTextColor)
sprite.backgroundColor = getThemeColor(visuals.labelBackgroundColor)
sprite.padding = 2
sprite.textHeight = 8
return sprite
}}
/>
) : (
{
const linkArg = link as OrgRoamLink
if (!visuals.citeDashes || linkArg.type !== 'cite') {
return null
}
return [visuals.citeDashLength, visuals.citeGapLength]
}}
/>
)}
)
})
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)
}