@internetstiftelsen/charts 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bar.d.ts +13 -0
- package/bar.js +137 -0
- package/base-chart.d.ts +72 -0
- package/base-chart.js +227 -0
- package/chart-interface.d.ts +11 -0
- package/chart-interface.js +1 -0
- package/grid.d.ts +10 -0
- package/grid.js +63 -0
- package/layout-manager.d.ts +38 -0
- package/layout-manager.js +123 -0
- package/legend.d.ts +23 -0
- package/legend.js +157 -0
- package/line.d.ts +11 -0
- package/line.js +82 -0
- package/package.json +57 -0
- package/theme.d.ts +2 -0
- package/theme.js +31 -0
- package/title.d.ts +18 -0
- package/title.js +88 -0
- package/tooltip.d.ts +15 -0
- package/tooltip.js +138 -0
- package/types.d.ts +83 -0
- package/types.js +4 -0
- package/utils.d.ts +2 -0
- package/utils.js +5 -0
- package/validation.d.ts +29 -0
- package/validation.js +75 -0
- package/x-axis.d.ts +16 -0
- package/x-axis.js +70 -0
- package/xy-chart.d.ts +18 -0
- package/xy-chart.js +272 -0
- package/y-axis.d.ts +15 -0
- package/y-axis.js +58 -0
package/bar.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Selection } from 'd3';
|
|
2
|
+
import type { BarConfig, DataItem, ScaleType, Orientation } from './types';
|
|
3
|
+
import type { ChartComponent } from '@/lib/chart-interface';
|
|
4
|
+
export declare class Bar implements ChartComponent {
|
|
5
|
+
readonly type: "bar";
|
|
6
|
+
readonly dataKey: string;
|
|
7
|
+
readonly fill: string;
|
|
8
|
+
readonly orientation: Orientation;
|
|
9
|
+
constructor(config: BarConfig);
|
|
10
|
+
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, data: DataItem[], xKey: string, x: any, y: any, parseValue: (value: any) => number, xScaleType?: ScaleType): void;
|
|
11
|
+
private renderVertical;
|
|
12
|
+
private renderHorizontal;
|
|
13
|
+
}
|
package/bar.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export class Bar {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
Object.defineProperty(this, "type", {
|
|
4
|
+
enumerable: true,
|
|
5
|
+
configurable: true,
|
|
6
|
+
writable: true,
|
|
7
|
+
value: 'bar'
|
|
8
|
+
});
|
|
9
|
+
Object.defineProperty(this, "dataKey", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
writable: true,
|
|
13
|
+
value: void 0
|
|
14
|
+
});
|
|
15
|
+
Object.defineProperty(this, "fill", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: void 0
|
|
20
|
+
});
|
|
21
|
+
Object.defineProperty(this, "orientation", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
writable: true,
|
|
25
|
+
value: void 0
|
|
26
|
+
});
|
|
27
|
+
this.dataKey = config.dataKey;
|
|
28
|
+
this.fill = config.fill || '#8884d8';
|
|
29
|
+
this.orientation = config.orientation || 'vertical';
|
|
30
|
+
}
|
|
31
|
+
render(plotGroup, data, xKey, x, y, parseValue, xScaleType = 'band') {
|
|
32
|
+
if (this.orientation === 'vertical') {
|
|
33
|
+
this.renderVertical(plotGroup, data, xKey, x, y, parseValue, xScaleType);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.renderHorizontal(plotGroup, data, xKey, x, y, parseValue, xScaleType);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
renderVertical(plotGroup, data, xKey, x, y, parseValue, xScaleType) {
|
|
40
|
+
const getXPosition = (d) => {
|
|
41
|
+
const xValue = d[xKey];
|
|
42
|
+
let scaledValue;
|
|
43
|
+
switch (xScaleType) {
|
|
44
|
+
case 'band':
|
|
45
|
+
scaledValue = xValue;
|
|
46
|
+
break;
|
|
47
|
+
case 'time':
|
|
48
|
+
scaledValue =
|
|
49
|
+
xValue instanceof Date ? xValue : new Date(xValue);
|
|
50
|
+
break;
|
|
51
|
+
case 'linear':
|
|
52
|
+
case 'log':
|
|
53
|
+
scaledValue =
|
|
54
|
+
typeof xValue === 'number' ? xValue : Number(xValue);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
return x(scaledValue) || 0;
|
|
58
|
+
};
|
|
59
|
+
const bandwidth = x.bandwidth ? x.bandwidth() : 20;
|
|
60
|
+
// Get the baseline value from the Y scale's domain
|
|
61
|
+
// For linear scales, use 0 if it's in the domain, otherwise use domain max (bottom of chart)
|
|
62
|
+
// For log scales, use the minimum value from the domain
|
|
63
|
+
const yDomain = y.domain();
|
|
64
|
+
const baselineValue = yDomain[0] >= 0 ? Math.max(0, yDomain[0]) : yDomain[0];
|
|
65
|
+
const yBaseline = y(baselineValue) || 0;
|
|
66
|
+
// Add bar rectangles
|
|
67
|
+
plotGroup
|
|
68
|
+
.selectAll(`.bar-${this.dataKey.replace(/\s+/g, '-')}`)
|
|
69
|
+
.data(data)
|
|
70
|
+
.join('rect')
|
|
71
|
+
.attr('class', `bar-${this.dataKey.replace(/\s+/g, '-')}`)
|
|
72
|
+
.attr('x', (d) => {
|
|
73
|
+
const xPos = getXPosition(d);
|
|
74
|
+
// For non-band scales, center the bar
|
|
75
|
+
return xScaleType === 'band' ? xPos : xPos - bandwidth / 2;
|
|
76
|
+
})
|
|
77
|
+
.attr('y', (d) => {
|
|
78
|
+
const yPos = y(parseValue(d[this.dataKey])) || 0;
|
|
79
|
+
return Math.min(yBaseline, yPos);
|
|
80
|
+
})
|
|
81
|
+
.attr('width', bandwidth)
|
|
82
|
+
.attr('height', (d) => {
|
|
83
|
+
const yPos = y(parseValue(d[this.dataKey])) || 0;
|
|
84
|
+
return Math.abs(yBaseline - yPos);
|
|
85
|
+
})
|
|
86
|
+
.attr('fill', this.fill);
|
|
87
|
+
}
|
|
88
|
+
renderHorizontal(plotGroup, data, xKey, x, y, parseValue, yScaleType) {
|
|
89
|
+
const getYPosition = (d) => {
|
|
90
|
+
const yValue = d[xKey];
|
|
91
|
+
let scaledValue;
|
|
92
|
+
switch (yScaleType) {
|
|
93
|
+
case 'band':
|
|
94
|
+
scaledValue = yValue;
|
|
95
|
+
break;
|
|
96
|
+
case 'time':
|
|
97
|
+
scaledValue =
|
|
98
|
+
yValue instanceof Date ? yValue : new Date(yValue);
|
|
99
|
+
break;
|
|
100
|
+
case 'linear':
|
|
101
|
+
case 'log':
|
|
102
|
+
scaledValue =
|
|
103
|
+
typeof yValue === 'number' ? yValue : Number(yValue);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
return y(scaledValue) || 0;
|
|
107
|
+
};
|
|
108
|
+
const bandwidth = y.bandwidth ? y.bandwidth() : 20;
|
|
109
|
+
// Get the baseline value from the scale's domain
|
|
110
|
+
// For linear scales, use 0 if it's in the domain, otherwise use domain min
|
|
111
|
+
// For log scales, use the minimum value from the domain
|
|
112
|
+
const domain = x.domain();
|
|
113
|
+
const baselineValue = domain[0] >= 0 ? Math.max(0, domain[0]) : domain[0];
|
|
114
|
+
const xBaseline = x(baselineValue) || 0;
|
|
115
|
+
// Add bar rectangles (horizontal)
|
|
116
|
+
plotGroup
|
|
117
|
+
.selectAll(`.bar-${this.dataKey.replace(/\s+/g, '-')}`)
|
|
118
|
+
.data(data)
|
|
119
|
+
.join('rect')
|
|
120
|
+
.attr('class', `bar-${this.dataKey.replace(/\s+/g, '-')}`)
|
|
121
|
+
.attr('x', (d) => {
|
|
122
|
+
const xPos = x(parseValue(d[this.dataKey])) || 0;
|
|
123
|
+
return Math.min(xBaseline, xPos);
|
|
124
|
+
})
|
|
125
|
+
.attr('y', (d) => {
|
|
126
|
+
const yPos = getYPosition(d);
|
|
127
|
+
// For non-band scales, center the bar
|
|
128
|
+
return yScaleType === 'band' ? yPos : yPos - bandwidth / 2;
|
|
129
|
+
})
|
|
130
|
+
.attr('width', (d) => {
|
|
131
|
+
const xPos = x(parseValue(d[this.dataKey])) || 0;
|
|
132
|
+
return Math.abs(xPos - xBaseline);
|
|
133
|
+
})
|
|
134
|
+
.attr('height', bandwidth)
|
|
135
|
+
.attr('fill', this.fill);
|
|
136
|
+
}
|
|
137
|
+
}
|
package/base-chart.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type Selection } from 'd3';
|
|
2
|
+
import type { DataItem, ChartTheme, AxisScaleConfig } from './types';
|
|
3
|
+
import type { ChartComponent } from '@/lib/chart-interface';
|
|
4
|
+
import type { XAxis } from './x-axis';
|
|
5
|
+
import type { YAxis } from './y-axis';
|
|
6
|
+
import type { Grid } from './grid';
|
|
7
|
+
import type { Tooltip } from './tooltip';
|
|
8
|
+
import type { Legend } from './legend';
|
|
9
|
+
import type { Title } from './title';
|
|
10
|
+
import { LayoutManager, type PlotAreaBounds } from './layout-manager';
|
|
11
|
+
export type BaseChartConfig = {
|
|
12
|
+
data: DataItem[];
|
|
13
|
+
theme?: Partial<ChartTheme>;
|
|
14
|
+
scales?: AxisScaleConfig;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Base chart class that provides common functionality for all chart types
|
|
18
|
+
*/
|
|
19
|
+
export declare abstract class BaseChart {
|
|
20
|
+
protected data: DataItem[];
|
|
21
|
+
protected readonly theme: ChartTheme;
|
|
22
|
+
protected readonly scaleConfig: AxisScaleConfig;
|
|
23
|
+
protected width: number;
|
|
24
|
+
protected xAxis: XAxis | null;
|
|
25
|
+
protected yAxis: YAxis | null;
|
|
26
|
+
protected grid: Grid | null;
|
|
27
|
+
protected tooltip: Tooltip | null;
|
|
28
|
+
protected legend: Legend | null;
|
|
29
|
+
protected title: Title | null;
|
|
30
|
+
protected svg: Selection<SVGSVGElement, undefined, null, undefined> | null;
|
|
31
|
+
protected plotGroup: Selection<SVGGElement, undefined, null, undefined> | null;
|
|
32
|
+
protected container: HTMLElement | null;
|
|
33
|
+
protected x: any;
|
|
34
|
+
protected y: any;
|
|
35
|
+
protected resizeObserver: ResizeObserver | null;
|
|
36
|
+
protected layoutManager: LayoutManager;
|
|
37
|
+
protected plotArea: PlotAreaBounds | null;
|
|
38
|
+
protected constructor(config: BaseChartConfig);
|
|
39
|
+
/**
|
|
40
|
+
* Adds a component (axis, grid, tooltip, etc.) to the chart
|
|
41
|
+
*/
|
|
42
|
+
abstract addChild(component: ChartComponent): this;
|
|
43
|
+
/**
|
|
44
|
+
* Renders the chart to the specified target element
|
|
45
|
+
*/
|
|
46
|
+
render(target: string): HTMLElement | null;
|
|
47
|
+
/**
|
|
48
|
+
* Performs the actual rendering logic
|
|
49
|
+
*/
|
|
50
|
+
private performRender;
|
|
51
|
+
/**
|
|
52
|
+
* Get layout-aware components in order
|
|
53
|
+
*/
|
|
54
|
+
private getLayoutComponents;
|
|
55
|
+
/**
|
|
56
|
+
* Setup ResizeObserver for automatic resize handling
|
|
57
|
+
*/
|
|
58
|
+
private setupResizeObserver;
|
|
59
|
+
/**
|
|
60
|
+
* Subclasses must implement this method to define their rendering logic
|
|
61
|
+
*/
|
|
62
|
+
protected abstract renderChart(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Updates the chart with new data
|
|
65
|
+
*/
|
|
66
|
+
update(data: DataItem[]): void;
|
|
67
|
+
/**
|
|
68
|
+
* Destroys the chart and cleans up resources
|
|
69
|
+
*/
|
|
70
|
+
destroy(): void;
|
|
71
|
+
protected parseValue(value: any): number;
|
|
72
|
+
}
|
package/base-chart.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { create } from 'd3';
|
|
2
|
+
import { defaultTheme } from './theme';
|
|
3
|
+
import { ChartValidator } from './validation';
|
|
4
|
+
import { LayoutManager } from './layout-manager';
|
|
5
|
+
/**
|
|
6
|
+
* Base chart class that provides common functionality for all chart types
|
|
7
|
+
*/
|
|
8
|
+
export class BaseChart {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
Object.defineProperty(this, "data", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: void 0
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(this, "theme", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: void 0
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(this, "scaleConfig", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: void 0
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(this, "width", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: void 0
|
|
33
|
+
}); // Current rendering width
|
|
34
|
+
Object.defineProperty(this, "xAxis", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: null
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, "yAxis", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: null
|
|
45
|
+
});
|
|
46
|
+
Object.defineProperty(this, "grid", {
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
writable: true,
|
|
50
|
+
value: null
|
|
51
|
+
});
|
|
52
|
+
Object.defineProperty(this, "tooltip", {
|
|
53
|
+
enumerable: true,
|
|
54
|
+
configurable: true,
|
|
55
|
+
writable: true,
|
|
56
|
+
value: null
|
|
57
|
+
});
|
|
58
|
+
Object.defineProperty(this, "legend", {
|
|
59
|
+
enumerable: true,
|
|
60
|
+
configurable: true,
|
|
61
|
+
writable: true,
|
|
62
|
+
value: null
|
|
63
|
+
});
|
|
64
|
+
Object.defineProperty(this, "title", {
|
|
65
|
+
enumerable: true,
|
|
66
|
+
configurable: true,
|
|
67
|
+
writable: true,
|
|
68
|
+
value: null
|
|
69
|
+
});
|
|
70
|
+
Object.defineProperty(this, "svg", {
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
73
|
+
writable: true,
|
|
74
|
+
value: null
|
|
75
|
+
});
|
|
76
|
+
Object.defineProperty(this, "plotGroup", {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
configurable: true,
|
|
79
|
+
writable: true,
|
|
80
|
+
value: null
|
|
81
|
+
});
|
|
82
|
+
Object.defineProperty(this, "container", {
|
|
83
|
+
enumerable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
writable: true,
|
|
86
|
+
value: null
|
|
87
|
+
});
|
|
88
|
+
Object.defineProperty(this, "x", {
|
|
89
|
+
enumerable: true,
|
|
90
|
+
configurable: true,
|
|
91
|
+
writable: true,
|
|
92
|
+
value: null
|
|
93
|
+
});
|
|
94
|
+
Object.defineProperty(this, "y", {
|
|
95
|
+
enumerable: true,
|
|
96
|
+
configurable: true,
|
|
97
|
+
writable: true,
|
|
98
|
+
value: null
|
|
99
|
+
});
|
|
100
|
+
Object.defineProperty(this, "resizeObserver", {
|
|
101
|
+
enumerable: true,
|
|
102
|
+
configurable: true,
|
|
103
|
+
writable: true,
|
|
104
|
+
value: null
|
|
105
|
+
});
|
|
106
|
+
Object.defineProperty(this, "layoutManager", {
|
|
107
|
+
enumerable: true,
|
|
108
|
+
configurable: true,
|
|
109
|
+
writable: true,
|
|
110
|
+
value: void 0
|
|
111
|
+
});
|
|
112
|
+
Object.defineProperty(this, "plotArea", {
|
|
113
|
+
enumerable: true,
|
|
114
|
+
configurable: true,
|
|
115
|
+
writable: true,
|
|
116
|
+
value: null
|
|
117
|
+
});
|
|
118
|
+
// Validate data
|
|
119
|
+
ChartValidator.validateData(config.data);
|
|
120
|
+
this.data = config.data;
|
|
121
|
+
this.theme = { ...defaultTheme, ...config.theme };
|
|
122
|
+
this.width = this.theme.width;
|
|
123
|
+
this.scaleConfig = config.scales || {};
|
|
124
|
+
this.layoutManager = new LayoutManager(this.theme);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Renders the chart to the specified target element
|
|
128
|
+
*/
|
|
129
|
+
render(target) {
|
|
130
|
+
const container = document.querySelector(target);
|
|
131
|
+
if (!container) {
|
|
132
|
+
throw new Error(`Container "${target}" not found`);
|
|
133
|
+
}
|
|
134
|
+
this.container = container;
|
|
135
|
+
container.innerHTML = '';
|
|
136
|
+
// Perform initial render
|
|
137
|
+
this.performRender();
|
|
138
|
+
// Set up ResizeObserver for automatic resize handling
|
|
139
|
+
this.setupResizeObserver();
|
|
140
|
+
return container;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Performs the actual rendering logic
|
|
144
|
+
*/
|
|
145
|
+
performRender() {
|
|
146
|
+
if (!this.container)
|
|
147
|
+
return;
|
|
148
|
+
// Calculate current width
|
|
149
|
+
this.width = Math.min(this.container.getBoundingClientRect().width || this.theme.width, this.theme.width);
|
|
150
|
+
// Clear and setup SVG
|
|
151
|
+
this.container.innerHTML = '';
|
|
152
|
+
this.svg = create('svg')
|
|
153
|
+
.attr('width', '100%')
|
|
154
|
+
.attr('height', this.theme.height)
|
|
155
|
+
.style('max-width', `${this.theme.width}px`)
|
|
156
|
+
.style('display', 'block');
|
|
157
|
+
this.container.appendChild(this.svg.node());
|
|
158
|
+
// Calculate layout
|
|
159
|
+
const layoutTheme = { ...this.theme, width: this.width };
|
|
160
|
+
this.layoutManager = new LayoutManager(layoutTheme);
|
|
161
|
+
const components = this.getLayoutComponents();
|
|
162
|
+
this.plotArea = this.layoutManager.calculateLayout(components);
|
|
163
|
+
// Create plot group
|
|
164
|
+
this.plotGroup = this.svg.append('g').attr('class', 'chart-plot');
|
|
165
|
+
// Render chart content
|
|
166
|
+
this.renderChart();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get layout-aware components in order
|
|
170
|
+
*/
|
|
171
|
+
getLayoutComponents() {
|
|
172
|
+
const components = [];
|
|
173
|
+
if (this.title)
|
|
174
|
+
components.push(this.title);
|
|
175
|
+
if (this.xAxis)
|
|
176
|
+
components.push(this.xAxis);
|
|
177
|
+
if (this.yAxis)
|
|
178
|
+
components.push(this.yAxis);
|
|
179
|
+
if (this.legend)
|
|
180
|
+
components.push(this.legend);
|
|
181
|
+
return components;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Setup ResizeObserver for automatic resize handling
|
|
185
|
+
*/
|
|
186
|
+
setupResizeObserver() {
|
|
187
|
+
if (!this.container)
|
|
188
|
+
return;
|
|
189
|
+
if (this.resizeObserver) {
|
|
190
|
+
this.resizeObserver.disconnect();
|
|
191
|
+
}
|
|
192
|
+
this.resizeObserver = new ResizeObserver(() => this.performRender());
|
|
193
|
+
this.resizeObserver.observe(this.container);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Updates the chart with new data
|
|
197
|
+
*/
|
|
198
|
+
update(data) {
|
|
199
|
+
ChartValidator.validateData(data);
|
|
200
|
+
this.data = data;
|
|
201
|
+
if (!this.container) {
|
|
202
|
+
throw new Error('Chart must be rendered before update()');
|
|
203
|
+
}
|
|
204
|
+
this.performRender();
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Destroys the chart and cleans up resources
|
|
208
|
+
*/
|
|
209
|
+
destroy() {
|
|
210
|
+
this.tooltip?.cleanup();
|
|
211
|
+
if (this.resizeObserver) {
|
|
212
|
+
this.resizeObserver.disconnect();
|
|
213
|
+
this.resizeObserver = null;
|
|
214
|
+
}
|
|
215
|
+
if (this.container) {
|
|
216
|
+
this.container.innerHTML = '';
|
|
217
|
+
}
|
|
218
|
+
this.svg = null;
|
|
219
|
+
this.plotGroup = null;
|
|
220
|
+
this.plotArea = null;
|
|
221
|
+
this.x = null;
|
|
222
|
+
this.y = null;
|
|
223
|
+
}
|
|
224
|
+
parseValue(value) {
|
|
225
|
+
return typeof value === 'string' ? parseFloat(value) : value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ChartComponent {
|
|
2
|
+
type: 'line' | 'bar' | 'xAxis' | 'yAxis' | 'grid' | 'tooltip' | 'legend' | 'title';
|
|
3
|
+
}
|
|
4
|
+
export type ComponentSpace = {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
position: 'top' | 'bottom' | 'left' | 'right';
|
|
8
|
+
};
|
|
9
|
+
export interface LayoutAwareComponent extends ChartComponent {
|
|
10
|
+
getRequiredSpace(): ComponentSpace;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/grid.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Selection } from 'd3';
|
|
2
|
+
import type { GridConfig, ChartTheme } from './types';
|
|
3
|
+
import type { ChartComponent } from '@/lib/chart-interface';
|
|
4
|
+
export declare class Grid implements ChartComponent {
|
|
5
|
+
readonly type: "grid";
|
|
6
|
+
readonly horizontal: boolean;
|
|
7
|
+
readonly vertical: boolean;
|
|
8
|
+
constructor(config?: GridConfig);
|
|
9
|
+
render(plotGroup: Selection<SVGGElement, undefined, null, undefined>, x: any, y: any, theme: ChartTheme): void;
|
|
10
|
+
}
|
package/grid.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { axisBottom, axisLeft } from 'd3';
|
|
2
|
+
export class Grid {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
Object.defineProperty(this, "type", {
|
|
5
|
+
enumerable: true,
|
|
6
|
+
configurable: true,
|
|
7
|
+
writable: true,
|
|
8
|
+
value: 'grid'
|
|
9
|
+
});
|
|
10
|
+
Object.defineProperty(this, "horizontal", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: void 0
|
|
15
|
+
});
|
|
16
|
+
Object.defineProperty(this, "vertical", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: void 0
|
|
21
|
+
});
|
|
22
|
+
this.horizontal = config?.horizontal ?? true;
|
|
23
|
+
this.vertical = config?.vertical ?? true;
|
|
24
|
+
}
|
|
25
|
+
render(plotGroup, x, y, theme) {
|
|
26
|
+
// Get plot area dimensions from the scale ranges
|
|
27
|
+
const xRange = x.range();
|
|
28
|
+
const yRange = y.range();
|
|
29
|
+
const plotWidth = xRange[1] - xRange[0];
|
|
30
|
+
const plotHeight = yRange[0] - yRange[1];
|
|
31
|
+
if (this.horizontal) {
|
|
32
|
+
plotGroup
|
|
33
|
+
.append('g')
|
|
34
|
+
.attr('class', 'grid-lines horizontal')
|
|
35
|
+
.attr('transform', `translate(${xRange[0]},0)`)
|
|
36
|
+
.call(axisLeft(y)
|
|
37
|
+
.ticks(5)
|
|
38
|
+
.tickSize(-plotWidth)
|
|
39
|
+
.tickFormat(() => ''))
|
|
40
|
+
.call((g) => g
|
|
41
|
+
.selectAll('.tick line')
|
|
42
|
+
.attr('stroke', theme.gridColor)
|
|
43
|
+
.attr('stroke-opacity', 0.5))
|
|
44
|
+
.selectAll('.domain')
|
|
45
|
+
.remove();
|
|
46
|
+
}
|
|
47
|
+
if (this.vertical) {
|
|
48
|
+
plotGroup
|
|
49
|
+
.append('g')
|
|
50
|
+
.attr('class', 'grid-lines vertical')
|
|
51
|
+
.attr('transform', `translate(0,${yRange[0]})`)
|
|
52
|
+
.call(axisBottom(x)
|
|
53
|
+
.tickSize(-plotHeight)
|
|
54
|
+
.tickFormat(() => ''))
|
|
55
|
+
.call((g) => g
|
|
56
|
+
.selectAll('.tick line')
|
|
57
|
+
.attr('stroke', theme.gridColor)
|
|
58
|
+
.attr('stroke-opacity', 0.5))
|
|
59
|
+
.selectAll('.domain')
|
|
60
|
+
.remove();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { LayoutAwareComponent } from './chart-interface';
|
|
2
|
+
import type { ChartTheme } from './types';
|
|
3
|
+
export type PlotAreaBounds = {
|
|
4
|
+
left: number;
|
|
5
|
+
right: number;
|
|
6
|
+
top: number;
|
|
7
|
+
bottom: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
export type ComponentPosition = {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Layout manager that calculates component positions and plot area bounds
|
|
17
|
+
* Follows D3's margin convention pattern
|
|
18
|
+
*/
|
|
19
|
+
export declare class LayoutManager {
|
|
20
|
+
private theme;
|
|
21
|
+
private plotBounds;
|
|
22
|
+
private componentPositions;
|
|
23
|
+
constructor(theme: ChartTheme);
|
|
24
|
+
/**
|
|
25
|
+
* Calculate layout based on registered components
|
|
26
|
+
* Returns the plot area bounds
|
|
27
|
+
*/
|
|
28
|
+
calculateLayout(components: LayoutAwareComponent[]): PlotAreaBounds;
|
|
29
|
+
/**
|
|
30
|
+
* Get the position for a specific component
|
|
31
|
+
*/
|
|
32
|
+
getComponentPosition(component: LayoutAwareComponent): ComponentPosition;
|
|
33
|
+
/**
|
|
34
|
+
* Calculate positions for all components based on their space requirements
|
|
35
|
+
* Components are positioned in registration order, stacking outward from the plot area
|
|
36
|
+
*/
|
|
37
|
+
private calculateComponentPositions;
|
|
38
|
+
}
|