Market charts: calendar, candles, fitted axes, live candles, axis tags (ADR 0085–0092) - #56
Merged
Merged
Conversation
Everything "What is next" listed — the tidy tree, probability paper, the statistical instruments, the raster, the horizon chart, the bivariate channel, the map — has shipped. The section now groups what exists by the question each answers and keeps a short list of what is genuinely open: v1.0, a mark that fills a closed ring, a conic projection, labels that avoid other layers', a keyframe timeline. The status note at the top said the seams were still being reshaped; they are not. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Text, ErrorBar, QQ, Graph, Contour, Raster and Horizon; axis breaks, label formats and locales; second axes and tracks; label avoidance and callouts; and nulls in text and time columns. Each shipped with its ADR and none reached the one-list summary CONCEPT §14 now points to. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The record stopped at the gantt chart while ternary charts, horizons, rasters, the bivariate channel, probability paper, the tidy tree and the instruments came before it and hatching through axis breaks came after. Each entry is drawn from its ADR and its amendments, in the order they were implemented. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CONCEPT §15 says v1.0.0 returns when the surface settles and nothing says what settled means. The record makes it four stdlib-only checks and a second audit: a test that applies the growth rule, with a point, an interval or a rectangle counted as one value; a committed API baseline CI diffs; a JSON corpus that is only ever added to; and the first audit's verdicts extended over what arrived since. Four interfaces that break the rule as clarified — stat.Family, coord.Exploder, geom.LabelPlacer, scale.Breaker — change before the tag. Proposed rather than accepted: nothing in it is implemented yet. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review of the proposal. The baseline check let every added line through after v1, which is the one addition that breaks every outside implementation; it now refuses a method added to an exported interface and leaves a new field to the reviewer's zero-value question. The exception to the growth rule is a value of fixed arity rather than a geometric shape, which is what ColorAt's bivariate reading is. The test is said to be an allowlist by name and to read every exported interface, the corpus pins documents rather than pixels, and the Go floor is out of scope. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Second review. A committed manifest compared with the code refuses nothing, because a change updates both; compatibility is now checked against the manifest at the latest release tag, so a declaration added in a minor is protected from then on. The manifest records whether each struct is comparable, since a slice field breaks == and map keys with no change in behaviour; covers js/wasm, where backend/canvas and Live.Bind exist; and unkeyed struct literals are stated outside the promise. The release tooling's Major: 0 lock from ADR 0059 is lifted as a step of its own rather than found at the tag. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…view The review reproduced two silent round-trip losses — a parallel coord reading an undescribable dimension back as linear, and every size scale reading back as the built-in area mapping — and found four things v1 would freeze by accident: a window's positional callbacks, registry names that can collide with a later built-in, tuning budgets exported as constants, and comparability on request types that exist to grow. Each is recorded as a change the second audit starts from. The checks are sharpened to match: the growth-rule test reads callbacks and every module that tags v1; the compatibility reference is the latest earlier release, never the tag under test, and v1.0.0 has none; a method is refused only on an interface already published; and a declaration gaining a platform is an addition. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MarkerTriangleDown is appended to the marker set, as its doc comment requires, and mirrors the upward triangle through its centroid so a pair drawn at one point occupies the same box turned over. The dialect spells it "triangle-down", Vega-Lite's own name. The theme's redundant-encoding ladder is left alone: it is a separate decision and every golden draws it. Generated with AI Co-Authored-By: AI <ai@example.com>
MovingAverage is centred, which is wrong at the right-hand edge of a live chart: its last value moves when the next row arrives. TrailingMean, EMA, RollingStdDev, RollingMin, RollingMax and Cumsum look only backwards, so appending a row changes nothing already drawn, and a test says so. RollingStdDev is the population form a Bollinger band is defined with, summed twice per window for the cancellation StdDev avoids. OHLC summarises ticks into candles of a fixed width from an origin and leaves an interval nobody traded in without one. BinWeighted is Bin summing weights, with Bin's own edges, so the buys and the sells of a volume profile line up. Every Append form is allocation-free into a warm slice. Generated with AI Co-Authored-By: AI <ai@example.com>
…arks A Rect body over an ErrorBar wick, a Bollinger band as an Area, an EMA and an SMA, crossings as upward and downward triangles, daily volume in a bottom track sharing time, and the volume at each price in a right track sharing price, on a time axis with the weekends folded. Each place the recipe costs the caller a line — the direction column, the weekend spans, the profile's hand-stacked rects — is a gap ADR 0085 ranks. Generated with AI Co-Authored-By: AI <ai@example.com>
A catalogue in the manner of 0058: the candle, the trading calendar, the combined panels, indicators, annotations and interaction, each with what exists, what is a recipe and what is missing, ranked by what a reader loses. The trading calendar is first, which is 0083's deferred business-day axis; the candle as one mark second. The Stat interface stays refused, because an indicator is numbers in and numbers out drawn by an ordinary mark. The easy wins landed with the record. Generated with AI Co-Authored-By: AI <ai@example.com>
A calendar is a value in scale — sessions per weekday in a location, holidays and short days as the caller's lists — whose Folds(from, to) the time axis takes like any others, so the dialect is unchanged. The one amendment to 0083: a tick at a calendar step of a day or longer that lands in a fold moves to the next open, because with the nights folded every midnight is inside one and the axis has no dates. Opens(from, to) gives stat.OHLCAt the session edges a fixed width cannot across DST. Generated with AI Co-Authored-By: AI <ai@example.com>
Under Horizontal the slots are the Y column and the measurement the X one, with X2 and Y2 swapping along. A mark reads its columns through config.roles, trains and maps through config.axes, and turns once where it emits with config.box and config.point, so the coord, explode, extrude and hit-testing are unchanged. A dodged group lists its first series on top. The dialect writes "orientation" on these marks, puts a horizontal stack on the x channel, and reads Vega-Lite's "orient" on a bar or a boxplot as the same thing. Generated with AI Co-Authored-By: AI <ai@example.com>
The volume profile in examples/market is now a stacked horizontal Bar grouped by side, drawing the same picture as the hand-stacked rects it replaces. ADR 0085's fourth rank is marked landed, and 0053's note that bars and boxplots had no orientation points to 0087. Generated with AI Co-Authored-By: AI <ai@example.com>
A calendar is now an interface — Location, WeekStart, and the stretches of one day that count — so a roster, a term list or a calendar computed in another calendar system is a caller's type. Workweek is the configurable value for the rule-shaped ones, with no default set of days: Monday to Friday, Monday to Saturday and Sunday to Thursday are all spelled out. The week start is the calendar's and the axis's, via scale.WeekStart; week ticks are Monday today only because Go truncates from its zero time, which was a Monday. Generated with AI Co-Authored-By: AI <ai@example.com>
Workweek takes a DayRule — func(day time.Time) bool — and the hours of an open day, Days takes each weekday's own hours, and CalendarFunc takes the whole per-day question. The library ships the rules (Weekdays, Dates, EveryNth, Between, And, Or, Not) and three wrappers for any calendar (Closed, Special, StartWeek) instead of a builder on one type. The rule is about a day rather than an instant because a predicate over instants cannot give the exact edges a fold needs. Generated with AI Co-Authored-By: AI <ai@example.com>
Calendar is an interface — Location, WeekStart, and Open for one day — so a roster, a term list or a calendar computed in another calendar system is a caller's type or a CalendarFunc. Workweek and Days build the rule-shaped ones from DayRules the package ships (Weekdays, Dates, EveryNth, Between, And, Or, Not), and Closed, Special and StartWeek wrap any calendar. Folds turns the closed time between two instants into a time axis's folds and Opens gives the start of every open stretch. Spans are wall clock, read through time.Date, so an 09:30 open stays at 09:30 across a daylight saving change; an overnight span is split at midnight. An invalid answer from a calendar is ErrCalendar. Generated with AI Co-Authored-By: AI <ai@example.com>
WeekStart sets the weekday a week tick falls on, walked by date rather than truncated from Go's zero time, which is why every weekly axis was Monday until now; Monday stays the default. TimeCalendar gives an axis a calendar's week start and zone. The dialect writes "weekStart". On a folded axis a tick at a day, week, month or year step that lands inside a fold moves to the fold's upper edge and is labelled for where it stands, so an intraday axis with its nights folded has dates. A tick inside a break is still dropped. Generated with AI Co-Authored-By: AI <ai@example.com>
For periods that are not a fixed width — a session, a shift, a day across a clock change. Each row finds its period by binary search, and the Append form is allocation-free into a warm slice. Generated with AI Co-Authored-By: AI <ai@example.com>
examples/market takes its trading days and its folds from one calendar, Monday to Friday with a holiday closed. ADR 0086 is Accepted, amended: spans are wall clock, Dates reads a date as written, a closed day is closed midnight to midnight, Opens merges across midnight, and a tick moves out of a fold but not out of a break. Generated with AI Co-Authored-By: AI <ai@example.com>
One mark reading a position and the four values through one option, geom.OHLC, deciding each row's direction itself (since its open, or since the previous close), and drawing wick and body in four calls whatever the row count. Direction is drawn twice — theme colours that survive a colour-vision deficiency, and hollow or filled, on by default under redundant encoding. The OHLC bar is a style of the same mark. stat.Candle gains End and figure.CandleTable turns candles into the table the mark reads. The dialect gains "candlestick" with open, high, low and close channels rather than spending y and y2 on two of them. Generated with AI Co-Authored-By: AI <ai@example.com>
stat.Candle gains End — the next period's start for OHLC and OHLCAt, and the last row's time for OHLCAt's still-open last candle. figure.CandleTable turns candles into the table geom.Candle reads. Generated with AI Co-Authored-By: AI <ai@example.com>
geom.Candle takes a position and geom.OHLC(open, high, low, close), and draws wick and body as one mark: wicks of a colour one stroked path, bodies one filled path, so a thousand candles are four calls. A row rises when its close is at or above its open, or with Direction(SincePrevious) its previous close; a doji is at least a pixel tall. Rising and Falling set each direction's colour and label, with theme defaults of blue and vermilion; Hollow outlines rising bodies and is on under redundant encoding; CandleStyle(Ticks) is the OHLC bar. Inconsistent data is drawn as given and the row is reported at its close. The dialect writes "candlestick" with open, high, low and close channels. A tick at the lower edge of a fold now moves with ticks inside it, so a day tick at a weekend's Saturday midnight is labelled Monday. Generated with AI Co-Authored-By: AI <ai@example.com>
examples/market replaces its Rect-and-ErrorBar recipe with one Candle layer. ADR 0088 is Accepted, amended; 0086 records the fold-edge tick, 0087 lists Candle under marks never oriented, and 0085's second rank is marked landed. Generated with AI Co-Authored-By: AI <ai@example.com>
scale.FitView makes a value axis fit the rows the panel's pinned X axis shows: render hands each layer that interval as Training.Within in the one training pass, point, interval and connected marks restrict their value training to it, and a layer that cannot fits loosely rather than clipping. Live's wheel, rubber band and pan leave a fitting axis alone. data.Stream.ReplaceLast revises the newest row only, and stat.Resampler turns OHLC inside out for a feed that arrives a tick at a time. Generated with AI Co-Authored-By: AI <ai@example.com>
FitView and LogFitView declare that an axis is trained on the rows its panel's other axis shows. Fitter reports it, and Pinner — an interface of its own, since Zoomer is published — reports whether a domain is pinned, which is when a fit applies. The dialect writes "fit": "view". Generated with AI Co-Authored-By: AI <ai@example.com>
Render passes a pinned X domain as Training.Within to every layer whose Y fits, and resets each fitting axis once per frame first, because a trained domain only widens. Points train on rows in view, marks with width on rows whose span overlaps it, connected paths also on the value where they cross each edge, and stacks on a view padded by their reach. A layer that does not read Within trains on every row, fitting loosely rather than clipping. Live's wheel, rubber band and pan leave a fitting axis alone, and a View does not record one. Generated with AI Co-Authored-By: AI <ai@example.com>
Stream.ReplaceLast overwrites the newest row under Append's lock, the one revision a live source makes. stat.Resampler is OHLC for a feed that arrives a tick at a time: Add returns the candle as it stands and whether it is new, over a width or a calendar's edges, and refuses a tick for a candle that has closed. Generated with AI Co-Authored-By: AI <ai@example.com>
-live folds a seeded tick feed into one-minute candles, revises the open one in place, and draws them on a fitted price axis and a fitted volume track; the reader wheels in at the midpoint and the host keeps the view on the newest candle. ADR 0089 is Accepted, amended: a fitting axis is reset every frame, stacks fit a padded view, and following the data stays the host's one line. Generated with AI Co-Authored-By: AI <ai@example.com>
A probe shows a windowed stream's axes never shrink: a trained domain only widens and render keeps its scales between renders, so the time axis keeps the history the window dropped and examples/stream's sliding window never slid. Every render now forgets each trained range once before its layers train. A pinned domain is kept, so a zoom survives; a set of discovered names — a category's slot, a series' colour — is remembered as identity; continuous colour and size ramps follow the range rule; scale.HighWater opts back into accumulation. Generated with AI Co-Authored-By: AI <ai@example.com>
Retrain drops what a scale was trained on and keeps its pin, options and range — on linear, log, symlog, time and probability axes, the continuous and classed colour scales, both bivariate scales and the size scale. A pinned scale ignores it. Ordinal axes and discrete colour scales do not implement it: their names are identity. HighWater opts a linear axis back into keeping its extent across renders, written "highWater": true. Generated with AI Co-Authored-By: AI <ai@example.com>
Before any layer trains, render retrains each panel's axes and the colour and size scales its layers describe. A trained domain only widens and a plot keeps its scales between renders, so a windowed stream's time axis kept the history the window had dropped and its value axis kept a peak long gone. A zoom is a pinned domain and survives; categories keep their slots and series their colours. This replaces ADR 0089's refit. Generated with AI Co-Authored-By: AI <ai@example.com>
examples/stream builds its chart in a function its new test shares, and the test checks deterministically that a full window's time axis starts where the window does. ADR 0090 is Accepted, amended. Generated with AI Co-Authored-By: AI <ai@example.com>
geom.DirectionBy(from, to) colours each row by whether to is at or above from — or above the previous row's to — through the channel ColorBy feeds, so every mark with a colour column takes it and the two are exclusive. It paints in the candle's Rising and Falling colours and labels, so a volume track agrees with its candles, works on a numeric stream, and the legend's merge by label lists the directions once. The dialect gains direction and directionFrom channels.
A row rises when its to column is at or above its from column, or — from empty — above the previous row's to; a tie rises. The direction is a discrete colour scale of two labels installed on the layer, so bars batch by it, paths colour in stretches, the legend names rising and falling, and a row missing a value is not drawn, with no mark knowing what a direction is. Colours and labels are the candle's Rising and Falling, or the theme's, bound by render's retrain pass. ColorBy and DirectionBy on one layer is ErrColorAndDirection. The dialect writes direction and directionFrom channels.
examples/market drops its hand-computed dir column and its Named scale, and the live chart's volume track is coloured by direction straight from the stream's numeric open and close. ADR 0091 is Accepted, amended.
The last price and a crosshair readout are one mechanism: a value written on its axis, which nothing drew outside a panel before. An AxisTag is furniture a layer (geom.Tagger) or an overlay asks for, drawn in the gutter over the tick labels it covers; geom.LastValue asks for one at the newest row. Crosshair snaps to the row nearest the pointer along X through Index.NearestAlong and tags its position. RSI and VWAP become stat functions because composing them gets Wilder's smoothing and the session reset wrong; MACD stays a recipe.
RSI uses Wilder's averages — seeded with the simple mean of the first window of changes, then moved 1/window of the way — which an EMA composition does not; it is checked against the seed's own arithmetic and StockCharts' published example to its rounding. VWAP resets its sums at every session edge, which a Cumsum composition forgets. MACD stays a recipe: composing EMAs is its definition.
A tick label has the tick step's precision, so a last price of 94.37 on an axis ticked in whole units would read 94, and a time axis could not write an arbitrary instant at all. ValueLabelOf gives a linear value as many decimals as it needs, up to two past the ticks, and a time value its date, with the time of day when it is not a midnight.
An AxisTag is a value written on an axis in its gutter. Layers ask for tags through the optional geom.Tagger; render places them after ranging a panel, leaves out the tick labels they cover and draws them after the data, pushing tags on one axis apart. geom.LastValue draws a rule and a tag at the newest row, coloured by Color or DirectionBy, and trains its axis on that value only. Index.NearestAlong finds the row nearest a position along one axis; Crosshair.Snap uses it to cross at the candle under the pointer's time, and Crosshair.Tags writes its position on the axes through render.DrawAxisTags. The dialect writes lastValue and rule.
examples/market gains an RSI track and a last-price tag; -live tags the open candle's price and draws a crosshair snapping to the candle under a resting pointer into the file it writes. chart-types.md carries the RSI, MACD, VWAP, last-price and crosshair recipes. ADR 0092 is Accepted, amended.
A watchlist is a facet by symbol whose panels each carry the plot's tracks — repeated per panel and subset by the facet like its layers, free where the panel is free — laid out as a block per facet cell with a fixed panel order. ErrTrackWithFacet, which refused a band spanning a facet, is retired; the spanning band stays unbuilt. A comparison is geom.Relative: each series divided by its first value in view, so the zero follows the view, on a percent axis whose NumberFormat gains a + flag.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Market charts, from a catalogue (ADR 0085) to every mechanism it ranked, each recorded before it was built.
Records
What it adds
RSI(Wilder's) andVWAP(per session); trailing windows (TrailingMean,EMA,RollingStdDev,RollingMin/Max,Cumsum),OHLC,OHLCAt,BinWeighted, andResamplerfor a tick feed. All Append forms allocation-free.Calendar(interface) withWorkweek,Days,CalendarFunc,Always, day rules (Weekdays,Dates,EveryNth,Between,And/Or/Not) and wrappers (Closed,Special,StartWeek);Folds,Opens;WeekStart,TimeCalendar;FitView/LogFitView,Pinner,Retrainer,HighWater,ValueLabelOf.AxisTag/Tagger,LastValue;CandlewithOHLC,Direction,Rising,Falling,Hollow,CandleStyle;Orient(Horizontal)forBar,Histogram,Boxplot;DirectionBy;Training.Within.Index.NearestAlong; overlay:Crosshair.Snap,Crosshair.Tags.Stream.ReplaceLast. ir:MarkerTriangleDown. figure:CandleTable.lastValue,rule,candlestick,open/high/low/close,direction/directionFrom,orientationon bar/histogram/boxplot,fit,highWater,weekStart.Behaviour changes worth a look
examples/streamalways claimed), and a repeatedPlot.Renderafter the data changed describes the new data. No golden file changed.WeekStartmakes it a choice.Examples
go run ./examples/market— candles, an RSI track, a last-price tag, Bollinger band, EMA/SMA, buy/sell markers, volume by direction, volume profile, weekends and a holiday folded from one calendar.go run ./examples/market -live— tick feed into minute candles, open candle revised in place, zoomed time axis with fitted price and volume axes, a last-price tag and a snapping crosshair.Also in this branch
The first seven commits are
docs/roadmap-and-v1(ADR 0084 and the roadmap/features/milestones docs), which this branch was started from and which had not been pushed.Checks
go vet ./...andgo test ./...pass;data,statandexamples/streamalso under-race.