@micha.bigler/ui-core-micha 3.14.3 → 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 (367) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +7 -0
  3. package/dist/auth/UserMenu.js +1 -1
  4. package/dist/auth/apiClient.js +7 -6
  5. package/dist/chat/ChatList.js +1 -1
  6. package/dist/chat/ChatThread.js +2 -1
  7. package/dist/chat/ChatWorkspace.js +1 -1
  8. package/dist/chat/ModelPicker.js +2 -5
  9. package/dist/chat/kitSxRegistry.js +9 -0
  10. package/dist/chat/useChatSocket.js +1 -1
  11. package/dist/components/LoginForm.js +1 -1
  12. package/dist/components/SecurityComponent.js +1 -1
  13. package/dist/components/UserListComponent.js +2 -8
  14. package/dist/components/charts/ChartFrame.js +1 -1
  15. package/dist/components/charts/ScatterChart.js +2 -1
  16. package/dist/components/charts/kitSxRegistry.js +6 -0
  17. package/dist/entries/charts.js +6 -0
  18. package/dist/entries/chat.js +8 -0
  19. package/dist/entries/markdown.js +1 -0
  20. package/dist/index.js +0 -14
  21. package/dist/layout/SectionNav.js +1 -1
  22. package/dist/messaging/AttachmentList.js +2 -2
  23. package/dist/messaging/Composer.js +2 -1
  24. package/dist/messaging/ConversationLaunchers.js +1 -4
  25. package/dist/messaging/ConversationList.js +1 -1
  26. package/dist/messaging/DirectMessageLauncher.js +1 -1
  27. package/dist/messaging/MessagingProvider.js +0 -0
  28. package/dist/messaging/PollCard.js +7 -5
  29. package/dist/messaging/ReadTicks.js +2 -2
  30. package/dist/messaging/Thread.js +5 -3
  31. package/dist/notifications/NotificationBell.js +4 -7
  32. package/dist/notifications/NotificationSettings.js +1 -1
  33. package/dist/onboarding/OnboardingProvider.js +3 -6
  34. package/dist/pages/AccountPage.js +1 -2
  35. package/dist/pages/LoginPage.js +2 -3
  36. package/dist/pages/PasswordChangePage.js +2 -3
  37. package/dist/pages/PasswordInvitePage.js +2 -3
  38. package/dist/pages/PasswordResetRequestPage.js +2 -3
  39. package/dist/pages/SignUpPage.js +2 -3
  40. package/dist/pages/SignupConfirmPage.js +2 -3
  41. package/dist/theme/createAppTheme.js +5 -4
  42. package/dist/theme/kitSxEntry.js +11 -0
  43. package/dist/theme/kitSxRegistry.js +2 -15
  44. package/dist/theme/themeCompleteness.js +3 -12
  45. package/dist/utils/auth-errors.js +4 -3
  46. package/package.json +41 -16
  47. package/.github/workflows/publish.yml +0 -126
  48. package/DESIGN.md +0 -60
  49. package/WORK_ORDERS.md +0 -148
  50. package/dev/entries.jsx +0 -721
  51. package/dev/fixtures.js +0 -26
  52. package/dev/main.jsx +0 -75
  53. package/dev/mockTransport.jsx +0 -128
  54. package/docs/CHART-LAYOUT.md +0 -157
  55. package/docs/DARK-TOKENS.md +0 -140
  56. package/docs/TYPE-SCALE.md +0 -111
  57. package/docs/messaging-deviations.md +0 -118
  58. package/index.html +0 -12
  59. package/renovate.json +0 -10
  60. package/scripts/chart-api-census.mjs +0 -415
  61. package/src/auth/AuthContext.jsx +0 -158
  62. package/src/auth/ReauthModal.jsx +0 -82
  63. package/src/auth/UserMenu.jsx +0 -121
  64. package/src/auth/apiClient.jsx +0 -232
  65. package/src/auth/authApi.jsx +0 -539
  66. package/src/auth/authConfig.jsx +0 -34
  67. package/src/auth/reauth.js +0 -47
  68. package/src/chat/ChatComposer.jsx +0 -36
  69. package/src/chat/ChatList.jsx +0 -87
  70. package/src/chat/ChatThread.jsx +0 -128
  71. package/src/chat/ChatWorkspace.jsx +0 -244
  72. package/src/chat/MarkdownReply.jsx +0 -22
  73. package/src/chat/ModelPicker.jsx +0 -85
  74. package/src/chat/api.js +0 -18
  75. package/src/chat/useChatSocket.js +0 -32
  76. package/src/chat/wire.js +0 -7
  77. package/src/components/AccessCodeManager.jsx +0 -288
  78. package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
  79. package/src/components/AllowedEmailDomainsManager.jsx +0 -92
  80. package/src/components/AuthFactorRequirementCard.jsx +0 -95
  81. package/src/components/BulkInviteCsvTab.jsx +0 -198
  82. package/src/components/EmptyState.jsx +0 -38
  83. package/src/components/LoginForm.jsx +0 -159
  84. package/src/components/MFAComponent.jsx +0 -350
  85. package/src/components/MfaLoginComponent.jsx +0 -252
  86. package/src/components/MobileBottomNav.jsx +0 -82
  87. package/src/components/PasskeysComponent.jsx +0 -252
  88. package/src/components/PasswordChangeForm.jsx +0 -87
  89. package/src/components/PasswordResetRequestForm.jsx +0 -43
  90. package/src/components/PasswordSetForm.jsx +0 -97
  91. package/src/components/ProfileComponent.jsx +0 -348
  92. package/src/components/QrSignupManager.jsx +0 -404
  93. package/src/components/QrSignupValidityManager.jsx +0 -106
  94. package/src/components/RegistrationMethodsManager.jsx +0 -150
  95. package/src/components/SecurityComponent.jsx +0 -141
  96. package/src/components/SocialLoginButtons.jsx +0 -83
  97. package/src/components/SoftChip.jsx +0 -93
  98. package/src/components/StatTile.jsx +0 -39
  99. package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
  100. package/src/components/TurnstileWidget.jsx +0 -99
  101. package/src/components/UserInviteComponent.jsx +0 -76
  102. package/src/components/UserListComponent.jsx +0 -618
  103. package/src/components/charts/BarChart.jsx +0 -102
  104. package/src/components/charts/ChartFrame.jsx +0 -214
  105. package/src/components/charts/LineChart.jsx +0 -126
  106. package/src/components/charts/ScatterChart.jsx +0 -299
  107. package/src/components/charts/TimeSeriesChart.jsx +0 -300
  108. package/src/components/charts/chartDefaults.js +0 -644
  109. package/src/components/charts/chartLabels.js +0 -36
  110. package/src/components/charts/exportChart.js +0 -258
  111. package/src/components/charts/formatters.js +0 -39
  112. package/src/components/charts/palette.js +0 -33
  113. package/src/components/charts/yearTickInterval.js +0 -7
  114. package/src/i18n/authTranslations.ts +0 -2651
  115. package/src/i18n/chartsTranslations.ts +0 -91
  116. package/src/i18n/chatTranslations.ts +0 -55
  117. package/src/i18n/messagingTranslations.ts +0 -120
  118. package/src/i18n/notificationsTranslations.ts +0 -32
  119. package/src/i18n/onboardingTranslations.ts +0 -29
  120. package/src/i18n/sectionNavTranslations.ts +0 -20
  121. package/src/i18n/uiCoreTranslations.js +0 -29
  122. package/src/i18n/userMenuTranslations.ts +0 -5
  123. package/src/index.js +0 -158
  124. package/src/layout/PageLayout.jsx +0 -36
  125. package/src/layout/SectionNav.jsx +0 -320
  126. package/src/messaging/AttachmentList.jsx +0 -145
  127. package/src/messaging/Composer.jsx +0 -197
  128. package/src/messaging/ConversationLaunchers.jsx +0 -51
  129. package/src/messaging/ConversationList.jsx +0 -109
  130. package/src/messaging/DirectMessageLauncher.jsx +0 -112
  131. package/src/messaging/MessageBubble.jsx +0 -183
  132. package/src/messaging/MessagingProvider.jsx +0 -0
  133. package/src/messaging/MessagingScopeConfig.jsx +0 -24
  134. package/src/messaging/PollCard.jsx +0 -163
  135. package/src/messaging/ReactionBar.jsx +0 -43
  136. package/src/messaging/ReadTicks.jsx +0 -82
  137. package/src/messaging/Thread.jsx +0 -195
  138. package/src/messaging/api.js +0 -48
  139. package/src/messaging/composerImageCompression.js +0 -35
  140. package/src/messaging/linkifyText.jsx +0 -37
  141. package/src/notifications/NotificationBell.jsx +0 -84
  142. package/src/notifications/NotificationSettings.jsx +0 -330
  143. package/src/notifications/NotificationsProvider.jsx +0 -224
  144. package/src/notifications/PopupSurface.jsx +0 -105
  145. package/src/notifications/api.js +0 -55
  146. package/src/notifications/feedApi.js +0 -18
  147. package/src/notifications/realtime.jsx +0 -138
  148. package/src/notifications/serviceWorker/sw.js +0 -50
  149. package/src/onboarding/OnboardingProvider.jsx +0 -326
  150. package/src/onboarding/OnboardingWizard.jsx +0 -61
  151. package/src/onboarding/WizardDialogShell.jsx +0 -51
  152. package/src/onboarding/api.js +0 -13
  153. package/src/onboarding/stepSelection.js +0 -16
  154. package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
  155. package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
  156. package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
  157. package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
  158. package/src/pages/AccountPage.jsx +0 -394
  159. package/src/pages/LoginPage.jsx +0 -343
  160. package/src/pages/PasswordChangePage.jsx +0 -62
  161. package/src/pages/PasswordInvitePage.jsx +0 -177
  162. package/src/pages/PasswordResetRequestPage.jsx +0 -68
  163. package/src/pages/SignUpPage.jsx +0 -268
  164. package/src/pages/SignupConfirmPage.jsx +0 -155
  165. package/src/testing/checkKitIntegration.js +0 -233
  166. package/src/theme/createAppTheme.js +0 -423
  167. package/src/theme/fonts.js +0 -3
  168. package/src/theme/index.js +0 -21
  169. package/src/theme/kitSxRegistry.js +0 -285
  170. package/src/theme/themeCompleteness.js +0 -1045
  171. package/src/theme/tokens.js +0 -478
  172. package/src/utils/auth-errors.js +0 -76
  173. package/src/utils/authService.js +0 -223
  174. package/src/utils/resolveErrorText.js +0 -28
  175. package/src/utils/webauthn.js +0 -51
  176. package/tests/AccountPage.profileStatements.test.jsx +0 -162
  177. package/tests/AccountPage.test.jsx +0 -200
  178. package/tests/AuthContext.test.jsx +0 -129
  179. package/tests/BarChart.test.jsx +0 -112
  180. package/tests/BrowserPushStep.test.jsx +0 -110
  181. package/tests/ChartFrame.test.jsx +0 -336
  182. package/tests/ConversationList.test.jsx +0 -144
  183. package/tests/CookieConsentStep.test.jsx +0 -69
  184. package/tests/EmptyState.test.jsx +0 -62
  185. package/tests/LineChart.test.jsx +0 -80
  186. package/tests/MessagingProvider.test.jsx +0 -50
  187. package/tests/MobileBottomNav.test.jsx +0 -319
  188. package/tests/NotificationBell.test.jsx +0 -60
  189. package/tests/NotificationSettings.test.jsx +0 -276
  190. package/tests/NotificationsProvider.test.jsx +0 -240
  191. package/tests/OnboardingProvider.test.jsx +0 -294
  192. package/tests/OnboardingWizard.test.jsx +0 -143
  193. package/tests/PasswordChangeForm.test.jsx +0 -70
  194. package/tests/PasswordInvitePage.test.jsx +0 -142
  195. package/tests/PasswordSetForm.test.jsx +0 -70
  196. package/tests/PopupSurface.test.jsx +0 -142
  197. package/tests/ProfileComponent.refresh.test.jsx +0 -194
  198. package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
  199. package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
  200. package/tests/ScatterChart.test.jsx +0 -270
  201. package/tests/SectionNav.test.jsx +0 -363
  202. package/tests/SignUpPage.test.jsx +0 -208
  203. package/tests/SoftChip.test.jsx +0 -105
  204. package/tests/StatTile.test.jsx +0 -57
  205. package/tests/Thread.test.jsx +0 -151
  206. package/tests/TimeSeriesChart.test.jsx +0 -367
  207. package/tests/UserListComponent.test.jsx +0 -192
  208. package/tests/UserMenu.test.jsx +0 -171
  209. package/tests/apiClientLanguage.test.js +0 -33
  210. package/tests/apiClientPublicPaths.test.js +0 -178
  211. package/tests/authApiLogin.test.js +0 -42
  212. package/tests/authApiRegistration.test.js +0 -31
  213. package/tests/authErrorCodeRendering.test.jsx +0 -77
  214. package/tests/authErrors.test.js +0 -74
  215. package/tests/authTranslations.test.js +0 -74
  216. package/tests/chart-api-census.test.js +0 -366
  217. package/tests/chartAxisTickBand.test.jsx +0 -95
  218. package/tests/chartDefaults.test.js +0 -902
  219. package/tests/chartLabels.test.js +0 -50
  220. package/tests/chartsPalette.test.js +0 -99
  221. package/tests/chatApi.test.js +0 -48
  222. package/tests/chatDriverNames.test.jsx +0 -71
  223. package/tests/chatExports.test.js +0 -3
  224. package/tests/chatList.test.jsx +0 -84
  225. package/tests/chatPicker.test.jsx +0 -82
  226. package/tests/chatSocket.test.jsx +0 -26
  227. package/tests/chatThread.test.jsx +0 -116
  228. package/tests/chatTranslations.test.js +0 -47
  229. package/tests/chatWorkspace.test.jsx +0 -234
  230. package/tests/checkKitIntegration.test.jsx +0 -219
  231. package/tests/createAppTheme.test.js +0 -132
  232. package/tests/createAppThemeDark.test.js +0 -127
  233. package/tests/createAppThemeLightGuard.test.js +0 -40
  234. package/tests/createAppThemePageContrast.test.js +0 -108
  235. package/tests/createAppThemeTypography.test.js +0 -53
  236. package/tests/errorCallSiteGuard.test.js +0 -32
  237. package/tests/exportChart.test.js +0 -313
  238. package/tests/feedApi.test.js +0 -46
  239. package/tests/fixtures/light-theme-baseline.json +0 -2234
  240. package/tests/i18nAggregate.test.js +0 -120
  241. package/tests/i18nGermanVariant.test.js +0 -65
  242. package/tests/loginPageErrorResolution.test.jsx +0 -102
  243. package/tests/messagingApi.test.js +0 -32
  244. package/tests/messagingChunk6.test.jsx +0 -116
  245. package/tests/messagingCompactBubble.test.jsx +0 -64
  246. package/tests/messagingComposer.test.jsx +0 -136
  247. package/tests/messagingComposerChunk5.test.jsx +0 -113
  248. package/tests/messagingContractConformance.test.js +0 -69
  249. package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
  250. package/tests/messagingExports.test.js +0 -21
  251. package/tests/messagingInteractions.test.jsx +0 -136
  252. package/tests/messagingLastMessagePreview.test.jsx +0 -93
  253. package/tests/messagingLinkify.test.jsx +0 -51
  254. package/tests/messagingMessageActions.test.jsx +0 -163
  255. package/tests/messagingMsg6.test.jsx +0 -105
  256. package/tests/messagingMsg6f.test.jsx +0 -155
  257. package/tests/messagingMsg6g.test.jsx +0 -166
  258. package/tests/messagingMsg6h.test.jsx +0 -136
  259. package/tests/messagingMsg6i.test.jsx +0 -129
  260. package/tests/messagingPollRendering.test.jsx +0 -142
  261. package/tests/messagingReactionMerge.test.jsx +0 -77
  262. package/tests/messagingReadTicksTooltip.test.jsx +0 -36
  263. package/tests/messagingThreadReceipts.test.jsx +0 -178
  264. package/tests/messagingThreadingQuoting.test.jsx +0 -87
  265. package/tests/messagingUnreadLifecycle.test.jsx +0 -78
  266. package/tests/notificationsApi.test.js +0 -41
  267. package/tests/notificationsExports.test.js +0 -34
  268. package/tests/onboardingDescriptors.test.js +0 -132
  269. package/tests/packageTreeShaking.test.js +0 -126
  270. package/tests/realtime.test.jsx +0 -219
  271. package/tests/resolveErrorText.test.js +0 -72
  272. package/tests/stepSelection.test.js +0 -53
  273. package/tests/themeCompleteness.test.js +0 -626
  274. package/tests/timeSeriesLegend.test.js +0 -99
  275. package/tests/userScreenTranslations.test.jsx +0 -218
  276. package/tsconfig.build.json +0 -18
  277. package/vite.config.mjs +0 -6
  278. package/work-orders/AUTH-1.md +0 -74
  279. package/work-orders/AUTH-2.md +0 -74
  280. package/work-orders/AUTH-4.md +0 -183
  281. package/work-orders/AUTH-5.md +0 -154
  282. package/work-orders/AUTH-7.md +0 -181
  283. package/work-orders/AUTH-8.md +0 -184
  284. package/work-orders/CHART-1.md +0 -285
  285. package/work-orders/CHART-2.md +0 -222
  286. package/work-orders/CHART-3.md +0 -163
  287. package/work-orders/CHART-4.md +0 -203
  288. package/work-orders/CHART-5.md +0 -226
  289. package/work-orders/CHART-6.md +0 -428
  290. package/work-orders/CHART-7.md +0 -175
  291. package/work-orders/DX-1.md +0 -169
  292. package/work-orders/DX-2.md +0 -188
  293. package/work-orders/DX-3.md +0 -195
  294. package/work-orders/I18N-1.md +0 -254
  295. package/work-orders/I18N-3.md +0 -207
  296. package/work-orders/MSG-3.md +0 -375
  297. package/work-orders/MSG-3b.md +0 -1002
  298. package/work-orders/MSG-3c.md +0 -378
  299. package/work-orders/MSG-3d.md +0 -268
  300. package/work-orders/MSG-3e.md +0 -164
  301. package/work-orders/MSG-3f.md +0 -391
  302. package/work-orders/MSG-6e.md +0 -308
  303. package/work-orders/MSG-6f.md +0 -214
  304. package/work-orders/MSG-6g.md +0 -244
  305. package/work-orders/MSG-6h.md +0 -238
  306. package/work-orders/MSG-6i.md +0 -271
  307. package/work-orders/MSG-6j.md +0 -117
  308. package/work-orders/NOTIF-12.md +0 -214
  309. package/work-orders/NOTIF-13.md +0 -142
  310. package/work-orders/NOTIF-14.md +0 -151
  311. package/work-orders/PRIM-1.md +0 -177
  312. package/work-orders/SHELL-1.md +0 -255
  313. package/work-orders/SHELL-2.md +0 -197
  314. package/work-orders/SHELL-3.md +0 -359
  315. package/work-orders/SHELL-4.md +0 -351
  316. package/work-orders/SHELL-5.md +0 -345
  317. package/work-orders/SHELL-6.md +0 -210
  318. package/work-orders/THEME-1.md +0 -587
  319. package/work-orders/THEME-10.md +0 -303
  320. package/work-orders/THEME-11.md +0 -208
  321. package/work-orders/THEME-2.md +0 -339
  322. package/work-orders/THEME-3.md +0 -210
  323. package/work-orders/THEME-4.md +0 -271
  324. package/work-orders/THEME-5.md +0 -341
  325. package/work-orders/THEME-6.md +0 -247
  326. package/work-orders/THEME-7.md +0 -82
  327. package/work-orders/THEME-8.md +0 -211
  328. package/work-orders/THEME-9.md +0 -194
  329. package/work-orders/UCM-AUTH-10.md +0 -490
  330. package/work-orders/UCM-AUTH-11.md +0 -185
  331. package/work-orders/UCM-AUTH-12.md +0 -98
  332. package/work-orders/UCM-AUTH-13.md +0 -47
  333. package/work-orders/UCM-AUTH-9.md +0 -207
  334. package/work-orders/UCM-CHART-10.md +0 -150
  335. package/work-orders/UCM-CHART-11.md +0 -132
  336. package/work-orders/UCM-CHART-12.md +0 -348
  337. package/work-orders/UCM-CHART-13.md +0 -122
  338. package/work-orders/UCM-CHART-14.md +0 -198
  339. package/work-orders/UCM-CHART-15.md +0 -187
  340. package/work-orders/UCM-CHART-16.md +0 -169
  341. package/work-orders/UCM-CHART-17.md +0 -195
  342. package/work-orders/UCM-CHART-18.md +0 -200
  343. package/work-orders/UCM-CHART-8.md +0 -147
  344. package/work-orders/UCM-CHART-9.md +0 -265
  345. package/work-orders/UCM-CHAT-1.md +0 -190
  346. package/work-orders/UCM-CHAT-2.md +0 -75
  347. package/work-orders/UCM-DEP-1.md +0 -63
  348. package/work-orders/UCM-I18N-4.md +0 -226
  349. package/work-orders/UCM-I18N-5.md +0 -211
  350. package/work-orders/UCM-MSG-20.md +0 -192
  351. package/work-orders/UCM-PRIM-2.md +0 -114
  352. package/work-orders/UCM-SHELL-7.md +0 -134
  353. package/work-orders/UCM-SHELL-8.md +0 -125
  354. package/work-orders/UCM-TEST-1.md +0 -304
  355. package/work-orders/UCM-THEME-13.md +0 -69
  356. package/work-orders/UCM-THEME-14.md +0 -312
  357. package/work-orders/UCM-THEME-15.md +0 -356
  358. package/work-orders/UCM-THEME-16.md +0 -80
  359. package/work-orders/UCM-THEME-17.md +0 -111
  360. package/work-orders/UCM-THEME-18.md +0 -184
  361. package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
  362. package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
  363. package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
  364. package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
  365. package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
  366. package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
  367. 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;