@gravity-ui/charts 1.7.1 → 1.8.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/cjs/components/ChartInner/index.js +1 -1
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +4 -2
- package/dist/cjs/components/Legend/index.d.ts +1 -0
- package/dist/cjs/components/Legend/index.js +104 -50
- package/dist/cjs/components/Legend/styles.css +11 -0
- package/dist/cjs/components/Tooltip/DefaultContent.js +6 -3
- package/dist/cjs/hooks/useSeries/index.d.ts +4 -2
- package/dist/cjs/hooks/useSeries/prepare-legend.d.ts +11 -9
- package/dist/cjs/hooks/useSeries/prepare-legend.js +58 -20
- package/dist/cjs/hooks/useSeries/types.d.ts +5 -2
- package/dist/cjs/types/chart/legend.d.ts +6 -0
- package/dist/esm/components/ChartInner/index.js +1 -1
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +4 -2
- package/dist/esm/components/Legend/index.d.ts +1 -0
- package/dist/esm/components/Legend/index.js +104 -50
- package/dist/esm/components/Legend/styles.css +11 -0
- package/dist/esm/components/Tooltip/DefaultContent.js +6 -3
- package/dist/esm/hooks/useSeries/index.d.ts +4 -2
- package/dist/esm/hooks/useSeries/prepare-legend.d.ts +11 -9
- package/dist/esm/hooks/useSeries/prepare-legend.js +58 -20
- package/dist/esm/hooks/useSeries/types.d.ts +5 -2
- package/dist/esm/types/chart/legend.d.ts +6 -0
- package/package.json +1 -1
|
@@ -71,7 +71,7 @@ export const ChartInner = (props) => {
|
|
|
71
71
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
72
72
|
React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
|
|
73
73
|
shapes),
|
|
74
|
-
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip }))),
|
|
74
|
+
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayerRef.current }))),
|
|
75
75
|
React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
|
|
76
76
|
'--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
|
|
77
77
|
} }),
|
|
@@ -20,8 +20,10 @@ export declare function useChartInnerProps(props: Props): {
|
|
|
20
20
|
top: number;
|
|
21
21
|
};
|
|
22
22
|
pagination: {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
pages: {
|
|
24
|
+
start: number;
|
|
25
|
+
end: number;
|
|
26
|
+
}[];
|
|
25
27
|
} | undefined;
|
|
26
28
|
};
|
|
27
29
|
legendItems: import("../../hooks").LegendItem[][];
|
|
@@ -18,27 +18,28 @@ const getLegendPosition = (args) => {
|
|
|
18
18
|
return { top, left: offsetWidth + width / 2 - contentWidth / 2 };
|
|
19
19
|
};
|
|
20
20
|
const appendPaginator = (args) => {
|
|
21
|
-
const { container,
|
|
21
|
+
const { container, pageIndex, legend, transform, pages, onArrowClick } = args;
|
|
22
22
|
const paginationLine = container.append('g').attr('class', b('pagination'));
|
|
23
|
+
const maxPage = pages.length;
|
|
23
24
|
let computedWidth = 0;
|
|
24
25
|
paginationLine
|
|
25
26
|
.append('text')
|
|
26
27
|
.text('▲')
|
|
27
28
|
.attr('class', function () {
|
|
28
|
-
return b('pagination-arrow', { inactive:
|
|
29
|
+
return b('pagination-arrow', { inactive: pageIndex === 0 });
|
|
29
30
|
})
|
|
30
31
|
.style('font-size', legend.itemStyle.fontSize)
|
|
31
32
|
.each(function () {
|
|
32
33
|
computedWidth += this.getComputedTextLength();
|
|
33
34
|
})
|
|
34
35
|
.on('click', function () {
|
|
35
|
-
if (
|
|
36
|
-
onArrowClick(
|
|
36
|
+
if (pageIndex - 1 >= 0) {
|
|
37
|
+
onArrowClick(pageIndex - 1);
|
|
37
38
|
}
|
|
38
39
|
});
|
|
39
40
|
paginationLine
|
|
40
41
|
.append('text')
|
|
41
|
-
.text(`${
|
|
42
|
+
.text(`${pageIndex + 1}/${maxPage}`)
|
|
42
43
|
.attr('class', b('pagination-counter'))
|
|
43
44
|
.attr('x', computedWidth)
|
|
44
45
|
.style('font-size', legend.itemStyle.fontSize)
|
|
@@ -49,13 +50,13 @@ const appendPaginator = (args) => {
|
|
|
49
50
|
.append('text')
|
|
50
51
|
.text('▼')
|
|
51
52
|
.attr('class', function () {
|
|
52
|
-
return b('pagination-arrow', { inactive:
|
|
53
|
+
return b('pagination-arrow', { inactive: pageIndex === maxPage - 1 });
|
|
53
54
|
})
|
|
54
55
|
.attr('x', computedWidth)
|
|
55
56
|
.style('font-size', legend.itemStyle.fontSize)
|
|
56
57
|
.on('click', function () {
|
|
57
|
-
if (
|
|
58
|
-
onArrowClick(
|
|
58
|
+
if (pageIndex + 1 < maxPage) {
|
|
59
|
+
onArrowClick(pageIndex + 1);
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
62
|
paginationLine.attr('transform', transform);
|
|
@@ -64,7 +65,7 @@ const legendSymbolGenerator = lineGenerator()
|
|
|
64
65
|
.x((d) => d.x)
|
|
65
66
|
.y((d) => d.y);
|
|
66
67
|
function renderLegendSymbol(args) {
|
|
67
|
-
const { selection, legend } = args;
|
|
68
|
+
const { selection, legend, legendLineHeight } = args;
|
|
68
69
|
const line = selection.data();
|
|
69
70
|
const getXPosition = (i) => {
|
|
70
71
|
return line.slice(0, i).reduce((acc, legendItem) => {
|
|
@@ -82,7 +83,7 @@ function renderLegendSymbol(args) {
|
|
|
82
83
|
const color = d.visible ? d.color : '';
|
|
83
84
|
switch (d.symbol.shape) {
|
|
84
85
|
case 'path': {
|
|
85
|
-
const y =
|
|
86
|
+
const y = legendLineHeight / 2;
|
|
86
87
|
const points = [
|
|
87
88
|
{ x: x, y },
|
|
88
89
|
{ x: x + d.symbol.width, y },
|
|
@@ -100,7 +101,7 @@ function renderLegendSymbol(args) {
|
|
|
100
101
|
break;
|
|
101
102
|
}
|
|
102
103
|
case 'rect': {
|
|
103
|
-
const y = (
|
|
104
|
+
const y = (legendLineHeight - d.symbol.height) / 2;
|
|
104
105
|
element
|
|
105
106
|
.append('rect')
|
|
106
107
|
.attr('x', x)
|
|
@@ -113,7 +114,7 @@ function renderLegendSymbol(args) {
|
|
|
113
114
|
break;
|
|
114
115
|
}
|
|
115
116
|
case 'symbol': {
|
|
116
|
-
const y =
|
|
117
|
+
const y = legendLineHeight / 2;
|
|
117
118
|
const translateX = x + d.symbol.width / 2;
|
|
118
119
|
element
|
|
119
120
|
.append('svg:path')
|
|
@@ -134,28 +135,36 @@ function renderLegendSymbol(args) {
|
|
|
134
135
|
});
|
|
135
136
|
}
|
|
136
137
|
export const Legend = (props) => {
|
|
137
|
-
const { boundsWidth, chartSeries, legend, items, config, onItemClick, onUpdate } = props;
|
|
138
|
+
const { boundsWidth, chartSeries, legend, items, config, htmlLayout, onItemClick, onUpdate } = props;
|
|
138
139
|
const ref = React.useRef(null);
|
|
139
|
-
const [
|
|
140
|
+
const [pageIndex, setPageIndex] = React.useState(0);
|
|
140
141
|
React.useEffect(() => {
|
|
141
|
-
|
|
142
|
+
setPageIndex(0);
|
|
142
143
|
}, [boundsWidth]);
|
|
143
144
|
React.useEffect(() => {
|
|
144
|
-
var _a, _b, _c, _d, _e;
|
|
145
|
-
if (!ref.current) {
|
|
145
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
146
|
+
if (!ref.current || !htmlLayout) {
|
|
146
147
|
return;
|
|
147
148
|
}
|
|
148
149
|
const svgElement = select(ref.current);
|
|
149
150
|
svgElement.selectAll('*').remove();
|
|
151
|
+
const htmlElement = select(htmlLayout);
|
|
152
|
+
htmlElement.selectAll('[data-legend]').remove();
|
|
153
|
+
const htmlContainer = legend.html
|
|
154
|
+
? htmlElement.append('div').attr('data-legend', 1).style('position', 'absolute')
|
|
155
|
+
: null;
|
|
150
156
|
let legendWidth = 0;
|
|
151
157
|
if (legend.type === 'discrete') {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
158
|
+
const start = (_b = (_a = config.pagination) === null || _a === void 0 ? void 0 : _a.pages[pageIndex]) === null || _b === void 0 ? void 0 : _b.start;
|
|
159
|
+
const end = (_d = (_c = config.pagination) === null || _c === void 0 ? void 0 : _c.pages[pageIndex]) === null || _d === void 0 ? void 0 : _d.end;
|
|
160
|
+
const pageItems = typeof start === 'number' && typeof end === 'number'
|
|
161
|
+
? items.slice(start, end)
|
|
155
162
|
: items;
|
|
156
|
-
|
|
163
|
+
const legendLineHeights = [];
|
|
164
|
+
pageItems.forEach((line) => {
|
|
157
165
|
var _a;
|
|
158
166
|
const legendLine = svgElement.append('g').attr('class', b('line'));
|
|
167
|
+
const htmlLegendLine = htmlContainer === null || htmlContainer === void 0 ? void 0 : htmlContainer.append('div').style('position', 'absolute');
|
|
159
168
|
const legendItemTemplate = legendLine
|
|
160
169
|
.selectAll('legend-history')
|
|
161
170
|
.data(line)
|
|
@@ -175,22 +184,57 @@ export const Legend = (props) => {
|
|
|
175
184
|
legend.itemDistance);
|
|
176
185
|
}, 0);
|
|
177
186
|
};
|
|
178
|
-
|
|
179
|
-
legendItemTemplate
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
const legendLineHeight = Math.max(...line.map((l) => l.height));
|
|
188
|
+
renderLegendSymbol({ selection: legendItemTemplate, legend, legendLineHeight });
|
|
189
|
+
if (htmlLegendLine) {
|
|
190
|
+
htmlLegendLine
|
|
191
|
+
.selectAll('legend-item')
|
|
192
|
+
.data(line)
|
|
193
|
+
.enter()
|
|
194
|
+
.append('div')
|
|
195
|
+
.attr('class', function (d) {
|
|
196
|
+
const mods = { selected: d.visible, unselected: !d.visible };
|
|
197
|
+
return b('item-text-html', mods);
|
|
198
|
+
})
|
|
199
|
+
.style('font-size', legend.itemStyle.fontSize)
|
|
200
|
+
.style('position', 'absolute')
|
|
201
|
+
.style('left', function (d, i) {
|
|
202
|
+
return `${getXPosition(i) + d.symbol.width + d.symbol.padding}px`;
|
|
203
|
+
})
|
|
204
|
+
.style('top', function (d) {
|
|
205
|
+
if (d.height < legendLineHeight) {
|
|
206
|
+
return `${(legendLineHeight - d.height) / 2}px`;
|
|
207
|
+
}
|
|
208
|
+
return '0px';
|
|
209
|
+
})
|
|
210
|
+
.on('click', function (e, d) {
|
|
211
|
+
onItemClick({ name: d.name, metaKey: e.metaKey });
|
|
212
|
+
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate();
|
|
213
|
+
})[legend.html ? 'html' : 'text'](function (d) {
|
|
214
|
+
return d.name;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
legendItemTemplate
|
|
219
|
+
.append('text')
|
|
220
|
+
.attr('x', function (legendItem, i) {
|
|
221
|
+
return (getXPosition(i) +
|
|
222
|
+
legendItem.symbol.width +
|
|
223
|
+
legendItem.symbol.padding);
|
|
224
|
+
})
|
|
225
|
+
.attr('height', legend.height)
|
|
226
|
+
.attr('class', function (d) {
|
|
227
|
+
const mods = { selected: d.visible, unselected: !d.visible };
|
|
228
|
+
return b('item-text', mods);
|
|
229
|
+
})
|
|
230
|
+
.html(function (d) {
|
|
231
|
+
return ('name' in d && d.name);
|
|
232
|
+
})
|
|
233
|
+
.style('font-size', legend.itemStyle.fontSize);
|
|
234
|
+
}
|
|
235
|
+
const contentWidth = (legend.html
|
|
236
|
+
? getXPosition(line.length) - legend.itemDistance
|
|
237
|
+
: (_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
|
|
194
238
|
let left = 0;
|
|
195
239
|
switch (legend.justifyContent) {
|
|
196
240
|
case 'center': {
|
|
@@ -209,27 +253,26 @@ export const Legend = (props) => {
|
|
|
209
253
|
break;
|
|
210
254
|
}
|
|
211
255
|
}
|
|
212
|
-
const top =
|
|
256
|
+
const top = legendLineHeights.reduce((acc, h) => acc + h, 0);
|
|
257
|
+
legendLineHeights.push(legendLineHeight);
|
|
213
258
|
legendLine.attr('transform', `translate(${[left, top].join(',')})`);
|
|
259
|
+
htmlLegendLine === null || htmlLegendLine === void 0 ? void 0 : htmlLegendLine.style('transform', `translate(${left}px, ${top}px)`);
|
|
214
260
|
});
|
|
215
261
|
if (config.pagination) {
|
|
216
|
-
const transform = `translate(${[
|
|
217
|
-
0,
|
|
218
|
-
legend.lineHeight * config.pagination.limit + legend.lineHeight / 2,
|
|
219
|
-
].join(',')})`;
|
|
262
|
+
const transform = `translate(${[0, legend.height - legend.lineHeight / 2].join(',')})`;
|
|
220
263
|
appendPaginator({
|
|
221
264
|
container: svgElement,
|
|
222
|
-
|
|
223
|
-
maxPage: config.pagination.maxPage,
|
|
265
|
+
pageIndex: pageIndex,
|
|
224
266
|
legend,
|
|
225
267
|
transform,
|
|
226
|
-
|
|
268
|
+
pages: config.pagination.pages,
|
|
269
|
+
onArrowClick: setPageIndex,
|
|
227
270
|
});
|
|
228
271
|
}
|
|
229
272
|
}
|
|
230
273
|
else {
|
|
231
274
|
// gradient rect
|
|
232
|
-
const domain = (
|
|
275
|
+
const domain = (_e = legend.colorScale.domain) !== null && _e !== void 0 ? _e : [];
|
|
233
276
|
const rectHeight = CONTINUOUS_LEGEND_SIZE.height;
|
|
234
277
|
svgElement.call(createGradientRect, {
|
|
235
278
|
y: legend.title.height + legend.title.margin,
|
|
@@ -291,9 +334,9 @@ export const Legend = (props) => {
|
|
|
291
334
|
.attr('class', b('title'))
|
|
292
335
|
.append('text')
|
|
293
336
|
.attr('dx', dx)
|
|
294
|
-
.attr('font-weight', (
|
|
295
|
-
.attr('font-size', (
|
|
296
|
-
.attr('fill', (
|
|
337
|
+
.attr('font-weight', (_f = legend.title.style.fontWeight) !== null && _f !== void 0 ? _f : null)
|
|
338
|
+
.attr('font-size', (_g = legend.title.style.fontSize) !== null && _g !== void 0 ? _g : null)
|
|
339
|
+
.attr('fill', (_h = legend.title.style.fontColor) !== null && _h !== void 0 ? _h : null)
|
|
297
340
|
.style('dominant-baseline', 'text-before-edge')
|
|
298
341
|
.html(legend.title.text);
|
|
299
342
|
}
|
|
@@ -304,6 +347,17 @@ export const Legend = (props) => {
|
|
|
304
347
|
contentWidth: legendWidth,
|
|
305
348
|
});
|
|
306
349
|
svgElement.attr('transform', `translate(${[left, config.offset.top].join(',')})`);
|
|
307
|
-
|
|
350
|
+
htmlContainer === null || htmlContainer === void 0 ? void 0 : htmlContainer.style('transform', `translate(${left}px, ${config.offset.top}px)`);
|
|
351
|
+
}, [
|
|
352
|
+
boundsWidth,
|
|
353
|
+
chartSeries,
|
|
354
|
+
onItemClick,
|
|
355
|
+
onUpdate,
|
|
356
|
+
legend,
|
|
357
|
+
items,
|
|
358
|
+
config,
|
|
359
|
+
pageIndex,
|
|
360
|
+
htmlLayout,
|
|
361
|
+
]);
|
|
308
362
|
return React.createElement("g", { className: b(), ref: ref, width: boundsWidth, height: legend.height });
|
|
309
363
|
};
|
|
@@ -27,6 +27,17 @@
|
|
|
27
27
|
.gcharts-legend__item-text:hover {
|
|
28
28
|
fill: var(--g-color-text-complementary);
|
|
29
29
|
}
|
|
30
|
+
.gcharts-legend__item-text-html {
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
user-select: none;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
.gcharts-legend__item-text-html_unselected {
|
|
36
|
+
color: var(--g-color-text-hint);
|
|
37
|
+
}
|
|
38
|
+
.gcharts-legend__item-text-html:hover {
|
|
39
|
+
color: var(--g-color-text-complementary);
|
|
40
|
+
}
|
|
30
41
|
.gcharts-legend__pagination {
|
|
31
42
|
user-select: none;
|
|
32
43
|
fill: var(--g-color-text-primary);
|
|
@@ -135,9 +135,12 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
135
135
|
});
|
|
136
136
|
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
137
137
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
138
|
-
React.createElement("span",
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
React.createElement("span", { dangerouslySetInnerHTML: {
|
|
139
|
+
__html: [seriesData.name || seriesData.id]
|
|
140
|
+
.flat()
|
|
141
|
+
.join('\n'),
|
|
142
|
+
} }),
|
|
143
|
+
"\u00A0",
|
|
141
144
|
React.createElement("span", null, formattedValue)));
|
|
142
145
|
}
|
|
143
146
|
case 'sankey': {
|
|
@@ -18,8 +18,10 @@ export declare const useSeries: (args: Args) => {
|
|
|
18
18
|
top: number;
|
|
19
19
|
};
|
|
20
20
|
pagination: {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
pages: {
|
|
22
|
+
start: number;
|
|
23
|
+
end: number;
|
|
24
|
+
}[];
|
|
23
25
|
} | undefined;
|
|
24
26
|
};
|
|
25
27
|
preparedLegend: import("./types").PreparedLegend;
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import type { ChartData } from '../../types';
|
|
2
2
|
import type { PreparedAxis, PreparedChart } from '../useChartOptions/types';
|
|
3
3
|
import type { LegendItem, PreparedLegend, PreparedSeries } from './types';
|
|
4
|
-
export declare
|
|
5
|
-
legend: ChartData[
|
|
6
|
-
series: ChartData[
|
|
7
|
-
})
|
|
8
|
-
export declare
|
|
4
|
+
export declare function getPreparedLegend(args: {
|
|
5
|
+
legend: ChartData['legend'];
|
|
6
|
+
series: ChartData['series']['data'];
|
|
7
|
+
}): PreparedLegend;
|
|
8
|
+
export declare function getLegendComponents(args: {
|
|
9
9
|
chartWidth: number;
|
|
10
10
|
chartHeight: number;
|
|
11
|
-
chartMargin: PreparedChart[
|
|
11
|
+
chartMargin: PreparedChart['margin'];
|
|
12
12
|
series: PreparedSeries[];
|
|
13
13
|
preparedLegend: PreparedLegend;
|
|
14
14
|
preparedYAxis: PreparedAxis[];
|
|
15
|
-
})
|
|
15
|
+
}): {
|
|
16
16
|
legendConfig: {
|
|
17
17
|
offset: {
|
|
18
18
|
left: number;
|
|
19
19
|
top: number;
|
|
20
20
|
};
|
|
21
21
|
pagination: {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
pages: {
|
|
23
|
+
start: number;
|
|
24
|
+
end: number;
|
|
25
|
+
}[];
|
|
24
26
|
} | undefined;
|
|
25
27
|
};
|
|
26
28
|
legendItems: LegendItem[][];
|
|
@@ -6,7 +6,7 @@ import { CONTINUOUS_LEGEND_SIZE, legendDefaults } from '../../constants';
|
|
|
6
6
|
import { getDefaultColorStops, getDomainForContinuousColorScale, getHorisontalSvgTextHeight, getLabelsSize, } from '../../utils';
|
|
7
7
|
import { getBoundsWidth } from '../useChartDimensions';
|
|
8
8
|
import { getYAxisWidth } from '../useChartDimensions/utils';
|
|
9
|
-
export
|
|
9
|
+
export function getPreparedLegend(args) {
|
|
10
10
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
11
11
|
const { legend, series } = args;
|
|
12
12
|
const enabled = Boolean(typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : series.length > 1);
|
|
@@ -69,30 +69,38 @@ export const getPreparedLegend = (args) => {
|
|
|
69
69
|
width: legendWidth,
|
|
70
70
|
ticks,
|
|
71
71
|
colorScale,
|
|
72
|
+
html: get(legend, 'html', false),
|
|
72
73
|
};
|
|
73
|
-
}
|
|
74
|
-
|
|
74
|
+
}
|
|
75
|
+
function getFlattenLegendItems(series, preparedLegend) {
|
|
75
76
|
return series.reduce((acc, s) => {
|
|
76
77
|
const legendEnabled = get(s, 'legend.enabled', true);
|
|
77
78
|
if (legendEnabled) {
|
|
78
|
-
acc.push(Object.assign(Object.assign({}, s), { symbol: s.legend.symbol }));
|
|
79
|
+
acc.push(Object.assign(Object.assign({}, s), { height: preparedLegend.lineHeight, symbol: s.legend.symbol }));
|
|
79
80
|
}
|
|
80
81
|
return acc;
|
|
81
82
|
}, []);
|
|
82
|
-
}
|
|
83
|
-
|
|
83
|
+
}
|
|
84
|
+
function getGroupedLegendItems(args) {
|
|
84
85
|
const { maxLegendWidth, items, preparedLegend } = args;
|
|
85
86
|
const result = [[]];
|
|
87
|
+
const bodySelection = select(document.body);
|
|
86
88
|
let textWidthsInLine = [0];
|
|
87
89
|
let lineIndex = 0;
|
|
88
90
|
items.forEach((item) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const itemSelection = preparedLegend.html
|
|
92
|
+
? bodySelection
|
|
93
|
+
.append('div')
|
|
94
|
+
.html(item.name)
|
|
95
|
+
.style('position', 'absolute')
|
|
96
|
+
.style('display', 'inline-block')
|
|
97
|
+
: bodySelection.append('text').text(item.name);
|
|
98
|
+
itemSelection
|
|
92
99
|
.style('font-size', preparedLegend.itemStyle.fontSize)
|
|
93
100
|
.each(function () {
|
|
94
101
|
const resultItem = clone(item);
|
|
95
|
-
const textWidth = this.getBoundingClientRect()
|
|
102
|
+
const { height, width: textWidth } = this.getBoundingClientRect();
|
|
103
|
+
resultItem.height = height;
|
|
96
104
|
resultItem.textWidth = textWidth;
|
|
97
105
|
textWidthsInLine.push(textWidth);
|
|
98
106
|
const textsWidth = textWidthsInLine.reduce((acc, width) => acc + width, 0);
|
|
@@ -114,12 +122,36 @@ const getGroupedLegendItems = (args) => {
|
|
|
114
122
|
.remove();
|
|
115
123
|
});
|
|
116
124
|
return result;
|
|
117
|
-
}
|
|
118
|
-
|
|
125
|
+
}
|
|
126
|
+
function getPagination(args) {
|
|
127
|
+
const { items, maxLegendHeight, paginatorHeight } = args;
|
|
128
|
+
const pages = [];
|
|
129
|
+
let currentPageIndex = 0;
|
|
130
|
+
let currentHeight = 0;
|
|
131
|
+
items.forEach((item, i) => {
|
|
132
|
+
if (!pages[currentPageIndex]) {
|
|
133
|
+
pages[currentPageIndex] = { start: i, end: i };
|
|
134
|
+
}
|
|
135
|
+
const legendLineHeight = Math.max(...item.map((item) => item.height));
|
|
136
|
+
currentHeight += legendLineHeight;
|
|
137
|
+
if (currentHeight > maxLegendHeight - paginatorHeight) {
|
|
138
|
+
pages[currentPageIndex].end = i;
|
|
139
|
+
currentPageIndex += 1;
|
|
140
|
+
currentHeight = legendLineHeight;
|
|
141
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#end
|
|
142
|
+
pages[currentPageIndex] = { start: i, end: i + (i === items.length - 1 ? 1 : 0) };
|
|
143
|
+
}
|
|
144
|
+
else if (i === items.length - 1) {
|
|
145
|
+
pages[currentPageIndex].end = i + 1;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return { pages };
|
|
149
|
+
}
|
|
150
|
+
export function getLegendComponents(args) {
|
|
119
151
|
const { chartWidth, chartHeight, chartMargin, series, preparedLegend, preparedYAxis } = args;
|
|
120
152
|
const maxLegendWidth = getBoundsWidth({ chartWidth, chartMargin, preparedYAxis });
|
|
121
153
|
const maxLegendHeight = (chartHeight - chartMargin.top - chartMargin.bottom - preparedLegend.margin) / 2;
|
|
122
|
-
const flattenLegendItems = getFlattenLegendItems(series);
|
|
154
|
+
const flattenLegendItems = getFlattenLegendItems(series, preparedLegend);
|
|
123
155
|
const items = getGroupedLegendItems({
|
|
124
156
|
maxLegendWidth,
|
|
125
157
|
items: flattenLegendItems,
|
|
@@ -127,13 +159,19 @@ export const getLegendComponents = (args) => {
|
|
|
127
159
|
});
|
|
128
160
|
let pagination;
|
|
129
161
|
if (preparedLegend.type === 'discrete') {
|
|
130
|
-
|
|
162
|
+
const lineHeights = items.reduce((acc, item) => {
|
|
163
|
+
acc.push(Math.max(...item.map((item) => item.height)));
|
|
164
|
+
return acc;
|
|
165
|
+
}, []);
|
|
166
|
+
let legendHeight = lineHeights.reduce((acc, height) => acc + height, 0);
|
|
131
167
|
if (maxLegendHeight < legendHeight) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
168
|
+
const lines = Math.floor(maxLegendHeight / preparedLegend.lineHeight);
|
|
169
|
+
legendHeight = preparedLegend.lineHeight * lines;
|
|
170
|
+
pagination = getPagination({
|
|
171
|
+
items,
|
|
172
|
+
maxLegendHeight: legendHeight,
|
|
173
|
+
paginatorHeight: preparedLegend.lineHeight,
|
|
174
|
+
});
|
|
137
175
|
}
|
|
138
176
|
preparedLegend.height = legendHeight;
|
|
139
177
|
}
|
|
@@ -143,4 +181,4 @@ export const getLegendComponents = (args) => {
|
|
|
143
181
|
top,
|
|
144
182
|
};
|
|
145
183
|
return { legendConfig: { offset, pagination }, legendItems: items };
|
|
146
|
-
}
|
|
184
|
+
}
|
|
@@ -39,6 +39,7 @@ export type OnLegendItemClick = (data: {
|
|
|
39
39
|
}) => void;
|
|
40
40
|
export type LegendItem = {
|
|
41
41
|
color: string;
|
|
42
|
+
height: number;
|
|
42
43
|
name: string;
|
|
43
44
|
symbol: PreparedLegendSymbol;
|
|
44
45
|
textWidth: number;
|
|
@@ -51,8 +52,10 @@ export type LegendConfig = {
|
|
|
51
52
|
top: number;
|
|
52
53
|
};
|
|
53
54
|
pagination?: {
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
pages: {
|
|
56
|
+
start: number;
|
|
57
|
+
end: number;
|
|
58
|
+
}[];
|
|
56
59
|
};
|
|
57
60
|
};
|
|
58
61
|
export type PreparedHaloOptions = {
|
|
@@ -71,7 +71,7 @@ export const ChartInner = (props) => {
|
|
|
71
71
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
72
72
|
React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
|
|
73
73
|
shapes),
|
|
74
|
-
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip }))),
|
|
74
|
+
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayerRef.current }))),
|
|
75
75
|
React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
|
|
76
76
|
'--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
|
|
77
77
|
} }),
|
|
@@ -20,8 +20,10 @@ export declare function useChartInnerProps(props: Props): {
|
|
|
20
20
|
top: number;
|
|
21
21
|
};
|
|
22
22
|
pagination: {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
pages: {
|
|
24
|
+
start: number;
|
|
25
|
+
end: number;
|
|
26
|
+
}[];
|
|
25
27
|
} | undefined;
|
|
26
28
|
};
|
|
27
29
|
legendItems: import("../../hooks").LegendItem[][];
|
|
@@ -18,27 +18,28 @@ const getLegendPosition = (args) => {
|
|
|
18
18
|
return { top, left: offsetWidth + width / 2 - contentWidth / 2 };
|
|
19
19
|
};
|
|
20
20
|
const appendPaginator = (args) => {
|
|
21
|
-
const { container,
|
|
21
|
+
const { container, pageIndex, legend, transform, pages, onArrowClick } = args;
|
|
22
22
|
const paginationLine = container.append('g').attr('class', b('pagination'));
|
|
23
|
+
const maxPage = pages.length;
|
|
23
24
|
let computedWidth = 0;
|
|
24
25
|
paginationLine
|
|
25
26
|
.append('text')
|
|
26
27
|
.text('▲')
|
|
27
28
|
.attr('class', function () {
|
|
28
|
-
return b('pagination-arrow', { inactive:
|
|
29
|
+
return b('pagination-arrow', { inactive: pageIndex === 0 });
|
|
29
30
|
})
|
|
30
31
|
.style('font-size', legend.itemStyle.fontSize)
|
|
31
32
|
.each(function () {
|
|
32
33
|
computedWidth += this.getComputedTextLength();
|
|
33
34
|
})
|
|
34
35
|
.on('click', function () {
|
|
35
|
-
if (
|
|
36
|
-
onArrowClick(
|
|
36
|
+
if (pageIndex - 1 >= 0) {
|
|
37
|
+
onArrowClick(pageIndex - 1);
|
|
37
38
|
}
|
|
38
39
|
});
|
|
39
40
|
paginationLine
|
|
40
41
|
.append('text')
|
|
41
|
-
.text(`${
|
|
42
|
+
.text(`${pageIndex + 1}/${maxPage}`)
|
|
42
43
|
.attr('class', b('pagination-counter'))
|
|
43
44
|
.attr('x', computedWidth)
|
|
44
45
|
.style('font-size', legend.itemStyle.fontSize)
|
|
@@ -49,13 +50,13 @@ const appendPaginator = (args) => {
|
|
|
49
50
|
.append('text')
|
|
50
51
|
.text('▼')
|
|
51
52
|
.attr('class', function () {
|
|
52
|
-
return b('pagination-arrow', { inactive:
|
|
53
|
+
return b('pagination-arrow', { inactive: pageIndex === maxPage - 1 });
|
|
53
54
|
})
|
|
54
55
|
.attr('x', computedWidth)
|
|
55
56
|
.style('font-size', legend.itemStyle.fontSize)
|
|
56
57
|
.on('click', function () {
|
|
57
|
-
if (
|
|
58
|
-
onArrowClick(
|
|
58
|
+
if (pageIndex + 1 < maxPage) {
|
|
59
|
+
onArrowClick(pageIndex + 1);
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
62
|
paginationLine.attr('transform', transform);
|
|
@@ -64,7 +65,7 @@ const legendSymbolGenerator = lineGenerator()
|
|
|
64
65
|
.x((d) => d.x)
|
|
65
66
|
.y((d) => d.y);
|
|
66
67
|
function renderLegendSymbol(args) {
|
|
67
|
-
const { selection, legend } = args;
|
|
68
|
+
const { selection, legend, legendLineHeight } = args;
|
|
68
69
|
const line = selection.data();
|
|
69
70
|
const getXPosition = (i) => {
|
|
70
71
|
return line.slice(0, i).reduce((acc, legendItem) => {
|
|
@@ -82,7 +83,7 @@ function renderLegendSymbol(args) {
|
|
|
82
83
|
const color = d.visible ? d.color : '';
|
|
83
84
|
switch (d.symbol.shape) {
|
|
84
85
|
case 'path': {
|
|
85
|
-
const y =
|
|
86
|
+
const y = legendLineHeight / 2;
|
|
86
87
|
const points = [
|
|
87
88
|
{ x: x, y },
|
|
88
89
|
{ x: x + d.symbol.width, y },
|
|
@@ -100,7 +101,7 @@ function renderLegendSymbol(args) {
|
|
|
100
101
|
break;
|
|
101
102
|
}
|
|
102
103
|
case 'rect': {
|
|
103
|
-
const y = (
|
|
104
|
+
const y = (legendLineHeight - d.symbol.height) / 2;
|
|
104
105
|
element
|
|
105
106
|
.append('rect')
|
|
106
107
|
.attr('x', x)
|
|
@@ -113,7 +114,7 @@ function renderLegendSymbol(args) {
|
|
|
113
114
|
break;
|
|
114
115
|
}
|
|
115
116
|
case 'symbol': {
|
|
116
|
-
const y =
|
|
117
|
+
const y = legendLineHeight / 2;
|
|
117
118
|
const translateX = x + d.symbol.width / 2;
|
|
118
119
|
element
|
|
119
120
|
.append('svg:path')
|
|
@@ -134,28 +135,36 @@ function renderLegendSymbol(args) {
|
|
|
134
135
|
});
|
|
135
136
|
}
|
|
136
137
|
export const Legend = (props) => {
|
|
137
|
-
const { boundsWidth, chartSeries, legend, items, config, onItemClick, onUpdate } = props;
|
|
138
|
+
const { boundsWidth, chartSeries, legend, items, config, htmlLayout, onItemClick, onUpdate } = props;
|
|
138
139
|
const ref = React.useRef(null);
|
|
139
|
-
const [
|
|
140
|
+
const [pageIndex, setPageIndex] = React.useState(0);
|
|
140
141
|
React.useEffect(() => {
|
|
141
|
-
|
|
142
|
+
setPageIndex(0);
|
|
142
143
|
}, [boundsWidth]);
|
|
143
144
|
React.useEffect(() => {
|
|
144
|
-
var _a, _b, _c, _d, _e;
|
|
145
|
-
if (!ref.current) {
|
|
145
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
146
|
+
if (!ref.current || !htmlLayout) {
|
|
146
147
|
return;
|
|
147
148
|
}
|
|
148
149
|
const svgElement = select(ref.current);
|
|
149
150
|
svgElement.selectAll('*').remove();
|
|
151
|
+
const htmlElement = select(htmlLayout);
|
|
152
|
+
htmlElement.selectAll('[data-legend]').remove();
|
|
153
|
+
const htmlContainer = legend.html
|
|
154
|
+
? htmlElement.append('div').attr('data-legend', 1).style('position', 'absolute')
|
|
155
|
+
: null;
|
|
150
156
|
let legendWidth = 0;
|
|
151
157
|
if (legend.type === 'discrete') {
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
158
|
+
const start = (_b = (_a = config.pagination) === null || _a === void 0 ? void 0 : _a.pages[pageIndex]) === null || _b === void 0 ? void 0 : _b.start;
|
|
159
|
+
const end = (_d = (_c = config.pagination) === null || _c === void 0 ? void 0 : _c.pages[pageIndex]) === null || _d === void 0 ? void 0 : _d.end;
|
|
160
|
+
const pageItems = typeof start === 'number' && typeof end === 'number'
|
|
161
|
+
? items.slice(start, end)
|
|
155
162
|
: items;
|
|
156
|
-
|
|
163
|
+
const legendLineHeights = [];
|
|
164
|
+
pageItems.forEach((line) => {
|
|
157
165
|
var _a;
|
|
158
166
|
const legendLine = svgElement.append('g').attr('class', b('line'));
|
|
167
|
+
const htmlLegendLine = htmlContainer === null || htmlContainer === void 0 ? void 0 : htmlContainer.append('div').style('position', 'absolute');
|
|
159
168
|
const legendItemTemplate = legendLine
|
|
160
169
|
.selectAll('legend-history')
|
|
161
170
|
.data(line)
|
|
@@ -175,22 +184,57 @@ export const Legend = (props) => {
|
|
|
175
184
|
legend.itemDistance);
|
|
176
185
|
}, 0);
|
|
177
186
|
};
|
|
178
|
-
|
|
179
|
-
legendItemTemplate
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
const legendLineHeight = Math.max(...line.map((l) => l.height));
|
|
188
|
+
renderLegendSymbol({ selection: legendItemTemplate, legend, legendLineHeight });
|
|
189
|
+
if (htmlLegendLine) {
|
|
190
|
+
htmlLegendLine
|
|
191
|
+
.selectAll('legend-item')
|
|
192
|
+
.data(line)
|
|
193
|
+
.enter()
|
|
194
|
+
.append('div')
|
|
195
|
+
.attr('class', function (d) {
|
|
196
|
+
const mods = { selected: d.visible, unselected: !d.visible };
|
|
197
|
+
return b('item-text-html', mods);
|
|
198
|
+
})
|
|
199
|
+
.style('font-size', legend.itemStyle.fontSize)
|
|
200
|
+
.style('position', 'absolute')
|
|
201
|
+
.style('left', function (d, i) {
|
|
202
|
+
return `${getXPosition(i) + d.symbol.width + d.symbol.padding}px`;
|
|
203
|
+
})
|
|
204
|
+
.style('top', function (d) {
|
|
205
|
+
if (d.height < legendLineHeight) {
|
|
206
|
+
return `${(legendLineHeight - d.height) / 2}px`;
|
|
207
|
+
}
|
|
208
|
+
return '0px';
|
|
209
|
+
})
|
|
210
|
+
.on('click', function (e, d) {
|
|
211
|
+
onItemClick({ name: d.name, metaKey: e.metaKey });
|
|
212
|
+
onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate();
|
|
213
|
+
})[legend.html ? 'html' : 'text'](function (d) {
|
|
214
|
+
return d.name;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
legendItemTemplate
|
|
219
|
+
.append('text')
|
|
220
|
+
.attr('x', function (legendItem, i) {
|
|
221
|
+
return (getXPosition(i) +
|
|
222
|
+
legendItem.symbol.width +
|
|
223
|
+
legendItem.symbol.padding);
|
|
224
|
+
})
|
|
225
|
+
.attr('height', legend.height)
|
|
226
|
+
.attr('class', function (d) {
|
|
227
|
+
const mods = { selected: d.visible, unselected: !d.visible };
|
|
228
|
+
return b('item-text', mods);
|
|
229
|
+
})
|
|
230
|
+
.html(function (d) {
|
|
231
|
+
return ('name' in d && d.name);
|
|
232
|
+
})
|
|
233
|
+
.style('font-size', legend.itemStyle.fontSize);
|
|
234
|
+
}
|
|
235
|
+
const contentWidth = (legend.html
|
|
236
|
+
? getXPosition(line.length) - legend.itemDistance
|
|
237
|
+
: (_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
|
|
194
238
|
let left = 0;
|
|
195
239
|
switch (legend.justifyContent) {
|
|
196
240
|
case 'center': {
|
|
@@ -209,27 +253,26 @@ export const Legend = (props) => {
|
|
|
209
253
|
break;
|
|
210
254
|
}
|
|
211
255
|
}
|
|
212
|
-
const top =
|
|
256
|
+
const top = legendLineHeights.reduce((acc, h) => acc + h, 0);
|
|
257
|
+
legendLineHeights.push(legendLineHeight);
|
|
213
258
|
legendLine.attr('transform', `translate(${[left, top].join(',')})`);
|
|
259
|
+
htmlLegendLine === null || htmlLegendLine === void 0 ? void 0 : htmlLegendLine.style('transform', `translate(${left}px, ${top}px)`);
|
|
214
260
|
});
|
|
215
261
|
if (config.pagination) {
|
|
216
|
-
const transform = `translate(${[
|
|
217
|
-
0,
|
|
218
|
-
legend.lineHeight * config.pagination.limit + legend.lineHeight / 2,
|
|
219
|
-
].join(',')})`;
|
|
262
|
+
const transform = `translate(${[0, legend.height - legend.lineHeight / 2].join(',')})`;
|
|
220
263
|
appendPaginator({
|
|
221
264
|
container: svgElement,
|
|
222
|
-
|
|
223
|
-
maxPage: config.pagination.maxPage,
|
|
265
|
+
pageIndex: pageIndex,
|
|
224
266
|
legend,
|
|
225
267
|
transform,
|
|
226
|
-
|
|
268
|
+
pages: config.pagination.pages,
|
|
269
|
+
onArrowClick: setPageIndex,
|
|
227
270
|
});
|
|
228
271
|
}
|
|
229
272
|
}
|
|
230
273
|
else {
|
|
231
274
|
// gradient rect
|
|
232
|
-
const domain = (
|
|
275
|
+
const domain = (_e = legend.colorScale.domain) !== null && _e !== void 0 ? _e : [];
|
|
233
276
|
const rectHeight = CONTINUOUS_LEGEND_SIZE.height;
|
|
234
277
|
svgElement.call(createGradientRect, {
|
|
235
278
|
y: legend.title.height + legend.title.margin,
|
|
@@ -291,9 +334,9 @@ export const Legend = (props) => {
|
|
|
291
334
|
.attr('class', b('title'))
|
|
292
335
|
.append('text')
|
|
293
336
|
.attr('dx', dx)
|
|
294
|
-
.attr('font-weight', (
|
|
295
|
-
.attr('font-size', (
|
|
296
|
-
.attr('fill', (
|
|
337
|
+
.attr('font-weight', (_f = legend.title.style.fontWeight) !== null && _f !== void 0 ? _f : null)
|
|
338
|
+
.attr('font-size', (_g = legend.title.style.fontSize) !== null && _g !== void 0 ? _g : null)
|
|
339
|
+
.attr('fill', (_h = legend.title.style.fontColor) !== null && _h !== void 0 ? _h : null)
|
|
297
340
|
.style('dominant-baseline', 'text-before-edge')
|
|
298
341
|
.html(legend.title.text);
|
|
299
342
|
}
|
|
@@ -304,6 +347,17 @@ export const Legend = (props) => {
|
|
|
304
347
|
contentWidth: legendWidth,
|
|
305
348
|
});
|
|
306
349
|
svgElement.attr('transform', `translate(${[left, config.offset.top].join(',')})`);
|
|
307
|
-
|
|
350
|
+
htmlContainer === null || htmlContainer === void 0 ? void 0 : htmlContainer.style('transform', `translate(${left}px, ${config.offset.top}px)`);
|
|
351
|
+
}, [
|
|
352
|
+
boundsWidth,
|
|
353
|
+
chartSeries,
|
|
354
|
+
onItemClick,
|
|
355
|
+
onUpdate,
|
|
356
|
+
legend,
|
|
357
|
+
items,
|
|
358
|
+
config,
|
|
359
|
+
pageIndex,
|
|
360
|
+
htmlLayout,
|
|
361
|
+
]);
|
|
308
362
|
return React.createElement("g", { className: b(), ref: ref, width: boundsWidth, height: legend.height });
|
|
309
363
|
};
|
|
@@ -27,6 +27,17 @@
|
|
|
27
27
|
.gcharts-legend__item-text:hover {
|
|
28
28
|
fill: var(--g-color-text-complementary);
|
|
29
29
|
}
|
|
30
|
+
.gcharts-legend__item-text-html {
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
user-select: none;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
.gcharts-legend__item-text-html_unselected {
|
|
36
|
+
color: var(--g-color-text-hint);
|
|
37
|
+
}
|
|
38
|
+
.gcharts-legend__item-text-html:hover {
|
|
39
|
+
color: var(--g-color-text-complementary);
|
|
40
|
+
}
|
|
30
41
|
.gcharts-legend__pagination {
|
|
31
42
|
user-select: none;
|
|
32
43
|
fill: var(--g-color-text-primary);
|
|
@@ -135,9 +135,12 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
135
135
|
});
|
|
136
136
|
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
137
137
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
138
|
-
React.createElement("span",
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
React.createElement("span", { dangerouslySetInnerHTML: {
|
|
139
|
+
__html: [seriesData.name || seriesData.id]
|
|
140
|
+
.flat()
|
|
141
|
+
.join('\n'),
|
|
142
|
+
} }),
|
|
143
|
+
"\u00A0",
|
|
141
144
|
React.createElement("span", null, formattedValue)));
|
|
142
145
|
}
|
|
143
146
|
case 'sankey': {
|
|
@@ -18,8 +18,10 @@ export declare const useSeries: (args: Args) => {
|
|
|
18
18
|
top: number;
|
|
19
19
|
};
|
|
20
20
|
pagination: {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
pages: {
|
|
22
|
+
start: number;
|
|
23
|
+
end: number;
|
|
24
|
+
}[];
|
|
23
25
|
} | undefined;
|
|
24
26
|
};
|
|
25
27
|
preparedLegend: import("./types").PreparedLegend;
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
import type { ChartData } from '../../types';
|
|
2
2
|
import type { PreparedAxis, PreparedChart } from '../useChartOptions/types';
|
|
3
3
|
import type { LegendItem, PreparedLegend, PreparedSeries } from './types';
|
|
4
|
-
export declare
|
|
5
|
-
legend: ChartData[
|
|
6
|
-
series: ChartData[
|
|
7
|
-
})
|
|
8
|
-
export declare
|
|
4
|
+
export declare function getPreparedLegend(args: {
|
|
5
|
+
legend: ChartData['legend'];
|
|
6
|
+
series: ChartData['series']['data'];
|
|
7
|
+
}): PreparedLegend;
|
|
8
|
+
export declare function getLegendComponents(args: {
|
|
9
9
|
chartWidth: number;
|
|
10
10
|
chartHeight: number;
|
|
11
|
-
chartMargin: PreparedChart[
|
|
11
|
+
chartMargin: PreparedChart['margin'];
|
|
12
12
|
series: PreparedSeries[];
|
|
13
13
|
preparedLegend: PreparedLegend;
|
|
14
14
|
preparedYAxis: PreparedAxis[];
|
|
15
|
-
})
|
|
15
|
+
}): {
|
|
16
16
|
legendConfig: {
|
|
17
17
|
offset: {
|
|
18
18
|
left: number;
|
|
19
19
|
top: number;
|
|
20
20
|
};
|
|
21
21
|
pagination: {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
pages: {
|
|
23
|
+
start: number;
|
|
24
|
+
end: number;
|
|
25
|
+
}[];
|
|
24
26
|
} | undefined;
|
|
25
27
|
};
|
|
26
28
|
legendItems: LegendItem[][];
|
|
@@ -6,7 +6,7 @@ import { CONTINUOUS_LEGEND_SIZE, legendDefaults } from '../../constants';
|
|
|
6
6
|
import { getDefaultColorStops, getDomainForContinuousColorScale, getHorisontalSvgTextHeight, getLabelsSize, } from '../../utils';
|
|
7
7
|
import { getBoundsWidth } from '../useChartDimensions';
|
|
8
8
|
import { getYAxisWidth } from '../useChartDimensions/utils';
|
|
9
|
-
export
|
|
9
|
+
export function getPreparedLegend(args) {
|
|
10
10
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
11
11
|
const { legend, series } = args;
|
|
12
12
|
const enabled = Boolean(typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : series.length > 1);
|
|
@@ -69,30 +69,38 @@ export const getPreparedLegend = (args) => {
|
|
|
69
69
|
width: legendWidth,
|
|
70
70
|
ticks,
|
|
71
71
|
colorScale,
|
|
72
|
+
html: get(legend, 'html', false),
|
|
72
73
|
};
|
|
73
|
-
}
|
|
74
|
-
|
|
74
|
+
}
|
|
75
|
+
function getFlattenLegendItems(series, preparedLegend) {
|
|
75
76
|
return series.reduce((acc, s) => {
|
|
76
77
|
const legendEnabled = get(s, 'legend.enabled', true);
|
|
77
78
|
if (legendEnabled) {
|
|
78
|
-
acc.push(Object.assign(Object.assign({}, s), { symbol: s.legend.symbol }));
|
|
79
|
+
acc.push(Object.assign(Object.assign({}, s), { height: preparedLegend.lineHeight, symbol: s.legend.symbol }));
|
|
79
80
|
}
|
|
80
81
|
return acc;
|
|
81
82
|
}, []);
|
|
82
|
-
}
|
|
83
|
-
|
|
83
|
+
}
|
|
84
|
+
function getGroupedLegendItems(args) {
|
|
84
85
|
const { maxLegendWidth, items, preparedLegend } = args;
|
|
85
86
|
const result = [[]];
|
|
87
|
+
const bodySelection = select(document.body);
|
|
86
88
|
let textWidthsInLine = [0];
|
|
87
89
|
let lineIndex = 0;
|
|
88
90
|
items.forEach((item) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
const itemSelection = preparedLegend.html
|
|
92
|
+
? bodySelection
|
|
93
|
+
.append('div')
|
|
94
|
+
.html(item.name)
|
|
95
|
+
.style('position', 'absolute')
|
|
96
|
+
.style('display', 'inline-block')
|
|
97
|
+
: bodySelection.append('text').text(item.name);
|
|
98
|
+
itemSelection
|
|
92
99
|
.style('font-size', preparedLegend.itemStyle.fontSize)
|
|
93
100
|
.each(function () {
|
|
94
101
|
const resultItem = clone(item);
|
|
95
|
-
const textWidth = this.getBoundingClientRect()
|
|
102
|
+
const { height, width: textWidth } = this.getBoundingClientRect();
|
|
103
|
+
resultItem.height = height;
|
|
96
104
|
resultItem.textWidth = textWidth;
|
|
97
105
|
textWidthsInLine.push(textWidth);
|
|
98
106
|
const textsWidth = textWidthsInLine.reduce((acc, width) => acc + width, 0);
|
|
@@ -114,12 +122,36 @@ const getGroupedLegendItems = (args) => {
|
|
|
114
122
|
.remove();
|
|
115
123
|
});
|
|
116
124
|
return result;
|
|
117
|
-
}
|
|
118
|
-
|
|
125
|
+
}
|
|
126
|
+
function getPagination(args) {
|
|
127
|
+
const { items, maxLegendHeight, paginatorHeight } = args;
|
|
128
|
+
const pages = [];
|
|
129
|
+
let currentPageIndex = 0;
|
|
130
|
+
let currentHeight = 0;
|
|
131
|
+
items.forEach((item, i) => {
|
|
132
|
+
if (!pages[currentPageIndex]) {
|
|
133
|
+
pages[currentPageIndex] = { start: i, end: i };
|
|
134
|
+
}
|
|
135
|
+
const legendLineHeight = Math.max(...item.map((item) => item.height));
|
|
136
|
+
currentHeight += legendLineHeight;
|
|
137
|
+
if (currentHeight > maxLegendHeight - paginatorHeight) {
|
|
138
|
+
pages[currentPageIndex].end = i;
|
|
139
|
+
currentPageIndex += 1;
|
|
140
|
+
currentHeight = legendLineHeight;
|
|
141
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#end
|
|
142
|
+
pages[currentPageIndex] = { start: i, end: i + (i === items.length - 1 ? 1 : 0) };
|
|
143
|
+
}
|
|
144
|
+
else if (i === items.length - 1) {
|
|
145
|
+
pages[currentPageIndex].end = i + 1;
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return { pages };
|
|
149
|
+
}
|
|
150
|
+
export function getLegendComponents(args) {
|
|
119
151
|
const { chartWidth, chartHeight, chartMargin, series, preparedLegend, preparedYAxis } = args;
|
|
120
152
|
const maxLegendWidth = getBoundsWidth({ chartWidth, chartMargin, preparedYAxis });
|
|
121
153
|
const maxLegendHeight = (chartHeight - chartMargin.top - chartMargin.bottom - preparedLegend.margin) / 2;
|
|
122
|
-
const flattenLegendItems = getFlattenLegendItems(series);
|
|
154
|
+
const flattenLegendItems = getFlattenLegendItems(series, preparedLegend);
|
|
123
155
|
const items = getGroupedLegendItems({
|
|
124
156
|
maxLegendWidth,
|
|
125
157
|
items: flattenLegendItems,
|
|
@@ -127,13 +159,19 @@ export const getLegendComponents = (args) => {
|
|
|
127
159
|
});
|
|
128
160
|
let pagination;
|
|
129
161
|
if (preparedLegend.type === 'discrete') {
|
|
130
|
-
|
|
162
|
+
const lineHeights = items.reduce((acc, item) => {
|
|
163
|
+
acc.push(Math.max(...item.map((item) => item.height)));
|
|
164
|
+
return acc;
|
|
165
|
+
}, []);
|
|
166
|
+
let legendHeight = lineHeights.reduce((acc, height) => acc + height, 0);
|
|
131
167
|
if (maxLegendHeight < legendHeight) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
168
|
+
const lines = Math.floor(maxLegendHeight / preparedLegend.lineHeight);
|
|
169
|
+
legendHeight = preparedLegend.lineHeight * lines;
|
|
170
|
+
pagination = getPagination({
|
|
171
|
+
items,
|
|
172
|
+
maxLegendHeight: legendHeight,
|
|
173
|
+
paginatorHeight: preparedLegend.lineHeight,
|
|
174
|
+
});
|
|
137
175
|
}
|
|
138
176
|
preparedLegend.height = legendHeight;
|
|
139
177
|
}
|
|
@@ -143,4 +181,4 @@ export const getLegendComponents = (args) => {
|
|
|
143
181
|
top,
|
|
144
182
|
};
|
|
145
183
|
return { legendConfig: { offset, pagination }, legendItems: items };
|
|
146
|
-
}
|
|
184
|
+
}
|
|
@@ -39,6 +39,7 @@ export type OnLegendItemClick = (data: {
|
|
|
39
39
|
}) => void;
|
|
40
40
|
export type LegendItem = {
|
|
41
41
|
color: string;
|
|
42
|
+
height: number;
|
|
42
43
|
name: string;
|
|
43
44
|
symbol: PreparedLegendSymbol;
|
|
44
45
|
textWidth: number;
|
|
@@ -51,8 +52,10 @@ export type LegendConfig = {
|
|
|
51
52
|
top: number;
|
|
52
53
|
};
|
|
53
54
|
pagination?: {
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
pages: {
|
|
56
|
+
start: number;
|
|
57
|
+
end: number;
|
|
58
|
+
}[];
|
|
56
59
|
};
|
|
57
60
|
};
|
|
58
61
|
export type PreparedHaloOptions = {
|