LineBreakChart
A three-line break chart from closing prices: lines go on while the close makes new ends, and turn only past the last three lines.
Demo
| Line | Direction | From | To | Formed |
|---|---|---|---|---|
| 1 | Up | $64.14 | $64.93 | Apr 2, 2026 |
| 2 | Up | $64.93 | $65.72 | Apr 3, 2026 |
| 3 | Up | $65.72 | $66.16 | Apr 6, 2026 |
| 4 | Down | $65.72 | $63.80 | Apr 14, 2026 |
| 5 | Down | $63.80 | $62.91 | Apr 15, 2026 |
| 6 | Down | $62.91 | $62.25 | Apr 16, 2026 |
| 7 | Down | $62.25 | $61.38 | Apr 20, 2026 |
| 8 | Down | $61.38 | $60.98 | Apr 21, 2026 |
| 9 | Up | $61.38 | $63.06 | Apr 24, 2026 |
| 10 | Up | $63.06 | $63.77 | Apr 27, 2026 |
| 11 | Up | $63.77 | $64.38 | Apr 28, 2026 |
| 12 | Up | $64.38 | $65.18 | Apr 29, 2026 |
| 13 | Down | $64.38 | $62.71 | May 5, 2026 |
| 14 | Down | $62.71 | $62.48 | May 11, 2026 |
| 15 | Down | $62.48 | $61.79 | May 15, 2026 |
| 16 | Down | $61.79 | $60.83 | May 18, 2026 |
| 17 | Up | $61.79 | $63.50 | May 22, 2026 |
| 18 | Up | $63.50 | $63.96 | May 25, 2026 |
| 19 | Up | $63.96 | $64.54 | Jun 4, 2026 |
| 20 | Up | $64.54 | $65.10 | Jun 12, 2026 |
| 21 | Down | $64.54 | $63.08 | Jun 16, 2026 |
| 22 | Up | $64.54 | $65.69 | Jul 1, 2026 |
| 23 | Up | $65.69 | $66.07 | Jul 7, 2026 |
| 24 | Up | $66.07 | $66.39 | Jul 8, 2026 |
| 25 | Up | $66.39 | $67.10 | Jul 9, 2026 |
| 26 | Down | $66.39 | $65.41 | Jul 16, 2026 |
| 27 | Down | $65.41 | $65.10 | Jul 17, 2026 |
| 28 | Down | $65.10 | $64.57 | Jul 20, 2026 |
| 29 | Down | $64.57 | $64.22 | Jul 21, 2026 |
| 30 | Up | $64.57 | $66.06 | Jul 24, 2026 |
| 31 | Down | $64.57 | $63.87 | Jul 31, 2026 |
| 32 | Down | $63.87 | $63.04 | Aug 3, 2026 |
| 33 | Down | $63.04 | $62.99 | Aug 4, 2026 |
| 34 | Down | $62.99 | $62.81 | Aug 5, 2026 |
| 35 | Down | $62.81 | $62.07 | Aug 7, 2026 |
| 36 | Down | $62.07 | $61.06 | Aug 10, 2026 |
| 37 | Down | $61.06 | $60.74 | Aug 11, 2026 |
| 38 | Down | $60.74 | $60.68 | Aug 18, 2026 |
| 39 | Down | $60.68 | $59.85 | Aug 19, 2026 |
| 40 | Down | $59.85 | $59.00 | Aug 20, 2026 |
| 41 | Down | $59.00 | $58.03 | Aug 21, 2026 |
| 42 | Down | $58.03 | $57.55 | Aug 24, 2026 |
| 43 | Down | $57.55 | $57.12 | Sep 11, 2026 |
| 44 | Up | $57.55 | $59.23 | Sep 17, 2026 |
| 45 | Up | $59.23 | $59.59 | Sep 21, 2026 |
| 46 | Up | $59.59 | $59.71 | Sep 22, 2026 |
| 47 | Down | $59.59 | $57.51 | Sep 28, 2026 |
| 48 | Down | $57.51 | $57.12 | Sep 29, 2026 |
| 49 | Up | $57.51 | $59.77 | Oct 2, 2026 |
| 50 | Up | $59.77 | $60.37 | Oct 5, 2026 |
| Line | Direction | From | To | Formed |
|---|---|---|---|---|
| 1 | Up | $64.14 | $64.93 | Apr 2, 2026 |
| 2 | Up | $64.93 | $65.72 | Apr 3, 2026 |
| 3 | Up | $65.72 | $66.16 | Apr 6, 2026 |
| 4 | Down | $65.72 | $64.47 | Apr 10, 2026 |
| 5 | Down | $64.47 | $63.80 | Apr 14, 2026 |
| 6 | Down | $63.80 | $62.91 | Apr 15, 2026 |
| 7 | Down | $62.91 | $62.25 | Apr 16, 2026 |
| 8 | Down | $62.25 | $61.38 | Apr 20, 2026 |
| 9 | Down | $61.38 | $60.98 | Apr 21, 2026 |
| 10 | Up | $61.38 | $63.06 | Apr 24, 2026 |
| 11 | Up | $63.06 | $63.77 | Apr 27, 2026 |
| 12 | Up | $63.77 | $64.38 | Apr 28, 2026 |
| 13 | Up | $64.38 | $65.18 | Apr 29, 2026 |
| 14 | Down | $64.38 | $63.21 | May 4, 2026 |
| 15 | Down | $63.21 | $62.71 | May 5, 2026 |
| 16 | Down | $62.71 | $62.48 | May 11, 2026 |
| 17 | Down | $62.48 | $61.79 | May 15, 2026 |
| 18 | Down | $61.79 | $60.83 | May 18, 2026 |
| 19 | Up | $61.79 | $62.71 | May 21, 2026 |
| 20 | Up | $62.71 | $63.50 | May 22, 2026 |
| 21 | Up | $63.50 | $63.96 | May 25, 2026 |
| 22 | Down | $63.50 | $62.67 | May 27, 2026 |
| 23 | Down | $62.67 | $62.07 | May 28, 2026 |
| 24 | Down | $62.07 | $62.06 | Jun 1, 2026 |
| 25 | Up | $62.07 | $63.25 | Jun 2, 2026 |
| 26 | Up | $63.25 | $63.61 | Jun 3, 2026 |
| 27 | Up | $63.61 | $64.54 | Jun 4, 2026 |
| 28 | Up | $64.54 | $65.10 | Jun 12, 2026 |
| 29 | Down | $64.54 | $63.08 | Jun 16, 2026 |
| 30 | Up | $64.54 | $65.69 | Jul 1, 2026 |
| 31 | Up | $65.69 | $66.07 | Jul 7, 2026 |
| 32 | Up | $66.07 | $66.39 | Jul 8, 2026 |
| 33 | Up | $66.39 | $67.10 | Jul 9, 2026 |
| 34 | Down | $66.39 | $65.73 | Jul 13, 2026 |
| 35 | Down | $65.73 | $65.41 | Jul 16, 2026 |
| 36 | Down | $65.41 | $65.10 | Jul 17, 2026 |
| 37 | Down | $65.10 | $64.57 | Jul 20, 2026 |
| 38 | Down | $64.57 | $64.22 | Jul 21, 2026 |
| 39 | Up | $64.57 | $65.11 | Jul 22, 2026 |
| 40 | Up | $65.11 | $66.06 | Jul 24, 2026 |
| 41 | Down | $65.11 | $64.54 | Jul 30, 2026 |
| 42 | Down | $64.54 | $63.87 | Jul 31, 2026 |
| 43 | Down | $63.87 | $63.04 | Aug 3, 2026 |
| 44 | Down | $63.04 | $62.99 | Aug 4, 2026 |
| 45 | Down | $62.99 | $62.81 | Aug 5, 2026 |
| 46 | Down | $62.81 | $62.07 | Aug 7, 2026 |
| 47 | Down | $62.07 | $61.06 | Aug 10, 2026 |
| 48 | Down | $61.06 | $60.74 | Aug 11, 2026 |
| 49 | Down | $60.74 | $60.68 | Aug 18, 2026 |
| 50 | Down | $60.68 | $59.85 | Aug 19, 2026 |
| 51 | Down | $59.85 | $59.00 | Aug 20, 2026 |
| 52 | Down | $59.00 | $58.03 | Aug 21, 2026 |
| 53 | Down | $58.03 | $57.55 | Aug 24, 2026 |
| 54 | Up | $58.03 | $59.11 | Aug 26, 2026 |
| 55 | Up | $59.11 | $59.77 | Aug 31, 2026 |
| 56 | Up | $59.77 | $59.82 | Sep 1, 2026 |
| 57 | Down | $59.77 | $58.26 | Sep 4, 2026 |
| 58 | Down | $58.26 | $57.62 | Sep 7, 2026 |
| 59 | Down | $57.62 | $57.12 | Sep 11, 2026 |
| 60 | Up | $57.62 | $58.75 | Sep 16, 2026 |
| 61 | Up | $58.75 | $59.23 | Sep 17, 2026 |
| 62 | Up | $59.23 | $59.59 | Sep 21, 2026 |
| 63 | Up | $59.59 | $59.71 | Sep 22, 2026 |
| 64 | Down | $59.59 | $58.75 | Sep 24, 2026 |
| 65 | Down | $58.75 | $58.03 | Sep 25, 2026 |
| 66 | Down | $58.03 | $57.51 | Sep 28, 2026 |
| 67 | Down | $57.51 | $57.12 | Sep 29, 2026 |
| 68 | Up | $57.51 | $58.10 | Sep 30, 2026 |
| 69 | Up | $58.10 | $58.98 | Oct 1, 2026 |
| 70 | Up | $58.98 | $59.77 | Oct 2, 2026 |
| 71 | Up | $59.77 | $60.37 | Oct 5, 2026 |
| 72 | Down | $59.77 | $58.29 | Oct 12, 2026 |
| 73 | Down | $58.29 | $57.72 | Oct 13, 2026 |
| Line | Direction | From | To |
|---|---|---|---|
| 1 | Up | 10.00 | 11.00 |
| 2 | Up | 11.00 | 12.00 |
| 3 | Up | 12.00 | 13.00 |
| 4 | Up | 13.00 | 13.40 |
| 5 | Down | 13.00 | 9.50 |
| 6 | Down | 9.50 | 9.00 |
Get it
It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.
$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/line_break_chart.qwemain.qwe
import ui/theme.qwe use theme_cssuse ui/line_break_chart.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>Example
The code of the preview above.
demos/line_break_chart.qwe
// Prices from a seeded random walk (Park–Miller), the same on every render: `count`// candles from the day `from` on, skipping weekends, daily or hourly (10:00 to 16:00),// rounded to `places` decimals.fn line_break_chart_demo_walk(seed: int, count: int, start: num, from: str, hourly: bool = false, places: int = 2) -> list { let s = seed let rnd = () => { s = s * 16807 % 2147483647 s / 2147483647 } let scale = pow(10, places) let cut = (v) => round(v * scale) / scale let p = start let day = from let hour = 10 let out = [] while len(out) < count { if date.weekday(day) <= 5 { let open = cut(p * (1 + (rnd() - 0.5) * 0.006)) let close = cut(open * (1 + (rnd() - 0.485) * (hourly ? 0.012 : 0.035))) let high = cut(max([open, close]) * (1 + rnd() * 0.01)) let low = cut(min([open, close]) * (1 - rnd() * 0.01)) let volume = round((hourly ? 90000 : 900000) * (1 + rnd() * 1.5 + abs(close - open) * 30 / open)) out = out | push({ time: hourly ? `${day}T${hour < 10 ? "0" : ""}${hour}:00:00Z` : day, open, high, low, close, volume }) p = close } if hourly && hour < 16 && date.weekday(day) <= 5 { hour += 1 } else { hour = 10 day = date.add_days(day, 1) } } out}let line_break_chart_demo_daily = line_break_chart_demo_walk(19, 140, 64.3, "2026-04-01")view LineBreakChartDemo() = <div.demo-stack> <div id="line_break_chart-demo-three"> <LineBreakChart label="Umbrella Corp., three-line break" show_label={true} candles={line_break_chart_demo_daily} prefix="$"/> </div> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 32px 40px"> <div id="line_break_chart-demo-two"> <LineBreakChart label="Two-line break" show_label={true} candles={line_break_chart_demo_daily} lines={2} prefix="$" height={200}/> </div> <div id="line_break_chart-demo-closes"> <LineBreakChart label="Closes alone" show_label={true} height={200} closes={[10, 11, 12, 13, 12.5, 11.5, 10.8, 13.4, 9.5, 9]}/> </div> </div> </div>Source
ui/line_break_chart.qwe
// LineBreakChart: a three-line break chart, worked out from closing prices: a new// line (a box from the last line's end to the close) each time the close goes past// the last line's end, and a line the other way only when it goes past the far end// of the last three lines, so small turns are left out and time drops out. Green// lines rise, red ones fall. CSS only: hovering a line shows its range and when it// formed, and screen readers get the lines as a table.//// <LineBreakChart label="ACME, three-line break" candles={bars} prefix="$"/>// <LineBreakChart label="ACME, two-line break" candles={bars} lines={2}/>// <LineBreakChart label="Index" closes={[4012.5, 4020.1, 4031.8, 4018.2]}/>//// candles: a list of { time, close } (OHLC candles do), oldest first; or closes: the// numbers alone, without times. lines: how many lines a turn must break (3).// height: the plot's height in pixels (280). last_line: a dashed line at the last// close, with its price on the axis (true). prefix and suffix: around every price shown// ("$"); decimals: digits after the point of prices (2). label: what the chart shows;// it names it for screen readers and captions their table. show_label also writes it// above the chart.import ui/theme.qwe use ui_clslet line_break_chart_css = css`.ui-lbc { --ui-lbc-up: var(--ui-success); --ui-lbc-down: var(--ui-danger); display: grid; gap: 10px; min-width: 0; margin: 0; font: 12px/1.3 var(--ui-font); color: var(--ui-fg); }.ui-lbc-title { margin: 0; font-size: 14px; font-weight: 600; }.ui-lbc-meta { margin: 0; color: var(--ui-muted); font-size: 13px; }.ui-lbc-up { --k: var(--ui-lbc-up); } .ui-lbc-down { --k: var(--ui-lbc-down); }.ui-lbc-frame { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 8px; padding-top: 8px; }.ui-lbc-plot { position: relative; height: var(--h); }.ui-lbc-grid { position: absolute; left: 0; right: 0; height: 1px; background: color-mix(in srgb, var(--ui-border) 70%, transparent); }.ui-lbc-last { position: absolute; z-index: 1; left: 0; right: 0; border-top: 1px dashed var(--k); opacity: .75; pointer-events: none; }.ui-lbc-cols { position: absolute; inset: 0; display: flex; }.ui-lbc-col { position: relative; flex: 1; min-width: 0; }.ui-lbc-col:hover { background: color-mix(in srgb, var(--ui-subtle) 70%, transparent); }.ui-lbc-box { position: absolute; left: 8%; right: 8%; min-height: 2px; border: 1px solid var(--k); border-radius: 2px; background: color-mix(in srgb, var(--k) 70%, transparent); }.ui-lbc-down .ui-lbc-box { background: color-mix(in srgb, var(--k) 18%, transparent); }.ui-lbc-col:hover .ui-lbc-box { background: var(--k); }.ui-lbc-tip { position: absolute; z-index: 20; top: 0; left: calc(50% + 12px); display: none; min-width: 150px; padding: 7px 10px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius-sm); background: var(--ui-surface); box-shadow: var(--ui-shadow); color: var(--ui-fg); font-size: 12px; pointer-events: none; }.ui-lbc-flip .ui-lbc-tip { left: auto; right: calc(50% + 12px); }.ui-lbc-col:hover .ui-lbc-tip { display: block; }.ui-lbc-tip strong { display: block; margin-bottom: 3px; font-weight: 600; white-space: nowrap; color: var(--k); }.ui-lbc-tip p { display: flex; align-items: center; gap: 6px; margin: 2px 0 0; color: var(--ui-muted); white-space: nowrap; }.ui-lbc-tip b { margin-left: auto; padding-left: 12px; color: var(--ui-fg); font-weight: 600; font-variant-numeric: tabular-nums; }.ui-lbc-y { position: relative; height: var(--h); }.ui-lbc-y span { position: absolute; left: 0; transform: translateY(-50%); color: var(--ui-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }.ui-lbc-y .ui-lbc-tag { z-index: 1; left: -4px; padding: 1px 4px; border-radius: 3px; background: var(--k); color: var(--ui-bg); font-weight: 600; }.ui-lbc-x { position: relative; height: 16px; margin-top: 6px; }.ui-lbc-x span { position: absolute; top: 0; transform: translateX(-50%); color: var(--ui-muted); white-space: nowrap; }.ui-lbc-x .ui-lbc-first { transform: none; }.ui-lbc-x .ui-lbc-end { transform: translateX(-100%); }.ui-lbc-empty { display: grid; place-items: center; height: var(--h); border: 1px dashed var(--ui-border); border-radius: var(--ui-radius-sm); color: var(--ui-muted); }`// n (a whole number, 0 or more) with at least `width` digits.fn line_break_chart_pad(n: int, width: int) -> str { let s = str(n) len(s) < width ? repeat("0", width - len(s)) + s : s}// A whole number with commas: 1234567 is "1,234,567".fn line_break_chart_group(n: int) -> str = n < 1000 ? str(n) : line_break_chart_group(floor(n / 1000)) + "," + line_break_chart_pad(n % 1000, 3)// A value as it reads, with commas and `places` digits after the point (up to two as// needed when that is null): 48210.5 is "48,210.50" with 2. Worked out in whole// numbers, so the server and the browser write the same text.fn line_break_chart_num(v: num, places: int? = null) -> str { let digits = places == null ? 2 : (places < 0 ? 0 : (places > 6 ? 6 : places)) let scale = [1, 10, 100, 1000, 10000, 100000, 1000000][digits] let units = round(abs(v) * scale) let frac = units % scale let tail = digits == 0 || (places == null && frac == 0) ? "" : (places == null && frac % 10 == 0 ? `.${floor(frac / 10)}` : `.${line_break_chart_pad(frac, digits)}`) `${v < 0 && units > 0 ? "-" : ""}${line_break_chart_group(floor(units / scale))}${tail}`}// A large number, short: 1500 is "1.5k", 2000000 is "2M", 0.25 is "0.25".fn line_break_chart_short(v: num) -> str { let a = abs(v) let unit = a >= 1000000000 ? 1000000000 : (a >= 1000000 ? 1000000 : (a >= 1000 ? 1000 : 1)) let tenths = round(a * 10 / unit) let mark = unit == 1000000000 ? "B" : (unit == 1000000 ? "M" : "k") unit == 1 ? line_break_chart_num(v) : `${v < 0 ? "-" : ""}${line_break_chart_group(floor(tenths / 10))}${tenths % 10 != 0 ? `.${tenths % 10}` : ""}${mark}`}// A change in percent, signed, to two decimals: "+1.25%", "-0.40%".fn line_break_chart_signed(p: num) -> str = `${p > 0 ? "+" : (p < 0 && round(abs(p) * 100) > 0 ? "-" : "")}${line_break_chart_num(abs(p), 2)}%`// A coordinate out of 100 (or a length), to two decimals.fn line_break_chart_pct(n: num) -> str { let r = round(n * 100) / 100 r == floor(r) ? str(floor(r)) : str(r)}// The axis from lo to hi, widened to round ticks: about `steps` steps of 1, 2, 2.5 or 5 x 10^n.fn line_break_chart_scale(lo: num, hi: num, steps: int = 5) -> map { let span = hi - lo == 0 ? (lo == 0 ? 1 : abs(lo) / 10) : hi - lo let raw = span / steps let mag = 1 while mag * 10 <= raw { mag = mag * 10 } while mag > raw { mag = mag / 10 } let r = raw / mag let step = (r <= 1 ? 1 : (r <= 2 ? 2 : (r <= 2.5 ? 2.5 : (r <= 5 ? 5 : 10)))) * mag let a = floor(lo / step + 0.000001) let b0 = ceil(hi / step - 0.000001) let b = b0 == a ? a + 1 : b0 { lo: a * step, hi: b * step, ticks: for i in range(b - a + 1) => (a + i) * step }}// A candle's time as it reads: "Mar 2, 2026" for a date, "Mar 2, 14:30" for a date-time.fn line_break_chart_when(t) -> str = type_of(t) != "str" || !date.valid(t) ? str(t) : (len(t) > 10 ? date.format(t, "MMM D, HH:mm") : date.format(t, "MMM D, YYYY"))// Where the time axis is labelled: about `limit` of { i, text }, at the starts of years,// months or days when the times cross them ("2026", "Mar", "Mar 2"), else every few// candles ("14:30", "Mar 2").fn line_break_chart_ticks(times: list, limit: int) -> list { let n = len(times) let thin = (idx: list) => { let step = ceil(len(idx) / limit) range(len(idx)) | filter((k) => k % step == 0) | map((k) => idx[k]) } let spaced = (fmt_at) => { let step = ceil(n / limit) range(n) | filter((i) => i % step == floor(step / 2)) | map((i) => { i, text: fmt_at(i) }) } if n == 0 { return [] } if !all(times, (t) => type_of(t) == "str" && date.valid(t)) { return spaced((i) => str(times[i])) } let days = times | map((t) => slice(t, 0, 10)) let changes = (keys: list) => range(1, n) | filter((i) => keys[i] != keys[i - 1]) let day_starts = changes(days) let month_starts = changes(days | map((d) => slice(d, 0, 7))) let year_starts = changes(days | map((d) => slice(d, 0, 4))) let picked = if any(times, (t) => len(t) > 10) { len(day_starts) >= 3 ? thin(day_starts) | map((i) => { i, text: date.format(days[i], "MMM D") }) : spaced((i) => index_of(day_starts, i) != null ? date.format(days[i], "MMM D") : date.format(times[i], "HH:mm")) } else if len(year_starts) >= 3 && len(month_starts) > limit * 2 { thin(year_starts) | map((i) => { i, text: slice(days[i], 0, 4) }) } else if len(month_starts) >= 2 { thin(month_starts) | map((i) => { i, text: index_of(year_starts, i) != null ? slice(days[i], 0, 4) : date.format(days[i], "MMM") }) } else { spaced((i) => date.format(days[i], "MMM D")) } // Starts that fall close together (a short month) keep the first. let gap = n / (limit * 1.5) reduce(picked, [], (kept, t) => len(kept) == 0 || t.i - kept[-1].i >= gap ? kept + [t] : kept)}// The lines: a list of { up, lo, hi, time, close }, the time and close of the candle that// made each. The same way on: from the last line's end to a close past it. A turn: from// the last line's start, when the close goes past the far end of the last `count` lines.fn line_break_chart_lines(points: list, count: int) -> list { let out = [] if len(points) == 0 { return out } let start = points[0].close for p in drop(points, 1) { let c = p.close if len(out) == 0 { if c != start { out = out | push({ up: c > start, lo: min([c, start]), hi: max([c, start]), time: p.time, close: c }) } } else { let last = out[-1] let recent = take(reverse(out), count < 1 ? 1 : count) if last.up && c > last.hi { out = out | push({ up: true, lo: last.hi, hi: c, time: p.time, close: c }) } else if last.up && c < min(recent | map((l) => l.lo)) { out = out | push({ up: false, lo: c, hi: last.lo, time: p.time, close: c }) } else if !last.up && c < last.lo { out = out | push({ up: false, lo: c, hi: last.lo, time: p.time, close: c }) } else if !last.up && c > max(recent | map((l) => l.hi)) { out = out | push({ up: true, lo: last.hi, hi: c, time: p.time, close: c }) } } } out}view LineBreakChart(candles: list = [], closes: list? = null, lines: int = 3, height: int = 280, last_line: bool = true, prefix: str = "", suffix: str = "", decimals: int = 2, label: str = "Line break chart", show_label: bool = false, class: str? = null) { style global line_break_chart_css let points = closes != null ? (closes | map((c) => { time: null, close: c })) : (candles | map((c) => { time: c.time, close: c.close })) let vals = points | map((p) => p.close) let steps = line_break_chart_lines(points, lines) let n = len(steps) let last = len(vals) > 0 ? vals[-1] : null let known = flatten(steps | map((b) => [b.lo, b.hi])) + (last != null ? [last] : []) let sc = line_break_chart_scale(len(known) == 0 ? 0 : min(known), len(known) == 0 ? 1 : max(known), 5) let y = (v) => (sc.hi - v) * 100 / (sc.hi - sc.lo) let x = (i) => (i + 0.5) * 100 / n let pct = (v) => line_break_chart_pct(v) let price = (v) => `${v < 0 ? "-" : ""}${prefix}${line_break_chart_num(abs(v), decimals)}${suffix}` let tick = (t) => `${t < 0 ? "-" : ""}${prefix}${line_break_chart_num(abs(t))}` let side = (up) => up ? "ui-lbc-up" : "ui-lbc-down" let last_up = n == 0 || steps[-1].up let timed = n > 0 && all(steps, (b) => b.time != null) let axis_width = max([4] + (sc.ticks | map((t) => len(tick(t)))) + [last != null ? len(price(last)) + 1 : 0]) <figure class={ui_cls(["ui-lbc", class])} aria-label={label} style={`--h: ${height}px`}> {show_label && <figcaption.ui-lbc-title>{label}</figcaption>} <p.ui-lbc-meta>{lines}-line break · {n} {n == 1 ? "line" : "lines"}</p> {n == 0 ? <div.ui-lbc-empty>The price has not moved.</div> : <div.ui-lbc-frame aria-hidden="true"> <div.ui-lbc-plot> {for t in sc.ticks => <span.ui-lbc-grid style={`top: ${pct(y(t))}%`}></span>} {last_line && last != null && <span class={`ui-lbc-last ${side(last_up)}`} style={`top: ${pct(y(last))}%`}></span>} <div.ui-lbc-cols> {for [i, b] in enumerate(steps) => <div class={ui_cls(["ui-lbc-col", side(b.up), n > 1 && i >= n / 2 && "ui-lbc-flip"])}> <span.ui-lbc-box style={`top: ${pct(y(b.hi))}%; height: ${pct(y(b.lo) - y(b.hi))}%`}></span> <span.ui-lbc-tip> <strong>{b.up ? "Up" : "Down"} line {i + 1}</strong> <p>From<b>{price(b.up ? b.lo : b.hi)}</b></p> <p>To<b>{price(b.up ? b.hi : b.lo)}</b></p> {b.time != null && <p>Formed<b>{line_break_chart_when(b.time)}</b></p>} <p>Close then<b>{price(b.close)}</b></p> </span> </div>} </div> </div> <div.ui-lbc-y style={`width: ${axis_width}ch`}> {for t in sc.ticks => !(last_line && last != null && abs(y(t) - y(last)) * height < 1400) && <span style={`top: ${pct(y(t))}%`}>{tick(t)}</span>} {last_line && last != null && <span class={`ui-lbc-tag ${side(last_up)}`} style={`top: ${pct(y(last))}%`}>{price(last)}</span>} </div> <div.ui-lbc-x> {timed && (for t in line_break_chart_ticks(steps | map((b) => b.time), 7) => <span class={ui_cls([x(t.i) < 4 && "ui-lbc-first", x(t.i) > 96 && "ui-lbc-end"])} style={`left: ${pct(x(t.i))}%`}>{t.text}</span>)} </div> <span></span> </div>} <table.ui-sr> <caption>{label}: {lines}-line break</caption> <thead><tr><th scope="col">Line</th><th scope="col">Direction</th><th scope="col">From</th><th scope="col">To</th> {timed && <th scope="col">Formed</th>}</tr></thead> <tbody> {for [i, b] in enumerate(steps) => <tr> <th scope="row">{i + 1}</th><td>{b.up ? "Up" : "Down"}</td> <td>{price(b.up ? b.lo : b.hi)}</td><td>{price(b.up ? b.hi : b.lo)}</td> {timed && <td>{line_break_chart_when(b.time)}</td>} </tr>} </tbody> </table> </figure>}Related
BollingerChartPrices with Bollinger bands of a period and a number of deviations, closes outside them marked, as a line or candles.7 views · 5 downloadsCandlestickChartOHLC candles, green up and red down, with a volume pane, a date axis, moving averages and the details of each candle on hover.5 views · 7 downloadsDepthChartAn order book's bids and asks as stepped areas of the size waiting at each price or better, around the mid price, with the spread.5 views · 8 downloadsHeikinAshiChartHeikin-Ashi candles worked out from OHLC, which smooth the noise so trends show as runs of one colour, with the real close on hover.5 views · 6 downloadsLiveCandlesCandles built from ticks as they come: the open candle grows, a new one starts each interval, with the last price and the time left.6 views · 6 downloadsMacdChartMACD from closing prices: the MACD and signal lines and the histogram between them, under a price chart or on its own.7 views · 5 downloads
Thread
Thread 0 posts
On LineBreakChart: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.