Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release: 1.3.5 #342

Merged
merged 8 commits into from
Feb 27, 2024
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "unovis",
"version": "1.3.4",
"version": "1.3.5",
"license": "Apache-2.0",
"private": true,
"workspaces": [
Expand Down
4 changes: 2 additions & 2 deletions packages/angular/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@unovis/angular",
"description": "Modular data visualization framework for React, Angular, Svelte, and vanilla TypeScript or JavaScript",
"version": "1.3.4",
"version": "1.3.5",
"repository": {
"type": "git",
"url": "https://github.com/f5/unovis.git",
Expand Down Expand Up @@ -45,7 +45,7 @@
},
"peerDependencies": {
"@unovis/shared": "*",
"@unovis/ts": "1.3.4",
"@unovis/ts": "1.3.5",
"@angular/common": "12 - 16",
"@angular/compiler": "12 - 16",
"@angular/core": "12 - 16"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ export class VisNestedDonutComponent<Datum> implements NestedDonutConfigInterfac
/** When true, the component will display empty segments (the ones that have `0` values) as tiny slices.
* Default: `false` */
@Input() showEmptySegments?: boolean

/** Show labels for individual segments. Default: `true` */
@Input() showSegmentLabels?: boolean

@Input() data: Datum[]

component: NestedDonut<Datum> | undefined
Expand Down
4 changes: 2 additions & 2 deletions packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@unovis/react",
"description": "Modular data visualization framework for React, Angular, Svelte, and vanilla TypeScript or JavaScript",
"version": "1.3.4",
"version": "1.3.5",
"repository": {
"type": "git",
"url": "https://github.com/f5/unovis.git",
Expand Down Expand Up @@ -35,7 +35,7 @@
"publish:dist": "rm -rf dist/.cache; cp ./{LICENSE,README.md,package.json} ./dist; cd ./dist; npm publish"
},
"peerDependencies": {
"@unovis/ts": "1.3.4",
"@unovis/ts": "1.3.5",
"react": ">=16.8.0 || ^17 || ^18",
"react-dom": ">=16.8.0 || ^17 || ^18"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@unovis/shared",
"description": "Modular data visualization framework for React, Angular, Svelte, and vanilla TypeScript or JavaScript",
"version": "1.3.4",
"version": "1.3.5",
"repository": {
"type": "git",
"url": "https://github.com/f5/unovis.git",
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@unovis/svelte",
"description": "Modular data visualization framework for React, Angular, Svelte, and vanilla TypeScript or JavaScript",
"version": "1.3.4",
"version": "1.3.5",
"repository": {
"type": "git",
"url": "https://github.com/f5/unovis.git",
Expand Down Expand Up @@ -42,7 +42,7 @@
"publish:dist": "rm -rf dist/.cache; cp ./{LICENSE,README.md,package.json} ./dist; cd ./dist; npm publish"
},
"peerDependencies": {
"@unovis/ts": "1.3.4",
"@unovis/ts": "1.3.5",
"svelte": "^3.48.0 || ^4.0.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@unovis/ts",
"description": "Modular data visualization framework for React, Angular, Svelte, and vanilla TypeScript or JavaScript",
"version": "1.3.4",
"version": "1.3.5",
"repository": {
"type": "git",
"url": "https://github.com/f5/unovis.git",
Expand Down
43 changes: 27 additions & 16 deletions packages/ts/src/components/chord-diagram/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { GraphData, GraphDataModel } from 'data-models/graph'
// Utils
import { getNumber, isNumber, getString, getValue } from 'utils/data'
import { estimateStringPixelLength } from 'utils/text'
import { getCSSVariableValueInPixels } from 'utils/misc'

// Types
import { Spacing } from 'types/spacing'
Expand Down Expand Up @@ -50,10 +51,6 @@ export class ChordDiagram<
arcGen = arc<ChordNode<N>>()
radiusScale: ScalePower<number, number> = scalePow()

private _nodes: ChordNode<N>[] = []
private _links: ChordRibbon<N>[] = []
private _rootNode: ChordNode<N>

events = {
[ChordDiagram.selectors.node]: {
mouseover: this._onNodeMouseOver.bind(this),
Expand All @@ -69,13 +66,18 @@ export class ChordDiagram<
},
}

private _nodes: ChordNode<N>[] = []
private _links: ChordRibbon<N>[] = []
private _rootNode: ChordNode<N>

private get _forceHighlight (): boolean {
return this.config.highlightedNodeId !== undefined || this.config.highlightedLinkIds?.length > 0
}

constructor (config?: ChordDiagramConfigInterface<N, L>) {
super()
if (config) this.setConfig(config)

this.background = this.g.append('rect').attr('class', s.background)
this.linkGroup = this.g.append('g').attr('class', s.links)
this.nodeGroup = this.g.append('g').attr('class', s.nodes)
Expand All @@ -84,12 +86,15 @@ export class ChordDiagram<

get bleed (): Spacing {
const { config } = this
const padding = 4 + LABEL_PADDING * 2
const padding = LABEL_PADDING * 2
let top = 0; let bottom = 0; let right = 0; let left = 0
this._nodes.forEach(n => {
const nodeLabelAlignment = getValue(n.data, config.nodeLabelAlignment)
if (n.height === 0 && nodeLabelAlignment === ChordLabelAlignment.Perpendicular) {
const labelWidth = estimateStringPixelLength(getString(n.data as N, config.nodeLabel) ?? '', 16)
const label = getString(n.data as N, config.nodeLabel) ?? ''
const fontSize = getCSSVariableValueInPixels('var(--vis-chord-diagram-label-text-font-size)', this.element)
const labelWidth = estimateStringPixelLength(label, fontSize)

const [x, y] = this.arcGen.centroid(n)

if (x < 0) left = Math.max(left, labelWidth)
Expand Down Expand Up @@ -157,13 +162,17 @@ export class ChordDiagram<
const size = Math.min(this._width, this._height)
const radius = size / 2 - max([bleed.top, bleed.bottom, bleed.left, bleed.right])

this.radiusScale.range([0, radius - config.nodeWidth])
const numLevels = 1 + config.nodeLevels?.length
const maxSpace = config.nodeWidth * numLevels
const nodeWidth = radius > maxSpace ? config.nodeWidth : Math.max(radius / numLevels, 0)

this.radiusScale.range([0, Math.max(radius, 0)])

this.arcGen
.startAngle(d => d.x0 + config.padAngle / 2 - (d.value ? 0 : Math.PI / 360))
.endAngle(d => d.x1 - config.padAngle / 2 + (d.value ? 0 : Math.PI / 360))
.cornerRadius(d => getNumber(d.data, config.cornerRadius))
.innerRadius(d => this.radiusScale(d.y1) - getNumber(d, config.nodeWidth))
.innerRadius(d => Math.max(this.radiusScale(d.y1) - nodeWidth, 0))
.outerRadius(d => this.radiusScale(d.y1))

this.g.classed(s.transparent, this._forceHighlight)
Expand All @@ -181,16 +190,17 @@ export class ChordDiagram<
const linksSelection = this.linkGroup
.selectAll<SVGPathElement, ChordRibbon<N>>(`.${s.link}`)
.data(this._links, d => String(d.data._id))
.classed(s.highlightedLink, l => {
const linkId = l.data.id ?? l.data._indexGlobal
return config.highlightedLinkIds?.includes(linkId)
})

const linksEnter = linksSelection.enter().append('path')
.attr('class', s.link)
.call(createLink, this.radiusScale)

const linksMerged = linksSelection.merge(linksEnter)
const linksMerged = linksSelection
.merge(linksEnter)
.classed(s.highlightedLink, l => {
const linkId = l.data.id ?? l.data._indexGlobal
return config.highlightedLinkIds?.includes(linkId)
})
linksMerged.call(updateLink, config, this.radiusScale, duration)

linksSelection.exit()
Expand All @@ -200,20 +210,21 @@ export class ChordDiagram<
const nodesSelection = this.nodeGroup
.selectAll<SVGPathElement, ChordNode<N>>(`.${s.node}`)
.data(this._nodes, d => String(d.uid))
.classed(s.highlightedNode, d => config.highlightedNodeId === d.data._id)

const nodesEnter = nodesSelection.enter().append('path')
.attr('class', s.node)
.call(createNode, config)

const nodesMerged = nodesSelection.merge(nodesEnter)
const nodesMerged = nodesSelection
.merge(nodesEnter)
.classed(s.highlightedNode, d => config.highlightedNodeId === d.data._id)
nodesMerged.call(updateNode, config, this.arcGen, duration, this.bleed)

nodesSelection.exit()
.call(removeNode, duration)

// Labels
const labelWidth = size - radius - config.nodeWidth
const labelWidth = size - radius
const labels = this.labelGroup
.selectAll<SVGGElement, ChordNode<N>>(`.${s.label}`)
.data(this._nodes, d => String(d.uid))
Expand Down
52 changes: 24 additions & 28 deletions packages/ts/src/components/chord-diagram/modules/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,39 +101,35 @@ export function updateLabel<N extends ChordInputNode, L extends ChordInputLink>
.style('opacity', 1)

const label: Selection<SVGTextElement, ChordNode<N>, SVGElement, unknown> = selection.select(`.${s.labelText}`)
label.select('textPath').remove()
label
.text(d => getString(d.data, nodeLabel))
label.selectAll('textPath').remove()

label.text(d => getString(d.data, nodeLabel))
.style('transition', `fill ${duration}ms`)
.style('fill', d => getColor(d.data, nodeLabelColor) ?? getLabelFillColor(d, config))
.style('text-anchor', d => getLabelTextAnchor(d, config))
.each((d: ChordNode<N>, i: number, elements) => {
const nodeLabelAlignment = getValue(d.data, config.nodeLabelAlignment) ?? ChordLabelAlignment.Along
const radianArcLength = d.x1 - d.x0 - getNumber(d.data, config.padAngle) * 2
const radius = radiusScale(d.y1) - getNumber(d, config.nodeWidth) / 2
const arcLength = radius * radianArcLength
const maxWidth = (nodeLabelAlignment === ChordLabelAlignment.Along ? arcLength : width - LABEL_PADDING * 2)

label.each((d: ChordNode<N>, i: number, elements) => {
const nodeLabelAlignment = getValue(d.data, config.nodeLabelAlignment) ?? ChordLabelAlignment.Along
const radianArcLength = d.x1 - d.x0 - getNumber(d.data, config.padAngle) * 2
const radius = radiusScale(d.y1) - getNumber(d, config.nodeWidth) / 2
const arcLength = radius * radianArcLength
const maxWidth = (nodeLabelAlignment === ChordLabelAlignment.Along ? arcLength : width) - LABEL_PADDING * 2

const textElementSelection = select(elements[i])
trimSVGText(textElementSelection, maxWidth)
textElementSelection
.attr('dx', nodeLabelAlignment === ChordLabelAlignment.Along ? LABEL_PADDING : null)
.attr('dy', nodeLabelAlignment === ChordLabelAlignment.Along ? getNumber(d.data, nodeWidth) / 2 : null)

if (nodeLabelAlignment === ChordLabelAlignment.Along) {
const textElement = select(elements[i])
const textWidth = textElement.node().getBoundingClientRect().width
const labelText = textElement.text()
.call(trimSVGText, maxWidth)
.attr('dx', nodeLabelAlignment === ChordLabelAlignment.Along ? LABEL_PADDING : null)
.attr('dy', nodeLabelAlignment === ChordLabelAlignment.Along ? getNumber(d.data, nodeWidth) / 2 : null)

select(elements[i])
.text('')
.style('display', textWidth > maxWidth && 'none')

select(elements[i]).append('textPath')
.attr('href', `#${d.uid}`)
.text(labelText)
}
})
const textWidth = textElement.node().getComputedTextLength()
const labelText = textElement.text()
if (nodeLabelAlignment === ChordLabelAlignment.Along) {
textElement.text('')
if (textWidth <= maxWidth) {
textElement.append('textPath')
.attr('href', `#${d.uid}`)
.text(labelText)
}
}
})

smartTransition(label, duration)
.attr('transform', d => {
Expand Down
Loading
Loading