@galaxy-io/dls 1.2.7 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/avatar/Avatar.d.ts +2 -1
- package/dist/avatar/Avatar.js +103 -14
- package/dist/buttons/Button.d.ts +6 -1
- package/dist/buttons/Button.js +8 -4
- package/dist/charts/BarChart.d.ts +30 -20
- package/dist/charts/BarChart.js +183 -253
- package/dist/charts/ChartAxis.d.ts +47 -22
- package/dist/charts/ChartAxis.js +81 -60
- package/dist/charts/ChartCategoryTargets.d.ts +27 -0
- package/dist/charts/ChartCategoryTargets.js +75 -0
- package/dist/charts/ChartEmptyState.d.ts +17 -0
- package/dist/charts/ChartEmptyState.js +29 -0
- package/dist/charts/ChartFrame.d.ts +110 -0
- package/dist/charts/ChartFrame.js +143 -0
- package/dist/charts/ChartLegend.d.ts +27 -4
- package/dist/charts/ChartLegend.js +46 -23
- package/dist/charts/ChartPrimitives.d.ts +230 -0
- package/dist/charts/ChartPrimitives.js +305 -0
- package/dist/charts/ChartTooltip.d.ts +25 -4
- package/dist/charts/ChartTooltip.js +55 -49
- package/dist/charts/LineChart.d.ts +42 -20
- package/dist/charts/LineChart.js +175 -241
- package/dist/charts/PieChart.d.ts +48 -32
- package/dist/charts/PieChart.js +180 -170
- package/dist/charts/barChartGeometry.d.ts +120 -0
- package/dist/charts/barChartGeometry.js +210 -0
- package/dist/charts/barChartLegend.d.ts +25 -0
- package/dist/charts/barChartLegend.js +23 -0
- package/dist/charts/chartFormat.d.ts +27 -0
- package/dist/charts/chartFormat.js +71 -0
- package/dist/charts/chartScales.d.ts +120 -0
- package/dist/charts/chartScales.js +165 -0
- package/dist/charts/chartTheme.d.ts +18 -0
- package/dist/charts/chartTheme.js +26 -0
- package/dist/charts/constants.d.ts +241 -18
- package/dist/charts/constants.js +261 -32
- package/dist/charts/lineChartGeometry.d.ts +92 -0
- package/dist/charts/lineChartGeometry.js +134 -0
- package/dist/charts/pieChartGeometry.d.ts +60 -0
- package/dist/charts/pieChartGeometry.js +79 -0
- package/dist/charts/types.d.ts +428 -54
- package/dist/charts/types.js +155 -0
- package/dist/charts/useCartesianChartLayout.d.ts +70 -0
- package/dist/charts/useCartesianChartLayout.js +56 -0
- package/dist/charts/useChartDimensions.d.ts +47 -0
- package/dist/charts/useChartDimensions.js +63 -0
- package/dist/charts/useChartFormatters.d.ts +19 -0
- package/dist/charts/useChartFormatters.js +20 -0
- package/dist/charts/useChartInteraction.d.ts +76 -0
- package/dist/charts/useChartInteraction.js +126 -0
- package/dist/charts/useChartTooltipPosition.d.ts +53 -0
- package/dist/charts/useChartTooltipPosition.js +39 -0
- package/dist/charts/useSeriesColorResolver.d.ts +37 -0
- package/dist/charts/useSeriesColorResolver.js +27 -0
- package/dist/hooks/useElementClientSize.d.ts +20 -0
- package/dist/hooks/useElementClientSize.js +43 -0
- package/dist/inputs/CheckboxInput.d.ts +7 -2
- package/dist/inputs/CheckboxInput.js +30 -14
- package/dist/inputs/Input.d.ts +1 -1
- package/dist/inputs/Input.js +6 -2
- package/dist/inputs/MultiSelectInput.js +1 -1
- package/dist/inputs/RadioInput.js +7 -2
- package/dist/styles.css +69 -74
- package/dist/table/InfiniteSpreadsheet.d.ts +6 -0
- package/dist/table/InfiniteSpreadsheet.js +235 -0
- package/dist/table/InfiniteTable.d.ts +7 -88
- package/dist/table/InfiniteTable.js +366 -577
- package/dist/{spreadsheet → table}/SpreadsheetCell.d.ts +6 -4
- package/dist/table/SpreadsheetCell.js +71 -0
- package/dist/{spreadsheet → table}/SpreadsheetCellEditor.js +27 -21
- package/dist/table/SpreadsheetHeaderCell.d.ts +23 -0
- package/dist/table/SpreadsheetHeaderCell.js +91 -0
- package/dist/table/SpreadsheetMenuItem.d.ts +14 -0
- package/dist/table/SpreadsheetMenuItem.js +33 -0
- package/dist/table/SpreadsheetPrimitives.d.ts +172 -0
- package/dist/table/SpreadsheetPrimitives.js +105 -0
- package/dist/table/SpreadsheetRow.d.ts +33 -0
- package/dist/table/SpreadsheetRow.js +73 -0
- package/dist/{spreadsheet → table}/SpreadsheetRowActionCell.d.ts +5 -7
- package/dist/table/SpreadsheetRowActionCell.js +51 -0
- package/dist/table/TableExpandCell.d.ts +11 -0
- package/dist/table/TableExpandCell.js +24 -0
- package/dist/table/TableExpandedRow.d.ts +16 -0
- package/dist/table/TableExpandedRow.js +77 -0
- package/dist/table/TableHeaderCellContent.d.ts +30 -0
- package/dist/table/TableHeaderCellContent.js +48 -0
- package/dist/table/TablePrimitives.d.ts +307 -0
- package/dist/table/TablePrimitives.js +298 -0
- package/dist/table/TableSelectCell.d.ts +18 -0
- package/dist/table/TableSelectCell.js +26 -0
- package/dist/table/TableStatusRow.d.ts +18 -0
- package/dist/table/TableStatusRow.js +50 -0
- package/dist/table/VirtualizedInfiniteTable.d.ts +11 -27
- package/dist/table/VirtualizedInfiniteTable.js +182 -317
- package/dist/table/types.d.ts +249 -0
- package/dist/table/types.js +51 -0
- package/dist/table/useInfiniteScrollSentinel.d.ts +24 -0
- package/dist/table/useInfiniteScrollSentinel.js +53 -0
- package/dist/{spreadsheet/hooks → table}/useSpreadsheetEditing.d.ts +1 -1
- package/dist/{spreadsheet/hooks → table}/useSpreadsheetEditing.js +1 -1
- package/dist/table/useTableColumnLayout.d.ts +28 -0
- package/dist/table/useTableColumnLayout.js +63 -0
- package/dist/table/useTableColumnResize.d.ts +38 -0
- package/dist/table/useTableColumnResize.js +155 -0
- package/dist/table/useTableKeyboardNavigation.d.ts +25 -0
- package/dist/table/useTableKeyboardNavigation.js +83 -0
- package/dist/table/useTableRowExpansion.d.ts +24 -0
- package/dist/table/useTableRowExpansion.js +62 -0
- package/dist/table/useTableRowSelection.d.ts +27 -0
- package/dist/table/useTableRowSelection.js +94 -0
- package/dist/table/utils.d.ts +50 -0
- package/dist/table/utils.js +78 -0
- package/dist/text/MarkdownText.js +45 -27
- package/dist/theme/variants/dark.js +1 -1
- package/dist/widget/Widget.d.ts +2 -0
- package/dist/widget/Widget.js +14 -7
- package/package.json +1 -5
- package/dist/assets/components/enrichment/PerplexityIcon.d.ts +0 -3
- package/dist/assets/components/enrichment/PerplexityIcon.js +0 -35
- package/dist/assets/components/enrichment/TavilyIcon.d.ts +0 -3
- package/dist/assets/components/enrichment/TavilyIcon.js +0 -15
- package/dist/assets/components/onboarding/TenantSetupDark.d.ts +0 -3
- package/dist/assets/components/onboarding/TenantSetupDark.js +0 -498
- package/dist/assets/components/onboarding/TenantSetupLight.d.ts +0 -3
- package/dist/assets/components/onboarding/TenantSetupLight.js +0 -482
- package/dist/assets/components/onboarding/UserSetupDark.d.ts +0 -3
- package/dist/assets/components/onboarding/UserSetupDark.js +0 -1086
- package/dist/assets/components/onboarding/UserSetupLight.d.ts +0 -3
- package/dist/assets/components/onboarding/UserSetupLight.js +0 -1024
- package/dist/assets/components/onboarding/WorkspaceSetupBuildDark.d.ts +0 -3
- package/dist/assets/components/onboarding/WorkspaceSetupBuildDark.js +0 -466
- package/dist/assets/components/onboarding/WorkspaceSetupBuildLight.d.ts +0 -3
- package/dist/assets/components/onboarding/WorkspaceSetupBuildLight.js +0 -460
- package/dist/assets/components/onboarding/WorkspaceSetupConnectDark.d.ts +0 -3
- package/dist/assets/components/onboarding/WorkspaceSetupConnectDark.js +0 -336
- package/dist/assets/components/onboarding/WorkspaceSetupConnectLight.d.ts +0 -3
- package/dist/assets/components/onboarding/WorkspaceSetupConnectLight.js +0 -336
- package/dist/assets/components/onboarding/WorkspaceSetupIntegrateDark.d.ts +0 -3
- package/dist/assets/components/onboarding/WorkspaceSetupIntegrateDark.js +0 -153
- package/dist/assets/components/onboarding/WorkspaceSetupIntegrateLight.d.ts +0 -3
- package/dist/assets/components/onboarding/WorkspaceSetupIntegrateLight.js +0 -153
- package/dist/assets/components/projects/MappingMethodAutoIntrospectDark.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodAutoIntrospectDark.js +0 -117
- package/dist/assets/components/projects/MappingMethodAutoIntrospectLight.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodAutoIntrospectLight.js +0 -117
- package/dist/assets/components/projects/MappingMethodBlankDark.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodBlankDark.js +0 -150
- package/dist/assets/components/projects/MappingMethodBlankDarkHovered.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodBlankDarkHovered.js +0 -150
- package/dist/assets/components/projects/MappingMethodBlankLight.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodBlankLight.js +0 -150
- package/dist/assets/components/projects/MappingMethodBlankLightHovered.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodBlankLightHovered.js +0 -150
- package/dist/assets/components/projects/MappingMethodJsonDark.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodJsonDark.js +0 -90
- package/dist/assets/components/projects/MappingMethodJsonDarkHovered.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodJsonDarkHovered.js +0 -90
- package/dist/assets/components/projects/MappingMethodJsonLight.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodJsonLight.js +0 -90
- package/dist/assets/components/projects/MappingMethodJsonLightHovered.d.ts +0 -3
- package/dist/assets/components/projects/MappingMethodJsonLightHovered.js +0 -90
- package/dist/assets/components/projects/ProjectsEmptyStateDark.d.ts +0 -3
- package/dist/assets/components/projects/ProjectsEmptyStateDark.js +0 -454
- package/dist/assets/components/projects/ProjectsEmptyStateLight.d.ts +0 -3
- package/dist/assets/components/projects/ProjectsEmptyStateLight.js +0 -456
- package/dist/assets/components/root/CreateProjectActionCardDark.d.ts +0 -3
- package/dist/assets/components/root/CreateProjectActionCardDark.js +0 -1796
- package/dist/assets/components/root/CreateProjectActionCardDarkHovered.d.ts +0 -3
- package/dist/assets/components/root/CreateProjectActionCardDarkHovered.js +0 -1796
- package/dist/assets/components/root/CreateProjectActionCardLight.d.ts +0 -3
- package/dist/assets/components/root/CreateProjectActionCardLight.js +0 -1786
- package/dist/assets/components/root/CreateProjectActionCardLightHovered.d.ts +0 -3
- package/dist/assets/components/root/CreateProjectActionCardLightHovered.js +0 -1786
- package/dist/assets/components/root/CreateSourceActionCardDark.d.ts +0 -3
- package/dist/assets/components/root/CreateSourceActionCardDark.js +0 -879
- package/dist/assets/components/root/CreateSourceActionCardDarkHovered.d.ts +0 -3
- package/dist/assets/components/root/CreateSourceActionCardDarkHovered.js +0 -879
- package/dist/assets/components/root/CreateSourceActionCardLight.d.ts +0 -3
- package/dist/assets/components/root/CreateSourceActionCardLight.js +0 -889
- package/dist/assets/components/root/CreateSourceActionCardLightHovered.d.ts +0 -3
- package/dist/assets/components/root/CreateSourceActionCardLightHovered.js +0 -889
- package/dist/assets/components/sources/AttioIcon.d.ts +0 -3
- package/dist/assets/components/sources/AttioIcon.js +0 -11
- package/dist/assets/components/sources/BigquerySourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/BigquerySourceIcon.js +0 -39
- package/dist/assets/components/sources/BrowserbaseSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/BrowserbaseSourceIcon.js +0 -35
- package/dist/assets/components/sources/ClickhouseLogoSourceIconDark.d.ts +0 -3
- package/dist/assets/components/sources/ClickhouseLogoSourceIconDark.js +0 -13
- package/dist/assets/components/sources/ClickhouseLogoSourceIconLight.d.ts +0 -3
- package/dist/assets/components/sources/ClickhouseLogoSourceIconLight.js +0 -13
- package/dist/assets/components/sources/ConfluenceSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/ConfluenceSourceIcon.js +0 -30
- package/dist/assets/components/sources/GithubSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/GithubSourceIcon.js +0 -27
- package/dist/assets/components/sources/GoogleDriveIcon.d.ts +0 -3
- package/dist/assets/components/sources/GoogleDriveIcon.js +0 -45
- package/dist/assets/components/sources/HubspotSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/HubspotSourceIcon.js +0 -14
- package/dist/assets/components/sources/JiraSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/JiraSourceIcon.js +0 -54
- package/dist/assets/components/sources/LinearSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/LinearSourceIcon.js +0 -14
- package/dist/assets/components/sources/MysqlLogoSourceIconDark.d.ts +0 -3
- package/dist/assets/components/sources/MysqlLogoSourceIconDark.js +0 -17
- package/dist/assets/components/sources/NetsuiteSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/NetsuiteSourceIcon.js +0 -32
- package/dist/assets/components/sources/NotionIcon.d.ts +0 -3
- package/dist/assets/components/sources/NotionIcon.js +0 -20
- package/dist/assets/components/sources/PostgresqlIcon.d.ts +0 -3
- package/dist/assets/components/sources/PostgresqlIcon.js +0 -61
- package/dist/assets/components/sources/S3Icon.d.ts +0 -3
- package/dist/assets/components/sources/S3Icon.js +0 -76
- package/dist/assets/components/sources/SalesforceSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/SalesforceSourceIcon.js +0 -23
- package/dist/assets/components/sources/SlackIcon.d.ts +0 -3
- package/dist/assets/components/sources/SlackIcon.js +0 -32
- package/dist/assets/components/sources/SnowflakeLogoSourceIconDark.d.ts +0 -3
- package/dist/assets/components/sources/SnowflakeLogoSourceIconDark.js +0 -14
- package/dist/assets/components/sources/SnowflakeLogoSourceIconLight.d.ts +0 -3
- package/dist/assets/components/sources/SnowflakeLogoSourceIconLight.js +0 -14
- package/dist/assets/components/sources/WikipediaSourceIcon.d.ts +0 -3
- package/dist/assets/components/sources/WikipediaSourceIcon.js +0 -1490
- package/dist/avatar/AvatarEmpty.d.ts +0 -7
- package/dist/avatar/AvatarEmpty.js +0 -99
- package/dist/avatar/types.d.ts +0 -5
- package/dist/avatar/types.js +0 -9
- package/dist/charts/utils.d.ts +0 -29
- package/dist/charts/utils.js +0 -62
- package/dist/icons/BackIcon.d.ts +0 -3
- package/dist/icons/BackIcon.js +0 -13
- package/dist/icons/CloseIcon.d.ts +0 -3
- package/dist/icons/CloseIcon.js +0 -13
- package/dist/icons/CreateIcon.d.ts +0 -3
- package/dist/icons/CreateIcon.js +0 -13
- package/dist/icons/ForwardIcon.d.ts +0 -3
- package/dist/icons/ForwardIcon.js +0 -13
- package/dist/icons/HelpIcon.d.ts +0 -3
- package/dist/icons/HelpIcon.js +0 -14
- package/dist/icons/ProjectsIcon.d.ts +0 -3
- package/dist/icons/ProjectsIcon.js +0 -12
- package/dist/icons/SearchIcon.d.ts +0 -3
- package/dist/icons/SearchIcon.js +0 -12
- package/dist/icons/SourcesIcon.d.ts +0 -3
- package/dist/icons/SourcesIcon.js +0 -12
- package/dist/icons/TextIcon.d.ts +0 -4
- package/dist/icons/TextIcon.js +0 -22
- package/dist/icons/WebScrapeIcon.d.ts +0 -4
- package/dist/icons/WebScrapeIcon.js +0 -22
- package/dist/icons/WebhookIcon.d.ts +0 -4
- package/dist/icons/WebhookIcon.js +0 -22
- package/dist/icons/sources/AttioLogomark.d.ts +0 -4
- package/dist/icons/sources/AttioLogomark.js +0 -8
- package/dist/icons/sources/BrowserbaseLogomark.d.ts +0 -4
- package/dist/icons/sources/BrowserbaseLogomark.js +0 -8
- package/dist/icons/sources/ClickhouseLogomark.d.ts +0 -4
- package/dist/icons/sources/ClickhouseLogomark.js +0 -16
- package/dist/icons/sources/ConfluenceLogomark.d.ts +0 -4
- package/dist/icons/sources/ConfluenceLogomark.js +0 -8
- package/dist/icons/sources/GithubLogomark.d.ts +0 -4
- package/dist/icons/sources/GithubLogomark.js +0 -8
- package/dist/icons/sources/GoogleBigqueryLogomark.d.ts +0 -4
- package/dist/icons/sources/GoogleBigqueryLogomark.js +0 -8
- package/dist/icons/sources/GoogleDriveLogomark.d.ts +0 -4
- package/dist/icons/sources/GoogleDriveLogomark.js +0 -8
- package/dist/icons/sources/HubspotLogomark.d.ts +0 -4
- package/dist/icons/sources/HubspotLogomark.js +0 -8
- package/dist/icons/sources/JiraLogomark.d.ts +0 -4
- package/dist/icons/sources/JiraLogomark.js +0 -8
- package/dist/icons/sources/LinearLogomark.d.ts +0 -4
- package/dist/icons/sources/LinearLogomark.js +0 -8
- package/dist/icons/sources/MySQLLogomark.d.ts +0 -4
- package/dist/icons/sources/MySQLLogomark.js +0 -8
- package/dist/icons/sources/NetsuiteLogomark.d.ts +0 -4
- package/dist/icons/sources/NetsuiteLogomark.js +0 -8
- package/dist/icons/sources/NotionLogomark.d.ts +0 -4
- package/dist/icons/sources/NotionLogomark.js +0 -8
- package/dist/icons/sources/PostgresLogomark.d.ts +0 -4
- package/dist/icons/sources/PostgresLogomark.js +0 -8
- package/dist/icons/sources/S3Logomark.d.ts +0 -4
- package/dist/icons/sources/S3Logomark.js +0 -8
- package/dist/icons/sources/SalesforceLogomark.d.ts +0 -4
- package/dist/icons/sources/SalesforceLogomark.js +0 -8
- package/dist/icons/sources/SlackLogoIconmark.d.ts +0 -4
- package/dist/icons/sources/SlackLogoIconmark.js +0 -8
- package/dist/icons/sources/SnowflakeLogomark.d.ts +0 -4
- package/dist/icons/sources/SnowflakeLogomark.js +0 -16
- package/dist/icons/sources/WikipediaLogomark.d.ts +0 -4
- package/dist/icons/sources/WikipediaLogomark.js +0 -8
- package/dist/spreadsheet/InfiniteSpreadsheet.d.ts +0 -3
- package/dist/spreadsheet/InfiniteSpreadsheet.js +0 -265
- package/dist/spreadsheet/SpreadsheetAddColumnButton.d.ts +0 -6
- package/dist/spreadsheet/SpreadsheetAddColumnButton.js +0 -32
- package/dist/spreadsheet/SpreadsheetCell.js +0 -115
- package/dist/spreadsheet/SpreadsheetHeaderCell.d.ts +0 -18
- package/dist/spreadsheet/SpreadsheetHeaderCell.js +0 -141
- package/dist/spreadsheet/SpreadsheetRow.d.ts +0 -21
- package/dist/spreadsheet/SpreadsheetRow.js +0 -68
- package/dist/spreadsheet/SpreadsheetRowActionCell.js +0 -78
- package/dist/spreadsheet/hooks/useColumnResize.d.ts +0 -6
- package/dist/spreadsheet/hooks/useColumnResize.js +0 -46
- package/dist/spreadsheet/types.d.ts +0 -92
- package/dist/spreadsheet/types.js +0 -21
- /package/dist/{spreadsheet → table}/SpreadsheetCellEditor.d.ts +0 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { BarChartNormalization, BarCorners } from "./types.js";
|
|
2
|
+
import { BAR_WIDTH_RATIO, MAX_GAP_SHARE } from "./constants.js";
|
|
3
|
+
import { buildValueScale, sanitizeValue } from "./chartScales.js";
|
|
4
|
+
//#region src/charts/barChartGeometry.ts
|
|
5
|
+
/**
|
|
6
|
+
* Pick gap sizes from how crowded the chart is.
|
|
7
|
+
*
|
|
8
|
+
* Sparse charts stay roomy regardless of width; once there are enough bars to
|
|
9
|
+
* matter, the gaps tighten as the plot narrows.
|
|
10
|
+
*/
|
|
11
|
+
function getBarSpacing(plotWidth, totalBars) {
|
|
12
|
+
if (totalBars < 13 || plotWidth > 650) return {
|
|
13
|
+
groupGap: 24,
|
|
14
|
+
barGap: 6
|
|
15
|
+
};
|
|
16
|
+
if (plotWidth > 500) return {
|
|
17
|
+
groupGap: 16,
|
|
18
|
+
barGap: 4
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
groupGap: 12,
|
|
22
|
+
barGap: 3
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Rectangle with one pair of corners rounded.
|
|
27
|
+
*
|
|
28
|
+
* The radius is clamped to half the height as well as half the width, so a very
|
|
29
|
+
* short bar renders as a symmetric cap instead of a lopsided half-pill.
|
|
30
|
+
*/
|
|
31
|
+
function roundedRectPath(x, y, width, height, radius, corners) {
|
|
32
|
+
const r = corners === BarCorners.NONE ? 0 : Math.max(0, Math.min(radius, width / 2, height / 2));
|
|
33
|
+
if (r === 0) return `M${x},${y} h${width} v${height} h${-width} z`;
|
|
34
|
+
if (corners === BarCorners.TOP) return [
|
|
35
|
+
`M${x},${y + r}`,
|
|
36
|
+
`a${r},${r} 0 0 1 ${r},${-r}`,
|
|
37
|
+
`h${width - 2 * r}`,
|
|
38
|
+
`a${r},${r} 0 0 1 ${r},${r}`,
|
|
39
|
+
`v${height - r}`,
|
|
40
|
+
`h${-width}`,
|
|
41
|
+
`z`
|
|
42
|
+
].join(" ");
|
|
43
|
+
return [
|
|
44
|
+
`M${x},${y}`,
|
|
45
|
+
`h${width}`,
|
|
46
|
+
`v${height - r}`,
|
|
47
|
+
`a${r},${r} 0 0 1 ${-r},${r}`,
|
|
48
|
+
`h${-(width - 2 * r)}`,
|
|
49
|
+
`a${r},${r} 0 0 1 ${-r},${-r}`,
|
|
50
|
+
`z`
|
|
51
|
+
].join(" ");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Turn one bar's components into stacked `[from, to]` intervals.
|
|
55
|
+
*
|
|
56
|
+
* Positive and negative values accumulate on separate cursors, which is what
|
|
57
|
+
* lets a bar mixing both stack upward and downward from the baseline without
|
|
58
|
+
* any special-casing downstream — and is also why percent normalization is a
|
|
59
|
+
* one-line division rather than a second code path.
|
|
60
|
+
*/
|
|
61
|
+
function stackComponents(values, normalization) {
|
|
62
|
+
let positiveCursor = 0;
|
|
63
|
+
let negativeCursor = 0;
|
|
64
|
+
const intervals = values.map((value) => {
|
|
65
|
+
if (value >= 0) {
|
|
66
|
+
const from = positiveCursor;
|
|
67
|
+
positiveCursor += value;
|
|
68
|
+
return {
|
|
69
|
+
value,
|
|
70
|
+
from,
|
|
71
|
+
to: positiveCursor
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const to = negativeCursor;
|
|
75
|
+
negativeCursor += value;
|
|
76
|
+
return {
|
|
77
|
+
value,
|
|
78
|
+
from: negativeCursor,
|
|
79
|
+
to
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
if (normalization !== BarChartNormalization.PERCENT) return intervals;
|
|
83
|
+
const magnitude = positiveCursor - negativeCursor;
|
|
84
|
+
if (magnitude === 0) return intervals;
|
|
85
|
+
return intervals.map((interval) => ({
|
|
86
|
+
value: interval.value,
|
|
87
|
+
from: interval.from / magnitude,
|
|
88
|
+
to: interval.to / magnitude
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Signed value domain across every group.
|
|
93
|
+
*
|
|
94
|
+
* Percent-normalized charts always span 0–1, so the axis is stable no matter
|
|
95
|
+
* what the underlying totals are.
|
|
96
|
+
*/
|
|
97
|
+
function getBarValueDomain(groups, normalization) {
|
|
98
|
+
if (normalization === BarChartNormalization.PERCENT) return [0, 1];
|
|
99
|
+
let min = 0;
|
|
100
|
+
let max = 0;
|
|
101
|
+
for (const group of groups) for (const bar of group.bars) {
|
|
102
|
+
let positive = 0;
|
|
103
|
+
let negative = 0;
|
|
104
|
+
for (const component of bar.components) {
|
|
105
|
+
const value = sanitizeValue(component.value);
|
|
106
|
+
if (value >= 0) positive += value;
|
|
107
|
+
else negative += value;
|
|
108
|
+
}
|
|
109
|
+
if (positive > max) max = positive;
|
|
110
|
+
if (negative < min) min = negative;
|
|
111
|
+
}
|
|
112
|
+
return [min, max];
|
|
113
|
+
}
|
|
114
|
+
function buildBarChartGeometry({ groups, plotWidth, plotHeight, normalization, isStacked, domain, resolveColor }) {
|
|
115
|
+
const valueScale = buildValueScale(domain, [plotHeight, 0]);
|
|
116
|
+
const baselineY = valueScale(0);
|
|
117
|
+
const rects = [];
|
|
118
|
+
const bands = [];
|
|
119
|
+
if (groups.length === 0 || plotWidth <= 0 || plotHeight <= 0) return {
|
|
120
|
+
rects,
|
|
121
|
+
bands,
|
|
122
|
+
valueScale
|
|
123
|
+
};
|
|
124
|
+
const { groupGap, barGap } = getBarSpacing(plotWidth, groups.reduce((sum, group) => sum + group.bars.length, 0));
|
|
125
|
+
const step = plotWidth / groups.length;
|
|
126
|
+
const effectiveGroupGap = Math.min(groupGap, step * MAX_GAP_SHARE);
|
|
127
|
+
const groupWidth = Math.max(1, step - effectiveGroupGap);
|
|
128
|
+
groups.forEach((group, groupIndex) => {
|
|
129
|
+
const bandX = groupIndex * step;
|
|
130
|
+
const groupX = bandX + (step - groupWidth) / 2;
|
|
131
|
+
const barCount = Math.max(1, group.bars.length);
|
|
132
|
+
const effectiveBarGap = Math.min(barGap, groupWidth / barCount * MAX_GAP_SHARE);
|
|
133
|
+
const slotWidth = (groupWidth - (barCount - 1) * effectiveBarGap) / barCount;
|
|
134
|
+
const barWidth = Math.max(1, Math.min(slotWidth * BAR_WIDTH_RATIO, 44));
|
|
135
|
+
const radius = barWidth < 40 ? 2 : 4;
|
|
136
|
+
const barsStartX = groupX + (groupWidth - (barCount * barWidth + (barCount - 1) * effectiveBarGap)) / 2;
|
|
137
|
+
let groupTopY = baselineY;
|
|
138
|
+
group.bars.forEach((bar, barIndex) => {
|
|
139
|
+
const barX = barsStartX + barIndex * (barWidth + effectiveBarGap);
|
|
140
|
+
const values = bar.components.map((component) => sanitizeValue(component.value));
|
|
141
|
+
const intervals = stackComponents(values, normalization);
|
|
142
|
+
let firstPositive = -1;
|
|
143
|
+
let lastPositive = -1;
|
|
144
|
+
let firstNegative = -1;
|
|
145
|
+
let lastNegative = -1;
|
|
146
|
+
values.forEach((value, index) => {
|
|
147
|
+
if (value > 0) {
|
|
148
|
+
if (firstPositive === -1) firstPositive = index;
|
|
149
|
+
lastPositive = index;
|
|
150
|
+
}
|
|
151
|
+
if (value < 0) {
|
|
152
|
+
if (firstNegative === -1) firstNegative = index;
|
|
153
|
+
lastNegative = index;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
bar.components.forEach((component, componentIndex) => {
|
|
157
|
+
const { value, from, to } = intervals[componentIndex];
|
|
158
|
+
if (value === 0) return;
|
|
159
|
+
const isNegative = value < 0;
|
|
160
|
+
const isCap = componentIndex === (isNegative ? lastNegative : lastPositive);
|
|
161
|
+
const fromY = Math.round(valueScale(from));
|
|
162
|
+
const toY = Math.round(valueScale(to));
|
|
163
|
+
const topY = Math.min(fromY, toY);
|
|
164
|
+
const rawHeight = Math.abs(fromY - toY);
|
|
165
|
+
const isStackedBar = bar.components.length > 1;
|
|
166
|
+
const shave = componentIndex === (isNegative ? firstNegative : firstPositive) ? 0 : 2;
|
|
167
|
+
const height = Math.max(isStackedBar ? 0 : 2, rawHeight - shave);
|
|
168
|
+
const y = isNegative ? topY + shave : topY;
|
|
169
|
+
if (!isNegative && y < groupTopY) groupTopY = y;
|
|
170
|
+
rects.push({
|
|
171
|
+
id: `${groupIndex}:${barIndex}:${component.key}`,
|
|
172
|
+
groupIndex,
|
|
173
|
+
metric: bar.metric,
|
|
174
|
+
componentKey: component.key,
|
|
175
|
+
label: component.label,
|
|
176
|
+
value,
|
|
177
|
+
x: barX,
|
|
178
|
+
y,
|
|
179
|
+
width: barWidth,
|
|
180
|
+
height,
|
|
181
|
+
color: resolveColor({
|
|
182
|
+
metric: bar.metric,
|
|
183
|
+
componentIndex,
|
|
184
|
+
isStacked,
|
|
185
|
+
explicit: component.color
|
|
186
|
+
}),
|
|
187
|
+
corners: isCap ? isNegative ? BarCorners.BOTTOM : BarCorners.TOP : BarCorners.NONE,
|
|
188
|
+
radius
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
bands.push({
|
|
193
|
+
groupIndex,
|
|
194
|
+
label: group.label,
|
|
195
|
+
x: bandX,
|
|
196
|
+
y: 0,
|
|
197
|
+
width: step,
|
|
198
|
+
height: plotHeight,
|
|
199
|
+
anchorX: bandX + step / 2,
|
|
200
|
+
anchorY: groupTopY
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
rects,
|
|
205
|
+
bands,
|
|
206
|
+
valueScale
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
export { buildBarChartGeometry, getBarValueDomain, roundedRectPath };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BarChartGroupDatum, ChartLegendItem, ChartSeriesStyles } from "./types";
|
|
2
|
+
import type { SeriesColorResolver } from "./useSeriesColorResolver";
|
|
3
|
+
/**
|
|
4
|
+
* Legend entries for a bar chart.
|
|
5
|
+
*
|
|
6
|
+
* A pure walk over the data, kept out of the component because it is the one
|
|
7
|
+
* legend in the DLS that cannot be a `.map()` over the `series` record: a bar's
|
|
8
|
+
* *components* are a second dimension with no record of their own, so a stacked
|
|
9
|
+
* chart's legend has to be recovered from the data itself.
|
|
10
|
+
*/
|
|
11
|
+
interface GetBarLegendItemsArgs<TMetric extends string, TComponentKey extends string> {
|
|
12
|
+
/**
|
|
13
|
+
* Whether the chart as a whole is stacked.
|
|
14
|
+
*
|
|
15
|
+
* Decides which dimension the legend names, and must match the flag the
|
|
16
|
+
* geometry coloured with — a legend naming metrics beside bars coloured by
|
|
17
|
+
* component describes a chart that is not on screen.
|
|
18
|
+
*/
|
|
19
|
+
isStacked: boolean;
|
|
20
|
+
series: ChartSeriesStyles<TMetric>;
|
|
21
|
+
groups: BarChartGroupDatum<TMetric, TComponentKey>[];
|
|
22
|
+
colors: SeriesColorResolver<TMetric>;
|
|
23
|
+
}
|
|
24
|
+
export declare function getBarLegendItems<TMetric extends string, TComponentKey extends string>({ isStacked, series, groups, colors, }: GetBarLegendItemsArgs<TMetric, TComponentKey>): ChartLegendItem[];
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/charts/barChartLegend.ts
|
|
2
|
+
function getBarLegendItems({ isStacked, series, groups, colors }) {
|
|
3
|
+
if (!isStacked) return colors.keys.map((metric) => ({
|
|
4
|
+
key: metric,
|
|
5
|
+
label: series[metric].label,
|
|
6
|
+
color: colors.resolve(metric)
|
|
7
|
+
}));
|
|
8
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const group of groups) for (const bar of group.bars) bar.components.forEach((component, componentIndex) => {
|
|
10
|
+
if (!seen.has(component.key)) seen.set(component.key, {
|
|
11
|
+
label: component.label,
|
|
12
|
+
index: componentIndex,
|
|
13
|
+
explicit: component.color
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
return [...seen.entries()].map(([key, { label, index, explicit }]) => ({
|
|
17
|
+
key,
|
|
18
|
+
label,
|
|
19
|
+
color: colors.resolveByIndex(index, explicit)
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { getBarLegendItems };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ChartValueUnit } from "./types";
|
|
2
|
+
import type { ChartLabelFormatter, ChartPlotTooltipModel, ChartValueFormatter } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Format a value with a built-in unit.
|
|
5
|
+
*
|
|
6
|
+
* `PERCENT` takes a 0–100 value rather than a 0–1 fraction, matching how
|
|
7
|
+
* percentage data usually arrives, and appends the sign itself instead of using
|
|
8
|
+
* `style: "percent"` (which would divide by 100).
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatChartValue(value: number, unit: ChartValueUnit): string;
|
|
11
|
+
/**
|
|
12
|
+
* Pick the effective value formatter.
|
|
13
|
+
*
|
|
14
|
+
* An explicit `valueFormatter` always wins; otherwise the unit supplies a
|
|
15
|
+
* default, and `PLAIN` is the fallback when neither is given.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveValueFormatter(valueFormatter?: ChartValueFormatter, unit?: ChartValueUnit): ChartValueFormatter;
|
|
18
|
+
/** Pick the effective label formatter, defaulting to identity. */
|
|
19
|
+
export declare function resolveLabelFormatter(labelFormatter?: ChartLabelFormatter): ChartLabelFormatter;
|
|
20
|
+
/**
|
|
21
|
+
* Flatten a tooltip model into one sentence, for a screen reader.
|
|
22
|
+
*
|
|
23
|
+
* Derived from the tooltip rather than written separately so the two cannot
|
|
24
|
+
* drift: whatever a sighted reader sees on hover is what a keyboard reader
|
|
25
|
+
* hears on focus, by construction.
|
|
26
|
+
*/
|
|
27
|
+
export declare function describeTooltipModel(model: ChartPlotTooltipModel): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ChartValueUnit } from "./types.js";
|
|
2
|
+
import { match } from "ts-pattern";
|
|
3
|
+
//#region src/charts/chartFormat.ts
|
|
4
|
+
/**
|
|
5
|
+
* Value and label formatting for the chart components.
|
|
6
|
+
*
|
|
7
|
+
* Resolving the formatter once, centrally, is what keeps axis ticks, value
|
|
8
|
+
* labels and tooltip rows from drifting apart — a recurring problem when every
|
|
9
|
+
* call site writes its own one-liner.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* `Intl` formatters are comparatively expensive to construct, and the charts
|
|
13
|
+
* format every tick and tooltip row on each render, so instances are cached per
|
|
14
|
+
* unit.
|
|
15
|
+
*/
|
|
16
|
+
var FORMATTER_CACHE = /* @__PURE__ */ new Map();
|
|
17
|
+
function getIntlFormatter(unit) {
|
|
18
|
+
const cached = FORMATTER_CACHE.get(unit);
|
|
19
|
+
if (cached) return cached;
|
|
20
|
+
const formatter = match(unit).with(ChartValueUnit.PLAIN, () => new Intl.NumberFormat(void 0, { maximumFractionDigits: 2 })).with(ChartValueUnit.CURRENCY, () => new Intl.NumberFormat(void 0, {
|
|
21
|
+
style: "currency",
|
|
22
|
+
currency: "USD",
|
|
23
|
+
maximumFractionDigits: 0
|
|
24
|
+
})).with(ChartValueUnit.PERCENT, () => new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 })).with(ChartValueUnit.COMPACT, () => new Intl.NumberFormat(void 0, {
|
|
25
|
+
notation: "compact",
|
|
26
|
+
maximumFractionDigits: 1
|
|
27
|
+
})).exhaustive();
|
|
28
|
+
FORMATTER_CACHE.set(unit, formatter);
|
|
29
|
+
return formatter;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Format a value with a built-in unit.
|
|
33
|
+
*
|
|
34
|
+
* `PERCENT` takes a 0–100 value rather than a 0–1 fraction, matching how
|
|
35
|
+
* percentage data usually arrives, and appends the sign itself instead of using
|
|
36
|
+
* `style: "percent"` (which would divide by 100).
|
|
37
|
+
*/
|
|
38
|
+
function formatChartValue(value, unit) {
|
|
39
|
+
if (!Number.isFinite(value)) return "—";
|
|
40
|
+
const formatted = getIntlFormatter(unit).format(value);
|
|
41
|
+
return unit === ChartValueUnit.PERCENT ? `${formatted}%` : formatted;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Pick the effective value formatter.
|
|
45
|
+
*
|
|
46
|
+
* An explicit `valueFormatter` always wins; otherwise the unit supplies a
|
|
47
|
+
* default, and `PLAIN` is the fallback when neither is given.
|
|
48
|
+
*/
|
|
49
|
+
function resolveValueFormatter(valueFormatter, unit) {
|
|
50
|
+
if (valueFormatter) return valueFormatter;
|
|
51
|
+
const resolvedUnit = unit ?? ChartValueUnit.PLAIN;
|
|
52
|
+
return (value) => formatChartValue(value, resolvedUnit);
|
|
53
|
+
}
|
|
54
|
+
/** Pick the effective label formatter, defaulting to identity. */
|
|
55
|
+
function resolveLabelFormatter(labelFormatter) {
|
|
56
|
+
return labelFormatter ?? ((label) => label);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Flatten a tooltip model into one sentence, for a screen reader.
|
|
60
|
+
*
|
|
61
|
+
* Derived from the tooltip rather than written separately so the two cannot
|
|
62
|
+
* drift: whatever a sighted reader sees on hover is what a keyboard reader
|
|
63
|
+
* hears on focus, by construction.
|
|
64
|
+
*/
|
|
65
|
+
function describeTooltipModel(model) {
|
|
66
|
+
if (model.rows.length === 0) return model.label;
|
|
67
|
+
const rows = model.rows.map((row) => `${row.label} ${row.formattedValue}`).join(", ");
|
|
68
|
+
return `${model.label}: ${rows}`;
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { describeTooltipModel, formatChartValue, resolveLabelFormatter, resolveValueFormatter };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { ChartMargin, ChartValueDomain, ChartValueFormatter } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Scale construction and axis sizing for the chart components.
|
|
4
|
+
*
|
|
5
|
+
* Pure arithmetic with no React and no theme access, so it can be unit-reasoned
|
|
6
|
+
* about and stays safe for Linaria's build-time evaluation.
|
|
7
|
+
*/
|
|
8
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
9
|
+
/**
|
|
10
|
+
* Coerce a datum value into something safe to feed a scale.
|
|
11
|
+
*
|
|
12
|
+
* A single `NaN` or `Infinity` in consumer data otherwise propagates into every
|
|
13
|
+
* path's `d` attribute and blanks the entire chart rather than one mark.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sanitizeValue(value: number | undefined | null): number;
|
|
16
|
+
/**
|
|
17
|
+
* Merge a partial margin over the defaults.
|
|
18
|
+
*
|
|
19
|
+
* Written per-field rather than as a spread: spreading an object that carries
|
|
20
|
+
* an explicit `undefined` overwrites the default with `undefined` rather than
|
|
21
|
+
* falling back to it, which is easy to trip over when the partial is assembled
|
|
22
|
+
* from separate optional values.
|
|
23
|
+
*
|
|
24
|
+
* `defaults` exists because the cartesian default reserves axis gutters a
|
|
25
|
+
* radial chart has no use for. Taking it as a parameter keeps the merge
|
|
26
|
+
* per-field: a chart that substituted its own defaults with `margin ?? OWN`
|
|
27
|
+
* would silently fall back to the *cartesian* defaults the moment a caller
|
|
28
|
+
* passed a partial, handing an axis-less chart a 52px left gutter.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveMargin(partial?: Partial<ChartMargin>, defaults?: ChartMargin): ChartMargin;
|
|
31
|
+
/** Inner plot dimensions, never negative. */
|
|
32
|
+
export declare function getPlotDimensions(width: number, height: number, margin: ChartMargin): {
|
|
33
|
+
plotWidth: number;
|
|
34
|
+
plotHeight: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Widen a margin to leave room for axis titles.
|
|
38
|
+
*
|
|
39
|
+
* The reservation has to happen here rather than inside the axis, because the
|
|
40
|
+
* margin is resolved before any axis renders — and with the root SVG set to
|
|
41
|
+
* `overflow: visible`, an unreserved title would paint outside the container and
|
|
42
|
+
* be clipped by whatever the consumer wraps the chart in.
|
|
43
|
+
*
|
|
44
|
+
* Applied to the caller's *partial* before {@link resolveMargin} fills in the
|
|
45
|
+
* defaults, so an explicit `margin.left` still governs the tick gutter and the
|
|
46
|
+
* title band is added on top of it rather than eating into it. Returns the input
|
|
47
|
+
* untouched when there are no titles, so the common case allocates nothing.
|
|
48
|
+
*/
|
|
49
|
+
export declare function addAxisTitleSpace(partial: Partial<ChartMargin> | undefined, { hasValueTitle, hasCategoryTitle }: {
|
|
50
|
+
hasValueTitle: boolean;
|
|
51
|
+
hasCategoryTitle: boolean;
|
|
52
|
+
}): Partial<ChartMargin> | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Turn a raw data extent into the domain the axis and the marks both use.
|
|
55
|
+
*
|
|
56
|
+
* Applies the caller's override, then three behaviours worth knowing:
|
|
57
|
+
*
|
|
58
|
+
* - **Zero is always in the domain.** Bars need a baseline to grow from, and
|
|
59
|
+
* without this a negative value maps outside the range and renders as a
|
|
60
|
+
* rectangle with negative height, which paints nothing at all.
|
|
61
|
+
* - **A zero-width domain is widened to 0–1.** d3's `normalize()` returns a
|
|
62
|
+
* constant `0.5` when the domain has no extent, so empty or all-zero data
|
|
63
|
+
* would otherwise map every value to the middle of the plot and strand the
|
|
64
|
+
* baseline halfway up.
|
|
65
|
+
* - **`nice()` rounds the ends outward** to values a reader would choose.
|
|
66
|
+
*
|
|
67
|
+
* Split out of {@link buildValueScale} so the domain is computed exactly once
|
|
68
|
+
* per chart and then handed to both the tick list and the geometry. Building it
|
|
69
|
+
* twice — once at a throwaway range to get ticks, once at the real range to
|
|
70
|
+
* place marks — leaves two `nice()` calls that must agree forever.
|
|
71
|
+
*
|
|
72
|
+
* `nice()` deliberately takes **no argument**. The default count is 10, not the
|
|
73
|
+
* five ticks {@link DEFAULT_VALUE_TICK_COUNT} asks for, and nicing to the tick
|
|
74
|
+
* count instead would change the axis extent on every existing chart.
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveValueDomain(raw: [number, number], override?: Partial<ChartValueDomain>): [number, number];
|
|
77
|
+
/**
|
|
78
|
+
* Linear scale over an already-resolved domain.
|
|
79
|
+
*
|
|
80
|
+
* Does no nicing and no zero-forcing — {@link resolveValueDomain} has done
|
|
81
|
+
* both. Takes the domain rather than raw data so the same numbers drive the
|
|
82
|
+
* axis, the ticks and every mark.
|
|
83
|
+
*/
|
|
84
|
+
export declare function buildValueScale(domain: [number, number], range: [number, number]): import("d3-scale").ScaleLinear<number, number, never>;
|
|
85
|
+
/** Point scale for categories, used by the line chart. */
|
|
86
|
+
export declare function buildCategoryPointScale(labels: string[], range: [number, number], padding?: number): import("d3-scale").ScalePoint<string>;
|
|
87
|
+
/**
|
|
88
|
+
* Left margin needed to fit the widest formatted tick label.
|
|
89
|
+
*
|
|
90
|
+
* Estimated from character count rather than measured from the DOM, mirroring
|
|
91
|
+
* `estimateColumnWidth` in `table/utils.ts`, so it works during server rendering
|
|
92
|
+
* and build-time evaluation. The estimate only has to be close: tick labels
|
|
93
|
+
* right-align against the plot and ellipsize, so an underestimate truncates one
|
|
94
|
+
* label rather than breaking the layout, and {@link MAX_AUTO_MARGIN_LEFT} caps
|
|
95
|
+
* what a pathological formatter can take from the plot.
|
|
96
|
+
*
|
|
97
|
+
* Rounded up to {@link AUTO_MARGIN_LEFT_STEP} rather than used raw — see that
|
|
98
|
+
* constant for why an exact fit is the wrong target.
|
|
99
|
+
*
|
|
100
|
+
* Safe to call before the chart has been measured: `nice()` and `ticks()` depend
|
|
101
|
+
* on the domain and the requested count, never on the scale's range, so the tick
|
|
102
|
+
* values here are the same ones the axis renders later.
|
|
103
|
+
*/
|
|
104
|
+
export declare function estimateValueAxisMargin(ticks: number[], formatter: ChartValueFormatter): number;
|
|
105
|
+
/**
|
|
106
|
+
* How many categories to skip between rendered axis labels.
|
|
107
|
+
*
|
|
108
|
+
* Returns 1 when every label fits, so a chart that is not crowded renders
|
|
109
|
+
* exactly as it would with no skipping at all.
|
|
110
|
+
*/
|
|
111
|
+
export declare function getCategoryLabelStride(labels: string[], step: number): number;
|
|
112
|
+
/**
|
|
113
|
+
* Tick values for a resolved domain, honoring an explicit tick count.
|
|
114
|
+
*
|
|
115
|
+
* Needs no dimensions, which is what breaks the apparent circularity in the
|
|
116
|
+
* layout: the left margin must fit the tick labels, the margin decides the plot
|
|
117
|
+
* size, and the plot size builds the scale. Tick *values* fall out of the domain
|
|
118
|
+
* alone, so they can be known before anything is measured.
|
|
119
|
+
*/
|
|
120
|
+
export declare function getValueTicks(domain: [number, number], override?: Partial<ChartValueDomain>): number[];
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { CAPTION_CHAR_WIDTH_PX, DEFAULT_CHART_MARGIN } from "./constants.js";
|
|
2
|
+
import { scaleLinear, scalePoint } from "d3-scale";
|
|
3
|
+
//#region src/charts/chartScales.ts
|
|
4
|
+
/**
|
|
5
|
+
* Scale construction and axis sizing for the chart components.
|
|
6
|
+
*
|
|
7
|
+
* Pure arithmetic with no React and no theme access, so it can be unit-reasoned
|
|
8
|
+
* about and stays safe for Linaria's build-time evaluation.
|
|
9
|
+
*/
|
|
10
|
+
function clamp(value, min, max) {
|
|
11
|
+
return Math.min(Math.max(value, min), max);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Coerce a datum value into something safe to feed a scale.
|
|
15
|
+
*
|
|
16
|
+
* A single `NaN` or `Infinity` in consumer data otherwise propagates into every
|
|
17
|
+
* path's `d` attribute and blanks the entire chart rather than one mark.
|
|
18
|
+
*/
|
|
19
|
+
function sanitizeValue(value) {
|
|
20
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Merge a partial margin over the defaults.
|
|
24
|
+
*
|
|
25
|
+
* Written per-field rather than as a spread: spreading an object that carries
|
|
26
|
+
* an explicit `undefined` overwrites the default with `undefined` rather than
|
|
27
|
+
* falling back to it, which is easy to trip over when the partial is assembled
|
|
28
|
+
* from separate optional values.
|
|
29
|
+
*
|
|
30
|
+
* `defaults` exists because the cartesian default reserves axis gutters a
|
|
31
|
+
* radial chart has no use for. Taking it as a parameter keeps the merge
|
|
32
|
+
* per-field: a chart that substituted its own defaults with `margin ?? OWN`
|
|
33
|
+
* would silently fall back to the *cartesian* defaults the moment a caller
|
|
34
|
+
* passed a partial, handing an axis-less chart a 52px left gutter.
|
|
35
|
+
*/
|
|
36
|
+
function resolveMargin(partial, defaults = DEFAULT_CHART_MARGIN) {
|
|
37
|
+
return {
|
|
38
|
+
top: partial?.top ?? defaults.top,
|
|
39
|
+
right: partial?.right ?? defaults.right,
|
|
40
|
+
bottom: partial?.bottom ?? defaults.bottom,
|
|
41
|
+
left: partial?.left ?? defaults.left
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Inner plot dimensions, never negative. */
|
|
45
|
+
function getPlotDimensions(width, height, margin) {
|
|
46
|
+
return {
|
|
47
|
+
plotWidth: Math.max(0, width - margin.left - margin.right),
|
|
48
|
+
plotHeight: Math.max(0, height - margin.top - margin.bottom)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Widen a margin to leave room for axis titles.
|
|
53
|
+
*
|
|
54
|
+
* The reservation has to happen here rather than inside the axis, because the
|
|
55
|
+
* margin is resolved before any axis renders — and with the root SVG set to
|
|
56
|
+
* `overflow: visible`, an unreserved title would paint outside the container and
|
|
57
|
+
* be clipped by whatever the consumer wraps the chart in.
|
|
58
|
+
*
|
|
59
|
+
* Applied to the caller's *partial* before {@link resolveMargin} fills in the
|
|
60
|
+
* defaults, so an explicit `margin.left` still governs the tick gutter and the
|
|
61
|
+
* title band is added on top of it rather than eating into it. Returns the input
|
|
62
|
+
* untouched when there are no titles, so the common case allocates nothing.
|
|
63
|
+
*/
|
|
64
|
+
function addAxisTitleSpace(partial, { hasValueTitle, hasCategoryTitle }) {
|
|
65
|
+
if (!hasValueTitle && !hasCategoryTitle) return partial;
|
|
66
|
+
return {
|
|
67
|
+
...partial,
|
|
68
|
+
left: (partial?.left ?? DEFAULT_CHART_MARGIN.left) + (hasValueTitle ? 26 : 0),
|
|
69
|
+
bottom: (partial?.bottom ?? DEFAULT_CHART_MARGIN.bottom) + (hasCategoryTitle ? 18 : 0)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Turn a raw data extent into the domain the axis and the marks both use.
|
|
74
|
+
*
|
|
75
|
+
* Applies the caller's override, then three behaviours worth knowing:
|
|
76
|
+
*
|
|
77
|
+
* - **Zero is always in the domain.** Bars need a baseline to grow from, and
|
|
78
|
+
* without this a negative value maps outside the range and renders as a
|
|
79
|
+
* rectangle with negative height, which paints nothing at all.
|
|
80
|
+
* - **A zero-width domain is widened to 0–1.** d3's `normalize()` returns a
|
|
81
|
+
* constant `0.5` when the domain has no extent, so empty or all-zero data
|
|
82
|
+
* would otherwise map every value to the middle of the plot and strand the
|
|
83
|
+
* baseline halfway up.
|
|
84
|
+
* - **`nice()` rounds the ends outward** to values a reader would choose.
|
|
85
|
+
*
|
|
86
|
+
* Split out of {@link buildValueScale} so the domain is computed exactly once
|
|
87
|
+
* per chart and then handed to both the tick list and the geometry. Building it
|
|
88
|
+
* twice — once at a throwaway range to get ticks, once at the real range to
|
|
89
|
+
* place marks — leaves two `nice()` calls that must agree forever.
|
|
90
|
+
*
|
|
91
|
+
* `nice()` deliberately takes **no argument**. The default count is 10, not the
|
|
92
|
+
* five ticks {@link DEFAULT_VALUE_TICK_COUNT} asks for, and nicing to the tick
|
|
93
|
+
* count instead would change the axis extent on every existing chart.
|
|
94
|
+
*/
|
|
95
|
+
function resolveValueDomain(raw, override) {
|
|
96
|
+
const min = override?.min ?? raw[0];
|
|
97
|
+
const max = override?.max ?? raw[1];
|
|
98
|
+
const lo = Math.min(0, min);
|
|
99
|
+
const hi = Math.max(0, max);
|
|
100
|
+
const [niceLo, niceHi] = scaleLinear().domain([lo, lo === hi ? hi + 1 : hi]).nice().domain();
|
|
101
|
+
return [niceLo, niceHi];
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Linear scale over an already-resolved domain.
|
|
105
|
+
*
|
|
106
|
+
* Does no nicing and no zero-forcing — {@link resolveValueDomain} has done
|
|
107
|
+
* both. Takes the domain rather than raw data so the same numbers drive the
|
|
108
|
+
* axis, the ticks and every mark.
|
|
109
|
+
*/
|
|
110
|
+
function buildValueScale(domain, range) {
|
|
111
|
+
return scaleLinear().domain(domain).range(range);
|
|
112
|
+
}
|
|
113
|
+
/** Point scale for categories, used by the line chart. */
|
|
114
|
+
function buildCategoryPointScale(labels, range, padding = 0) {
|
|
115
|
+
return scalePoint().domain(labels).range(range).padding(padding);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Left margin needed to fit the widest formatted tick label.
|
|
119
|
+
*
|
|
120
|
+
* Estimated from character count rather than measured from the DOM, mirroring
|
|
121
|
+
* `estimateColumnWidth` in `table/utils.ts`, so it works during server rendering
|
|
122
|
+
* and build-time evaluation. The estimate only has to be close: tick labels
|
|
123
|
+
* right-align against the plot and ellipsize, so an underestimate truncates one
|
|
124
|
+
* label rather than breaking the layout, and {@link MAX_AUTO_MARGIN_LEFT} caps
|
|
125
|
+
* what a pathological formatter can take from the plot.
|
|
126
|
+
*
|
|
127
|
+
* Rounded up to {@link AUTO_MARGIN_LEFT_STEP} rather than used raw — see that
|
|
128
|
+
* constant for why an exact fit is the wrong target.
|
|
129
|
+
*
|
|
130
|
+
* Safe to call before the chart has been measured: `nice()` and `ticks()` depend
|
|
131
|
+
* on the domain and the requested count, never on the scale's range, so the tick
|
|
132
|
+
* values here are the same ones the axis renders later.
|
|
133
|
+
*/
|
|
134
|
+
function estimateValueAxisMargin(ticks, formatter) {
|
|
135
|
+
let widest = 0;
|
|
136
|
+
for (const tick of ticks) widest = Math.max(widest, formatter(tick).length * CAPTION_CHAR_WIDTH_PX);
|
|
137
|
+
const needed = Math.ceil(widest) + 12;
|
|
138
|
+
return clamp(Math.ceil(needed / 8) * 8, 32, 96);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* How many categories to skip between rendered axis labels.
|
|
142
|
+
*
|
|
143
|
+
* Returns 1 when every label fits, so a chart that is not crowded renders
|
|
144
|
+
* exactly as it would with no skipping at all.
|
|
145
|
+
*/
|
|
146
|
+
function getCategoryLabelStride(labels, step) {
|
|
147
|
+
if (labels.length === 0 || step <= 0) return 1;
|
|
148
|
+
let widest = 0;
|
|
149
|
+
for (const label of labels) widest = Math.max(widest, label.length * CAPTION_CHAR_WIDTH_PX);
|
|
150
|
+
const required = widest + 8;
|
|
151
|
+
return required <= step ? 1 : Math.ceil(required / step);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Tick values for a resolved domain, honoring an explicit tick count.
|
|
155
|
+
*
|
|
156
|
+
* Needs no dimensions, which is what breaks the apparent circularity in the
|
|
157
|
+
* layout: the left margin must fit the tick labels, the margin decides the plot
|
|
158
|
+
* size, and the plot size builds the scale. Tick *values* fall out of the domain
|
|
159
|
+
* alone, so they can be known before anything is measured.
|
|
160
|
+
*/
|
|
161
|
+
function getValueTicks(domain, override) {
|
|
162
|
+
return scaleLinear().domain(domain).ticks(override?.tickCount ?? 5);
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
export { addAxisTitleSpace, buildCategoryPointScale, buildValueScale, clamp, estimateValueAxisMargin, getCategoryLabelStride, getPlotDimensions, getValueTicks, resolveMargin, resolveValueDomain, sanitizeValue };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Theme } from "../theme/types";
|
|
2
|
+
import { ChartPalette } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Palette resolution for the chart components.
|
|
5
|
+
*
|
|
6
|
+
* Consumer data names a {@link ChartPalette} slot; the concrete color is looked
|
|
7
|
+
* up here against the active theme. That keeps raw hex out of application data
|
|
8
|
+
* and guarantees both themes render correctly.
|
|
9
|
+
*
|
|
10
|
+
* Linaria evaluates this module at build time, so it must not import components.
|
|
11
|
+
*/
|
|
12
|
+
/** Resolve a palette slot to a concrete color for the active theme. */
|
|
13
|
+
export declare function resolveChartPaletteColor(theme: Theme, palette: ChartPalette): string;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the color for a series, honoring an explicit slot and otherwise
|
|
16
|
+
* falling back to the series' position in the default palette order.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveSeriesColor(theme: Theme, index: number, palette?: ChartPalette): string;
|