@sproutsocial/seeds-react-data-viz 0.14.0 → 0.15.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.
@@ -1,721 +1,21 @@
1
1
  import {
2
- ChartLegend,
3
- ChartLegendLabel,
4
- ChartLegendLabelContentWithIcon,
5
- ChartTooltip,
6
- ChartTooltipFooter,
7
- ChartTooltipHeader,
8
- ChartTooltipPortal,
9
- ChartTooltipTable,
10
- ChartTooltipTitle,
11
- GlobalChartStyleOverrides,
12
2
  VERTICAL_BAR_CHART_DEFAULT_SERIES_LIMIT,
13
- transformTimeSeriesTooltipData,
14
3
  verticalBarChartStyles
15
- } from "../chunk-CDBW4SOR.js";
16
-
17
- // src/charts/bar/VerticalBarChart.tsx
18
- import { memo as memo3 } from "react";
19
-
20
- // src/charts/shared/chartBase.tsx
21
- import Highcharts from "highcharts";
22
- import { HighchartsReact } from "highcharts-react-official";
23
- import highchartsAccessibility from "highcharts/modules/accessibility";
24
- import highchartsAnnotations from "highcharts/modules/annotations";
25
- import highchartsExporting from "highcharts/modules/exporting";
26
- import highchartsOfflineExporting from "highcharts/modules/offline-exporting";
27
- import highchartsExportData from "highcharts/modules/export-data";
4
+ } from "../chunk-HQM3EIZW.js";
28
5
  import {
29
- useCallback,
30
- useEffect as useEffect2,
31
- useMemo,
32
- useRef as useRef2,
33
- useState as useState2
34
- } from "react";
35
- import { useTheme } from "styled-components";
36
- import { Box as Box3 } from "@sproutsocial/seeds-react-box";
37
-
38
- // src/charts/shared/ChartAnnotationMarkerPortal.tsx
39
- import { memo, useEffect, useRef, useState } from "react";
40
- import { createPortal } from "react-dom";
41
- import { Box } from "@sproutsocial/seeds-react-box";
42
- import { Icon } from "@sproutsocial/seeds-react-icon";
43
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
44
- function ChartAnnotationMarker({
45
- annotation,
46
- lineHeight
47
- }) {
48
- return /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", alignItems: "center", gap: 200, children: [
49
- annotation.icon ? /* @__PURE__ */ jsx(
50
- Box,
51
- {
52
- display: "flex",
53
- alignItems: "center",
54
- justifyContent: "center",
55
- width: "20px",
56
- height: "20px",
57
- bg: "icon.base",
58
- borderRadius: "50%",
59
- children: /* @__PURE__ */ jsx(Icon, { name: annotation.icon, size: "mini", color: "icon.inverse" })
60
- }
61
- ) : null,
62
- /* @__PURE__ */ jsx(
63
- Box,
64
- {
65
- height: `${lineHeight}px`,
66
- width: "0.5px",
67
- bg: annotation.color ?? "neutral.200"
68
- }
69
- )
70
- ] });
71
- }
72
- var MARKER_ATTR = "data-v2-chart-annotation-marker";
73
- var ChartAnnotationMarkerPortal = memo(
74
- function ChartAnnotationMarkerPortal2({
75
- annotationLookup,
76
- annotationContext,
77
- chartContainer,
78
- plotHeight
79
- }) {
80
- const annotationContainers = useRef({});
81
- const [, setRenderCount] = useState(0);
82
- const forceRender = () => setRenderCount((prev) => prev + 1);
83
- useEffect(() => {
84
- if (!annotationContext.labels) return;
85
- const newContainers = {};
86
- const existingAnnotationContainers = chartContainer.querySelectorAll(
87
- `div[${MARKER_ATTR}='true']`
88
- );
89
- existingAnnotationContainers.forEach(
90
- (annotation) => annotation.setAttribute(MARKER_ATTR, "false")
91
- );
92
- annotationContext.labels.forEach((label) => {
93
- const labelElement = label.graphic?.div;
94
- if (!labelElement) return;
95
- labelElement.querySelectorAll(`div[${MARKER_ATTR}]`).forEach((component) => component.remove());
96
- const annotationDiv = document.createElement("div");
97
- annotationDiv.setAttribute(MARKER_ATTR, "true");
98
- labelElement.appendChild(annotationDiv);
99
- const xValue = label.options?.point?.x;
100
- newContainers[xValue] = annotationDiv;
101
- });
102
- annotationContainers.current = newContainers;
103
- forceRender();
104
- requestAnimationFrame(() => {
105
- chartContainer.querySelectorAll("div.highcharts-annotation").forEach((annotationDiv) => {
106
- const reactAnnotations = annotationDiv.querySelectorAll(
107
- `div[${MARKER_ATTR}]`
108
- );
109
- const hasActiveAnnotations = Array.from(reactAnnotations).some(
110
- (div) => div.getAttribute(MARKER_ATTR) === "true"
111
- );
112
- if (!hasActiveAnnotations) annotationDiv.remove();
113
- });
114
- });
115
- }, [annotationContext.labels, annotationLookup, chartContainer]);
116
- return /* @__PURE__ */ jsx(Fragment, { children: Object.entries(annotationContainers.current).map(
117
- ([xValue, container]) => {
118
- const annotation = annotationLookup.get(Number(xValue));
119
- return annotation ? createPortal(
120
- /* @__PURE__ */ jsx(
121
- ChartAnnotationMarker,
122
- {
123
- annotation,
124
- lineHeight: plotHeight
125
- }
126
- ),
127
- container
128
- ) : null;
129
- }
130
- ) });
131
- }
132
- );
133
-
134
- // src/charts/shared/DefaultChartTooltip.tsx
135
- import { Box as Box2 } from "@sproutsocial/seeds-react-box";
136
- import { Icon as Icon2 } from "@sproutsocial/seeds-react-icon";
137
- import { Text } from "@sproutsocial/seeds-react-text";
138
-
139
- // src/charts/shared/formatters/valueFormatter.ts
140
- import { formatDuration } from "@sproutsocial/seeds-react-duration";
141
- import { formatNumeral } from "@sproutsocial/seeds-react-numeral";
142
- function valueFormatter({
143
- value,
144
- numberFormat = "decimal",
145
- currency = "USD",
146
- abbreviate = true,
147
- percentInput = "decimal",
148
- numberLocale,
149
- textLocale
150
- }) {
151
- const numberValue = Number(value);
152
- if (numberValue === 0) {
153
- return formatNumeral({ locale: numberLocale, number: 0 });
154
- }
155
- if (numberFormat === "duration") {
156
- return formatDuration({
157
- display: "narrow",
158
- locale: textLocale,
159
- milliseconds: numberValue
160
- });
161
- }
162
- if (numberFormat === "percent") {
163
- const whole = percentInput === "decimal" ? numberValue * 100 : numberValue;
164
- return formatNumeral({
165
- format: "percent",
166
- locale: numberLocale,
167
- number: whole
168
- });
169
- }
170
- return formatNumeral({
171
- abbreviate,
172
- currency,
173
- format: numberFormat,
174
- locale: numberLocale,
175
- number: numberValue
176
- });
177
- }
178
-
179
- // src/charts/shared/DefaultChartTooltip.tsx
180
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
181
- function formatTitle(position, timezone) {
182
- if (typeof position === "string") return position;
183
- return new Date(position).toLocaleDateString(void 0, {
184
- year: "numeric",
185
- month: "long",
186
- day: "numeric",
187
- weekday: "long",
188
- timeZone: timezone
189
- });
190
- }
191
- function AnnotationHeader({
192
- icon,
193
- title,
194
- description
195
- }) {
196
- const hasIconOrTitle = Boolean(icon || title);
197
- if (!hasIconOrTitle && !description) return null;
198
- return /* @__PURE__ */ jsxs2(Fragment2, { children: [
199
- hasIconOrTitle ? /* @__PURE__ */ jsx2(ChartTooltipHeader, { children: /* @__PURE__ */ jsxs2(Box2, { display: "flex", alignItems: "center", gap: 300, children: [
200
- icon ? /* @__PURE__ */ jsx2(Icon2, { name: icon, size: "mini", color: "icon.base" }) : null,
201
- title ? /* @__PURE__ */ jsx2(Text, { color: "text.body", fontSize: 200, children: title }) : null
202
- ] }) }) : null,
203
- description ? /* @__PURE__ */ jsx2(ChartTooltipHeader, { children: /* @__PURE__ */ jsx2(Text, { fontSize: 200, color: "text.subtext", children: description }) }) : null
204
- ] });
205
- }
206
- function DefaultChartTooltip({
207
- data,
208
- position,
209
- annotation,
210
- hasOnClick,
211
- tooltipClickLabel,
212
- timezone,
213
- invalidNumberLabel,
214
- valueFormat
215
- }) {
216
- const rows = data.map((d) => ({
217
- cells: [
218
- {
219
- content: /* @__PURE__ */ jsx2(ChartLegendLabel, { color: d.color, children: /* @__PURE__ */ jsx2(Text, { "aria-label": `${d.name}: `, children: d.name }) })
220
- },
221
- {
222
- // Format non-null values through the shared formatter when a
223
- // `valueFormat` is supplied. Tooltip values are always full precision
224
- // (`abbreviate: false`) — the axis tick scan never applies here (v1
225
- // parity: `VerticalBarChartTooltip` passed `abbreviate={false}`).
226
- content: /* @__PURE__ */ jsx2(Text, { children: d.value == null ? invalidNumberLabel ?? "\u2014" : valueFormat ? valueFormatter({
227
- ...valueFormat,
228
- value: d.value,
229
- abbreviate: false
230
- }) : d.value }),
231
- align: "right"
232
- }
233
- ]
234
- }));
235
- return /* @__PURE__ */ jsxs2(ChartTooltip, { children: [
236
- annotation ? /* @__PURE__ */ jsx2(
237
- AnnotationHeader,
238
- {
239
- icon: annotation.icon,
240
- title: annotation.title,
241
- description: annotation.description
242
- }
243
- ) : null,
244
- /* @__PURE__ */ jsx2(ChartTooltipTitle, { children: formatTitle(position, timezone) }),
245
- /* @__PURE__ */ jsx2(ChartTooltipTable, { rows }),
246
- hasOnClick && tooltipClickLabel ? /* @__PURE__ */ jsx2(ChartTooltipFooter, { children: typeof tooltipClickLabel === "string" ? /* @__PURE__ */ jsxs2(Box2, { display: "flex", alignItems: "center", gap: 300, children: [
247
- /* @__PURE__ */ jsx2(Icon2, { name: "hand-pointer-clicking-outline", color: "icon.base" }),
248
- /* @__PURE__ */ jsx2(Text, { color: "text.subtext", fontSize: 200, children: tooltipClickLabel })
249
- ] }) : tooltipClickLabel }) : null
250
- ] });
251
- }
252
-
253
- // src/charts/shared/SeriesLegend.tsx
254
- import { memo as memo2 } from "react";
255
- import { Icon as Icon3 } from "@sproutsocial/seeds-react-icon";
256
- import { jsx as jsx3 } from "react/jsx-runtime";
257
- var SeriesLegend = memo2(function SeriesLegend2({
258
- items
259
- }) {
260
- const labels = items.map((item) => ({
261
- content: /* @__PURE__ */ jsx3(
262
- ChartLegendLabelContentWithIcon,
263
- {
264
- icon: item.icon ? /* @__PURE__ */ jsx3(Icon3, { name: item.icon, fixedWidth: true, "aria-hidden": true }) : void 0,
265
- children: item.name
266
- }
267
- ),
268
- color: item.color
269
- }));
270
- return /* @__PURE__ */ jsx3(ChartLegend, { legendLabels: labels });
271
- });
272
-
273
- // src/charts/shared/chartExport.ts
274
- function defaultFilename(type, now) {
275
- const iso = now.toISOString();
276
- const stamp = `${iso.slice(0, 10)}-${iso.slice(11, 19).replace(/:/g, "-")}`;
277
- return slugify(type ? `chart-${type}-${stamp}` : `chart-${stamp}`);
278
- }
279
- function slugify(value) {
280
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
281
- }
282
-
283
- // src/charts/shared/chartBase.tsx
284
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
285
- highchartsAccessibility(Highcharts);
286
- highchartsAnnotations(Highcharts);
287
- highchartsExporting(Highcharts);
288
- highchartsOfflineExporting(Highcharts);
289
- highchartsExportData(Highcharts);
290
- function useSeedsChartSetup({
291
- series,
292
- includePatterns = false,
293
- seriesLimit,
294
- isSeriesLimitOverridden = false,
295
- hideSeriesLimitWarning = false,
296
- componentName
297
- }) {
298
- const theme = useTheme();
299
- const palette = theme.colors.DATAVIZ_COLORS_LIST;
300
- const cappedSeries = useMemo(
301
- () => seriesLimit !== void 0 && seriesLimit >= 1 && series.length > seriesLimit ? series.slice(0, seriesLimit) : series,
302
- [series, seriesLimit]
303
- );
304
- useEffect2(() => {
305
- if (seriesLimit !== void 0 && seriesLimit >= 1 && series.length > seriesLimit && !isSeriesLimitOverridden && !hideSeriesLimitWarning && componentName != null) {
306
- console.warn(
307
- `${componentName}: Maximum number of series (${seriesLimit}) exceeded. Only the first ${seriesLimit} series will be displayed.`
308
- );
309
- }
310
- }, [
311
- series.length,
312
- seriesLimit,
313
- isSeriesLimitOverridden,
314
- hideSeriesLimitWarning,
315
- componentName
316
- ]);
317
- const colors = useMemo(() => {
318
- const out = [];
319
- const paletteLen = palette.length;
320
- for (let i = 0; i < cappedSeries.length; i++) {
321
- const s = cappedSeries[i];
322
- out.push(s.color ?? palette[i % paletteLen] ?? "");
323
- }
324
- return out;
325
- }, [cappedSeries, palette]);
326
- const patterns = useMemo(() => {
327
- if (!includePatterns) return [];
328
- const out = [];
329
- for (let i = 0; i < cappedSeries.length; i++) {
330
- const s = cappedSeries[i];
331
- out.push(s.pattern ?? "solid");
332
- }
333
- return out;
334
- }, [cappedSeries, includePatterns]);
335
- return { theme, colors, patterns, series: cappedSeries };
336
- }
337
- function buildExportChartOptions(theme, colors) {
338
- const axisOptions = {
339
- lineColor: theme.colors.container.border.base,
340
- gridLineColor: theme.colors.container.border.base,
341
- labels: {
342
- style: {
343
- color: theme.colors.text.subtext,
344
- fontFamily: theme.fontFamily,
345
- fontSize: theme.typography[100].fontSize,
346
- fontWeight: String(theme.fontWeights.normal)
347
- }
348
- }
349
- };
350
- return {
351
- chart: {
352
- styledMode: false,
353
- backgroundColor: theme.colors.container.background.base
354
- },
355
- // Series without an explicit `color` fall through to this palette by index.
356
- colors: [...colors],
357
- // The on-screen legend is a React sibling (`SeriesLegend`) outside the chart
358
- // SVG, so the export can't capture it. Turn on Highcharts' native legend for
359
- // the clone instead, themed to approximate the Seeds `ChartLegend` (centered
360
- // row of swatch + `text.subtext` label). It can't reproduce per-series Seeds
361
- // icons — those series show colour + name only.
362
- legend: {
363
- enabled: true,
364
- symbolRadius: 4,
365
- symbolHeight: 12,
366
- symbolWidth: 12,
367
- itemStyle: {
368
- color: theme.colors.text.subtext,
369
- fontFamily: theme.fontFamily,
370
- fontSize: theme.typography[200].fontSize,
371
- fontWeight: String(theme.fontWeights.normal)
372
- }
373
- },
374
- xAxis: {
375
- ...axisOptions,
376
- crosshair: {
377
- color: theme.colors.container.border.decorative.neutral,
378
- width: 1
379
- }
380
- },
381
- // No zero-line: the `y-axis-zero-line` rule in chartStyles is a v1 leftover;
382
- // the v2 bar adapter never creates that plotLine.
383
- yAxis: axisOptions
384
- };
385
- }
386
- function createChartExportHandle(chart, getExportChartOptions) {
387
- const resolveName = (filename) => filename ?? defaultFilename(chart.options.chart?.type, /* @__PURE__ */ new Date());
388
- const exportImage = async (type, filename) => {
389
- chart.exportChartLocal(
390
- { type, filename: resolveName(filename) },
391
- getExportChartOptions()
392
- );
393
- };
394
- return {
395
- exportPNG: ({ filename } = {}) => exportImage("image/png", filename),
396
- exportSVG: ({ filename } = {}) => exportImage("image/svg+xml", filename),
397
- exportPDF: async ({ filename } = {}) => {
398
- const { loadPdfExportLibs } = await import("../pdfExportLibs-NWLZH5KL.js");
399
- loadPdfExportLibs();
400
- await exportImage("application/pdf", filename);
401
- },
402
- exportCSV: async ({ filename } = {}) => {
403
- const exporting = chart.options.exporting ??= {};
404
- const previous = exporting.filename;
405
- exporting.filename = resolveName(filename);
406
- try {
407
- chart.downloadCSV();
408
- } finally {
409
- exporting.filename = previous;
410
- }
411
- }
412
- };
413
- }
414
- function ChartRenderer({
415
- options,
416
- series,
417
- colors,
418
- tooltip,
419
- annotationLookup,
420
- hasOnClick,
421
- tooltipClickLabel,
422
- timezone,
423
- invalidNumberLabel,
424
- valueFormat,
425
- onReady
426
- }) {
427
- const [chart, setChart] = useState2(null);
428
- const [plotHeight, setPlotHeight] = useState2(0);
429
- const renderTooltip = tooltip ?? DefaultChartTooltip;
430
- const theme = useTheme();
431
- const exportThemeRef = useRef2({ theme, colors });
432
- exportThemeRef.current = { theme, colors };
433
- const captureChart = useCallback((chartInstance) => {
434
- const forExport = chartInstance.options.chart?.forExport;
435
- if (!forExport) {
436
- setChart(chartInstance);
437
- }
438
- }, []);
439
- const onReadyRef = useRef2(onReady);
440
- onReadyRef.current = onReady;
441
- useEffect2(() => {
442
- if (!chart) return;
443
- onReadyRef.current?.(
444
- createChartExportHandle(
445
- chart,
446
- () => buildExportChartOptions(
447
- exportThemeRef.current.theme,
448
- exportThemeRef.current.colors
449
- )
450
- )
451
- );
452
- }, [chart]);
453
- useEffect2(() => {
454
- if (!chart) return;
455
- setPlotHeight(chart.plotHeight);
456
- const unbind = Highcharts.addEvent(chart, "redraw", () => {
457
- setPlotHeight(chart.plotHeight);
458
- });
459
- return () => unbind();
460
- }, [chart]);
461
- if (series.length === 0) return null;
462
- const chartWithAnnotations = chart;
463
- const hasRenderedAnnotations = !!chartWithAnnotations?.annotations?.length && !!annotationLookup?.size && plotHeight > 0;
464
- const highchartsOptions = {
465
- ...options,
466
- tooltip: { ...options.tooltip, useHTML: true },
467
- // Export stays client-side/offline only. `fallbackToExportServer: false`
468
- // stops Highcharts from POSTing chart SVG/data to its public export server
469
- // (https://export.highcharts.com/) when a local export fails — our
470
- // `useHTML: true` tooltips are a known trigger for offline-rasterization
471
- // failure, so this guard is load-bearing, not theoretical. `contextButton`
472
- // is disabled because v2 charts expose export only through the `onReady`
473
- // handle; programmatic `exportChartLocal` / `downloadCSV` are unaffected.
474
- exporting: {
475
- fallbackToExportServer: false,
476
- buttons: { contextButton: { enabled: false } }
477
- }
478
- };
479
- return /* @__PURE__ */ jsxs3(Fragment3, { children: [
480
- /* @__PURE__ */ jsx4(GlobalChartStyleOverrides, {}),
481
- /* @__PURE__ */ jsx4(
482
- HighchartsReact,
483
- {
484
- highcharts: Highcharts,
485
- options: highchartsOptions,
486
- callback: captureChart
487
- }
488
- ),
489
- hasRenderedAnnotations ? /* @__PURE__ */ jsx4(
490
- ChartAnnotationMarkerPortal,
491
- {
492
- annotationLookup,
493
- annotationContext: chartWithAnnotations.annotations[0],
494
- chartContainer: chartWithAnnotations.container,
495
- plotHeight
496
- }
497
- ) : null,
498
- chart ? /* @__PURE__ */ jsx4(
499
- ChartTooltipPortal,
500
- {
501
- chart,
502
- renderContent: (context) => {
503
- const data = transformTimeSeriesTooltipData({
504
- context,
505
- data: series.map((s, i) => ({
506
- name: s.name,
507
- points: [],
508
- styles: { color: s.color ?? colors[i] ?? "" }
509
- }))
510
- }).map((row) => ({
511
- color: row.color,
512
- name: row.name,
513
- value: row.value
514
- }));
515
- const position = context.x;
516
- const resolved = annotationLookup?.get(context.point.x);
517
- const annotation = resolved ? {
518
- icon: resolved.icon,
519
- color: resolved.color,
520
- title: resolved.title,
521
- description: resolved.description
522
- } : void 0;
523
- return renderTooltip({
524
- data,
525
- position,
526
- annotation,
527
- hasOnClick,
528
- tooltipClickLabel,
529
- timezone,
530
- invalidNumberLabel,
531
- valueFormat
532
- });
533
- }
534
- }
535
- ) : null,
536
- /* @__PURE__ */ jsx4(Box3, { mt: 350, children: /* @__PURE__ */ jsx4(
537
- SeriesLegend,
538
- {
539
- items: series.map((s, i) => ({
540
- name: s.name,
541
- icon: s.icon,
542
- color: s.color ?? colors[i] ?? ""
543
- }))
544
- }
545
- ) })
546
- ] });
547
- }
6
+ ChartRenderer,
7
+ buildAnnotationConfig,
8
+ buildBaseChartOptions,
9
+ buildDimensionalAxis,
10
+ defaultValueAxisLabelFormatter,
11
+ makeValueAxisLabelFormatter,
12
+ resolveTooltipTimezone,
13
+ useSeedsChartSetup
14
+ } from "../chunk-OAN5VC7M.js";
15
+ import "../chunk-LC3HGWDD.js";
548
16
 
549
- // src/charts/shared/annotations.ts
550
- function buildAnnotationConfig(annotations) {
551
- if (!annotations?.length) {
552
- return { annotations: void 0, lookup: void 0 };
553
- }
554
- const labels = [];
555
- const lookup = /* @__PURE__ */ new Map();
556
- for (const annotation of annotations) {
557
- const { position } = annotation;
558
- labels.push({
559
- // Highcharts anchor — always uses `x` regardless of chart orientation.
560
- point: { x: position, y: 0, xAxis: 0, yAxis: 0 },
561
- // Space (not empty) — Highcharts reverts to its built-in label text on empty string.
562
- text: " "
563
- });
564
- lookup.set(position, annotation);
565
- }
566
- if (labels.length === 0) {
567
- return { annotations: void 0, lookup: void 0 };
568
- }
569
- return {
570
- // `useHTML: true` makes Highcharts attach an HTML `graphic.div` to each
571
- // label, which the marker portal needs as its render target. `padding: 0`
572
- // removes Highcharts' default left padding so markers stay centered.
573
- annotations: [{ labels, labelOptions: { useHTML: true, padding: 0 } }],
574
- lookup
575
- };
576
- }
577
-
578
- // src/charts/shared/formatters/datetimeFormatter.ts
579
- var DAY_MS = 864e5;
580
- var WEEK_MS = 6048e5;
581
- var MIN_MONTH_MS = 24192e5;
582
- var MIN_YEAR_MS = 31536e6;
583
- var getDatePartsInTimezone = (date, timezone) => {
584
- const formatter = new Intl.DateTimeFormat("en-US", {
585
- year: "numeric",
586
- month: "numeric",
587
- day: "numeric",
588
- timeZone: timezone
589
- });
590
- const parts = formatter.formatToParts(date);
591
- return {
592
- day: parseInt(parts.find((p) => p.type === "day")?.value ?? "0", 10),
593
- month: parseInt(parts.find((p) => p.type === "month")?.value ?? "0", 10),
594
- year: parseInt(parts.find((p) => p.type === "year")?.value ?? "0", 10)
595
- };
596
- };
597
- var deriveGranularity = (tickPositions) => {
598
- if (tickPositions.length < 2) {
599
- return "day";
600
- }
601
- const deltas = [];
602
- for (let i = 1; i < tickPositions.length; i++) {
603
- const curr = tickPositions[i];
604
- const prev = tickPositions[i - 1];
605
- if (curr === void 0 || prev === void 0) {
606
- continue;
607
- }
608
- deltas.push(Math.abs(curr - prev));
609
- }
610
- deltas.sort((a, b) => a - b);
611
- const mid = Math.floor(deltas.length / 2);
612
- const upper = deltas[mid];
613
- const lower = deltas[mid - 1];
614
- const median = deltas.length % 2 === 0 && lower !== void 0 ? (lower + upper) / 2 : upper;
615
- if (median < DAY_MS) {
616
- return "hour";
617
- }
618
- if (median < WEEK_MS) {
619
- return "day";
620
- }
621
- if (median < MIN_MONTH_MS) {
622
- return "week";
623
- }
624
- if (median < MIN_YEAR_MS) {
625
- return "month";
626
- }
627
- return "year";
628
- };
629
- function datetimeFormatter({
630
- value,
631
- tickPositions = [],
632
- // optional
633
- timezone = "UTC",
634
- timeFormat = "12",
635
- textLocale = "en-US"
636
- }) {
637
- if (typeof value === "string" || !Number.isFinite(value)) {
638
- return { primary: String(value) };
639
- }
640
- const granularity = deriveGranularity(tickPositions);
641
- const tickIndex = tickPositions.indexOf(value);
642
- const isFirst = tickIndex === 0;
643
- const previousValue = tickPositions[tickIndex - 1];
644
- const valueDate = new Date(value);
645
- const valueParts = getDatePartsInTimezone(valueDate, timezone);
646
- const previousValueDate = previousValue ? new Date(previousValue) : void 0;
647
- const previousValueParts = previousValueDate ? getDatePartsInTimezone(previousValueDate, timezone) : void 0;
648
- let firstPartOptions = {};
649
- let secondPartOptions = {};
650
- switch (granularity) {
651
- case "hour":
652
- firstPartOptions = timeFormat === "24" ? { hour: "numeric", minute: "numeric", hour12: false } : { hour: "numeric" };
653
- if (isFirst || valueParts.day !== previousValueParts?.day) {
654
- secondPartOptions = { day: "numeric", month: "short" };
655
- }
656
- break;
657
- case "day":
658
- case "week":
659
- firstPartOptions = { day: "numeric" };
660
- if (isFirst || valueParts.month !== previousValueParts?.month) {
661
- secondPartOptions = { month: "short" };
662
- }
663
- break;
664
- case "month":
665
- firstPartOptions = { month: "short" };
666
- if (isFirst || valueParts.year !== previousValueParts?.year) {
667
- secondPartOptions = { year: "numeric" };
668
- }
669
- break;
670
- case "year":
671
- firstPartOptions = { year: "numeric" };
672
- break;
673
- default:
674
- firstPartOptions = {};
675
- break;
676
- }
677
- const primary = new Intl.DateTimeFormat(textLocale, {
678
- ...firstPartOptions,
679
- timeZone: timezone
680
- }).format(valueDate);
681
- if (Object.keys(secondPartOptions).length > 0) {
682
- const secondary = new Intl.DateTimeFormat(textLocale, {
683
- ...secondPartOptions,
684
- timeZone: timezone
685
- }).format(valueDate);
686
- return { primary, secondary };
687
- }
688
- return { primary };
689
- }
690
-
691
- // src/charts/shared/valueAxisLabelFormatter.ts
692
- import { formatNumeral as formatNumeral2 } from "@sproutsocial/seeds-react-numeral";
693
- var FIXED_LOCALE = "en-US";
694
- function deriveAbbreviateFromTicks(context) {
695
- const tickPositions = context.axis.tickPositions;
696
- const maxValue = tickPositions && tickPositions.length > 0 ? tickPositions[tickPositions.length - 1] : void 0;
697
- return typeof maxValue === "number" && maxValue > 9999 ? 1e3 : true;
698
- }
699
- function defaultValueAxisLabelFormatter() {
700
- const numberValue = Number(this.value);
701
- if (numberValue === 0) {
702
- return formatNumeral2({ locale: FIXED_LOCALE, number: 0 });
703
- }
704
- const abbreviate = deriveAbbreviateFromTicks(this);
705
- return formatNumeral2({
706
- abbreviate,
707
- format: "decimal",
708
- locale: FIXED_LOCALE,
709
- number: numberValue
710
- });
711
- }
712
- function makeValueAxisLabelFormatter(format) {
713
- return function() {
714
- const numberValue = Number(this.value);
715
- const abbreviate = format.abbreviate === false ? false : deriveAbbreviateFromTicks(this);
716
- return valueFormatter({ ...format, value: numberValue, abbreviate });
717
- };
718
- }
17
+ // src/charts/bar/VerticalBarChart.tsx
18
+ import { memo } from "react";
719
19
 
720
20
  // src/charts/bar/adapter.ts
721
21
  function highchartsType(direction) {
@@ -724,65 +24,22 @@ function highchartsType(direction) {
724
24
  return "column";
725
25
  }
726
26
  }
727
- function makeDatetimeAxisLabelFormatter(timezone, timeFormat) {
728
- return function() {
729
- const { primary, secondary } = datetimeFormatter({
730
- value: this.value,
731
- tickPositions: this.axis.tickPositions ?? [],
732
- timezone,
733
- timeFormat
734
- });
735
- return secondary ? `${primary}<br/><span class="hc-axis-label-secondary">${secondary}</span>` : primary;
736
- };
737
- }
738
- function buildXAxis(xAxis, timezone) {
739
- if (xAxis.type === "datetime") {
740
- return {
741
- type: "datetime",
742
- crosshair: xAxis.crosshair,
743
- labels: {
744
- formatter: makeDatetimeAxisLabelFormatter(timezone, xAxis.timeFormat)
745
- }
746
- };
747
- }
748
- return {
749
- type: "category",
750
- categories: [...xAxis.categories],
751
- crosshair: xAxis.crosshair
752
- };
753
- }
754
27
  function buildBarChartOptions(props, direction, series = props.series) {
755
28
  const chartType = highchartsType(direction);
756
29
  const isDatetime = props.xAxis.type === "datetime";
757
- const tooltipTimezone = isDatetime ? props.timezone ?? "UTC" : void 0;
30
+ const tooltipTimezone = resolveTooltipTimezone(props.xAxis, props.timezone);
758
31
  const { annotations, lookup: annotationLookup } = buildAnnotationConfig(
759
32
  props.annotations
760
33
  );
761
34
  const options = {
762
- chart: {
35
+ ...buildBaseChartOptions({
763
36
  type: chartType,
764
- styledMode: true,
765
- animation: false,
766
- // Leave room above the plot area for marker icons that sit above the chart.
767
- ...annotations ? { marginTop: 24 } : {}
768
- },
769
- accessibility: { description: props.description },
770
- title: { text: "" },
771
- credits: { enabled: false },
772
- legend: { enabled: false },
773
- tooltip: {
774
- enabled: true,
775
- outside: true,
776
- padding: 0,
777
- shape: "rect",
778
- shared: true,
779
- hideDelay: 0,
780
- valueSuffix: props.valueSuffix
781
- },
782
- xAxis: buildXAxis(props.xAxis, tooltipTimezone ?? "UTC"),
783
- // `timezone` is chart-global in Highcharts (`time.timezone`) and only
784
- // meaningful for a datetime axis, so it's omitted otherwise.
785
- ...tooltipTimezone ? { time: { timezone: tooltipTimezone } } : {},
37
+ description: props.description,
38
+ valueSuffix: props.valueSuffix,
39
+ reserveTopMargin: Boolean(annotations),
40
+ timezone: tooltipTimezone
41
+ }),
42
+ xAxis: buildDimensionalAxis(props.xAxis, tooltipTimezone ?? "UTC"),
786
43
  yAxis: {
787
44
  min: props.yAxis?.min,
788
45
  max: props.yAxis?.max,
@@ -853,7 +110,7 @@ function buildBarChartOptions(props, direction, series = props.series) {
853
110
 
854
111
  // src/charts/bar/styles.ts
855
112
  import styled from "styled-components";
856
- import { Box as Box4 } from "@sproutsocial/seeds-react-box";
113
+ import { Box } from "@sproutsocial/seeds-react-box";
857
114
 
858
115
  // src/charts/shared/styles.ts
859
116
  import { css } from "styled-components";
@@ -876,14 +133,14 @@ var axisLabelV2Styles = css`
876
133
  `;
877
134
 
878
135
  // src/charts/bar/styles.ts
879
- var VerticalBarStyledBox = styled(Box4)`
136
+ var VerticalBarStyledBox = styled(Box)`
880
137
  ${verticalBarChartStyles}
881
138
  ${axisLabelV2Styles}
882
139
  `;
883
140
 
884
141
  // src/charts/bar/VerticalBarChart.tsx
885
- import { jsx as jsx5 } from "react/jsx-runtime";
886
- var VerticalBarChart = memo3(function VerticalBarChart2(props) {
142
+ import { jsx } from "react/jsx-runtime";
143
+ var VerticalBarChart = memo(function VerticalBarChart2(props) {
887
144
  const seriesLimit = props.seriesLimit ?? VERTICAL_BAR_CHART_DEFAULT_SERIES_LIMIT;
888
145
  const { colors, series } = useSeedsChartSetup({
889
146
  series: props.series,
@@ -898,13 +155,13 @@ var VerticalBarChart = memo3(function VerticalBarChart2(props) {
898
155
  series
899
156
  );
900
157
  const hasOnClick = Boolean(props.onClick);
901
- return /* @__PURE__ */ jsx5(
158
+ return /* @__PURE__ */ jsx(
902
159
  VerticalBarStyledBox,
903
160
  {
904
161
  $colors: colors,
905
162
  $hasOnClick: hasOnClick,
906
163
  bg: "container.background.base",
907
- children: /* @__PURE__ */ jsx5(
164
+ children: /* @__PURE__ */ jsx(
908
165
  ChartRenderer,
909
166
  {
910
167
  options,