montzar-chart
v0.7.2
Published
Vue 3 candlestick chart shell with TradingView-style drawing tools on lightweight-charts
Maintainers
Readme
montzar-chart
Vue 3 candlestick chart with TradingView-style drawing tools on lightweight-charts.
The chart does not fetch your API. You load OHLC in the host app and pass candles.
Install
npm install montzar-chart vue lightweight-chartsvue (^3.5.0) and lightweight-charts (^5.2.1) are peer dependencies. ESM-only.
Usage
<script setup lang="ts">
import { ref, watch } from 'vue'
import { Chart, type ChartCandle, type ChartInterval, type ChartRange, type ChartSymbol } from 'montzar-chart'
import 'montzar-chart/styles.css'
const symbol: ChartSymbol = { id: 'XAUUSD', title: 'XAU / USD' }
const interval = ref<ChartInterval>('15m')
const range = ref<ChartRange>('24h')
const candles = ref<ChartCandle[]>([])
const lastPrice = ref<number | null>(null)
const loading = ref(false)
async function load() {
loading.value = true
try {
const rows = await fetchYourOhlc({ interval: interval.value, range: range.value })
candles.value = rows.map((row) => ({
time: Math.floor(new Date(row.date).getTime() / 1000),
open: row.open,
high: row.high,
low: row.low,
close: row.close,
}))
} finally {
loading.value = false
}
}
watch([interval, range], load, { immediate: true })
</script>
<template>
<Chart
:symbol="symbol"
:candles="candles"
:last-price="lastPrice"
:loading="loading"
:empty="!candles.length"
storage-key="demo"
v-model:interval="interval"
v-model:range="range"
/>
</template>time is UNIX seconds, not milliseconds. Import montzar-chart/styles.css once.
Host time / date labels
Candle time stays UNIX seconds (UTC). The chart does not hardcode a timezone or calendar. Pass formatters from the host app:
<Chart
:time-formatter="formatChartTime"
:tick-mark-formatter="formatChartTick"
...
/>// Example: Iran / Jalali in the host — other apps pass their own helpers
function formatChartTime(time: number) {
return formatUnixInHostTz(time, 'full') // host-owned
}
function formatChartTick(time: number, tickMarkType: number) {
// tickMarkType: 0=Year 1=Month 2=Day 3=Time 4=TimeWithSeconds
return formatUnixTickInHostTz(time, tickMarkType)
}Omit either prop to keep the lightweight-charts default (browser local).
When the user changes interval or range, update your fetch and replace candles. Pass live ticks as lastPrice. For bid/ask last-price lines (MetaTrader-style), also pass askPrice and bidPrice; dual lines appear only when both are finite numbers.
To limit UI choices (for example when your API only supports a few timeframes), pass intervals and/or ranges:
<Chart
:intervals="['1m', '5m', '15m', '1h']"
:ranges="['1h', '24h', '7d', '30d']"
...
/>Omit either prop (or pass an empty array) to keep the full built-in list. If the current v-model:interval / v-model:range is not in the allowed list, the chart switches to the first allowed value.
Price pick (interactionMode)
Default is analysis: left-click pans / draws as before. Pass interaction-mode="trading" to add a desktop context-menu item «ثبت سفارش در این قیمت». Choosing it emits @price-pick and does not place an order — order UI stays in the host app.
For tap-to-pick (mobile-first, also works on desktop), the host arms the chart:
<Chart
interaction-mode="trading"
:price-pick-armed="armed"
@price-pick="onPricePick"
...
/>function onPricePick(payload: { price: number; time: number | null; source: 'context-menu' | 'tap' }) {
// `price` is in the same units as `candles` (e.g. toman). Convert in the host if the API uses rial.
}While pricePickArmed is true, a tap/click on the canvas emits source: 'tap' and pan/draw are paused. Right-click place-order stays available. Bid/ask lines are unrelated and stay as they are.
Trading lines (tradingLines)
The chart only draws host-owned price lines. The host maps backend rows and passes visual props. The package does not know Limit, Stop, TP, Position, or admin.
<Chart
:trading-lines="tradingLines"
@trading-line-action="onTradingLineAction"
@trading-line-move="onTradingLineMove"
...
/>const tradingLines: ChartTradingLine[] = [
{ id: 'ordri:12', kind: 'ordri', side: 'buy', price: 4_200_000, label: 'اردری خرید', lineStyle: 'solid' },
{ id: 'trigger:7', kind: 'trigger', side: 'sell', price: 4_250_000, condition: 'gte', label: 'حد فروش', cancelable: true, lineStyle: 'dashed' },
{ id: 'tp:3', kind: 'trigger', side: 'sell', price: 4_300_000, label: 'حد سود', draggable: true, cancelable: true, color: '#0d9488' },
]color/lineStyleare host-owned. If omitted: buy green / sell red;ordrisolid, otherwise dashedpriceis in the same units ascandles- a short click near a line emits
@trading-line-action{ id, kind, action: 'cancel' | 'click' }(cancelwhencancelable) - set
draggable: trueto allow vertical drag. The chart emits@trading-line-move{ id, kind, price, phase: 'start' | 'move' | 'end' | 'cancel' }. Persist onend; the package does not call your API - pass
armed-trading-line-idwith adraggableline id to let the next pointerdown anywhere on the canvas drag/place that line (no 8px hit). Clicking the line itself still uses the normal grab/click path. Pan is paused for that gesture; cursor isns-resize
Props
| Prop | Type | Role |
|---|---|---|
| symbol | { id, title, logo? } | Header title / logo |
| candles | { time, open, high, low, close }[] | OHLC |
| lastPrice | number \| null | Live last price (updates the forming candle) |
| askPrice | number \| null | Live buy/ask; with bidPrice shows two last-price lines |
| bidPrice | number \| null | Live sell/bid; with askPrice shows two last-price lines |
| loading | boolean | Loading message |
| empty | boolean | Empty message |
| symbols | ChartSymbol[] | Header symbol search |
| storageKey | string | localStorage key for drawings |
| intervals | ChartInterval[] | Timeframes shown in the picker (default: all) |
| ranges | ChartRange[] | Range chips shown at the bottom (default: all) |
| v-model:interval | 1m … 1M | Timeframe |
| v-model:range | 1h | 24h | 7d | 30d | Range chips |
| interactionMode | 'analysis' | 'trading' | Default analysis. trading shows the place-order context item |
| pricePickArmed | boolean | With trading, tap/click emits @price-pick (source: 'tap') |
| tradingLines | ChartTradingLine[] | Host-owned price lines (color/label from host) |
| armedTradingLineId | string \| null | Pointerdown anywhere drags that draggable line |
| timeFormatter | (time: number) => string | Host-owned crosshair time label (UNIX seconds UTC). Omit → library default |
| tickMarkFormatter | (time, tickMarkType, locale) => string \| null | Host-owned axis ticks. Omit → library default |
| @select-symbol | id | Symbol picked in the header |
| @price-pick | { price, time, source } | Place-order from context menu or armed tap |
| @trading-line-action | { id, kind, action } | Click near a host trading line |
| @trading-line-move | { id, kind, price, phase } | Vertical drag of a draggable line |
Versions
After you publish a new version (0.1.1, 0.2.0, …):
npm install montzar-chart@latestUse "montzar-chart": "^0.7.1" so npm update receives compatible updates.
Maintainers: from the repo root, npm version patch -w montzar-chart then npm publish -w montzar-chart --access public.
License
MIT
