termcast 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +8 -7
- package/dist/build.js.map +1 -1
- package/dist/cli.js +0 -40
- package/dist/cli.js.map +1 -1
- package/dist/components/bar-graph.d.ts +23 -8
- package/dist/components/bar-graph.d.ts.map +1 -1
- package/dist/components/bar-graph.js +84 -40
- package/dist/components/bar-graph.js.map +1 -1
- package/dist/components/dotted-line-graph.d.ts +86 -0
- package/dist/components/dotted-line-graph.d.ts.map +1 -0
- package/dist/components/dotted-line-graph.js +260 -0
- package/dist/components/dotted-line-graph.js.map +1 -0
- package/dist/components/extension-preferences.d.ts.map +1 -1
- package/dist/components/extension-preferences.js +1 -10
- package/dist/components/extension-preferences.js.map +1 -1
- package/dist/components/graph.d.ts.map +1 -1
- package/dist/components/graph.js +7 -1
- package/dist/components/graph.js.map +1 -1
- package/dist/components/histogram.d.ts +42 -0
- package/dist/components/histogram.d.ts.map +1 -0
- package/dist/components/histogram.js +115 -0
- package/dist/components/histogram.js.map +1 -0
- package/dist/components/horizontal-bar-graph.d.ts +47 -0
- package/dist/components/horizontal-bar-graph.d.ts.map +1 -0
- package/dist/components/horizontal-bar-graph.js +137 -0
- package/dist/components/horizontal-bar-graph.js.map +1 -0
- package/dist/components/list.d.ts +2 -0
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +10 -10
- package/dist/components/list.js.map +1 -1
- package/dist/examples/bar-graph-weekly.js +2 -2
- package/dist/examples/bar-graph-weekly.js.map +1 -1
- package/dist/examples/charts-showcase-barchart.d.ts +2 -0
- package/dist/examples/charts-showcase-barchart.d.ts.map +1 -0
- package/dist/examples/charts-showcase-barchart.js +10 -0
- package/dist/examples/charts-showcase-barchart.js.map +1 -0
- package/dist/examples/charts-showcase-bargraph.d.ts +2 -0
- package/dist/examples/charts-showcase-bargraph.d.ts.map +1 -0
- package/dist/examples/charts-showcase-bargraph.js +60 -0
- package/dist/examples/charts-showcase-bargraph.js.map +1 -0
- package/dist/examples/charts-showcase-candle.d.ts +2 -0
- package/dist/examples/charts-showcase-candle.d.ts.map +1 -0
- package/dist/examples/charts-showcase-candle.js +30 -0
- package/dist/examples/charts-showcase-candle.js.map +1 -0
- package/dist/examples/charts-showcase-graph.d.ts +2 -0
- package/dist/examples/charts-showcase-graph.d.ts.map +1 -0
- package/dist/examples/charts-showcase-graph.js +33 -0
- package/dist/examples/charts-showcase-graph.js.map +1 -0
- package/dist/examples/charts-showcase-heatmap.d.ts +2 -0
- package/dist/examples/charts-showcase-heatmap.d.ts.map +1 -0
- package/dist/examples/charts-showcase-heatmap.js +36 -0
- package/dist/examples/charts-showcase-heatmap.js.map +1 -0
- package/dist/examples/charts-showcase-mixed.d.ts +2 -0
- package/dist/examples/charts-showcase-mixed.d.ts.map +1 -0
- package/dist/examples/charts-showcase-mixed.js +30 -0
- package/dist/examples/charts-showcase-mixed.js.map +1 -0
- package/dist/examples/charts-showcase-progress.d.ts +2 -0
- package/dist/examples/charts-showcase-progress.d.ts.map +1 -0
- package/dist/examples/charts-showcase-progress.js +10 -0
- package/dist/examples/charts-showcase-progress.js.map +1 -0
- package/dist/examples/graph-multi-series.js +1 -1
- package/dist/examples/graph-multi-series.js.map +1 -1
- package/dist/examples/horizontal-bar-graph-weekly.d.ts +2 -0
- package/dist/examples/horizontal-bar-graph-weekly.d.ts.map +1 -0
- package/dist/examples/horizontal-bar-graph-weekly.js +67 -0
- package/dist/examples/horizontal-bar-graph-weekly.js.map +1 -0
- package/dist/examples/simple-dotted-line-graph.d.ts +2 -0
- package/dist/examples/simple-dotted-line-graph.d.ts.map +1 -0
- package/dist/examples/simple-dotted-line-graph.js +39 -0
- package/dist/examples/simple-dotted-line-graph.js.map +1 -0
- package/dist/examples/simple-histogram.d.ts +2 -0
- package/dist/examples/simple-histogram.d.ts.map +1 -0
- package/dist/examples/simple-histogram.js +47 -0
- package/dist/examples/simple-histogram.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +15 -6
- package/dist/logger.js.map +1 -1
- package/dist/platform/node/sqlite.d.ts +6 -5
- package/dist/platform/node/sqlite.d.ts.map +1 -1
- package/dist/platform/node/sqlite.js +30 -14
- package/dist/platform/node/sqlite.js.map +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +11 -9
- package/dist/theme.js.map +1 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +8 -19
- package/dist/utils/run-command.js.map +1 -1
- package/dist/utils.d.ts +1 -19
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -100
- package/dist/utils.js.map +1 -1
- package/package.json +14 -16
- package/src/build.tsx +11 -10
- package/src/cli.tsx +3 -40
- package/src/compile.vitest.tsx +3 -3
- package/src/components/bar-graph.tsx +217 -111
- package/src/components/dotted-line-graph.tsx +407 -0
- package/src/components/extension-preferences.tsx +2 -12
- package/src/components/graph.tsx +5 -1
- package/src/components/histogram.tsx +228 -0
- package/src/components/horizontal-bar-graph.tsx +279 -0
- package/src/components/list.tsx +20 -15
- package/src/examples/action-shortcut.vitest.tsx +17 -17
- package/src/examples/bar-graph-weekly.tsx +2 -2
- package/src/examples/bar-graph-weekly.vitest.tsx +63 -62
- package/src/examples/charts-showcase-bargraph.tsx +103 -0
- package/src/examples/detail-metadata-showcase.vitest.tsx +13 -18
- package/src/examples/form-basic.vitest.tsx +35 -35
- package/src/examples/form-dropdown.vitest.tsx +11 -11
- package/src/examples/form-scroll.vitest.tsx +1 -1
- package/src/examples/form-tagpicker.vitest.tsx +11 -11
- package/src/examples/github.vitest.tsx +22 -22
- package/src/examples/graph-bar-chart.vitest.tsx +8 -8
- package/src/examples/graph-multi-series.tsx +1 -1
- package/src/examples/graph-row.vitest.tsx +14 -14
- package/src/examples/graph-styles.vitest.tsx +77 -77
- package/src/examples/horizontal-bar-graph-weekly.tsx +138 -0
- package/src/examples/horizontal-bar-graph-weekly.vitest.tsx +164 -0
- package/src/examples/list-detail-metadata.vitest.tsx +4 -4
- package/src/examples/list-with-detail.vitest.tsx +46 -46
- package/src/examples/simple-candle-chart.vitest.tsx +8 -8
- package/src/examples/simple-dotted-line-graph.tsx +53 -0
- package/src/examples/simple-dotted-line-graph.vitest.tsx +62 -0
- package/src/examples/simple-grid.vitest.tsx +4 -4
- package/src/examples/simple-histogram.tsx +90 -0
- package/src/examples/simple-navigation.vitest.tsx +4 -4
- package/src/examples/swift-extension.vitest.tsx +3 -3
- package/src/examples/toast-variations.vitest.tsx +5 -5
- package/src/extensions/dev.vitest.tsx +8 -8
- package/src/index.tsx +21 -0
- package/src/logger.tsx +16 -6
- package/src/platform/node/sqlite.ts +29 -13
- package/src/theme.tsx +11 -10
- package/src/utils/run-command.tsx +10 -19
- package/src/utils.tsx +0 -163
- package/src/examples/store.tsx +0 -4
- package/src/examples/store.vitest.tsx +0 -78
- package/src/extensions/home.tsx +0 -227
- package/src/extensions/store.tsx +0 -375
package/src/cli.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import { goke } from 'goke'
|
|
|
11
11
|
import { getWatcher } from './watcher'
|
|
12
12
|
import { buildExtensionCommands } from './build'
|
|
13
13
|
import { logger } from './logger'
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
import { searchStoreListings } from './store-api/search'
|
|
16
16
|
import './globals'
|
|
17
17
|
import { startDevMode, triggerRebuild } from './extensions/dev'
|
|
@@ -19,7 +19,7 @@ import { compileExtension } from './compile'
|
|
|
19
19
|
import { releaseExtension } from './release'
|
|
20
20
|
import { buildApp } from './app'
|
|
21
21
|
import { themeNames } from './themes'
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
import { showToast, Toast } from './apis/toast'
|
|
24
24
|
import packageJson from '../package.json'
|
|
25
25
|
|
|
@@ -174,40 +174,7 @@ cli
|
|
|
174
174
|
await runDevAction(rawExtensionPath)
|
|
175
175
|
})
|
|
176
176
|
|
|
177
|
-
cli
|
|
178
|
-
.command('build [path]', 'Build and install the extension to user store')
|
|
179
|
-
.action(async (extensionPath, options) => {
|
|
180
|
-
extensionPath = path.resolve(extensionPath || process.cwd())
|
|
181
177
|
|
|
182
|
-
console.log('Building extension...')
|
|
183
|
-
try {
|
|
184
|
-
const buildResult = await buildExtensionCommands({
|
|
185
|
-
extensionPath,
|
|
186
|
-
format: 'esm',
|
|
187
|
-
target: 'bun',
|
|
188
|
-
})
|
|
189
|
-
console.log(`Successfully built ${buildResult.commands.length} commands`)
|
|
190
|
-
|
|
191
|
-
for (const cmd of buildResult.commands) {
|
|
192
|
-
if (cmd.bundledPath) {
|
|
193
|
-
console.log(` ✓ ${cmd.name}`)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const packageJsonPath = path.join(extensionPath, 'package.json')
|
|
198
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
|
|
199
|
-
const extensionName = packageJson.name || path.basename(extensionPath)
|
|
200
|
-
installExtension({
|
|
201
|
-
extensionName,
|
|
202
|
-
extensionSourcePath: extensionPath,
|
|
203
|
-
})
|
|
204
|
-
console.log(`\nExtension installed to store as '${extensionName}'`)
|
|
205
|
-
process.exit(0)
|
|
206
|
-
} catch (error: any) {
|
|
207
|
-
console.error('Build failed:', error.message)
|
|
208
|
-
process.exit(1)
|
|
209
|
-
}
|
|
210
|
-
})
|
|
211
178
|
|
|
212
179
|
cli
|
|
213
180
|
.command('compile [path]', 'Compile the extension to a standalone executable')
|
|
@@ -846,11 +813,7 @@ cli
|
|
|
846
813
|
}
|
|
847
814
|
})
|
|
848
815
|
|
|
849
|
-
|
|
850
|
-
.command('legacy-raycast-store', 'List and run installed extensions')
|
|
851
|
-
.action(async () => {
|
|
852
|
-
await runHomeCommand()
|
|
853
|
-
})
|
|
816
|
+
|
|
854
817
|
|
|
855
818
|
|
|
856
819
|
|
package/src/compile.vitest.tsx
CHANGED
|
@@ -90,10 +90,10 @@ test('compile extension and run executable', async () => {
|
|
|
90
90
|
|
|
91
91
|
Commands
|
|
92
92
|
›List Items Displays a simple list with some items view
|
|
93
|
-
|
|
93
|
+
Sear...temsSearch and filter...gh a list of items view
|
|
94
94
|
Google Oauth view
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
useP...DemoShows how to use ...rom @raycast/utils view
|
|
96
|
+
Sh...ateShows the current a...tate in JSON format view
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
↵ run command ↑↓ navigate ^k actions :vim
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Pure React/opentui implementation using <box> elements with justifyContent
|
|
5
5
|
* "space-evenly" for bar distribution. Each bar is a column of stacked colored
|
|
6
|
-
* segments sized via flexGrow.
|
|
6
|
+
* segments sized via flexGrow. Segments render with a thin lower-block glyph
|
|
7
|
+
* instead of painted backgrounds, which keeps the chart airy like Histogram.
|
|
8
|
+
* Y-axis labels render on the left. X-axis labels sit below each bar, truncated with
|
|
7
9
|
* overflow="hidden" when the bar is narrower than the label text.
|
|
8
10
|
*
|
|
9
|
-
* Legend is a compact row
|
|
11
|
+
* Legend is a compact bottom row by default. Use legendPosition="right"
|
|
12
|
+
* for a right-side column.
|
|
10
13
|
*
|
|
11
|
-
* Color palette
|
|
12
|
-
* accent, info, success, warning, error, secondary, primary (cycles with %)
|
|
14
|
+
* Color palette comes from getThemePalette() and cycles with %.
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
17
|
import React, { ReactNode, useMemo } from 'react'
|
|
@@ -33,25 +35,28 @@ export interface BarGraphProps extends BoxProps {
|
|
|
33
35
|
height?: number
|
|
34
36
|
/** X-axis labels, one per bar position */
|
|
35
37
|
labels?: string[]
|
|
36
|
-
/**
|
|
38
|
+
/** Width of each bar in terminal columns (default: 3) */
|
|
39
|
+
barWidth?: number
|
|
40
|
+
/** Gap between bars in terminal columns (default: 1) */
|
|
41
|
+
barGap?: number
|
|
42
|
+
/** Character used for bar cells (default: "▃") */
|
|
43
|
+
barCharacter?: string
|
|
44
|
+
/** Show Y-axis labels and separator (default: true) */
|
|
45
|
+
showYAxis?: boolean
|
|
46
|
+
/** Number of Y-axis tick labels (default: 5) */
|
|
47
|
+
yTicks?: number
|
|
48
|
+
/** Custom Y-axis label formatter */
|
|
49
|
+
yFormat?: (value: number) => string
|
|
50
|
+
/** Show compact legend (default: true when any series has a title) */
|
|
37
51
|
showLegend?: boolean
|
|
52
|
+
/** Legend placement (default: "bottom") */
|
|
53
|
+
legendPosition?: 'bottom' | 'right'
|
|
38
54
|
/** BarGraph.Series children */
|
|
39
55
|
children: ReactNode
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
interface BarGraphType {
|
|
43
|
-
(props: BarGraphProps): any
|
|
44
|
-
Series: (props: BarGraphSeriesProps) => any
|
|
45
|
-
}
|
|
46
|
-
|
|
47
58
|
// ── Internal types ───────────────────────────────────────────────────
|
|
48
59
|
|
|
49
|
-
interface CollectedSeries {
|
|
50
|
-
data: number[]
|
|
51
|
-
color: string
|
|
52
|
-
title?: string
|
|
53
|
-
}
|
|
54
|
-
|
|
55
60
|
// ── BarGraph.Series (data-only, renders null like Graph.Line) ────────
|
|
56
61
|
|
|
57
62
|
const BarGraphSeries = (_props: BarGraphSeriesProps): any => {
|
|
@@ -60,33 +65,43 @@ const BarGraphSeries = (_props: BarGraphSeriesProps): any => {
|
|
|
60
65
|
|
|
61
66
|
// ── Main BarGraph component ──────────────────────────────────────────
|
|
62
67
|
|
|
63
|
-
const BarGraph:
|
|
68
|
+
const BarGraph: {
|
|
69
|
+
(props: BarGraphProps): any
|
|
70
|
+
Series: (props: BarGraphSeriesProps) => any
|
|
71
|
+
} = (props) => {
|
|
64
72
|
const theme = useTheme()
|
|
65
|
-
const {
|
|
73
|
+
const {
|
|
74
|
+
height = 15,
|
|
75
|
+
labels = [],
|
|
76
|
+
barWidth = 3,
|
|
77
|
+
barGap = 1,
|
|
78
|
+
barCharacter = '▃',
|
|
79
|
+
showYAxis = true,
|
|
80
|
+
yTicks = 5,
|
|
81
|
+
yFormat,
|
|
82
|
+
showLegend,
|
|
83
|
+
legendPosition = 'bottom',
|
|
84
|
+
children,
|
|
85
|
+
...rest
|
|
86
|
+
} = props
|
|
66
87
|
|
|
67
88
|
const palette = getThemePalette(theme)
|
|
68
89
|
|
|
69
90
|
// Collect series from children
|
|
70
|
-
const seriesList = useMemo<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
data: childProps.data,
|
|
84
|
-
color,
|
|
85
|
-
title: childProps.title,
|
|
91
|
+
const seriesList = useMemo<Array<{ data: number[]; color: string; title?: string }>>(() => {
|
|
92
|
+
return React.Children.toArray(children)
|
|
93
|
+
.filter(React.isValidElement)
|
|
94
|
+
.map((child, index) => {
|
|
95
|
+
const childProps = child.props as BarGraphSeriesProps
|
|
96
|
+
return {
|
|
97
|
+
data: childProps.data,
|
|
98
|
+
color: resolveColor(childProps.color) || palette[index % palette.length]!,
|
|
99
|
+
title: childProps.title,
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
.filter((series) => {
|
|
103
|
+
return Array.isArray(series.data)
|
|
86
104
|
})
|
|
87
|
-
colorIndex++
|
|
88
|
-
})
|
|
89
|
-
return result
|
|
90
105
|
}, [children, palette])
|
|
91
106
|
|
|
92
107
|
// Compute number of bars from max data length across series
|
|
@@ -110,95 +125,186 @@ const BarGraph: BarGraphType = (props) => {
|
|
|
110
125
|
|
|
111
126
|
// Whether to show legend: explicit prop, or auto when any series has a title
|
|
112
127
|
const legendVisible = showLegend ?? seriesList.some((s) => s.title)
|
|
128
|
+
const legendRows = seriesList.filter((series) => {
|
|
129
|
+
return Boolean(series.title)
|
|
130
|
+
})
|
|
131
|
+
const legendTitleWidth = Math.max(0, ...legendRows.map((series) => {
|
|
132
|
+
return series.title?.length || 0
|
|
133
|
+
}))
|
|
134
|
+
const legendGap = 1
|
|
135
|
+
const legendWidth = legendVisible ? legendGap + 2 + legendTitleWidth : 0
|
|
136
|
+
const legendOnRight = legendVisible && legendPosition === 'right'
|
|
137
|
+
const legendOnBottom = legendVisible && legendPosition === 'bottom'
|
|
138
|
+
const hasLabels = labels.length > 0
|
|
139
|
+
const plotHeight = Math.max(1, height - (hasLabels ? 1 : 0))
|
|
140
|
+
const safeBarWidth = Math.max(1, Math.floor(barWidth))
|
|
141
|
+
const safeBarGap = Math.max(0, Math.floor(barGap))
|
|
142
|
+
const safeYTicks = Math.max(2, Math.floor(yTicks))
|
|
143
|
+
const formatYValue = yFormat || ((value: number) => {
|
|
144
|
+
return value >= 1000 ? value.toFixed(0) : value.toFixed(1)
|
|
145
|
+
})
|
|
146
|
+
const yAxisLabels = Array.from({ length: safeYTicks }, (_, index) => {
|
|
147
|
+
const value = maxTotal * (1 - index / (safeYTicks - 1))
|
|
148
|
+
return formatYValue(value)
|
|
149
|
+
})
|
|
150
|
+
const yAxisWidth = showYAxis ? Math.max(...yAxisLabels.map((label) => label.length)) : 0
|
|
151
|
+
const yAxisLabelByRow = new Map(yAxisLabels.map((label, index) => {
|
|
152
|
+
const row = Math.round((index / (safeYTicks - 1)) * (plotHeight - 1))
|
|
153
|
+
return [row, label] as const
|
|
154
|
+
}))
|
|
155
|
+
const xAxisWidth = numBars * safeBarWidth + Math.max(0, numBars - 1) * safeBarGap
|
|
156
|
+
const xAxisLabelLine = (() => {
|
|
157
|
+
const chars = Array.from({ length: xAxisWidth }, () => ' ')
|
|
158
|
+
let occupiedEnd = -1
|
|
159
|
+
|
|
160
|
+
labels.forEach((label, index) => {
|
|
161
|
+
if (!label) {
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
const visibleLabel = label.slice(0, xAxisWidth)
|
|
165
|
+
const barStart = index * (safeBarWidth + safeBarGap)
|
|
166
|
+
const barCenter = barStart + Math.floor(safeBarWidth / 2)
|
|
167
|
+
const labelStart = Math.max(0, Math.min(
|
|
168
|
+
barCenter - Math.floor(visibleLabel.length / 2),
|
|
169
|
+
xAxisWidth - visibleLabel.length,
|
|
170
|
+
))
|
|
171
|
+
|
|
172
|
+
if (labelStart <= occupiedEnd) {
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Array.from(visibleLabel).forEach((char, charIndex) => {
|
|
177
|
+
chars[labelStart + charIndex] = char
|
|
178
|
+
})
|
|
179
|
+
occupiedEnd = labelStart + visibleLabel.length - 1
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
return chars.join('')
|
|
183
|
+
})()
|
|
113
184
|
|
|
114
185
|
if (numBars === 0 || maxTotal === 0) {
|
|
115
186
|
return null
|
|
116
187
|
}
|
|
117
188
|
|
|
118
189
|
return (
|
|
119
|
-
<box flexDirection=
|
|
120
|
-
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<box flexGrow={emptyGrow} />
|
|
151
|
-
)}
|
|
152
|
-
{/* Segments: last series at top, first at bottom.
|
|
153
|
-
Each segment uses backgroundColor for the visual fill, plus
|
|
154
|
-
a single █ with matching fg so bars appear in text snapshots.
|
|
155
|
-
wrapMode="none" prevents text from expanding the segment height. */}
|
|
156
|
-
{[...seriesList].reverse().map((series, reverseIdx) => {
|
|
157
|
-
const value = series.data[barIdx] || 0
|
|
158
|
-
if (value <= 0) {
|
|
159
|
-
return null
|
|
160
|
-
}
|
|
161
|
-
return (
|
|
190
|
+
<box flexDirection={legendOnRight ? 'row' : 'column'} {...rest}>
|
|
191
|
+
<box flexDirection="column" flexGrow={1} flexShrink={1} overflow="hidden">
|
|
192
|
+
<box flexDirection="row" height={height} width="100%" alignItems="flex-start" overflow="hidden">
|
|
193
|
+
{showYAxis ? (
|
|
194
|
+
<box flexDirection="column" width={yAxisWidth + 1} height={height} flexShrink={0} overflow="hidden">
|
|
195
|
+
{Array.from({ length: plotHeight }, (_, row) => {
|
|
196
|
+
const label = yAxisLabelByRow.get(row) || ''
|
|
197
|
+
return (
|
|
198
|
+
<box key={row} flexDirection="row" height={1} flexShrink={0} overflow="hidden">
|
|
199
|
+
<box width={yAxisWidth} overflow="hidden" flexShrink={0}>
|
|
200
|
+
<text wrapMode="none" fg={theme.textMuted}>{label.padStart(yAxisWidth)}</text>
|
|
201
|
+
</box>
|
|
202
|
+
<text wrapMode="none" fg={theme.textMuted}>│</text>
|
|
203
|
+
</box>
|
|
204
|
+
)
|
|
205
|
+
})}
|
|
206
|
+
{hasLabels ? <box height={1} flexShrink={0} /> : null}
|
|
207
|
+
</box>
|
|
208
|
+
) : null}
|
|
209
|
+
|
|
210
|
+
<box flexDirection="column" height={height} flexGrow={1} flexShrink={1} overflow="hidden">
|
|
211
|
+
{/* Bars area: overflow="hidden" clips excess bars when there are too many.
|
|
212
|
+
alignItems="flex-start" left-aligns bars in wide containers. */}
|
|
213
|
+
<box flexDirection="row" height={plotHeight} width="100%" alignItems="flex-start" overflow="hidden">
|
|
214
|
+
{Array.from({ length: numBars }, (_, barIdx) => {
|
|
215
|
+
const barTotal = stackedTotals[barIdx]!
|
|
216
|
+
const emptyGrow = maxTotal - barTotal
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<React.Fragment key={barIdx}>
|
|
220
|
+
{barIdx > 0 && safeBarGap > 0 ? <box width={safeBarGap} flexShrink={0} /> : null}
|
|
162
221
|
<box
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
overflow="hidden"
|
|
222
|
+
flexDirection="column"
|
|
223
|
+
height="100%"
|
|
224
|
+
flexGrow={0}
|
|
225
|
+
flexShrink={0}
|
|
226
|
+
width={safeBarWidth}
|
|
169
227
|
>
|
|
170
|
-
{/*
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
228
|
+
{/* Plot area: spacer on top pushes colored segments to the bottom
|
|
229
|
+
so all bars are bottom-aligned regardless of total value. */}
|
|
230
|
+
<box flexDirection="column" flexGrow={1} width="100%">
|
|
231
|
+
{emptyGrow > 0 && (
|
|
232
|
+
<box flexGrow={emptyGrow} />
|
|
233
|
+
)}
|
|
234
|
+
{/* Segments: last series at top, first at bottom. The repeated
|
|
235
|
+
lower-block glyph wraps inside the fixed-width segment, so it
|
|
236
|
+
stays visible in snapshots without filling the whole cell. */}
|
|
237
|
+
{[...seriesList].reverse().map((series, reverseIdx) => {
|
|
238
|
+
const value = series.data[barIdx] || 0
|
|
239
|
+
if (value <= 0) {
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
return (
|
|
243
|
+
<box
|
|
244
|
+
key={reverseIdx}
|
|
245
|
+
flexGrow={value}
|
|
246
|
+
width="100%"
|
|
247
|
+
minHeight={1}
|
|
248
|
+
overflow="hidden"
|
|
249
|
+
>
|
|
250
|
+
{/* Absolute-positioned text doesn't affect flex layout.
|
|
251
|
+
The parent height is purely from flexGrow. The text
|
|
252
|
+
wraps to fill the area and gets clipped. */}
|
|
253
|
+
<box position="absolute" width="100%" height="100%" overflow="hidden">
|
|
254
|
+
<text fg={series.color}>{barCharacter.repeat(200)}</text>
|
|
255
|
+
</box>
|
|
256
|
+
</box>
|
|
257
|
+
)
|
|
258
|
+
})}
|
|
175
259
|
</box>
|
|
176
260
|
</box>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
{/* X-axis label */}
|
|
181
|
-
{label !== undefined && (
|
|
182
|
-
<box height={1} width="100%" overflow="hidden" flexShrink={0}>
|
|
183
|
-
<text wrapMode="none" fg={theme.textMuted}>{label}</text>
|
|
184
|
-
</box>
|
|
185
|
-
)}
|
|
261
|
+
</React.Fragment>
|
|
262
|
+
)
|
|
263
|
+
})}
|
|
186
264
|
</box>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
265
|
+
{hasLabels ? (
|
|
266
|
+
<box height={1} width="100%" overflow="hidden" flexShrink={0}>
|
|
267
|
+
<text wrapMode="none" fg={theme.textMuted}>{xAxisLabelLine}</text>
|
|
268
|
+
</box>
|
|
269
|
+
) : null}
|
|
270
|
+
</box>
|
|
271
|
+
</box>
|
|
191
272
|
</box>
|
|
192
|
-
|
|
193
|
-
{
|
|
273
|
+
|
|
274
|
+
{/* Legend: right side. Labels stay left-aligned, color swatches sit on the right. */}
|
|
275
|
+
{legendOnRight && (
|
|
276
|
+
<box
|
|
277
|
+
flexDirection="column"
|
|
278
|
+
width={legendWidth}
|
|
279
|
+
height={height}
|
|
280
|
+
flexShrink={0}
|
|
281
|
+
overflow="hidden"
|
|
282
|
+
>
|
|
283
|
+
<box flexDirection="column" height={height} justifyContent="flex-end" overflow="hidden">
|
|
284
|
+
{legendRows.map((series, index) => {
|
|
285
|
+
return (
|
|
286
|
+
<box key={index} flexDirection="row" height={1} flexShrink={0} overflow="hidden">
|
|
287
|
+
<box width={legendGap} flexShrink={0} />
|
|
288
|
+
<box width={legendTitleWidth} overflow="hidden" flexShrink={0}>
|
|
289
|
+
<text fg={theme.textMuted} wrapMode="none">{series.title}</text>
|
|
290
|
+
</box>
|
|
291
|
+
<text fg={series.color} wrapMode="none"> ■</text>
|
|
292
|
+
</box>
|
|
293
|
+
)
|
|
294
|
+
})}
|
|
295
|
+
</box>
|
|
296
|
+
</box>
|
|
297
|
+
)}
|
|
298
|
+
{legendOnBottom && (
|
|
194
299
|
<box height={1} width="100%" flexShrink={0} overflow="hidden">
|
|
195
300
|
<text wrapMode="none">
|
|
196
|
-
{
|
|
197
|
-
const
|
|
301
|
+
{legendRows.map((series, index) => {
|
|
302
|
+
const separator = index < legendRows.length - 1 ? ' ' : ''
|
|
198
303
|
return (
|
|
199
|
-
<React.Fragment key={
|
|
200
|
-
<span fg={series.color}
|
|
201
|
-
<span fg={theme.textMuted}>{series.title}
|
|
304
|
+
<React.Fragment key={index}>
|
|
305
|
+
<span fg={series.color}>■</span>
|
|
306
|
+
<span fg={theme.textMuted}> {series.title}</span>
|
|
307
|
+
<span fg={theme.textMuted}>{separator}</span>
|
|
202
308
|
</React.Fragment>
|
|
203
309
|
)
|
|
204
310
|
})}
|