@opendata-ai/openchart-vanilla 6.24.2 → 6.25.0
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/dist/index.js +65 -47
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/renderers/axes.ts +72 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-vanilla",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.25.0",
|
|
4
4
|
"description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Riley Hilliard",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@floating-ui/dom": "^1.7.6",
|
|
53
|
-
"@opendata-ai/openchart-core": "6.
|
|
54
|
-
"@opendata-ai/openchart-engine": "6.
|
|
53
|
+
"@opendata-ai/openchart-core": "6.25.0",
|
|
54
|
+
"@opendata-ai/openchart-engine": "6.25.0",
|
|
55
55
|
"d3-force": "^3.0.0",
|
|
56
56
|
"d3-quadtree": "^3.0.1"
|
|
57
57
|
},
|
package/src/renderers/axes.ts
CHANGED
|
@@ -13,7 +13,8 @@ function renderAxis(
|
|
|
13
13
|
layout: ChartLayout,
|
|
14
14
|
): void {
|
|
15
15
|
const g = createSVGElement('g');
|
|
16
|
-
|
|
16
|
+
const isRight = orientation === 'y' && axis.orient === 'right';
|
|
17
|
+
g.setAttribute('class', `oc-axis oc-axis-${isRight ? 'y2' : orientation}`);
|
|
17
18
|
|
|
18
19
|
const { area } = layout;
|
|
19
20
|
|
|
@@ -66,46 +67,45 @@ function renderAxis(
|
|
|
66
67
|
label.textContent = tick.label;
|
|
67
68
|
g.appendChild(label);
|
|
68
69
|
} else {
|
|
69
|
-
// Label (no tick marks -- gridlines provide sufficient reference)
|
|
70
70
|
const label = createSVGElement('text');
|
|
71
71
|
label.setAttribute('class', 'oc-axis-tick');
|
|
72
72
|
setAttrs(label, {
|
|
73
|
-
x: area.x - 6,
|
|
73
|
+
x: isRight ? area.x + area.width + 6 : area.x - 6,
|
|
74
74
|
y: tick.position,
|
|
75
|
-
'text-anchor': 'end',
|
|
75
|
+
'text-anchor': isRight ? 'start' : 'end',
|
|
76
76
|
'dominant-baseline': 'central',
|
|
77
77
|
});
|
|
78
78
|
applyTextStyle(label, axis.tickLabelStyle);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
hi = mid - 1;
|
|
79
|
+
if (!isRight) {
|
|
80
|
+
// Truncate categorical left y-axis labels that exceed available space
|
|
81
|
+
const availableWidth = area.x - 6;
|
|
82
|
+
const fontSize = axis.tickLabelStyle.fontSize;
|
|
83
|
+
const fontWeight = axis.tickLabelStyle.fontWeight;
|
|
84
|
+
const fullWidth = estimateTextWidth(tick.label, fontSize, fontWeight);
|
|
85
|
+
if (fullWidth > availableWidth && availableWidth > 20) {
|
|
86
|
+
const ellipsis = '…';
|
|
87
|
+
const ellipsisWidth = estimateTextWidth(ellipsis, fontSize, fontWeight);
|
|
88
|
+
let lo = 0;
|
|
89
|
+
let hi = tick.label.length;
|
|
90
|
+
while (lo < hi) {
|
|
91
|
+
const mid = (lo + hi + 1) >>> 1;
|
|
92
|
+
const candidate = tick.label.slice(0, mid);
|
|
93
|
+
if (
|
|
94
|
+
estimateTextWidth(candidate, fontSize, fontWeight) + ellipsisWidth <=
|
|
95
|
+
availableWidth
|
|
96
|
+
) {
|
|
97
|
+
lo = mid;
|
|
98
|
+
} else {
|
|
99
|
+
hi = mid - 1;
|
|
100
|
+
}
|
|
102
101
|
}
|
|
102
|
+
label.textContent = lo > 0 ? tick.label.slice(0, lo).trimEnd() + ellipsis : ellipsis;
|
|
103
|
+
const titleEl = createSVGElement('title');
|
|
104
|
+
titleEl.textContent = tick.label;
|
|
105
|
+
label.appendChild(titleEl);
|
|
106
|
+
} else {
|
|
107
|
+
label.textContent = tick.label;
|
|
103
108
|
}
|
|
104
|
-
label.textContent = lo > 0 ? tick.label.slice(0, lo).trimEnd() + ellipsis : ellipsis;
|
|
105
|
-
// Preserve the full label for accessibility / tooltips
|
|
106
|
-
const titleEl = createSVGElement('title');
|
|
107
|
-
titleEl.textContent = tick.label;
|
|
108
|
-
label.appendChild(titleEl);
|
|
109
109
|
} else {
|
|
110
110
|
label.textContent = tick.label;
|
|
111
111
|
}
|
|
@@ -114,31 +114,34 @@ function renderAxis(
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
// Gridlines (positions are also absolute from the scales)
|
|
117
|
-
for
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
117
|
+
// Skip gridlines for right-side y-axis (left y-axis gridlines are sufficient)
|
|
118
|
+
if (!isRight) {
|
|
119
|
+
for (const gridline of axis.gridlines) {
|
|
120
|
+
const gl = createSVGElement('line');
|
|
121
|
+
gl.setAttribute('class', 'oc-gridline');
|
|
122
|
+
if (orientation === 'y') {
|
|
123
|
+
setAttrs(gl, {
|
|
124
|
+
x1: area.x,
|
|
125
|
+
y1: gridline.position,
|
|
126
|
+
x2: area.x + area.width,
|
|
127
|
+
y2: gridline.position,
|
|
128
|
+
stroke: layout.theme.colors.gridline,
|
|
129
|
+
'stroke-width': 1,
|
|
130
|
+
'stroke-opacity': 0.6,
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
setAttrs(gl, {
|
|
134
|
+
x1: gridline.position,
|
|
135
|
+
y1: area.y,
|
|
136
|
+
x2: gridline.position,
|
|
137
|
+
y2: area.y + area.height,
|
|
138
|
+
stroke: layout.theme.colors.gridline,
|
|
139
|
+
'stroke-width': 1,
|
|
140
|
+
'stroke-opacity': 0.6,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
g.appendChild(gl);
|
|
140
144
|
}
|
|
141
|
-
g.appendChild(gl);
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
// Axis label
|
|
@@ -171,8 +174,17 @@ function renderAxis(
|
|
|
171
174
|
y: titleY,
|
|
172
175
|
'text-anchor': 'middle',
|
|
173
176
|
});
|
|
177
|
+
} else if (isRight) {
|
|
178
|
+
// Rotated right y-axis label
|
|
179
|
+
const titleX = area.x + area.width + 45;
|
|
180
|
+
setAttrs(axisLabel, {
|
|
181
|
+
x: titleX,
|
|
182
|
+
y: area.y + area.height / 2,
|
|
183
|
+
'text-anchor': 'middle',
|
|
184
|
+
transform: `rotate(90, ${titleX}, ${area.y + area.height / 2})`,
|
|
185
|
+
});
|
|
174
186
|
} else {
|
|
175
|
-
// Rotated y-axis label
|
|
187
|
+
// Rotated left y-axis label
|
|
176
188
|
setAttrs(axisLabel, {
|
|
177
189
|
x: area.x - 45,
|
|
178
190
|
y: area.y + area.height / 2,
|
|
@@ -193,4 +205,7 @@ export function renderAxes(parent: SVGElement, layout: ChartLayout): void {
|
|
|
193
205
|
if (layout.axes.y) {
|
|
194
206
|
renderAxis(parent, layout.axes.y, 'y', layout);
|
|
195
207
|
}
|
|
208
|
+
if (layout.axes.y2) {
|
|
209
|
+
renderAxis(parent, layout.axes.y2, 'y', layout);
|
|
210
|
+
}
|
|
196
211
|
}
|