kibo: part 5

This commit is contained in:
nym21
2025-03-31 17:17:34 +02:00
parent 1c72362c6b
commit cc5091e28c
7 changed files with 383 additions and 242 deletions

View File

@@ -521,9 +521,10 @@
gap: 0.25rem; gap: 0.25rem;
> span.colors { > span.colors {
margin-top: 0.25rem;
display: flex; display: flex;
width: 0.75rem; width: 0.625rem;
height: 0.75rem; height: 0.625rem;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
border-radius: 9999px; border-radius: 9999px;
@@ -966,9 +967,13 @@
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
z-index: 20; z-index: 20;
margin-bottom: 2rem; flex: 1;
margin-top: 2rem;
margin-bottom: 1.5rem;
> legend { > legend {
text-transform: lowercase;
flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1.5rem; gap: 1.5rem;
@@ -976,7 +981,7 @@
margin-right: var(--negative-main-padding); margin-right: var(--negative-main-padding);
padding-left: var(--main-padding); padding-left: var(--main-padding);
padding-right: var(--main-padding); padding-right: var(--main-padding);
padding-bottom: 1.25rem; padding-bottom: 1.5rem;
overflow-x: auto; overflow-x: auto;
min-width: 0; min-width: 0;
@@ -1015,13 +1020,16 @@
} }
> a { > a {
padding: 0.375rem; padding-left: 0.375rem;
margin: -0.375rem; padding-right: 0.375rem;
margin-left: -0.375rem;
margin-right: -0.375rem;
} }
} }
} }
.lightweight-chart { .lightweight-chart {
min-height: 0;
height: 100%; height: 100%;
margin-right: var(--negative-main-padding); margin-right: var(--negative-main-padding);
} }

View File

@@ -64,7 +64,7 @@ interface BaseSeries {
visible: Accessor<boolean>; visible: Accessor<boolean>;
} }
interface SingleSeries extends BaseSeries { interface SingleSeries extends BaseSeries {
iseries: ISeriesApi<any>; iseries: ISeriesApi<SeriesType>;
dataset: Accessor<(SingleValueData | CandlestickData)[] | null>; dataset: Accessor<(SingleValueData | CandlestickData)[] | null>;
} }
interface SplitSeries extends BaseSeries { interface SplitSeries extends BaseSeries {
@@ -108,7 +108,7 @@ interface Marker {
weight: number; weight: number;
time: Time; time: Time;
value: number; value: number;
seriesChunk: ISeriesApi<any>; seriesChunk: ISeriesApi<SeriesType>;
} }
interface HoveredLegend { interface HoveredLegend {

View File

@@ -1,8 +1,10 @@
// @ts-check // @ts-check
/** @import {SeriesDefinition} from './v5.0.5/types' */ /** @import {ISeriesApi, SeriesDefinition} from './v5.0.5/types' */
export default import("./v5.0.5/script.js").then((lc) => { export default import("./v5.0.5/script.js").then((lc) => {
const oklchToRGBA = createOklchToRGBA();
/** /**
* @param {Object} args * @param {Object} args
* @param {HTMLElement} args.element * @param {HTMLElement} args.element
@@ -63,13 +65,6 @@ export default import("./v5.0.5/script.js").then((lc) => {
borderColor: colors.border(), borderColor: colors.border(),
}), }),
({ defaultColor, offColor, borderColor }) => { ({ defaultColor, offColor, borderColor }) => {
console.log(
defaultColor,
offColor,
borderColor,
`rgba(${oklchToRGBA(borderColor).join(", ")})`,
);
chart.applyOptions({ chart.applyOptions({
layout: { layout: {
textColor: offColor, textColor: offColor,
@@ -102,36 +97,25 @@ export default import("./v5.0.5/script.js").then((lc) => {
return chart; return chart;
} }
/**
* @type {DeepPartial<SeriesOptionsCommon>}
*/
const defaultSeriesOptions = {
// @ts-ignore
lineWidth: 1.5,
// priceLineVisible: false,
// baseLineVisible: false,
// baseLineColor: "",
};
/** /**
* @param {Object} args * @param {Object} args
* @param {string} args.id * @param {string} args.id
* @param {HTMLElement} args.parent * @param {HTMLElement} args.parent
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {"static" | "scrollable"} args.kind
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {VecsResources} args.vecsResources * @param {VecsResources} args.vecsResources
* @param {Owner | null} [args.owner] * @param {Owner | null} [args.owner]
* @param {true} [args.fitContentOnResize]
*/ */
function createChartElement({ function createChartElement({
parent, parent,
signals, signals,
colors, colors,
kind,
utils, utils,
vecsResources, vecsResources,
owner: _owner, owner: _owner,
fitContentOnResize,
}) { }) {
let owner = _owner || signals.getOwner(); let owner = _owner || signals.getOwner();
@@ -141,6 +125,8 @@ export default import("./v5.0.5/script.js").then((lc) => {
const legend = createLegend({ const legend = createLegend({
parent: div, parent: div,
utils,
signals,
}); });
const chartDiv = window.document.createElement("div"); const chartDiv = window.document.createElement("div");
@@ -150,89 +136,109 @@ export default import("./v5.0.5/script.js").then((lc) => {
let ichart = /** @type {IChartApi | null} */ (null); let ichart = /** @type {IChartApi | null} */ (null);
let timeScaleSet = false; let timeScaleSet = false;
if (kind === "static") { if (fitContentOnResize) {
new ResizeObserver(() => ichart?.timeScale().fitContent()).observe( new ResizeObserver(() => ichart?.timeScale().fitContent()).observe(
chartDiv, chartDiv,
); );
} }
/** @type {Index} */ /** @type {Index} */
let index = 0; let vecIndex = 0; // Default value, overwritten
let timeResource = /** @type {VecResource| null} */ (null); let timeResource = /** @type {VecResource| null} */ (null);
let timeScaleSetCallback = /** @type {VoidFunction | null} */ (null);
/** /**
* @param {ISeriesApi<any>} series * @param {ISeriesApi<SeriesType>} series
* @param {VecResource} valuesResource * @param {VecResource} valuesResource
*/ */
function createSetDataEffect(series, valuesResource) { function createSetDataEffect(series, valuesResource) {
signals.createEffect( signals.runWithOwner(owner, () =>
() => [timeResource?.fetched(), valuesResource.fetched()], signals.createEffect(
([indexes, _ohlcs]) => { () => [timeResource?.fetched(), valuesResource.fetched()],
if (!ichart) throw Error("IChart should be initialized"); ([indexes, _ohlcs]) => {
if (!ichart) throw Error("IChart should be initialized");
if (!indexes || !_ohlcs) return; if (!indexes || !_ohlcs) return;
const ohlcs = /** @type {OHLCTuple[]} */ (_ohlcs); const ohlcs = /** @type {OHLCTuple[]} */ (_ohlcs);
let length = Math.min(indexes.length, ohlcs.length); let length = Math.min(indexes.length, ohlcs.length);
const data = new Array(length); const data = new Array(length);
let prevTime = null; let prevTime = null;
let offset = 0; let offset = 0;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const time = indexes[i]; const time = indexes[i];
if (prevTime && prevTime === time) { if (prevTime && prevTime === time) {
offset += 1; offset += 1;
}
const v = ohlcs[i];
if (typeof v === "number") {
data[i - offset] = {
time,
value: v,
};
} else {
data[i - offset] = {
time,
open: v[0],
high: v[1],
low: v[2],
close: v[3],
};
}
prevTime = time;
} }
const v = ohlcs[i]; data.length -= offset;
if (typeof v === "number") { series.setData(data);
data[i - offset] = { timeScaleSetCallback?.();
time, if (
value: v, !timeScaleSet &&
}; (vecIndex === /** @satisfies {Yearindex} */ (15) ||
} else { vecIndex === /** @satisfies {Decadeindex} */ (16))
data[i - offset] = { ) {
time, ichart
open: v[0], .timeScale()
high: v[1], .setVisibleLogicalRange({ from: -1, to: data.length });
low: v[2],
close: v[3],
};
} }
prevTime = time; timeScaleSet = true;
} },
data.length -= offset; ),
series.setData(data);
if (
!timeScaleSet &&
(index === /** @satisfies {Yearindex} */ (15) ||
index === /** @satisfies {Decadeindex} */ (16))
) {
ichart
.timeScale()
.setVisibleLogicalRange({ from: -1, to: data.length });
}
timeScaleSet = true;
},
); );
} }
const activeResources = /** @type {VecResource[]} */ ([]);
ichart?.subscribeCrosshairMove(
utils.debounce(() => {
activeResources.forEach((v) => {
v.fetch();
});
}),
);
return { return {
inner: () => ichart,
/** /**
* @param {Index} _index * @param {Object} args
* @param {Index} args.index
* @param {VoidFunction} [args.timeScaleSetCallback]
*/ */
create(_index) { create({ index: _index, timeScaleSetCallback: _timeScaleSetCallback }) {
index = _index; vecIndex = _index;
timeScaleSetCallback = _timeScaleSetCallback || null;
if (ichart) throw Error("IChart shouldn't be initialized"); if (ichart) throw Error("IChart shouldn't be initialized");
timeResource = vecsResources.getOrCreate( timeResource = vecsResources.getOrCreate(
index, vecIndex,
index === /** @satisfies {Height} */ (2) vecIndex === /** @satisfies {Height} */ (2)
? "fixed-timestamp" ? "fixed-timestamp"
: "timestamp", : "timestamp",
); );
timeResource.fetch(); timeResource.fetch();
ichart = createLightweightChart({ ichart = createLightweightChart({
index, index: vecIndex,
element: chartDiv, element: chartDiv,
signals, signals,
colors, colors,
@@ -240,14 +246,18 @@ export default import("./v5.0.5/script.js").then((lc) => {
}); });
}, },
/** /**
* @param {VecId} id * @param {Object} args
* @param {number} [paneNumber] * @param {VecId} args.vecId
* @param {string} args.name
* @param {number} [args.paneNumber]
* @param {boolean} [args.defaultActive]
*/ */
addCandlestickSeries(id, paneNumber) { addCandlestickSeries({ vecId, name, paneNumber, defaultActive }) {
if (!ichart || !timeResource) throw Error("Chart not fully set"); if (!ichart || !timeResource) throw Error("Chart not fully set");
const valuesResource = vecsResources.getOrCreate(index, id); const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
valuesResource.fetch(); valuesResource.fetch();
activeResources.push(valuesResource);
const green = colors.green(); const green = colors.green();
const red = colors.red(); const red = colors.red();
@@ -259,32 +269,61 @@ export default import("./v5.0.5/script.js").then((lc) => {
wickUpColor: green, wickUpColor: green,
wickDownColor: red, wickDownColor: red,
borderVisible: false, borderVisible: false,
visible: defaultActive !== false,
}, },
paneNumber, paneNumber,
); );
legend.add({
series,
name,
id: vecId,
defaultActive,
colors: [colors.green, colors.red],
url: valuesResource.url,
});
createSetDataEffect(series, valuesResource); createSetDataEffect(series, valuesResource);
return series; return series;
}, },
/** /**
* @param {VecId} id * @param {Object} args
* @param {number} [paneNumber] * @param {VecId} args.vecId
* @param {string} args.name
* @param {Color} [args.color]
* @param {number} [args.paneNumber]
* @param {boolean} [args.defaultActive]
*/ */
addLineSeries(id, paneNumber) { addLineSeries({ vecId, name, color, paneNumber, defaultActive }) {
if (!ichart || !timeResource) throw Error("Chart not fully set"); if (!ichart || !timeResource) throw Error("Chart not fully set");
const valuesResource = vecsResources.getOrCreate(index, id); const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
valuesResource.fetch(); valuesResource.fetch();
activeResources.push(valuesResource);
color ||= colors.orange;
const series = ichart.addSeries( const series = ichart.addSeries(
/** @type {SeriesDefinition<'Line'>} */ (lc.LineSeries), /** @type {SeriesDefinition<'Line'>} */ (lc.LineSeries),
{ {
lineWidth: /** @type {any} */ (1.5), lineWidth: /** @type {any} */ (1.5),
visible: defaultActive !== false,
priceLineVisible: false,
color: color(),
}, },
paneNumber, paneNumber,
); );
legend.add({
series,
colors: [color],
id: vecId,
name,
defaultActive,
url: valuesResource.url,
});
createSetDataEffect(series, valuesResource); createSetDataEffect(series, valuesResource);
return series; return series;
@@ -299,6 +338,7 @@ export default import("./v5.0.5/script.js").then((lc) => {
ichart?.remove(); ichart?.remove();
ichart = null; ichart = null;
timeScaleSet = false; timeScaleSet = false;
activeResources.length = 0;
legend.reset(); legend.reset();
}, },
}; };
@@ -313,19 +353,152 @@ export default import("./v5.0.5/script.js").then((lc) => {
/** /**
* @param {Object} args * @param {Object} args
* @param {Element} args.parent * @param {Element} args.parent
* @param {Signals} args.signals
* @param {Utilities} args.utils
*/ */
function createLegend({ parent }) { function createLegend({ parent, signals, utils }) {
const legendElement = window.document.createElement("legend"); const legendElement = window.document.createElement("legend");
parent.append(legendElement); parent.append(legendElement);
const hovered = signals.createSignal(
/** @type {ISeriesApi<SeriesType> | null} */ (null),
);
return { return {
/**
* @param {Object} args
* @param {ISeriesApi<SeriesType>} args.series
* @param {string} args.id
* @param {string} args.name
* @param {Color[]} args.colors
* @param {boolean} [args.defaultActive]
* @param {string} [args.url]
*/
add({ series, id, name, colors, defaultActive, url }) {
const div = window.document.createElement("div");
legendElement.append(div);
const nameId = utils.stringToId(name);
const active = signals.createSignal(defaultActive ?? true, {
save: {
keyPrefix: id,
key: nameId,
...utils.serde.boolean,
},
});
signals.createEffect(active, (active) => {
series.applyOptions({
visible: active,
});
});
const { input, label } = utils.dom.createLabeledInput({
inputId: utils.stringToId(`legend-${id}-${nameId}`),
inputName: utils.stringToId(`selected-${id}-${nameId}`),
inputValue: "value",
labelTitle: "Click to toggle",
inputChecked: active(),
onClick: () => {
active.set(input.checked);
},
type: "checkbox",
});
const spanMain = window.document.createElement("span");
spanMain.classList.add("main");
label.append(spanMain);
const spanName = utils.dom.createSpanName(name);
spanMain.append(spanName);
div.append(label);
label.addEventListener("mouseover", () => {
const h = hovered();
if (!h || h !== series) {
hovered.set(series);
}
});
label.addEventListener("mouseleave", () => {
hovered.set(null);
});
function shouldHighlight() {
const h = hovered();
return !h || h === series;
}
/**
* @param {string} color
*/
function tameColor(color) {
return `${color.slice(0, -1)} / 50%)`;
}
const spanColors = window.document.createElement("span");
spanColors.classList.add("colors");
spanMain.prepend(spanColors);
colors.forEach((color) => {
const spanColor = window.document.createElement("span");
spanColors.append(spanColor);
signals.createEffect(
() => ({
color: color(),
shouldHighlight: shouldHighlight(),
}),
({ color, shouldHighlight }) => {
if (shouldHighlight) {
spanColor.style.backgroundColor = color;
} else {
spanColor.style.backgroundColor = tameColor(color);
}
},
);
});
const initialColors = /** @type {Record<string, any>} */ ({});
const darkenedColors = /** @type {Record<string, any>} */ ({});
const seriesOptions = series.options();
if (!seriesOptions) return;
Object.entries(seriesOptions).forEach(([k, v]) => {
if (k.toLowerCase().includes("color") && typeof v === "string") {
if (!v.startsWith("oklch")) return;
initialColors[k] = v;
darkenedColors[k] = tameColor(v);
} else if (k === "lastValueVisible" && v) {
initialColors[k] = true;
darkenedColors[k] = false;
}
});
signals.createEffect(shouldHighlight, (shouldHighlight) => {
if (shouldHighlight) {
series.applyOptions(initialColors);
} else {
series.applyOptions(darkenedColors);
}
});
if (url) {
const anchor = window.document.createElement("a");
anchor.href = url;
anchor.target = "_blank";
anchor.rel = "noopener noreferrer";
div.append(anchor);
}
},
reset() { reset() {
legendElement.innerHTML = ""; legendElement.innerHTML = "";
}, },
}; };
} }
const oklchToRGBA = (() => { function createOklchToRGBA() {
{ {
/** /**
* *
@@ -401,7 +574,13 @@ const oklchToRGBA = (() => {
return function (oklch) { return function (oklch) {
oklch = oklch.replace("oklch(", ""); oklch = oklch.replace("oklch(", "");
oklch = oklch.replace(")", ""); oklch = oklch.replace(")", "");
const lch = oklch.split(" ").map((v, i) => { let splitOklch = oklch.split(" / ");
let alpha = 1;
if (splitOklch.length === 2) {
alpha = Number(splitOklch.pop()?.replace("%", "")) / 100;
}
splitOklch = oklch.split(" ");
const lch = splitOklch.map((v, i) => {
if (!i && v.includes("%")) { if (!i && v.includes("%")) {
return Number(v.replace("%", "")) / 100; return Number(v.replace("%", "")) / 100;
} else { } else {
@@ -415,7 +594,7 @@ const oklchToRGBA = (() => {
).map((v) => { ).map((v) => {
return Math.max(Math.min(Math.round(v * 255), 255), 0); return Math.max(Math.min(Math.round(v * 255), 255), 0);
}); });
return [...rgb, 1]; return [...rgb, alpha];
}; };
} }
})(); }

View File

@@ -33,7 +33,7 @@ const importSignals = import("./2024-11-02/script.js").then((_signals) => {
/** /**
* @template T * @template T
* @param {T} initialValue * @param {T} initialValue
* @param {SignalOptions<T> & {save?: {keyPrefix: string; key: string; serialize: (v: NonNullable<T>) => string; deserialize: (v: string) => NonNullable<T>}}} [options] * @param {SignalOptions<T> & {save?: {keyPrefix: string; key: string; serialize: (v: T) => string; deserialize: (v: string) => T; serializeParam?: boolean}}} [options]
* @returns {Signal<T>} * @returns {Signal<T>}
*/ */
createSignal(initialValue, options) { createSignal(initialValue, options) {
@@ -55,7 +55,11 @@ const importSignals = import("./2024-11-02/script.js").then((_signals) => {
const storageKey = `${save.keyPrefix}-${paramKey}`; const storageKey = `${save.keyPrefix}-${paramKey}`;
let serialized = /** @type {string | null} */ (null); let serialized = /** @type {string | null} */ (null);
serialized = new URLSearchParams(window.location.search).get(paramKey); if (options.save.serializeParam !== false) {
serialized = new URLSearchParams(window.location.search).get(
paramKey,
);
}
if (serialized === null) { if (serialized === null) {
serialized = localStorage.getItem(storageKey); serialized = localStorage.getItem(storageKey);

View File

@@ -36,29 +36,58 @@ export function init({
signals, signals,
colors, colors,
id: "chart", id: "chart",
kind: "scrollable",
utils, utils,
vecsResources, vecsResources,
}); });
const index = createIndexSelector({ elements, signals, utils }); const index_ = createIndexSelector({ elements, signals, utils });
// const vecs = signals.createSignal( let firstRun = true;
// /** @type {Set<VecResource>} */ (new Set()),
// {
// equals: false,
// },
// );
signals.createEffect(selected, (option) => { signals.createEffect(selected, (option) => {
titleElement.innerHTML = option.title; titleElement.innerHTML = option.title;
signals.createEffect(index, (index) => { signals.createEffect(index_, (index) => {
utils.url.writeParam("index", String(index));
chart.reset({ owner: signals.getOwner() }); chart.reset({ owner: signals.getOwner() });
chart.create(index); const TIMERANGE_LS_KEY = `chart-timerange-${index}`;
const candles = chart.addCandlestickSeries("ohlc"); const from = signals.createSignal(/** @type {number | null} */ (null), {
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "from",
serializeParam: firstRun,
},
});
const to = signals.createSignal(/** @type {number | null} */ (null), {
save: {
...utils.serde.optNumber,
keyPrefix: TIMERANGE_LS_KEY,
key: "to",
serializeParam: firstRun,
},
});
chart.create({
index,
timeScaleSetCallback: () => {
const from_ = from();
const to_ = to();
if (from_ !== null && to_ !== null) {
chart.inner()?.timeScale().setVisibleLogicalRange({
from: from_,
to: to_,
});
}
},
});
const candles = chart.addCandlestickSeries({
vecId: "ohlc",
name: "Price",
});
signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => { signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
if (!latest) return; if (!latest) return;
const last = /** @type { CandlestickData | undefined} */ ( const last = /** @type { CandlestickData | undefined} */ (
@@ -69,128 +98,35 @@ export function init({
}); });
[ [
{ blueprints: option.top, paneIndex: 0 }, { blueprints: option.top, paneNumber: 0 },
{ blueprints: option.bottom, paneIndex: 1 }, { blueprints: option.bottom, paneNumber: 1 },
].forEach(({ blueprints, paneIndex }) => { ].forEach(({ blueprints, paneNumber }) => {
blueprints?.forEach((blueprint) => { blueprints?.forEach((blueprint) => {
if (vecIdToIndexes[blueprint.key].includes(index)) { if (vecIdToIndexes[blueprint.key].includes(index)) {
const series = chart.addLineSeries(blueprint.key, paneIndex); chart.addLineSeries({
series.applyOptions({ vecId: blueprint.key,
visible: blueprint.defaultActive !== false, color: blueprint.color,
color: blueprint.color?.(), name: blueprint.title,
defaultActive: blueprint.defaultActive,
paneNumber,
}); });
} }
}); });
}); });
chart
.inner()
?.timeScale()
.subscribeVisibleLogicalRangeChange(
utils.debounce((t) => {
from.set(t.from);
to.set(t.to);
}),
);
firstRun = false;
}); });
}); });
// function createFetchChunksOfVisibleDatasetsEffect() {
// signals.createEffect(
// () => ({
// ids: chart.visibleDatasetIds(),
// activeDatasets: activeDatasets(),
// }),
// ({ ids, activeDatasets }) => {
// const datasets = Array.from(activeDatasets);
// if (ids.length === 0 || datasets.length === 0) return;
// for (let i = 0; i < ids.length; i++) {
// const id = ids[i];
// for (let j = 0; j < datasets.length; j++) {
// datasets[j].fetch(id);
// }
// }
// },
// );
// }
// createFetchChunksOfVisibleDatasetsEffect();
// /**
// * @param {ChartOption} option
// */
// function applyChartOption(option) {
// chart.visibleTimeRange.set(chart.getInitialVisibleTimeRange());
// activeDatasets.set((s) => {
// s.clear();
// return s;
// });
// const chartsBlueprints = [option.top || [], option.bottom].flatMap(
// (list) => (list ? [list] : []),
// );
// chartsBlueprints.map((seriesBlueprints, paneIndex) => {
// const chartPane = chart.createPane({
// paneIndex,
// unit: paneIndex ? option.unit : "US Dollars",
// });
// if (!paneIndex) {
// /** @type {AnyDatasetPath} */
// const datasetPath = `${scale}-to-price`;
// const dataset = datasets.getOrCreate(scale, datasetPath);
// // Don't trigger reactivity by design
// activeDatasets().add(dataset);
// const priceSeries = chartPane.createSplitSeries({
// blueprint: {
// datasetPath,
// title: "BTC Price",
// type: "Candlestick",
// },
// dataset,
// id: option.id,
// index: -1,
// });
// signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
// if (!latest) return;
// const index = utils.chunkIdToIndex(scale, latest.year);
// priceSeries.forEach((splitSeries) => {
// const series = splitSeries.chunks.at(index);
// if (series) {
// signals.createEffect(series, (series) => {
// series?.update(latest);
// });
// }
// });
// });
// }
// [...seriesBlueprints].reverse().forEach((blueprint, index) => {
// const dataset = datasets.getOrCreate(scale, blueprint.datasetPath);
// // Don't trigger reactivity by design
// activeDatasets().add(dataset);
// chartPane.createSplitSeries({
// index,
// blueprint,
// id: option.id,
// dataset,
// });
// });
// activeDatasets.set((s) => s);
// return chart;
// });
// }
// function createApplyChartOptionEffect() {
// signals.createEffect(selected, (option) => {
// chart.reset({ scale: option.scale, owner: signals.getOwner() });
// applyChartOption(option);
// });
// }
// createApplyChartOptionEffect();
} }
/** /**

View File

@@ -866,6 +866,20 @@ function createUtils() {
return Number(v); return Number(v);
}, },
}, },
optNumber: {
/**
* @param {number | null} v
*/
serialize(v) {
return v !== null ? String(v) : "";
},
/**
* @param {string} v
*/
deserialize(v) {
return v ? Number(v) : null;
},
},
date: { date: {
/** /**
* @param {Date} v * @param {Date} v

View File

@@ -4946,21 +4946,9 @@ function createPartialOptions(colors) {
color: colors.orange, color: colors.orange,
}, },
{ {
key: "block-interval-max", key: "block-interval-median",
title: "Max", title: "Median",
color: colors.pink, color: colors.amber,
defaultActive: false,
},
{
key: "block-interval-min",
title: "Min",
color: colors.green,
defaultActive: false,
},
{
key: "block-interval-90p",
title: "90p",
color: colors.rose,
defaultActive: false, defaultActive: false,
}, },
{ {
@@ -4969,24 +4957,36 @@ function createPartialOptions(colors) {
color: colors.red, color: colors.red,
defaultActive: false, defaultActive: false,
}, },
{
key: "block-interval-median",
title: "Median",
color: colors.amber,
defaultActive: false,
},
{ {
key: "block-interval-25p", key: "block-interval-25p",
title: "25p", title: "25p",
color: colors.yellow, color: colors.yellow,
defaultActive: false, defaultActive: false,
}, },
{
key: "block-interval-90p",
title: "90p",
color: colors.rose,
defaultActive: false,
},
{ {
key: "block-interval-10p", key: "block-interval-10p",
title: "10p", title: "10p",
color: colors.lime, color: colors.lime,
defaultActive: false, defaultActive: false,
}, },
{
key: "block-interval-max",
title: "Max",
color: colors.pink,
defaultActive: false,
},
{
key: "block-interval-min",
title: "Min",
color: colors.green,
defaultActive: false,
},
], ],
}, },
], ],