From fb7fa7eba28f1a247160a64ace65c6fb3af52a0d Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Fri, 17 Oct 2025 15:05:12 +0100 Subject: [PATCH 01/20] expose instance api --- package.json | 2 +- src/ChartProComponent.tsx | 90 ++++++++++++++++++++------------------- src/KLineChartPro.tsx | 20 ++++++++- src/types.ts | 5 ++- 4 files changed, 70 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 71098f39..dd1a0ed8 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,6 @@ "solid-js": "^1.6.11" }, "peerDependencies": { - "klinecharts": ">=9.0.0" + "klinecharts": ">=10.0.0" } } diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index 47696350..8fe1dfbf 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -65,9 +65,10 @@ function createIndicator (widget: Nullable, indicatorName: string, isStac }, isStack, paneOptions) ?? null } +export const [ widget, setWidget ] = createSignal>(null) + const ChartProComponent: Component = props => { let widgetRef: HTMLDivElement | undefined = undefined - let widget: Nullable = null let priceUnitDom: HTMLElement @@ -105,7 +106,7 @@ const ChartProComponent: Component = props => { setTheme, getTheme: () => theme(), setStyles, - getStyles: () => widget!.getStyles(), + getStyles: () => widget()!.getStyles(), setLocale, getLocale: () => locale(), setTimezone: (timezone: string) => { setTimezone({ key: timezone, text: translateTimezone(props.timezone, locale()) }) }, @@ -113,11 +114,14 @@ const ChartProComponent: Component = props => { setSymbol, getSymbol: () => symbol(), setPeriod, - getPeriod: () => period() + getPeriod: () => period(), + getInstanceApi: () => widget(), + resize: () => widget()?.resize(), + dispose: () => {} }) const documentResize = () => { - widget?.resize() + widget()?.resize() } const adjustFromTo = (period: Period, toTimestamp: number, count: number) => { @@ -174,7 +178,7 @@ const ChartProComponent: Component = props => { onMount(() => { window.addEventListener('resize', documentResize) - widget = init(widgetRef!, { + setWidget(init(widgetRef!, { customApi: { formatDate: (dateTimeFormat: Intl.DateTimeFormat, timestamp, format: string, type: FormatDateType) => { const p = period() @@ -209,10 +213,10 @@ const ChartProComponent: Component = props => { return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD HH:mm') } } - }) + })) - if (widget) { - const watermarkContainer = widget.getDom('candle_pane', DomPosition.Main) + if (widget()) { + const watermarkContainer = widget()!.getDom('candle_pane', DomPosition.Main) if (watermarkContainer) { let watermark = document.createElement('div') watermark.className = 'klinecharts-pro-watermark' @@ -225,49 +229,49 @@ const ChartProComponent: Component = props => { watermarkContainer.appendChild(watermark) } - const priceUnitContainer = widget.getDom('candle_pane', DomPosition.YAxis) + const priceUnitContainer = widget()!.getDom('candle_pane', DomPosition.YAxis) priceUnitDom = document.createElement('span') priceUnitDom.className = 'klinecharts-pro-price-unit' priceUnitContainer?.appendChild(priceUnitDom) } mainIndicators().forEach(indicator => { - createIndicator(widget, indicator, true, { id: 'candle_pane' }) + createIndicator(widget(), indicator, true, { id: 'candle_pane' }) }) const subIndicatorMap = {} props.subIndicators!.forEach(indicator => { - const paneId = createIndicator(widget, indicator, true) + const paneId = createIndicator(widget(), indicator, true) if (paneId) { // @ts-expect-error subIndicatorMap[indicator] = paneId } }) setSubIndicators(subIndicatorMap) - widget?.loadMore(timestamp => { + widget()?.loadMore(timestamp => { loading = true const get = async () => { const p = period() const [to] = adjustFromTo(p, timestamp!, 1) const [from] = adjustFromTo(p, to, 500) const kLineDataList = await props.datafeed.getHistoryKLineData(symbol(), p, from, to) - widget?.applyMoreData(kLineDataList, kLineDataList.length > 0) + widget()?.applyMoreData(kLineDataList, kLineDataList.length > 0) loading = false } get() }) - widget?.subscribeAction(ActionType.OnTooltipIconClick, (data) => { + widget()?.subscribeAction(ActionType.OnTooltipIconClick, (data) => { if (data.indicatorName) { switch (data.iconId) { case 'visible': { - widget?.overrideIndicator({ name: data.indicatorName, visible: true }, data.paneId) + widget()?.overrideIndicator({ name: data.indicatorName, visible: true }, data.paneId) break } case 'invisible': { - widget?.overrideIndicator({ name: data.indicatorName, visible: false }, data.paneId) + widget()?.overrideIndicator({ name: data.indicatorName, visible: false }, data.paneId) break } case 'setting': { - const indicator = widget?.getIndicatorByPaneId(data.paneId, data.indicatorName) as Indicator + const indicator = widget()?.getIndicatorByPaneId(data.paneId, data.indicatorName) as Indicator setIndicatorSettingModalParams({ visible: true, indicatorName: data.indicatorName, paneId: data.paneId, calcParams: indicator.calcParams }) @@ -276,12 +280,12 @@ const ChartProComponent: Component = props => { case 'close': { if (data.paneId === 'candle_pane') { const newMainIndicators = [...mainIndicators()] - widget?.removeIndicator('candle_pane', data.indicatorName) + widget()?.removeIndicator('candle_pane', data.indicatorName) newMainIndicators.splice(newMainIndicators.indexOf(data.indicatorName), 1) setMainIndicators(newMainIndicators) } else { const newIndicators = { ...subIndicators() } - widget?.removeIndicator(data.paneId, data.indicatorName) + widget()?.removeIndicator(data.paneId, data.indicatorName) // @ts-expect-error delete newIndicators[data.indicatorName] setSubIndicators(newIndicators) @@ -305,7 +309,7 @@ const ChartProComponent: Component = props => { } else { priceUnitDom.style.display = 'none' } - widget?.setPriceVolumePrecision(s?.pricePrecision ?? 2, s?.volumePrecision ?? 0) + widget()?.setPriceVolumePrecision(s?.pricePrecision ?? 2, s?.volumePrecision ?? 0) }) createEffect((prev?: PrevSymbolPeriod) => { @@ -320,9 +324,9 @@ const ChartProComponent: Component = props => { const get = async () => { const [from, to] = adjustFromTo(p, new Date().getTime(), 500) const kLineDataList = await props.datafeed.getHistoryKLineData(s, p, from, to) - widget?.applyNewData(kLineDataList, kLineDataList.length > 0) + widget()?.applyNewData(kLineDataList, kLineDataList.length > 0) props.datafeed.subscribe(s, p, data => { - widget?.updateData(data) + widget()?.updateData(data) }) loading = false setLoadingVisible(false) @@ -335,9 +339,9 @@ const ChartProComponent: Component = props => { createEffect(() => { const t = theme() - widget?.setStyles(t) + widget()?.setStyles(t) const color = t === 'dark' ? '#929AA5' : '#76808F' - widget?.setStyles({ + widget()?.setStyles({ indicator: { tooltip: { icons: [ @@ -424,17 +428,17 @@ const ChartProComponent: Component = props => { }) createEffect(() => { - widget?.setLocale(locale()) + widget()?.setLocale(locale()) }) createEffect(() => { - widget?.setTimezone(timezone().key) + widget()?.setTimezone(timezone().key) }) createEffect(() => { if (styles()) { - widget?.setStyles(styles()) - setWidgetDefaultStyles(lodashClone(widget!.getStyles())) + widget()?.setStyles(styles()) + setWidgetDefaultStyles(lodashClone(widget()!.getStyles())) } }) @@ -457,10 +461,10 @@ const ChartProComponent: Component = props => { onMainIndicatorChange={data => { const newMainIndicators = [...mainIndicators()] if (data.added) { - createIndicator(widget, data.name, true, { id: 'candle_pane' }) + createIndicator(widget(), data.name, true, { id: 'candle_pane' }) newMainIndicators.push(data.name) } else { - widget?.removeIndicator('candle_pane', data.name) + widget()?.removeIndicator('candle_pane', data.name) newMainIndicators.splice(newMainIndicators.indexOf(data.name), 1) } setMainIndicators(newMainIndicators) @@ -468,14 +472,14 @@ const ChartProComponent: Component = props => { onSubIndicatorChange={data => { const newSubIndicators = { ...subIndicators() } if (data.added) { - const paneId = createIndicator(widget, data.name) + const paneId = createIndicator(widget(), data.name) if (paneId) { // @ts-expect-error newSubIndicators[data.name] = paneId } } else { if (data.paneId) { - widget?.removeIndicator(data.paneId, data.name) + widget()?.removeIndicator(data.paneId, data.name) // @ts-expect-error delete newSubIndicators[data.name] } @@ -494,10 +498,10 @@ const ChartProComponent: Component = props => { { setSettingModalVisible(false) }} onChange={style => { - widget?.setStyles(style) + widget()?.setStyles(style) }} onRestoreDefault={(options: SelectDataSourceItem[]) => { const style = {} @@ -505,7 +509,7 @@ const ChartProComponent: Component = props => { const key = option.key lodashSet(style, key, utils.formatValue(widgetDefaultStyles(), key)) }) - widget?.setStyles(style) + widget()?.setStyles(style) }} /> @@ -523,7 +527,7 @@ const ChartProComponent: Component = props => { onClose={() => { setIndicatorSettingModalParams({ visible: false, indicatorName: '', paneId: '', calcParams: [] }) }} onConfirm={(params)=> { const modalParams = indicatorSettingModalParams() - widget?.overrideIndicator({ name: modalParams.indicatorName, calcParams: params }, modalParams.paneId) + widget()?.overrideIndicator({ name: modalParams.indicatorName, calcParams: params }, modalParams.paneId) }} /> @@ -536,7 +540,7 @@ const ChartProComponent: Component = props => { onMenuClick={async () => { try { await startTransition(() => setDrawingBarVisible(!drawingBarVisible())) - widget?.resize() + widget()?.resize() } catch (e) {} }} onSymbolClick={() => { setSymbolSearchModalVisible(!symbolSearchModalVisible()) }} @@ -546,7 +550,7 @@ const ChartProComponent: Component = props => { onSettingClick={() => { setSettingModalVisible((visible => !visible)) }} onScreenshotClick={() => { if (widget) { - const url = widget.getConvertPictureUrl(true, 'jpeg', props.theme === 'dark' ? '#151517' : '#ffffff') + const url = widget()!.getConvertPictureUrl(true, 'jpeg', props.theme === 'dark' ? '#151517' : '#ffffff') setScreenshotUrl(url) } }} @@ -559,11 +563,11 @@ const ChartProComponent: Component = props => { { widget?.createOverlay(overlay) }} - onModeChange={mode => { widget?.overrideOverlay({ mode: mode as OverlayMode }) }} - onLockChange={lock => { widget?.overrideOverlay({ lock }) }} - onVisibleChange={visible => { widget?.overrideOverlay({ visible }) }} - onRemoveClick={(groupId) => { widget?.removeOverlay({ groupId }) }}/> + onDrawingItemClick={overlay => { widget()?.createOverlay(overlay) }} + onModeChange={mode => { widget()?.overrideOverlay({ mode: mode as OverlayMode }) }} + onLockChange={lock => { widget()?.overrideOverlay({ lock }) }} + onVisibleChange={visible => { widget()?.overrideOverlay({ visible }) }} + onRemoveClick={(groupId) => { widget()?.removeOverlay({ groupId }) }}/>
= null + destroy() { + if (this._container) + dispose(this._container!) + + this._container = null + this._chartApi = null + } setTheme (theme: string): void { this._container?.setAttribute('data-theme', theme) @@ -128,4 +135,13 @@ export default class KLineChartPro implements ChartPro { getPeriod (): Period { return this._chartApi!.getPeriod() } + getInstanceApi(): Nullable { + return widget() + } + resize(): void { + widget()!.resize() + } + dispose(): void { + this.destroy() + } } diff --git a/src/types.ts b/src/types.ts index 8f1b97c9..c25e26b1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,7 +12,7 @@ * limitations under the License. */ -import { KLineData, Styles, DeepPartial } from 'klinecharts' +import { KLineData, Styles, DeepPartial, Nullable, Chart } from 'klinecharts' export interface SymbolInfo { ticker: string @@ -71,4 +71,7 @@ export interface ChartPro { getSymbol(): SymbolInfo setPeriod(period: Period): void getPeriod(): Period + getInstanceApi(): Nullable + resize(): void + dispose(): void } From d0a10b7c4ae5fdad2384c6ff277c35c884eefddd Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Fri, 17 Oct 2025 18:13:26 +0100 Subject: [PATCH 02/20] fix widget initialization formatter --- package-lock.json | 14 ++++++------- package.json | 2 +- src/ChartProComponent.tsx | 41 ++++++++++++++++++++------------------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index d178a974..7a8d0e66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "vitest": "^0.28.4" }, "peerDependencies": { - "klinecharts": ">=9.0.0" + "klinecharts": "^10.0.0-alpha9" } }, "node_modules/@adobe/css-tools": { @@ -5224,9 +5224,9 @@ } }, "node_modules/klinecharts": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/klinecharts/-/klinecharts-9.1.1.tgz", - "integrity": "sha512-OnHeStm2zFf6iTPMPB720+gpD8psXmajZfd7eqZN+Z5FxnYF7sSsPE02J84JTJ0AVrXYOdLJShIdYkr7aNN5LA==", + "version": "10.0.0-alpha9", + "resolved": "https://registry.npmjs.org/klinecharts/-/klinecharts-10.0.0-alpha9.tgz", + "integrity": "sha512-cpI8x2TE7qLr36WKLFbD8chg5Un3i+o1JKy/H0IMxSh5YqhTvfth7PMB5okY4a37HfyIa53n26l/NT44usk6Fg==", "peer": true }, "node_modules/klona": { @@ -11421,9 +11421,9 @@ } }, "klinecharts": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/klinecharts/-/klinecharts-9.1.1.tgz", - "integrity": "sha512-OnHeStm2zFf6iTPMPB720+gpD8psXmajZfd7eqZN+Z5FxnYF7sSsPE02J84JTJ0AVrXYOdLJShIdYkr7aNN5LA==", + "version": "10.0.0-alpha9", + "resolved": "https://registry.npmjs.org/klinecharts/-/klinecharts-10.0.0-alpha9.tgz", + "integrity": "sha512-cpI8x2TE7qLr36WKLFbD8chg5Un3i+o1JKy/H0IMxSh5YqhTvfth7PMB5okY4a37HfyIa53n26l/NT44usk6Fg==", "peer": true }, "klona": { diff --git a/package.json b/package.json index dd1a0ed8..35311006 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,6 @@ "solid-js": "^1.6.11" }, "peerDependencies": { - "klinecharts": ">=10.0.0" + "klinecharts": "^10.0.0-alpha9" } } diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index 8fe1dfbf..a199881e 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -16,7 +16,8 @@ import { createSignal, createEffect, onMount, Show, onCleanup, startTransition, import { init, dispose, utils, Nullable, Chart, OverlayMode, Styles, - TooltipIconPosition, ActionType, PaneOptions, Indicator, DomPosition, FormatDateType + TooltipIconPosition, ActionType, PaneOptions, Indicator, DomPosition, FormatDateType, + FormatDateParams } from 'klinecharts' import lodashSet from 'lodash/set' @@ -43,9 +44,9 @@ interface PrevSymbolPeriod { } function createIndicator (widget: Nullable, indicatorName: string, isStack?: boolean, paneOptions?: PaneOptions): Nullable { - if (indicatorName === 'VOL') { - paneOptions = { gap: { bottom: 2 }, ...paneOptions } - } + // if (indicatorName === 'VOL') { + // paneOptions = { state: { bottom: 2 }, ...paneOptions } + // } return widget?.createIndicator({ name: indicatorName, // @ts-expect-error @@ -179,38 +180,38 @@ const ChartProComponent: Component = props => { onMount(() => { window.addEventListener('resize', documentResize) setWidget(init(widgetRef!, { - customApi: { - formatDate: (dateTimeFormat: Intl.DateTimeFormat, timestamp, format: string, type: FormatDateType) => { + formatter: { + formatDate: (params: FormatDateParams) => { const p = period() switch (p.timespan) { case 'minute': { - if (type === FormatDateType.XAxis) { - return utils.formatDate(dateTimeFormat, timestamp, 'HH:mm') + if (params.type === 'xAxis') { + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'HH:mm') } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD HH:mm') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD HH:mm') } case 'hour': { - if (type === FormatDateType.XAxis) { - return utils.formatDate(dateTimeFormat, timestamp, 'MM-DD HH:mm') + if (params.type === 'xAxis') { + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'MM-DD HH:mm') } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD HH:mm') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD HH:mm') } case 'day': - case 'week': return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD') + case 'week': return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD') case 'month': { - if (type === FormatDateType.XAxis) { - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM') + if (params.type === 'xAxis') { + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM') } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD') } case 'year': { - if (type === FormatDateType.XAxis) { - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY') + if (params.type === 'xAxis') { + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY') } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD') } } - return utils.formatDate(dateTimeFormat, timestamp, 'YYYY-MM-DD HH:mm') + return utils.formatDate(params.dateTimeFormat, params.timestamp, 'YYYY-MM-DD HH:mm') } } })) From a7dfc679a302fd94ab133ce3aad7b6a3b23ba184 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Fri, 17 Oct 2025 19:26:03 +0100 Subject: [PATCH 03/20] implement dataloader --- src/ChartProComponent.tsx | 32 ++++++++++++-------------------- src/DataLoader.ts | 35 +++++++++++++++++++++++++++++++++++ src/KLineChartPro.tsx | 4 +++- 3 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 src/DataLoader.ts diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index a199881e..7f916c0f 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -16,7 +16,7 @@ import { createSignal, createEffect, onMount, Show, onCleanup, startTransition, import { init, dispose, utils, Nullable, Chart, OverlayMode, Styles, - TooltipIconPosition, ActionType, PaneOptions, Indicator, DomPosition, FormatDateType, + ActionType, PaneOptions, Indicator, DomPosition, FormatDateType, FormatDateParams } from 'klinecharts' @@ -33,9 +33,11 @@ import { import { translateTimezone } from './widget/timezone-modal/data' import { SymbolInfo, Period, ChartProOptions, ChartPro } from './types' +import ChartDataLoader from './DataLoader' -export interface ChartProComponentProps extends Required> { +export interface ChartProComponentProps extends Required> { ref: (chart: ChartPro) => void + dataloader: ChartDataLoader } interface PrevSymbolPeriod { @@ -44,9 +46,9 @@ interface PrevSymbolPeriod { } function createIndicator (widget: Nullable, indicatorName: string, isStack?: boolean, paneOptions?: PaneOptions): Nullable { - // if (indicatorName === 'VOL') { - // paneOptions = { state: { bottom: 2 }, ...paneOptions } - // } + if (indicatorName === 'VOL') { + paneOptions = { axis: { gap: { bottom: 2 } }, ...paneOptions } + } return widget?.createIndicator({ name: indicatorName, // @ts-expect-error @@ -217,7 +219,7 @@ const ChartProComponent: Component = props => { })) if (widget()) { - const watermarkContainer = widget()!.getDom('candle_pane', DomPosition.Main) + const watermarkContainer = widget()!.getDom('candle_pane', 'main') if (watermarkContainer) { let watermark = document.createElement('div') watermark.className = 'klinecharts-pro-watermark' @@ -230,7 +232,7 @@ const ChartProComponent: Component = props => { watermarkContainer.appendChild(watermark) } - const priceUnitContainer = widget()!.getDom('candle_pane', DomPosition.YAxis) + const priceUnitContainer = widget()!.getDom('candle_pane', 'yAxis') priceUnitDom = document.createElement('span') priceUnitDom.className = 'klinecharts-pro-price-unit' priceUnitContainer?.appendChild(priceUnitDom) @@ -248,19 +250,9 @@ const ChartProComponent: Component = props => { } }) setSubIndicators(subIndicatorMap) - widget()?.loadMore(timestamp => { - loading = true - const get = async () => { - const p = period() - const [to] = adjustFromTo(p, timestamp!, 1) - const [from] = adjustFromTo(p, to, 500) - const kLineDataList = await props.datafeed.getHistoryKLineData(symbol(), p, from, to) - widget()?.applyMoreData(kLineDataList, kLineDataList.length > 0) - loading = false - } - get() - }) - widget()?.subscribeAction(ActionType.OnTooltipIconClick, (data) => { + widget()?.setDataLoader(props.dataloader) + + widget()?.subscribeAction('onCandleTooltipFeatureClick', (data) => { if (data.indicatorName) { switch (data.iconId) { case 'visible': { diff --git a/src/DataLoader.ts b/src/DataLoader.ts new file mode 100644 index 00000000..fc84d4ff --- /dev/null +++ b/src/DataLoader.ts @@ -0,0 +1,35 @@ +import { DataLoader, DataLoaderGetBarsParams, DataLoaderSubscribeBarParams, DataLoaderUnsubscribeBarParams } from "klinecharts"; +import { Datafeed } from "./types"; + +export default class ChartDataLoader implements DataLoader { + private _datafeed: Datafeed; + + constructor(datafeed: Datafeed) { + this._datafeed = datafeed; + } + + async getBars (params: DataLoaderGetBarsParams): Promise { + const { type, timestamp, symbol, period, callback } = params; + widget()?.loadMore(timestamp => { + loading = true + const get = async () => { + const p = period() + const [to] = adjustFromTo(p, timestamp!, 1) + const [from] = adjustFromTo(p, to, 500) + const kLineDataList = await props.datafeed.getHistoryKLineData(symbol(), p, from, to) + widget()?.applyMoreData(kLineDataList, kLineDataList.length > 0) + loading = false + } + get() + }) + const klineData = await this._datafeed.getHistoryKLineData(symbol, period, from, to); + } + + subscribeBar (params: DataLoaderSubscribeBarParams): void { + const { symbol, period, callback } = params; + } + + unsubscribeBar (params: DataLoaderUnsubscribeBarParams): void { + const { symbol, period } = params; + } +} \ No newline at end of file diff --git a/src/KLineChartPro.tsx b/src/KLineChartPro.tsx index 01130f5e..63e308c1 100644 --- a/src/KLineChartPro.tsx +++ b/src/KLineChartPro.tsx @@ -19,6 +19,7 @@ import { utils, Nullable, DeepPartial, Styles, Chart, dispose } from 'klinechart import ChartProComponent, { widget } from './ChartProComponent' import { SymbolInfo, Period, ChartPro, ChartProOptions } from './types' +import ChartDataLoader from './DataLoader' const Logo = (
  • { - props.onMainIndicatorChange({ name, paneId: 'candle_pane', added: !checked }) + props.onMainIndicatorChange({ name, id: 'candle_pane', added: !checked }) }}>
  • @@ -76,7 +77,7 @@ const IndicatorModal: Component = props => { class="row" onClick={_ => { // @ts-expect-error - props.onSubIndicatorChange({ name, paneId: props.subIndicators[name] ?? '', added: !checked }); + props.onSubIndicatorChange({ name, id: props.subIndicators[name] ?? '', added: !checked }); }}> From 0bce5309f24fdfc4844acfb80bbb7c48fce18f01 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Mon, 20 Oct 2025 17:12:56 +0100 Subject: [PATCH 07/20] remove comment --- src/ChartProComponent.tsx | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index 7ae24a16..8644d943 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -76,35 +76,6 @@ function createIndicator (widget: Chart, indicatorName: string, isStack?: boolea } }}, isStack, paneOptions) ?? null - // if (!indi) - // return null - - // const _indi = widget.getIndicators({ id: indi, name: indicatorName, paneId: paneOptions?.id}).at(0) - // widget.overrideIndicator({ - // id: indi, - // name: indicatorName, - // createTooltipDataSource: (param) => { - // const indiStyles = param.chart.getStyles().indicator - // const features = indiStyles.tooltip.features - // const icons: TooltipFeatureStyle[] = [] - // if (param.indicator.visible) { - // icons.push(features[1]) - // icons.push(features[2]) - // icons.push(features[3]) - // } else { - // icons.push(features[0]) - // icons.push(features[2]) - // icons.push(features[3]) - // } - // return { - // name: `${indicatorName}_${indi}`, - // calcParamsText: indicatorName, - // features: icons, - // legends: [] - // } - // } - // }) - return indi } From 9449f1964b0f3bc8f27bb34e7cba8a2f80665953 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Tue, 21 Oct 2025 11:48:19 +0100 Subject: [PATCH 08/20] implement position positionStyle and tick stores --- src/store/overlayStyle/positionStyleStore.ts | 193 ++++++++++ src/store/positionStore.ts | 363 +++++++++++++++++++ src/store/tickStore.ts | 5 + 3 files changed, 561 insertions(+) create mode 100644 src/store/overlayStyle/positionStyleStore.ts create mode 100644 src/store/positionStore.ts create mode 100644 src/store/tickStore.ts diff --git a/src/store/overlayStyle/positionStyleStore.ts b/src/store/overlayStyle/positionStyleStore.ts new file mode 100644 index 00000000..a4ac2d8a --- /dev/null +++ b/src/store/overlayStyle/positionStyleStore.ts @@ -0,0 +1,193 @@ +import { createSignal } from 'solid-js'; + +export const [buyStyle, setBuyStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#00698b', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#00698b' + } +}) + +export const [buyLimitStyle, setBuyLimitStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#00698b', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#00698b' + } +}) + +export const [buyStopStyle, setBuyStopStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#00698b', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#00698b' + } +}) + +export const [sellStyle, setSellStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#fb7b50', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#fb7b50' + } +}) + +export const [sellLimitStyle, setSellLimitStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#fb7b50', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#fb7b50' + } +}) + +export const [sellStopStyle, setSellStopStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#fb7b50', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#fb7b50' + } +}) + +export const [takeProfitStyle, setTakeProfitStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#00698b', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#00698b' + } +}) + +export const [stopLossStyle, setStopLossStyle] = createSignal({ + lineStyle: { + style: 'dashed', + size: 1, + color: '#fb7b50', + dashedValue: [4, 4] + }, + labelStyle: { + style: 'fill', + size: 12, + family:'Helvetica Neue', + weight: 'normal', + paddingLeft: 5, + paddingRight: 5, + paddingBottom: 5, + paddingTop: 5, + borderStyle: 'solid', + borderSize: 1, + color: '#FFFFFF', + borderColor: '#00698b', + backgroundColor: '#fb7b50' + } +}) \ No newline at end of file diff --git a/src/store/positionStore.ts b/src/store/positionStore.ts new file mode 100644 index 00000000..c288788c --- /dev/null +++ b/src/store/positionStore.ts @@ -0,0 +1,363 @@ +import { Chart, Nullable, Overlay, OverlayEvent, Point } from 'klinecharts'; +import { ExitType, OrderInfo, OrderModifyInfo, OrderResource, OrderType } from '../types'; +import { createSignal } from 'solid-js'; +import { currenttick } from './tickStore'; +import { instanceapi, symbol } from '../ChartProComponent'; +// import { setOrderModalVisible } from './chartStateStore'; +// import { syntheticPausePlay } from './keyEventStore'; + +export const [chartapi, setChartapi] = createSignal>(null); +export const [ordercontr, setOrderContr] = createSignal>(null) +export const [orderList, setOrderList] = createSignal([]) +export const [currentequity, setCurrentequity] = createSignal(0) + +export const useOrder = () => { + const onOrderPlaced = (order: OrderInfo|null) => { + // setOrderModalVisible(false) + // syntheticPausePlay(false) + if (order) { + drawOrder(order) + let orderlist = orderList() + if (!orderlist.find(orda => orda.orderId === order?.orderId)) { + orderlist.push(order) + setOrderList(orderlist) + } + } + } + + /** + * This method may be used interchangeably with calcStopOrTarget + * + * @param top + * @param middle + * @param dp + * @param usereal + * @param buysell + * @returns string + */ + const calcTarget = (top:number, middle:number, dp:number, usereal: boolean = false, buysell: 'buy'|'sell' = 'buy'): string => { + let multiplier = Math.pow(10, dp - 1), value: string + if (buysell === 'buy') + value = usereal ? ((top - middle)*multiplier).toFixed(2) : (top - middle).toFixed(dp) + else + value = usereal ? ((middle - top)*multiplier).toFixed(2) : (middle - top).toFixed(dp) + return value + } + + /** + * Calculate the distance between Entry Point and Stoploss or Takeprofit + * + * @param middle the base line you want to calculate from + * @param bottom the target line to calculate to + * @param dp number of decimal places round up to + * @param usereal should result be in pips or points, true will return pips + * @param buysell are we calculating for a buy trade or a sell one + * @returns return a string of the difference in pips or points + */ + const calcStopOrTarget = (middle:number, bottom:number, dp:number, usereal: boolean = false, buysell: 'buy'|'sell' = 'buy'): string => { + let multiplier = Math.pow(10, dp -1), value: string + if (buysell === 'sell') + value = usereal ? ((middle - bottom)*multiplier).toFixed(2) : (middle - bottom).toFixed(dp) + else + value = usereal ? ((bottom - middle)*multiplier).toFixed(2) : (bottom - middle).toFixed(dp) + + return value + } + + /** + * Calculate the profit or loss of a trade in pips or points + * + * @param middle the entry point of the trade + * @param dp number of decimal places to round up to + * @param usereal should result be in pips or points, true will return pips + * @param buysell are we calculating for a buy trade or a sell one + * @returns return a string of profit or loss in pips or points + */ + const calcPL = (middle:number, dp:number, usereal: boolean = false, buysell: 'buy'|'sell' = 'buy'): string => { + let multiplier = dp = 3 ? 10 : Math.pow(10, dp -1), value: string + if (currenttick()) { + if (buysell === 'buy') + value = usereal ? ((currenttick()!.close-middle) * multiplier).toFixed(2) : (currenttick()!.close-middle).toFixed(dp) + else + value = usereal ? ((middle-currenttick()!.close) * multiplier).toFixed(2) : (middle-currenttick()!.close).toFixed(dp) + } else { + value = '' + } + + return value + } + + const triggerPending = (overlay: Overlay, action: OrderType) => { + const doJob = async () => { + let id = overlay.id + let order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null + if (order) { + order.action = action + instanceapi()?.removeOverlay({ + id, + groupId: overlay.groupId, + name: overlay.name + }) + const updatedorder = await ordercontr()?.modifyOrder({ + id: order.orderId, + action: order.action, + entrypoint: order.entryPoint + })! + if (updatedorder) { + const orderlist = orderList().map(order => (order.orderId === updatedorder.orderId ? updatedorder : order)) + setOrderList(orderlist) + drawOrder(updatedorder) + } + } + } + doJob() + } + + const updateOrder = (order: OrderModifyInfo) => { + const doJob = async () => { + await ordercontr()?.modifyOrder(order) + } + doJob() + } + + const removeStopOrTP = (overlay: Overlay, removal: 'sl'|'tp') => { + const doJob = async () => { + let id = overlay.id + let order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null + if (order) { + order = await ordercontr()?.unsetSlOrTP(order.orderId, removal)! + if (order) { + instanceapi()?.removeOverlay({ + id, + groupId: overlay.groupId, + name: overlay.name + }) + const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) + setOrderList(orderlist) + drawOrder(order) + } + } + } + doJob() + } + + const closeOrder = (overlay: Overlay, type: ExitType): void => { + const doJob = async () => { + let id = overlay.id + let order: OrderInfo|null + console.log(`got to close order: ${id}, exittype: ${type}`) + if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found + instanceapi()?.removeOverlay({ //remove the overlay first to prevent flooding this backend with api calls + id: overlay.id, + groupId: overlay.groupId, + name: overlay.name + }) + const updatedorder = await ordercontr()?.modifyOrder({ + id: order.orderId, + exitpoint: currenttick()?.close, + exittype: type, + pips: type == 'cancel' ? undefined : order.pips, //in a real application this should be calculated on backend + pl: type == 'cancel' ? undefined : order.pl //in a real application this should be calculated on backend + })! + console.log(updatedorder) + + if (updatedorder) { + // const session = chartsession() + // session!.current_bal = +session?.current_bal! + +updatedorder.pl! + // setChartsession(session) + const orderlist = orderList().map(orda => (orda.orderId === updatedorder?.orderId ? updatedorder : orda)) + setOrderList(orderlist) + } else { + drawOrder(order) // draw the order overlay back cose we couldn't close it + } + } + } + doJob() + } + + const updatePipsAndPL = (overlay: Overlay, text:any) => { + let id = overlay.id + let order: OrderInfo|null + if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found + order.pips = parseFloat(text) + order.pl = order.pips * order.lotSize * symbol()?.dollarPerPip! + const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) + setOrderList(orderlist) + } + } + + const updateStopLossAndReturnValue = (event: OverlayEvent, points:Partial|Partial[]|undefined) => { + let id = event.overlay.id + let order: OrderInfo|null + if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found + order!.stopLoss = parseFloat( (points as Partial[])[0].value?.toFixed(instanceapi()?.getPriceVolumePrecision().price)!) + const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) + setOrderList(orderlist) + return order?.stopLoss + } + } + + const updateEntryPointAndReturnValue = (event:OverlayEvent, points:Partial|Partial[]|undefined) => { + let id = event.overlay.id + let order: OrderInfo|null + if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found + order!.entryPoint = parseFloat( (points as Partial[])[0].value?.toFixed(instanceapi()?.getPriceVolumePrecision().price)!) + const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) + setOrderList(orderlist) + return order?.entryPoint + } + } + + const updateTakeProfitAndReturnValue = (event:OverlayEvent, points:Partial|Partial[]|undefined) => { + let id = event.overlay.id + let order: OrderInfo|null + if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found + order!.takeProfit = parseFloat( (points as Partial[])[0].value?.toFixed(instanceapi()?.getPriceVolumePrecision().price)!) + const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) + setOrderList(orderlist) + return order?.takeProfit + } + } + + const updatePositionOrder = (event:OverlayEvent) => { + let id = event.overlay.id + let order: OrderInfo|null + if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found + useOrder().updateOrder({ + id: order.orderId, + stoploss: order.stopLoss, + entrypoint: order.entryPoint, + takeprofit: order.takeProfit + }) + } + } + + return { onOrderPlaced, calcTarget, calcStopOrTarget, calcPL, triggerPending, updateOrder, closeOrder, removeStopOrTP, + updatePipsAndPL, updateStopLossAndReturnValue, updateEntryPointAndReturnValue, updateTakeProfitAndReturnValue, + updatePositionOrder + } +}; + +export const drawOrder = (order: OrderInfo|null) => { + if (!order) + return + let overlay = instanceapi()?.getOverlayById(`orderline_${order!.orderId}`) + if(overlay) { + instanceapi()?.removeOverlay({ //remove the overlay first to prevent flooding this backend with api calls + id: overlay.id, + groupId: overlay.groupId, + name: overlay.name + }) + } + let name = '' + let lock = false; + order!.entryPoint = order?.entryPoint! - 0.00001+0.00001 //for some reason adding and subtracting the same value stop the overlay from vanishing when dragged + order!.stopLoss = order?.stopLoss! - 0.00001+0.00001 + order!.takeProfit = order?.takeProfit! - 0.00001+0.00001 + let points = [ + { timestamp: Date.parse(order?.entryTime!), value: order?.entryPoint } + ] + switch (order?.action) { + case 'buy': + if (!order?.stopLoss && !order?.takeProfit) { + name = 'buyLine' + lock = true + } else if (order.stopLoss && !order.takeProfit) { + name = 'buyLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } else if (!order.stopLoss && order.takeProfit) { + name = 'buyProfitLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + } else if (order.stopLoss && order.takeProfit) { + name = 'buyProfitLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } + break + case 'buystop': + if (!order?.stopLoss && !order?.takeProfit) { + name = 'buystopLine' + } else if (order.stopLoss && !order.takeProfit) { + name = 'buystopLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } else if (!order.stopLoss && order.takeProfit) { + name = 'buystopProfitLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + } else if (order.stopLoss && order.takeProfit) { + name = 'buystopProfitLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } + break + case 'buylimit': + if (!order?.stopLoss && !order?.takeProfit) { + name = 'buyLimitLine' + } else if (order.stopLoss && !order.takeProfit) { + name = 'buyLimitLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } else if (!order.stopLoss && order.takeProfit) { + name = 'buyLimitProfitLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + } else if (order.stopLoss && order.takeProfit) { + name = 'buyLimitProfitLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } + break + case 'sell': + if (!order?.stopLoss && !order?.takeProfit) { + name = 'sellLine' + lock = true + } else if (order.stopLoss && !order.takeProfit) { + name = 'sellLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } else if (!order.stopLoss && order.takeProfit) { + name = 'sellProfitLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + } else if (order.stopLoss && order.takeProfit) { + name = 'sellProfitLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } + break + case 'sellstop': + if (!order?.stopLoss && !order?.takeProfit) { + name = 'sellstopLine' + } else if (order.stopLoss && !order.takeProfit) { + name = 'sellstopLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } else if (!order.stopLoss && order.takeProfit) { + name = 'sellstopProfitLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + } else if (order.stopLoss && order.takeProfit) { + name = 'sellstopProfitLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } + break + case 'selllimit': + if (!order?.stopLoss && !order?.takeProfit) { + name = 'sellLimitLine' + } else if (order.stopLoss && !order.takeProfit) { + name = 'sellLimitLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } else if (!order.stopLoss && order.takeProfit) { + name = 'sellLimitProfitLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + } else if (order.stopLoss && order.takeProfit) { + name = 'sellLimitProfitLossLine' + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) + points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) + } + break + default: + break + } + instanceapi()?.createOverlay({ + name, + id: `orderline_${order?.orderId}`, + groupId: 'orderLine', + points, + lock, + }) +}; \ No newline at end of file diff --git a/src/store/tickStore.ts b/src/store/tickStore.ts new file mode 100644 index 00000000..ad5e0026 --- /dev/null +++ b/src/store/tickStore.ts @@ -0,0 +1,5 @@ +import { KLineData, Nullable } from 'klinecharts'; +import { createSignal } from 'solid-js'; + +export const [currenttick, setCurrentTick] = createSignal>(null); +export const [tickTimestamp, setTickTimestamp] = createSignal() From 6db6912780d99a7ce9df298402715e4ae143bd99 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Tue, 21 Oct 2025 11:49:57 +0100 Subject: [PATCH 09/20] added basic buy order line --- src/extension/position/buy/buyLine.ts | 80 +++++++++++++++++++++++++++ src/helpers.ts | 25 +++++++++ 2 files changed, 105 insertions(+) create mode 100644 src/extension/position/buy/buyLine.ts create mode 100644 src/helpers.ts diff --git a/src/extension/position/buy/buyLine.ts b/src/extension/position/buy/buyLine.ts new file mode 100644 index 00000000..cd5b225c --- /dev/null +++ b/src/extension/position/buy/buyLine.ts @@ -0,0 +1,80 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OverlayTemplate,utils } from 'klinecharts' +import { useOrder } from '../../../store/positionStore' +import { buyStyle } from '../../../store/overlayStyle/positionStyleStore' +import { getPrecision } from '../../../helpers' +// import { useOverlaySettings } from '../../../store/overlaySettingStore' + +const buyLine: OverlayTemplate = { + name: 'buyLine', + totalStep: 2, + needDefaultPointFigure: true, + needDefaultXAxisFigure: true, + needDefaultYAxisFigure: true, + createPointFigures: ({chart, yAxis, overlay, coordinates, bounding }) => { + const precision = getPrecision(chart, overlay, yAxis) + let text = useOrder().calcPL(overlay.points[0].value!, precision.price, true) + useOrder().updatePipsAndPL(overlay, text) + return [ + { + type: 'line', + attrs: { coordinates: [{ x: 0, y: coordinates[0].y }, { x: bounding.width, y: coordinates[0].y }] }, + styles: buyStyle().lineStyle, + ignoreEvent: true + }, + { + type: 'text', + attrs: { x: bounding.width, y: coordinates[0].y, text: text.length ? `buy | ${text}` : 'buy', align: 'right', baseline: 'middle' }, + styles: buyStyle().labelStyle + } + ] + }, + createYAxisFigures: ({ chart, overlay, coordinates, bounding, yAxis }) => { + const precision = getPrecision(chart, overlay, yAxis) + console.info('overlay extend dat is: ', overlay.extendData) + const isFromZero = yAxis?.isFromZero() ?? false + let textAlign: CanvasTextAlign + let x: number + if (isFromZero) { + textAlign = 'left' + x = 0 + } else { + textAlign = 'right' + x = bounding.width + } + let text: string|null|undefined + if (utils.isValid(overlay.extendData)) { + if (!utils.isFunction(overlay.extendData)) { + text = (overlay.extendData ?? '') as string + } else { + text = overlay.extendData(overlay) as string + } + } + if (!utils.isValid(text) && overlay.points[0].value !== undefined) { + text = utils.formatPrecision(overlay.points[0].value, precision.price) + } + // let width = utils.calcTextWidth((text as string)) + // const height = width/(text as string).length * 3 + // width = width + height * 2 + return { type: 'text', attrs: { x, y: coordinates[0].y, text: text ?? '', align: textAlign, baseline: 'middle' }, styles: buyStyle().labelStyle } + }, + onRightClick: (event): boolean => { + // useOverlaySettings().singlePopup(event, 'buy') + return true + } +} + +export default buyLine \ No newline at end of file diff --git a/src/helpers.ts b/src/helpers.ts new file mode 100644 index 00000000..fabc88c7 --- /dev/null +++ b/src/helpers.ts @@ -0,0 +1,25 @@ +import { Chart, Nullable, Overlay, YAxis } from "klinecharts" + +export const getScreenSize = () => { + return {x: window.innerWidth, y: window.innerHeight} +} + +export const getPrecision = (chart: Chart, overlay: Overlay, yAxis: Nullable):{ price: number, volume: number } => { + const precision = { + price: 0, + volume: 0 + } + + const symbol = chart.getSymbol() + if ((yAxis?.isInCandle() ?? true) && symbol) { + precision.price = symbol.pricePrecision + precision.volume = symbol.volumePrecision + } else { + const indicators = chart.getIndicators({ paneId: overlay.paneId }) + indicators.forEach(indicator => { + precision.price = Math.max(precision.price, indicator.precision) + }) + } + + return precision +} \ No newline at end of file From d90ac77a654b2771dad1dc960836cf1f28514169 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Tue, 21 Oct 2025 11:50:34 +0100 Subject: [PATCH 10/20] added types necessary for position api --- src/ChartProComponent.tsx | 120 +++++++++++++++++--------------------- src/types.ts | 115 +++++++++++++++++++++++++++++++++++- 2 files changed, 168 insertions(+), 67 deletions(-) diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index 8644d943..cb50a865 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -16,11 +16,8 @@ import { createSignal, createEffect, onMount, Show, onCleanup, startTransition, import { init, dispose, utils, Nullable, Chart, OverlayMode, Styles, - ActionType, PaneOptions, Indicator, DomPosition, FormatDateType, - FormatDateParams, - TooltipFeatureStyle, - IndicatorTooltipData, - FeatureType + PaneOptions, Indicator, FormatDateParams, TooltipFeatureStyle, + IndicatorTooltipData } from 'klinecharts' import lodashSet from 'lodash/set' @@ -37,7 +34,6 @@ import { translateTimezone } from './widget/timezone-modal/data' import { SymbolInfo, Period, ChartProOptions, ChartPro } from './types' import ChartDataLoader from './DataLoader' -import { filter, set } from 'lodash' export interface ChartProComponentProps extends Required> { ref: (chart: ChartPro) => void @@ -59,15 +55,11 @@ function createIndicator (widget: Chart, indicatorName: string, isStack?: boolea const indiStyles = param.chart.getStyles().indicator const features = indiStyles.tooltip.features const icons: TooltipFeatureStyle[] = [] - if (param.indicator.visible) { - icons.push(features[1]) - icons.push(features[2]) - icons.push(features[3]) - } else { - icons.push(features[0]) - icons.push(features[2]) - icons.push(features[3]) - } + + icons.push(param.indicator.visible ? features[1] : features[0]) + icons.push(features[2]) + icons.push(features[3]) + return { name: `${indicatorName}_${indi}`, calcParamsText: indicatorName, @@ -79,18 +71,16 @@ function createIndicator (widget: Chart, indicatorName: string, isStack?: boolea return indi } -export const [ widget, setWidget ] = createSignal>(null) export const [loadingVisible, setLoadingVisible] = createSignal(false) export const [symbol, setSymbol] = createSignal>(null) export const [period, setPeriod] = createSignal>(null) +export const [instanceapi, setInstanceapi] = createSignal>(null) const ChartProComponent: Component = props => { let widgetRef: HTMLDivElement | undefined = undefined let priceUnitDom: HTMLElement - let loading = false - const [theme, setTheme] = createSignal(props.theme) const [styles, setStyles] = createSignal(props.styles) const [locale, setLocale] = createSignal(props.locale) @@ -121,7 +111,7 @@ const ChartProComponent: Component = props => { setTheme, getTheme: () => theme(), setStyles, - getStyles: () => widget()!.getStyles(), + getStyles: () => instanceapi()!.getStyles(), setLocale, getLocale: () => locale(), setTimezone: (timezone: string) => { setTimezone({ key: timezone, text: translateTimezone(props.timezone, locale()) }) }, @@ -130,18 +120,18 @@ const ChartProComponent: Component = props => { getSymbol: () => symbol()!, setPeriod, getPeriod: () => period()!, - getInstanceApi: () => widget(), - resize: () => widget()?.resize(), + getInstanceApi: () => instanceapi(), + resize: () => instanceapi()?.resize(), dispose: () => {} }) const documentResize = () => { - widget()?.resize() + instanceapi()?.resize() } onMount(() => { window.addEventListener('resize', documentResize) - setWidget(init(widgetRef!, { + setInstanceapi(init(widgetRef!, { formatter: { formatDate: (params: FormatDateParams) => { const p = period()! @@ -178,9 +168,9 @@ const ChartProComponent: Component = props => { } })) - if (widget()) { + if (instanceapi()) { console.info('ChartPro widget initialized') - const watermarkContainer = widget()!.getDom('candle_pane', 'main') + const watermarkContainer = instanceapi()!.getDom('candle_pane', 'main') if (watermarkContainer) { let watermark = document.createElement('div') watermark.className = 'klinecharts-pro-watermark' @@ -193,30 +183,30 @@ const ChartProComponent: Component = props => { watermarkContainer.appendChild(watermark) } - const priceUnitContainer = widget()!.getDom('candle_pane', 'yAxis') + const priceUnitContainer = instanceapi()!.getDom('candle_pane', 'yAxis') priceUnitDom = document.createElement('span') priceUnitDom.className = 'klinecharts-pro-price-unit' priceUnitContainer?.appendChild(priceUnitDom) - widget()?.subscribeAction('onCrosshairFeatureClick', (data) => { + instanceapi()?.subscribeAction('onCrosshairFeatureClick', (data) => { console.info('onCrosshairFeatureClick', data) }) - widget()?.subscribeAction('onIndicatorTooltipFeatureClick', (data) => { + instanceapi()?.subscribeAction('onIndicatorTooltipFeatureClick', (data) => { console.info('onIndicatorTooltipFeatureClick', data) const _data = data as { paneId: string, feature: TooltipFeatureStyle, indicator: Indicator } // if (_data.indicatorName) { switch (_data.feature.id) { case 'visible': { - widget()?.overrideIndicator({ name: _data.indicator.name, visible: true, paneId: _data.paneId }) + instanceapi()?.overrideIndicator({ name: _data.indicator.name, visible: true, paneId: _data.paneId }) break } case 'invisible': { - widget()?.overrideIndicator({ name: _data.indicator.name, visible: false, paneId: _data.paneId }) + instanceapi()?.overrideIndicator({ name: _data.indicator.name, visible: false, paneId: _data.paneId }) break } case 'setting': { - const indicator = widget()?.getIndicators({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }).at(0) + const indicator = instanceapi()?.getIndicators({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }).at(0) if (!indicator) return setIndicatorSettingModalParams({ visible: true, indicatorName: _data.indicator.name, paneId: _data.paneId, calcParams: indicator.calcParams @@ -226,12 +216,12 @@ const ChartProComponent: Component = props => { case 'close': { if (_data.paneId === 'candle_pane') { const newMainIndicators = [...mainIndicators()] - widget()?.removeIndicator({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }) + instanceapi()?.removeIndicator({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }) newMainIndicators.splice(newMainIndicators.indexOf(_data.indicator.name), 1) setMainIndicators(newMainIndicators) } else { const newIndicators = { ...subIndicators() } - widget()?.removeIndicator({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }) + instanceapi()?.removeIndicator({ paneId: _data.paneId, name: _data.indicator.name, id: _data.indicator.id }) // @ts-expect-error delete newIndicators[_data.indicator.name] setSubIndicators(newIndicators) @@ -241,7 +231,7 @@ const ChartProComponent: Component = props => { // } }) - widget()?.subscribeAction('onCandleTooltipFeatureClick', (data) => { + instanceapi()?.subscribeAction('onCandleTooltipFeatureClick', (data) => { console.info('onCandleTooltipFeatureClick', data) }) @@ -252,12 +242,12 @@ const ChartProComponent: Component = props => { } else { priceUnitDom.style.display = 'none' } - widget()?.setSymbol({ ticker: s!.ticker, pricePrecision: s?.pricePrecision ?? 2, volumePrecision: s?.volumePrecision ?? 0 }) - widget()?.setPeriod(period()!) - widget()?.setDataLoader(props.dataloader) + instanceapi()?.setSymbol({ ticker: s!.ticker, pricePrecision: s?.pricePrecision ?? 2, volumePrecision: s?.volumePrecision ?? 0 }) + instanceapi()?.setPeriod(period()!) + instanceapi()?.setDataLoader(props.dataloader) } - const w = widget() + const w = instanceapi() if (w) { mainIndicators().forEach(indicator => { @@ -291,11 +281,11 @@ const ChartProComponent: Component = props => { if (prev?.period.span !== p!.span && prev?.period.type !== p!.type) { console.info('period changed: set period', p) - widget()?.setPeriod(p!) + instanceapi()?.setPeriod(p!) } if (prev?.symbol?.ticker !== s!.ticker) console.info('ticker changed: set symbol', s) - widget()?.setSymbol({ + instanceapi()?.setSymbol({ ticker: s!.ticker, pricePrecision: s!.pricePrecision, volumePrecision: s!.volumePrecision, @@ -314,9 +304,9 @@ const ChartProComponent: Component = props => { createEffect(() => { const t = theme() - widget()?.setStyles(t) + instanceapi()?.setStyles(t) const color = t === 'dark' ? '#929AA5' : '#76808F' - widget()?.setStyles({ + instanceapi()?.setStyles({ indicator: { tooltip: { features: [ @@ -324,7 +314,7 @@ const ChartProComponent: Component = props => { id: 'visible', position: 'middle', marginLeft: 8, - marginTop: 7, + marginTop: 1, marginRight: 0, marginBottom: 0, paddingLeft: 0, @@ -346,7 +336,7 @@ const ChartProComponent: Component = props => { id: 'invisible', position: 'middle', marginLeft: 8, - marginTop: 7, + marginTop: 1, marginRight: 0, marginBottom: 0, paddingLeft: 0, @@ -368,7 +358,7 @@ const ChartProComponent: Component = props => { id: 'setting', position: 'middle', marginLeft: 6, - marginTop: 7, + marginTop: 1, marginBottom: 0, marginRight: 0, paddingLeft: 0, @@ -390,7 +380,7 @@ const ChartProComponent: Component = props => { id: 'close', position: 'middle', marginLeft: 6, - marginTop: 7, + marginTop: 1, marginRight: 0, marginBottom: 0, paddingLeft: 0, @@ -415,17 +405,17 @@ const ChartProComponent: Component = props => { }) createEffect(() => { - widget()?.setLocale(locale()) + instanceapi()?.setLocale(locale()) }) createEffect(() => { - widget()?.setTimezone(timezone().key) + instanceapi()?.setTimezone(timezone().key) }) createEffect(() => { if (styles()) { - widget()?.setStyles(styles()) - setWidgetDefaultStyles(lodashClone(widget()!.getStyles())) + instanceapi()?.setStyles(styles()) + setWidgetDefaultStyles(lodashClone(instanceapi()!.getStyles())) } }) @@ -448,10 +438,10 @@ const ChartProComponent: Component = props => { onMainIndicatorChange={data => { const newMainIndicators = [...mainIndicators()] if (data.added) { - createIndicator(widget()!, data.name, true, { id: 'candle_pane' }) + createIndicator(instanceapi()!, data.name, true, { id: 'candle_pane' }) newMainIndicators.push(data.name) } else { - widget()?.removeIndicator({name: data.name, paneId: 'candle_pane', id: data.id ?? undefined}) + instanceapi()?.removeIndicator({name: data.name, paneId: 'candle_pane', id: data.id ?? undefined}) newMainIndicators.splice(newMainIndicators.indexOf(data.name), 1) } setMainIndicators(newMainIndicators) @@ -460,14 +450,14 @@ const ChartProComponent: Component = props => { console.info('onSubIndicatorChange', data) const newSubIndicators = { ...subIndicators() } if (data.added) { - const id = createIndicator(widget()!, data.name) + const id = createIndicator(instanceapi()!, data.name) if (id) { // @ts-expect-error newSubIndicators[data.name] = id } } else { if (data.id) { - widget()?.removeIndicator({name: data.name, id: data.id}) + instanceapi()?.removeIndicator({name: data.name, id: data.id}) // @ts-expect-error delete newSubIndicators[data.name] } @@ -486,10 +476,10 @@ const ChartProComponent: Component = props => { { setSettingModalVisible(false) }} onChange={style => { - widget()?.setStyles(style) + instanceapi()?.setStyles(style) }} onRestoreDefault={(options: SelectDataSourceItem[]) => { const style = {} @@ -497,7 +487,7 @@ const ChartProComponent: Component = props => { const key = option.key lodashSet(style, key, utils.formatValue(widgetDefaultStyles(), key)) }) - widget()?.setStyles(style) + instanceapi()?.setStyles(style) }} /> @@ -515,7 +505,7 @@ const ChartProComponent: Component = props => { onClose={() => { setIndicatorSettingModalParams({ visible: false, indicatorName: '', paneId: '', calcParams: [] }) }} onConfirm={(params)=> { const modalParams = indicatorSettingModalParams() - widget()?.overrideIndicator({ name: modalParams.indicatorName, calcParams: params, paneId: modalParams.paneId }) + instanceapi()?.overrideIndicator({ name: modalParams.indicatorName, calcParams: params, paneId: modalParams.paneId }) }} /> @@ -528,7 +518,7 @@ const ChartProComponent: Component = props => { onMenuClick={async () => { try { await startTransition(() => setDrawingBarVisible(!drawingBarVisible())) - widget()?.resize() + instanceapi()?.resize() } catch (e) {} }} onSymbolClick={() => { setSymbolSearchModalVisible(!symbolSearchModalVisible()) }} @@ -538,7 +528,7 @@ const ChartProComponent: Component = props => { onSettingClick={() => { setSettingModalVisible((visible => !visible)) }} onScreenshotClick={() => { if (widget) { - const url = widget()!.getConvertPictureUrl(true, 'jpeg', props.theme === 'dark' ? '#151517' : '#ffffff') + const url = instanceapi()!.getConvertPictureUrl(true, 'jpeg', props.theme === 'dark' ? '#151517' : '#ffffff') setScreenshotUrl(url) } }} @@ -551,11 +541,11 @@ const ChartProComponent: Component = props => { { widget()?.createOverlay(overlay) }} - onModeChange={mode => { widget()?.overrideOverlay({ mode: mode as OverlayMode }) }} - onLockChange={lock => { widget()?.overrideOverlay({ lock }) }} - onVisibleChange={visible => { widget()?.overrideOverlay({ visible }) }} - onRemoveClick={(groupId) => { widget()?.removeOverlay({ groupId }) }}/> + onDrawingItemClick={overlay => { instanceapi()?.createOverlay(overlay) }} + onModeChange={mode => { instanceapi()?.overrideOverlay({ mode: mode as OverlayMode }) }} + onLockChange={lock => { instanceapi()?.overrideOverlay({ lock }) }} + onVisibleChange={visible => { instanceapi()?.overrideOverlay({ visible }) }} + onRemoveClick={(groupId) => { instanceapi()?.removeOverlay({ groupId }) }}/>
    void +export type OrderPlacedCallback = (data: OrderInfo|null) => void //this should be called when a user has successfully placed an order from consumer project side export interface SymbolInfo { ticker: string @@ -23,15 +30,105 @@ export interface SymbolInfo { pricePrecision?: number volumePrecision?: number priceCurrency?: string + dollarPerPip?: number type?: string logo?: string } +export interface OrderInfo { + orderId: number + action: OrderType + entryPoint: number + exitPoint?: number + stopLoss?: number + takeProfit?: number + lotSize: number + pips?: number + pl?: number + entryTime?: string + exitTime?: string + exitType?: ExitType + partials?: string + sessionId?: number +} + +export interface OrderModifyInfo { + id: number + action?: OrderType + entrypoint?: number + exitpoint?: number + stoploss?: number + takeprofit?: number + lotsize?: number + pips?: number + pl?: number + exittime?: string + exittype?: ExitType + partials?: string +} + export interface Period extends DefaultPeriod { text: string } -export type DatafeedSubscribeCallback = (data: KLineData) => void +type IndicatorsType = { + value?: IndicatorCreate, + isStack?: boolean, + paneOptions?: PaneOptions +} + +type OverlaysType = { + value?: OverlayCreate, + paneId: string +} + +type FiguresType = { + value?: string|FigureCreate, + ctx: CanvasRenderingContext2D +} + +type OrderStyleType = { + lineStyle?: { + style?: string, + size?: number, + color?: string, + dashedValue?: number[] + }, + labelStyle?: { + style?: string, + size?: number, + family?: string, + weight?: string, + paddingLeft?: number, + paddingRight?: number, + paddingBottom?: number, + paddingTop?: number, + borderStyle?: string, + borderSize?: number, + color?: string, + borderColor?: string, + backgroundColor?: string + } +} + +export type OrderStylesType = { + buyStyle?: OrderStyleType, + buyLimitStyle?: OrderStyleType, + buyStopStyle?: OrderStyleType, + sellStyle?: OrderStyleType, + sellLimitStyle?: OrderStyleType, + sellStopStyle?: OrderStyleType, + stopLossStyle?: OrderStyleType, + takeProfitStyle?: OrderStyleType +} + +export interface ChartObjType { + styleObj?: DeepPartial + overlays?: OverlaysType[] + figures?: FiguresType[] + indicators?: IndicatorsType[] + orderStyles?: OrderStylesType +} export interface Datafeed { searchSymbols (search?: string): Promise @@ -45,6 +142,16 @@ export interface ChartDataLoaderType extends DataLoader { loading: boolean } +export interface OrderResource { + retrieveOrder (order_id: number): Promise + retrieveOrders (action?: OrderType, session_id?: number|string): Promise + openOrder (action: OrderType, lot_size: number, entry_price: number, stop_loss?: number, take_profit?: number): Promise + closeOrder (order_id: number, lotsize?: number): Promise + modifyOrder (order: OrderModifyInfo): Promise + unsetSlOrTP (order_id: string|number, slortp: 'sl'|'tp'): Promise + launchOrderModal (type: OrderModalType, callback: OrderPlacedCallback, order?: OrderModifyInfo): void +} + export interface ChartProOptions { container: string | HTMLElement styles?: DeepPartial @@ -52,6 +159,7 @@ export interface ChartProOptions { theme?: string locale?: string drawingBarVisible?: boolean + orderPanelVisible?: boolean symbol: SymbolInfo period: Period periods?: Period[] @@ -59,6 +167,9 @@ export interface ChartProOptions { mainIndicators?: string[] subIndicators?: string[] datafeed: Datafeed + dataTimestamp: number + orderController: OrderResource + rootElementId: string } export interface ChartPro { From 79e28ffd6ba9d4e71c827baafa3df8a5b12dd8e0 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Tue, 21 Oct 2025 12:35:45 +0100 Subject: [PATCH 11/20] fix imports and outdated klinecharts api calls --- src/ChartProComponent.tsx | 2 +- src/store/positionStore.ts | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index cb50a865..70325239 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -527,7 +527,7 @@ const ChartProComponent: Component = props => { onTimezoneClick={() => { setTimezoneModalVisible((visible => !visible)) }} onSettingClick={() => { setSettingModalVisible((visible => !visible)) }} onScreenshotClick={() => { - if (widget) { + if (instanceapi()) { const url = instanceapi()!.getConvertPictureUrl(true, 'jpeg', props.theme === 'dark' ? '#151517' : '#ffffff') setScreenshotUrl(url) } diff --git a/src/store/positionStore.ts b/src/store/positionStore.ts index c288788c..1007fc76 100644 --- a/src/store/positionStore.ts +++ b/src/store/positionStore.ts @@ -1,8 +1,9 @@ -import { Chart, Nullable, Overlay, OverlayEvent, Point } from 'klinecharts'; +import { Chart, Nullable, Overlay, OverlayEvent, Point, YAxis } from 'klinecharts'; import { ExitType, OrderInfo, OrderModifyInfo, OrderResource, OrderType } from '../types'; import { createSignal } from 'solid-js'; import { currenttick } from './tickStore'; import { instanceapi, symbol } from '../ChartProComponent'; +import { getPrecision } from '../helpers'; // import { setOrderModalVisible } from './chartStateStore'; // import { syntheticPausePlay } from './keyEventStore'; @@ -74,7 +75,7 @@ export const useOrder = () => { * @returns return a string of profit or loss in pips or points */ const calcPL = (middle:number, dp:number, usereal: boolean = false, buysell: 'buy'|'sell' = 'buy'): string => { - let multiplier = dp = 3 ? 10 : Math.pow(10, dp -1), value: string + let multiplier = dp == 3 ? 10 : Math.pow(10, dp -1), value: string if (currenttick()) { if (buysell === 'buy') value = usereal ? ((currenttick()!.close-middle) * multiplier).toFixed(2) : (currenttick()!.close-middle).toFixed(dp) @@ -186,41 +187,41 @@ export const useOrder = () => { } } - const updateStopLossAndReturnValue = (event: OverlayEvent, points:Partial|Partial[]|undefined) => { - let id = event.overlay.id + const updateStopLossAndReturnValue = (overlay: Overlay, chart: Chart, yAxis: Nullable) => { + let id = overlay.id let order: OrderInfo|null if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - order!.stopLoss = parseFloat( (points as Partial[])[0].value?.toFixed(instanceapi()?.getPriceVolumePrecision().price)!) + order!.stopLoss = parseFloat( overlay.points[0].value?.toFixed(getPrecision(chart, overlay, yAxis).price)!) const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) setOrderList(orderlist) return order?.stopLoss } } - const updateEntryPointAndReturnValue = (event:OverlayEvent, points:Partial|Partial[]|undefined) => { - let id = event.overlay.id + const updateEntryPointAndReturnValue = (overlay: Overlay, chart: Chart, yAxis: Nullable) => { + let id = overlay.id let order: OrderInfo|null if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - order!.entryPoint = parseFloat( (points as Partial[])[0].value?.toFixed(instanceapi()?.getPriceVolumePrecision().price)!) + order!.entryPoint = parseFloat( overlay.points[0].value?.toFixed(getPrecision(chart, overlay, yAxis).price)!) const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) setOrderList(orderlist) return order?.entryPoint } } - const updateTakeProfitAndReturnValue = (event:OverlayEvent, points:Partial|Partial[]|undefined) => { - let id = event.overlay.id + const updateTakeProfitAndReturnValue = (overlay: Overlay, chart: Chart, yAxis: Nullable) => { + let id = overlay.id let order: OrderInfo|null if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - order!.takeProfit = parseFloat( (points as Partial[])[0].value?.toFixed(instanceapi()?.getPriceVolumePrecision().price)!) + order!.takeProfit = parseFloat( overlay.points[0].value?.toFixed(getPrecision(chart, overlay, yAxis).price)!) const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) setOrderList(orderlist) return order?.takeProfit } } - const updatePositionOrder = (event:OverlayEvent) => { - let id = event.overlay.id + const updatePositionOrder = (overlay: Overlay) => { + let id = overlay.id let order: OrderInfo|null if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found useOrder().updateOrder({ @@ -241,7 +242,7 @@ export const useOrder = () => { export const drawOrder = (order: OrderInfo|null) => { if (!order) return - let overlay = instanceapi()?.getOverlayById(`orderline_${order!.orderId}`) + let overlay = instanceapi()?.getOverlays({ id: `orderline_${order!.orderId}`, 'paneId': 'candle_pane' }).at(0) if(overlay) { instanceapi()?.removeOverlay({ //remove the overlay first to prevent flooding this backend with api calls id: overlay.id, From 2551bd021d81c65ecdf1978f15cd9a85a596fda5 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Tue, 21 Oct 2025 21:42:01 +0100 Subject: [PATCH 12/20] implement order controllers --- src/DefaultOrderController.ts | 159 ++++++++++++++++++++++++++++++++++ src/DummyOrderController.ts | 137 +++++++++++++++++++++++++++++ src/KLineChartPro.tsx | 10 ++- src/index.ts | 13 ++- src/types.ts | 8 +- 5 files changed, 317 insertions(+), 10 deletions(-) create mode 100644 src/DefaultOrderController.ts create mode 100644 src/DummyOrderController.ts diff --git a/src/DefaultOrderController.ts b/src/DefaultOrderController.ts new file mode 100644 index 00000000..445bc783 --- /dev/null +++ b/src/DefaultOrderController.ts @@ -0,0 +1,159 @@ +import { OrderInfo, OrderModalType, OrderModifyInfo, OrderPlacedCallback, OrderResource, OrderType } from "./types"; + +type MethodType = 'POST'|'GET'|'DELETE'|'PUT' + +export default class DefaultOrderController implements OrderResource { + constructor (_apiurl: string, _apikey: string, _accound_id: number|string) { + this.apiurl = _apiurl + this.apikey = _apikey + this.testsesson_id = _accound_id + } + + private apikey: string + private apiurl: string + private testsesson_id: number|string + + async retrieveOrder(order_id: number): Promise { + const response = await this.makeFetchWithAuthAndBody('GET', `${this.apiurl}/positions/${order_id}`) + const resp = await response?.json() + return { + entryPoint: resp.data.entrypoint, + stopLoss: resp.data.stoploss, + takeProfit: resp.data.takeprofit, + lotSize: resp.data.lotsize, + pl: resp.data.pl, + accountId: resp.data.account_id, + orderId: resp.data.id, + entryTime: resp.data.entrytime, + exitTime: resp.data.exittime, + exitPoint: resp.data.exitpoint, + action: resp.data.action + } + } + + async retrieveOrders(action?: OrderType, session_id?: number): Promise { + try { + const response = await this.makeFetchWithAuthAndBody('GET', `${this.apiurl}/positions`) + const result = await response!.json() + return (result.data || []).map((data: any) => ({ + entryPoint: data.entrypoint, + stopLoss: data.stoploss, + takeProfit: data.takeprofit, + lotSize: data.lotsize, + pl: data.pl, + accountId: data.account_id, + orderId: data.id, + entryTime: data.entrytime, + exitTime: data.exittime, + exitPoint: data.exitpoint, + action: data.action + })) + } catch (err) { + return [] + } + } + + async openOrder(action: OrderType, lot_size: number, entry_price: number, stop_loss?: number, take_profit?: number): Promise { + const response = await this.makeFetchWithAuthAndBody('POST', `${this.apiurl}/positions`, { + account_id: this.testsesson_id, + action: action, + entrypoint: entry_price, + stoploss: stop_loss, + takeprofit: take_profit, + }) + + const data = await response?.json() + return { + orderId: data.id, + accountId: data.account_id, + action: data.action, + entryPoint: data.entrypoint, + exitPoint: data.exitpoint, + stopLoss: data.stoploss, + takeProfit: data.takeprofit, + lotSize: data.lotsize, + pips: data.pips, + pl: data.pl, + entryTime: data.entrytime, + exitTime: data.exittime, + exitType: data.exittype, + partials: data.partials + } + } + + async closeOrder(order_id: number, lotsize?: number): Promise { + try { + const response = await this.makeFetchWithAuthAndBody('PUT', `${this.apiurl}/positions/${order_id}`) + const data = await response?.json() + return data + } catch (err) { + return null + } + } + + async modifyOrder(order: OrderModifyInfo): Promise { + const response = await this.makeFetchWithAuthAndBody('PUT', `${this.apiurl}/positions/${order.id}`, order) + const data = await response?.json() + return { + orderId: data.id, + accountId: data.account_id, + action: data.action, + entryPoint: data.entrypoint, + exitPoint: data.exitpoint, + stopLoss: data.stoploss, + takeProfit: data.takeprofit, + lotSize: data.lotsize, + pips: data.pips, + pl: data.pl, + entryTime: data.entrytime, + exitTime: data.exittime, + exitType: data.exittype, + partials: data.partials + } + } + + async unsetSlOrTP(order_id: string|number, slortp: 'sl'|'tp'): Promise { + const response = await this.makeFetchWithAuthAndBody('PUT', `${this.apiurl}/positions/${order_id}/unset/${slortp}`) + const data = await response?.json() + return { + orderId: data.id, + accountId: data.account_id, + action: data.action, + entryPoint: data.entrypoint, + exitPoint: data.exitpoint, + stopLoss: data.stoploss, + takeProfit: data.takeprofit, + lotSize: data.lotsize, + pips: data.pips, + pl: data.pl, + entryTime: data.entrytime, + exitTime: data.exittime, + exitType: data.exittype, + partials: data.partials + } + } + + launchOrderModal(type: OrderModalType, callback: OrderPlacedCallback, order?: OrderModifyInfo): void { + return ; + } + + private async makeFetchWithAuthAndBody (method: MethodType, endpoint: string, params?: object): Promise { + const options: RequestInit = { + method: method, + credentials: "include", + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${this.apikey}` + }, + body: params ? JSON.stringify(params) : null + }; + try { + const res = await fetch(`${endpoint}`, options) + return res + } catch (err:any) { + alert(err.message ?? 'An error occured') + return null + } + } +} \ No newline at end of file diff --git a/src/DummyOrderController.ts b/src/DummyOrderController.ts new file mode 100644 index 00000000..79ea75cc --- /dev/null +++ b/src/DummyOrderController.ts @@ -0,0 +1,137 @@ +import { Nullable } from "klinecharts"; +import { OrderInfo, OrderModalType, OrderModifyInfo, OrderPlacedCallback, OrderResource, OrderType } from "./types"; + +export default class DummyOrderController implements OrderResource { + constructor (storage_name = 'dummy_order_controller') { + this.storage_name = storage_name + } + + private storage_name: string + + async retrieveOrder(order_id: number): Promise> { + return this.retrieveOrderFromLs(order_id) + } + + async retrieveOrders(action?: OrderType, account_id?: number): Promise { + let orders = this.retrieveOrdersFromLs() + + if (action) + orders = orders.filter((o, i) => o.action == action) + + if (account_id) + orders = orders.filter(o => o.accountId == account_id) + + return orders + } + + async openOrder(action: OrderType, size: number, entry: number, stop_loss?: number, take_profit?: number): Promise { + const orders = this.retrieveOrdersFromLs() + + const order = { + orderId: orders.length ? orders.at(orders.length - 1)!.orderId + 1 : 0, + action: action, + entryPoint: entry, + exitPoint: undefined, + stopLoss: stop_loss, + takeProfit: take_profit, + lotSize: size, + pips: 0, + pl: 0, + entryTime: new Date().getTime().toString(), + exitTime: undefined, + exitType: undefined, + partials: undefined + } + this.storeOrder(order, orders) + return order + } + + async closeOrder(order_id: number, lotsize?: number): Promise { + try { + const orders = this.retrieveOrdersFromLs() + const index = orders.findIndex(o => o.orderId == order_id) + if (index < 0) + return null + + const order = orders[index] + + if (!lotsize || lotsize > order.lotSize) + lotsize = order.lotSize + + order.lotSize -= lotsize + this.storeOrder(order, orders, index) + return order + } catch (err) { + return null + } + } + + async modifyOrder(_order: OrderModifyInfo): Promise { + const orders = this.retrieveOrdersFromLs() + const index = orders.findIndex(o => o.orderId == _order.id) + if (index < 0) + return null + + const order = orders[index] + //TODO: add order modify validations to prevent performing some actions on already activated order + + order.action = _order.action ?? order.action + order.entryPoint = _order.entrypoint ?? order.entryPoint + order.exitPoint = _order.exitpoint ?? order.exitPoint + order.stopLoss = _order.stoploss ?? order.stopLoss + order.takeProfit = _order.takeprofit ?? order.takeProfit + order.lotSize = _order.lotsize ?? order.lotSize + order.pips = _order.pips ?? order.pips + order.pl = _order.pl ?? order.pl + order.entryTime = ['buy', 'sell'].includes(order.action) ? new Date().getTime().toString() : order.entryTime + order.exitTime = order.exitPoint && order.exitTime ? new Date().getTime().toString() : order.exitTime + + this.storeOrder(order, orders, index) + return order + } + + async unsetSlOrTP(order_id: string|number, slortp: 'sl'|'tp'): Promise { + const orders = this.retrieveOrdersFromLs() + const index = orders.findIndex(o => o.orderId == order_id) + if (index < 0) + return null + + const order = orders[index] + if (slortp == 'sl') + order.stopLoss = undefined + else + order.takeProfit = undefined + + this.storeOrder(order, orders, index) + return order + } + + launchOrderModal(type: OrderModalType, callback: OrderPlacedCallback, order?: OrderModifyInfo): void { + return ; + } + + private retrieveOrdersFromLs (): OrderInfo[] { + return JSON.parse(localStorage.getItem(this.storage_name) ?? '[]') as OrderInfo[] + } + + private retrieveOrderFromLs(id: number): OrderInfo|null + { + return this.retrieveOrdersFromLs().find(o => o.orderId == id) ?? null + } + + private storeOrder(order: OrderInfo, orders?: OrderInfo[], index?: number) + { + if (!orders) + orders = this.retrieveOrdersFromLs() + + if (index && index > orders.length) + throw new Error('storeOrder: index cannot be greater than total order length') + + if (index) + orders[index] = order + else + orders.push(order) + + localStorage.setItem(this.storage_name, JSON.stringify(orders)) + } +} \ No newline at end of file diff --git a/src/KLineChartPro.tsx b/src/KLineChartPro.tsx index 1c7e4d25..e10dd1a0 100644 --- a/src/KLineChartPro.tsx +++ b/src/KLineChartPro.tsx @@ -16,7 +16,7 @@ import { render } from 'solid-js/web' import { utils, Nullable, DeepPartial, Styles, Chart, dispose } from 'klinecharts' -import ChartProComponent, { widget } from './ChartProComponent' +import ChartProComponent, { instanceapi } from './ChartProComponent' import { SymbolInfo, Period, ChartPro, ChartProOptions } from './types' import ChartDataLoader from './DataLoader' @@ -52,6 +52,7 @@ export default class KLineChartPro implements ChartPro { theme={options.theme ?? 'light'} locale={options.locale ?? 'zh-CN'} drawingBarVisible={options.drawingBarVisible ?? true} + orderPanelVisible={options.orderPanelVisible ?? false} symbol={options.symbol} period={options.period} periods={ @@ -71,6 +72,9 @@ export default class KLineChartPro implements ChartPro { timezone={options.timezone ?? 'Etc/UTC'} mainIndicators={options.mainIndicators ?? ['MA']} subIndicators={options.subIndicators ?? ['VOL']} + dataTimestamp={options.dataTimestamp ?? new Date().getTime()} + orderController={options.orderController} + rootElementId={options.rootElementId ?? this._container?.id ?? ''} dataloader={dataLoader}/> ), this._container @@ -138,10 +142,10 @@ export default class KLineChartPro implements ChartPro { return this._chartApi!.getPeriod() } getInstanceApi(): Nullable { - return widget() + return instanceapi() } resize(): void { - widget()!.resize() + instanceapi()!.resize() } dispose(): void { this.destroy() diff --git a/src/index.ts b/src/index.ts index bcb178a9..36597dfd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,11 +17,15 @@ import { registerOverlay } from 'klinecharts' import overlays from './extension' import DefaultDatafeed from './DefaultDatafeed' +import DefaultOrderController from './DefaultOrderController' +import DummyOrderController from './DummyOrderController' import KLineChartPro from './KLineChartPro' import { load } from './i18n' -import { Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback, ChartProOptions, ChartPro } from './types' +import { OrderType, ExitType, OrderModalType, OrderInfo, OrderModifyInfo, OrderResource, Datafeed, + SymbolInfo, Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType +} from './types' import './index.less' @@ -29,10 +33,13 @@ overlays.forEach(o => { registerOverlay(o) }) export { DefaultDatafeed, + DefaultOrderController, + DummyOrderController, KLineChartPro, load as loadLocales } export type { - Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback, ChartProOptions, ChartPro -} + OrderInfo, OrderModifyInfo, OrderType, ExitType, OrderModalType, OrderResource, Datafeed, SymbolInfo, + Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index e6cd3a11..30ccbea7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -49,7 +49,7 @@ export interface OrderInfo { exitTime?: string exitType?: ExitType partials?: string - sessionId?: number + accountId?: number } export interface OrderModifyInfo { @@ -144,7 +144,7 @@ export interface ChartDataLoaderType extends DataLoader { export interface OrderResource { retrieveOrder (order_id: number): Promise - retrieveOrders (action?: OrderType, session_id?: number|string): Promise + retrieveOrders (action?: OrderType, account_id?: number|string): Promise openOrder (action: OrderType, lot_size: number, entry_price: number, stop_loss?: number, take_profit?: number): Promise closeOrder (order_id: number, lotsize?: number): Promise modifyOrder (order: OrderModifyInfo): Promise @@ -154,6 +154,7 @@ export interface OrderResource { export interface ChartProOptions { container: string | HTMLElement + rootElementId?: string styles?: DeepPartial watermark?: string | Node theme?: string @@ -167,9 +168,8 @@ export interface ChartProOptions { mainIndicators?: string[] subIndicators?: string[] datafeed: Datafeed - dataTimestamp: number + dataTimestamp?: number orderController: OrderResource - rootElementId: string } export interface ChartPro { From 17907e144a2e71e1c95d059c77a3637361c927ac Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Thu, 23 Oct 2025 13:43:04 +0100 Subject: [PATCH 13/20] storing temporary progress --- src/extension/index.ts | 5 +- src/extension/position/buy/buyLine.ts | 80 ------------- src/extension/position/positionLine.ts | 155 +++++++++++++++++++++++++ src/store/positionStore.ts | 75 +++++++++++- 4 files changed, 233 insertions(+), 82 deletions(-) delete mode 100644 src/extension/position/buy/buyLine.ts create mode 100644 src/extension/position/positionLine.ts diff --git a/src/extension/index.ts b/src/extension/index.ts index 89a44b15..b34acd42 100644 --- a/src/extension/index.ts +++ b/src/extension/index.ts @@ -17,12 +17,15 @@ import anyWaves from './anyWaves' import abcd from './abcd' import xabcd from './xabcd' +import positionLine from './position/positionLine' + const overlays = [ arrow, circle, rect, triangle, parallelogram, fibonacciCircle, fibonacciSegment, fibonacciSpiral, fibonacciSpeedResistanceFan, fibonacciExtension, gannBox, - threeWaves, fiveWaves, eightWaves, anyWaves, abcd, xabcd + threeWaves, fiveWaves, eightWaves, anyWaves, abcd, xabcd, + positionLine(), ] export default overlays diff --git a/src/extension/position/buy/buyLine.ts b/src/extension/position/buy/buyLine.ts deleted file mode 100644 index cd5b225c..00000000 --- a/src/extension/position/buy/buyLine.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { OverlayTemplate,utils } from 'klinecharts' -import { useOrder } from '../../../store/positionStore' -import { buyStyle } from '../../../store/overlayStyle/positionStyleStore' -import { getPrecision } from '../../../helpers' -// import { useOverlaySettings } from '../../../store/overlaySettingStore' - -const buyLine: OverlayTemplate = { - name: 'buyLine', - totalStep: 2, - needDefaultPointFigure: true, - needDefaultXAxisFigure: true, - needDefaultYAxisFigure: true, - createPointFigures: ({chart, yAxis, overlay, coordinates, bounding }) => { - const precision = getPrecision(chart, overlay, yAxis) - let text = useOrder().calcPL(overlay.points[0].value!, precision.price, true) - useOrder().updatePipsAndPL(overlay, text) - return [ - { - type: 'line', - attrs: { coordinates: [{ x: 0, y: coordinates[0].y }, { x: bounding.width, y: coordinates[0].y }] }, - styles: buyStyle().lineStyle, - ignoreEvent: true - }, - { - type: 'text', - attrs: { x: bounding.width, y: coordinates[0].y, text: text.length ? `buy | ${text}` : 'buy', align: 'right', baseline: 'middle' }, - styles: buyStyle().labelStyle - } - ] - }, - createYAxisFigures: ({ chart, overlay, coordinates, bounding, yAxis }) => { - const precision = getPrecision(chart, overlay, yAxis) - console.info('overlay extend dat is: ', overlay.extendData) - const isFromZero = yAxis?.isFromZero() ?? false - let textAlign: CanvasTextAlign - let x: number - if (isFromZero) { - textAlign = 'left' - x = 0 - } else { - textAlign = 'right' - x = bounding.width - } - let text: string|null|undefined - if (utils.isValid(overlay.extendData)) { - if (!utils.isFunction(overlay.extendData)) { - text = (overlay.extendData ?? '') as string - } else { - text = overlay.extendData(overlay) as string - } - } - if (!utils.isValid(text) && overlay.points[0].value !== undefined) { - text = utils.formatPrecision(overlay.points[0].value, precision.price) - } - // let width = utils.calcTextWidth((text as string)) - // const height = width/(text as string).length * 3 - // width = width + height * 2 - return { type: 'text', attrs: { x, y: coordinates[0].y, text: text ?? '', align: textAlign, baseline: 'middle' }, styles: buyStyle().labelStyle } - }, - onRightClick: (event): boolean => { - // useOverlaySettings().singlePopup(event, 'buy') - return true - } -} - -export default buyLine \ No newline at end of file diff --git a/src/extension/position/positionLine.ts b/src/extension/position/positionLine.ts new file mode 100644 index 00000000..7caa9ce0 --- /dev/null +++ b/src/extension/position/positionLine.ts @@ -0,0 +1,155 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Coordinate, utils } from 'klinecharts' +import { PositionLineProperties, PositionOverlayTemplate, useOrder } from '../../store/positionStore' +import { buyStyle } from '../../store/overlayStyle/positionStyleStore' +import { getPrecision } from '../../helpers' +// import { useOverlaySettings } from '../../../store/overlaySettingStore' + +const buyLine = (): PositionOverlayTemplate => { + let properties: PositionLineProperties = { + price: undefined, + text: undefined, + bodyBackgroundColor: undefined, + bodyBorderColor: undefined, + bodyTextColor: undefined, + tooltip: undefined, + quantity: undefined, + quantityFont: undefined, + quantityColor: undefined, + quantityBackgroundColor: undefined, + quantityBorderColor: undefined, + modifyTooltip: undefined, + cancelButtonIconColor: undefined, + cancelButtonBackgroundColor: undefined, + cancelButtonBorderColor: undefined, + lineColor: undefined, + lineWidth: undefined, + lineStyle: undefined, + lineLength: undefined, + } + + return { + name: 'positionLine', + totalStep: 2, + needDefaultPointFigure: true, + needDefaultXAxisFigure: false, + needDefaultYAxisFigure: true, + createPointFigures: ({chart, yAxis, overlay, coordinates, bounding }) => { + console.info('Position Line price is set to: ', properties.price) + const precision = getPrecision(chart, overlay, yAxis) + // let text = useOrder().calcPL(overlay.points[0].value!, precision.price, true) + // useOrder().updatePipsAndPL(overlay, text) + if (properties.price !== undefined) { + console.info('calculated y coordinate is: ', (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y) + } + return [ + { + type: 'line', + attrs: { + coordinates: [ + { + x: 0, + y: properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y + }, + { + x: bounding.width, + y: properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y + } + ] + }, + styles: buyStyle().lineStyle, + ignoreEvent: true + }, + { + key: 'label', + type: 'text', + attrs: { + x: bounding.width, + y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, + text: properties.text ?? 'Position Line', + align: 'right', + baseline: 'middle' + }, + styles: buyStyle().labelStyle + } + ] + }, + createYAxisFigures: ({ chart, overlay, coordinates, bounding, yAxis }) => { + const precision = getPrecision(chart, overlay, yAxis) + console.info('overlay extend dat is: ', overlay.extendData) + const isFromZero = yAxis?.isFromZero() ?? false + let textAlign: CanvasTextAlign + let x: number + if (isFromZero) { + textAlign = 'left' + x = 0 + } else { + textAlign = 'right' + x = bounding.width + } + let text: string|null|undefined + if (properties.price !== undefined) { + overlay.points[0].value = properties.price + } + + if (utils.isValid(overlay.extendData)) { + if (!utils.isFunction(overlay.extendData)) { + text = (overlay.extendData ?? '') as string + } else { + text = overlay.extendData(overlay) as string + } + } + if (!utils.isValid(text) && overlay.points[0].value !== undefined) { + text = utils.formatPrecision(overlay.points[0].value, precision.price) + } + + // let width = utils.calcTextWidth((text as string)) + // const height = width/(text as string).length * 3 + // width = width + height * 2 + return { + type: 'text', + attrs: { + x, + y: properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, + text: text ?? '', + align: textAlign, baseline: + 'middle' + }, + styles: buyStyle().labelStyle + } + }, + onRightClick: (event): boolean => { + // useOverlaySettings().singlePopup(event, 'buy') + return true + }, + onClick: (event): boolean => { + event.figure?. + return true + } + setPrice(price: number) { + console.info('setPrice called with price: ', price) + properties.price = price + return this + }, + setText(text: string) { + console.info('setText called with text: ', text) + properties.text = text + return this + } + } +} + +export default buyLine \ No newline at end of file diff --git a/src/store/positionStore.ts b/src/store/positionStore.ts index 1007fc76..3fbcf2eb 100644 --- a/src/store/positionStore.ts +++ b/src/store/positionStore.ts @@ -1,4 +1,4 @@ -import { Chart, Nullable, Overlay, OverlayEvent, Point, YAxis } from 'klinecharts'; +import { Chart, Nullable, Overlay, OverlayEvent, OverlayTemplate, Point, YAxis } from 'klinecharts'; import { ExitType, OrderInfo, OrderModifyInfo, OrderResource, OrderType } from '../types'; import { createSignal } from 'solid-js'; import { currenttick } from './tickStore'; @@ -12,6 +12,79 @@ export const [ordercontr, setOrderContr] = createSignal> export const [orderList, setOrderList] = createSignal([]) export const [currentequity, setCurrentequity] = createSignal(0) +export interface PositionLineProperties { + price?: number + text?: string + bodyBackgroundColor?: string + bodyTextColor?: string + bodyBorderColor?: string + tooltip?: string + quantity?: string + quantityFont?: string + quantityColor?: string + quantityBackgroundColor?: string + quantityBorderColor?: string + modifyTooltip?: string + cancelButtonIconColor?: string + cancelButtonBackgroundColor?: string + cancelButtonBorderColor?: string + lineColor?: string + lineWidth?: number + lineStyle?: 'solid'|'dashed'|'dotted' + lineLength?: number +} + +export interface PositionOverlayTemplate extends OverlayTemplate { + setPrice?: (price: number) => void + setText?: (text: string) => void + setBodyBackgroundColor?: (color: string) => void + setBodyTextColor?: (color: string) => void + setBodyBorderColor?: (color: string) => void + setTooltip?: (tooltip: string) => void + setQuantity?: (quantity: string) => void + setQuantityFont?: (font: string) => void + setQuantityColor?: (color: string) => void + setQuantityBackgroundColor?: (color: string) => void + setQuantityBorderColor?: (color: string) => void + setModifyTooltip?: (tooltip: string) => void + setCancelButtonIconColor?: (color: string) => void + setCancelButtonBackgroundColor?: (color: string) => void + setCancelButtonBorderColor?: (color: string) => void + setLineColor?: (color: string) => void + // onCancel?: (callback: (event: OverlayEvent) => void) => void + setLineWidth?: (width: number) => void + setLineStyle?: (style: 'solid'|'dashed'|'dotted') => void + // onModify?: (callback: (event: OverlayEvent) => void) => void + setLineLength?: (length: number) => void +} + +// export interface OrderOverlayTemplate extends OverlayTemplate { +// setPrice?: (price: number) => void +// setText?: (text: string) => void +// setBodyBackgroundColor?: (color: string) => void +// setBodyTextColor?: (color: string) => void +// setBodyBorderColor?: (color: string) => void +// setTooltip?: (tooltip: string) => void +// setQuantity?: (quantity: string) => void +// setQuantityFont?: (font: string) => void +// setQuantityColor?: (color: string) => void +// setQuantityBackgroundColor?: (color: string) => void +// setQuantityBorderColor?: (color: string) => void +// setPendingTooltip?: (tooltip: string) => void +// setActivateButtonIconColor?: (color: string) => void +// setActivateButtonBackgroundColor?: (color: string) => void +// setActivateButtonBorderColor?: (color: string) => void +// setLineColor?: (color: string) => void +// onActivate?: (callback: (event: OverlayEvent) => void) => void +// setLineWidth?: (width: number) => void +// setLineStyle?: (style: 'solid'|'dashed'|'dotted') => void +// setLineLength?: (length: number) => void +// } + +export const createOrderLine = () => { + return instanceapi()?.createOverlay('') +} + export const useOrder = () => { const onOrderPlaced = (order: OrderInfo|null) => { // setOrderModalVisible(false) From c0e72b7ed7f73958cb0810ad24440f0379ccaba7 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Thu, 23 Oct 2025 16:42:20 +0100 Subject: [PATCH 14/20] implement basic multicharts foundation --- src/Chart.ts | 277 +++++++++++++++++++++++ src/ChartProComponent.tsx | 2 +- src/DataLoader.ts | 2 +- src/DefaultDatafeed.ts | 2 +- src/DefaultOrderController.ts | 2 +- src/DummyOrderController.ts | 2 +- src/KLineChartPro.tsx | 2 +- src/index.ts | 2 +- src/store/positionStore.ts | 2 +- src/{ => types}/types.ts | 4 + src/widget/period-bar/index.tsx | 2 +- src/widget/symbol-search-modal/index.tsx | 2 +- 12 files changed, 291 insertions(+), 10 deletions(-) create mode 100644 src/Chart.ts rename src/{ => types}/types.ts (99%) diff --git a/src/Chart.ts b/src/Chart.ts new file mode 100644 index 00000000..bd6ced74 --- /dev/null +++ b/src/Chart.ts @@ -0,0 +1,277 @@ +import { ActionCallback, ActionType, BarSpace, Bounding, ConvertFilter, Coordinate, Crosshair, DataLoader, DecimalFold, DeepPartial, DomPosition, Formatter, Indicator, IndicatorCreate, IndicatorFilter, init, KLineData, Nullable, Options, Overlay, OverlayCreate, OverlayFilter, PaneOptions, Period, PickPartial, PickRequired, Point, Styles, SymbolInfo, ThousandsSeparator, VisibleRange } from "klinecharts"; +import { ProChart } from "./types/types"; + +export default class Chart implements ProChart +{ + private _chart: ProChart; + private _charts: Map = new Map(); + + public id: string; + + constructor (ds: string | HTMLElement, options?: Options | undefined) { + const chart = init(ds, options); + + if (!chart) { + throw new Error('Failed to initialize chart'); + } + this._chart = chart; + this.id = chart.id; + } + + static init (ds: string | HTMLElement, options?: Options | undefined): Chart { + return new Chart(ds, options); + } + + get chart (): ProChart { + return this._chart; + } + + get charts (): ProChart[] { + return Array.from(this._charts.values()); + } + + setActiveChart (id: string) { + const chart = this._charts.get(id); + if (chart) { + this._chart = chart; + } + + return this + } + + chartById (id: string): ProChart | undefined { + return this._charts.get(id); + } + + /** + * Base Proxy Methods + */ + + getDom (paneId?: string, position?: DomPosition): Nullable { + return this._chart.getDom(paneId, position); + } + getSize (paneId?: string, position?: DomPosition): Nullable { + return this._chart.getSize(paneId, position); + } + createIndicator (value: string | IndicatorCreate, isStack?: boolean, paneOptions?: PaneOptions): Nullable { + return this._chart.createIndicator(value, isStack, paneOptions); + } + getIndicators (filter?: IndicatorFilter): Indicator[] { + return this._chart.getIndicators(filter); + } + createOverlay (value: string | OverlayCreate | Array): Nullable | Array> { + return this._chart.createOverlay(value); + } + getOverlays (filter?: OverlayFilter): Overlay[] { + return this._chart.getOverlays(filter); + } + setPaneOptions (options: PaneOptions): void { + return this._chart.setPaneOptions(options); + } + getPaneOptions (id?: string): Nullable | PaneOptions[] { + return this._chart.getPaneOptions(id); + } + scrollByDistance (distance: number, animationDuration?: number): void { + return this._chart.scrollByDistance(distance, animationDuration); + } + scrollToRealTime (animationDuration?: number): void { + return this._chart.scrollToRealTime(animationDuration); + } + scrollToDataIndex (dataIndex: number, animationDuration?: number): void { + return this._chart.scrollToDataIndex(dataIndex, animationDuration); + } + scrollToTimestamp (timestamp: number, animationDuration?: number): void { + return this._chart.scrollToTimestamp(timestamp, animationDuration); + } + zoomAtCoordinate (scale: number, coordinate?: Coordinate, animationDuration?: number): void { + return this._chart.zoomAtCoordinate(scale, coordinate, animationDuration); + } + zoomAtDataIndex (scale: number, dataIndex: number, animationDuration?: number): void { + return this._chart.zoomAtDataIndex(scale, dataIndex, animationDuration); + } + zoomAtTimestamp (scale: number, timestamp: number, animationDuration?: number): void { + return this._chart.zoomAtTimestamp(scale, timestamp, animationDuration); + } + convertToPixel (points: Partial | Array>, filter?: ConvertFilter): Partial | Array> { + return this._chart.convertToPixel(points, filter); + } + convertFromPixel (coordinates: Array>, filter?: ConvertFilter): Partial | Array> { + return this._chart.convertFromPixel(coordinates, filter); + } + executeAction (type: ActionType, data: Crosshair): void { + return this._chart.executeAction(type, data); + } + subscribeAction (type: ActionType, callback: ActionCallback): void { + return this._chart.subscribeAction(type, callback); + } + unsubscribeAction (type: ActionType, callback?: ActionCallback): void { + return this._chart.unsubscribeAction(type, callback); + } + getConvertPictureUrl (includeOverlay?: boolean, type?: "png" | "jpeg" | "bmp", backgroundColor?: string): string { + return this._chart.getConvertPictureUrl(includeOverlay, type, backgroundColor); + } + resize (): void { + return this._chart.resize(); + } + + + /** + * Store Proxy Methods + */ + setStyles (value: string | DeepPartial): void + { + return this._chart.setStyles(value); + } + getStyles (): Styles + { + return this._chart.getStyles(); + } + setFormatter (formatter: Partial): void + { + return this._chart.setFormatter(formatter); + } + getFormatter (): Formatter + { + return this._chart.getFormatter(); + } + setLocale (locale: string): void + { + return this._chart.setLocale(locale); + } + getLocale (): string + { + return this._chart.getLocale(); + } + setTimezone (timezone: string): void + { + return this._chart.setTimezone(timezone); + } + getTimezone (): string + { + return this._chart.getTimezone(); + } + setThousandsSeparator (thousandsSeparator: Partial): void + { + return this._chart.setThousandsSeparator(thousandsSeparator); + } + getThousandsSeparator (): ThousandsSeparator + { + return this._chart.getThousandsSeparator(); + } + setDecimalFold (decimalFold: Partial): void + { + return this._chart.setDecimalFold(decimalFold); + } + getDecimalFold (): DecimalFold + { + return this._chart.getDecimalFold(); + } + //Still deciding about using type from klinecharts or our own type + setSymbol (symbol: PickPartial): void + { + return this._chart.setSymbol(symbol); + } + //Still deciding about using type from klinecharts or our own type + getSymbol (): Nullable + { + return this._chart.getSymbol(); + } + //Still deciding about using type from klinecharts or our own type + setPeriod (period: Period): void + { + return this._chart.setPeriod(period); + } + //Still deciding about using type from klinecharts or our own type + getPeriod (): Nullable + { + return this._chart.getPeriod(); + } + getDataList (): KLineData[] + { + return this._chart.getDataList(); + } + setOffsetRightDistance (distance: number): void + { + return this._chart.setOffsetRightDistance(distance); + } + getOffsetRightDistance (): number + { + return this._chart.getOffsetRightDistance(); + } + setMaxOffsetLeftDistance (distance: number): void + { + return this._chart.setMaxOffsetLeftDistance(distance); + } + setMaxOffsetRightDistance (distance: number): void + { + return this._chart.setMaxOffsetRightDistance(distance); + } + setLeftMinVisibleBarCount (barCount: number): void + { + return this._chart.setLeftMinVisibleBarCount(barCount); + } + setRightMinVisibleBarCount (barCount: number): void + { + return this._chart.setRightMinVisibleBarCount(barCount); + } + setBarSpace (space: number): void + { + return this._chart.setBarSpace(space); + } + getBarSpace (): BarSpace + { + return this._chart.getBarSpace(); + } + getVisibleRange (): VisibleRange + { + return this._chart.getVisibleRange(); + } + setDataLoader (dataLoader: DataLoader): void + { + return this._chart.setDataLoader(dataLoader); + } + overrideIndicator (override: IndicatorCreate): boolean + { + return this._chart.overrideIndicator(override); + } + removeIndicator (filter?: IndicatorFilter): boolean + { + return this._chart.removeIndicator(filter); + } + overrideOverlay (override: Partial): boolean + { + return this._chart.overrideOverlay(override); + } + removeOverlay (filter?: OverlayFilter): boolean + { + return this._chart.removeOverlay(filter); + } + setZoomEnabled (enabled: boolean): void + { + return this._chart.setZoomEnabled(enabled); + } + isZoomEnabled (): boolean + { + return this._chart.isZoomEnabled(); + } + setScrollEnabled (enabled: boolean): void + { + return this._chart.setScrollEnabled(enabled); + } + isScrollEnabled (): boolean + { + return this._chart.isScrollEnabled(); + } + resetData (): void + { + return this._chart.resetData(); + } + + /** + * Custom methods + */ + + getOverlay (filter?: PickRequired): Overlay[] { + return this._chart.getOverlays(filter); + } +} \ No newline at end of file diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index 70325239..761ae2e2 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -32,7 +32,7 @@ import { import { translateTimezone } from './widget/timezone-modal/data' -import { SymbolInfo, Period, ChartProOptions, ChartPro } from './types' +import { SymbolInfo, Period, ChartProOptions, ChartPro } from './types/types' import ChartDataLoader from './DataLoader' export interface ChartProComponentProps extends Required> { diff --git a/src/DataLoader.ts b/src/DataLoader.ts index a1aa5913..f7133975 100644 --- a/src/DataLoader.ts +++ b/src/DataLoader.ts @@ -1,5 +1,5 @@ import { DataLoaderGetBarsParams, DataLoaderSubscribeBarParams, DataLoaderUnsubscribeBarParams } from "klinecharts"; -import { ChartDataLoaderType, Datafeed, Period, SymbolInfo } from "./types"; +import { ChartDataLoaderType, Datafeed, Period, SymbolInfo } from "./types/types"; import { period, setLoadingVisible, symbol } from "./ChartProComponent"; export default class ChartDataLoader implements ChartDataLoaderType { diff --git a/src/DefaultDatafeed.ts b/src/DefaultDatafeed.ts index 4774dc7d..27862ce5 100644 --- a/src/DefaultDatafeed.ts +++ b/src/DefaultDatafeed.ts @@ -14,7 +14,7 @@ import { KLineData } from 'klinecharts' -import { Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback } from './types' +import { Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback } from './types/types' export default class DefaultDatafeed implements Datafeed { diff --git a/src/DefaultOrderController.ts b/src/DefaultOrderController.ts index 445bc783..23a08b3f 100644 --- a/src/DefaultOrderController.ts +++ b/src/DefaultOrderController.ts @@ -1,4 +1,4 @@ -import { OrderInfo, OrderModalType, OrderModifyInfo, OrderPlacedCallback, OrderResource, OrderType } from "./types"; +import { OrderInfo, OrderModalType, OrderModifyInfo, OrderPlacedCallback, OrderResource, OrderType } from "./types/types"; type MethodType = 'POST'|'GET'|'DELETE'|'PUT' diff --git a/src/DummyOrderController.ts b/src/DummyOrderController.ts index 79ea75cc..5c9c0094 100644 --- a/src/DummyOrderController.ts +++ b/src/DummyOrderController.ts @@ -1,5 +1,5 @@ import { Nullable } from "klinecharts"; -import { OrderInfo, OrderModalType, OrderModifyInfo, OrderPlacedCallback, OrderResource, OrderType } from "./types"; +import { OrderInfo, OrderModalType, OrderModifyInfo, OrderPlacedCallback, OrderResource, OrderType } from "./types/types"; export default class DummyOrderController implements OrderResource { constructor (storage_name = 'dummy_order_controller') { diff --git a/src/KLineChartPro.tsx b/src/KLineChartPro.tsx index e10dd1a0..6b8a893a 100644 --- a/src/KLineChartPro.tsx +++ b/src/KLineChartPro.tsx @@ -18,7 +18,7 @@ import { utils, Nullable, DeepPartial, Styles, Chart, dispose } from 'klinechart import ChartProComponent, { instanceapi } from './ChartProComponent' -import { SymbolInfo, Period, ChartPro, ChartProOptions } from './types' +import { SymbolInfo, Period, ChartPro, ChartProOptions } from './types/types' import ChartDataLoader from './DataLoader' const Logo = ( diff --git a/src/index.ts b/src/index.ts index 36597dfd..8525e7cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,7 @@ import { load } from './i18n' import { OrderType, ExitType, OrderModalType, OrderInfo, OrderModifyInfo, OrderResource, Datafeed, SymbolInfo, Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType -} from './types' +} from './types/types' import './index.less' diff --git a/src/store/positionStore.ts b/src/store/positionStore.ts index 3fbcf2eb..bbcd1fdb 100644 --- a/src/store/positionStore.ts +++ b/src/store/positionStore.ts @@ -1,5 +1,5 @@ import { Chart, Nullable, Overlay, OverlayEvent, OverlayTemplate, Point, YAxis } from 'klinecharts'; -import { ExitType, OrderInfo, OrderModifyInfo, OrderResource, OrderType } from '../types'; +import { ExitType, OrderInfo, OrderModifyInfo, OrderResource, OrderType } from '../types/types'; import { createSignal } from 'solid-js'; import { currenttick } from './tickStore'; import { instanceapi, symbol } from '../ChartProComponent'; diff --git a/src/types.ts b/src/types/types.ts similarity index 99% rename from src/types.ts rename to src/types/types.ts index 30ccbea7..3153d3a7 100644 --- a/src/types.ts +++ b/src/types/types.ts @@ -71,6 +71,10 @@ export interface Period extends DefaultPeriod { text: string } +export interface ProChart extends Chart { + +} + type IndicatorsType = { value?: IndicatorCreate, isStack?: boolean, diff --git a/src/widget/period-bar/index.tsx b/src/widget/period-bar/index.tsx index 974b7089..9d154ac1 100644 --- a/src/widget/period-bar/index.tsx +++ b/src/widget/period-bar/index.tsx @@ -14,7 +14,7 @@ import { Component, Show, createSignal, onMount, onCleanup } from 'solid-js' -import { SymbolInfo, Period } from '../../types' +import { SymbolInfo, Period } from '../../types/types' import i18n from '../../i18n' diff --git a/src/widget/symbol-search-modal/index.tsx b/src/widget/symbol-search-modal/index.tsx index 8bd1764c..ccbb6ac6 100644 --- a/src/widget/symbol-search-modal/index.tsx +++ b/src/widget/symbol-search-modal/index.tsx @@ -18,7 +18,7 @@ import { Modal, List, Input } from '../../component' import i18n from '../../i18n' -import { SymbolInfo, ChartDataLoaderType } from '../../types' +import { SymbolInfo, ChartDataLoaderType } from '../../types/types' export interface SymbolSearchModalProps { locale: string From 6a1e7033a8f5a47c60b6ad6585c423995bb7937e Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Fri, 24 Oct 2025 16:37:04 +0100 Subject: [PATCH 15/20] complete basic implementation of Highly configurable OrderLine overlay --- src/Chart.ts | 99 ++-- src/ChartProComponent.tsx | 11 +- src/KLineChartPro.tsx | 6 +- src/extension/index.ts | 2 +- .../{positionLine.ts => orderLine.ts} | 131 +++++- src/index.ts | 5 +- src/store/overlayStyle/positionStyleStore.ts | 8 +- src/store/positionStore.ts | 422 +----------------- src/types/overlayTypes.ts | 67 +++ src/types/types.ts | 19 +- 10 files changed, 294 insertions(+), 476 deletions(-) rename src/extension/position/{positionLine.ts => orderLine.ts} (57%) create mode 100644 src/types/overlayTypes.ts diff --git a/src/Chart.ts b/src/Chart.ts index bd6ced74..3c2ec139 100644 --- a/src/Chart.ts +++ b/src/Chart.ts @@ -1,13 +1,28 @@ -import { ActionCallback, ActionType, BarSpace, Bounding, ConvertFilter, Coordinate, Crosshair, DataLoader, DecimalFold, DeepPartial, DomPosition, Formatter, Indicator, IndicatorCreate, IndicatorFilter, init, KLineData, Nullable, Options, Overlay, OverlayCreate, OverlayFilter, PaneOptions, Period, PickPartial, PickRequired, Point, Styles, SymbolInfo, ThousandsSeparator, VisibleRange } from "klinecharts"; -import { ProChart } from "./types/types"; +import { + ActionCallback, ActionType, BarSpace, Bounding, ConvertFilter, Coordinate, Crosshair, DataLoader, DecimalFold, + DeepPartial, DomPosition, Formatter, Indicator, IndicatorCreate, IndicatorFilter, init, KLineData, Nullable, + Options, Overlay, OverlayCreate, OverlayFilter, PaneOptions, Period, PickPartial, PickRequired, Point, Styles, + SymbolInfo, ThousandsSeparator, VisibleRange, Chart as KLineChart +} from "klinecharts"; +import { ProChart, UndoOptions } from "./types/types"; +import { OrderOverlay, OrderOverlayCreate } from "./types/overlayTypes"; +import { isArray } from "lodash"; export default class Chart implements ProChart { - private _chart: ProChart; - private _charts: Map = new Map(); + private _chart: KLineChart; + private _charts: Map = new Map(); public id: string; + get chart (): KLineChart { + return this._chart; + } + + get charts (): KLineChart[] { + return Array.from(this._charts.values()); + } + constructor (ds: string | HTMLElement, options?: Options | undefined) { const chart = init(ds, options); @@ -22,27 +37,6 @@ export default class Chart implements ProChart return new Chart(ds, options); } - get chart (): ProChart { - return this._chart; - } - - get charts (): ProChart[] { - return Array.from(this._charts.values()); - } - - setActiveChart (id: string) { - const chart = this._charts.get(id); - if (chart) { - this._chart = chart; - } - - return this - } - - chartById (id: string): ProChart | undefined { - return this._charts.get(id); - } - /** * Base Proxy Methods */ @@ -271,7 +265,62 @@ export default class Chart implements ProChart * Custom methods */ + setActiveChart (id: string) { + const chart = this._charts.get(id); + if (chart) { + this._chart = chart; + } + + return this + } + + chartById (id: string): KLineChart | undefined { + return this._charts.get(id); + } + getOverlay (filter?: PickRequired): Overlay[] { return this._chart.getOverlays(filter); } + + createOrderLine (options?: UndoOptions): Nullable { + const dataList = this._chart.getDataList() + const overlays = this._chart.createOverlay({ + name: 'orderLine', + paneId: 'candle_pane', + points: [{ + timestamp: dataList[dataList.length - 40].timestamp, + value: dataList[dataList.length - 40].close + }] + }); + if (!overlays) { + return null + } + + return this._chart.getOverlays({ id: overlays as string, paneId: 'candle_pane' })[0] as Nullable; + } + + createOrderLines (nums: number, options?: UndoOptions): Array> { + const points: Array> = [] + const dataList = this._chart.getDataList() + const step = Math.floor(dataList.length / (nums + 1)) + for (let i = 1; i <= nums; i++) { + points.push({ + timestamp: dataList[step * i].timestamp, + value: dataList[step * i].close + }) + } + + const values: OverlayCreate = { + name: 'orderLine', + paneId: 'candle_pane', + points: points + } + const overlays = this._chart.createOverlay(values); + + if (!overlays || (isArray(overlays) && overlays.length === 0)) { + return []; + } + + return (overlays as Array).map(o => this._chart.getOverlays({ id: o!, paneId: 'canlde_pane' })[0]) as Array>; + } } \ No newline at end of file diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index 761ae2e2..9f5c3186 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -15,7 +15,7 @@ import { createSignal, createEffect, onMount, Show, onCleanup, startTransition, Component } from 'solid-js' import { - init, dispose, utils, Nullable, Chart, OverlayMode, Styles, + init, dispose, utils, Nullable, OverlayMode, Styles, PaneOptions, Indicator, FormatDateParams, TooltipFeatureStyle, IndicatorTooltipData } from 'klinecharts' @@ -32,8 +32,9 @@ import { import { translateTimezone } from './widget/timezone-modal/data' -import { SymbolInfo, Period, ChartProOptions, ChartPro } from './types/types' +import { SymbolInfo, Period, ChartProOptions, ChartPro, ProChart } from './types/types' import ChartDataLoader from './DataLoader' +import Chart from './Chart' export interface ChartProComponentProps extends Required> { ref: (chart: ChartPro) => void @@ -45,7 +46,7 @@ interface PrevSymbolPeriod { period: Period } -function createIndicator (widget: Chart, indicatorName: string, isStack?: boolean, paneOptions?: PaneOptions): Nullable { +function createIndicator (widget: ProChart, indicatorName: string, isStack?: boolean, paneOptions?: PaneOptions): Nullable { if (indicatorName === 'VOL') { paneOptions = { axis: { gap: { bottom: 2 } }, ...paneOptions } } @@ -74,7 +75,7 @@ function createIndicator (widget: Chart, indicatorName: string, isStack?: boolea export const [loadingVisible, setLoadingVisible] = createSignal(false) export const [symbol, setSymbol] = createSignal>(null) export const [period, setPeriod] = createSignal>(null) -export const [instanceapi, setInstanceapi] = createSignal>(null) +export const [instanceapi, setInstanceapi] = createSignal>(null) const ChartProComponent: Component = props => { let widgetRef: HTMLDivElement | undefined = undefined @@ -131,7 +132,7 @@ const ChartProComponent: Component = props => { onMount(() => { window.addEventListener('resize', documentResize) - setInstanceapi(init(widgetRef!, { + setInstanceapi(Chart.init(widgetRef!, { formatter: { formatDate: (params: FormatDateParams) => { const p = period()! diff --git a/src/KLineChartPro.tsx b/src/KLineChartPro.tsx index 6b8a893a..3c2ea98c 100644 --- a/src/KLineChartPro.tsx +++ b/src/KLineChartPro.tsx @@ -14,11 +14,11 @@ import { render } from 'solid-js/web' -import { utils, Nullable, DeepPartial, Styles, Chart, dispose } from 'klinecharts' +import { utils, Nullable, DeepPartial, Styles, dispose } from 'klinecharts' import ChartProComponent, { instanceapi } from './ChartProComponent' -import { SymbolInfo, Period, ChartPro, ChartProOptions } from './types/types' +import { SymbolInfo, Period, ChartPro, ChartProOptions, ProChart } from './types/types' import ChartDataLoader from './DataLoader' const Logo = ( @@ -141,7 +141,7 @@ export default class KLineChartPro implements ChartPro { getPeriod (): Period { return this._chartApi!.getPeriod() } - getInstanceApi(): Nullable { + getInstanceApi(): Nullable { return instanceapi() } resize(): void { diff --git a/src/extension/index.ts b/src/extension/index.ts index b34acd42..dfd17bee 100644 --- a/src/extension/index.ts +++ b/src/extension/index.ts @@ -17,7 +17,7 @@ import anyWaves from './anyWaves' import abcd from './abcd' import xabcd from './xabcd' -import positionLine from './position/positionLine' +import positionLine from './position/orderLine' const overlays = [ arrow, diff --git a/src/extension/position/positionLine.ts b/src/extension/position/orderLine.ts similarity index 57% rename from src/extension/position/positionLine.ts rename to src/extension/position/orderLine.ts index 7caa9ce0..432eaf4e 100644 --- a/src/extension/position/positionLine.ts +++ b/src/extension/position/orderLine.ts @@ -12,14 +12,14 @@ * limitations under the License. */ -import { Coordinate, utils } from 'klinecharts' -import { PositionLineProperties, PositionOverlayTemplate, useOrder } from '../../store/positionStore' +import { Coordinate, LineStyle, LineType, TextStyle, utils } from 'klinecharts' +import { OrderLineProperties, OrderOverlay, OrderOverlayCreate } from '../../types/overlayTypes' import { buyStyle } from '../../store/overlayStyle/positionStyleStore' import { getPrecision } from '../../helpers' // import { useOverlaySettings } from '../../../store/overlaySettingStore' -const buyLine = (): PositionOverlayTemplate => { - let properties: PositionLineProperties = { +const OrderLine = (): OrderOverlayCreate => { + let properties: OrderLineProperties = { price: undefined, text: undefined, bodyBackgroundColor: undefined, @@ -39,10 +39,39 @@ const buyLine = (): PositionOverlayTemplate => { lineWidth: undefined, lineStyle: undefined, lineLength: undefined, + lineDashedValue: undefined } + const lineStyle = (): LineStyle => { + return { + style: properties.lineStyle ?? buyStyle().lineStyle.style, + size: properties.lineWidth ?? buyStyle().lineStyle.size, + color: properties.lineColor ?? buyStyle().lineStyle.color, + dashedValue: properties.lineDashedValue ?? buyStyle().lineStyle.dashedValue + } + } + + const labelStyle = (): TextStyle => { + return { + style: 'fill', + size: 12, + weight: 'normal', + family: properties.quantityFont ?? buyStyle().labelStyle.family, + color: properties.quantityColor ?? buyStyle().labelStyle.color, + backgroundColor: properties.quantityBackgroundColor ?? buyStyle().labelStyle.backgroundColor, + borderColor: properties.quantityBorderColor ?? buyStyle().labelStyle.borderColor, + borderStyle: 'solid' as const, + borderSize: 1, + borderDashedValue: [1, 1], + borderRadius: 3, + paddingLeft: 5, + paddingRight: 5, + paddingTop: 5, + paddingBottom: 5 + }} + return { - name: 'positionLine', + name: 'orderLine', totalStep: 2, needDefaultPointFigure: true, needDefaultXAxisFigure: false, @@ -70,7 +99,7 @@ const buyLine = (): PositionOverlayTemplate => { } ] }, - styles: buyStyle().lineStyle, + styles: lineStyle(), ignoreEvent: true }, { @@ -83,7 +112,7 @@ const buyLine = (): PositionOverlayTemplate => { align: 'right', baseline: 'middle' }, - styles: buyStyle().labelStyle + styles: labelStyle() } ] }, @@ -128,28 +157,100 @@ const buyLine = (): PositionOverlayTemplate => { align: textAlign, baseline: 'middle' }, - styles: buyStyle().labelStyle + styles: labelStyle() } }, onRightClick: (event): boolean => { // useOverlaySettings().singlePopup(event, 'buy') return true }, - onClick: (event): boolean => { - event.figure?. - return true - } + // onClick: (event): boolean => { + // event.figure?. + // return true + // } setPrice(price: number) { console.info('setPrice called with price: ', price) properties.price = price - return this + return this as OrderOverlay }, setText(text: string) { console.info('setText called with text: ', text) properties.text = text - return this + return this as OrderOverlay + }, + setBodyBackgroundColor(color: string) { + properties.bodyBackgroundColor = color + return this as OrderOverlay + }, + setBodyBorderColor(color: string) { + properties.bodyBorderColor = color + return this as OrderOverlay + }, + setBodyTextColor(color: string) { + properties.bodyTextColor = color + return this as OrderOverlay + }, + setTooltip(tooltip: string) { + properties.tooltip = tooltip + return this as OrderOverlay + }, + setQuantity(quantity: number|string) { + properties.quantity = quantity + return this as OrderOverlay + }, + setQuantityFont(font: string) { + properties.quantityFont = font + return this as OrderOverlay + }, + setQuantityColor(color: string) { + properties.quantityColor = color + return this as OrderOverlay + }, + setQuantityBackgroundColor(color: string) { + properties.quantityBackgroundColor = color + return this as OrderOverlay + }, + setQuantityBorderColor(color: string) { + properties.quantityBorderColor = color + return this as OrderOverlay + }, + setModifyTooltip(tooltip: string) { + properties.modifyTooltip = tooltip + return this as OrderOverlay + }, + setCancelButtonIconColor(color: string) { + properties.cancelButtonIconColor = color + return this as OrderOverlay + }, + setCancelButtonBackgroundColor(color: string) { + properties.cancelButtonBackgroundColor = color + return this as OrderOverlay + }, + setCancelButtonBorderColor(color: string) { + properties.cancelButtonBorderColor = color + return this as OrderOverlay + }, + setLineColor(color: string) { + properties.lineColor = color + return this as OrderOverlay + }, + setLineWidth(width: number) { + properties.lineWidth = width + return this as OrderOverlay + }, + setLineStyle(style: LineType) { + properties.lineStyle = style + return this as OrderOverlay + }, + setLineLength(length: number) { + properties.lineLength = length + return this as OrderOverlay + }, + setLineDashedValue(dashedValue: number[]) { + properties.lineDashedValue = dashedValue + return this as OrderOverlay } } } -export default buyLine \ No newline at end of file +export default OrderLine \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 8525e7cd..9ff0512d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,8 @@ import KLineChartPro from './KLineChartPro' import { load } from './i18n' import { OrderType, ExitType, OrderModalType, OrderInfo, OrderModifyInfo, OrderResource, Datafeed, - SymbolInfo, Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType + SymbolInfo, Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType, + ProChart } from './types/types' import './index.less' @@ -41,5 +42,5 @@ export { export type { OrderInfo, OrderModifyInfo, OrderType, ExitType, OrderModalType, OrderResource, Datafeed, SymbolInfo, - Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType + Period, DatafeedSubscribeCallback, OrderPlacedCallback, ChartProOptions, ChartPro, ChartObjType, ProChart } \ No newline at end of file diff --git a/src/store/overlayStyle/positionStyleStore.ts b/src/store/overlayStyle/positionStyleStore.ts index a4ac2d8a..042ed2d1 100644 --- a/src/store/overlayStyle/positionStyleStore.ts +++ b/src/store/overlayStyle/positionStyleStore.ts @@ -1,6 +1,7 @@ +import { LineStyle, StateTextStyle } from 'klinecharts'; import { createSignal } from 'solid-js'; -export const [buyStyle, setBuyStyle] = createSignal({ +export const [buyStyle, setBuyStyle] = createSignal<{ lineStyle: LineStyle, labelStyle: StateTextStyle}>({ lineStyle: { style: 'dashed', size: 1, @@ -18,9 +19,12 @@ export const [buyStyle, setBuyStyle] = createSignal({ paddingTop: 5, borderStyle: 'solid', borderSize: 1, + borderDashedValue: [1,1], + borderRadius: 3, color: '#FFFFFF', borderColor: '#00698b', - backgroundColor: '#00698b' + backgroundColor: '#00698b', + show: true, } }) diff --git a/src/store/positionStore.ts b/src/store/positionStore.ts index bbcd1fdb..c6bbaa94 100644 --- a/src/store/positionStore.ts +++ b/src/store/positionStore.ts @@ -12,426 +12,6 @@ export const [ordercontr, setOrderContr] = createSignal> export const [orderList, setOrderList] = createSignal([]) export const [currentequity, setCurrentequity] = createSignal(0) -export interface PositionLineProperties { - price?: number - text?: string - bodyBackgroundColor?: string - bodyTextColor?: string - bodyBorderColor?: string - tooltip?: string - quantity?: string - quantityFont?: string - quantityColor?: string - quantityBackgroundColor?: string - quantityBorderColor?: string - modifyTooltip?: string - cancelButtonIconColor?: string - cancelButtonBackgroundColor?: string - cancelButtonBorderColor?: string - lineColor?: string - lineWidth?: number - lineStyle?: 'solid'|'dashed'|'dotted' - lineLength?: number -} - -export interface PositionOverlayTemplate extends OverlayTemplate { - setPrice?: (price: number) => void - setText?: (text: string) => void - setBodyBackgroundColor?: (color: string) => void - setBodyTextColor?: (color: string) => void - setBodyBorderColor?: (color: string) => void - setTooltip?: (tooltip: string) => void - setQuantity?: (quantity: string) => void - setQuantityFont?: (font: string) => void - setQuantityColor?: (color: string) => void - setQuantityBackgroundColor?: (color: string) => void - setQuantityBorderColor?: (color: string) => void - setModifyTooltip?: (tooltip: string) => void - setCancelButtonIconColor?: (color: string) => void - setCancelButtonBackgroundColor?: (color: string) => void - setCancelButtonBorderColor?: (color: string) => void - setLineColor?: (color: string) => void - // onCancel?: (callback: (event: OverlayEvent) => void) => void - setLineWidth?: (width: number) => void - setLineStyle?: (style: 'solid'|'dashed'|'dotted') => void - // onModify?: (callback: (event: OverlayEvent) => void) => void - setLineLength?: (length: number) => void -} - -// export interface OrderOverlayTemplate extends OverlayTemplate { -// setPrice?: (price: number) => void -// setText?: (text: string) => void -// setBodyBackgroundColor?: (color: string) => void -// setBodyTextColor?: (color: string) => void -// setBodyBorderColor?: (color: string) => void -// setTooltip?: (tooltip: string) => void -// setQuantity?: (quantity: string) => void -// setQuantityFont?: (font: string) => void -// setQuantityColor?: (color: string) => void -// setQuantityBackgroundColor?: (color: string) => void -// setQuantityBorderColor?: (color: string) => void -// setPendingTooltip?: (tooltip: string) => void -// setActivateButtonIconColor?: (color: string) => void -// setActivateButtonBackgroundColor?: (color: string) => void -// setActivateButtonBorderColor?: (color: string) => void -// setLineColor?: (color: string) => void -// onActivate?: (callback: (event: OverlayEvent) => void) => void -// setLineWidth?: (width: number) => void -// setLineStyle?: (style: 'solid'|'dashed'|'dotted') => void -// setLineLength?: (length: number) => void -// } - export const createOrderLine = () => { return instanceapi()?.createOverlay('') -} - -export const useOrder = () => { - const onOrderPlaced = (order: OrderInfo|null) => { - // setOrderModalVisible(false) - // syntheticPausePlay(false) - if (order) { - drawOrder(order) - let orderlist = orderList() - if (!orderlist.find(orda => orda.orderId === order?.orderId)) { - orderlist.push(order) - setOrderList(orderlist) - } - } - } - - /** - * This method may be used interchangeably with calcStopOrTarget - * - * @param top - * @param middle - * @param dp - * @param usereal - * @param buysell - * @returns string - */ - const calcTarget = (top:number, middle:number, dp:number, usereal: boolean = false, buysell: 'buy'|'sell' = 'buy'): string => { - let multiplier = Math.pow(10, dp - 1), value: string - if (buysell === 'buy') - value = usereal ? ((top - middle)*multiplier).toFixed(2) : (top - middle).toFixed(dp) - else - value = usereal ? ((middle - top)*multiplier).toFixed(2) : (middle - top).toFixed(dp) - return value - } - - /** - * Calculate the distance between Entry Point and Stoploss or Takeprofit - * - * @param middle the base line you want to calculate from - * @param bottom the target line to calculate to - * @param dp number of decimal places round up to - * @param usereal should result be in pips or points, true will return pips - * @param buysell are we calculating for a buy trade or a sell one - * @returns return a string of the difference in pips or points - */ - const calcStopOrTarget = (middle:number, bottom:number, dp:number, usereal: boolean = false, buysell: 'buy'|'sell' = 'buy'): string => { - let multiplier = Math.pow(10, dp -1), value: string - if (buysell === 'sell') - value = usereal ? ((middle - bottom)*multiplier).toFixed(2) : (middle - bottom).toFixed(dp) - else - value = usereal ? ((bottom - middle)*multiplier).toFixed(2) : (bottom - middle).toFixed(dp) - - return value - } - - /** - * Calculate the profit or loss of a trade in pips or points - * - * @param middle the entry point of the trade - * @param dp number of decimal places to round up to - * @param usereal should result be in pips or points, true will return pips - * @param buysell are we calculating for a buy trade or a sell one - * @returns return a string of profit or loss in pips or points - */ - const calcPL = (middle:number, dp:number, usereal: boolean = false, buysell: 'buy'|'sell' = 'buy'): string => { - let multiplier = dp == 3 ? 10 : Math.pow(10, dp -1), value: string - if (currenttick()) { - if (buysell === 'buy') - value = usereal ? ((currenttick()!.close-middle) * multiplier).toFixed(2) : (currenttick()!.close-middle).toFixed(dp) - else - value = usereal ? ((middle-currenttick()!.close) * multiplier).toFixed(2) : (middle-currenttick()!.close).toFixed(dp) - } else { - value = '' - } - - return value - } - - const triggerPending = (overlay: Overlay, action: OrderType) => { - const doJob = async () => { - let id = overlay.id - let order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null - if (order) { - order.action = action - instanceapi()?.removeOverlay({ - id, - groupId: overlay.groupId, - name: overlay.name - }) - const updatedorder = await ordercontr()?.modifyOrder({ - id: order.orderId, - action: order.action, - entrypoint: order.entryPoint - })! - if (updatedorder) { - const orderlist = orderList().map(order => (order.orderId === updatedorder.orderId ? updatedorder : order)) - setOrderList(orderlist) - drawOrder(updatedorder) - } - } - } - doJob() - } - - const updateOrder = (order: OrderModifyInfo) => { - const doJob = async () => { - await ordercontr()?.modifyOrder(order) - } - doJob() - } - - const removeStopOrTP = (overlay: Overlay, removal: 'sl'|'tp') => { - const doJob = async () => { - let id = overlay.id - let order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null - if (order) { - order = await ordercontr()?.unsetSlOrTP(order.orderId, removal)! - if (order) { - instanceapi()?.removeOverlay({ - id, - groupId: overlay.groupId, - name: overlay.name - }) - const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) - setOrderList(orderlist) - drawOrder(order) - } - } - } - doJob() - } - - const closeOrder = (overlay: Overlay, type: ExitType): void => { - const doJob = async () => { - let id = overlay.id - let order: OrderInfo|null - console.log(`got to close order: ${id}, exittype: ${type}`) - if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - instanceapi()?.removeOverlay({ //remove the overlay first to prevent flooding this backend with api calls - id: overlay.id, - groupId: overlay.groupId, - name: overlay.name - }) - const updatedorder = await ordercontr()?.modifyOrder({ - id: order.orderId, - exitpoint: currenttick()?.close, - exittype: type, - pips: type == 'cancel' ? undefined : order.pips, //in a real application this should be calculated on backend - pl: type == 'cancel' ? undefined : order.pl //in a real application this should be calculated on backend - })! - console.log(updatedorder) - - if (updatedorder) { - // const session = chartsession() - // session!.current_bal = +session?.current_bal! + +updatedorder.pl! - // setChartsession(session) - const orderlist = orderList().map(orda => (orda.orderId === updatedorder?.orderId ? updatedorder : orda)) - setOrderList(orderlist) - } else { - drawOrder(order) // draw the order overlay back cose we couldn't close it - } - } - } - doJob() - } - - const updatePipsAndPL = (overlay: Overlay, text:any) => { - let id = overlay.id - let order: OrderInfo|null - if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - order.pips = parseFloat(text) - order.pl = order.pips * order.lotSize * symbol()?.dollarPerPip! - const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) - setOrderList(orderlist) - } - } - - const updateStopLossAndReturnValue = (overlay: Overlay, chart: Chart, yAxis: Nullable) => { - let id = overlay.id - let order: OrderInfo|null - if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - order!.stopLoss = parseFloat( overlay.points[0].value?.toFixed(getPrecision(chart, overlay, yAxis).price)!) - const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) - setOrderList(orderlist) - return order?.stopLoss - } - } - - const updateEntryPointAndReturnValue = (overlay: Overlay, chart: Chart, yAxis: Nullable) => { - let id = overlay.id - let order: OrderInfo|null - if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - order!.entryPoint = parseFloat( overlay.points[0].value?.toFixed(getPrecision(chart, overlay, yAxis).price)!) - const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) - setOrderList(orderlist) - return order?.entryPoint - } - } - - const updateTakeProfitAndReturnValue = (overlay: Overlay, chart: Chart, yAxis: Nullable) => { - let id = overlay.id - let order: OrderInfo|null - if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - order!.takeProfit = parseFloat( overlay.points[0].value?.toFixed(getPrecision(chart, overlay, yAxis).price)!) - const orderlist = orderList().map(orda => (orda.orderId === order?.orderId ? order : orda)) - setOrderList(orderlist) - return order?.takeProfit - } - } - - const updatePositionOrder = (overlay: Overlay) => { - let id = overlay.id - let order: OrderInfo|null - if (order = orderList().find(order => order.orderId === parseInt(id.replace('orderline_', ''))) ?? null) { // order found - useOrder().updateOrder({ - id: order.orderId, - stoploss: order.stopLoss, - entrypoint: order.entryPoint, - takeprofit: order.takeProfit - }) - } - } - - return { onOrderPlaced, calcTarget, calcStopOrTarget, calcPL, triggerPending, updateOrder, closeOrder, removeStopOrTP, - updatePipsAndPL, updateStopLossAndReturnValue, updateEntryPointAndReturnValue, updateTakeProfitAndReturnValue, - updatePositionOrder - } -}; - -export const drawOrder = (order: OrderInfo|null) => { - if (!order) - return - let overlay = instanceapi()?.getOverlays({ id: `orderline_${order!.orderId}`, 'paneId': 'candle_pane' }).at(0) - if(overlay) { - instanceapi()?.removeOverlay({ //remove the overlay first to prevent flooding this backend with api calls - id: overlay.id, - groupId: overlay.groupId, - name: overlay.name - }) - } - let name = '' - let lock = false; - order!.entryPoint = order?.entryPoint! - 0.00001+0.00001 //for some reason adding and subtracting the same value stop the overlay from vanishing when dragged - order!.stopLoss = order?.stopLoss! - 0.00001+0.00001 - order!.takeProfit = order?.takeProfit! - 0.00001+0.00001 - let points = [ - { timestamp: Date.parse(order?.entryTime!), value: order?.entryPoint } - ] - switch (order?.action) { - case 'buy': - if (!order?.stopLoss && !order?.takeProfit) { - name = 'buyLine' - lock = true - } else if (order.stopLoss && !order.takeProfit) { - name = 'buyLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } else if (!order.stopLoss && order.takeProfit) { - name = 'buyProfitLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - } else if (order.stopLoss && order.takeProfit) { - name = 'buyProfitLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } - break - case 'buystop': - if (!order?.stopLoss && !order?.takeProfit) { - name = 'buystopLine' - } else if (order.stopLoss && !order.takeProfit) { - name = 'buystopLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } else if (!order.stopLoss && order.takeProfit) { - name = 'buystopProfitLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - } else if (order.stopLoss && order.takeProfit) { - name = 'buystopProfitLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } - break - case 'buylimit': - if (!order?.stopLoss && !order?.takeProfit) { - name = 'buyLimitLine' - } else if (order.stopLoss && !order.takeProfit) { - name = 'buyLimitLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } else if (!order.stopLoss && order.takeProfit) { - name = 'buyLimitProfitLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - } else if (order.stopLoss && order.takeProfit) { - name = 'buyLimitProfitLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } - break - case 'sell': - if (!order?.stopLoss && !order?.takeProfit) { - name = 'sellLine' - lock = true - } else if (order.stopLoss && !order.takeProfit) { - name = 'sellLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } else if (!order.stopLoss && order.takeProfit) { - name = 'sellProfitLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - } else if (order.stopLoss && order.takeProfit) { - name = 'sellProfitLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } - break - case 'sellstop': - if (!order?.stopLoss && !order?.takeProfit) { - name = 'sellstopLine' - } else if (order.stopLoss && !order.takeProfit) { - name = 'sellstopLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } else if (!order.stopLoss && order.takeProfit) { - name = 'sellstopProfitLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - } else if (order.stopLoss && order.takeProfit) { - name = 'sellstopProfitLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } - break - case 'selllimit': - if (!order?.stopLoss && !order?.takeProfit) { - name = 'sellLimitLine' - } else if (order.stopLoss && !order.takeProfit) { - name = 'sellLimitLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } else if (!order.stopLoss && order.takeProfit) { - name = 'sellLimitProfitLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - } else if (order.stopLoss && order.takeProfit) { - name = 'sellLimitProfitLossLine' - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.takeProfit }) - points.push({ timestamp: Date.parse(order?.entryTime!), value: order.stopLoss }) - } - break - default: - break - } - instanceapi()?.createOverlay({ - name, - id: `orderline_${order?.orderId}`, - groupId: 'orderLine', - points, - lock, - }) -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/types/overlayTypes.ts b/src/types/overlayTypes.ts new file mode 100644 index 00000000..a01f26a9 --- /dev/null +++ b/src/types/overlayTypes.ts @@ -0,0 +1,67 @@ +import { LineType, Overlay, OverlayTemplate, StateLineStyle } from "klinecharts" + +export interface OverlayProperties { + text?: string + color?: string + lineWidth?: number + lineStyle?: LineType + lineLength?: number + lineDashedValue?: number[] + tooltip?: string + backgroundColor?: string + borderColor?: string + borderWidth?: number +} + +export interface OrderLineProperties extends OverlayProperties { + price?: number + bodyBackgroundColor?: string + bodyTextColor?: string + bodyBorderColor?: string + tooltip?: string + quantity?: number|string + quantityFont?: string + quantityColor?: string + quantityBackgroundColor?: string + quantityBorderColor?: string + modifyTooltip?: string + cancelButtonIconColor?: string + cancelButtonBackgroundColor?: string + cancelButtonBorderColor?: string + lineColor?: string + lineWidth?: number + lineStyle?: LineType + lineLength?: number +} + +interface OrderOverlayAttributes { + setPrice: (price: number) => OrderOverlay + setText: (text: string) => OrderOverlay + setBodyBackgroundColor: (color: string) => OrderOverlay + setBodyTextColor: (color: string) => OrderOverlay + setBodyBorderColor: (color: string) => OrderOverlay + setTooltip: (tooltip: string) => OrderOverlay + setQuantity: (quantity: string) => OrderOverlay + setQuantityFont: (font: string) => OrderOverlay + setQuantityColor: (color: string) => OrderOverlay + setQuantityBackgroundColor: (color: string) => OrderOverlay + setQuantityBorderColor: (color: string) => OrderOverlay + setModifyTooltip: (tooltip: string) => OrderOverlay + setCancelButtonIconColor: (color: string) => OrderOverlay + setCancelButtonBackgroundColor: (color: string) => OrderOverlay + setCancelButtonBorderColor: (color: string) => OrderOverlay + setLineColor: (color: string) => OrderOverlay + setLineWidth: (width: number) => OrderOverlay + setLineStyle: (style: LineType) => OrderOverlay + setLineLength: (length: number) => OrderOverlay + setLineDashedValue: (dashedValue: number[]) => OrderOverlay + onMoveStart?: (params: T, callback: (params: T) => void) => void + onMove?: (params: T, callback: (params: T) => void) => void + onMoveEnd?: (params: T, callback: (params: T) => void) => void + onCancel?: (params: T, callback: (params: T) => void) => void + onModify?: (params: T, callback: (params: T) => void) => void +} + +export interface OrderOverlay extends Overlay, OrderOverlayAttributes {} + +export interface OrderOverlayCreate extends OverlayTemplate, OrderOverlayAttributes {} \ No newline at end of file diff --git a/src/types/types.ts b/src/types/types.ts index 3153d3a7..70c4b202 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -12,7 +12,8 @@ * limitations under the License. */ -import { KLineData, Styles, DeepPartial, Nullable, Chart, DataLoader, Period as DefaultPeriod, IndicatorCreate, PaneOptions, OverlayCreate, FigureCreate } from 'klinecharts' +import { KLineData, Styles, DeepPartial, Nullable, Chart, DataLoader, Period as DefaultPeriod, IndicatorCreate, PaneOptions, OverlayCreate, FigureCreate, PickRequired, OverlayFilter, Overlay } from 'klinecharts' +import { OrderOverlay } from './overlayTypes' export type OrderType = 'buy'|'sell'|'buystop'|'buylimit'|'sellstop'|'selllimit' export type OrderModalType = 'placeorder'|'modifyorder'|'closepartial' @@ -21,6 +22,13 @@ export type ExitType = 'stoploss'|'takeprofit'|'breakeven'|'manualclose'|'cancel export type DatafeedSubscribeCallback = (data: KLineData, timestamp?: number) => void export type OrderPlacedCallback = (data: OrderInfo|null) => void //this should be called when a user has successfully placed an order from consumer project side +export interface UndoOptions { + /** + * A boolean flag. Controls if undo should be disabled. + */ + disableUndo?: boolean +} + export interface SymbolInfo { ticker: string name?: string @@ -72,7 +80,14 @@ export interface Period extends DefaultPeriod { } export interface ProChart extends Chart { - + chart: Chart + charts: Array + + setActiveChart (id: string): void + chartById (id: string): Chart | undefined + getOverlay (filter?: PickRequired): Overlay[] + createOrderLine (options?: UndoOptions): Nullable + createOrderLines (nums: number, options?: UndoOptions): Array> } type IndicatorsType = { From 82097753fe3e2f4ef0151b0d2244659146572b13 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Sun, 26 Oct 2025 18:54:54 +0100 Subject: [PATCH 16/20] full implementation of custom styling for order line --- src/extension/position/orderLine.ts | 181 ++++++++++++------- src/store/overlayStyle/positionStyleStore.ts | 2 +- src/types/overlayTypes.ts | 79 ++++++-- 3 files changed, 181 insertions(+), 81 deletions(-) diff --git a/src/extension/position/orderLine.ts b/src/extension/position/orderLine.ts index 432eaf4e..654f6d55 100644 --- a/src/extension/position/orderLine.ts +++ b/src/extension/position/orderLine.ts @@ -20,26 +20,9 @@ import { getPrecision } from '../../helpers' const OrderLine = (): OrderOverlayCreate => { let properties: OrderLineProperties = { - price: undefined, - text: undefined, - bodyBackgroundColor: undefined, - bodyBorderColor: undefined, - bodyTextColor: undefined, - tooltip: undefined, - quantity: undefined, - quantityFont: undefined, - quantityColor: undefined, - quantityBackgroundColor: undefined, - quantityBorderColor: undefined, - modifyTooltip: undefined, - cancelButtonIconColor: undefined, - cancelButtonBackgroundColor: undefined, - cancelButtonBorderColor: undefined, - lineColor: undefined, - lineWidth: undefined, - lineStyle: undefined, - lineLength: undefined, - lineDashedValue: undefined + isBodyVisible: true, + isCancelButtonVisible: true, + isQuantityVisible: true, } const lineStyle = (): LineStyle => { @@ -51,23 +34,23 @@ const OrderLine = (): OrderOverlayCreate => { } } - const labelStyle = (): TextStyle => { + const labelStyle = (type: 'body'|'quantity'|'cancel-button'): TextStyle => { return { style: 'fill', - size: 12, - weight: 'normal', - family: properties.quantityFont ?? buyStyle().labelStyle.family, - color: properties.quantityColor ?? buyStyle().labelStyle.color, - backgroundColor: properties.quantityBackgroundColor ?? buyStyle().labelStyle.backgroundColor, - borderColor: properties.quantityBorderColor ?? buyStyle().labelStyle.borderColor, - borderStyle: 'solid' as const, - borderSize: 1, - borderDashedValue: [1, 1], - borderRadius: 3, - paddingLeft: 5, - paddingRight: 5, - paddingTop: 5, - paddingBottom: 5 + size: (type == 'body' ? properties.bodySize : type == 'quantity' ? properties.quantitySize : properties.cancelButtonSize) ?? properties.bodySize ?? buyStyle().labelStyle.size, + weight: (type == 'body' ? properties.bodyWeight : type == 'quantity' ? properties.quantityWeight : properties.cancelButtonWeight) ?? properties.bodyWeight ?? buyStyle().labelStyle.weight, + family: (type == 'body' ? properties.bodyFont : type == 'quantity' ? properties.quantityFont : properties.bodyFont) ?? properties.bodyFont ?? buyStyle().labelStyle.family, + color: (type == 'body' ? properties.bodyTextColor : type == 'quantity' ? properties.quantityColor : properties.cancelButtonIconColor) ?? properties.bodyTextColor ?? buyStyle().labelStyle.color, + backgroundColor: (type == 'body' ? properties.bodyBackgroundColor : type == 'quantity' ? properties.quantityBackgroundColor : properties.cancelButtonBackgroundColor) ?? properties.bodyBackgroundColor ?? buyStyle().labelStyle.backgroundColor, + borderColor: (type == 'body' ? properties.bodyBorderColor : type == 'quantity' ? properties.quantityBorderColor : properties.cancelButtonBorderColor) ?? properties.bodyBorderColor ?? buyStyle().labelStyle.borderColor, + borderStyle: properties.borderStyle ?? buyStyle().labelStyle.borderStyle, + borderSize: properties.borderSize ?? buyStyle().labelStyle.borderSize, + borderDashedValue: properties.borderDashedValue ?? buyStyle().labelStyle.borderDashedValue, + borderRadius: properties.borderRadius ?? buyStyle().labelStyle.borderRadius, + paddingLeft: (type == 'body' ? properties.bodyPaddingLeft : type == 'quantity' ? properties.quantityPaddingLeft : properties.cancelButtonPaddingLeft) ?? properties.bodyPaddingLeft ?? buyStyle().labelStyle.paddingLeft, + paddingRight: (type == 'body' ? properties.bodyPaddingRight : type == 'quantity' ? properties.quantityPaddingRight : properties.cancelButtonPaddingRight) ?? properties.bodyPaddingRight ?? buyStyle().labelStyle.paddingRight, + paddingTop: (type == 'body' ? properties.bodyPaddingTop : type == 'quantity' ? properties.quantityPaddingTop : properties.cancelButtonPaddingTop) ?? properties.bodyPaddingTop ?? buyStyle().labelStyle.paddingTop, + paddingBottom: (type == 'body' ? properties.bodyPaddingBottom : type == 'quantity' ? properties.quantityPaddingBottom : properties.cancelButtonPaddingBottom) ?? properties.bodyPaddingBottom ?? buyStyle().labelStyle.paddingBottom }} return { @@ -102,8 +85,33 @@ const OrderLine = (): OrderOverlayCreate => { styles: lineStyle(), ignoreEvent: true }, + // { + // key: 'body', + // type: 'rect', + // attrs: { + // x: bounding.width - 90, + // y:(properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y! : coordinates[0].y) - 10, + // width: 70, + // height: 20 + // }, + // styles: { + // style: 'stroke', + // // color + // color: '#ff000020', + // // border style + // borderStyle: 'dashed', + // // border color + // borderColor: '#002affff', + // // frame size + // borderSize: 1, + // // border dotted line parameters + // borderDashedValue: [0, 0], + // // Border fillet value + // borderRadius: 3 + // } + // }, { - key: 'label', + key: 'body', type: 'text', attrs: { x: bounding.width, @@ -112,7 +120,31 @@ const OrderLine = (): OrderOverlayCreate => { align: 'right', baseline: 'middle' }, - styles: labelStyle() + styles: labelStyle('body') + }, + { + key: 'quantity', + type: 'text', + attrs: { + x: bounding.width - chart.calcTextWidth(properties.text ?? 'Position Line') - 10, + y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, + text: properties.text ?? 'Position Line', + align: 'right', + baseline: 'middle' + }, + styles: labelStyle('quantity') + }, + { + key: 'cancel-button', + type: 'text', + attrs: { + x: bounding.width, + y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, + text: 'X', + align: 'right', + baseline: 'middle' + }, + styles: labelStyle('cancel-button') } ] }, @@ -157,7 +189,7 @@ const OrderLine = (): OrderOverlayCreate => { align: textAlign, baseline: 'middle' }, - styles: labelStyle() + styles: labelStyle('body') } }, onRightClick: (event): boolean => { @@ -178,26 +210,57 @@ const OrderLine = (): OrderOverlayCreate => { properties.text = text return this as OrderOverlay }, - setBodyBackgroundColor(color: string) { - properties.bodyBackgroundColor = color + setQuantity(quantity: number|string) { + properties.quantity = quantity return this as OrderOverlay }, - setBodyBorderColor(color: string) { - properties.bodyBorderColor = color + setModifyTooltip(tooltip: string) { + properties.modifyTooltip = tooltip + return this as OrderOverlay + }, + setTooltip(tooltip: string) { + properties.tooltip = tooltip + return this as OrderOverlay + }, + + setLineColor(color: string) { + properties.lineColor = color + return this as OrderOverlay + }, + setLineWidth(width: number) { + properties.lineWidth = width + return this as OrderOverlay + }, + setLineStyle(style: LineType) { + properties.lineStyle = style + return this as OrderOverlay + }, + setLineLength(length: number) { + properties.lineDashedValue = [length, length] + return this as OrderOverlay + }, + setLineDashedValue(dashedValue: number[]) { + properties.lineDashedValue = dashedValue + return this as OrderOverlay + }, + + setBodyFont(font: string) { + properties.bodyFont = font return this as OrderOverlay }, setBodyTextColor(color: string) { properties.bodyTextColor = color return this as OrderOverlay }, - setTooltip(tooltip: string) { - properties.tooltip = tooltip + setBodyBackgroundColor(color: string) { + properties.bodyBackgroundColor = color return this as OrderOverlay }, - setQuantity(quantity: number|string) { - properties.quantity = quantity + setBodyBorderColor(color: string) { + properties.bodyBorderColor = color return this as OrderOverlay }, + setQuantityFont(font: string) { properties.quantityFont = font return this as OrderOverlay @@ -214,10 +277,7 @@ const OrderLine = (): OrderOverlayCreate => { properties.quantityBorderColor = color return this as OrderOverlay }, - setModifyTooltip(tooltip: string) { - properties.modifyTooltip = tooltip - return this as OrderOverlay - }, + setCancelButtonIconColor(color: string) { properties.cancelButtonIconColor = color return this as OrderOverlay @@ -230,24 +290,21 @@ const OrderLine = (): OrderOverlayCreate => { properties.cancelButtonBorderColor = color return this as OrderOverlay }, - setLineColor(color: string) { - properties.lineColor = color - return this as OrderOverlay - }, - setLineWidth(width: number) { - properties.lineWidth = width + + setBorderStyle(style: LineType) { + properties.borderStyle = style return this as OrderOverlay }, - setLineStyle(style: LineType) { - properties.lineStyle = style + setBorderSize(size: number) { + properties.borderSize = size return this as OrderOverlay }, - setLineLength(length: number) { - properties.lineLength = length + setBorderDashedValue(dashedValue: number[]) { + properties.borderDashedValue = dashedValue return this as OrderOverlay }, - setLineDashedValue(dashedValue: number[]) { - properties.lineDashedValue = dashedValue + setBorderRadius(radius: number) { + properties.borderRadius = radius return this as OrderOverlay } } diff --git a/src/store/overlayStyle/positionStyleStore.ts b/src/store/overlayStyle/positionStyleStore.ts index 042ed2d1..94b24e3a 100644 --- a/src/store/overlayStyle/positionStyleStore.ts +++ b/src/store/overlayStyle/positionStyleStore.ts @@ -19,7 +19,7 @@ export const [buyStyle, setBuyStyle] = createSignal<{ lineStyle: LineStyle, labe paddingTop: 5, borderStyle: 'solid', borderSize: 1, - borderDashedValue: [1,1], + borderDashedValue: [0,0], borderRadius: 3, color: '#FFFFFF', borderColor: '#00698b', diff --git a/src/types/overlayTypes.ts b/src/types/overlayTypes.ts index a01f26a9..240f84b7 100644 --- a/src/types/overlayTypes.ts +++ b/src/types/overlayTypes.ts @@ -13,48 +13,91 @@ export interface OverlayProperties { borderWidth?: number } -export interface OrderLineProperties extends OverlayProperties { +export interface OrderLineProperties { price?: number + text?: string + quantity?: number|string + modifyTooltip?: string + tooltip?: string + + lineColor?: string + lineWidth?: number + lineStyle?: LineType + lineDashedValue?: number[] + + bodySize?: number + bodyWeight?: number | string + bodyFont?: string bodyBackgroundColor?: string - bodyTextColor?: string bodyBorderColor?: string - tooltip?: string - quantity?: number|string + bodyTextColor?: string + bodyPaddingLeft?: number + bodyPaddingRight?: number + bodyPaddingTop?: number + bodyPaddingBottom?: number + isBodyVisible: boolean + + quantitySize?: number + quantityWeight?: number | string quantityFont?: string quantityColor?: string quantityBackgroundColor?: string quantityBorderColor?: string - modifyTooltip?: string + quantityPaddingLeft?: number + quantityPaddingRight?: number + quantityPaddingTop?: number + quantityPaddingBottom?: number + isQuantityVisible: boolean + + cancelButtonSize?: number + cancelButtonWeight?: number | string cancelButtonIconColor?: string cancelButtonBackgroundColor?: string cancelButtonBorderColor?: string - lineColor?: string - lineWidth?: number - lineStyle?: LineType - lineLength?: number + cancelButtonPaddingLeft?: number + cancelButtonPaddingRight?: number + cancelButtonPaddingTop?: number + cancelButtonPaddingBottom?: number + isCancelButtonVisible: boolean + + borderStyle?: LineType, + borderSize?: number, + borderDashedValue?: number[], + borderRadius?: number } interface OrderOverlayAttributes { setPrice: (price: number) => OrderOverlay setText: (text: string) => OrderOverlay - setBodyBackgroundColor: (color: string) => OrderOverlay + setQuantity: (quantity: string) => OrderOverlay + setModifyTooltip: (tooltip: string) => OrderOverlay + setTooltip: (tooltip: string) => OrderOverlay + + setLineColor: (color: string) => OrderOverlay + setLineWidth: (width: number) => OrderOverlay + setLineStyle: (style: LineType) => OrderOverlay + setLineLength: (length: number) => OrderOverlay + setLineDashedValue: (dashedValue: number[]) => OrderOverlay + + setBodyFont: (font: string) => OrderOverlay setBodyTextColor: (color: string) => OrderOverlay + setBodyBackgroundColor: (color: string) => OrderOverlay setBodyBorderColor: (color: string) => OrderOverlay - setTooltip: (tooltip: string) => OrderOverlay - setQuantity: (quantity: string) => OrderOverlay + setQuantityFont: (font: string) => OrderOverlay setQuantityColor: (color: string) => OrderOverlay setQuantityBackgroundColor: (color: string) => OrderOverlay setQuantityBorderColor: (color: string) => OrderOverlay - setModifyTooltip: (tooltip: string) => OrderOverlay + setCancelButtonIconColor: (color: string) => OrderOverlay setCancelButtonBackgroundColor: (color: string) => OrderOverlay setCancelButtonBorderColor: (color: string) => OrderOverlay - setLineColor: (color: string) => OrderOverlay - setLineWidth: (width: number) => OrderOverlay - setLineStyle: (style: LineType) => OrderOverlay - setLineLength: (length: number) => OrderOverlay - setLineDashedValue: (dashedValue: number[]) => OrderOverlay + + setBorderStyle: (style: LineType) => OrderOverlay + setBorderSize: (size: number) => OrderOverlay + setBorderDashedValue: (dashedValue: number[]) => OrderOverlay + setBorderRadius: (radius: number) => OrderOverlay + onMoveStart?: (params: T, callback: (params: T) => void) => void onMove?: (params: T, callback: (params: T) => void) => void onMoveEnd?: (params: T, callback: (params: T) => void) => void From 99d7f12de1adbdae9ad6380af67d13617fa6ac4a Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Mon, 27 Oct 2025 10:39:10 +0100 Subject: [PATCH 17/20] saving changes temporarily --- src/extension/position/orderLine.ts | 39 ++++++++++++++++++----------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/extension/position/orderLine.ts b/src/extension/position/orderLine.ts index 654f6d55..ce8ab2e9 100644 --- a/src/extension/position/orderLine.ts +++ b/src/extension/position/orderLine.ts @@ -16,6 +16,7 @@ import { Coordinate, LineStyle, LineType, TextStyle, utils } from 'klinecharts' import { OrderLineProperties, OrderOverlay, OrderOverlayCreate } from '../../types/overlayTypes' import { buyStyle } from '../../store/overlayStyle/positionStyleStore' import { getPrecision } from '../../helpers' +import { instanceapi } from '../../ChartProComponent' // import { useOverlaySettings } from '../../../store/overlaySettingStore' const OrderLine = (): OrderOverlayCreate => { @@ -62,11 +63,16 @@ const OrderLine = (): OrderOverlayCreate => { createPointFigures: ({chart, yAxis, overlay, coordinates, bounding }) => { console.info('Position Line price is set to: ', properties.price) const precision = getPrecision(chart, overlay, yAxis) - // let text = useOrder().calcPL(overlay.points[0].value!, precision.price, true) - // useOrder().updatePipsAndPL(overlay, text) if (properties.price !== undefined) { console.info('calculated y coordinate is: ', (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y) } + const bodyStyle = labelStyle('body') + const quantityStyle = labelStyle('quantity') + const cancelStyle = labelStyle('cancel-button') + const cancelText = 'X' + const quantityMarginRight = utils.calcTextWidth(cancelText) + cancelStyle.paddingLeft + cancelStyle.paddingRight + cancelStyle.borderSize + const bodyMarginRight = utils.calcTextWidth((properties.quantity ?? 'Size').toString()) + quantityStyle.paddingLeft + quantityStyle.paddingRight + quantityStyle.borderSize + quantityMarginRight + return [ { type: 'line', @@ -87,17 +93,20 @@ const OrderLine = (): OrderOverlayCreate => { }, // { // key: 'body', - // type: 'rect', + // type: 'text', // attrs: { // x: bounding.width - 90, // y:(properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y! : coordinates[0].y) - 10, - // width: 70, - // height: 20 + // align: 'right', + // baseline: 'middle', + // text: 'Testing' // }, // styles: { // style: 'stroke', // // color - // color: '#ff000020', + // color: '#000000ff', + + // backgroundColor: '#00698b', // // border style // borderStyle: 'dashed', // // border color @@ -105,7 +114,7 @@ const OrderLine = (): OrderOverlayCreate => { // // frame size // borderSize: 1, // // border dotted line parameters - // borderDashedValue: [0, 0], + // borderDashedValue: [2, 2], // // Border fillet value // borderRadius: 3 // } @@ -114,37 +123,37 @@ const OrderLine = (): OrderOverlayCreate => { key: 'body', type: 'text', attrs: { - x: bounding.width, + x: 100 + bounding.right, y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, text: properties.text ?? 'Position Line', align: 'right', baseline: 'middle' }, - styles: labelStyle('body') + styles: bodyStyle }, { key: 'quantity', type: 'text', attrs: { - x: bounding.width - chart.calcTextWidth(properties.text ?? 'Position Line') - 10, + x: 30 + bounding.left, y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, - text: properties.text ?? 'Position Line', + text: (properties.quantity ?? 'Size').toString(), align: 'right', baseline: 'middle' }, - styles: labelStyle('quantity') + styles: quantityStyle }, { key: 'cancel-button', type: 'text', attrs: { - x: bounding.width, + x: bounding.width - 50, y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, - text: 'X', + text: cancelText, align: 'right', baseline: 'middle' }, - styles: labelStyle('cancel-button') + styles: cancelStyle } ] }, From b033bbffeee5a1a8f931fbb435b1887d180e7b47 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Mon, 27 Oct 2025 19:34:28 +0100 Subject: [PATCH 18/20] full implementation of order-line overlay completed --- src/extension/position/orderLine.ts | 189 ++++++++++++++----- src/helpers.ts | 9 + src/iconfonts-old/fonts/icomoon.eot | Bin 0 -> 2548 bytes src/iconfonts-old/fonts/icomoon.svg | 14 ++ src/iconfonts-old/fonts/icomoon.ttf | Bin 0 -> 2384 bytes src/iconfonts-old/fonts/icomoon.woff | Bin 0 -> 2460 bytes src/iconfonts-old/style.css | 47 +++++ src/iconfonts/fonts/icomoon.eot | Bin 2548 -> 4608 bytes src/iconfonts/fonts/icomoon.svg | 40 +++- src/iconfonts/fonts/icomoon.ttf | Bin 2384 -> 4444 bytes src/iconfonts/fonts/icomoon.woff | Bin 2460 -> 4520 bytes src/iconfonts/ie7/ie7.css | 55 ++++++ src/iconfonts/ie7/ie7.js | 49 +++++ src/iconfonts/style.css | 67 ++++++- src/store/overlayStyle/positionStyleStore.ts | 2 +- src/store/positionStore.ts | 17 -- src/types/overlayTypes.ts | 28 ++- src/types/types.ts | 2 + 18 files changed, 435 insertions(+), 84 deletions(-) create mode 100644 src/iconfonts-old/fonts/icomoon.eot create mode 100644 src/iconfonts-old/fonts/icomoon.svg create mode 100644 src/iconfonts-old/fonts/icomoon.ttf create mode 100644 src/iconfonts-old/fonts/icomoon.woff create mode 100644 src/iconfonts-old/style.css create mode 100644 src/iconfonts/ie7/ie7.css create mode 100644 src/iconfonts/ie7/ie7.js delete mode 100644 src/store/positionStore.ts diff --git a/src/extension/position/orderLine.ts b/src/extension/position/orderLine.ts index ce8ab2e9..34b789f7 100644 --- a/src/extension/position/orderLine.ts +++ b/src/extension/position/orderLine.ts @@ -12,18 +12,26 @@ * limitations under the License. */ -import { Coordinate, LineStyle, LineType, TextStyle, utils } from 'klinecharts' -import { OrderLineProperties, OrderOverlay, OrderOverlayCreate } from '../../types/overlayTypes' +import { Coordinate, LineStyle, LineType, OverlayEvent, Point, TextStyle, utils } from 'klinecharts' +import { OrderLineProperties, OrderOverlay, OrderOverlayCreate, OverlayEventListenerParams } from '../../types/overlayTypes' import { buyStyle } from '../../store/overlayStyle/positionStyleStore' -import { getPrecision } from '../../helpers' -import { instanceapi } from '../../ChartProComponent' +import { convertFontweightNameToNumber, getPrecision } from '../../helpers' +import { FontWeights } from "../../types/types" // import { useOverlaySettings } from '../../../store/overlaySettingStore' +const executeCallback = (listener?: OverlayEventListenerParams, event?: OverlayEvent) => { + if (listener) { + const { params, callback } = listener + callback(params, event) + } +} + const OrderLine = (): OrderOverlayCreate => { let properties: OrderLineProperties = { isBodyVisible: true, isCancelButtonVisible: true, isQuantityVisible: true, + marginRight: 10 } const lineStyle = (): LineStyle => { @@ -37,10 +45,10 @@ const OrderLine = (): OrderOverlayCreate => { const labelStyle = (type: 'body'|'quantity'|'cancel-button'): TextStyle => { return { - style: 'fill', + style: buyStyle().labelStyle.style, size: (type == 'body' ? properties.bodySize : type == 'quantity' ? properties.quantitySize : properties.cancelButtonSize) ?? properties.bodySize ?? buyStyle().labelStyle.size, weight: (type == 'body' ? properties.bodyWeight : type == 'quantity' ? properties.quantityWeight : properties.cancelButtonWeight) ?? properties.bodyWeight ?? buyStyle().labelStyle.weight, - family: (type == 'body' ? properties.bodyFont : type == 'quantity' ? properties.quantityFont : properties.bodyFont) ?? properties.bodyFont ?? buyStyle().labelStyle.family, + family: (type == 'body' ? properties.bodyFont : type == 'quantity' ? properties.quantityFont : 'icomoon') ?? properties.bodyFont ?? buyStyle().labelStyle.family, color: (type == 'body' ? properties.bodyTextColor : type == 'quantity' ? properties.quantityColor : properties.cancelButtonIconColor) ?? properties.bodyTextColor ?? buyStyle().labelStyle.color, backgroundColor: (type == 'body' ? properties.bodyBackgroundColor : type == 'quantity' ? properties.quantityBackgroundColor : properties.cancelButtonBackgroundColor) ?? properties.bodyBackgroundColor ?? buyStyle().labelStyle.backgroundColor, borderColor: (type == 'body' ? properties.bodyBorderColor : type == 'quantity' ? properties.quantityBorderColor : properties.cancelButtonBorderColor) ?? properties.bodyBorderColor ?? buyStyle().labelStyle.borderColor, @@ -57,10 +65,11 @@ const OrderLine = (): OrderOverlayCreate => { return { name: 'orderLine', totalStep: 2, - needDefaultPointFigure: true, + needDefaultPointFigure: false, needDefaultXAxisFigure: false, needDefaultYAxisFigure: true, createPointFigures: ({chart, yAxis, overlay, coordinates, bounding }) => { + console.info('bounding is: ', bounding) console.info('Position Line price is set to: ', properties.price) const precision = getPrecision(chart, overlay, yAxis) if (properties.price !== undefined) { @@ -69,12 +78,19 @@ const OrderLine = (): OrderOverlayCreate => { const bodyStyle = labelStyle('body') const quantityStyle = labelStyle('quantity') const cancelStyle = labelStyle('cancel-button') - const cancelText = 'X' - const quantityMarginRight = utils.calcTextWidth(cancelText) + cancelStyle.paddingLeft + cancelStyle.paddingRight + cancelStyle.borderSize - const bodyMarginRight = utils.calcTextWidth((properties.quantity ?? 'Size').toString()) + quantityStyle.paddingLeft + quantityStyle.paddingRight + quantityStyle.borderSize + quantityMarginRight + const cancelText = '\ue900' + const quantityText = (properties.quantity ?? 'Size').toString() + const bodyText = properties.text ?? 'Position Line' + const cancelMarginRight = properties.marginRight + const quantityMarginRight = utils.calcTextWidth(cancelText) + cancelStyle.paddingLeft + cancelStyle.paddingRight + cancelMarginRight - (properties.borderSize ?? buyStyle().labelStyle.borderSize) + const bodyMarginRight = utils.calcTextWidth((quantityText).toString()) + quantityStyle.paddingLeft + quantityStyle.paddingRight + quantityMarginRight + const lineMarginRight = utils.calcTextWidth((bodyText).toString()) + bodyStyle.paddingLeft + bodyStyle.paddingRight + bodyMarginRight + console.info('bodyMarginRight: ', bodyMarginRight, ' quantityMarginRight: ', quantityMarginRight, ' cancelMarginRight: ', cancelMarginRight, lineMarginRight) + console.info('cancel text width is: ', utils.calcTextWidth(cancelText), 'X widht is: ', utils.calcTextWidth('X')) return [ { + key: 'price-line', type: 'line', attrs: { coordinates: [ @@ -82,6 +98,24 @@ const OrderLine = (): OrderOverlayCreate => { x: 0, y: properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y }, + { + x: bounding.width - lineMarginRight, + y: properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y + } + ] + }, + styles: lineStyle(), + ignoreEvent: true + }, + { + key: 'price-line-two', + type: 'line', + attrs: { + coordinates: [ + { + x: bounding.width - cancelMarginRight, + y: properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y + }, { x: bounding.width, y: properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y @@ -91,41 +125,13 @@ const OrderLine = (): OrderOverlayCreate => { styles: lineStyle(), ignoreEvent: true }, - // { - // key: 'body', - // type: 'text', - // attrs: { - // x: bounding.width - 90, - // y:(properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y! : coordinates[0].y) - 10, - // align: 'right', - // baseline: 'middle', - // text: 'Testing' - // }, - // styles: { - // style: 'stroke', - // // color - // color: '#000000ff', - - // backgroundColor: '#00698b', - // // border style - // borderStyle: 'dashed', - // // border color - // borderColor: '#002affff', - // // frame size - // borderSize: 1, - // // border dotted line parameters - // borderDashedValue: [2, 2], - // // Border fillet value - // borderRadius: 3 - // } - // }, { key: 'body', type: 'text', attrs: { - x: 100 + bounding.right, + x: bounding.width - bodyMarginRight, y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, - text: properties.text ?? 'Position Line', + text:bodyText, align: 'right', baseline: 'middle' }, @@ -135,9 +141,9 @@ const OrderLine = (): OrderOverlayCreate => { key: 'quantity', type: 'text', attrs: { - x: 30 + bounding.left, + x: bounding.width - quantityMarginRight, y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, - text: (properties.quantity ?? 'Size').toString(), + text: quantityText, align: 'right', baseline: 'middle' }, @@ -147,7 +153,7 @@ const OrderLine = (): OrderOverlayCreate => { key: 'cancel-button', type: 'text', attrs: { - x: bounding.width - 50, + x: bounding.width - cancelMarginRight, y:properties.price ? (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y : coordinates[0].y, text: cancelText, align: 'right', @@ -201,14 +207,39 @@ const OrderLine = (): OrderOverlayCreate => { styles: labelStyle('body') } }, - onRightClick: (event): boolean => { - // useOverlaySettings().singlePopup(event, 'buy') - return true + // onRightClick: (event): boolean => { + // // useOverlaySettings().singlePopup(event, 'buy') + // return false + // }, + onPressedMoveStart: (event): boolean => { + executeCallback(properties.onMoveStart, event) + + return false }, - // onClick: (event): boolean => { - // event.figure?. - // return true - // } + onPressedMoving: (event): boolean => { + properties.price = (event.chart.convertFromPixel([{ y: event.y, x: event.x }], { paneId: 'candle_pane' }) as Partial).value + executeCallback(properties.onMove, event) + + return false + }, + onPressedMoveEnd: (event): boolean => { + executeCallback(properties.onMoveEnd, event) + + return false + }, + onClick: (event): boolean => { + switch(event.figure?.key) { + case 'cancel-button': + executeCallback(properties.onCancel, event) + break; + case 'quantity': + executeCallback(properties.onModify, event) + break; + default: + } + return false + }, + setPrice(price: number) { console.info('setPrice called with price: ', price) properties.price = price @@ -257,6 +288,13 @@ const OrderLine = (): OrderOverlayCreate => { properties.bodyFont = font return this as OrderOverlay }, + setBodyFontWeight(weight: FontWeights | number) { + if (utils.isString(weight)) { + weight = convertFontweightNameToNumber(weight as FontWeights) + } + properties.bodyWeight = weight + return this as OrderOverlay + }, setBodyTextColor(color: string) { properties.bodyTextColor = color return this as OrderOverlay @@ -274,6 +312,13 @@ const OrderLine = (): OrderOverlayCreate => { properties.quantityFont = font return this as OrderOverlay }, + setQuantityFontWeight(weight: FontWeights | number) { + if (utils.isString(weight)) { + weight = convertFontweightNameToNumber(weight as FontWeights) + } + properties.quantityWeight = weight + return this as OrderOverlay + }, setQuantityColor(color: string) { properties.quantityColor = color return this as OrderOverlay @@ -287,6 +332,13 @@ const OrderLine = (): OrderOverlayCreate => { return this as OrderOverlay }, + setCancelButtonFontWeight(weight: FontWeights | number) { + if (utils.isString(weight)) { + weight = convertFontweightNameToNumber(weight as FontWeights) + } + properties.cancelButtonWeight = weight + return this as OrderOverlay + }, setCancelButtonIconColor(color: string) { properties.cancelButtonIconColor = color return this as OrderOverlay @@ -315,7 +367,44 @@ const OrderLine = (): OrderOverlayCreate => { setBorderRadius(radius: number) { properties.borderRadius = radius return this as OrderOverlay + }, + + onMoveStart(params: T, callback: (params: T) => void) { + properties.onMoveStart = { + params: params, + callback: callback as (params: unknown) => void, + } + return this as OrderOverlay + }, + onMove(params: T, callback: (params: T) => void) { + properties.onMove = { + params: params, + callback: callback as (params: unknown) => void, + } + return this as OrderOverlay + }, + onMoveEnd(params: T, callback: (params: T) => void) { + properties.onMoveEnd = { + params: params, + callback: callback as (params: unknown) => void, + } + return this as OrderOverlay + }, + onCancel(params: T, callback: (params: T) => void) { + properties.onCancel = { + params: params, + callback: callback as (params: unknown) => void, + } + return this as OrderOverlay + }, + onModify(params: T, callback: (params: T) => void) { + properties.onModify = { + params: params, + callback: callback as (params: unknown) => void, + } + return this as OrderOverlay } + } } diff --git a/src/helpers.ts b/src/helpers.ts index fabc88c7..5bb1ee58 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,4 +1,5 @@ import { Chart, Nullable, Overlay, YAxis } from "klinecharts" +import { FontWeights } from "./types/types" export const getScreenSize = () => { return {x: window.innerWidth, y: window.innerHeight} @@ -22,4 +23,12 @@ export const getPrecision = (chart: Chart, overlay: Overlay, yAxis: Nul } return precision +} + +export const convertFontweightNameToNumber = (weight: FontWeights): number => { + const weights: { [key: string]: number } = { + 'thin': 100, 'extra-light': 200, 'light': 300, 'normal': 400, 'medium': 500, 'semi-bold': 600, 'bold': 700, 'extra-bold': 800, 'black': 900 + } + + return weights[weight] } \ No newline at end of file diff --git a/src/iconfonts-old/fonts/icomoon.eot b/src/iconfonts-old/fonts/icomoon.eot new file mode 100644 index 0000000000000000000000000000000000000000..2a32810836f0d5f12b9bcc51cca79b9b58b97933 GIT binary patch literal 2548 zcmbVOO>7%Q6n?X7@1J9@?f5UTvvzE}E}~hdw(AWT;!62znzRW?s04vfH*uZf#14+r zHYjQfLI^4(%8haZfrOCYP^m)VPwlBf;>3vy5-9DV;tCQ7;qYeHN$e5{iIHaJy>Gtv z=FQLBo!>-&X{-boXfFh=6N*eux8sy+mhC%NRvsP$aKL$3get6JtwIF}4w!)=lwbo^ zpa3;Qd~h0(8muFiaw2d9H0U?D4_hNhC$zy;V4;6%Hkp#$*1L#&jrGjpYGKWP`iE;s z3ZXt%Dy*+z??j#ULTP35+`aLwo2Xv`uwGp*7M5azH&N&Q!13%d0{mO-cK|${{*mR? z`XwKbk5QjPJ+e|=ELb?^Q`GCIyH^XB)^I`Jp#CNuuM}2`H%^Fuq5cWZ@y1$py^e8W z5vjijuai8_f5Jykqv{S4b1w_qIYX?DldD-~r3t-nk9OSgbg>yZMwCp^Y&c zoiT)CX3rX|5LjRUaR~%GTn4eo19s={ogHd~G8i)1+4ft|uGKFAf-JcO!^@#RZcz4l z|ApOxGCRTAK*hZv*-S!>@*=^8^Y}%AjY86?tS%C>M*?9q5|kvVTWZ{I+{aFmNU*Uj zNftZL*{vit}n+bxpR*e2hS9)r@jM-Y|_YcGg? zK@{gEXACr4o|>+l-0B|}{h}ZYJ^N@WUl<)*2zJ{T#y&Z=B|GPGU48D0GiZ`?$K(z5 zd3}%2F*NwvDJfu&r3L=Nan8=g(hO7nN#9hQz0OcNb52#m-3x*s1cYY>UhhRPm`RsZ zHKB})B7#FgK#4}5)A|CO#3`?&w%mPPxml;<;)imq^NBf8*y!~4M(6vqfYoLVRgb*N z@IpFf4@ei@W0+Ko@8DhnynD5sKiRwNDVT;2;4}CVuP)sV+%cK)`#706NRrU@F%ngi z49}@aqLDm;8hMIjvUw({s52eCQ2%<|I6w11b&n74CLS3*Ep}w4vzzU5 z&${|;tRv%y%Oi2w(&HZY^stWjh#W_ZwWVCM?yi8mPa2CmtVnW)h#Vhw#vN&g#n$f% z&d&=SY!Ay=Glx!avM?-2#5Le#L}8S3aTAG*g~ibhp-E#cnZ$%a6B*}#i%16T#G#Cp zF=&m;!&-7^D5(wS6|YZGd|rjIIoKZ0xF^KQd%^Y7;96HOX0D(!^sg1T_E#_xf7g}$ z-=P^R2)8Ztur_RlYeuSg@jGO}+$>{mdLarU@H8v{#G^F3)Yj~zH^ + + +Generated by IcoMoon + + + + + + + + + + \ No newline at end of file diff --git a/src/iconfonts-old/fonts/icomoon.ttf b/src/iconfonts-old/fonts/icomoon.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6e7c6be58d55201715e41ead9fac7bc27ca7b7b3 GIT binary patch literal 2384 zcmbVOU2Gdg5T3oW@6WN%cKnyvIXgB!7tx$u+w}!Z<4XB!nzji^s04vfH*uZd#14+r zHYh3uAp{i?@J4w9frOCYp;Cp!pW3Gii6@?TK?0?HsCY&KAslA!E~#58A+gr(d^0;U zJF~lUYajsNfXl!_W@`DES8anZ>p8dhqm*R{%%^`MGL&V;!j*c}j)q>ejj2 z6WiC2zX)Kzyj&?SC5Emc&;5b(g=G}*Z?WG4@EPPESzc>h3;_8A`8nictBu97jblDT zzJz09}pt2+o<5HMAmNgF%!_AOM3u z06ZXOExAL+0O*e!D);pMOLiS<>?G>|759P^@+mdW ziv$PG7ZeE&3dv;)x=2il3WU)}Sdye(sePw?2Z+qb2#WWz@K+B=jYORt3;O3-9)vSW~e6R->$a0&C#NJ`D?1$~I< zgdP22Cob&r|v7d&Ixj3#%QEJmujnS91=f$8PigPDs3~97BHC;csotY4W zq96=E^JukH9vfc>_c|EHd18E9cFz@i2D}$$(4^>1$eZeO`o2Qf@X%|gq>wX_6ZrGT zI476LF-+}eeM@ooyCb>$IaQ7JE(n4U5}qD>y&r|)e6FghDP=+wQ8+Awlz9AEZ6L%+ zobqyZ+dI%xoOQb{d?Y8jpO_Pc&F)}-e11R+*&X&seB7N9g~@0fRl-fqzN4#BXKp&@SK_^8Y!VrBS%QSP-0SQ zp+tsA6oo^iNV0(_qZa~EJRq3_dHpb+L7GsV6z(@QOOpg5nNU|eE=Ik}#E&RS=!!u+ zJ!&2$IYcEwo$2U>`qz`j`I!f*`vQ13@yO_Dv7`FWv>?PD2x{=jig7Dfb# zcm~~!D2#C)ZZeg(u{hf$SZb^-pPDq(M8-YnA(EkX{6OB$7;5cHBU*ZRIIWG86n{Wb z0)B;YxY$15gfGI%`@v;saGe>9n;CRR{*{5}UIwGd_dJFF9h#9r^u9z7Y9nU2W~7QA zzeF~y%^KFGAL1|yPr(8}GES>Y?W|6Ea{?V@NxvTfL)cN0s$sTQefGF&mLwi&$|ySP zNEv8MP1h8qYrp*J(^Ivm$s2{&ZZV9bH@q-DR^BhF`+4$qac-_idm@)h&|Xv3bGdxj z9jC>F74`s2&S0&CG;KcK-;bqblujpmb+%ZXrF|!xNMvcpy|KQvtFPSLc|m^icW~H@ zPxmheztkx(sa0k8*5NnaY-ne^8MwK$vA6a-nbSB5=8VZxGvu#hFJW&|YRpLB0|l0d z!ep5nD53EP@HKtSuQ*q4`kF zu|~ZWt5)ijX1P^aik;nxO)NG}Vr&|W8Edz2^(wll8xfVEg>(sJXJHG+2_u1%W&#+a M`EQ`}R357%Q6n?X7@1J9@?f5UTvvzE}E~43_w(AWT;z~)JrfHjygh~(ybraVKPVC|& zZG)onBZNeS1l%a%00=HfaHv!v@uyTms*pG#4qT8xX%7`wI3R?>n^`w?X(S}ZdNbeq z=FNNavv0;1CW}QNfJJ@}9ITz9^?qe?rauipBk$e#h9aQMqzV{_9+Q zZ5`K3V_0@PBA$Pjyajdu?h5xk>Tg;t`3*8eM!mfdce_UM5rEq~zrDK+M|c!u2+Exj zV}M0L6u~}BeK-Dpao3>APw)=VaZgAto6_T=Ot2DsL78BskxV*g%EW5XfN%y0D~i&sG;cL; zVWTJ{+}u$Vn^P2=b`mCGyHgOIHbrUfkZ(v2Gilx=nlL9B@z5A zpa2d2I$wkVI1Wp&2A6O>21)5zGiMGFGnYvRf@Urq4ER)>4>qD8x`-$w;(AIH;%HbP zCY>m=HgU2zTGWW&XPKb0!&%FVK@xE`mM#>1nbtxNXVDXyCE|kLhrxatKI#&L4o$00 zP1Z-BH=dD$k}S_2pJr^dIyqH4u{AI*2W3ebe)7Rep;R234|h8_&Ut)nOLfoYyZXEr zrg2E#n@~6Or_DWsj^UwKPAVa1A|r|Cj|xs9k>R-N&*rA)?sZ2p*|WMH?Vgt;DI`5H z_-Zeb;cTX&>nUwqmXREmLRvijl+hOw6hV6-z2)ue%Fno67d})IosZ1Q(ne>nH$K;A zgzOG`q<-jSPLwhUXGl5!4#%YvVu$b?;J4S<{gc1JpM)v+06v8;@Ux=Zfjg#h!GNF= z7wIRoeu%{NeohqheqxXUk_LH+y>9V7dH$v3p zh&$=ZxNMFAPk3%l>fn2L&YnGROi-l}NgGE z2R%e#X2%X>?Hn^}UK%m_hll%(k%HzAXj;IpaSj*X;~V!ycy(`b19WojD;Tp@&>6XJ z1)jSr7)`$I$^GxCu@yw`S?EDy#G0-(Q_YW;iw$$Lin-~BIE=#MFb|N7)9ljQvy*-~ zfwr`y_eX${Z7Hdb0uULbMf9@Of8l=?dp}8 ze13+?PCAiDQ(^ZoyM0$)yuS0S`sg3vurd9YgP)reSZyoI=zEAaGAnpTvo`}bmlpm; z+fB|GkgOg{p~FzWhEhOj(3bTm-~$a7iN>Y5ZDi=(FTt1eHN$cz8dF-83qEVbA|N7X zy%rqe(=E&+&b6=&@kR^V@n3?!g$3j%T3CT@xJ=(Cn~aPLdo0W$uCy?Z_^}qYA>L|X zI|Lxs!UFQsEv!HmK3ZO=uh#1|ScV0t!zxN0YNyJLwdHy(cE}ijQ&5Hmz7@-~1N|HV z0|xdTwJ$kcu57H78W^;U6WxFnC_!Ui%VNE@9;=jV`ypTM~pP-L;! kF?t2v)Yyzlu#W8_a%W%@>v6V#6V?K7j(FhhM{+;@2lb*|KL7v# literal 0 HcmV?d00001 diff --git a/src/iconfonts-old/style.css b/src/iconfonts-old/style.css new file mode 100644 index 00000000..d72d33a5 --- /dev/null +++ b/src/iconfonts-old/style.css @@ -0,0 +1,47 @@ +@font-face { + font-family: 'icomoon'; + src: url('fonts/icomoon.eot?f4efml'); + src: url('fonts/icomoon.eot?f4efml#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?f4efml') format('truetype'), + url('fonts/icomoon.woff?f4efml') format('woff'), + url('fonts/icomoon.svg?f4efml#icomoon') format('svg'); + font-weight: normal; + font-style: normal; + font-display: block; +} + +[class^="icon-"], [class*=" icon-"] { + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'icomoon' !important; + speak: never; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-icon-close:before { + content: "\e900"; + color: #fff; +} +.icon-icon-invisible:before { + content: "\e901"; + color: #fff; +} +.icon-icon-setting:before { + content: "\e902"; + color: #fff; +} +.icon-icon-visible:before { + content: "\e903"; + color: #fff; +} +.icon-icon-cancel:before { + content: "\ea0f"; + color: #fff; +} diff --git a/src/iconfonts/fonts/icomoon.eot b/src/iconfonts/fonts/icomoon.eot index 2a32810836f0d5f12b9bcc51cca79b9b58b97933..9bb535ce6b3300439afcff8eee1f707ff30de2fe 100644 GIT binary patch literal 4608 zcmb_gZ){sv6+ibq`#sxtf4a8W9FneT(>5e+)AVoL z^v~8#)nw2#4aT%iOu+b*AtcZQ6Cd~lV|uI_tcVXoLV^!e6tYSrd_f@K1I-J+d!L=! zZHg%3+V|db&O7(qbI!fz-1{E25*^lv7)jJv7+q(aGB(~w=e%~AeEaC>`=1f{=oB5J zGMz&$(*hWLG(mGTPnYQ|&C&%>!n7Zh3v>~@oF`7(DNDVA_x{l!@(G)$LNfK1CVO)J zR&^4TcTs1Kotr)X>%YGK1el`0U!0%4cpiNV@Fn!F`LkC~+?f7s7x3>Ad0i*xW{+<> z_554V>IZn z0Dl8`d|~$7+?#vc{{sF)%<*H)NP1KHB`B}iJ^M4|nVa*~ z6$Z5%OOa@GST@&9D$1yM$bC^OaRKnzc#Yzj-OGY2N)9>+dKli2NdYasBClLu`QB>N zs|{oNJ{OJ{{kcdu=!ZV&R7LeMRY@iFjH)ET zILHjHsNgoBEJmv_77E%d5Y|_)rDRzuDaLX`ASwC!SFf6hqh(goK4PXdHWY6)s;_q+dR*HLy~eSwOK(LaR{IP96leXx9I!yBW!i< z4tUHjMZ$`o`B)F*?I&4M?~znR?_pUs2x68!!3zC@Qbz9|WII_Gh&$N;%Z0lnqd(k* z1@g0qYV=_ZdKl-)z~6jW4@&{^GcHQSWL&zRg#dLiK4S!{$A@>ZJfK0wg&B~-ThhL~&(YMYbsRe6YL+`?$ys=8kK%U?yF5(W(IUBBql%{N%@iCm zo;JIxY_g+}*(=!GQp*mFc?8>@#|lnKuvsq*XM2W*da}cVu~0Y`3x{G-lTYpp?hZy} z|AunCT)BD+cG(sLq7PW0-LqgM{kqowKdUAz=xQ+ZQ`uo#U0c&w2>Hg|Cp%x-fkFX%tg`_Tq)uPXt)a{VOl4eBERhLwva%ucbEY`dcehcHJ zGhH?=0?)c6Nlon?)4N7zH-f4WXI~lEw{L*gR6d{L^^C5c$QL?Vl029YVfUeI2C>qX z%^pf75(q63I`!zAlLG^jye{WbsT{BH4YKaKDOKQ`EtEq1q_86qElQ`zA{sVn0=rZz z=-9hFJ~~-K8gE%FRZ5F3!Duun#nD=a_4Z1;z7A<-X>qY+MuQfo2ifgl)NI%FcBFW5 z4k1V4CuZWZ?#+hm7}AjG5^$-8FiCL7%2Zmc2+?A$HAsWZ>x(71f=O(_4O5jC`8=w~ z3*06*c}b%XY}*WRV9f^(s`?-S$8v9nv;=oK_cm07pW$-&9XU#u zZ(BM2sF=$S+i@iDwd6A4WrYG>8Bzn@yG3t6 ziVKi87>66dx+%tWkU>KbWS=%6ao8Yji4&?%98kJ!M^axw5c4?^F`=M1o&st{ji@-D zjEo`C=_AvxJcjbh^pVr`@cyx}#dsnSUmP3TUkC45TAHc-W%I#dwurkk4-Bjdk(O$s zA*Rlz9W+YMz^2Afw$I|Kh4;sOy}g-0P*IW0tCl(o{RWPJRMiXe>8;(}hqveQ`P}xy z-Q8QaMx#1z4jw%k-TLq+VLh?7(zCd;G&*`J7F#1dAw@P&x%6;@{AR~y~HZWd_@jffb3i-_Pv6XKbM_^)d?=x%6Ix1R z70YBro;)o4imb?J%|z;`R5XpdJS}pkf_z2{A|ceNB|>S9i%S6P zlS#;2M+bw+wqt3R*6yzJ%>3Hi-}8`2V8{i5?2#-S#+(Njb@s{vmhESLAo(UpNYmcbz%s zsPnY*4d;7GQh5>FT(dC!n4ixmH46k%ZG7}jRevV9Y+EG2GgZ0_xW5WJ0AH@cPQ2}h zs;~n3-YV>&cKR}QI&-)|@!^RLOMvIAunhQ#D(nD!tqMD#ZM+IApifj`4;AR!r;e4+ zmCFl_UoQ5~UATCvyb#}>?cMxy#ipY+B~HxEUp_l~q47J&rktbYg-h}IxrMn4vzO+M z$B$l(?><(33c6)+((z9vmte&_KAtRy4=A(1=jb?SN9iis-Qv^BQ`Ju|S*!%|!hb6N E24!5XQgVyoBM3ggRKeE2K!}aKf+BLx-3i*sGCTA8zS-F+C0--)s{+g^WqD9))C{dWv2FP3 z0B$Lk)4H1Ln14J1Sd=)mrf%gZHxoNKu%_*-K2Pmdh>L))lwDCXUC9cu{7(L0mIVKy zcmVu;#EGn4D7J&06Q_vd+GbjnWPV9pAa2psVh+7cxFg}fY1mNpm6K`ZllX!@+0Sim z6(Gog;tY&Kh*LAelKe%OXKd6MCNYQrI30%gi?F|J+i9m7Zq4jQheRWot9lm$qpt8D zjRQj?Zp&}qe_9pml9&-8^O*1X$B`E`F=@qw&ir}%L)68rbu4vqa@NUdv##X`>;9Rp z8MVezc -Generated by IcoMoon + + + +{ + "fontFamily": "icomoon", + "majorVersion": 1, + "minorVersion": 0, + "version": "Version 1.0", + "fontId": "icomoon", + "psName": "icomoon", + "subFamily": "Regular", + "fullName": "icomoon", + "description": "Font generated by IcoMoon." +} + + + - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/iconfonts/fonts/icomoon.ttf b/src/iconfonts/fonts/icomoon.ttf index 6e7c6be58d55201715e41ead9fac7bc27ca7b7b3..3f471aa832a9801755b1a2be672b0a0116192dc1 100644 GIT binary patch literal 4444 zcmb_fU2I!P6+UyXeXs4rb{zkuiR1XEzAk|}iS6VTvTZDqrrS1MhooDzX&aKZX`24T zO@DUVRkbo`wOUwK+Z8LIJY^9Q2tmaIPq3^mtqNPj!y+NU0~LjAB@$i`2za2m;hVX3 zYPYFG5o6EHIcLtCIp@rrGjoX%Q3I7prtac+SJvCCj)U?(>eTUb)8~Klw|BM^u?XJZZFOx;NQf=d^4p-hR@^K8 zl|eygYfE8U=xS&(#6lEY3;r51YS$JMD-9zCNnSnOvrfW6)hG)FEEWjs&0A73 zESV5%m^w7-AT*5;STFr~ja>AkES(bpLo2X}n& z1y_qZnRBQwJ+HWxWKNRK+|#c{o8nE8T>hk{b+k@89F7*pi`!m{gV>hO&1zaYy4&dl zanR8cO(Z5VTU!*D5`86mt$FLF{_!Ty%ir=Q{h!(IbX@j_=F_@Z5|Ed1 zQ7RVW(!DGQsDtqp!`MAOy@TZd4KOZDhZHVfPl^4rHdGIUa5k}Je7EGCWByioQ}ej5 zyFvEkJt^+Ibal^L?swKR?{5I_xJOf%;l22p3$_ExqMqwf(H{K>@k#0Ay!&4nZt>A9HAvbrxv|2-rqmY>rytE z%<>A~AnUFfQW?J4L`lR?5+@STqO^-F;=>`0Eq9AQA~k zF|_6pt+m{$twEYmTv#X?k$}nR0d_wSFd>vKf1#Xj*+~lJmY}-mbME-SqSkjmC z*VS6PVIMIs94Jj+i@lEz2q|CWrh518B+c3hXuD^iV2W6n1>g^njz-3kxqjUWZgDF zaoHeki5sd{Tu_>9MN)5G5OY}(F~NYip8RTB4Xe1G^t3L~>7$cxZb5l-^62SW_`t}> zLM$GSEsTsDsDbw^E>2bdvhn0JOT@#eCni>eNQ>3k5L08*HX5cEVN-o5TX%8A!pGyj z*50%~pr}aZ6-({;J{?y;vf>5#^ybdaBR$z%F57dYvvc$2NJPWS!KFnao1Z=s(&DSj zT?;#k!^3mY=qhP(DZGx#o&TMXdhzDhb}V#B;kXu!&J7P2fvhf9k@d47n>dGf7pJzd z9w$S?-znmb$?Zr)ja%lrd!pnAw0`|waFd*jv}_MO|SUuSRN zy=WdZ5B>aLogCahNG9>#u*!V)-ncIpjQdh?U((0Qrojq4d6@VWSb@>1fz(khbJ|10 zFc=w9p}a<{fDIFQTI5az`HUDuLa5yr52k!vTpVDpOhV=wIv8XWOr!w(3S|g|Aj5#M z5Q{WrKqS@2IsYEALb`Ij)rWg(qX4fQdmSE4t2TP}AiX!i+FAR=ZD!PFgj%>JBh+Sx zzfYKdV$q3P8`@#7%TV>^`0FxMHEu2A!f7WSYtNjis=5@OX1~1nv?O(J!dxvPLWtpT_b3 zM->uX6u`Ib2^N*Ik|F(5?v$^{H|1a2^0xQwS^Kd4wEbQC2TDSD4cuI_5dDPT&nQ(3 z1X5`{bhDyA6I_-p65y!{T?X7&fo*^0GHaPjhsfN^}mjMDqt`E?k@|&BuB& z-E@Fv=mNe2=lB5R^iYPnH=MO0acpMx^4aMNSab%8UZ%4&O&2!g94^gYip|c<&s>}qWH@zOr%mcdQO*S>_+XCY=@XfzFchE9NXjIN^HEezOa82}wcUZ~xa$Kn3~ DD|0N5 delta 519 zcmaivJxD@P6vzMfUB3klD_unFV=G7;vM7|6pn`&u)_SNZ=*x#XSOmHh)mBb5v<15(+()ctQ#`e^^}4WECe8!;Vj?C+M}lQy!#njS6C@aq_#B=4eqm zW_6$!oS&c6fR5R7+)=X)+bTmNy=ikG?$Yt-*1RyZo zwfy=1)2c}7_!764I&9w3fVp+@PPPzptbvJaB*d8kzMEC~w)pFF%d1yFI|`iF7Xh3voMwFA2bg3v{^#h|muIhFUFQSfVO~Sz z$1AhTb66fIeS_7T2dw{kxo~k6_FmuS69I|hLwbXl2@!4zzX1NW(bGSYG_?Y+EtJWt zD>*_oheWNck(`K%G49g}Pnh?76+l6ZF*t~paRrHqoI~$}nj(=v3vY-UH#WYz*|6!_ z^lT25x5{OV6VS=V+o0Pv?VIk+K~AUTb-&4X)i>>C2ur+j`joAUcSPUwS9mShK&rC3x+$Wj!H15{;- za&8sMVzwNjUXQ^7VZ9kcN|MH+d@eH-vQ)#+-V7vxEf$r$9xV2o!518o)EEjajpqx) z#pFq=-(s~M9-H9Q_NDyz%AsrBqgKDwVi`R4k@0$_tWRilqf%>Are3)D&q7rZZ<1rM-39VzIPX zUfTP51jIllJ+CN<(5Tf4;-IA^6pc zNc4DTvc=qB4iO z*n=+0coOh8o7P2RfSiU)Z)YB$9;=|53XktUkHQBtY#rc?Nf81dP zQ*)3y<3mkxN7`X(=ynB;AGb7$ZK7b#>^UGgEkhO?b?t2utd`bEfqG= zlt~=qY*wLZuZ!9^+krh9v%uN(*M^c^gM(ekp@ERs7Yg~jA)&z`ws}T9LD9LbTsKp$ z)`IJbW1JHfn^7`MK24?xiAPy< zDYenbPL8i8ELr~WabitKDtuZs`pi+q2uW;df(Kn?O9fKMk1vHnjoab3GM-;*H*g+! z`l=u_v<9a4kIZfdRXt9>*mwAFAFHu+I>zdfqMS`<0!>jCOo*^2P&$KHX-OuJMiTh@dUe6x$h5I-@TNJNX&#dGiMyDUE1 zXhaxqD&|Z1Vv{Er^ax?J`U$1A)T)#rP0JUHc`fMC8Qnu4c!FB1qO>B#^Lq&KiVt6j z$$GpRvZL39Oc#MmT?ms1bF4`6#S$0I*V=+K$h=X^izO^#2(DWyUu5gZJTEYtti(=S z#0%RxNe?0JZF^|Um3G(FTDxH+&`7K|jn#!*tD=)!u2lWr_#o(kCLiGV=YslgHMnmJ z)`PROrBO}KJ+3r2&Qu=97r;Q(2PRVSK?IIv-uAj8%;n76P!WEH%i(wAC`Ejruet4- z-L0?L-EKRb;Ru}pBII~y--&_wf#0KiEhlWpmB99r$%L0B;b5`1+1)~zgRH?k zya>t~pBEr18H6C?w(;`I25F1mP`&(uQbZ$?dNZ7uPVtEGdieF^mJ_mH#`UBoRDmp> zoPKi;%A31cSSuJ>pX$TN_=)emsB|LZL09LPQ$Mu|2~44H=@;+5PjB7bdQ1Nua-e)npLqXR z2fcp#F8$V>t*_GScrWTl^m2n79bQUXgH_Wppq| z%jrk~_~c3u3PG9%V<8r4N`pvJALsiZ)d=az^;RG5@tp#^a_m)jG$EVF^9SkuDcVNc zrtVO!HY3!+H5s8cJN$J@{{sz8-QLj-b6tk2H^*I;Z^6#Vpj~0}N@j&zAzLJ&M?^v7%UNDFM)?<$x z6tqlIZSff+28m5$`2VAFi8kUPA##R>gp{BO{}4OGE8;Ej=cbJ5N9L4y#Jp(!viUtJ zD!mSFrkRiYkloKHRSP&$X&mHMMSse<3|j=iGZnfBI9q{DfG<^GGyXB~R$vMAgB93D zTFDo1(y7S`iUT7CEC8Obz#`y%71#v$S_L*k+i(SzK%c0%mAMPEt8-_=%aPZthBw=4l{HZ=v^6csWM>{3uS*;C=6 zix3?Z=}>_}BweFFAh3(~Iz+@aZ&%P;Z}~DapP6|x5BA1eaX62{p%4(jW3GTzvs+4e z#8sYglnm{NEM)*l0{J$VYv=VMF6B6Tz}VE2J)Sx~K=uS+IN|zOE!Gk-DGDI2V9h+2 zH&Cl35F#sLJjErRvzLUNFCe?d*uymrq?$@cBnX4pL@>%F80oDn6>zZl#)~u!!{ebO z$B=#E@H4K + + +*/ + +(function() { + function addIcon(el, entity) { + var html = el.innerHTML; + el.innerHTML = '' + entity + '' + html; + } + var icons = { + 'icon-icon-close': '', + 'icon-icon-invisible': '', + 'icon-icon-setting': '', + 'icon-icon-visible': '', + 'icon-lock': '', + 'icon-unlocked': '', + 'icon-bin': '', + 'icon-plus': '', + 'icon-minus': '', + 'icon-cancel-circle': '', + 'icon-blocked': '', + 'icon-cross': '', + 'icon-checkmark': '', + 'icon-checkmark2': '', + 'icon-enter': '', + 'icon-exit': '', + 'icon-circle-down': '', + 'icon-circle-left': '', + '0': 0 + }, + els = document.getElementsByTagName('*'), + i, c, el; + for (i = 0; ; i += 1) { + el = els[i]; + if(!el) { + break; + } + c = el.className; + c = c.match(/icon-[^\s'"]+/); + if (c && icons[c[0]]) { + addIcon(el, icons[c[0]]); + } + } +}()); diff --git a/src/iconfonts/style.css b/src/iconfonts/style.css index cd63227f..81e61cd9 100644 --- a/src/iconfonts/style.css +++ b/src/iconfonts/style.css @@ -1,14 +1,15 @@ @font-face { font-family: 'icomoon'; - src: url('fonts/icomoon.eot?f4efml'); - src: url('fonts/icomoon.eot?f4efml#iefix') format('embedded-opentype'), - url('fonts/icomoon.ttf?f4efml') format('truetype'), - url('fonts/icomoon.woff?f4efml') format('woff'), - url('fonts/icomoon.svg?f4efml#icomoon') format('svg'); + src: url('fonts/icomoon.eot?mr6pu2'); + src: url('fonts/icomoon.eot?mr6pu2#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?mr6pu2') format('truetype'), + url('fonts/icomoon.woff?mr6pu2') format('woff'), + url('fonts/icomoon.svg?mr6pu2#icomoon') format('svg'); font-weight: normal; font-style: normal; font-display: block; } +@import url("./ie7/ie7.css"); [class^="icon-"], [class*=" icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ @@ -41,3 +42,59 @@ content: "\e903"; color: #fff; } +.icon-lock:before { + content: "\e98f"; + color: #fff; +} +.icon-unlocked:before { + content: "\e990"; + color: #fff; +} +.icon-bin:before { + content: "\e9ac"; + color: #fff; +} +.icon-plus:before { + content: "\ea0a"; + color: #fff; +} +.icon-minus:before { + content: "\ea0b"; + color: #fff; +} +.icon-cancel-circle:before { + content: "\ea0d"; + color: #fff; +} +.icon-blocked:before { + content: "\ea0e"; + color: #fff; +} +.icon-cross:before { + content: "\ea0f"; + color: #fff; +} +.icon-checkmark:before { + content: "\ea10"; + color: #fff; +} +.icon-checkmark2:before { + content: "\ea11"; + color: #fff; +} +.icon-enter:before { + content: "\ea13"; + color: #fff; +} +.icon-exit:before { + content: "\ea14"; + color: #fff; +} +.icon-circle-down:before { + content: "\ea43"; + color: #fff; +} +.icon-circle-left:before { + content: "\ea44"; + color: #fff; +} diff --git a/src/store/overlayStyle/positionStyleStore.ts b/src/store/overlayStyle/positionStyleStore.ts index 94b24e3a..f000027f 100644 --- a/src/store/overlayStyle/positionStyleStore.ts +++ b/src/store/overlayStyle/positionStyleStore.ts @@ -9,7 +9,7 @@ export const [buyStyle, setBuyStyle] = createSignal<{ lineStyle: LineStyle, labe dashedValue: [4, 4] }, labelStyle: { - style: 'fill', + style: 'stroke_fill', size: 12, family:'Helvetica Neue', weight: 'normal', diff --git a/src/store/positionStore.ts b/src/store/positionStore.ts deleted file mode 100644 index c6bbaa94..00000000 --- a/src/store/positionStore.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Chart, Nullable, Overlay, OverlayEvent, OverlayTemplate, Point, YAxis } from 'klinecharts'; -import { ExitType, OrderInfo, OrderModifyInfo, OrderResource, OrderType } from '../types/types'; -import { createSignal } from 'solid-js'; -import { currenttick } from './tickStore'; -import { instanceapi, symbol } from '../ChartProComponent'; -import { getPrecision } from '../helpers'; -// import { setOrderModalVisible } from './chartStateStore'; -// import { syntheticPausePlay } from './keyEventStore'; - -export const [chartapi, setChartapi] = createSignal>(null); -export const [ordercontr, setOrderContr] = createSignal>(null) -export const [orderList, setOrderList] = createSignal([]) -export const [currentequity, setCurrentequity] = createSignal(0) - -export const createOrderLine = () => { - return instanceapi()?.createOverlay('') -} \ No newline at end of file diff --git a/src/types/overlayTypes.ts b/src/types/overlayTypes.ts index 240f84b7..6c6fe64b 100644 --- a/src/types/overlayTypes.ts +++ b/src/types/overlayTypes.ts @@ -1,4 +1,10 @@ -import { LineType, Overlay, OverlayTemplate, StateLineStyle } from "klinecharts" +import { LineType, Overlay, OverlayEvent, OverlayTemplate, StateLineStyle } from "klinecharts" +import { FontWeights } from "./types" + +export interface OverlayEventListenerParams { + params: unknown, + callback: (params: unknown, event?: OverlayEvent) => void +} export interface OverlayProperties { text?: string @@ -19,6 +25,7 @@ export interface OrderLineProperties { quantity?: number|string modifyTooltip?: string tooltip?: string + marginRight: number lineColor?: string lineWidth?: number @@ -64,6 +71,12 @@ export interface OrderLineProperties { borderSize?: number, borderDashedValue?: number[], borderRadius?: number + + onMoveStart?: OverlayEventListenerParams + onMove?: OverlayEventListenerParams + onMoveEnd?: OverlayEventListenerParams + onCancel?: OverlayEventListenerParams + onModify?: OverlayEventListenerParams } interface OrderOverlayAttributes { @@ -80,16 +93,19 @@ interface OrderOverlayAttributes { setLineDashedValue: (dashedValue: number[]) => OrderOverlay setBodyFont: (font: string) => OrderOverlay + setBodyFontWeight: (weight: FontWeights | number) => OrderOverlay setBodyTextColor: (color: string) => OrderOverlay setBodyBackgroundColor: (color: string) => OrderOverlay setBodyBorderColor: (color: string) => OrderOverlay setQuantityFont: (font: string) => OrderOverlay + setQuantityFontWeight: (weight: FontWeights | number) => OrderOverlay setQuantityColor: (color: string) => OrderOverlay setQuantityBackgroundColor: (color: string) => OrderOverlay setQuantityBorderColor: (color: string) => OrderOverlay setCancelButtonIconColor: (color: string) => OrderOverlay + setCancelButtonFontWeight: (weight: FontWeights | number) => OrderOverlay setCancelButtonBackgroundColor: (color: string) => OrderOverlay setCancelButtonBorderColor: (color: string) => OrderOverlay @@ -98,11 +114,11 @@ interface OrderOverlayAttributes { setBorderDashedValue: (dashedValue: number[]) => OrderOverlay setBorderRadius: (radius: number) => OrderOverlay - onMoveStart?: (params: T, callback: (params: T) => void) => void - onMove?: (params: T, callback: (params: T) => void) => void - onMoveEnd?: (params: T, callback: (params: T) => void) => void - onCancel?: (params: T, callback: (params: T) => void) => void - onModify?: (params: T, callback: (params: T) => void) => void + onMoveStart: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay + onMove: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay + onMoveEnd: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay + onCancel: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay + onModify: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay } export interface OrderOverlay extends Overlay, OrderOverlayAttributes {} diff --git a/src/types/types.ts b/src/types/types.ts index 70c4b202..1f092cc1 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -15,6 +15,8 @@ import { KLineData, Styles, DeepPartial, Nullable, Chart, DataLoader, Period as DefaultPeriod, IndicatorCreate, PaneOptions, OverlayCreate, FigureCreate, PickRequired, OverlayFilter, Overlay } from 'klinecharts' import { OrderOverlay } from './overlayTypes' + +export type FontWeights = 'thin' | 'extra-light' | 'light' | 'normal' | 'medium' | 'semi-bold' | 'bold' | 'extra-bold' | 'black' export type OrderType = 'buy'|'sell'|'buystop'|'buylimit'|'sellstop'|'selllimit' export type OrderModalType = 'placeorder'|'modifyorder'|'closepartial' export type ExitType = 'stoploss'|'takeprofit'|'breakeven'|'manualclose'|'cancel' From c880f32a2571977ba883d22e00b23a9cf019b988 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Mon, 27 Oct 2025 20:11:28 +0100 Subject: [PATCH 19/20] restrict available api to ui implemented api's for orderLines --- src/Chart.ts | 2 ++ src/types/overlayTypes.ts | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Chart.ts b/src/Chart.ts index 3c2ec139..0540673d 100644 --- a/src/Chart.ts +++ b/src/Chart.ts @@ -296,6 +296,7 @@ export default class Chart implements ProChart return null } + ///@ts-expect-error return this._chart.getOverlays({ id: overlays as string, paneId: 'candle_pane' })[0] as Nullable; } @@ -321,6 +322,7 @@ export default class Chart implements ProChart return []; } + ///@ts-expect-error return (overlays as Array).map(o => this._chart.getOverlays({ id: o!, paneId: 'canlde_pane' })[0]) as Array>; } } \ No newline at end of file diff --git a/src/types/overlayTypes.ts b/src/types/overlayTypes.ts index 6c6fe64b..18e437cf 100644 --- a/src/types/overlayTypes.ts +++ b/src/types/overlayTypes.ts @@ -1,4 +1,4 @@ -import { LineType, Overlay, OverlayEvent, OverlayTemplate, StateLineStyle } from "klinecharts" +import { LineType, Overlay, OverlayEvent, OverlayTemplate, PickPartial, StateLineStyle } from "klinecharts" import { FontWeights } from "./types" export interface OverlayEventListenerParams { @@ -79,7 +79,7 @@ export interface OrderLineProperties { onModify?: OverlayEventListenerParams } -interface OrderOverlayAttributes { +type OrderOverlayAttributes = { setPrice: (price: number) => OrderOverlay setText: (text: string) => OrderOverlay setQuantity: (quantity: string) => OrderOverlay @@ -121,6 +121,6 @@ interface OrderOverlayAttributes { onModify: (params: T, callback: (params: T, event?: OverlayEvent) => void) => OrderOverlay } -export interface OrderOverlay extends Overlay, OrderOverlayAttributes {} +export type OrderOverlay = Pick & OrderOverlayAttributes export interface OrderOverlayCreate extends OverlayTemplate, OrderOverlayAttributes {} \ No newline at end of file From b577b73d683b0990b54d4c702aeaeac473ab8a20 Mon Sep 17 00:00:00 2001 From: Abdulbasit Mamman Date: Tue, 28 Oct 2025 17:28:32 +0100 Subject: [PATCH 20/20] remove random logs --- src/ChartProComponent.tsx | 8 ++++++++ src/extension/position/orderLine.ts | 31 +++++++++++------------------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/ChartProComponent.tsx b/src/ChartProComponent.tsx index 9f5c3186..15701bdd 100644 --- a/src/ChartProComponent.tsx +++ b/src/ChartProComponent.tsx @@ -236,6 +236,14 @@ const ChartProComponent: Component = props => { console.info('onCandleTooltipFeatureClick', data) }) + instanceapi()?.subscribeAction('onZoom', (data) => { + console.info('chart zoomed: ', data) + }) + + instanceapi()?.subscribeAction('onCrosshairChange', (data) => { + console.info('crosshair change: ', data) + }) + const s = symbol() if (s?.priceCurrency) { priceUnitDom.innerHTML = s?.priceCurrency.toLocaleUpperCase() diff --git a/src/extension/position/orderLine.ts b/src/extension/position/orderLine.ts index 34b789f7..77ef9637 100644 --- a/src/extension/position/orderLine.ts +++ b/src/extension/position/orderLine.ts @@ -68,13 +68,7 @@ const OrderLine = (): OrderOverlayCreate => { needDefaultPointFigure: false, needDefaultXAxisFigure: false, needDefaultYAxisFigure: true, - createPointFigures: ({chart, yAxis, overlay, coordinates, bounding }) => { - console.info('bounding is: ', bounding) - console.info('Position Line price is set to: ', properties.price) - const precision = getPrecision(chart, overlay, yAxis) - if (properties.price !== undefined) { - console.info('calculated y coordinate is: ', (chart.convertToPixel({ timestamp: chart.getDataList().at(chart.getDataList().length -1)?.timestamp, value: properties.price }) as Partial ).y) - } + createPointFigures: ({chart, coordinates, bounding }) => { const bodyStyle = labelStyle('body') const quantityStyle = labelStyle('quantity') const cancelStyle = labelStyle('cancel-button') @@ -85,8 +79,6 @@ const OrderLine = (): OrderOverlayCreate => { const quantityMarginRight = utils.calcTextWidth(cancelText) + cancelStyle.paddingLeft + cancelStyle.paddingRight + cancelMarginRight - (properties.borderSize ?? buyStyle().labelStyle.borderSize) const bodyMarginRight = utils.calcTextWidth((quantityText).toString()) + quantityStyle.paddingLeft + quantityStyle.paddingRight + quantityMarginRight const lineMarginRight = utils.calcTextWidth((bodyText).toString()) + bodyStyle.paddingLeft + bodyStyle.paddingRight + bodyMarginRight - console.info('bodyMarginRight: ', bodyMarginRight, ' quantityMarginRight: ', quantityMarginRight, ' cancelMarginRight: ', cancelMarginRight, lineMarginRight) - console.info('cancel text width is: ', utils.calcTextWidth(cancelText), 'X widht is: ', utils.calcTextWidth('X')) return [ { @@ -165,7 +157,6 @@ const OrderLine = (): OrderOverlayCreate => { }, createYAxisFigures: ({ chart, overlay, coordinates, bounding, yAxis }) => { const precision = getPrecision(chart, overlay, yAxis) - console.info('overlay extend dat is: ', overlay.extendData) const isFromZero = yAxis?.isFromZero() ?? false let textAlign: CanvasTextAlign let x: number @@ -192,9 +183,6 @@ const OrderLine = (): OrderOverlayCreate => { text = utils.formatPrecision(overlay.points[0].value, precision.price) } - // let width = utils.calcTextWidth((text as string)) - // const height = width/(text as string).length * 3 - // width = width + height * 2 return { type: 'text', attrs: { @@ -207,10 +195,17 @@ const OrderLine = (): OrderOverlayCreate => { styles: labelStyle('body') } }, - // onRightClick: (event): boolean => { - // // useOverlaySettings().singlePopup(event, 'buy') - // return false - // }, + onSelected: (event) => { + if (event.preventDefault) + event.preventDefault() + event.overlay.mode = 'normal' + return false + }, + onRightClick: (event): boolean => { + if (event.preventDefault) + event.preventDefault() + return false + }, onPressedMoveStart: (event): boolean => { executeCallback(properties.onMoveStart, event) @@ -241,12 +236,10 @@ const OrderLine = (): OrderOverlayCreate => { }, setPrice(price: number) { - console.info('setPrice called with price: ', price) properties.price = price return this as OrderOverlay }, setText(text: string) { - console.info('setText called with text: ', text) properties.text = text return this as OrderOverlay },