Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
86896c1
Core | Utils: Fix merge() class instance value handling and clone-on-…
rokotyan Jul 8, 2026
c6bdfda
Component | All: Fix shared default config aliasing when constructed …
rokotyan Jul 8, 2026
4d41206
Container | XY: Fix data change detection in setData
rokotyan Jul 8, 2026
b4ac750
Core | Component: Add _onDestroy teardown hook and fix component reso…
rokotyan Jul 8, 2026
bcc86a4
Core | GraphDataModel: Index nodes and links with maps instead of qua…
rokotyan Jul 8, 2026
aa63d02
Core | Tooltip: Avoid re-querying the DOM on every dispatched event
rokotyan Jul 8, 2026
339974d
Container | XY: Warn when updateComponents receives a mismatched numb…
rokotyan Jul 8, 2026
e5de46a
Container: Unify tooltip hide behavior between XY and Single containers
rokotyan Jul 8, 2026
4017895
Container | Single: Validate extended sizing support instead of crashing
rokotyan Jul 8, 2026
9a53463
Core | Container: Make render() awaitable and share the render schedu…
rokotyan Jul 8, 2026
325568b
Container | XY: Decouple the container from concrete component config…
rokotyan Jul 8, 2026
750a8b4
Container: Reconcile SVG children on update instead of full DOM teardown
rokotyan Jul 8, 2026
9b1d6ab
Container: Consolidate component size, margin, and color propagation
rokotyan Jul 8, 2026
88b657c
Svelte: Re-render the chart when child component data or config changes
rokotyan Jul 8, 2026
3010871
Vue: Replace implicit proxy tracking with explicit container notifica…
rokotyan Jul 8, 2026
124340e
React | Vue | Solid: Stop passing datasets and framework props into c…
rokotyan Jul 8, 2026
eb9d504
Misc | Wrappers: Unify the data prop contract across all five frameworks
rokotyan Jul 8, 2026
55b6fd1
Shared | Autogen: Consolidate the five component generator drivers
rokotyan Jul 8, 2026
37459ef
Component | Crosshair: Bisect over cached sorted x values on mousemove
rokotyan Jul 8, 2026
5fbd9d4
Container | XY | Component: Compute scale domains once per render cyc…
rokotyan Jul 8, 2026
1de5d84
Component | Graph: Avoid per-node forced reflows; shallow-copy data m…
rokotyan Jul 8, 2026
286d29c
Core | Utils: Stop deep-cloning configs on every configuration update
rokotyan Jul 8, 2026
ac29b47
Component | Axis: Cache auto-margin measurements and snapshot rects i…
rokotyan Jul 8, 2026
c8d0cab
Shared | Examples: Add data-reactivity example to the multi-framework…
rokotyan Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/ts/src/components/annotations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class Annotations extends ComponentCore<unknown[], AnnotationsConfigInter

constructor (config?: AnnotationsConfigInterface) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
}

_render (customDuration?: number): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/area/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class Area<Datum> extends XYComponentCore<Datum, AreaConfigInterface<Datu

constructor (config?: AreaConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)

// Determine if the provided chart should be stacked
this.stacked = Array.isArray(this.config.y)
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/axis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class Axis<Datum> extends XYComponentCore<Datum, AxisConfigInterface<Datu

constructor (config?: AxisConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)

this.g.attr('axis-type', this.config.type)

Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/brush/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class Brush<Datum> extends XYComponentCore<Datum, BrushConfigInterface<Da

constructor (config?: BrushConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)

const directions: BrushHandleType[] = [{ type: BrushDirection.West }, { type: BrushDirection.East }]
this.unselectedRange = this.g
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/bullet-legend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class BulletLegend {

this.element = this.div.node()

if (config) this.setConfig(config)
this.setConfig(config)
}

setConfig (config: BulletLegendConfigInterface): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/chord-diagram/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class ChordDiagram<

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

this.background = this.g.append('rect').attr('class', s.background)
this.linkGroup = this.g.append('g').attr('class', s.links)
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/crosshair/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class Crosshair<Datum> extends XYComponentCore<Datum, CrosshairConfigInte

constructor (config?: CrosshairConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)

this.g.style('opacity', 0)
this.line = this.g.append('line')
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/donut/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export class Donut<Datum> extends ComponentCore<Datum[], DonutConfigInterface<Da

constructor (config?: DonutConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
this.arcBackground = this.g.append('path')
this.arcGroup = this.g.append('g')
this.centralLabel = this.g.append('text')
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/flow-legend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class FlowLegend {

this.labels = this.div.append('div')

if (config) this.setConfig(config)
this.setConfig(config)
}

setConfig (config: FlowLegendConfigInterface): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/free-brush/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class FreeBrush<Datum> extends XYComponentCore<Datum, FreeBrushConfigInte

constructor (config: FreeBrushConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)

this.brush = this.g
.append('g')
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/graph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export class Graph<

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

this._backgroundRect = this.g.append('rect').attr('class', generalSelectors.background)
this._graphGroup = this.g.append('g').attr('class', generalSelectors.graphGroup)
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/grouped-bar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class GroupedBar<Datum> extends XYComponentCore<Datum, GroupedBarConfigIn

constructor (config?: GroupedBarConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
}

get bleed (): Spacing {
Expand Down
14 changes: 7 additions & 7 deletions packages/ts/src/components/leaflet-map/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class LeafletMap<Datum extends GenericDataRecord> extends ComponentCore<D
.attr('class', s.root)
.attr('aria-hidden', true)

if (config) this.setConfig(config)
this.setConfig(config)

if (!this._container.clientWidth) {
console.warn('Unovis | Leaflet Map: The width of the container is not set. Setting to 100%.')
Expand Down Expand Up @@ -184,9 +184,9 @@ export class LeafletMap<Datum extends GenericDataRecord> extends ComponentCore<D

this._map.leaflet.setView(initialMapCenter, initialMapZoom)

if (isDarkThemeEnabled() && config.styleDarkTheme) {
if (isDarkThemeEnabled() && this.config.styleDarkTheme) {
this._isDarkThemeActive = true
this.setTheme(config.styleDarkTheme)
this.setTheme(this.config.styleDarkTheme)
}

this.config.onMapInitialized?.()
Expand Down Expand Up @@ -226,10 +226,10 @@ export class LeafletMap<Datum extends GenericDataRecord> extends ComponentCore<D
setConfig (config: LeafletMapConfigInterface<Datum>): void {
super.setConfig(config)

if (config.width) this._containerSelection.style('width', isString(config.width) ? config.width : `${config.width}px`)
if (config.height) this._containerSelection.style('height', isString(config.height) ? config.height : `${config.height}px`)
if (config?.width) this._containerSelection.style('width', isString(config.width) ? config.width : `${config.width}px`)
if (config?.height) this._containerSelection.style('height', isString(config.height) ? config.height : `${config.height}px`)

if (this._map && config.renderer === LeafletMapRenderer.MapLibre) {
if (this._map && config?.renderer === LeafletMapRenderer.MapLibre) {
const layer = this._map.layer as any // Using any because the typings are not full
const maplibreMap = layer.getMaplibreMap()
if (maplibreMap.isStyleLoaded()) updateTopoJson(maplibreMap, this.config)
Expand All @@ -242,7 +242,7 @@ export class LeafletMap<Datum extends GenericDataRecord> extends ComponentCore<D
}

// Apply the `aria-label` attribute
this._containerSelection.attr('aria-label', config.ariaLabel)
this._containerSelection.attr('aria-label', this.config.ariaLabel)
}

setData (data: Datum[]): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/line/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class Line<Datum> extends XYComponentCore<Datum, LineConfigInterface<Datu

constructor (config?: LineConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
}

get bleed (): Spacing {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/nested-donut/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ NestedDonutConfigInterface<Datum>

constructor (config?: NestedDonutConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
this.arcBackground = this.g.append('g')
this.arcGroup = this.g.append('g')
.attr('class', s.segmentsGroup)
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/plotband/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class Plotband<Datum> extends XYComponentCore<Datum, PlotbandConfigInterf

constructor (config: PlotbandConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)

this.plotband = this.g.append('rect')
.attr('class', s.plotband)
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/plotline/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class Plotline<Datum> extends XYComponentCore<Datum, PlotlineConfigInterf

constructor (config: PlotlineConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)

this.plotline = this.g
.append('line')
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/radial-bar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class RadialBar<Datum> extends ComponentCore<Datum[], RadialBarConfigInte

constructor (config?: RadialBarConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
this.trackGroup = this.g.append('g')
this.barGroup = this.g.append('g')
this.centralLabel = this.g.append('text')
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/rolling-pin-legend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class RollingPinLegend {

this.element = this.div.node()

if (config) this.setConfig(config)
this.setConfig(config)
}

setConfig (config: RollingPinLegendConfigInterface): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/sankey/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class Sankey<

constructor (config?: SankeyConfigInterface<N, L>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
// eslint-disable-next-line @typescript-eslint/naming-convention
this._gNode = this.g.node() as (SVGGElement & { __zoom: ZoomTransform })
this._backgroundRect = this.g.append('rect').attr('class', s.background).style('pointer-events', 'all')
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/scatter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class Scatter<Datum> extends XYComponentCore<Datum, ScatterConfigInterfac

constructor (config?: ScatterConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
}

setConfig (config: ScatterConfigInterface<Datum>): void {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/stacked-bar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class StackedBar<Datum> extends XYComponentCore<Datum, StackedBarConfigIn

constructor (config?: StackedBarConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
}

get bleed (): Spacing {
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/timeline/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export class Timeline<Datum> extends XYComponentCore<Datum, TimelineConfigInterf

constructor (config?: TimelineConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)

// Invisible background rect to track events
this._background = this.g.append('rect').attr('class', s.background)
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/topojson-map/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export class TopoJSONMap<
.on('zoom', this._onZoom.bind(this))
.on('end', this._onZoomEnd.bind(this))

if (config) this.setConfig(config)
this.setConfig(config)
if (data) this.setData(data)

this.g.append('defs')
Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/treemap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class Treemap<Datum> extends ComponentCore<Datum[], TreemapConfigInterfac

constructor (config?: TreemapConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
this.tiles = this.g.append('g').attr('class', s.tiles)
}

Expand Down
2 changes: 1 addition & 1 deletion packages/ts/src/components/xy-labels/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class XYLabels<Datum> extends XYComponentCore<Datum, XYLabelsConfigInterf

constructor (config?: XYLabelsConfigInterface<Datum>) {
super()
if (config) this.setConfig(config)
this.setConfig(config)
}

_render (customDuration?: number): void {
Expand Down
4 changes: 2 additions & 2 deletions packages/ts/src/containers/xy-container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ScaleDimension } from 'types/scale'
import { Direction } from 'types/direction'

// Utils
import { clamp, clean, flatten, isEqual } from 'utils/data'
import { clamp, clean, flatten } from 'utils/data'
import { guid } from 'utils/misc'

// Config
Expand Down Expand Up @@ -117,6 +117,7 @@ export class XYContainer<Datum> extends ContainerCore {
const { components, config } = this
if (!data) return

const hasDataUpdated = this.datamodel.data !== data

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we miss this. :p

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is embarrassing 😄

this.datamodel.data = data

components.forEach((c) => {
Expand All @@ -130,7 +131,6 @@ export class XYContainer<Datum> extends ContainerCore {
// Hide tooltip and crosshair if the data has changed
// Important: We still want to do `setData` for the components above even if the data hasn't changed
// because calling `updateContainer` may add new components and we need to pass them the data
const hasDataUpdated = !isEqual(this.datamodel.data, data)
if (hasDataUpdated) {
config.tooltip?.hide()
config.crosshair?.hide()
Expand Down
6 changes: 4 additions & 2 deletions packages/ts/src/core/xy-component/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ export class XYComponentCore<

setConfig (config: ConfigInterface): void {
// We don't allow changing scales after the component has been initialized
if (this.config?.xScale) config.xScale = this.config.xScale
if (this.config?.yScale) config.yScale = this.config.yScale
if (config) {
if (this.config?.xScale) config.xScale = this.config.xScale
if (this.config?.yScale) config.yScale = this.config.yScale
}

super.setConfig(config)
}
Expand Down
9 changes: 6 additions & 3 deletions packages/ts/src/utils/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export const isEqual = (
skipKeys: string[] = [],
visited: Set<any> = new Set()
): boolean => {
if (a === b) return true

if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) return false

Expand Down Expand Up @@ -114,7 +116,9 @@ export const cloneDeep = <T>(obj: T, stack: Map<any, any> = new Map()): T => {
export const merge = <T, K>(obj1: T, obj2: K, visited: Map<any, any> = new Map()): T & K => {
type Rec = Record<string | number, unknown>

if (!obj1 || !obj2) return obj1 as T & K
// Clone `obj1` even when there's nothing to merge, so that callers never get a reference
// to the original object (e.g. a shared default config) that they could accidentally mutate
if (!obj1 || !obj2) return (obj1 ? cloneDeep(obj1) : obj1) as T & K
if ((obj1 as unknown) === (obj2 as unknown)) return obj1 as T & K

const newObj = (isAClassInstance(obj1 as Rec) ? obj1 : cloneDeep(obj1)) as T & K
Expand All @@ -129,9 +133,8 @@ export const merge = <T, K>(obj1: T, obj2: K, visited: Map<any, any> = new Map()

if (isPlainObject((obj1 as Rec)[key]) && isPlainObject((obj2 as Rec)[key])) {
(newObj as Rec)[key] = merge((obj1 as Rec)[key], (obj2 as Rec)[key], visited)
} else if (isAClassInstance(obj2 as Rec)) {
(newObj as Rec)[key] = obj2
} else {
// `cloneDeep` copies class instances and primitives by reference, and clones plain objects / arrays
(newObj as Rec)[key] = cloneDeep((obj2 as Rec)[key])
}
})
Expand Down