Skip to content

Commit 8899ee5

Browse files
committed
3.4.1 prework
1 parent 9fd8329 commit 8899ee5

5 files changed

Lines changed: 49 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [3.4.1] - 2026-04-22
9+
10+
### Added
11+
12+
- **`CandlestickChart` HOC** (`semiotic/xy`) — wraps `chartType="candlestick"` with the same mode-aware, animated, push-API conventions as the other XY HOCs. Required: `highAccessor`, `lowAccessor`. Optional: `openAccessor` + `closeAccessor` — omit both and the chart degrades to a range/dumbbell visualization (endpoint dots + wick, no body). Honors `mode="primary" | "context" | "sparkline"`: `scalePadding` scales from width (12 / 10 / 3) to keep leftmost/rightmost bars from clipping, `extentPadding` drops to 2% at widths ≤200 so the y-domain isn't padded into uselessness, and sparkline zeroes `top`/`bottom` margin (axes are stripped, so the 2px defaults were dead space). Full docs page at `/charts/candlestick-chart` with static ↔ streaming toggle, range-chart demo, compact-mode grid for OHLC + Range, and an Animation section demoing data-morph (seeded regenerate button) and a sliding push/remove window.
13+
- **Candlestick animation support** — the transition pipeline in `pipelineTransitions.ts` gained full enter/update/exit branches for `type: "candlestick"` nodes. Bars matching by x-identity smoothly interpolate all four y-coords (`openY`, `closeY`, `highY`, `lowY`) when data updates; new bars fade in; scrolled-off bars fade out with a held-in-place gray stub. Snapshot carries `bodyWidth` too so exits don't jump to a 6px fallback on the final frame. Renderer now composites `decayOpacity * style.opacity` so decay and transition fades stack. `getNodeIdentity` prefers an existing `_transitionKey` over the datum-derived key so exit stubs stay stable across overlapping transitions (fixes a latent reshuffle risk for *all* exit-node types, not just candlestick).
14+
- **Server-side rendering for candlestick**`renderChart("CandlestickChart", ...)` works through a new entry in `serverChartConfigs.ts`. Passthrough config: HOC-level accessors map 1:1 to frame-level ones; `openAccessor`/`closeAccessor` are forwarded without defaults so `PipelineStore` can auto-detect range mode.
15+
- **`compactMode: boolean` on `useChartMode` return** — the context∨sparkline union now lives on the hook instead of being recomputed in each HOC. `GaugeChart` consumes it (replaces the local `modeIsContext || modeIsSparkline` flag and collapses three conditional-render branches into one).
16+
- **`candlestick-range-*` visual regression fixtures** — 3 new modes × 3 browsers = 9 baselines added to the chart-modes matrix specifically covering range-mode rendering (the path that motivated the dot-radius cap).
17+
18+
### Changed
19+
20+
- **Candlestick sparkline rendering** — three rendering changes converge to make high/low lines actually visible at 120×24:
21+
- Wick is drawn **on top** of the body at `layout.height < 60` with a 2px minimum stroke. At sparkline heights the protrusion above/below a tiny body is often <2px and lands on subpixel boundaries, antialiasing to ~11% alpha (invisible). Drawing the wick last shows the full high-low range as a continuous line through the body.
22+
- **Range-mode dot radius** scales with `bodyWidth/2` and caps at `layout.height * 0.12` (was hardcoded `max(wickWidth * 2, 4)` — ≥4px always, marble-sized on a 24px row). Scales up for primary/context.
23+
- Scene builder now computes the **same gap-derived `bodyWidth`** in OHLC and range modes so the renderer has a scale-aware basis for dot sizing.
24+
- **`GaugeChart` needle formula simplification**`innerRadius > 20 ? innerRadius - 8 : radius - 1`. The `Math.max(1, ...)` / `Math.max(2, ...)` floors in the previous formula were dead: the guarded expression is always well above the floor in either branch.
25+
- **Type safety sweep**~216 `any` types eliminated across the codebase. Scene-node interfaces, scale helpers, hook returns, and accessor resolution gained concrete types. No behavior change; catches more regressions at compile time.
26+
- **Major dependency updates**`@playwright/test` + `playwright-chromium` `^1.17.1``^1.59.1` (regenerated 9 darwin baselines for chromium font-rendering shifts on label-heavy charts), `vitest` + `@vitest/coverage-v8` + `@vitest/ui` `^4.0.18``^4.1.4`, `typedoc` `^0.28.17``^0.28.19`, `@axe-core/playwright` `^4.11.1``^4.11.2`, `@modelcontextprotocol/sdk` 1.27.1 → 1.29.0, `@types/node` aligned to Node 22.19.17 (matches the Volta-pinned runtime). `.node-version` corrected from `18``22.22.1`.
27+
28+
### Fixed
29+
30+
- **`RealtimeHistogram.showLegend` dead pass-through**`showLegend` was being fed into `useChartMode` but the resolved value was never consumed (the HOC doesn't construct a `legend` prop for StreamXYFrame). Removed the feed-in and updated the comment to explain the absence.
31+
- **`arrowOfTime` wrongly exposed on `StreamOrdinalFrame`** — removed. The prop only applies to XY time-series layouts; its presence on the ordinal frame was a leftover from a shared-types refactor.
32+
- **Doc TOC duplicate-key warning** — two sections titled "When to reach for which" on `/theming/semantic-colors` slugged to the same React key. Renamed to "When to reach for which role" and "When to reach for which primitive"; `PageLayout` additionally de-dupes TOC keys defensively so a transient DOM overlap during route transitions can't re-surface the warning. `item.id` still carries the real heading id for anchor navigation; `item.key` is a separate React-only identifier.
33+
- **Shadowed cookbook import in `App.js`**`import CandlestickChartPage from "./pages/cookbook/..."` was re-importing the same symbol used by the new `/charts/` route, so the charts-route fell through to the cookbook recipe. Renamed to `CandlestickCookbookPage`.
34+
35+
### Tooling
36+
37+
- `ai/schema.json` and `validationMap.ts` gained `CandlestickChart` entries; `check-schema-freshness.js` and `check-ssr-alignment.js` both pass.
38+
839
## [3.4.0] - 2026-04-18
940

1041
### Added

ai/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json-schema.org/draft/2020-12/schema",
33
"name": "semiotic",
4-
"version": "3.4.0",
4+
"version": "3.4.1",
55
"description": "React data visualization library for charts, networks, and beyond",
66
"tools": [
77
{

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "semiotic",
3-
"version": "3.4.0",
3+
"version": "3.4.1",
44
"mcpName": "io.github.nteract/semiotic",
55
"description": "React data visualization library with built-in MCP server for AI-assisted chart generation",
66
"main": "dist/semiotic.min.js",

src/components/charts/xy/CandlestickChart.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { CandlestickStyle } from "../../stream/types"
99
import { useChartSelection, useChartMode, getCrosshairProps } from "../shared/hooks"
1010
import type { BaseChartProps, AxisConfig, ChartAccessor } from "../shared/types"
1111
import { normalizeTooltip, type TooltipProp } from "../../Tooltip/Tooltip"
12-
import { buildDefaultTooltip, accessorName } from "../shared/tooltipUtils"
12+
import { buildDefaultTooltip, accessorName, type TooltipFieldConfig } from "../shared/tooltipUtils"
1313
import ChartError from "../shared/ChartError"
1414
import { SafeRender, warnMissingField, renderEmptyState, renderLoadingState } from "../shared/withChartWrapper"
1515
import { validateArrayData } from "../shared/validateChartData"
@@ -155,21 +155,24 @@ export const CandlestickChart = forwardRef(function CandlestickChart<TDatum exte
155155
return { ...d, ...userMargin }
156156
}, [userMargin, resolved.marginDefaults, props.mode])
157157

158-
// Tooltip: OHLC when present, range when degraded.
158+
// Tooltip: OHLC when present, range when degraded. `ChartAccessor<TDatum,
159+
// number>` is narrower than TooltipFieldConfig.accessor by parameter
160+
// variance — a single cast at the boundary is cleaner than per-row noise.
159161
const defaultTooltipContent = useMemo(() => {
160-
const rows: Array<{ label: string; accessor: ChartAccessor<TDatum, number> | string; role?: "x" | "y" | "group"; format?: any }> = [
161-
{ label: xLabel || accessorName(xAccessor), accessor: xAccessor, role: "x", format: xFormat },
162+
type Acc = TooltipFieldConfig["accessor"]
163+
const rows: TooltipFieldConfig[] = [
164+
{ label: xLabel || accessorName(xAccessor), accessor: xAccessor as Acc, role: "x", format: xFormat },
162165
]
163166
if (!isRange) {
164-
rows.push({ label: "Open", accessor: openAccessor!, format: yFormat })
165-
rows.push({ label: "High", accessor: highAccessor, format: yFormat })
166-
rows.push({ label: "Low", accessor: lowAccessor, format: yFormat })
167-
rows.push({ label: "Close", accessor: closeAccessor!, format: yFormat })
167+
rows.push({ label: "Open", accessor: openAccessor! as Acc, format: yFormat })
168+
rows.push({ label: "High", accessor: highAccessor as Acc, format: yFormat })
169+
rows.push({ label: "Low", accessor: lowAccessor as Acc, format: yFormat })
170+
rows.push({ label: "Close", accessor: closeAccessor! as Acc, format: yFormat })
168171
} else {
169-
rows.push({ label: "High", accessor: highAccessor, role: "y", format: yFormat })
170-
rows.push({ label: "Low", accessor: lowAccessor, format: yFormat })
172+
rows.push({ label: "High", accessor: highAccessor as Acc, role: "y", format: yFormat })
173+
rows.push({ label: "Low", accessor: lowAccessor as Acc, format: yFormat })
171174
}
172-
return buildDefaultTooltip(rows as any)
175+
return buildDefaultTooltip(rows)
173176
}, [xAccessor, xLabel, xFormat, yFormat, highAccessor, lowAccessor, openAccessor, closeAccessor, isRange])
174177

175178
const validationError = validateArrayData({

0 commit comments

Comments
 (0)