@micha.bigler/ui-core-micha 3.14.4 → 4.0.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.
Files changed (341) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +7 -0
  3. package/dist/chat/kitSxRegistry.js +9 -0
  4. package/dist/components/charts/kitSxRegistry.js +6 -0
  5. package/dist/entries/charts.js +6 -0
  6. package/dist/entries/chat.js +8 -0
  7. package/dist/entries/markdown.js +1 -0
  8. package/dist/index.js +0 -14
  9. package/dist/pages/AccountPage.js +1 -2
  10. package/dist/pages/LoginPage.js +1 -2
  11. package/dist/pages/PasswordChangePage.js +1 -2
  12. package/dist/pages/PasswordInvitePage.js +1 -2
  13. package/dist/pages/PasswordResetRequestPage.js +1 -2
  14. package/dist/pages/SignUpPage.js +1 -2
  15. package/dist/pages/SignupConfirmPage.js +1 -2
  16. package/dist/theme/kitSxEntry.js +11 -0
  17. package/dist/theme/kitSxRegistry.js +2 -15
  18. package/package.json +32 -7
  19. package/.github/workflows/ci.yml +0 -35
  20. package/.github/workflows/publish.yml +0 -126
  21. package/DESIGN.md +0 -60
  22. package/WORK_ORDERS.md +0 -150
  23. package/dev/entries.jsx +0 -721
  24. package/dev/fixtures.js +0 -26
  25. package/dev/main.jsx +0 -75
  26. package/dev/mockTransport.jsx +0 -128
  27. package/docs/CHART-LAYOUT.md +0 -157
  28. package/docs/DARK-TOKENS.md +0 -140
  29. package/docs/TYPE-SCALE.md +0 -111
  30. package/docs/messaging-deviations.md +0 -118
  31. package/index.html +0 -12
  32. package/renovate.json +0 -10
  33. package/scripts/chart-api-census.mjs +0 -415
  34. package/src/auth/AuthContext.jsx +0 -158
  35. package/src/auth/ReauthModal.jsx +0 -82
  36. package/src/auth/UserMenu.jsx +0 -121
  37. package/src/auth/apiClient.jsx +0 -232
  38. package/src/auth/authApi.jsx +0 -539
  39. package/src/auth/authConfig.jsx +0 -34
  40. package/src/auth/reauth.js +0 -47
  41. package/src/chat/ChatComposer.jsx +0 -36
  42. package/src/chat/ChatList.jsx +0 -87
  43. package/src/chat/ChatThread.jsx +0 -128
  44. package/src/chat/ChatWorkspace.jsx +0 -244
  45. package/src/chat/MarkdownReply.jsx +0 -22
  46. package/src/chat/ModelPicker.jsx +0 -85
  47. package/src/chat/api.js +0 -18
  48. package/src/chat/useChatSocket.js +0 -32
  49. package/src/chat/wire.js +0 -7
  50. package/src/components/AccessCodeManager.jsx +0 -288
  51. package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
  52. package/src/components/AllowedEmailDomainsManager.jsx +0 -92
  53. package/src/components/AuthFactorRequirementCard.jsx +0 -95
  54. package/src/components/BulkInviteCsvTab.jsx +0 -198
  55. package/src/components/EmptyState.jsx +0 -38
  56. package/src/components/LoginForm.jsx +0 -159
  57. package/src/components/MFAComponent.jsx +0 -350
  58. package/src/components/MfaLoginComponent.jsx +0 -252
  59. package/src/components/MobileBottomNav.jsx +0 -82
  60. package/src/components/PasskeysComponent.jsx +0 -252
  61. package/src/components/PasswordChangeForm.jsx +0 -87
  62. package/src/components/PasswordResetRequestForm.jsx +0 -43
  63. package/src/components/PasswordSetForm.jsx +0 -97
  64. package/src/components/ProfileComponent.jsx +0 -348
  65. package/src/components/QrSignupManager.jsx +0 -404
  66. package/src/components/QrSignupValidityManager.jsx +0 -106
  67. package/src/components/RegistrationMethodsManager.jsx +0 -150
  68. package/src/components/SecurityComponent.jsx +0 -141
  69. package/src/components/SocialLoginButtons.jsx +0 -83
  70. package/src/components/SoftChip.jsx +0 -93
  71. package/src/components/StatTile.jsx +0 -39
  72. package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
  73. package/src/components/TurnstileWidget.jsx +0 -99
  74. package/src/components/UserInviteComponent.jsx +0 -76
  75. package/src/components/UserListComponent.jsx +0 -618
  76. package/src/components/charts/BarChart.jsx +0 -102
  77. package/src/components/charts/ChartFrame.jsx +0 -214
  78. package/src/components/charts/LineChart.jsx +0 -126
  79. package/src/components/charts/ScatterChart.jsx +0 -299
  80. package/src/components/charts/TimeSeriesChart.jsx +0 -300
  81. package/src/components/charts/chartDefaults.js +0 -644
  82. package/src/components/charts/chartLabels.js +0 -36
  83. package/src/components/charts/exportChart.js +0 -258
  84. package/src/components/charts/formatters.js +0 -39
  85. package/src/components/charts/palette.js +0 -33
  86. package/src/components/charts/yearTickInterval.js +0 -7
  87. package/src/i18n/authTranslations.ts +0 -2651
  88. package/src/i18n/chartsTranslations.ts +0 -91
  89. package/src/i18n/chatTranslations.ts +0 -55
  90. package/src/i18n/messagingTranslations.ts +0 -120
  91. package/src/i18n/notificationsTranslations.ts +0 -32
  92. package/src/i18n/onboardingTranslations.ts +0 -29
  93. package/src/i18n/sectionNavTranslations.ts +0 -20
  94. package/src/i18n/uiCoreTranslations.js +0 -29
  95. package/src/i18n/userMenuTranslations.ts +0 -5
  96. package/src/index.js +0 -158
  97. package/src/layout/PageLayout.jsx +0 -36
  98. package/src/layout/SectionNav.jsx +0 -320
  99. package/src/messaging/AttachmentList.jsx +0 -145
  100. package/src/messaging/Composer.jsx +0 -197
  101. package/src/messaging/ConversationLaunchers.jsx +0 -51
  102. package/src/messaging/ConversationList.jsx +0 -109
  103. package/src/messaging/DirectMessageLauncher.jsx +0 -112
  104. package/src/messaging/MessageBubble.jsx +0 -183
  105. package/src/messaging/MessagingProvider.jsx +0 -0
  106. package/src/messaging/MessagingScopeConfig.jsx +0 -24
  107. package/src/messaging/PollCard.jsx +0 -163
  108. package/src/messaging/ReactionBar.jsx +0 -43
  109. package/src/messaging/ReadTicks.jsx +0 -82
  110. package/src/messaging/Thread.jsx +0 -195
  111. package/src/messaging/api.js +0 -48
  112. package/src/messaging/composerImageCompression.js +0 -35
  113. package/src/messaging/linkifyText.jsx +0 -37
  114. package/src/notifications/NotificationBell.jsx +0 -84
  115. package/src/notifications/NotificationSettings.jsx +0 -330
  116. package/src/notifications/NotificationsProvider.jsx +0 -224
  117. package/src/notifications/PopupSurface.jsx +0 -105
  118. package/src/notifications/api.js +0 -55
  119. package/src/notifications/feedApi.js +0 -18
  120. package/src/notifications/realtime.jsx +0 -138
  121. package/src/notifications/serviceWorker/sw.js +0 -50
  122. package/src/onboarding/OnboardingProvider.jsx +0 -326
  123. package/src/onboarding/OnboardingWizard.jsx +0 -61
  124. package/src/onboarding/WizardDialogShell.jsx +0 -51
  125. package/src/onboarding/api.js +0 -13
  126. package/src/onboarding/stepSelection.js +0 -16
  127. package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
  128. package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
  129. package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
  130. package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
  131. package/src/pages/AccountPage.jsx +0 -394
  132. package/src/pages/LoginPage.jsx +0 -343
  133. package/src/pages/PasswordChangePage.jsx +0 -62
  134. package/src/pages/PasswordInvitePage.jsx +0 -177
  135. package/src/pages/PasswordResetRequestPage.jsx +0 -68
  136. package/src/pages/SignUpPage.jsx +0 -268
  137. package/src/pages/SignupConfirmPage.jsx +0 -155
  138. package/src/testing/checkKitIntegration.js +0 -233
  139. package/src/theme/createAppTheme.js +0 -423
  140. package/src/theme/fonts.js +0 -3
  141. package/src/theme/index.js +0 -21
  142. package/src/theme/kitSxRegistry.js +0 -285
  143. package/src/theme/themeCompleteness.js +0 -1045
  144. package/src/theme/tokens.js +0 -478
  145. package/src/utils/auth-errors.js +0 -76
  146. package/src/utils/authService.js +0 -223
  147. package/src/utils/resolveErrorText.js +0 -28
  148. package/src/utils/webauthn.js +0 -51
  149. package/tests/AccountPage.profileStatements.test.jsx +0 -162
  150. package/tests/AccountPage.test.jsx +0 -200
  151. package/tests/AuthContext.test.jsx +0 -129
  152. package/tests/BarChart.test.jsx +0 -112
  153. package/tests/BrowserPushStep.test.jsx +0 -110
  154. package/tests/ChartFrame.test.jsx +0 -336
  155. package/tests/ConversationList.test.jsx +0 -144
  156. package/tests/CookieConsentStep.test.jsx +0 -69
  157. package/tests/EmptyState.test.jsx +0 -62
  158. package/tests/LineChart.test.jsx +0 -80
  159. package/tests/MessagingProvider.test.jsx +0 -50
  160. package/tests/MobileBottomNav.test.jsx +0 -319
  161. package/tests/NotificationBell.test.jsx +0 -60
  162. package/tests/NotificationSettings.test.jsx +0 -276
  163. package/tests/NotificationsProvider.test.jsx +0 -240
  164. package/tests/OnboardingProvider.test.jsx +0 -294
  165. package/tests/OnboardingWizard.test.jsx +0 -143
  166. package/tests/PasswordChangeForm.test.jsx +0 -70
  167. package/tests/PasswordInvitePage.test.jsx +0 -142
  168. package/tests/PasswordSetForm.test.jsx +0 -70
  169. package/tests/PopupSurface.test.jsx +0 -142
  170. package/tests/ProfileComponent.refresh.test.jsx +0 -194
  171. package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
  172. package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
  173. package/tests/ScatterChart.test.jsx +0 -270
  174. package/tests/SectionNav.test.jsx +0 -363
  175. package/tests/SignUpPage.test.jsx +0 -208
  176. package/tests/SoftChip.test.jsx +0 -105
  177. package/tests/StatTile.test.jsx +0 -57
  178. package/tests/Thread.test.jsx +0 -151
  179. package/tests/TimeSeriesChart.test.jsx +0 -367
  180. package/tests/UserListComponent.test.jsx +0 -192
  181. package/tests/UserMenu.test.jsx +0 -171
  182. package/tests/apiClientLanguage.test.js +0 -33
  183. package/tests/apiClientPublicPaths.test.js +0 -178
  184. package/tests/authApiLogin.test.js +0 -42
  185. package/tests/authApiRegistration.test.js +0 -31
  186. package/tests/authErrorCodeRendering.test.jsx +0 -77
  187. package/tests/authErrors.test.js +0 -74
  188. package/tests/authTranslations.test.js +0 -74
  189. package/tests/chart-api-census.test.js +0 -366
  190. package/tests/chartAxisTickBand.test.jsx +0 -95
  191. package/tests/chartDefaults.test.js +0 -902
  192. package/tests/chartLabels.test.js +0 -50
  193. package/tests/chartsPalette.test.js +0 -99
  194. package/tests/chatApi.test.js +0 -48
  195. package/tests/chatDriverNames.test.jsx +0 -71
  196. package/tests/chatExports.test.js +0 -3
  197. package/tests/chatList.test.jsx +0 -84
  198. package/tests/chatPicker.test.jsx +0 -82
  199. package/tests/chatSocket.test.jsx +0 -26
  200. package/tests/chatThread.test.jsx +0 -116
  201. package/tests/chatTranslations.test.js +0 -47
  202. package/tests/chatWorkspace.test.jsx +0 -234
  203. package/tests/checkKitIntegration.test.jsx +0 -219
  204. package/tests/createAppTheme.test.js +0 -132
  205. package/tests/createAppThemeDark.test.js +0 -127
  206. package/tests/createAppThemeLightGuard.test.js +0 -40
  207. package/tests/createAppThemePageContrast.test.js +0 -108
  208. package/tests/createAppThemeTypography.test.js +0 -53
  209. package/tests/errorCallSiteGuard.test.js +0 -32
  210. package/tests/exportChart.test.js +0 -313
  211. package/tests/feedApi.test.js +0 -46
  212. package/tests/fixtures/light-theme-baseline.json +0 -2234
  213. package/tests/i18nAggregate.test.js +0 -120
  214. package/tests/i18nGermanVariant.test.js +0 -65
  215. package/tests/loginPageErrorResolution.test.jsx +0 -102
  216. package/tests/messagingApi.test.js +0 -32
  217. package/tests/messagingChunk6.test.jsx +0 -116
  218. package/tests/messagingCompactBubble.test.jsx +0 -64
  219. package/tests/messagingComposer.test.jsx +0 -136
  220. package/tests/messagingComposerChunk5.test.jsx +0 -113
  221. package/tests/messagingContractConformance.test.js +0 -69
  222. package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
  223. package/tests/messagingExports.test.js +0 -21
  224. package/tests/messagingInteractions.test.jsx +0 -136
  225. package/tests/messagingLastMessagePreview.test.jsx +0 -93
  226. package/tests/messagingLinkify.test.jsx +0 -51
  227. package/tests/messagingMessageActions.test.jsx +0 -163
  228. package/tests/messagingMsg6.test.jsx +0 -105
  229. package/tests/messagingMsg6f.test.jsx +0 -155
  230. package/tests/messagingMsg6g.test.jsx +0 -166
  231. package/tests/messagingMsg6h.test.jsx +0 -136
  232. package/tests/messagingMsg6i.test.jsx +0 -129
  233. package/tests/messagingPollRendering.test.jsx +0 -142
  234. package/tests/messagingReactionMerge.test.jsx +0 -77
  235. package/tests/messagingReadTicksTooltip.test.jsx +0 -36
  236. package/tests/messagingThreadReceipts.test.jsx +0 -178
  237. package/tests/messagingThreadingQuoting.test.jsx +0 -87
  238. package/tests/messagingUnreadLifecycle.test.jsx +0 -78
  239. package/tests/notificationsApi.test.js +0 -41
  240. package/tests/notificationsExports.test.js +0 -34
  241. package/tests/onboardingDescriptors.test.js +0 -132
  242. package/tests/packageTreeShaking.test.js +0 -126
  243. package/tests/realtime.test.jsx +0 -219
  244. package/tests/resolveErrorText.test.js +0 -72
  245. package/tests/stepSelection.test.js +0 -53
  246. package/tests/themeCompleteness.test.js +0 -626
  247. package/tests/timeSeriesLegend.test.js +0 -99
  248. package/tests/userScreenTranslations.test.jsx +0 -218
  249. package/tsconfig.build.json +0 -18
  250. package/vite.config.mjs +0 -6
  251. package/work-orders/AUTH-1.md +0 -74
  252. package/work-orders/AUTH-2.md +0 -74
  253. package/work-orders/AUTH-4.md +0 -183
  254. package/work-orders/AUTH-5.md +0 -154
  255. package/work-orders/AUTH-7.md +0 -181
  256. package/work-orders/AUTH-8.md +0 -184
  257. package/work-orders/CHART-1.md +0 -285
  258. package/work-orders/CHART-2.md +0 -222
  259. package/work-orders/CHART-3.md +0 -163
  260. package/work-orders/CHART-4.md +0 -203
  261. package/work-orders/CHART-5.md +0 -226
  262. package/work-orders/CHART-6.md +0 -428
  263. package/work-orders/CHART-7.md +0 -175
  264. package/work-orders/DX-1.md +0 -169
  265. package/work-orders/DX-2.md +0 -188
  266. package/work-orders/DX-3.md +0 -195
  267. package/work-orders/I18N-1.md +0 -254
  268. package/work-orders/I18N-3.md +0 -207
  269. package/work-orders/MSG-3.md +0 -375
  270. package/work-orders/MSG-3b.md +0 -1002
  271. package/work-orders/MSG-3c.md +0 -378
  272. package/work-orders/MSG-3d.md +0 -268
  273. package/work-orders/MSG-3e.md +0 -164
  274. package/work-orders/MSG-3f.md +0 -391
  275. package/work-orders/MSG-6e.md +0 -308
  276. package/work-orders/MSG-6f.md +0 -214
  277. package/work-orders/MSG-6g.md +0 -244
  278. package/work-orders/MSG-6h.md +0 -238
  279. package/work-orders/MSG-6i.md +0 -271
  280. package/work-orders/MSG-6j.md +0 -117
  281. package/work-orders/NOTIF-12.md +0 -214
  282. package/work-orders/NOTIF-13.md +0 -142
  283. package/work-orders/NOTIF-14.md +0 -151
  284. package/work-orders/PRIM-1.md +0 -177
  285. package/work-orders/SHELL-1.md +0 -255
  286. package/work-orders/SHELL-2.md +0 -197
  287. package/work-orders/SHELL-3.md +0 -359
  288. package/work-orders/SHELL-4.md +0 -351
  289. package/work-orders/SHELL-5.md +0 -345
  290. package/work-orders/SHELL-6.md +0 -210
  291. package/work-orders/THEME-1.md +0 -587
  292. package/work-orders/THEME-10.md +0 -303
  293. package/work-orders/THEME-11.md +0 -208
  294. package/work-orders/THEME-2.md +0 -339
  295. package/work-orders/THEME-3.md +0 -210
  296. package/work-orders/THEME-4.md +0 -271
  297. package/work-orders/THEME-5.md +0 -341
  298. package/work-orders/THEME-6.md +0 -247
  299. package/work-orders/THEME-7.md +0 -82
  300. package/work-orders/THEME-8.md +0 -211
  301. package/work-orders/THEME-9.md +0 -194
  302. package/work-orders/UCM-AUTH-10.md +0 -490
  303. package/work-orders/UCM-AUTH-11.md +0 -185
  304. package/work-orders/UCM-AUTH-12.md +0 -98
  305. package/work-orders/UCM-AUTH-13.md +0 -47
  306. package/work-orders/UCM-AUTH-9.md +0 -207
  307. package/work-orders/UCM-CHART-10.md +0 -150
  308. package/work-orders/UCM-CHART-11.md +0 -132
  309. package/work-orders/UCM-CHART-12.md +0 -348
  310. package/work-orders/UCM-CHART-13.md +0 -122
  311. package/work-orders/UCM-CHART-14.md +0 -198
  312. package/work-orders/UCM-CHART-15.md +0 -187
  313. package/work-orders/UCM-CHART-16.md +0 -169
  314. package/work-orders/UCM-CHART-17.md +0 -195
  315. package/work-orders/UCM-CHART-18.md +0 -200
  316. package/work-orders/UCM-CHART-8.md +0 -147
  317. package/work-orders/UCM-CHART-9.md +0 -265
  318. package/work-orders/UCM-CHAT-1.md +0 -190
  319. package/work-orders/UCM-CHAT-2.md +0 -75
  320. package/work-orders/UCM-CI-1.md +0 -65
  321. package/work-orders/UCM-DEP-1.md +0 -63
  322. package/work-orders/UCM-I18N-4.md +0 -226
  323. package/work-orders/UCM-I18N-5.md +0 -211
  324. package/work-orders/UCM-MSG-20.md +0 -192
  325. package/work-orders/UCM-PRIM-2.md +0 -114
  326. package/work-orders/UCM-SHELL-7.md +0 -134
  327. package/work-orders/UCM-SHELL-8.md +0 -125
  328. package/work-orders/UCM-TEST-1.md +0 -304
  329. package/work-orders/UCM-THEME-13.md +0 -69
  330. package/work-orders/UCM-THEME-14.md +0 -312
  331. package/work-orders/UCM-THEME-15.md +0 -356
  332. package/work-orders/UCM-THEME-16.md +0 -80
  333. package/work-orders/UCM-THEME-17.md +0 -111
  334. package/work-orders/UCM-THEME-18.md +0 -184
  335. package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
  336. package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
  337. package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
  338. package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
  339. package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
  340. package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
  341. package/work-orders/assets/UCM-THEME-17-type-scale.html +0 -391
@@ -1,299 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import { Box } from '@mui/material';
3
- import { useTheme } from '@mui/material/styles';
4
- import { useTranslation } from 'react-i18next';
5
- import { ScatterChart as MuiScatterChart } from '@mui/x-charts/ScatterChart';
6
- import { useXScale, useYScale } from '@mui/x-charts/hooks';
7
- import { useNeutralChartPalette } from './palette';
8
- import {
9
- DEFAULT_LEGEND_POSITION,
10
- assertRemovedChartProp,
11
- defaultNumericTickFormatter,
12
- resolveChartLayout,
13
- withAxisDefaults,
14
- withChartSlotDefaults,
15
- withGridDefaults,
16
- } from './chartDefaults';
17
-
18
- // Small enough that hram's largest cloud (~300 points, the tested magnitude -- state it, don't
19
- // imply it scales past that) doesn't fuse into a solid mass at the default (non-bubble) size.
20
- const DEFAULT_MARKER_RADIUS = 5;
21
- const BUBBLE_MIN_RADIUS = 3;
22
- const BUBBLE_RADIUS_SCALE = 10;
23
-
24
- /**
25
- * value -> radius, `max(3, 10 * sqrt(value / maxValue))` -- the formula the access-gap panel
26
- * already hand-rolls (THEME-10 scope 5). Exported so a consumer can size a legend swatch or a
27
- * standalone marker the same way the chart does.
28
- */
29
- export function scaleBubbleRadius(value, maxValue, { minRadius = BUBBLE_MIN_RADIUS, scale = BUBBLE_RADIUS_SCALE } = {}) {
30
- if (!Number.isFinite(value) || !Number.isFinite(maxValue) || maxValue <= 0) return minRadius;
31
- return Math.max(minRadius, scale * Math.sqrt(Math.max(0, value) / maxValue));
32
- }
33
-
34
- // A custom MUI marker slot -- the only way to vary radius/colour/shape PER POINT: MUI's own
35
- // Scatter passes a single `markerSize` per whole series (confirmed against
36
- // @mui/x-charts/ScatterChart/Scatter.js -- `size: series.markerSize`, never derived from a
37
- // point's own `z`), so bubble sizing and per-point overrides (a hollow status-quo marker, a
38
- // continuous-scale colour) can only be expressed by replacing the marker itself.
39
- function makeMarkerSlot(seriesById, { sizeAccessor, chartMax, getPointStyle }) {
40
- return function PresetScatterMarker({
41
- seriesId, dataIndex, color, x, y, isHighlighted, isFaded, size, onClick, ...rest
42
- }) {
43
- const point = seriesById[seriesId]?.data?.[dataIndex];
44
- const style = point && getPointStyle ? (getPointStyle(point, { seriesId, dataIndex }) || {}) : {};
45
- const radius = style.radius
46
- ?? (sizeAccessor && point ? scaleBubbleRadius(sizeAccessor(point), chartMax) : size);
47
- const r = (isHighlighted ? 1.15 : 1) * radius;
48
- const fill = style.hollow ? 'none' : (style.color ?? color);
49
- const stroke = style.color ?? color;
50
- const shared = {
51
- onClick,
52
- opacity: isFaded ? 0.3 : 1,
53
- stroke,
54
- strokeWidth: style.hollow ? 2 : (style.strokeWidth ?? 1),
55
- style: { cursor: onClick ? 'pointer' : 'unset' },
56
- ...rest,
57
- };
58
- if (style.shape === 'square') {
59
- return <rect x={x - r} y={y - r} width={r * 2} height={r * 2} fill={fill} {...shared} />;
60
- }
61
- if (style.shape === 'diamond') {
62
- return <polygon points={`${x},${y - r} ${x + r},${y} ${x},${y + r} ${x - r},${y}`} fill={fill} {...shared} />;
63
- }
64
- return <circle cx={x} cy={y} r={r} fill={fill} {...shared} />;
65
- };
66
- }
67
-
68
- /**
69
- * A computed curve (allocation's Pareto envelope) drawn inside the chart's own live coordinate
70
- * space. Render as a `ScatterChart` child -- `useXScale`/`useYScale` only resolve inside the
71
- * chart's own context, so this can never drift into a second, hand-rolled pixel mapping the way
72
- * the hand-drawn panels this preset replaces each built their own.
73
- */
74
- export function ScatterReferenceCurve({ points, color, dashed, strokeWidth = 2 }) {
75
- const xScale = useXScale();
76
- const yScale = useYScale();
77
- if (!points || points.length < 2) return null;
78
- const d = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${xScale(p.x)},${yScale(p.y)}`).join(' ');
79
- return <path d={d} fill="none" stroke={color} strokeWidth={strokeWidth} strokeDasharray={dashed ? '6 3' : undefined} />;
80
- }
81
-
82
- /**
83
- * A straight reference line with an optional inline label -- the access panel's labelled y=x
84
- * "no gap" diagonal, or optimization's dashed threshold line. Same live-scale rule as
85
- * `ScatterReferenceCurve`.
86
- */
87
- export function ScatterReferenceLine({
88
- from, to, color, dashed, strokeWidth = 1.5, label, labelAt, labelAngle = 0,
89
- }) {
90
- const xScale = useXScale();
91
- const yScale = useYScale();
92
- const labelX = labelAt ? xScale(labelAt.x) : null;
93
- const labelY = labelAt ? yScale(labelAt.y) : null;
94
- return (
95
- <g>
96
- <line
97
- x1={xScale(from.x)}
98
- y1={yScale(from.y)}
99
- x2={xScale(to.x)}
100
- y2={yScale(to.y)}
101
- stroke={color}
102
- strokeWidth={strokeWidth}
103
- strokeDasharray={dashed ? '5 3' : undefined}
104
- />
105
- {label && labelAt && (
106
- <text
107
- x={labelX}
108
- y={labelY}
109
- fontSize={10}
110
- fill={color}
111
- textAnchor="start"
112
- transform={labelAngle ? `rotate(${labelAngle}, ${labelX}, ${labelY})` : undefined}
113
- >
114
- {label}
115
- </text>
116
- )}
117
- </g>
118
- );
119
- }
120
-
121
- /**
122
- * Responsive MUI X-Charts scatter preset, drawn from three real consumers (THEME-10): a
123
- * continuously-coloured cost/outcome cloud with an envelope curve and marked points
124
- * (allocation performance), a bubble cloud with a labelled y=x reference diagonal and
125
- * categorical per-point colouring (access gap), and a cloud with a dashed reference line and a
126
- * hollow status-quo marker (optimization results). Reuses BarChart/LineChart's own axis
127
- * machinery (THEME-9) rather than sizing its own -- a scatter axis is a numeric axis like any
128
- * other, and a preset that re-derives axis sizing would re-open exactly what THEME-9 closed.
129
- *
130
- * `series` follows MUI's own scatter shape (`{id, label, data: [{x,y,z?,id?}], color?}`).
131
- * Multiple series render as a categorical cloud with a discrete legend, one colour per series
132
- * -- draw those colours from the app's own family/role register via `palette`; this package
133
- * does not invent one. A single series with no explicit `color` renders as one neutral mass:
134
- * undifferentiated data encodes no dimension, so it gets the role register's neutral tone
135
- * (`palette.js`'s `neutral`), never a series/KPI identity hue.
136
- *
137
- * `sizeAccessor(point) => number` turns on bubble mode: marker radius follows
138
- * `scaleBubbleRadius`, and every series' points are drawn largest-first so a small bubble is
139
- * never hidden under a large one -- the z-order is the whole correctness of a bubble chart, not
140
- * a decoration.
141
- *
142
- * `getPointStyle(point, {seriesId, dataIndex}) => {hollow?, color?, shape?, radius?,
143
- * strokeWidth?}` marks individual points -- a status-quo point drawn hollow against filled
144
- * candidates (allocation, optimization), a `'square'` shape for a source category the data
145
- * itself marks (allocation's "targeted" points), or a per-point colour from a continuous scale
146
- * the caller computed (this package does not ship a continuous colour-scale/legend component;
147
- * the caller already owns that logic and only needs a way to apply the resolved colour per
148
- * point, which this is it). Read the `point` object itself, not `dataIndex`, to correlate a
149
- * style back to your own data -- in bubble mode (`sizeAccessor` set) `dataIndex` indexes the
150
- * chart's internal z-ordered copy of your series, not your original array's order.
151
- *
152
- * Reference geometry (a computed curve, a straight line, either with an optional label) is
153
- * composed as `children` using `ScatterReferenceCurve`/`ScatterReferenceLine` above.
154
- *
155
- * `size` (UCM-CHART-12, UCM-CHART-15): `"compact" | "standard" | "tall" | "extra_tall" |
156
- * "super_tall"`, resolved through the theme's spacing scale by `resolveChartLayout`. `height`
157
- * (px) is the documented escape for a justified special case -- prefer `size`.
158
- * `minHeight`/`aspect`/`margin` are gone (see docs/CHART-LAYOUT.md).
159
- *
160
- * Tested magnitude: a few hundred marks (hram's largest cloud is ~300 points). Not verified,
161
- * and not implied to hold, at thousands+.
162
- *
163
- * THEME-10 was commissioned before its requirement set closed -- a forthcoming "paired point"
164
- * shape (optimization's MILP-vs-simulated pair, two marks joined by a connector) is named but
165
- * explicitly NOT built here. Nothing below assumes a datum produces exactly one mark: a point
166
- * object may carry arbitrary extra fields beyond `{x,y,z,id}` (as `getPointStyle`'s own status-
167
- * quo/bubble-radius usage already does), `getPointStyle`/`sizeAccessor` key off that datum, not
168
- * off a fixed mark count, and reference geometry is composed via `children`, not baked into the
169
- * per-point contract. A pair variant can therefore layer on later -- extra series entries plus a
170
- * connector drawn through `children` -- without reshaping `series` or either callback's contract.
171
- */
172
- export function ScatterChart({
173
- series = [],
174
- xAxisLabel,
175
- yAxisLabel,
176
- xAxis,
177
- yAxis,
178
- palette,
179
- size = 'standard',
180
- height,
181
- minHeight,
182
- aspect,
183
- margin,
184
- xLabels = 'auto',
185
- grid,
186
- hideLegend = series.length <= 1,
187
- legendPosition = DEFAULT_LEGEND_POSITION,
188
- sizeAccessor,
189
- getPointStyle,
190
- markerSize = DEFAULT_MARKER_RADIUS,
191
- slotProps,
192
- children,
193
- ...chartProps
194
- }) {
195
- const theme = useTheme();
196
- const { i18n } = useTranslation();
197
- const neutralPalette = useNeutralChartPalette();
198
-
199
- assertRemovedChartProp('ScatterChart', 'minHeight', minHeight, 'Use size="compact" | "standard" | "tall" | "extra_tall" | "super_tall", or height for the documented escape.');
200
- assertRemovedChartProp('ScatterChart', 'aspect', aspect, 'Removed with no replacement -- pick a size token; the chart no longer tracks width.');
201
- assertRemovedChartProp('ScatterChart', 'margin', margin, 'Removed -- the layout model owns margins completely.');
202
-
203
- const labelledXAxis = withAxisDefaults(
204
- xAxis,
205
- xAxisLabel,
206
- { scaleType: 'linear', valueFormatter: defaultNumericTickFormatter(i18n.language) },
207
- theme.typography.caption.fontSize,
208
- );
209
- const labelledYAxis = withAxisDefaults(
210
- yAxis,
211
- yAxisLabel,
212
- { scaleType: 'linear', valueFormatter: defaultNumericTickFormatter(i18n.language) },
213
- theme.typography.caption.fontSize,
214
- );
215
- // `resolveChartLayout`'s own y-axis sizing (via `sizeYAxisForContent`) expects `series[i].data`
216
- // to be a plain array of numbers -- true for BarChart/LineChart, but a scatter's data is
217
- // `{x,y,z,id}` points. Adapt AT THIS CALL SITE, not inside the shared resolver -- BarChart/
218
- // LineChart's own contract must not change for this (reviewer finding, live-caught under
219
- // THEME-10: the untested case silently found zero candidates and fell back to MUI's flat
220
- // default at a magnitude that didn't visibly overlap).
221
- const yValueSeries = useMemo(
222
- () => series.map((item) => ({ ...item, data: (item.data || []).map((point) => point?.y) })),
223
- [series],
224
- );
225
- const layout = resolveChartLayout({
226
- size,
227
- height,
228
- xAxis: labelledXAxis,
229
- yAxis: labelledYAxis,
230
- series: yValueSeries,
231
- xLabels,
232
- hideLegend,
233
- legendPosition,
234
- slotProps,
235
- tickFontSize: theme.typography.caption.fontSize,
236
- spacing: theme.spacing,
237
- defaultLineHeight: theme.typography.caption.lineHeight,
238
- });
239
-
240
- const isNeutral = series.length <= 1 && !series[0]?.color;
241
- const resolvedPalette = palette || (isNeutral ? [neutralPalette.neutral] : neutralPalette.categorical);
242
-
243
- // A single chart-wide max, NOT one per series: population (or whatever sizeAccessor reads) is
244
- // one comparable scale across the whole cloud (matches the access-gap panel's own
245
- // `maxPop = Math.max(1, ...points.map(p => p.population))`, computed over every point
246
- // regardless of division/settlement) -- a per-series max would let each series' own largest
247
- // point always render at full radius, making cross-series bubble size meaningless.
248
- const chartMax = useMemo(() => {
249
- if (!sizeAccessor) return 1;
250
- return Math.max(
251
- 1,
252
- ...series.flatMap((item) => (item.data || []).map((point) => sizeAccessor(point))).filter(Number.isFinite),
253
- );
254
- }, [series, sizeAccessor]);
255
-
256
- // THEME-10 scope 5: explicit z-order -- largest bubbles drawn first so smaller ones are never
257
- // hidden underneath (SVG paints later elements on top). A no-op outside bubble mode.
258
- const orderedSeries = useMemo(() => (
259
- sizeAccessor
260
- ? series.map((item) => ({
261
- ...item,
262
- data: [...(item.data || [])].sort((a, b) => sizeAccessor(b) - sizeAccessor(a)),
263
- }))
264
- : series
265
- ), [series, sizeAccessor]);
266
-
267
- // The marker's `dataIndex` refers to a position in whatever was actually handed to MUI --
268
- // `orderedSeries` (post z-order sort), not the caller's original `series` -- so the lookup
269
- // table the marker closes over must be built from that SAME array, or a sorted bubble series
270
- // would resolve every point's style/radius against the wrong (pre-sort) index.
271
- const orderedSeriesById = useMemo(
272
- () => Object.fromEntries(orderedSeries.map((item) => [item.id, item])),
273
- [orderedSeries],
274
- );
275
- const markerSlot = useMemo(
276
- () => makeMarkerSlot(orderedSeriesById, { sizeAccessor, chartMax, getPointStyle }),
277
- [orderedSeriesById, sizeAccessor, chartMax, getPointStyle],
278
- );
279
-
280
- return (
281
- <Box data-testid="scatter-chart-container" sx={layout.sx}>
282
- <MuiScatterChart
283
- {...chartProps}
284
- height={layout.chartHeight}
285
- series={orderedSeries.map((item) => ({ markerSize, ...item }))}
286
- xAxis={layout.xAxis}
287
- yAxis={layout.yAxis}
288
- colors={resolvedPalette}
289
- grid={withGridDefaults(grid)}
290
- hideLegend={hideLegend}
291
- margin={layout.margin}
292
- slots={{ marker: markerSlot }}
293
- slotProps={withChartSlotDefaults(slotProps, legendPosition)}
294
- >
295
- {children}
296
- </MuiScatterChart>
297
- </Box>
298
- );
299
- }
@@ -1,300 +0,0 @@
1
- import React, { useState } from 'react';
2
- import {
3
- Box,
4
- Checkbox,
5
- FormControlLabel,
6
- FormGroup,
7
- Radio,
8
- RadioGroup,
9
- } from '@mui/material';
10
- import { useTranslation } from 'react-i18next';
11
- import { ChartFrame } from './ChartFrame';
12
- import { BarChart } from './BarChart';
13
- import { useNeutralChartPalette } from './palette';
14
-
15
- // Fixed platform-wide range -> granularity mapping (operator decision, CHART-2 /
16
- // dcm ACT-1) — the resolution follows the window and is never an independent
17
- // caller-supplied control. Every preset lands between a dozen and fifty points.
18
- // Preset default height (CHART-4): MUI X-Charts' responsive container
19
- // measures its actual parent height via ResizeObserver and draws nothing
20
- // if that parent has none — ChartFrame's flex-centered content Box does
21
- // NOT stretch its child to fill available height (alignItems: 'center'),
22
- // so BarChart needs its own explicit size, not just ChartFrame's.
23
- // `height` forwards straight to MuiBarChart's own native height prop
24
- // (bypassing ResizeObserver-based measurement entirely), matching the
25
- // confirmed-working dev/entries.jsx BarChartEntry reference.
26
- // Deliberate, known exception to DESIGN.md #14 ("responsive = container-
27
- // sized, not fixed px"): width stays responsive (unset, still auto-
28
- // measured), only height is fixed, and only because MUI X-Charts' own
29
- // ResizeObserver-based height measurement is what silently produced a
30
- // zero-height chart in the first place (CHART-4). Do not "fix" this back
31
- // to a responsive/aspect-ratio height without re-verifying the underlying
32
- // MUI measurement bug is actually resolved.
33
- //
34
- // UCM-CHART-12: `CHART_HEIGHT` (320) is the number `"standard"` (BarChart's new `size` token) was
35
- // deliberately pinned to, specifically so this preset's own default did not silently move under
36
- // this migration -- still used here as ChartFrame's own `minHeight` floor (a card concern,
37
- // unaffected by CHART-12), and the inner BarChart now takes `size="standard"` instead of `height`.
38
- const CHART_HEIGHT = 320;
39
-
40
- // Dynamic sx factories are outside the registry's documented top-level-object
41
- // lower bound; the series colour is data-dependent at render time.
42
- const seriesToggleSx = (colour) => ({
43
- color: colour,
44
- '&.Mui-checked': { color: colour },
45
- });
46
-
47
- // CHART-5: how many x-axis tick labels to show, evenly spread across
48
- // data.xLabels, regardless of container width or label length. Replaces
49
- // MUI's own collision-based tick filtering (which, given enough densely
50
- // packed categories, can end up hiding every single label — confirmed live
51
- // with 24 hourly buckets) with a deterministic guarantee that some labels
52
- // always render. See makeTickLabelInterval below.
53
- const MAX_X_TICKS = 8;
54
-
55
- function makeTickLabelInterval(labelCount) {
56
- const step = Math.max(1, Math.ceil(labelCount / MAX_X_TICKS));
57
- const lastIndex = labelCount - 1;
58
- // `index % step === 0` alone systematically drops the final bucket unless
59
- // it happens to land exactly on a step boundary (e.g. 24 labels / step 3
60
- // covers indices 0..21, never 23) -- the most recent time bucket is
61
- // usually the one that matters most, so it is always force-included.
62
- return (_value, index) => index % step === 0 || index === lastIndex;
63
- }
64
-
65
- // CHART-5: a y-axis is "integer" when every value across its currently
66
- // visible series is a whole number — re-evaluated on every toggle (an axis
67
- // can flip from fractional to integer, or vice versa, as series are shown/
68
- // hidden). Empty (no visible series on this axis) is deliberately NOT
69
- // integer: there's nothing to format, and treating an empty list as
70
- // vacuously "all integer" would apply the formatter to an axis with no data.
71
- function isIntegerAxis(seriesList) {
72
- return seriesList.length > 0 && seriesList.every(
73
- (series) => (series.data || []).every((value) => Number.isInteger(value)),
74
- );
75
- }
76
-
77
- // Blanks the tick label for a non-integer value on an axis whose visible
78
- // series are otherwise all whole numbers -- a deterministic backstop since
79
- // d3's own "nice tick" choice for a small integer domain can still land on
80
- // a fractional position (e.g. domain [0,3] ticking at 0, 1.5, 3). Tooltip/
81
- // legend formatting (a different `location`) is left untouched -- only the
82
- // on-axis tick text is ever blanked.
83
- function integerTickFormatter(value, context) {
84
- if (context?.location !== 'tick') return String(value);
85
- return Number.isInteger(value) ? String(value) : '';
86
- }
87
-
88
- const RANGE_OPTIONS = [
89
- { key: '1d', granularity: 'hour', labelKey: 'TimeSeriesChart.RANGE_1_DAY' },
90
- { key: '1w', granularity: '4hour', labelKey: 'TimeSeriesChart.RANGE_1_WEEK' },
91
- { key: '1m', granularity: 'day', labelKey: 'TimeSeriesChart.RANGE_1_MONTH' },
92
- { key: '1y', granularity: 'month', labelKey: 'TimeSeriesChart.RANGE_1_YEAR' },
93
- ];
94
-
95
- function SeriesToggleDot({ color, active }) {
96
- return (
97
- <Box
98
- component="span"
99
- data-testid="series-colour-dot"
100
- sx={{
101
- width: (theme) => theme.spacing(1.5),
102
- height: (theme) => theme.spacing(1.5),
103
- borderRadius: '50%',
104
- bgcolor: color,
105
- opacity: active ? 1 : 0.35,
106
- }}
107
- />
108
- );
109
- }
110
-
111
- /**
112
- * A time-series preset on ChartFrame + BarChart: a range picker (emits the
113
- * selected range AND its mapped granularity to the host) and series toggle
114
- * checkboxes, in the frame's toolbar slot.
115
- *
116
- * Presentational only — takes `data` and callbacks, never fetches. The host
117
- * owns fetching; this component owns which range is selected and which
118
- * series are visible, telling the host what changed via `onRangeChange`.
119
- *
120
- * Second y-axis is opt-in per series (CHART-5, reversing CHART-2's original
121
- * "toggles only" choice on operator instruction): a series with
122
- * `axis: 'secondary'` plots against a second, independently-scaled y-axis
123
- * (requires `secondaryYAxisLabel`). Omit `axis` (or set it to `'primary'`)
124
- * on every series for today's single-shared-axis behaviour, unchanged.
125
- */
126
- export function TimeSeriesChart({
127
- title,
128
- subtitle,
129
- xAxisLabel,
130
- yAxisLabel,
131
- secondaryYAxisLabel,
132
- data,
133
- loading = false,
134
- error = false,
135
- onRangeChange,
136
- defaultRange = '1w',
137
- skipAnimation,
138
- }) {
139
- const { t } = useTranslation();
140
- const palette = useNeutralChartPalette().categorical;
141
- const seriesConfig = data?.series || [];
142
-
143
- const [range, setRange] = useState(defaultRange);
144
- // Series identity (keys) is NOT assumed stable at mount: the host commonly
145
- // mounts with empty data and fills it asynchronously (fetch-after-mount),
146
- // so visibleKeys must sync as new keys appear rather than being captured
147
- // once. seenKeys tracks every key ever observed so that a key present in
148
- // seriesConfig but absent from visibleKeys is recognized as a deliberate
149
- // user toggle-off (stays off) rather than treated as new (defaulted
150
- // visible) on the next data update. Both are plain state (not a ref) and
151
- // updated together in the render body — React's documented pattern for
152
- // syncing derived state from props — so an interrupted/discarded render
153
- // (concurrent features) rolls both back together instead of leaving
154
- // seenKeys mutated while the paired visibleKeys update is dropped.
155
- const [seenKeys, setSeenKeys] = useState(() => new Set());
156
- const [visibleKeys, setVisibleKeys] = useState(() => new Set());
157
-
158
- const currentKeys = seriesConfig.map((series) => series.key);
159
- const newKeys = currentKeys.filter((key) => !seenKeys.has(key));
160
- if (newKeys.length > 0) {
161
- setSeenKeys((previous) => {
162
- const next = new Set(previous);
163
- newKeys.forEach((key) => next.add(key));
164
- return next;
165
- });
166
- setVisibleKeys((previous) => {
167
- const next = new Set(previous);
168
- newKeys.forEach((key) => next.add(key));
169
- return next;
170
- });
171
- }
172
-
173
- const handleRangeChange = (event) => {
174
- const nextRange = event.target.value;
175
- setRange(nextRange);
176
- const option = RANGE_OPTIONS.find((candidate) => candidate.key === nextRange);
177
- onRangeChange?.(nextRange, option?.granularity);
178
- };
179
-
180
- const toggleSeries = (key) => {
181
- setVisibleKeys((previous) => {
182
- const next = new Set(previous);
183
- if (next.has(key)) next.delete(key);
184
- else next.add(key);
185
- return next;
186
- });
187
- };
188
-
189
- const visibleSeries = seriesConfig.filter((series) => visibleKeys.has(series.key));
190
- // Preserve each series' color by its ORIGINAL index, not the filtered
191
- // position — otherwise toggling one series off shifts every remaining
192
- // series' color, breaking the swatch-to-series identity the toggles rely on.
193
- const visiblePalette = visibleSeries.map(
194
- (series) => palette[seriesConfig.indexOf(series) % palette.length],
195
- );
196
- const isDataEmpty = seriesConfig.length === 0 || visibleSeries.length === 0;
197
-
198
- const hasSecondaryAxis = seriesConfig.some((series) => series.axis === 'secondary');
199
- if (hasSecondaryAxis && !secondaryYAxisLabel) {
200
- throw new Error('TimeSeriesChart requires secondaryYAxisLabel when a series uses axis: "secondary".');
201
- }
202
- const primaryVisibleSeries = visibleSeries.filter((series) => series.axis !== 'secondary');
203
- const secondaryVisibleSeries = visibleSeries.filter((series) => series.axis === 'secondary');
204
- const yAxis = hasSecondaryAxis
205
- ? [
206
- {
207
- id: 'primary',
208
- label: yAxisLabel,
209
- // MUI defaultizes an axis with no explicit `position` to 'left' ONLY
210
- // for the first entry in the array -- every subsequent entry
211
- // defaults to 'none' (never drawn) unless given one explicitly.
212
- // Both are set here, not just 'secondary', so this stays correct
213
- // even if the array order ever changes.
214
- position: 'left',
215
- ...(isIntegerAxis(primaryVisibleSeries) ? { valueFormatter: integerTickFormatter } : {}),
216
- },
217
- {
218
- id: 'secondary',
219
- label: secondaryYAxisLabel,
220
- position: 'right',
221
- ...(isIntegerAxis(secondaryVisibleSeries) ? { valueFormatter: integerTickFormatter } : {}),
222
- },
223
- ]
224
- : (isIntegerAxis(primaryVisibleSeries) ? [{ valueFormatter: integerTickFormatter }] : undefined);
225
-
226
- const toolbar = (
227
- <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 3, alignItems: 'center' }}>
228
- <RadioGroup
229
- row
230
- value={range}
231
- onChange={handleRangeChange}
232
- aria-label={t('TimeSeriesChart.RANGE_LABEL')}
233
- >
234
- {RANGE_OPTIONS.map((option) => (
235
- <FormControlLabel
236
- key={option.key}
237
- value={option.key}
238
- control={<Radio size="small" />}
239
- label={t(option.labelKey)}
240
- />
241
- ))}
242
- </RadioGroup>
243
- {seriesConfig.length > 0 && (
244
- <FormGroup row aria-label={t('TimeSeriesChart.SERIES_LABEL')}>
245
- {seriesConfig.map((series, index) => (
246
- <FormControlLabel
247
- key={series.key}
248
- control={(
249
- <Checkbox
250
- size="small"
251
- checked={visibleKeys.has(series.key)}
252
- onChange={() => toggleSeries(series.key)}
253
- icon={<SeriesToggleDot color={palette[index % palette.length]} active={false} />}
254
- checkedIcon={<SeriesToggleDot color={palette[index % palette.length]} active />}
255
- sx={seriesToggleSx(palette[index % palette.length])}
256
- />
257
- )}
258
- label={series.label}
259
- />
260
- ))}
261
- </FormGroup>
262
- )}
263
- </Box>
264
- );
265
-
266
- return (
267
- <ChartFrame
268
- title={title}
269
- subtitle={subtitle}
270
- toolbar={toolbar}
271
- loading={loading}
272
- error={error}
273
- isEmpty={isDataEmpty}
274
- minHeight={CHART_HEIGHT}
275
- >
276
- {!isDataEmpty && (
277
- <BarChart
278
- xAxisLabel={xAxisLabel}
279
- yAxisLabel={yAxisLabel}
280
- xAxis={[{
281
- data: data?.xLabels || [],
282
- tickLabelInterval: makeTickLabelInterval((data?.xLabels || []).length),
283
- }]}
284
- yAxis={yAxis}
285
- series={visibleSeries.map((series) => ({
286
- data: series.data,
287
- label: series.label,
288
- ...(hasSecondaryAxis && series.axis === 'secondary' ? { yAxisId: 'secondary' } : {}),
289
- }))}
290
- palette={visiblePalette}
291
- size="standard"
292
- hideLegend={seriesConfig.length > 0}
293
- skipAnimation={skipAnimation}
294
- />
295
- )}
296
- </ChartFrame>
297
- );
298
- }
299
-
300
- export default TimeSeriesChart;