LightweightCharts
TradingView Lightweight Charts for Phoenix LiveView.
Build interactive financial charts with declarative Elixir configuration and real-time data streaming.
Features
- 6 series types -- Candlestick, Line, Area, Bar, Histogram, Baseline
- Declarative configuration -- Typed Elixir structs with a pipeline-friendly builder API
- Real-time updates -- Stream data points to the browser via
push_event, no page reload - Bidirectional events -- Receive clicks, crosshair moves, and visible range changes in your LiveView
- Markers -- Place visual annotations (arrows, circles, squares) on data points
- Multi-pane layouts -- Display series in separate panes with independent price scales
- Responsive -- Auto-resizes via
ResizeObserver - Zero JS config -- Drop-in function component with a bundled hook
Installation
Add lightweight_charts to your dependencies in mix.exs:
def deps do
[
{:lightweight_charts, "~> 0.1.0"}
]
end
Then add the JavaScript hook to your assets/js/app.js.
With esbuild (default Phoenix bundler)
import { LightweightChartsHook } from "../../deps/lightweight_charts"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { LightweightCharts: LightweightChartsHook }
})
The import path ../../deps/lightweight_charts resolves from assets/js/ to the
package's entry point at deps/lightweight_charts/assets/js/index.js. esbuild
follows this path and bundles the hook along with the vendored lightweight-charts
library automatically.
With bun
import { LightweightChartsHook } from "../../deps/lightweight_charts"
let liveSocket = new LiveSocket("/live", Socket, {
hooks: { LightweightCharts: LightweightChartsHook }
})
Bun resolves the same relative import path. If you prefer, you can configure a
path alias in your bunfig.toml or bundler config to use a shorter import like
"lightweight_charts".
Quick Start
1. Build chart configuration
alias LightweightCharts.{Chart, Series}
chart =
Chart.new()
|> Chart.layout(background_color: "#1a1a2e", text_color: "#e0e0e0")
|> Chart.crosshair(mode: :magnet)
|> Chart.time_scale(time_visible: true)
|> Chart.add_series(
Series.candlestick(id: "candles", up_color: "#26a69a", down_color: "#ef5350")
)
|> Chart.add_series(
Series.histogram(id: "volume", color: "rgba(38,166,154,0.5)", pane_index: 1)
)
|> Chart.on(:click)
Configuration is built entirely with Elixir structs -- no maps, no raw JSON.
The pipeline API lets you compose options incrementally and the result is a
plain %Chart{} struct you can inspect, store, or pass around.
2. Render in your LiveView
In your mount/3:
def mount(_params, _session, socket) do
{:ok, assign(socket, chart: chart)}
endIn your template (HEEx):
<LightweightCharts.chart id="price-chart" chart={@chart} class="h-96 w-full" />
The component renders a div with the LightweightCharts hook attached and
the chart configuration serialized as a data-config attribute. The hook
reads the config on mount and creates the chart.
3. Push data
Send a full dataset to replace all series data:
LightweightCharts.push_data(socket, "price-chart", "candles", candle_data)Stream a single data point (appends or updates the last bar):
LightweightCharts.push_update(socket, "price-chart", "candles", new_candle)4. Handle events
Subscribe to events in your chart config:
chart =
Chart.new()
|> Chart.on(:click)
|> Chart.on(:crosshair_move)
|> Chart.on(:visible_range_change)Then handle them in your LiveView:
def handle_event("lc:click", %{"time" => time, "series_data" => data}, socket) do
# User clicked a point on the chart
{:noreply, socket}
end
def handle_event("lc:crosshairMove", %{"time" => time, "series_data" => data}, socket) do
# Crosshair moved
{:noreply, socket}
end5. Additional helpers
# Update chart or series options at runtime
LightweightCharts.push_options(socket, "price-chart", "chart", %{layout: %{background_color: "#000"}})
# Auto-fit all data into the visible area
LightweightCharts.fit_content(socket, "price-chart")
# Set the visible time range
LightweightCharts.set_visible_range(socket, "price-chart", ~U[2024-01-01 00:00:00Z], ~U[2024-06-01 00:00:00Z])
# Add markers to a series
alias LightweightCharts.Marker
markers = [
Marker.new(time: ~U[2024-01-15 00:00:00Z], position: :above_bar, shape: :arrow_down, color: "#ef5350", text: "Sell"),
Marker.new(time: ~U[2024-02-01 00:00:00Z], position: :below_bar, shape: :arrow_up, color: "#26a69a", text: "Buy")
]
LightweightCharts.set_markers(socket, "price-chart", "candles", markers)Data Formats
OHLC data (Candlestick, Bar)
[
%{time: ~U[2024-01-15 00:00:00Z], open: 185.0, high: 187.5, low: 184.2, close: 186.8},
%{time: ~D[2024-01-16], open: 186.8, high: 189.0, low: 186.0, close: 188.5}
]Single-value data (Line, Area, Histogram, Baseline)
[
%{time: ~U[2024-01-15 00:00:00Z], value: 42.5},
%{time: 1705363200, value: 43.1}
]Time format flexibility
Time values accept any of the following formats -- you can even mix them within the same dataset:
| Format | Example | Encoding |
|---|---|---|
DateTime | ~U[2024-01-15 00:00:00Z] | Unix timestamp (integer) |
NaiveDateTime | ~N[2024-01-15 00:00:00] | Unix timestamp (integer, assumes UTC) |
Date | ~D[2024-01-15] | "2024-01-15" string |
| Unix timestamp | 1705276800 | Passed through |
| String | "2024-01-15" | Passed through |
Series Types
| Type | Constructor | Data Fields |
|---|---|---|
| Candlestick | Series.candlestick/1 | time, open, high, low, close |
| Line | Series.line/1 | time, value |
| Area | Series.area/1 | time, value |
| Bar | Series.bar/1 | time, open, high, low, close |
| Histogram | Series.histogram/1 | time, value |
| Baseline | Series.baseline/1 | time, value |
Each constructor accepts keyword options for styling. For example:
Series.candlestick(id: "candles", up_color: "#26a69a", down_color: "#ef5350")
Series.line(id: "sma", color: "#2196f3", line_width: 2)
Series.histogram(id: "volume", color: "#26a69a", pane_index: 1)
Series.baseline(id: "delta", base_value: 0, top_line_color: "#26a69a", bottom_line_color: "#ef5350")Events
| Event | Atom | LiveView event name |
|---|---|---|
| Click | :click | "lc:click" |
| Double click | :dbl_click | "lc:dblClick" |
| Crosshair move | :crosshair_move | "lc:crosshairMove" |
| Visible range change | :visible_range_change | "lc:visibleTimeRangeChange" |
Chart Configuration Reference
The Chart builder supports these configuration sections:
Chart.new()
|> Chart.layout(background_color: "#fff", text_color: "#333", font_size: 12, font_family: "Arial")
|> Chart.grid(vert_lines_visible: false, horz_lines_color: "#eee")
|> Chart.crosshair(mode: :normal)
|> Chart.time_scale(time_visible: true, bar_spacing: 6, right_offset: 5)
|> Chart.right_price_scale(visible: true, border_visible: false)
|> Chart.left_price_scale(visible: false)
|> Chart.add_series(series)
|> Chart.on(:click)License
This package is licensed under Apache-2.0.
This package includes TradingView Lightweight Charts v5.1.0, which is also licensed under Apache-2.0. Copyright TradingView, Inc.