termcast 1.4.1 → 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.
Files changed (140) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +8 -7
  3. package/dist/build.js.map +1 -1
  4. package/dist/cli.js +0 -40
  5. package/dist/cli.js.map +1 -1
  6. package/dist/components/bar-graph.d.ts +23 -8
  7. package/dist/components/bar-graph.d.ts.map +1 -1
  8. package/dist/components/bar-graph.js +84 -40
  9. package/dist/components/bar-graph.js.map +1 -1
  10. package/dist/components/dotted-line-graph.d.ts +86 -0
  11. package/dist/components/dotted-line-graph.d.ts.map +1 -0
  12. package/dist/components/dotted-line-graph.js +260 -0
  13. package/dist/components/dotted-line-graph.js.map +1 -0
  14. package/dist/components/extension-preferences.d.ts.map +1 -1
  15. package/dist/components/extension-preferences.js +1 -10
  16. package/dist/components/extension-preferences.js.map +1 -1
  17. package/dist/components/graph.d.ts.map +1 -1
  18. package/dist/components/graph.js +7 -1
  19. package/dist/components/graph.js.map +1 -1
  20. package/dist/components/histogram.d.ts +42 -0
  21. package/dist/components/histogram.d.ts.map +1 -0
  22. package/dist/components/histogram.js +115 -0
  23. package/dist/components/histogram.js.map +1 -0
  24. package/dist/components/horizontal-bar-graph.d.ts +47 -0
  25. package/dist/components/horizontal-bar-graph.d.ts.map +1 -0
  26. package/dist/components/horizontal-bar-graph.js +137 -0
  27. package/dist/components/horizontal-bar-graph.js.map +1 -0
  28. package/dist/components/list.d.ts +2 -0
  29. package/dist/components/list.d.ts.map +1 -1
  30. package/dist/components/list.js +10 -10
  31. package/dist/components/list.js.map +1 -1
  32. package/dist/examples/bar-graph-weekly.js +2 -2
  33. package/dist/examples/bar-graph-weekly.js.map +1 -1
  34. package/dist/examples/charts-showcase-barchart.d.ts +2 -0
  35. package/dist/examples/charts-showcase-barchart.d.ts.map +1 -0
  36. package/dist/examples/charts-showcase-barchart.js +10 -0
  37. package/dist/examples/charts-showcase-barchart.js.map +1 -0
  38. package/dist/examples/charts-showcase-bargraph.d.ts +2 -0
  39. package/dist/examples/charts-showcase-bargraph.d.ts.map +1 -0
  40. package/dist/examples/charts-showcase-bargraph.js +60 -0
  41. package/dist/examples/charts-showcase-bargraph.js.map +1 -0
  42. package/dist/examples/charts-showcase-candle.d.ts +2 -0
  43. package/dist/examples/charts-showcase-candle.d.ts.map +1 -0
  44. package/dist/examples/charts-showcase-candle.js +30 -0
  45. package/dist/examples/charts-showcase-candle.js.map +1 -0
  46. package/dist/examples/charts-showcase-graph.d.ts +2 -0
  47. package/dist/examples/charts-showcase-graph.d.ts.map +1 -0
  48. package/dist/examples/charts-showcase-graph.js +33 -0
  49. package/dist/examples/charts-showcase-graph.js.map +1 -0
  50. package/dist/examples/charts-showcase-heatmap.d.ts +2 -0
  51. package/dist/examples/charts-showcase-heatmap.d.ts.map +1 -0
  52. package/dist/examples/charts-showcase-heatmap.js +36 -0
  53. package/dist/examples/charts-showcase-heatmap.js.map +1 -0
  54. package/dist/examples/charts-showcase-mixed.d.ts +2 -0
  55. package/dist/examples/charts-showcase-mixed.d.ts.map +1 -0
  56. package/dist/examples/charts-showcase-mixed.js +30 -0
  57. package/dist/examples/charts-showcase-mixed.js.map +1 -0
  58. package/dist/examples/charts-showcase-progress.d.ts +2 -0
  59. package/dist/examples/charts-showcase-progress.d.ts.map +1 -0
  60. package/dist/examples/charts-showcase-progress.js +10 -0
  61. package/dist/examples/charts-showcase-progress.js.map +1 -0
  62. package/dist/examples/graph-multi-series.js +1 -1
  63. package/dist/examples/graph-multi-series.js.map +1 -1
  64. package/dist/examples/horizontal-bar-graph-weekly.d.ts +2 -0
  65. package/dist/examples/horizontal-bar-graph-weekly.d.ts.map +1 -0
  66. package/dist/examples/horizontal-bar-graph-weekly.js +67 -0
  67. package/dist/examples/horizontal-bar-graph-weekly.js.map +1 -0
  68. package/dist/examples/simple-dotted-line-graph.d.ts +2 -0
  69. package/dist/examples/simple-dotted-line-graph.d.ts.map +1 -0
  70. package/dist/examples/simple-dotted-line-graph.js +39 -0
  71. package/dist/examples/simple-dotted-line-graph.js.map +1 -0
  72. package/dist/examples/simple-histogram.d.ts +2 -0
  73. package/dist/examples/simple-histogram.d.ts.map +1 -0
  74. package/dist/examples/simple-histogram.js +47 -0
  75. package/dist/examples/simple-histogram.js.map +1 -0
  76. package/dist/index.d.ts +6 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +6 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/platform/node/sqlite.d.ts +6 -5
  81. package/dist/platform/node/sqlite.d.ts.map +1 -1
  82. package/dist/platform/node/sqlite.js +30 -14
  83. package/dist/platform/node/sqlite.js.map +1 -1
  84. package/dist/theme.d.ts.map +1 -1
  85. package/dist/theme.js +11 -9
  86. package/dist/theme.js.map +1 -1
  87. package/dist/utils/run-command.d.ts.map +1 -1
  88. package/dist/utils/run-command.js +8 -19
  89. package/dist/utils/run-command.js.map +1 -1
  90. package/dist/utils.d.ts +1 -19
  91. package/dist/utils.d.ts.map +1 -1
  92. package/dist/utils.js +1 -100
  93. package/dist/utils.js.map +1 -1
  94. package/package.json +6 -8
  95. package/src/build.tsx +11 -10
  96. package/src/cli.tsx +3 -40
  97. package/src/compile.vitest.tsx +3 -3
  98. package/src/components/bar-graph.tsx +217 -111
  99. package/src/components/dotted-line-graph.tsx +407 -0
  100. package/src/components/extension-preferences.tsx +2 -12
  101. package/src/components/graph.tsx +5 -1
  102. package/src/components/histogram.tsx +228 -0
  103. package/src/components/horizontal-bar-graph.tsx +279 -0
  104. package/src/components/list.tsx +20 -15
  105. package/src/examples/action-shortcut.vitest.tsx +17 -17
  106. package/src/examples/bar-graph-weekly.tsx +2 -2
  107. package/src/examples/bar-graph-weekly.vitest.tsx +63 -62
  108. package/src/examples/charts-showcase-bargraph.tsx +103 -0
  109. package/src/examples/detail-metadata-showcase.vitest.tsx +12 -12
  110. package/src/examples/form-basic.vitest.tsx +11 -11
  111. package/src/examples/form-dropdown.vitest.tsx +11 -11
  112. package/src/examples/form-scroll.vitest.tsx +1 -1
  113. package/src/examples/form-tagpicker.vitest.tsx +11 -11
  114. package/src/examples/github.vitest.tsx +22 -22
  115. package/src/examples/graph-bar-chart.vitest.tsx +8 -8
  116. package/src/examples/graph-multi-series.tsx +1 -1
  117. package/src/examples/graph-row.vitest.tsx +14 -14
  118. package/src/examples/graph-styles.vitest.tsx +77 -77
  119. package/src/examples/horizontal-bar-graph-weekly.tsx +138 -0
  120. package/src/examples/horizontal-bar-graph-weekly.vitest.tsx +164 -0
  121. package/src/examples/list-detail-metadata.vitest.tsx +4 -4
  122. package/src/examples/list-with-detail.vitest.tsx +46 -46
  123. package/src/examples/simple-candle-chart.vitest.tsx +8 -8
  124. package/src/examples/simple-dotted-line-graph.tsx +53 -0
  125. package/src/examples/simple-dotted-line-graph.vitest.tsx +62 -0
  126. package/src/examples/simple-grid.vitest.tsx +4 -4
  127. package/src/examples/simple-heatmap.vitest.tsx +9 -9
  128. package/src/examples/simple-histogram.tsx +90 -0
  129. package/src/examples/simple-navigation.vitest.tsx +4 -4
  130. package/src/examples/swift-extension.vitest.tsx +3 -3
  131. package/src/extensions/dev.vitest.tsx +8 -8
  132. package/src/index.tsx +21 -0
  133. package/src/platform/node/sqlite.ts +29 -13
  134. package/src/theme.tsx +11 -10
  135. package/src/utils/run-command.tsx +10 -19
  136. package/src/utils.tsx +0 -163
  137. package/src/examples/store.tsx +0 -4
  138. package/src/examples/store.vitest.tsx +0 -78
  139. package/src/extensions/home.tsx +0 -227
  140. package/src/extensions/store.tsx +0 -375
@@ -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. Labels sit below each bar, truncated with
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 of Title pairs, no border.
11
+ * Legend is a compact bottom row by default. Use legendPosition="right"
12
+ * for a right-side column.
10
13
  *
11
- * Color palette (same as Graph and BarChart):
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
- /** Show compact legend below the chart (default: true when any series has a title) */
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: BarGraphType = (props) => {
68
+ const BarGraph: {
69
+ (props: BarGraphProps): any
70
+ Series: (props: BarGraphSeriesProps) => any
71
+ } = (props) => {
64
72
  const theme = useTheme()
65
- const { height = 15, labels = [], showLegend, children, ...rest } = props
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<CollectedSeries[]>(() => {
71
- const result: CollectedSeries[] = []
72
- let colorIndex = 0
73
- React.Children.forEach(children, (child) => {
74
- if (!React.isValidElement(child)) {
75
- return
76
- }
77
- const childProps = child.props as BarGraphSeriesProps
78
- if (!childProps.data) {
79
- return
80
- }
81
- const color = resolveColor(childProps.color) || palette[colorIndex % palette.length]!
82
- result.push({
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="column" {...rest}>
120
- {/* Bars area: overflow="hidden" clips excess bars when there are too many.
121
- alignItems="flex-start" left-aligns bars in wide containers. */}
122
- <box flexDirection="row" height={height} width="100%" alignItems="flex-start" overflow="hidden">
123
- {Array.from({ length: numBars }, (_, barIdx) => {
124
- const barTotal = stackedTotals[barIdx]!
125
- const emptyGrow = maxTotal - barTotal
126
- const label = labels[barIdx]
127
-
128
- const barElements: any[] = []
129
-
130
- // Min 1-col gap between bars (not before the first bar)
131
- if (barIdx > 0) {
132
- barElements.push(
133
- <box key={`gap-${barIdx}`} width={1} flexShrink={0} />
134
- )
135
- }
136
-
137
- barElements.push(
138
- <box
139
- key={barIdx}
140
- flexDirection="column"
141
- height="100%"
142
- flexGrow={0}
143
- flexShrink={0}
144
- width={3}
145
- >
146
- {/* Plot area: spacer on top pushes colored segments to the bottom
147
- so all bars are bottom-aligned regardless of total value. */}
148
- <box flexDirection="column" flexGrow={1} width="100%">
149
- {emptyGrow > 0 && (
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
- key={reverseIdx}
164
- flexGrow={value}
165
- backgroundColor={series.color}
166
- width="100%"
167
- minHeight={1}
168
- overflow="hidden"
222
+ flexDirection="column"
223
+ height="100%"
224
+ flexGrow={0}
225
+ flexShrink={0}
226
+ width={safeBarWidth}
169
227
  >
170
- {/* Absolute-positioned text doesn't affect flex layout.
171
- The parent height is purely from flexGrow. The text
172
- wraps to fill the area and gets clipped. */}
173
- <box position="absolute" width="100%" height="100%" overflow="hidden">
174
- <text fg={series.color}>{'█'.repeat(200)}</text>
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
- </box>
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
- return barElements
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
- {/* Legend: single line, no wrap, clips when too many series */}
193
- {legendVisible && (
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
- {seriesList.filter((s) => s.title).map((series, i, arr) => {
197
- const sep = i < arr.length - 1 ? ' ' : ''
301
+ {legendRows.map((series, index) => {
302
+ const separator = index < legendRows.length - 1 ? ' ' : ''
198
303
  return (
199
- <React.Fragment key={i}>
200
- <span fg={series.color}>■ </span>
201
- <span fg={theme.textMuted}>{series.title}{sep}</span>
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
  })}