Skip to content

Commit

Permalink
Component | Graph | Links: Supporting longer link labels; Clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
rokotyan committed Jan 5, 2024
1 parent c61aa2f commit a2aa426
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 82 deletions.
1 change: 0 additions & 1 deletion packages/ts/src/components/graph/modules/link/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { getX, getY } from '../node/helper'

export const getPolylineData = (d: { x1: number; x2: number; y1: number; y2: number}): string => `${d.x1},${d.y1} ${(d.x1 + d.x2) / 2},${(d.y1 + d.y2) / 2} ${d.x2},${d.y2}`

export const LINK_LABEL_RADIUS = 8
export const LINK_MARKER_WIDTH = 9
export const LINK_MARKER_HEIGHT = 7

Expand Down
121 changes: 62 additions & 59 deletions packages/ts/src/components/graph/modules/link/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { select, Selection } from 'd3-selection'
import { range } from 'd3-array'
import { Transition } from 'd3-transition'
import toPx from 'to-px'

// Utils
import { throttle, getValue, getNumber, getBoolean } from 'utils/data'
import { smartTransition } from 'utils/d3'
import { getCSSVariableValueInPixels } from 'utils/misc'
import { estimateStringPixelLength } from 'utils/text'

// Types
import { GraphInputLink, GraphInputNode } from 'types/graph'
Expand All @@ -23,7 +26,6 @@ import {
getLinkBandWidth,
getLinkColor,
getLinkLabelTextColor,
LINK_LABEL_RADIUS,
getLinkArrowStyle,
LINK_MARKER_WIDTH,
} from './helper'
Expand All @@ -33,6 +35,7 @@ import { ZoomLevel } from '../zoom-levels'
import * as generalSelectors from '../../style'
import * as linkSelectors from './style'


export function createLinks<N extends GraphInputNode, L extends GraphInputLink> (
selection: Selection<SVGGElement, GraphLink<N, L>, SVGGElement, unknown>
): void {
Expand All @@ -57,8 +60,14 @@ export function createLinks<N extends GraphInputNode, L extends GraphInputLink>
.append('circle')
.attr('class', linkSelectors.flowCircle)

selection.append('g')
.attr('class', linkSelectors.labelGroups)
const linkLabelGroup = selection.append('g')
.attr('class', linkSelectors.linkLabelGroup)

linkLabelGroup.append('rect')
.attr('class', linkSelectors.linkLabelBackground)

linkLabelGroup.append('text')
.attr('class', linkSelectors.linkLabelContent)
}

export function updateSelectedLinks<N extends GraphInputNode, L extends GraphInputLink> (
Expand Down Expand Up @@ -113,6 +122,8 @@ export function updateLinks<N extends GraphInputNode, L extends GraphInputLink>
const flowGroup = linkGroup.select(`.${linkSelectors.flowGroup}`)
const linkColor = getLinkColor(d, config)
const linkShiftTransform = getLinkShiftTransform(d, config.linkNeighborSpacing)
const linkLabelDatum = getValue<GraphLink<N, L>, GraphCircleLabel>(d, linkLabel, d._indexGlobal)
const linkLabelText = linkLabelDatum ? linkLabelDatum.text.toString() : undefined

const x1 = getX(d.source)
const y1 = getY(d.source)
Expand Down Expand Up @@ -154,7 +165,7 @@ export function updateLinks<N extends GraphInputNode, L extends GraphInputLink>
const linkPathElement = linkSupport.node()
const pathLength = linkPathElement.getTotalLength()
if (linkArrowStyle) {
const arrowPos = pathLength * 0.5
const arrowPos = pathLength * (linkLabelText ? 0.65 : 0.5)
const p1 = linkPathElement.getPointAtLength(arrowPos)
const p2 = linkPathElement.getPointAtLength(arrowPos + 1) // A point very close to p1

Expand Down Expand Up @@ -183,62 +194,54 @@ export function updateLinks<N extends GraphInputNode, L extends GraphInputLink>
.style('opacity', scale < ZoomLevel.Level2 ? 0 : 1)

// Labels
const labelGroups = linkGroup.selectAll(`.${linkSelectors.labelGroups}`)
const labelDatum = getValue<GraphLink<N, L>, GraphCircleLabel>(d, linkLabel, d._indexGlobal)
const markerWidth = linkArrowStyle ? LINK_MARKER_WIDTH * 2 : 0
const labelShift = getBoolean(d, linkLabelShiftFromCenter, d._indexGlobal) ? -markerWidth + 4 : 0
const labelPos = linkPathElement.getPointAtLength(pathLength / 2 + labelShift)
const labelTranslate = `translate(${labelPos.x}, ${labelPos.y})`

const labels = labelGroups
.selectAll<SVGGElement, GraphLink<N, L>>(`.${linkSelectors.labelGroup}`)
.data(labelDatum && labelDatum.text ? [labelDatum] : [])

// Enter
const labelsEnter = labels.enter().append('g')
.attr('class', linkSelectors.labelGroup)
.attr('transform', labelTranslate)
.style('opacity', 0)

labelsEnter.append('circle')
.attr('class', linkSelectors.labelCircle)
.attr('r', 0)

labelsEnter.append('text')
.attr('class', linkSelectors.labelContent)

// Update
const labelsUpdate = labels.merge(labelsEnter)

smartTransition(labelsUpdate.select(`.${linkSelectors.labelCircle}`), duration)
.attr('r', label => label.radius ?? LINK_LABEL_RADIUS)
.style('fill', label => label.color)

labelsUpdate.select(`.${linkSelectors.labelContent}`)
.text(label => label.text)
.attr('dy', '0.1em')
.style('fill', label => label.textColor ?? getLinkLabelTextColor(label))
.style('font-size', label => {
if (label.fontSize) return label.fontSize
const radius = label.radius ?? LINK_LABEL_RADIUS
return `${radius / Math.pow(label.text.toString().length, 0.4)}px`
})

smartTransition(labelsUpdate, duration)
.attr('transform', labelTranslate)
.style('cursor', label => label.cursor)
.style('opacity', 1)

// Exit
const labelsExit = labels.exit()
smartTransition(labelsExit.select(`.${linkSelectors.labelCircle}`), duration)
.attr('r', 0)

smartTransition(labelsExit, duration)
.style('opacity', 0)
.remove()
const linkLabelGroup = linkGroup.select<SVGGElement>(`.${linkSelectors.linkLabelGroup}`)

if (linkLabelText) {
const linkMarkerWidth = linkArrowStyle ? LINK_MARKER_WIDTH * 2 : 0
const linkLabelShift = getBoolean(d, linkLabelShiftFromCenter, d._indexGlobal) ? -linkMarkerWidth + 4 : 0
const linkLabelPos = linkPathElement.getPointAtLength(pathLength / 2 + linkLabelShift)
const linkLabelTranslate = `translate(${linkLabelPos.x}, ${linkLabelPos.y})`
const linkLabelBackground = linkLabelGroup.select<SVGRectElement>(`.${linkSelectors.linkLabelBackground}`)
const linkLabelContent = linkLabelGroup.select<SVGTextElement>(`.${linkSelectors.linkLabelContent}`)

// If the label was hidden or didn't have text before, we need to set the initial position
if (!linkLabelContent.text() || linkLabelContent.attr('hidden')) {
linkLabelGroup.attr('transform', linkLabelTranslate)
}

linkLabelGroup.attr('hidden', null)
.style('cursor', linkLabelDatum.cursor)

smartTransition(linkLabelGroup, duration)
.attr('transform', linkLabelTranslate)
.style('opacity', 1)

linkLabelContent
.text(linkLabelText)
.attr('dy', '0.1em')
.style('font-size', linkLabelDatum.fontSize)
.style('fill', linkLabelDatum.textColor ?? getLinkLabelTextColor(linkLabelDatum))

const shouldBeRenderedAsCircle = linkLabelText.length <= 2
const linkLabelPaddingVertical = 4
const linkLabelPaddingHorizontal = shouldBeRenderedAsCircle ? linkLabelPaddingVertical : 8
const linkLabelFontSize = toPx(linkLabelDatum.fontSize) ?? getCSSVariableValueInPixels('var(--vis-graph-link-label-font-size)', linkLabelContent.node())
const linkLabelWidthPx = estimateStringPixelLength(linkLabelText, linkLabelFontSize)
const linkLabelBackgroundBorderRadius = linkLabelDatum.radius ?? (shouldBeRenderedAsCircle ? linkLabelFontSize : 4)
const linkLabelBackgroundWidth = (shouldBeRenderedAsCircle ? linkLabelFontSize : linkLabelWidthPx)
linkLabelBackground
.attr('x', -linkLabelBackgroundWidth / 2 - linkLabelPaddingHorizontal)
.attr('y', -linkLabelFontSize / 2 - linkLabelPaddingVertical)
.attr('width', linkLabelBackgroundWidth + linkLabelPaddingHorizontal * 2)
.attr('height', linkLabelFontSize + linkLabelPaddingVertical * 2)
.attr('rx', linkLabelBackgroundBorderRadius)
.style('fill', linkLabelDatum.color)
} else {
linkLabelGroup.attr('hidden', true)
}
})

// Pointer Events
if (duration > 0) {
selection.attr('pointer-events', 'none')
const t = smartTransition(selection, duration) as Transition<SVGGElement, GraphLink<N, L>, SVGGElement, GraphLink<N, L>>
Expand Down Expand Up @@ -279,7 +282,7 @@ export function animateLinkFlow<N extends GraphInputNode, L extends GraphInputLi
const linkGroup = select(element)
const flowGroup = linkGroup.select(`.${linkSelectors.flowGroup}`)

const linkPathElement = linkGroup.select<SVGPathElement>(`.${linkSelectors.link}`).node()
const linkPathElement = linkGroup.select<SVGPathElement>(`.${linkSelectors.linkSupport}`).node()
const pathLength = linkPathElement.getTotalLength()

if (!getBoolean(d, linkFlow, d._indexGlobal)) return
Expand Down
36 changes: 17 additions & 19 deletions packages/ts/src/components/graph/modules/link/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,27 @@ export const variables = injectGlobal`
--vis-graph-link-greyout-opacity: 0.3;
--vis-graph-link-dashed-stroke-dasharray: 6 6;
--vis-graph-link-label-stroke-color: #fff;
--vis-graph-link-label-fill-color: #e6e9f3;
--vis-graph-link-label-text-color-dark: #494b56;
--vis-graph-link-label-font-size: 9pt;
--vis-graph-link-label-background: #e6e9f3;
--vis-graph-link-label-text-color-dark: #18181B;
--vis-graph-link-label-text-color-bright: #fff;
--vis-graph-link-label-text-color: var(--vis-graph-link-label-text-color-dark);
--vis-graph-link-band-opacity: 0.35;
--vis-graph-link-support-stroke-width: 10px;
--vis-dark-graph-link-stroke-color: #494b56;
--vis-dark-graph-link-label-stroke-color: #222;
--vis-dark-graph-link-label-fill-color: var(--vis-color-grey);
--vis-dark-graph-link-label-text-color: var(--vis-graph-link-label-text-color-bright)
--vis-dark-graph-link-label-background: #3f3f45;
--vis-dark-graph-link-label-text-color: var(--vis-graph-link-label-text-color-bright);
--vis-graph-link-dominant-baseline: middle;
}
body.theme-dark ${`.${links}`} {
--vis-graph-link-stroke-color: var(--vis-dark-graph-link-stroke-color);
--vis-graph-link-label-stroke-color: var(--vis-dark-graph-link-label-stroke-color);
--vis-graph-link-label-text-color: var(--vis-dark-graph-link-label-text-color);
--vis-graph-link-label-fill-color: var(--vis-dark-graph-link-label-fill-color);
--vis-graph-link-label-background: var(--vis-dark-graph-link-label-background);
}
`

Expand Down Expand Up @@ -105,27 +106,24 @@ export const flowCircle = css`
fill: var(--vis-graph-link-stroke-color);
`

export const labelGroups = css`
label: label-groups;
`

export const labelGroup = css`
export const linkLabelGroup = css`
label: label-group;
pointer-events: all;
`

export const labelCircle = css`
label: label-circle;
export const linkLabelBackground = css`
label: label-background;
fill: var(--vis-graph-link-label-fill-color);
stroke: var(--vis-graph-link-label-stroke-color);
fill: var(--vis-graph-link-label-background);
`

export const labelContent = css`
export const linkLabelContent = css`
label: label-content;
font-family: var(--vis-graph-icon-font-family), var(--vis-font-family);
font-size: var(--vis-graph-link-label-font-size);
font-family: var(--vis-font-family);
fill: var(--vis-graph-link-label-text-color);
text-anchor: middle;
dominant-baseline: middle;
dominant-baseline: var(--vis-graph-link-dominant-baseline);
user-select: none;
`
9 changes: 6 additions & 3 deletions packages/ts/src/components/graph/modules/node/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export const variables = injectGlobal`
--vis-dark-graph-node-greyout-color: #494b56;
--vis-dark-graph-node-icon-greyout-color: var(--vis-color-grey);
--vis-dark-graph-node-side-label-background-greyout-color: #494B56;
/* Misc */
--vis-graph-node-dominant-baseline: middle;
}
body.theme-dark ${`.${nodes}`} {
Expand Down Expand Up @@ -105,7 +108,7 @@ export const nodeIcon = css`
label: icon;
font-family: var(--vis-graph-icon-font-family), var(--vis-font-family);
dominant-baseline: middle;
dominant-baseline: var(--vis-graph-node-dominant-baseline);
text-anchor: middle;
pointer-events: none;
transition: .4s all;
Expand All @@ -116,7 +119,7 @@ export const nodeBottomIcon = css`
label: node-bottom-icon;
font-family: var(--vis-graph-icon-font-family), var(--vis-font-family);
font-size: var(--vis-graph-node-bottom-icon-font-size);
dominant-baseline: middle;
dominant-baseline: var(--vis-graph-node-dominant-baseline);
text-anchor: middle;
pointer-events: none;
transition: .4s fill;
Expand Down Expand Up @@ -181,7 +184,7 @@ export const sideLabel = css`
label: side-label;
font-family: var(--vis-graph-icon-font-family), var(--vis-font-family);
dominant-baseline: middle;
dominant-baseline: var(--vis-graph-node-dominant-baseline);
text-anchor: middle;
font-size: 16px;
fill: var(--vis-graph-node-side-label-fill-color-bright);
Expand Down
2 changes: 2 additions & 0 deletions packages/ts/src/components/graph/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export type GraphCircleLabel = {
radius?: number;
}

export type GraphLinkLabel = GraphCircleLabel

export enum GraphLinkStyle {
Dashed = 'dashed',
Solid = 'solid',
Expand Down

0 comments on commit a2aa426

Please sign in to comment.