diff --git a/website/scripts/chart.js b/website/scripts/chart.js
index 3e57a4eed..b905451a7 100644
--- a/website/scripts/chart.js
+++ b/website/scripts/chart.js
@@ -34,7 +34,7 @@ export function init({
}) {
console.log("init chart state");
- /** @type {Array<(IChartApi & {whitespace: ISeriesApi<"Line">})>} */
+ /** @type {ChartPane[]} */
let charts = [];
const scale = signals.createMemo(() => selected().scale);
@@ -53,15 +53,26 @@ export function init({
descriptionElement.innerHTML = option.serializedPath;
});
- const div = window.document.createElement("div");
- elements.charts.append(div);
+ // const div = window.document.createElement("div");
+ // elements.charts.append(div);
- const legendElement = window.document.createElement("legend");
- div.append(legendElement);
+ // const legendElement = window.document.createElement("legend");
+ // div.append(legendElement);
- const chartListElement = window.document.createElement("div");
- chartListElement.classList.add("chart-list");
- div.append(chartListElement);
+ // const chartListElement = window.document.createElement("div");
+ // chartListElement.classList.add("chart-list");
+ // div.append(chartListElement);
+ //
+ const {
+ chartListElement,
+ legendElement,
+ createPane: addChart,
+ } = lightweightCharts.createChart({
+ parent: elements.charts,
+ signals,
+ colors,
+ id: "chart",
+ });
/**
* @returns {TimeRange}
@@ -248,59 +259,6 @@ export function init({
}
createFetchChunksOfVisibleDatasetsEffect();
- /**
- * @param {HTMLElement} parent
- * @param {number} chartIndex
- */
- function createChartDiv(parent, chartIndex) {
- const chartWrapper = window.document.createElement("div");
- chartWrapper.classList.add("chart-wrapper");
- parent.append(chartWrapper);
-
- const chartDiv = window.document.createElement("div");
- chartDiv.classList.add("chart-div");
- chartWrapper.append(chartDiv);
-
- function createUnitAndModeElements() {
- const fieldset = window.document.createElement("fieldset");
- fieldset.dataset.size = "sm";
- chartWrapper.append(fieldset);
-
- const unitName = signals.createSignal("");
-
- const id = `chart-${chartIndex}-mode`;
-
- const chartModes = /** @type {const} */ (["Linear", "Log"]);
- const chartMode = signals.createSignal(
- /** @type {Lowercase
} */ (
- localStorage.getItem(id) ||
- chartModes[chartIndex ? 0 : 1].toLowerCase()
- ),
- );
-
- const field = utils.dom.createHorizontalChoiceField({
- choices: chartModes,
- selected: chartMode(),
- id,
- title: unitName,
- signals,
- });
- fieldset.append(field);
-
- field.addEventListener("change", (event) => {
- // @ts-ignore
- const value = event.target.value;
- localStorage.setItem(id, value);
- chartMode.set(value);
- });
-
- return { unitName, chartMode };
- }
- const { unitName, chartMode } = createUnitAndModeElements();
-
- return { chartDiv, unitName, chartMode };
- }
-
/**
* @param {IChartApi} chart
*/
@@ -526,7 +484,7 @@ export function init({
* @param {ResourceDataset} args.dataset
* @param {SeriesBlueprint} args.seriesBlueprint
* @param {Option} args.option
- * @param {IChartApi} args.chart
+ * @param {ChartPane} args.chart
* @param {number} args.index
* @param {Series[]} args.chartSeries
* @param {Accessor} args.lastVisibleDatasetIndex
@@ -630,42 +588,26 @@ export function init({
if (!s) {
switch (type) {
case "Baseline": {
- s = lightweightCharts.createBaseLineSeries({
- chart,
+ s = chart.createBaseLineSeries({
color,
options: seriesOptions,
owner,
- signals,
- colors,
});
break;
}
case "Candlestick": {
- s = lightweightCharts.createCandlesticksSeries({
- chart,
+ s = chart.createCandlesticksSeries({
options: seriesOptions,
owner,
- signals,
- colors,
});
break;
}
- // case "Histogram": {
- // s = createHistogramSeries({
- // chart,
- // options,
- // });
- // break;
- // }
default:
case "Line": {
- s = lightweightCharts.createLineSeries({
- chart,
+ s = chart.createLineSeries({
color,
options: seriesOptions,
owner,
- signals,
- colors,
});
break;
}
@@ -766,7 +708,7 @@ export function init({
* @param {PriceSeriesType} args.type
* @param {VoidFunction} args.setMinMaxMarkersWhenIdle
* @param {Option} args.option
- * @param {IChartApi} args.chart
+ * @param {ChartPane} args.chart
* @param {Series[]} args.chartSeries
* @param {Accessor} args.lastVisibleDatasetIndex
*/
@@ -864,16 +806,11 @@ export function init({
const allSeries = [];
charts = chartsBlueprints.map((seriesBlueprints, chartIndex) => {
- const { chartDiv, unitName, chartMode } = createChartDiv(
- chartListElement,
+ const chart = addChart({
chartIndex,
- );
-
- const chart = lightweightCharts.createChartWithWhitespace({
scale,
- element: chartDiv,
- signals,
- colors,
+ unit: option.unit || "US Dollars",
+ whitespace: true,
});
setInitialVisibleTimeRange(chart);
@@ -1063,12 +1000,6 @@ export function init({
});
}
createLinkPriceSeriesEffect();
-
- /** @type {Unit} */
- const unit = "US Dollars";
- unitName.set(unit);
- } else {
- unitName.set(option.unit);
}
[...seriesBlueprints].reverse().forEach((seriesBlueprint, index) => {
@@ -1110,9 +1041,7 @@ export function init({
function createChartVisibilityEffect() {
signals.createEffect(chartVisible, (chartVisible) => {
- const chartWrapper = chartDiv.parentElement;
- if (!chartWrapper) throw "Should exist";
- chartWrapper.hidden = !chartVisible;
+ chart.setHidden(!chartVisible);
});
}
createChartVisibilityEffect();
@@ -1134,12 +1063,6 @@ export function init({
}
createTimeScaleVisibilityEffect();
- signals.createEffect(chartMode, (chartMode) =>
- chart.priceScale("right").applyOptions({
- mode: chartMode === "linear" ? 0 : 1,
- }),
- );
-
chart.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => {
if (!logicalRange) return;
diff --git a/website/scripts/main.js b/website/scripts/main.js
index 437915041..497507d18 100644
--- a/website/scripts/main.js
+++ b/website/scripts/main.js
@@ -1,7 +1,7 @@
// @ts-check
/**
- * @import { Option, ResourceDataset, TimeScale, TimeRange, Unit, Marker, Weighted, DatasetPath, OHLC, FetchedJSON, DatasetValue, FetchedResult, AnyDatasetPath, SeriesBlueprint, BaselineSpecificSeriesBlueprint, CandlestickSpecificSeriesBlueprint, LineSpecificSeriesBlueprint, SpecificSeriesBlueprintWithChart, Signal, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, AnyPath, SimulationOption, Frequency } from "./types/self"
+ * @import { Option, ResourceDataset, TimeScale, TimeRange, Unit, Marker, Weighted, DatasetPath, OHLC, FetchedJSON, DatasetValue, FetchedResult, AnyDatasetPath, SeriesBlueprint, BaselineSpecificSeriesBlueprint, CandlestickSpecificSeriesBlueprint, LineSpecificSeriesBlueprint, SpecificSeriesBlueprintWithChart, Signal, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, AnyPath, SimulationOption, Frequency, CreatePaneParameters, CreateBaselineSeriesParams, CreateCandlestickSeriesParams, CreateLineSeriesParams } from "./types/self"
* @import {createChart as CreateClassicChart, createChartEx as CreateCustomChart, LineStyleOptions} from "../packages/lightweight-charts/v4.2.0/types";
* @import * as _ from "../packages/ufuzzy/v1.0.14/types"
* @import { DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, SingleValueData, ISeriesApi, Time, LineData, LogicalRange, SeriesMarker, CandlestickData, SeriesType, BaselineStyleOptions, SeriesOptionsCommon } from "../packages/lightweight-charts/v4.2.0/types"
@@ -252,7 +252,7 @@ function initPackages() {
* @param {Colors} args.colors
* @param {DeepPartial} [args.options]
*/
- function createChart({
+ function createLightweightChart({
scale,
element,
signals,
@@ -282,9 +282,6 @@ function initPackages() {
time: false,
},
},
- crosshair: {
- mode: 0,
- },
localization: {
priceFormatter: utils.locale.numberToShortUSFormat,
locale: "en-us",
@@ -360,127 +357,6 @@ function initPackages() {
baseLineColor: "",
};
- /**
- * @param {SpecificSeriesBlueprintWithChart & {colors: Colors, signals: Signals}} args
- */
- function createBaseLineSeries({
- chart,
- color,
- options,
- owner,
- colors,
- signals,
- }) {
- const topLineColor = color || colors.profit;
- const bottomLineColor = color || colors.loss;
-
- function computeColors() {
- return {
- topLineColor: topLineColor(),
- bottomLineColor: bottomLineColor(),
- };
- }
-
- const transparent = "transparent";
-
- /** @type {DeepPartial} */
- const seriesOptions = {
- priceScaleId: "right",
- ...defaultSeriesOptions,
- ...options,
- topFillColor1: transparent,
- topFillColor2: transparent,
- bottomFillColor1: transparent,
- bottomFillColor2: transparent,
- ...computeColors(),
- };
-
- const series = chart.addBaselineSeries(seriesOptions);
-
- signals.runWithOwner(owner, () => {
- signals.createEffect(computeColors, (computeColors) => {
- series.applyOptions(computeColors);
- });
- });
-
- return series;
- }
-
- /**
- * @param {SpecificSeriesBlueprintWithChart & {colors: Colors, signals: Signals}} args
- */
- function createCandlesticksSeries({
- chart,
- options,
- owner,
- signals,
- colors,
- }) {
- function computeColors() {
- const upColor = colors.profit();
- const downColor = colors.loss();
-
- return {
- upColor,
- wickUpColor: upColor,
- downColor,
- wickDownColor: downColor,
- };
- }
-
- const candlestickSeries = chart.addCandlestickSeries({
- baseLineVisible: false,
- borderVisible: false,
- priceLineVisible: false,
- baseLineColor: "",
- borderColor: "",
- borderDownColor: "",
- borderUpColor: "",
- ...options,
- ...computeColors(),
- });
-
- signals.runWithOwner(owner, () => {
- signals.createEffect(computeColors, (computeColors) => {
- candlestickSeries.applyOptions(computeColors);
- });
- });
-
- return candlestickSeries;
- }
-
- /**
- * @param {SpecificSeriesBlueprintWithChart & {colors: Colors, signals: Signals}} args
- */
- function createLineSeries({
- chart,
- color,
- options,
- owner,
- signals,
- colors,
- }) {
- function computeColors() {
- return {
- color: color(),
- };
- }
-
- const series = chart.addLineSeries({
- ...defaultSeriesOptions,
- ...options,
- ...computeColors(),
- });
-
- signals.runWithOwner(owner, () => {
- signals.createEffect(computeColors, (computeColors) => {
- series.applyOptions(computeColors);
- });
- });
-
- return series;
- }
-
function initWhitespace() {
const whitespaceStartDate = new Date("1970-01-01");
const whitespaceStartDateYear =
@@ -576,34 +452,285 @@ function initPackages() {
const { setWhitespace } = initWhitespace();
/**
- *
- * @param {Parameters[0]} args
+ * @typeof {Object} PaneParameters
+ * @property {Unit} param.unit
+ * @param {TimeScale} param.scale
+ * @param {number} [param.chartIndex]
+ * @param {true} [param.whitespace]
+ * @param {DeepPartial} [param.options]
*/
- function createChartWithWhitespace({
- element,
- scale,
- colors,
+
+ /**
+ * @param {Object} param0
+ * @param {string} param0.id
+ * @param {HTMLElement} param0.parent
+ * @param {Signals} param0.signals
+ * @param {Colors} param0.colors
+ * @param {"static" | "dynamic"} [param0.kind]
+ * @param {CreatePaneParameters[]} [param0.config]
+ */
+ function createChart({
+ parent,
signals,
+ colors,
+ id: chartId,
+ kind,
+ config,
}) {
- const chart =
- /** @type {IChartApi & {whitespace: ISeriesApi<"Line">}} */ (
- createChart({
- colors,
- element,
- scale,
+ const div = window.document.createElement("div");
+ div.classList.add("charts");
+ parent.append(div);
+
+ const legendElement = window.document.createElement("legend");
+ div.append(legendElement);
+
+ const chartListElement = window.document.createElement("div");
+ chartListElement.classList.add("chart-list");
+ div.append(chartListElement);
+
+ /**
+ * @param {CreatePaneParameters} param
+ */
+ function createPane({
+ chartIndex,
+ whitespace,
+ scale,
+ unit,
+ options,
+ config,
+ }) {
+ const chartWrapper = window.document.createElement("div");
+ chartWrapper.classList.add("chart-wrapper");
+ chartListElement.append(chartWrapper);
+
+ const chartDiv = window.document.createElement("div");
+ chartDiv.classList.add("chart-div");
+ chartWrapper.append(chartDiv);
+
+ options = { ...options };
+ if (kind === "static") {
+ options.handleScale = false;
+ options.handleScroll = false;
+ } else {
+ options.crosshair = {
+ ...options.crosshair,
+ mode: 0,
+ };
+ }
+
+ const _chart = createLightweightChart({
+ scale,
+ element: chartDiv,
+ signals,
+ colors,
+ options,
+ });
+
+ /**
+ * @param {CreateBaselineSeriesParams} args
+ */
+ function createBaseLineSeries({ color, options, owner, data }) {
+ const topLineColor = color || colors.profit;
+ const bottomLineColor = color || colors.loss;
+
+ function computeColors() {
+ return {
+ topLineColor: topLineColor(),
+ bottomLineColor: bottomLineColor(),
+ };
+ }
+
+ const transparent = "transparent";
+
+ /** @type {DeepPartial} */
+ const seriesOptions = {
+ priceScaleId: "right",
+ ...defaultSeriesOptions,
+ ...options,
+ topFillColor1: transparent,
+ topFillColor2: transparent,
+ bottomFillColor1: transparent,
+ bottomFillColor2: transparent,
+ ...computeColors(),
+ };
+
+ const series = _chart.addBaselineSeries(seriesOptions);
+
+ signals.runWithOwner(owner, () => {
+ signals.createEffect(computeColors, (computeColors) => {
+ series.applyOptions(computeColors);
+ });
+ });
+
+ if (data) {
+ series.setData(data);
+ }
+
+ return series;
+ }
+
+ /**
+ * @param {CreateCandlestickSeriesParams} args
+ */
+ function createCandlestickSeries({ options, owner, data }) {
+ function computeColors() {
+ const upColor = colors.profit();
+ const downColor = colors.loss();
+
+ return {
+ upColor,
+ wickUpColor: upColor,
+ downColor,
+ wickDownColor: downColor,
+ };
+ }
+
+ const series = _chart.addCandlestickSeries({
+ baseLineVisible: false,
+ borderVisible: false,
+ priceLineVisible: false,
+ baseLineColor: "",
+ borderColor: "",
+ borderDownColor: "",
+ borderUpColor: "",
+ ...options,
+ ...computeColors(),
+ });
+
+ signals.runWithOwner(owner, () => {
+ signals.createEffect(computeColors, (computeColors) => {
+ series.applyOptions(computeColors);
+ });
+ });
+
+ if (data) {
+ series.setData(data);
+ }
+
+ return series;
+ }
+
+ /**
+ * @param {CreateLineSeriesParams} args
+ */
+ function createLineSeries({ color, options, owner, data }) {
+ function computeColors() {
+ return {
+ color: color(),
+ };
+ }
+
+ const series = _chart.addLineSeries({
+ ...defaultSeriesOptions,
+ ...options,
+ ...computeColors(),
+ });
+
+ if (data) {
+ series.setData(data);
+ }
+
+ signals.runWithOwner(owner, () => {
+ signals.createEffect(computeColors, (computeColors) => {
+ series.applyOptions(computeColors);
+ });
+ });
+
+ return series;
+ }
+
+ const chart =
+ /** @type {IChartApi & { whitespace: ISeriesApi<"Line">, createBaseLineSeries: typeof createBaseLineSeries, createCandlesticksSeries: typeof createCandlestickSeries, createLineSeries: typeof createLineSeries; setHidden: (b: boolean) => void }} */ (
+ _chart
+ );
+
+ if (whitespace) {
+ chart.whitespace = setWhitespace(_chart, scale);
+ }
+
+ chart.createBaseLineSeries = createBaseLineSeries;
+ chart.createCandlesticksSeries = createCandlestickSeries;
+ chart.createLineSeries = createLineSeries;
+ chart.setHidden = (b) => {
+ chartWrapper.hidden = b;
+ };
+
+ function createUnitAndModeElements() {
+ const fieldset = window.document.createElement("fieldset");
+ fieldset.dataset.size = "sm";
+ chartWrapper.append(fieldset);
+
+ const id = `chart-${chartId}-${chartIndex}-mode`;
+
+ const chartModes = /** @type {const} */ (["Linear", "Log"]);
+ const chartMode = signals.createSignal(
+ /** @type {Lowercase} */ (
+ localStorage.getItem(id) ||
+ chartModes[chartIndex ? 0 : 1].toLowerCase()
+ ),
+ );
+
+ const field = utils.dom.createHorizontalChoiceField({
+ choices: chartModes,
+ selected: chartMode(),
+ id,
+ title: unit,
signals,
- })
- );
- chart.whitespace = setWhitespace(chart, scale);
- return chart;
+ });
+ fieldset.append(field);
+
+ field.addEventListener("change", (event) => {
+ // @ts-ignore
+ const value = event.target.value;
+ localStorage.setItem(id, value);
+ chartMode.set(value);
+ });
+
+ signals.createEffect(chartMode, (chartMode) =>
+ _chart.priceScale("right").applyOptions({
+ mode: chartMode === "linear" ? 0 : 1,
+ }),
+ );
+ }
+ createUnitAndModeElements();
+
+ config?.forEach((params) => {
+ switch (params.kind) {
+ case "line": {
+ chart.createLineSeries(params);
+ break;
+ }
+ case "candle": {
+ chart.createCandlesticksSeries(params);
+ break;
+ }
+ case "baseline": {
+ chart.createBaseLineSeries(params);
+ break;
+ }
+ }
+ });
+
+ if (kind === "static") {
+ chart.timeScale().fitContent();
+ }
+
+ return chart;
+ }
+
+ config?.forEach((params) => {
+ createPane(params);
+ });
+
+ return {
+ legendElement,
+ chartListElement,
+ createPane,
+ };
}
return {
createChart,
- createChartWithWhitespace,
- createBaseLineSeries,
- createCandlesticksSeries,
- createLineSeries,
};
},
),
@@ -654,6 +781,7 @@ const packages = initPackages();
/**
* @typedef {Awaited>} Signals
* @typedef {Awaited>} LightweightCharts
+ * @typedef {ReturnType>['createChart']>['createPane']>} ChartPane
*/
const options = import("./options.js");
@@ -1700,7 +1828,6 @@ const elements = {
search: utils.dom.getElementById("search"),
nav: utils.dom.getElementById("nav"),
navHeader: utils.dom.getElementById("nav-header"),
- selectedFrame: utils.dom.getElementById("selected-frame"),
searchInput: /** @type {HTMLInputElement} */ (
utils.dom.getElementById("search-input")
),
@@ -1914,8 +2041,13 @@ function createColors(dark) {
offDollars: emerald,
yellow,
+ lime,
orange,
red,
+ sky,
+ blue,
+ rose,
+ pink,
_1d: pink,
_1w: red,
@@ -2813,7 +2945,7 @@ packages.signals().then((signals) =>
}
createMobileSwitchEffect();
- utils.dom.onFirstIntersection(elements.selectedFrame, initSelectedFrame);
+ utils.dom.onFirstIntersection(elements.aside, initSelectedFrame);
}
initSelected();
diff --git a/website/scripts/simulation.js b/website/scripts/simulation.js
index 405902d95..3c0306830 100644
--- a/website/scripts/simulation.js
+++ b/website/scripts/simulation.js
@@ -292,14 +292,8 @@ export function init({
);
const firstParagraph = window.document.createElement("p");
- resultsElement.append(firstParagraph);
const secondParagraph = window.document.createElement("p");
- resultsElement.append(secondParagraph);
-
- const parent = window.document.createElement("div");
- parent.classList.add("chart-list");
- resultsElement.append(parent);
const owner = signals.getOwner();
@@ -330,24 +324,27 @@ export function init({
fees,
}) => {
console.log({ start, end });
- parent.innerHTML = "";
+
+ resultsElement.innerHTML = "";
+ resultsElement.append(firstParagraph);
+ resultsElement.append(secondParagraph);
if (!start || !end || start > end) return;
const range = utils.date.getRange(start, end);
/** @type {LineData