diff options
author | Thomas F. K. Jorna <[email protected]> | 2021-10-16 23:07:19 +0200 |
---|---|---|
committer | GitHub <[email protected]> | 2021-10-16 23:07:19 +0200 |
commit | ca2fa3dc8d405d0ec451f16b9fcc72cde6bbf470 (patch) | |
tree | a98c1af5379688b57985eebe051ffdb9422dbbb5 /components | |
parent | eb794cc2183595a4f42be393db6b52478053000c (diff) | |
parent | 677702087e7b1e001a489412f4ed26dfaa138df6 (diff) |
Merge pull request #117 from org-roam/feat/better-labels
Feat/better labels
Diffstat (limited to 'components')
-rw-r--r-- | components/Graph/drawLabels.ts | 130 | ||||
-rw-r--r-- | components/Tweaks/LabelsPanel.tsx | 217 | ||||
-rw-r--r-- | components/Tweaks/NodesNLinksPanel.tsx | 2 | ||||
-rw-r--r-- | components/Tweaks/PhysicsPanel.tsx | 56 | ||||
-rw-r--r-- | components/Tweaks/SliderWithInfo.tsx | 2 | ||||
-rw-r--r-- | components/Tweaks/TagColorPanel.tsx | 8 | ||||
-rw-r--r-- | components/Tweaks/TagPanel.tsx | 22 | ||||
-rw-r--r-- | components/Tweaks/VisualsPanel.tsx | 3 | ||||
-rw-r--r-- | components/config.ts | 16 |
9 files changed, 310 insertions, 146 deletions
diff --git a/components/Graph/drawLabels.ts b/components/Graph/drawLabels.ts new file mode 100644 index 0000000..fa19270 --- /dev/null +++ b/components/Graph/drawLabels.ts @@ -0,0 +1,130 @@ +import { OrgRoamNode } from '../../api' +import { NodeObject } from 'force-graph' +import { initialVisuals } from '../config' +import { hexToRGBA, LinksByNodeId } from '../../pages' +import wrap from 'word-wrap' + +export interface drawLabelsProps { + labelBackgroundColor: string + labelTextColor: string + node: NodeObject + ctx: any + globalScale: number + highlightedNodes: { [id: string]: {} } + previouslyHighlightedNodes: { [id: string]: {} } + visuals: typeof initialVisuals + opacity: number + nodeSize: (node: NodeObject) => number + filteredLinksByNodeId: LinksByNodeId + nodeRel: number + hoverNode: NodeObject | null + lastHoverNode: OrgRoamNode | null +} + +export const getLabelOpacity = ( + fadeFactor: number, + visuals: typeof initialVisuals, + globalScale: number, + opacity: number, + isHighlighty: boolean, +) => { + return isHighlighty + ? Math.max(fadeFactor, opacity) + : 1 * fadeFactor * (-1 * (visuals.highlightFade * opacity - 1)) +} + +export function drawLabels(props: drawLabelsProps) { + const { + labelBackgroundColor, + labelTextColor, + node, + ctx, + globalScale, + highlightedNodes, + previouslyHighlightedNodes, + visuals, + opacity, + nodeSize, + filteredLinksByNodeId, + nodeRel, + hoverNode, + lastHoverNode, + } = props + + if (!node) { + return + } + + if (!visuals.labels) { + return + } + const hoverId = hoverNode?.id ?? '' + const lastHoverId = lastHoverNode?.id ?? '' + const links = filteredLinksByNodeId[(node as OrgRoamNode).id] ?? [] + + const isHighlighty = !!(highlightedNodes[node.id!] || previouslyHighlightedNodes[node.id!]) + + const fadeFactor = Math.min( + 5 * (globalScale - visuals.labelScale) + + 2 * + Math.pow(Math.min(links.length, visuals.labelDynamicDegree), visuals.labelDynamicStrength), + 1, + ) + if (fadeFactor < 0.01 && !isHighlighty) { + return + } + const nodeTitle = (node as OrgRoamNode).title ?? '' + + const label = nodeTitle.substring(0, visuals.labelLength) + + const nodeS = Math.cbrt( + (visuals.nodeRel * nodeSize(node)) / 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) + // : (visuals.labelFontSize * nodeS) / 3 + + // * nodeS) / 3 + const textWidth = ctx.measureText(label).width + const bckgDimensions = [textWidth * 1.1, fontSize].map((n) => n + fontSize * 0.5) as [ + number, + number, + ] // some padding + + // draw label background + const textOpacity = getLabelOpacity(fadeFactor, visuals, globalScale, opacity, isHighlighty) + if (visuals.labelBackgroundColor && visuals.labelBackgroundOpacity) { + const backgroundOpacity = textOpacity * visuals.labelBackgroundOpacity + const labelBackground = hexToRGBA(labelBackgroundColor, backgroundOpacity) + ctx.fillStyle = labelBackground + ctx.fillRect( + node.x! - bckgDimensions[0] / 2, + node.y! - bckgDimensions[1] / 2 + nodeS, + ...bckgDimensions, + ) + } + + // draw label text + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + const labelText = hexToRGBA(labelTextColor, textOpacity) + ctx.fillStyle = labelText + ctx.font = `${fontSize}px Sans-Serif` + const wordsArray = wrap(label, { width: visuals.labelWordWrap }).split('\n') + + const truncatedWords = + nodeTitle.length > visuals.labelLength + ? [...wordsArray.slice(0, -1), `${wordsArray.slice(-1)}...`] + : wordsArray + + const highlightedNodeOffset = [hoverId, lastHoverId].includes((node as OrgRoamNode).id) + ? 1 + 0.3 * opacity + : 1 + truncatedWords.forEach((word, index) => { + ctx.fillText( + word, + node.x!, + node.y! + highlightedNodeOffset * nodeS * 8 + visuals.labelLineSpace * fontSize * index, + ) + }) +} diff --git a/components/Tweaks/LabelsPanel.tsx b/components/Tweaks/LabelsPanel.tsx index 013409e..c40b689 100644 --- a/components/Tweaks/LabelsPanel.tsx +++ b/components/Tweaks/LabelsPanel.tsx @@ -28,112 +28,135 @@ export const LabelsPanel = (props: LabelsPanelProps) => { <VStack spacing={2} justifyContent="flex-start" - divider={<StackDivider borderColor="gray.500" />} + divider={<StackDivider borderColor="gray.400" />} align="stretch" color="gray.800" > - <Box> - <Flex alignItems="center" justifyContent="space-between"> - <Text>Show labels</Text> - <Menu isLazy placement="right"> - <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}> - {!visuals.labels ? 'Never' : visuals.labels < 2 ? 'On Highlight' : 'Always'} - </MenuButton> - <Portal> - {' '} - <MenuList zIndex="popover" bgColor="gray.200"> - <MenuItem onClick={() => setVisuals({ ...visuals, labels: 0 })}>Never</MenuItem> - <MenuItem onClick={() => setVisuals({ ...visuals, labels: 1 })}> - On Highlight - </MenuItem> - <MenuItem onClick={() => setVisuals({ ...visuals, labels: 2 })}>Always</MenuItem> - <MenuItem onClick={() => setVisuals({ ...visuals, labels: 3 })}> - Always (even in 3D) - </MenuItem> - </MenuList> - </Portal> - </Menu> - </Flex> - <VStack - spacing={1} - justifyContent="flex-start" - divider={<StackDivider borderColor="gray.400" />} - align="stretch" - paddingLeft={2} - color="gray.800" - > - <SliderWithInfo - label="Label font size" - value={visuals.labelFontSize} - min={5} - max={20} - step={0.5} - onChange={(value) => setVisuals({ ...visuals, labelFontSize: value })} - /> - <SliderWithInfo - label="Max. label characters" - value={visuals.labelLength} - min={10} - max={100} - step={1} - onChange={(value) => setVisuals({ ...visuals, labelLength: value })} - /> + <Flex alignItems="center" justifyContent="space-between"> + <Text>Show labels</Text> + <Menu isLazy placement="right"> + <MenuButton as={Button} colorScheme="" color="black" rightIcon={<ChevronDownIcon />}> + {!visuals.labels ? 'Never' : visuals.labels < 2 ? 'On Highlight' : 'Always'} + </MenuButton> + <Portal> + {' '} + <MenuList zIndex="popover" bgColor="gray.200"> + <MenuItem onClick={() => setVisuals({ ...visuals, labels: 0 })}>Never</MenuItem> + <MenuItem onClick={() => setVisuals({ ...visuals, labels: 1 })}> + On Highlight + </MenuItem> + <MenuItem onClick={() => setVisuals({ ...visuals, labels: 2 })}>Always</MenuItem> + <MenuItem onClick={() => setVisuals({ ...visuals, labels: 3 })}> + Always (even in 3D) + </MenuItem> + </MenuList> + </Portal> + </Menu> + </Flex> + <Collapse in={visuals.labels > 1} animateOpacity> + <Box paddingTop={2}> <SliderWithInfo - label="Max. label line length" - value={visuals.labelWordWrap} - min={10} - max={100} - step={1} - onChange={(value) => setVisuals({ ...visuals, labelWordWrap: value })} + label="Label Appearance Scale" + value={visuals.labelScale * 2} + onChange={(value) => setVisuals({ ...visuals, labelScale: value / 2 })} /> + </Box> + <Box paddingTop={2}> <SliderWithInfo - label="Space between label lines" - value={visuals.labelLineSpace} - min={0.2} - max={3} - step={0.1} - onChange={(value) => setVisuals({ ...visuals, labelLineSpace: value })} + label="Label dynamicity" + infoText="By default, labels of nodes with more links will appear earlier than those with fewer. This slider changes the strength of this effect, put it at zero to disable it." + value={visuals.labelDynamicStrength} + min={0} + max={1} + step={0.05} + onChange={(value) => + setVisuals((curr: typeof initialVisuals) => ({ + ...curr, + labelDynamicStrength: value, + })) + } /> - <ColorMenu - colorList={colorList} - label="Text" - setVisuals={setVisuals} - value="labelTextColor" - visValue={visuals.labelTextColor} - /> - <ColorMenu - colorList={colorList} - label="Background" - setVisuals={setVisuals} - value="labelBackgroundColor" - visValue={visuals.labelBackgroundColor} - /> - <Collapse in={!!visuals.labelBackgroundColor} animateOpacity> - <Box paddingTop={2}> - <SliderWithInfo - label="Background opacity" - value={visuals.labelBackgroundOpacity} - onChange={(value) => { - console.log(visuals.labelBackgroundOpacity) - setVisuals({ ...visuals, labelBackgroundOpacity: value }) - }} - min={0} - max={1} - step={0.01} - /> - </Box> + <Collapse in={visuals.labelDynamicStrength > 0}> + <SliderWithInfo + label="Dynamic zoom degree cap" + infoText="By default, labels of nodes with more links will appear earlier than those with fewer. This slider changes the strength of this effect, put it at zero to disable it." + value={visuals.labelDynamicDegree} + min={1} + max={15} + step={1} + onChange={(value) => + setVisuals((curr: typeof initialVisuals) => ({ + ...curr, + labelDynamicDegree: value, + })) + } + /> </Collapse> - <Collapse in={visuals.labels > 1} animateOpacity> - <Box paddingTop={2}> - <SliderWithInfo - label="Label Appearance Scale" - value={visuals.labelScale * 5} - onChange={(value) => setVisuals({ ...visuals, labelScale: value / 5 })} - /> - </Box> - </Collapse> - </VStack> + </Box> + </Collapse> + <ColorMenu + colorList={colorList} + label="Text" + setVisuals={setVisuals} + value="labelTextColor" + visValue={visuals.labelTextColor} + /> + <Box> + <ColorMenu + colorList={colorList} + label="Background" + setVisuals={setVisuals} + value="labelBackgroundColor" + visValue={visuals.labelBackgroundColor} + /> + <Collapse in={!!visuals.labelBackgroundColor} animateOpacity> + <Box paddingTop={2}> + <SliderWithInfo + label="Background opacity" + value={visuals.labelBackgroundOpacity} + onChange={(value) => { + console.log(visuals.labelBackgroundOpacity) + setVisuals({ ...visuals, labelBackgroundOpacity: value }) + }} + min={0} + max={1} + step={0.01} + /> + </Box> + </Collapse> </Box> + <SliderWithInfo + label="Label font size" + value={visuals.labelFontSize} + min={5} + max={20} + step={0.5} + onChange={(value) => setVisuals({ ...visuals, labelFontSize: value })} + /> + <SliderWithInfo + label="Max. label characters" + value={visuals.labelLength} + min={10} + max={100} + step={1} + onChange={(value) => setVisuals({ ...visuals, labelLength: value })} + /> + <SliderWithInfo + label="Max. label line length" + value={visuals.labelWordWrap} + min={10} + max={100} + step={1} + onChange={(value) => setVisuals({ ...visuals, labelWordWrap: value })} + /> + <SliderWithInfo + label="Space between label lines" + value={visuals.labelLineSpace} + min={0.2} + max={3} + step={0.1} + onChange={(value) => setVisuals({ ...visuals, labelLineSpace: value })} + /> </VStack> ) } diff --git a/components/Tweaks/NodesNLinksPanel.tsx b/components/Tweaks/NodesNLinksPanel.tsx index dba927c..3321ae3 100644 --- a/components/Tweaks/NodesNLinksPanel.tsx +++ b/components/Tweaks/NodesNLinksPanel.tsx @@ -28,7 +28,7 @@ export const NodesNLinksPanel = (props: NodesNLinksPanelProps) => { onChange={(value) => setVisuals({ ...visuals, nodeRel: value })} /> <SliderWithInfo - label="Node connections size scale" + label="Node degree size multiplier" value={visuals.nodeSizeLinks} min={0} max={2} diff --git a/components/Tweaks/PhysicsPanel.tsx b/components/Tweaks/PhysicsPanel.tsx index 05fdba8..cd18f03 100644 --- a/components/Tweaks/PhysicsPanel.tsx +++ b/components/Tweaks/PhysicsPanel.tsx @@ -65,37 +65,15 @@ export const PhysicsPanel = (props: PhysicsPanelProps) => { onChange={(v) => setPhysicsCallback(v, 'charge', -1 / 100)} label="Repulsive Force" /> - <EnableSection - label="Collision" - infoText="Perfomance sap, disable if slow" - value={physics.collision} - onChange={() => setPhysics({ ...physics, collision: !physics.collision })} - > - <SliderWithInfo - value={physics.collisionStrength / 5} - onChange={(v) => setPhysicsCallback(v, 'collisionStrength', 1 / 5)} - label="Collision Radius" - infoText="Easy with this one, high values can lead to a real jiggly mess" - /> - </EnableSection> <SliderWithInfo value={physics.linkStrength * 5} onChange={(v) => setPhysicsCallback(v, 'linkStrength', 5)} label="Link Force" /> <SliderWithInfo - label="Link Iterations" - value={physics.linkIts} - onChange={(v) => setPhysicsCallback(v, 'linkIts', 1)} - min={0} - max={6} - step={1} - infoText="How many links down the line the physics of a single node affects (Slow)" - /> - <SliderWithInfo - label="Viscosity" - value={physics.velocityDecay * 10} - onChange={(v) => setPhysicsCallback(v, 'velocityDecay', 10)} + label="Stabilization rate" + value={physics.alphaDecay * 50} + onChange={(v) => setPhysicsCallback(v, 'alphaDecay', 50)} /> </VStack> <Box> @@ -114,10 +92,32 @@ export const PhysicsPanel = (props: PhysicsPanelProps) => { paddingLeft={3} color="gray.800" > + <EnableSection + label="Collision" + infoText="Perfomance sap, disable if slow" + value={physics.collision} + onChange={() => setPhysics({ ...physics, collision: !physics.collision })} + > + <SliderWithInfo + value={physics.collisionStrength / 5} + onChange={(v) => setPhysicsCallback(v, 'collisionStrength', 1 / 5)} + label="Collision Radius" + infoText="Easy with this one, high values can lead to a real jiggly mess" + /> + </EnableSection> + <SliderWithInfo + label="Link iterations" + value={physics.linkIts} + onChange={(v) => setPhysicsCallback(v, 'linkIts', 1)} + min={0} + max={6} + step={1} + infoText="How many links down the line the physics of a single node affects (Slow)" + /> <SliderWithInfo - label="Stabilization rate" - value={physics.alphaDecay * 50} - onChange={(v) => setPhysicsCallback(v, 'alphaDecay', 50)} + label="Viscosity" + value={physics.velocityDecay * 10} + onChange={(v) => setPhysicsCallback(v, 'velocityDecay', 10)} /> <EnableSection label="Center nodes" diff --git a/components/Tweaks/SliderWithInfo.tsx b/components/Tweaks/SliderWithInfo.tsx index 9d6903a..7984711 100644 --- a/components/Tweaks/SliderWithInfo.tsx +++ b/components/Tweaks/SliderWithInfo.tsx @@ -31,7 +31,7 @@ export const SliderWithInfo = ({ const { highlightColor } = useContext(ThemeContext) return ( <Box key={label} pt={1} pb={2}> - <Box display="flex" alignItems="flex-end"> + <Box display="flex" alignItems="flex-end" mb={2}> <Text>{label}</Text> {infoText && <InfoTooltip infoText={infoText} />} </Box> diff --git a/components/Tweaks/TagColorPanel.tsx b/components/Tweaks/TagColorPanel.tsx index 0a595e8..d20b540 100644 --- a/components/Tweaks/TagColorPanel.tsx +++ b/components/Tweaks/TagColorPanel.tsx @@ -40,7 +40,8 @@ export const TagColorPanel = (props: TagColorPanelProps) => { <Box> <CUIAutoComplete items={tagArray} - label="Add tag to filter" + labelStyleProps={{ fontWeight: 300, fontSize: 14 }} + label="Add tag to color" placeholder=" " disableCreateItem={true} selectedItems={selectedItems} @@ -60,9 +61,10 @@ export const TagColorPanel = (props: TagColorPanelProps) => { highlightItemBg="gray.400" toggleButtonStyleProps={{ variant: 'outline' }} inputStyleProps={{ + height: 8, focusBorderColor: highlightColor, color: 'gray.800', - borderColor: 'gray.600', + borderColor: 'gray.500', }} tagStyleProps={{ display: 'none', @@ -86,7 +88,7 @@ export const TagColorPanel = (props: TagColorPanelProps) => { return ( <Flex key={tag} alignItems="center" justifyContent="space-between" width="100%" pl={2}> <Box width="100%"> - <Text fontWeight="bold">{tag}</Text> + <Text>{tag}</Text> </Box> <Menu isLazy placement="right"> <MenuButton as={Button} colorScheme="" color="black"> diff --git a/components/Tweaks/TagPanel.tsx b/components/Tweaks/TagPanel.tsx index 4d2e355..8d63c6f 100644 --- a/components/Tweaks/TagPanel.tsx +++ b/components/Tweaks/TagPanel.tsx @@ -25,6 +25,7 @@ export const TagPanel = (props: TagPanelProps) => { return ( <CUIAutoComplete + labelStyleProps={{ fontWeight: 300, fontSize: 14 }} items={tagArray} label={'Add tag to ' + mode} placeholder=" " @@ -41,16 +42,25 @@ export const TagPanel = (props: TagPanelProps) => { highlightItemBg="gray.400" toggleButtonStyleProps={{ variant: 'outline' }} inputStyleProps={{ + mt: 2, + height: 8, focusBorderColor: highlightColor, color: 'gray.800', - borderColor: 'gray.600', + borderColor: 'gray.500', }} tagStyleProps={{ - rounded: 'full', - bg: highlightColor, - height: 8, - paddingLeft: 4, - fontWeight: 'bold', + justifyContent: 'flex-start', + //variant: 'subtle', + fontSize: 10, + borderColor: highlightColor, + borderWidth: 1, + borderRadius: 'md', + color: highlightColor, + bg: '', + height: 4, + mb: 2, + //paddingLeft: 4, + //fontWeight: 'bold', }} hideToggleButton itemRenderer={(selected) => selected.label} diff --git a/components/Tweaks/VisualsPanel.tsx b/components/Tweaks/VisualsPanel.tsx index d3c8415..23ba41f 100644 --- a/components/Tweaks/VisualsPanel.tsx +++ b/components/Tweaks/VisualsPanel.tsx @@ -108,6 +108,3 @@ export const VisualsPanel = (props: VisualsPanelProps) => { </VStack> ) } -function clickCallback(color: string): void { - throw new Error('Function not implemented.') -} diff --git a/components/config.ts b/components/config.ts index 60e68f3..32a4c21 100644 --- a/components/config.ts +++ b/components/config.ts @@ -56,23 +56,25 @@ export const initialVisuals = { arrowsColor: '', linkOpacity: 0.8, linkWidth: 1, - nodeRel: 4, + nodeRel: 3, nodeOpacity: 1, nodeResolution: 12, labels: 2, - labelScale: 1, + labelScale: 1.5, labelFontSize: 10, labelLength: 40, labelWordWrap: 25, labelLineSpace: 1, + labelDynamicDegree: 8, + labelDynamicStrength: 0.5, highlight: true, - highlightNodeSize: 1.2, - highlightLinkSize: 2, + highlightNodeSize: 1.1, + highlightLinkSize: 0.7, highlightFade: 0.8, highlightAnim: true, - animationSpeed: 420, + animationSpeed: 360, algorithmOptions: options, - algorithmName: 'SinusoidalOut', + algorithmName: 'CircularOut', linkColorScheme: 'gray.500', nodeColorScheme: [ 'red.500', @@ -105,7 +107,7 @@ export const initialVisuals = { refLinkHighlightColor: '', refNodeColor: 'black', nodeSizeLinks: 0.5, - nodeZoomSize: 1.3, + nodeZoomSize: 1.2, } export interface TagColors { |