ohlcv_chart 2.0.0
ohlcv_chart: ^2.0.0 copied to clipboard
Candlestick and market-depth charts for Flutter, with MA, BOLL and SAR overlays, MACD, KDJ, RSI, WR and CCI sub-charts, signal markers and interactive drawing tools.
ohlcv_chart #
A candlestick (K-line) and market-depth chart for Flutter, drawn entirely with
CustomPainter — no WebView, no JavaScript bridge.
Named for the open-high-low-close-volume bars it renders.

Features #
- Five chart types — candles, OHLC bars, a line, a filled area and a baseline chart — plus Heikin-Ashi and Renko as transforms of the candles themselves.
- 26 indicators, each a configured instance rather than a flag — so
ATR(8),ATR(14)andATR(20)are three panes, with their own settings and colours. - Main-chart overlays —
MA,EMA,BOLL,SAR,VWAP, Supertrend, Keltner and Donchian channels, and the Ichimoku Cloud. - Swing readers — a zigzag through the swing highs and lows, Fibonacci retracement of the last swing, and Elliott wave labels, all sized to the market by default.
- Sub-charts —
MACD,KDJ,RSI,WR,CCI,ATR,OBV,MFI,DMI, Stochastic RSI,ROC,TRIX, a volume average and the Awesome oscillator, each in its own stacked pane — resizable and reorderable by dragging. - Linear, logarithmic or percentage price axis, so a decade of compounding reads as well as an afternoon.
- 17 drawing tools — levels and rays, trend lines, arrows, extended lines, boxes, ellipses, triangles, Fibonacci retracements, a measuring tool, parallel channels, planned positions with their risk-to-reward, pinned notes and freehand strokes; each placed by tap or drag, then dragged, locked, hidden and removed.
- A line editor that opens on selection: colour, opacity, thickness, solid/dashed/dotted stroke, fill, label text and visibility, alerts, lock and delete — every option list, control and pixel of it configurable through
DrawingStyle. - Undo and redo, through a
ChartDrawingControllerthat owns the drawings and their history, with ⌘Z, ⇧⌘Z and Delete on the chart itself. - A layout that persists — every drawing serialises, so
jsonEncode(drawings.toJson())andChartDrawings.fromJsonare the whole story. - A drawing manager — a ready-made panel listing what is drawn, with show/hide, lock, delete, undo, redo and clear.
- Price alerts on a level, reported when the market crosses it.
- Crosshair on hover and an OHLC legend above the chart, which is how a chart reads on a desktop.
- Driven from your own code —
KChartControllerzooms, scrolls back to the live candle and hands you the chart as a PNG. - Buy/sell signal markers pinned to candles.
- Depth chart — a separate
DepthChartwidget for the order book, drawn as the cumulative curve, a per-rung histogram, both at once, or a numeric ladder of price, size and running total, on a linear, log or percentage axis and zoomable to the levels around the mid. - Info dialog on long press, either the built-in Material popup or your own builder.
- "Now price" line with a live countdown to the close of the current candle.
- Session dividers and a display time zone, so an intraday chart breaks where the trader's day does.
- SVG watermark loaded from an asset path you supply.
- Fully themeable —
ChartStylefor geometry,ChartColorsfor every colour,DrawingStylefor the drawing tools;ChartTranslationsfor every label. Filled or hollow candles, dashed or solid crosshair, pane separators, axis-label pills and a placed, tinted watermark. - Fits its box — the candles take whatever height the volume and indicator panes leave, so the chart works from a phone to a desktop window without arithmetic on your side.
Contents #
- Candlestick chart · Indicators
- Chart types · Price axis · The legend and the crosshair
- Drawing tools — what can be drawn, persisting a layout, undo and redo, the drawing manager, level alerts
- Customising the line editor · The long-press readout
- Driving the chart · Panes · Sessions and time zones
- Depth chart · Theming · Sizing
- Migrating from 1.x
Install #
dependencies:
ohlcv_chart: ^2.0.0
Usage #
Candlestick chart #
Feed it a List<KLineEntity>. Indicator values are computed in place by
DataUtil.calculate before the first paint, and again whenever new candles arrive:
import 'package:ohlcv_chart/ohlcv_chart.dart';
DataUtil.calculate(candles);
KChartWidget(
candles,
ChartColors(),
isTrendLine: false,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
indicators: [MaIndicator(period: 20), BollIndicator(), MacdIndicator()],
timeFormat: TimeFormat.YEAR_MONTH_DAY_WITH_HOUR,
fixedLength: 2,
onLoadMore: (isRight) {
if (!isRight) fetchOlderCandles();
},
);
KLineEntity.fromJson accepts the usual OHLCV shape (open, high, low,
close, vol, time/id), or build the entity directly.
Indicators #
Indicators are instances, not flags: pass as many as you like to indicators,
including several of the same kind with different settings. Overlays draw over
the candles; everything else takes a pane of its own, stacked in the order given.
KChartWidget(
candles,
ChartColors(),
isTrendLine: false,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
indicators: [
MaIndicator(period: 7),
MaIndicator(period: 25),
MaIndicator(period: 99, color: Colors.amber),
AtrIndicator(period: 8),
AtrIndicator(period: 14),
AtrIndicator(period: 20),
],
);

| Overlay | Settings |
|---|---|
MaIndicator |
period — the simple moving average |
EmaIndicator |
period |
BollIndicator |
period, deviations |
SarIndicator |
start, step, maximum |
VwapIndicator |
— |
SupertrendIndicator |
period, multiplier; flips colour with the trend |
KeltnerIndicator |
period, atrPeriod, multiplier |
DonchianIndicator |
period |
IchimokuIndicator |
conversionPeriod, basePeriod, spanPeriod, displacement; the cloud is shaded |
ZigZagIndicator |
depth — the swing size, in percent |
FibonacciIndicator |
depth, ratios; retraces the last swing |
ElliottWaveIndicator |
depth; labels the swings 1–5, A–C |
| Pane | Settings |
|---|---|
MacdIndicator |
fast, slow, signal |
KdjIndicator |
period, kSmoothing, dSmoothing |
RsiIndicator |
period, with 30/50/70 guides |
WrIndicator |
period |
CciIndicator |
period |
AtrIndicator |
period |
ObvIndicator |
— |
MfiIndicator |
period, with 20/80 guides |
DmiIndicator |
period, with the 20 guide |
StochRsiIndicator |
rsiPeriod, period, kSmoothing, dSmoothing, with 20/80 guides |
RocIndicator |
period |
TrixIndicator |
period, signalPeriod |
VolumeMaIndicator |
period |
AwesomeIndicator |
fast, slow |

Three of the overlays read the market's swings rather than a fixed window, all
from the same depth — the percentage move that ends a swing:
ZigZagIndicator draws the legs, FibonacciIndicator retraces the last one,
and ElliottWaveIndicator counts them. Left at its default of 0, depth
is sized from the candles — a fixed percentage that gives several swings on a
volatile daily chart finds none at all on a quiet intraday one — and the label
reads auto. Pass a positive number to set the threshold yourself. The wave count is a reading of the
swings, not a rules-checked Elliott count; treat it as a sketch to confirm by
eye.
IchimokuIndicator shifts its spans forward and its lagging line back as the
indicator is drawn. The chart holds one value per candle, so the stretch of
cloud that would project past the newest candle is not drawn.
Custom indicators
Subclass Indicator — give it a label, its lines, the settings that make
it distinct and a compute — and the chart scales, draws, legends and labels it
like a built-in one. Lines are drawn as a stroke, dots, a histogram, a
pivotLine (straight across the candles with no value, which is what a zigzag
needs) or markers (a dot with your own text beside it). fills shades the area
between two lines, and colorForPoint colours a single point — how the
Supertrend changes colour at a reversal.

The same kind of indicator, three times over, is the point of the list: each instance carries its own settings and colours.
Colours
Every indicator takes its colours from ChartColors, and a color (or
colors, for the multi-line ones) argument overrides that per instance:
indicators: [
MaIndicator(period: 20, color: Colors.amber),
MacdIndicator(colors: [Colors.grey, Colors.blue, Colors.orange]),
],
Repeated indicators of one kind take successive theme colours, so three moving averages are three different colours without being told.
Adding and editing at runtime
An indicator is identified by its type and settings — colours are deliberately
left out — so upsert restyles the one already on the chart instead of stacking
a duplicate:
final indicators = <Indicator>[MaIndicator(period: 20)];
indicators.upsert(AtrIndicator(period: 14)); // added
indicators.upsert(AtrIndicator(period: 14, color: Colors.red)); // recoloured
indicators.upsert(AtrIndicator(period: 20)); // a second pane
indicators.toggle(RsiIndicator()); // on, then off
Building a settings UI

indicatorCatalog describes every indicator — its settings, their ranges and
its colour slots — so an "add indicator" sheet can be driven by data rather than
a hard-coded list. The example app's IndicatorSheet is built entirely from it:
final type = indicatorCatalog.firstWhere((t) => t.name == 'ATR');
for (final setting in type.settings) {
print('${setting.label}: ${setting.defaultValue} '
'(${setting.min}–${setting.max})');
}
// What the colour pickers should show, per line.
final labels = type.lineLabels(); // ['ATR']
final defaults = type.defaultColors(ChartColors()); // theme colours
// Build one from the user's choices.
final indicator = type.create(
values: {'period': 8},
colors: [Colors.purple],
);
// And read an existing one back into the form.
final values = indicatorTypeOf(indicator)?.valuesOf(indicator); // {'period': 8}
DataUtil.calculate still fills the indicator fields on each KLineEntity,
which the long-press readout uses; the indicators themselves compute their own
values from the candles.
Chart types #
chartType decides what the candle area draws:
KChartWidget(
candles,
ChartColors(),
isTrendLine: false,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
chartType: ChartType.bars, // candles, bars, line, area, baseline
baselinePrice: 42_000, // baseline only; defaults to the oldest close in view
);
ChartType |
Draws |
|---|---|
candles |
a filled or hollow body with a wick — the default |
bars |
the high-low range, open ticked left and close ticked right |
line |
a line through the closes |
area |
the same line with the area beneath it washed in |
baseline |
the line washed towards a level, up-coloured above it and down-coloured below |
isLine: true still means ChartType.area, so nothing written against the older
API changes behaviour.
Heikin-Ashi and Renko
Both rewrite the candles rather than the way they are drawn, so they are
transforms rather than chart types. Run the list through CandleTransforms and
recompute the indicators over the result:
final ha = CandleTransforms.heikinAshi(candles);
DataUtil.calculate(ha);
final bricks = CandleTransforms.renko(
candles,
brickSize: CandleTransforms.atrBrickSize(candles) ?? 25,
);
DataUtil.calculate(bricks);
KChartWidget(ha, ChartColors(), /* … */);
Heikin-Ashi keeps one candle per candle, at the same times, so anything drawn on
the chart stays where it was. Renko throws time away between bricks: a brick is
laid every whole brickSize beyond the last, a reversal costs two bricks, and
each brick carries the volume of the candles it covers. atrBrickSize sizes a
brick from the market's own average true range, which is the usual way to pick
one.
Price axis #
KChartWidget(
candles,
ChartColors(),
isTrendLine: false,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(days: 1),
priceAxisScale: PriceAxisScale.logarithmic,
);
linear— equal prices take equal space. The default.logarithmic— equal ratios take equal space, so 10 → 20 covers as much of the axis as 100 → 200. A window whose low is zero or negative has no logarithm to space by and falls back to linear until it scrolls back into positive prices.percentage— spaced linearly, but the axis, the crosshair's price label and the current-price tag read as the move away from the oldest candle in view.
The volume and indicator panes always stay linear.
The legend and the crosshair #
With a mouse, the crosshair follows the pointer without waiting for a press —
that is crosshairOnHover, on by default and irrelevant to a touch screen, which
has nothing that hovers. The values then belong above the chart rather than in a
popup, which is what showOhlcLegend draws: date, open, high, low, close, the
move over the candle and its volume, on a legend row of its own above the
indicator legends, worded by ChartTranslations.
KChartWidget(
candles,
ChartColors(),
isTrendLine: false,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
showOhlcLegend: true,
crosshairOnHover: true,
);
The long-press readout is unchanged, and still opens on a press or — with
isTapShowInfoDialog — a tap.
Drawing tools #

Selecting a drawn line opens the editor over the chart: colour, thickness, stroke style, label text and visibility, lock and delete.

Set currentDrawingTool to put the chart into placement mode and handle the
callbacks to persist what the user draws. The per-kind lists below are the
original API and still work; a drawingController — see
Undo, redo and the drawing controller —
owns the whole layout for you instead, and drawings: takes drawings of any
kind, which is where the later ones live:
KChartWidget(
candles,
ChartColors(),
isTrendLine: true,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
currentDrawingTool: DrawingTool.trend,
trendLines: savedTrendLines,
horizontalLines: savedHorizontalLines,
verticalLines: savedVerticalLines,
rectangles: savedRectangles,
fibRetracements: savedFibRetracements,
drawings: savedMeasuresChannelsAndNotes,
onAddTrendLine: repository.save,
onRemoveTrendLine: repository.delete,
// Fires for every kind, and is the only report for the later ones.
onAddDrawing: repository.save,
onRemoveDrawing: repository.delete,
);
Placement works the way a charting desk expects. A horizontal or vertical line lands with a single tap. A trend line takes one tap per end: tap its start, move, and tap again to finish — the line rubber-bands along with the pointer in between. Pressing and dragging from one end to the other still draws a line in one gesture, on a touch screen as much as with a mouse. Escape abandons a line that is half-placed, as does tapping outside the chart or switching tools, and the line editor stays out of the way until the line is finished.
With magnetMode: true, each point placed snaps to the nearest open, high, low
or close within DrawingStyle.magnetSnapDistance pixels, and lands wherever the
pointer is when nothing is that close.
What can be drawn

DrawingTool |
Taps | Draws |
|---|---|---|
horizontal |
1 | a level across the whole chart |
horizontalRay |
1 | a level that only applies from that candle rightwards |
vertical |
1 | a line marking one candle |
text |
1 | a note pinned to a point, ready to be typed into |
trend |
2 | a segment between two anchors |
ray |
2 | a segment that carries on past its second anchor |
extendedLine |
2 | a segment that carries on past both anchors |
arrow |
2 | a segment with an arrowhead on its far end |
rectangle |
2 | a box between two opposite corners |
ellipse |
2 | an ellipse inscribed in that box |
fibRetracement |
2 | the levels of a swing, labelled and banded |
measure |
2 | a ruler: the move in price, in percent, in candles and in time |
triangle |
3 | a triangle over three corners |
channel |
3 | a base line and a parallel through the third point |
position |
3 | entry, target and stop, with the risk-to-reward worked out |
brush |
drag | a freehand stroke |
The three trend variants are all TrendLines: extend (LineExtension.none,
.right, .both) decides how far past its anchors the line runs, and arrow
puts a head on the far end, so a ray persisted by an older version of the app
still loads. HorizontalLine.startTime is what makes a level a ray. Rectangles
and retracements are their own types, RectangleDrawing and FibRetracement,
passed in rectangles and fibRetracements and reported through
onAddRectangle / onRemoveRectangle and onAddFibRetracement /
onRemoveFibRetracement.
Every two-point drawing shares one base, TwoPointDrawing: two (time, price)
anchors, either of which can be dragged, plus isComplete — false while the
second point is still following the pointer.
RectangleDrawing(
time1: candles[20].dateTime!, price1: candles[20].high,
time2: candles[30].dateTime!, price2: candles[30].low,
fillOpacity: 0.12,
);
FibRetracement(
time1: swingLow.dateTime!, price1: swingLow.low,
time2: swingHigh.dateTime!, price2: swingHigh.high,
levels: [0, 0.382, 0.5, 0.618, 1], // defaults to the usual seven
);
A retracement's levels run from the drawing rightwards to the edge of the chart,
since what a level is worth is what price does after the move. DrawingStyle
carries the rest of the geometry: arrowHeadLength, rectangleFillOpacity,
shapeFillOpacity, measureFillOpacity, channelFillOpacity,
positionFillOpacity, and fibLevels and fibFillOpacity for new retracements
and their bands.
The three-point shapes — TriangleDrawing, ParallelChannel, PositionDrawing
— share ThreePointDrawing, which adds time3 and price3 and is not complete
until the third point lands. A channel's parallel runs through that point; a
position takes its entry from the first, its target from the second and its stop
from the third, and works out reward, risk and riskReward for the label:
final plan = PositionDrawing(
time1: entry.dateTime!, price1: entry.close,
time2: candles.last.dateTime!, price2: entry.close * 1.06,
time3: candles.last.dateTime!, price3: entry.close * 0.98,
);
plan.isLong; // true — the target is above the entry
plan.riskReward; // 3.0
A measurement reads itself out the same way: priceMove, ratio and span, plus
the candle count the chart works out for the label. A note is a TextAnnotation
whose text the editor's label field types into, and a freehand stroke is a
FreehandDrawing — a list of (time, price) points, so it stays on the candles it
was drawn over.
keepToolArmed is your own state — the chart never changes currentDrawingTool
— but selectAfterDrawing: false stops the editor opening over each drawing as
it lands, which is what makes drawing five levels in a row bearable.
Tapping an existing line selects it and opens the editing toolbar. Dragging an
anchor of a two-point drawing moves that anchor; dragging the shape by its
stroke, its outline or one of its levels moves the whole thing, both anchors
together. Every edit — a new colour, a new thickness, a drag, a renamed
label — fires the matching onAdd* callback with the updated line, so
persisting a change is the same code path as persisting a new one.
Persisting a layout
Every drawing serialises, and drawingFromJson turns a map back into the drawing
it came from:
final drawings = ChartDrawings([
HorizontalLine(price: 42_000, title: 'entry'),
TrendLine(time1: a, price1: 1, time2: b, price2: 2),
]);
await prefs.setString('layout', jsonEncode(drawings.toJson()));
final saved = prefs.getString('layout');
final restored = saved == null
? ChartDrawings()
: ChartDrawings.fromJson(jsonDecode(saved) as Map<String, dynamic>);
ChartDrawings is an ordered set of drawings with typed views —
horizontalLines, trendLines, positions and the rest — so the chart can be
handed the whole layout at once through drawings:. A drawing of a kind this
version does not know is skipped rather than throwing, so a layout written by a
newer release still opens. copyDrawing deep-copies one, by round-tripping it
through its own JSON.
Undo, redo and the drawing controller
Hand the chart a ChartDrawingController and it owns the drawings: what the user
places, restyles, drags or deletes goes through the controller, which is what
makes undo possible.
final drawings = ChartDrawingController();
KChartWidget(
candles,
ChartColors(),
isTrendLine: true,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
drawingController: drawings,
);
drawings.undo(); // ⌘Z on the chart does the same
drawings.redo(); // ⇧⌘Z, or Ctrl+Y
drawings.clear(); // one undoable step
drawings.select(line); // opens the chart's editor on that drawing
jsonEncode(drawings.toJson());
An edit is a step, so restyling a line and undoing gets the old style back — the
controller keeps a deep copy of the last committed state, because the chart edits
a drawing in place and only reports it once the edit lands. historyLimit
(50 by default) is how far back it goes; clearHistory keeps the drawings and
drops the steps, which is what a fresh symbol wants.
Delete removes the selected drawing, and Escape abandons one being placed. All of
it can be turned off with enableKeyboardShortcuts: false. Without a controller
the per-kind lists and callbacks work exactly as before — there is simply no undo.
The drawing manager
DrawingManager is a plain widget over the same controller: every drawing by
name, with show/hide, lock, delete, undo, redo and clear.
Row(
children: [
Expanded(child: KChartWidget(candles, colors, drawingController: drawings, /* … */)),
SizedBox(width: 260, child: DrawingManager(controller: drawings)),
],
);
Tapping a row selects that drawing on the chart, and the chart's own selection
highlights the row. Hiding one leaves it in the layout but off the chart —
ChartLine.hidden — and every string it shows, including what each kind is
called, comes from DrawingTranslations.
Level alerts
A HorizontalLine with alert: true reports through onAlertCrossed whenever
the newest candle closes on the other side of it:
KChartWidget(
candles,
ChartColors(),
isTrendLine: true,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
drawings: [HorizontalLine(price: 42_000, alert: true)],
onAlertCrossed: (line, candle) => notifier.push('crossed ${line.price}'),
);
It fires once per crossing — the market has to come back through the level before it fires again — and the editor's bell button is what arms one from the chart.
Customising the line editor #
DrawingStyle decides what the toolbar offers, how it looks, and how close a tap
has to land to count. Everything is optional:
KChartWidget(
candles,
ChartColors(),
isTrendLine: true,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
drawingStyle: const DrawingStyle(
// What the user may pick
colorOptions: [Color(0xFF4DABF7), Color(0xFF12B886), Color(0xFFFA5252)],
thicknessOptions: [1, 2, 3, 5],
lineStyleOptions: [LineStyle.solid, LineStyle.dashed],
minThickness: 0.5,
maxThickness: 8,
// Which controls appear
showOpacityControl: false,
showLabelTextControl: true,
showFillControl: true, // shapes with an interior
showAlertControl: true, // levels only
// How the bar looks
toolbarAxis: Axis.horizontal,
toolbarInitialOffset: Offset(16, 40),
accentColor: Color(0xFF4DABF7),
iconSize: 20,
// How the lines themselves are painted
handleRadius: 7,
dashLength: 6,
dashGap: 4,
hitTestTolerance: 22,
// What a newly drawn shape looks like
rectangleFillOpacity: 0.12,
shapeFillOpacity: 0.12,
measureFillOpacity: 0.14,
channelFillOpacity: 0.08,
positionFillOpacity: 0.16,
),
);
A control that a drawing has no use for is left out whatever these say: the fill slider only appears on a shape with an interior, the alert bell only on a level, and the label field only on a drawing that can carry one.
A line's own appearance lives on the line, so you can style one before it ever reaches the chart:
HorizontalLine(
price: 68400,
title: 'take profit',
color: const Color(0xFF12B886),
thickness: 2,
style: LineStyle.dashed,
showLabel: true,
locked: true,
);
ChartLine.opacity reads and writes the alpha of color, and isDashed still
works for code written before LineStyle.
The long-press readout #

Holding the chart puts a crosshair under the finger with the price and time on
the axes, and a card of that candle's open, high, low, close, change and volume.
isTapShowInfoDialog opens it on a tap as well, infoDialogBuilder replaces the
card, and ChartTranslations names every row.
Driving the chart #
KChartController reaches into the chart from your own code: how far it is
zoomed, where it is scrolled, and what it looks like as an image.
final chart = KChartController();
KChartWidget(
candles,
ChartColors(),
isTrendLine: false,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
controller: chart,
);
chart.zoomIn();
chart.scrollToNow(); // animates back to the live candle
chart.isAtRightEdge; // whether it is already there
final png = await chart.capture(); // the chart as PNG bytes
capture returns the chart itself — candles, indicators, drawings — without the
line editor or any other control floating over it. Everything no-ops while no
chart is attached, so a controller built before its widget, or kept after it, is
harmless. The chart also shows its own button back to the live candle whenever it
is scrolled away from one; showScrollToNowButton: false turns that off, and its
tooltip comes from ChartTranslations.jumpToNow.
Panes #
An indicator pane can be made taller by dragging its lower edge, and moved up or down the stack by dragging its legend row:
KChartWidget(
candles,
ChartColors(),
isTrendLine: false,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
indicators: [MacdIndicator(), RsiIndicator()],
resizablePanes: true,
reorderablePanes: true,
onReorderPane: (from, to) => setState(() {
indicators.insert(to, indicators.removeAt(from));
}),
);
Heights live in the chart, between ChartStyle.minPaneHeight and
maxPaneHeight, and are given up whenever the panes themselves change. The order
does not: the indicators own that, so the chart reports where a pane was dropped
and leaves the move to you. ChartStyle.paneResizeTolerance and paneGrabHeight
decide how big each target is.
Sessions and time zones #
KChartWidget(
candles,
ChartColors(),
isTrendLine: false,
watermarkAssetPath: 'assets/logo.svg',
timeFrame: const Duration(minutes: 15),
chartStyle: const ChartStyle(showSessionDividers: true),
timeZoneOffset: const Duration(hours: -5),
);
timeZoneOffset is added to every candle's time before it is shown — on the axis,
in the crosshair, in the legend and when working out where a day starts. It
changes what is displayed and never the data, so drawings stay anchored to the
candles they were placed on. showSessionDividers then marks the first candle of
each day, in ChartColors.sessionDividerColor.
Depth chart #

DepthChart(
DepthEntity.bids(rawBids),
DepthEntity.asks(rawAsks),
baseUnit: 2,
quoteUnit: 6,
);
The chart plots each rung's vol as given, so it must be a running total.
DepthEntity.bids and DepthEntity.asks sort raw order-book rungs by price and
accumulate them in the right direction — from the best bid downwards and the best
ask upwards — which is what makes the two curves meet at the mid price.
Modes

mode chooses what the book looks like, and all four read the same data:
DepthChartMode |
What it draws |
|---|---|
cumulative |
The running total either side of the mid — the default, and the shape that shows how hard the book is to move through |
histogram |
One bar per level, each the size resting on that rung, so the individual walls stand out |
combined |
The curves with those bars behind them |
ladder |
The numbers: price, size and running total per row, with a bar behind each |
DepthChart(
DepthEntity.bids(rawBids),
DepthEntity.asks(rawAsks),
mode: DepthChartMode.combined,
scale: DepthScale.log,
zoom: 0.05, // only the book within 5% of the mid
);
scale spaces the volume axis — linear, log for a book whose far side dwarfs
the near one, or percent to label it as a share of the deepest total — and
zoom narrows the chart to the levels near the mid, where the trading is. A zoom
so tight that nothing would be left falls back to the whole book rather than to
an empty chart.
Each rung's own size is recovered from the cumulative curves by differencing, so
nothing extra has to be passed in. DepthBook.fromCurves does that on its own if
you want the levels for something else:
final book = DepthBook.fromCurves(bids, asks, zoom: 0.05);
for (final level in book.bids) {
print('${level.price}: ${level.size} resting of ${level.cumulative}');
}
The ladder is also a widget in its own right, for putting the numbers beside a chart rather than instead of it:
DepthLadder(bids, asks, levels: 12, barsShowTotal: false);
The long-press readout names the size resting on the rung under the finger as well as the running total out to it.
Theming #
KChartWidget(
candles,
ChartColors(
upColor: const Color(0xFF12B886),
dnColor: const Color(0xFFFA5252),
bgColor: const Color(0xFF0E1116),
),
chartStyle: const ChartStyle(),
chartTranslations: const ChartTranslations(),
// …
);
ChartTranslations carries every on-chart label (date, open, high, low,
close, changeAmount, change, amount, vol, jumpToNow), so localising
the chart is a matter of building one from your own AppLocalizations. Its
drawing field does the same for the line editor, the drawing manager and what
each of the seventeen kinds is called:
ChartTranslations(
date: l10n.date,
drawing: DrawingTranslations(
color: l10n.colour,
delete: l10n.delete,
fill: l10n.fill,
alert: l10n.setAlert,
drawings: l10n.drawings,
trendLineName: l10n.trendLine,
),
);
DrawingTranslations.nameOf is what turns a drawing into the name the manager
shows, so a kind you have renamed reads the same everywhere.
ChartColors gained sessionDividerColor for the day dividers, and ChartStyle
now has a copyWith, so a house geometry can be varied a switch at a time:
final style = ChartTheme.filled.copyWith(showSessionDividers: true);
The long-press readout sizes itself to its content between infoDialogWidth and
infoDialogMaxWidth, and is never wider than the chart. Set
isTapShowInfoDialog to open it on a plain tap as well.
Sizing #
mBaseHeight is the candle area alone; the volume pane (60px) and each indicator
pane (100px, until one is dragged) are stacked underneath. Left unset it is derived from the widget's
box, so the whole stack fits — put the chart in an Expanded and it fills the
space. Pass a number to pin the candle area instead, for instance inside a scroll
view where there is no height to divide up.
Migrating from 1.x #
Nothing was taken off KChartWidget: the per-kind drawing lists, their onAdd*
and onRemove* callbacks and isLine all still work, so most apps upgrade by
changing the version and nothing else. Three things to know:
-
Two defaults changed what an existing chart shows.
crosshairOnHoverandshowScrollToNowButtonare both on. The first only ever fires for a pointer that hovers, so a touch app never sees it; the second draws a small button over the bottom right corner whenever the chart is scrolled away from the newest candle. Set either tofalseto keep the old behaviour. -
A custom
ChartLinenow has to serialise.toJsonis part of the base class, since that is what lets a layout be saved and a drawing be copied for the undo history. Build yours onbaseJson, and register afromJsonof your own where you decode:class MyDrawing extends TwoPointDrawing { @override Map<String, dynamic> toJson() => { ...baseJson('myDrawing'), ...anchorsJson(), }; }Adopting
LabelledDrawingorFilledDrawingis what gets your drawing the editor's label field or its fill slider. -
The painters moved on, if you imported them from
src/rather than through the public API:ChartPainternow takes onedrawingslist rather than a list per kind, and reports the crosshair's candle through anemitInfoWindowcallback rather than aStreamSink.MainRenderer.getValueis now the exact inverse ofgetY, which also corrects a price read a few pixels out.
ChartLine.hidden is new and defaults to false, so nothing disappears; the
drawing manager is what turns it on.
Notes #
- The zoom slider renders only where there is no pinch gesture — web and desktop.
watermarkAssetPathmust point at an SVG registered in your app'spubspec.yamlassets; a missing asset is ignored and the chart renders without a watermark.
Credits #
The rendering core began as a derivative of the open-source k_chart package and
has since been substantially extended with drawing tools, signals, multi-indicator
stacking and a reworked painter pipeline.
License #
MIT — see LICENSE.