mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-10 15:03:32 -07:00
website: redesign part 23
This commit is contained in:
@@ -103,6 +103,7 @@
|
||||
<link rel="stylesheet" href="/learn/charts/controls/style.css" />
|
||||
<link rel="stylesheet" href="/learn/charts/legend/style.css" />
|
||||
<link rel="stylesheet" href="/learn/charts/scrubber/style.css" />
|
||||
<link rel="stylesheet" href="/learn/charts/area/style.css" />
|
||||
<link rel="stylesheet" href="/learn/charts/bar/style.css" />
|
||||
<link rel="stylesheet" href="/learn/charts/line/style.css" />
|
||||
<link rel="stylesheet" href="/learn/charts/dots/style.css" />
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { createAreaPathData, createLinePathData } from "../path.js";
|
||||
import { appendSeriesPath } from "../series-path.js";
|
||||
import { createOrderedIndexes } from "../order.js";
|
||||
import { createLineSeries } from "../line/series.js";
|
||||
|
||||
/**
|
||||
* @param {number} height
|
||||
* @param {{ date: Date, value: number, x: number, y: number }[]} points
|
||||
*/
|
||||
function createAreaPoints(height, points) {
|
||||
return points.map((point) => ({
|
||||
...point,
|
||||
y0: height,
|
||||
y1: point.y,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SVGGElement} group
|
||||
* @param {LoadedSeries[]} loadedSeries
|
||||
* @param {number} height
|
||||
* @param {SeriesHighlight} highlight
|
||||
* @param {import("../scale.js").ChartScale} scale
|
||||
* @param {import("../order.js").ChartOrder} order
|
||||
*/
|
||||
export function renderAreaPlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
scale,
|
||||
order,
|
||||
) {
|
||||
const plottedSeries = createLineSeries(loadedSeries, height, scale);
|
||||
const indexes = createOrderedIndexes(plottedSeries.length, order);
|
||||
|
||||
for (const index of indexes) {
|
||||
const { color, points } = plottedSeries[index];
|
||||
appendSeriesPath({
|
||||
group,
|
||||
highlight,
|
||||
index,
|
||||
chart: "area",
|
||||
color,
|
||||
d: createAreaPathData(createAreaPoints(height, points)),
|
||||
});
|
||||
|
||||
appendSeriesPath({
|
||||
group,
|
||||
highlight,
|
||||
index,
|
||||
chart: "line",
|
||||
color,
|
||||
d: createLinePathData(points),
|
||||
});
|
||||
}
|
||||
|
||||
return plottedSeries;
|
||||
}
|
||||
|
||||
/** @typedef {import("../highlight.js").SeriesHighlight} SeriesHighlight */
|
||||
/** @typedef {import("../index.js").LoadedSeries} LoadedSeries */
|
||||
@@ -0,0 +1,9 @@
|
||||
main.learn {
|
||||
figure[data-chart="series"] {
|
||||
path[data-chart="area"] {
|
||||
fill: var(--color, var(--orange));
|
||||
fill-opacity: 0.5;
|
||||
stroke: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,21 +35,21 @@ function createBarPathData(points, width) {
|
||||
* @param {LoadedSeries[]} loadedSeries
|
||||
* @param {number} height
|
||||
* @param {SeriesHighlight} highlight
|
||||
* @param {{ reversed: boolean }} options
|
||||
* @param {import("../scale.js").ChartScale} scale
|
||||
* @param {import("../order.js").ChartOrder} order
|
||||
*/
|
||||
export function renderBarPlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
options,
|
||||
scale,
|
||||
order,
|
||||
) {
|
||||
const { lineIndexes, plottedSeries, stackIndexes } = createStackedSeries(
|
||||
loadedSeries,
|
||||
height,
|
||||
options.reversed,
|
||||
order,
|
||||
scale,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { formatCoordinate } from "../path.js";
|
||||
import { appendSeriesPath } from "../series-path.js";
|
||||
import { createOrderedIndexes } from "../order.js";
|
||||
import { createLineSeries } from "../line/series.js";
|
||||
|
||||
const radius = 1;
|
||||
@@ -22,11 +23,21 @@ function createDotsPathData(points) {
|
||||
* @param {number} height
|
||||
* @param {SeriesHighlight} highlight
|
||||
* @param {import("../scale.js").ChartScale} scale
|
||||
* @param {import("../order.js").ChartOrder} order
|
||||
*/
|
||||
export function renderDotsPlot(group, loadedSeries, height, highlight, scale) {
|
||||
export function renderDotsPlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
scale,
|
||||
order,
|
||||
) {
|
||||
const plottedSeries = createLineSeries(loadedSeries, height, scale);
|
||||
const indexes = createOrderedIndexes(plottedSeries.length, order);
|
||||
|
||||
plottedSeries.forEach(({ color, points }, index) => {
|
||||
for (const index of indexes) {
|
||||
const { color, points } = plottedSeries[index];
|
||||
appendSeriesPath({
|
||||
group,
|
||||
highlight,
|
||||
@@ -35,7 +46,7 @@ export function renderDotsPlot(group, loadedSeries, height, highlight, scale) {
|
||||
color,
|
||||
d: createDotsPathData(points),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return plottedSeries;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { createFullscreenButton } from "./fullscreen.js";
|
||||
import { onChartVisibility } from "./intersection.js";
|
||||
import { createLegend } from "./legend/index.js";
|
||||
import {
|
||||
createOrderControl,
|
||||
getDefaultOrder,
|
||||
saveOrder,
|
||||
} from "./order.js";
|
||||
import { createChartRenderer } from "./renderer.js";
|
||||
import {
|
||||
createScaleControl,
|
||||
@@ -20,6 +25,36 @@ import {
|
||||
} from "./views.js";
|
||||
import { FALLBACK_VIEWBOX_HEIGHT, VIEWBOX_WIDTH } from "./viewbox.js";
|
||||
|
||||
/**
|
||||
* @template {string} T
|
||||
* @param {Object} args
|
||||
* @param {T} args.currentValue
|
||||
* @param {(currentValue: T, onChange: (value: T) => void) => HTMLFieldSetElement} args.createControl
|
||||
* @param {(chartKey: string, value: T) => void} args.save
|
||||
* @param {string} args.chartKey
|
||||
* @param {HTMLElement} args.figure
|
||||
* @param {string} args.dataKey
|
||||
* @param {(value: T) => void} args.setValue
|
||||
* @param {() => void} args.render
|
||||
*/
|
||||
function createChartSettingControl({
|
||||
currentValue,
|
||||
createControl,
|
||||
save,
|
||||
chartKey,
|
||||
figure,
|
||||
dataKey,
|
||||
setValue,
|
||||
render,
|
||||
}) {
|
||||
return createControl(currentValue, (value) => {
|
||||
setValue(value);
|
||||
save(chartKey, value);
|
||||
figure.dataset[dataKey] = value;
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {Chart} chart */
|
||||
export function createChart(chart) {
|
||||
const figure = document.createElement("figure");
|
||||
@@ -33,6 +68,7 @@ export function createChart(chart) {
|
||||
let currentTimeframe = getDefaultTimeframe(chartKey);
|
||||
let currentView = getDefaultView(chartKey, chart.defaultType);
|
||||
let currentScale = getDefaultScale(chartKey, chart.defaultScale);
|
||||
let currentOrder = getDefaultOrder(chartKey);
|
||||
const { legend, menu, items, readout } = createLegend(chart);
|
||||
|
||||
figure.dataset.chart = "series";
|
||||
@@ -40,6 +76,7 @@ export function createChart(chart) {
|
||||
figure.dataset.timeframe = currentTimeframe;
|
||||
figure.dataset.view = currentView;
|
||||
figure.dataset.scale = currentScale;
|
||||
figure.dataset.order = currentOrder;
|
||||
svg.setAttribute(
|
||||
"viewBox",
|
||||
`0 0 ${VIEWBOX_WIDTH} ${FALLBACK_VIEWBOX_HEIGHT}`,
|
||||
@@ -59,19 +96,44 @@ export function createChart(chart) {
|
||||
chart,
|
||||
getView: () => currentView,
|
||||
getScale: () => currentScale,
|
||||
getOrder: () => currentOrder,
|
||||
getTimeframe: () => currentTimeframe,
|
||||
});
|
||||
const viewControl = createViewControl(currentView, (view) => {
|
||||
currentView = view;
|
||||
saveView(chartKey, view);
|
||||
figure.dataset.view = view;
|
||||
renderer.renderCurrent();
|
||||
const viewControl = createChartSettingControl({
|
||||
currentValue: currentView,
|
||||
createControl: createViewControl,
|
||||
save: saveView,
|
||||
chartKey,
|
||||
figure,
|
||||
dataKey: "view",
|
||||
setValue: (view) => {
|
||||
currentView = view;
|
||||
},
|
||||
render: renderer.renderCurrent,
|
||||
});
|
||||
const scaleControl = createScaleControl(currentScale, (scale) => {
|
||||
currentScale = scale;
|
||||
saveScale(chartKey, scale);
|
||||
figure.dataset.scale = scale;
|
||||
renderer.renderCurrent();
|
||||
const scaleControl = createChartSettingControl({
|
||||
currentValue: currentScale,
|
||||
createControl: createScaleControl,
|
||||
save: saveScale,
|
||||
chartKey,
|
||||
figure,
|
||||
dataKey: "scale",
|
||||
setValue: (scale) => {
|
||||
currentScale = scale;
|
||||
},
|
||||
render: renderer.renderCurrent,
|
||||
});
|
||||
const orderControl = createChartSettingControl({
|
||||
currentValue: currentOrder,
|
||||
createControl: createOrderControl,
|
||||
save: saveOrder,
|
||||
chartKey,
|
||||
figure,
|
||||
dataKey: "order",
|
||||
setValue: (order) => {
|
||||
currentOrder = order;
|
||||
},
|
||||
render: renderer.renderCurrent,
|
||||
});
|
||||
const timeframeControl = createTimeframeControl(
|
||||
currentTimeframe,
|
||||
@@ -82,7 +144,7 @@ export function createChart(chart) {
|
||||
void renderer.loadCurrent();
|
||||
},
|
||||
);
|
||||
chartControls.append(viewControl, scaleControl);
|
||||
chartControls.append(viewControl, scaleControl, orderControl);
|
||||
timeControls.append(timeframeControl, createFullscreenButton(figure));
|
||||
controls.append(chartControls, timeControls);
|
||||
plot.append(svg, status);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createLinePathData } from "../path.js";
|
||||
import { appendSeriesPath } from "../series-path.js";
|
||||
import { createOrderedIndexes } from "../order.js";
|
||||
import { createLineSeries } from "./series.js";
|
||||
|
||||
/**
|
||||
@@ -8,11 +9,21 @@ import { createLineSeries } from "./series.js";
|
||||
* @param {number} height
|
||||
* @param {SeriesHighlight} highlight
|
||||
* @param {import("../scale.js").ChartScale} scale
|
||||
* @param {import("../order.js").ChartOrder} order
|
||||
*/
|
||||
export function renderLinePlot(group, loadedSeries, height, highlight, scale) {
|
||||
export function renderLinePlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
scale,
|
||||
order,
|
||||
) {
|
||||
const plottedSeries = createLineSeries(loadedSeries, height, scale);
|
||||
const indexes = createOrderedIndexes(plottedSeries.length, order);
|
||||
|
||||
plottedSeries.forEach(({ color, points }, index) => {
|
||||
for (const index of indexes) {
|
||||
const { color, points } = plottedSeries[index];
|
||||
appendSeriesPath({
|
||||
group,
|
||||
highlight,
|
||||
@@ -21,7 +32,7 @@ export function renderLinePlot(group, loadedSeries, height, highlight, scale) {
|
||||
color,
|
||||
d: createLinePathData(points),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return plottedSeries;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import { VIEWBOX_WIDTH } from "../viewbox.js";
|
||||
import { scaleY } from "../scale.js";
|
||||
import { createBounds, includeBoundValue, scaleY } from "../scale.js";
|
||||
|
||||
/** @param {LoadedSeries[]} series */
|
||||
function createValueBounds(series) {
|
||||
let min = Infinity;
|
||||
let max = -Infinity;
|
||||
let minPositive = Infinity;
|
||||
const bounds = createBounds();
|
||||
|
||||
for (const { entries } of series) {
|
||||
for (const { value } of entries) {
|
||||
min = Math.min(min, value);
|
||||
max = Math.max(max, value);
|
||||
if (value > 0) minPositive = Math.min(minPositive, value);
|
||||
includeBoundValue(bounds, value);
|
||||
}
|
||||
}
|
||||
|
||||
return { min, max, minPositive };
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { createChartSetting } from "./setting.js";
|
||||
|
||||
const orders = /** @type {const} */ ([
|
||||
{ value: "ascending", label: "Asc" },
|
||||
{ value: "descending", label: "Dsc" },
|
||||
]);
|
||||
const defaultOrder = "ascending";
|
||||
const setting = createChartSetting({
|
||||
storageKey: "order",
|
||||
legend: "Order",
|
||||
options: orders,
|
||||
defaultValue: defaultOrder,
|
||||
});
|
||||
|
||||
/** @param {string} chartKey */
|
||||
export function getDefaultOrder(chartKey) {
|
||||
return setting.get(chartKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} chartKey
|
||||
* @param {ChartOrder} order
|
||||
*/
|
||||
export function saveOrder(chartKey, order) {
|
||||
setting.save(chartKey, order);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ChartOrder} currentOrder
|
||||
* @param {(order: ChartOrder) => void} onChange
|
||||
*/
|
||||
export function createOrderControl(currentOrder, onChange) {
|
||||
return setting.create(currentOrder, onChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[]} indexes
|
||||
* @param {ChartOrder} order
|
||||
*/
|
||||
export function orderIndexes(indexes, order) {
|
||||
const orderedIndexes = [...indexes];
|
||||
|
||||
if (order === "descending") orderedIndexes.reverse();
|
||||
|
||||
return orderedIndexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} length
|
||||
* @param {ChartOrder} order
|
||||
*/
|
||||
export function createOrderedIndexes(length, order) {
|
||||
return orderIndexes(
|
||||
Array.from({ length }, (_, index) => index),
|
||||
order,
|
||||
);
|
||||
}
|
||||
|
||||
/** @typedef {(typeof orders)[number]["value"]} ChartOrder */
|
||||
@@ -1,3 +1,4 @@
|
||||
import { renderAreaPlot } from "./area/index.js";
|
||||
import { renderBarPlot } from "./bar/index.js";
|
||||
import { renderDotsPlot } from "./dots/index.js";
|
||||
import { renderLinePlot } from "./line/index.js";
|
||||
@@ -10,36 +11,61 @@ import { renderStackedPlot } from "./stacked/index.js";
|
||||
* @param {number} height
|
||||
* @param {SeriesHighlight} highlight
|
||||
* @param {ChartScale} scale
|
||||
* @param {ChartOrder} order
|
||||
*/
|
||||
export function renderPlot(view, group, loadedSeries, height, highlight, scale) {
|
||||
export function renderPlot(
|
||||
view,
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
scale,
|
||||
order,
|
||||
) {
|
||||
switch (view) {
|
||||
case "line":
|
||||
return renderLinePlot(group, loadedSeries, height, highlight, scale);
|
||||
case "bar":
|
||||
case "bar-reversed":
|
||||
return renderBarPlot(
|
||||
return renderLinePlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
{ reversed: view === "bar-reversed" },
|
||||
scale,
|
||||
order,
|
||||
);
|
||||
case "area":
|
||||
return renderAreaPlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
scale,
|
||||
order,
|
||||
);
|
||||
case "bar":
|
||||
return renderBarPlot(group, loadedSeries, height, highlight, scale, order);
|
||||
case "dots":
|
||||
return renderDotsPlot(group, loadedSeries, height, highlight, scale);
|
||||
default:
|
||||
return renderDotsPlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
scale,
|
||||
order,
|
||||
);
|
||||
case "stacked":
|
||||
return renderStackedPlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
{ reversed: view === "stacked-reversed" },
|
||||
scale,
|
||||
order,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {import("./highlight.js").SeriesHighlight} SeriesHighlight */
|
||||
/** @typedef {import("./index.js").LoadedSeries} LoadedSeries */
|
||||
/** @typedef {import("./order.js").ChartOrder} ChartOrder */
|
||||
/** @typedef {import("./scale.js").ChartScale} ChartScale */
|
||||
/** @typedef {import("./views.js").ChartView} ChartView */
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getViewBoxHeight, VIEWBOX_WIDTH } from "./viewbox.js";
|
||||
* @param {Chart} args.chart
|
||||
* @param {() => ChartView} args.getView
|
||||
* @param {() => ChartScale} args.getScale
|
||||
* @param {() => ChartOrder} args.getOrder
|
||||
* @param {() => TimeframeValue} args.getTimeframe
|
||||
*/
|
||||
export function createChartRenderer({
|
||||
@@ -26,6 +27,7 @@ export function createChartRenderer({
|
||||
chart,
|
||||
getView,
|
||||
getScale,
|
||||
getOrder,
|
||||
getTimeframe,
|
||||
}) {
|
||||
const group = createSvgElement("g");
|
||||
@@ -68,6 +70,7 @@ export function createChartRenderer({
|
||||
height,
|
||||
highlight,
|
||||
getScale(),
|
||||
getOrder(),
|
||||
),
|
||||
height,
|
||||
);
|
||||
@@ -132,6 +135,7 @@ export function createChartRenderer({
|
||||
/** @typedef {import("./index.js").Chart} Chart */
|
||||
/** @typedef {import("./index.js").LoadedSeries} LoadedSeries */
|
||||
/** @typedef {import("./legend/index.js").Readout} Readout */
|
||||
/** @typedef {import("./order.js").ChartOrder} ChartOrder */
|
||||
/** @typedef {import("./scale.js").ChartScale} ChartScale */
|
||||
/** @typedef {import("./timeframes.js").TimeframeValue} TimeframeValue */
|
||||
/** @typedef {import("./views.js").ChartView} ChartView */
|
||||
|
||||
@@ -36,6 +36,24 @@ export function createScaleControl(currentScale, onChange) {
|
||||
return setting.create(currentScale, onChange);
|
||||
}
|
||||
|
||||
export function createBounds() {
|
||||
return {
|
||||
min: Infinity,
|
||||
max: -Infinity,
|
||||
minPositive: Infinity,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ScaleBounds} bounds
|
||||
* @param {number} value
|
||||
*/
|
||||
export function includeBoundValue(bounds, value) {
|
||||
bounds.min = Math.min(bounds.min, value);
|
||||
bounds.max = Math.max(bounds.max, value);
|
||||
if (value > 0) bounds.minPositive = Math.min(bounds.minPositive, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
* @param {ScaleBounds} bounds
|
||||
|
||||
@@ -7,21 +7,21 @@ import { createStackedSeries } from "./series.js";
|
||||
* @param {LoadedSeries[]} loadedSeries
|
||||
* @param {number} height
|
||||
* @param {SeriesHighlight} highlight
|
||||
* @param {{ reversed: boolean }} options
|
||||
* @param {import("../scale.js").ChartScale} scale
|
||||
* @param {import("../order.js").ChartOrder} order
|
||||
*/
|
||||
export function renderStackedPlot(
|
||||
group,
|
||||
loadedSeries,
|
||||
height,
|
||||
highlight,
|
||||
options,
|
||||
scale,
|
||||
order,
|
||||
) {
|
||||
const { lineIndexes, plottedSeries, stackIndexes } = createStackedSeries(
|
||||
loadedSeries,
|
||||
height,
|
||||
options.reversed,
|
||||
order,
|
||||
scale,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
import { VIEWBOX_WIDTH } from "../viewbox.js";
|
||||
import { scaleY } from "../scale.js";
|
||||
import { orderIndexes } from "../order.js";
|
||||
import { createBounds, includeBoundValue, scaleY } from "../scale.js";
|
||||
|
||||
/**
|
||||
* @param {LoadedSeries[]} series
|
||||
* @param {number[]} stackIndexes
|
||||
* @param {number[]} stackOrder
|
||||
* @param {number[]} lineIndexes
|
||||
*/
|
||||
function createStackBounds(series, stackIndexes, lineIndexes) {
|
||||
function createStackBounds(series, stackOrder, lineIndexes) {
|
||||
const bounds = createBounds();
|
||||
const length = series[0].entries.length;
|
||||
let min = 0;
|
||||
let max = 0;
|
||||
let minPositive = Infinity;
|
||||
|
||||
includeBoundValue(bounds, 0);
|
||||
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
let negative = 0;
|
||||
let positive = 0;
|
||||
|
||||
for (const seriesIndex of stackIndexes) {
|
||||
for (const seriesIndex of stackOrder) {
|
||||
const value = series[seriesIndex].entries[index].value;
|
||||
const end = value < 0 ? negative + value : positive + value;
|
||||
|
||||
if (value < 0) negative += value;
|
||||
else positive += value;
|
||||
if (value < 0) negative = end;
|
||||
else positive = end;
|
||||
|
||||
includeBoundValue(bounds, end);
|
||||
}
|
||||
|
||||
min = Math.min(min, negative);
|
||||
max = Math.max(max, positive);
|
||||
if (positive > 0) minPositive = Math.min(minPositive, positive);
|
||||
|
||||
for (const seriesIndex of lineIndexes) {
|
||||
const value = series[seriesIndex].entries[index].value;
|
||||
|
||||
min = Math.min(min, value);
|
||||
max = Math.max(max, value);
|
||||
if (value > 0) minPositive = Math.min(minPositive, value);
|
||||
includeBoundValue(bounds, value);
|
||||
}
|
||||
}
|
||||
|
||||
return { min, max, minPositive };
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/** @returns {StackedPoint[]} */
|
||||
@@ -47,36 +45,36 @@ function createStackedPoints() {
|
||||
/**
|
||||
* @param {LoadedSeries[]} loadedSeries
|
||||
* @param {number} height
|
||||
* @param {boolean} reversed
|
||||
* @param {import("../order.js").ChartOrder} order
|
||||
* @param {import("../scale.js").ChartScale} scale
|
||||
*/
|
||||
export function createStackedSeries(loadedSeries, height, reversed, scale) {
|
||||
export function createStackedSeries(loadedSeries, height, order, scale) {
|
||||
const indexes = loadedSeries.map((_, index) => index);
|
||||
const lineIndexes = indexes.filter(
|
||||
(index) => loadedSeries[index].series.role === "line",
|
||||
const lineIndexes = orderIndexes(
|
||||
indexes.filter((index) => loadedSeries[index].series.role === "line"),
|
||||
order,
|
||||
);
|
||||
const stackIndexes = indexes.filter(
|
||||
(index) => loadedSeries[index].series.role !== "line",
|
||||
const stackIndexes = orderIndexes(
|
||||
indexes.filter((index) => loadedSeries[index].series.role !== "line"),
|
||||
order,
|
||||
);
|
||||
|
||||
const bounds = createStackBounds(loadedSeries, stackIndexes, lineIndexes);
|
||||
const length = loadedSeries[0].entries.length;
|
||||
const xScale = VIEWBOX_WIDTH / (length - 1);
|
||||
const order = [...stackIndexes];
|
||||
const plottedSeries = loadedSeries.map(({ series, color }) => ({
|
||||
series,
|
||||
color,
|
||||
points: createStackedPoints(),
|
||||
}));
|
||||
|
||||
if (reversed) order.reverse();
|
||||
const bounds = createStackBounds(loadedSeries, stackIndexes, lineIndexes);
|
||||
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
let negative = 0;
|
||||
let positive = 0;
|
||||
const x = index * xScale;
|
||||
|
||||
for (const seriesIndex of order) {
|
||||
for (const seriesIndex of stackIndexes) {
|
||||
const { date, value } = loadedSeries[seriesIndex].entries[index];
|
||||
const start = value < 0 ? negative : positive;
|
||||
const end = start + value;
|
||||
|
||||
@@ -2,18 +2,16 @@ import { createChartSetting } from "./setting.js";
|
||||
|
||||
export const viewTypes = /** @type {const} */ ({
|
||||
line: "line",
|
||||
area: "area",
|
||||
stacked: "stacked",
|
||||
stackedReversed: "stacked-reversed",
|
||||
bar: "bar",
|
||||
barReversed: "bar-reversed",
|
||||
dots: "dots",
|
||||
});
|
||||
const views = /** @type {const} */ ([
|
||||
{ value: viewTypes.line, label: "Line" },
|
||||
{ value: viewTypes.stacked, label: "Stack↑" },
|
||||
{ value: viewTypes.stackedReversed, label: "Stack↓" },
|
||||
{ value: viewTypes.bar, label: "Bars↑" },
|
||||
{ value: viewTypes.barReversed, label: "Bars↓" },
|
||||
{ value: viewTypes.area, label: "Area" },
|
||||
{ value: viewTypes.stacked, label: "Stack" },
|
||||
{ value: viewTypes.bar, label: "Bars" },
|
||||
{ value: viewTypes.dots, label: "Dots" },
|
||||
]);
|
||||
const defaultView = viewTypes.stacked;
|
||||
|
||||
Reference in New Issue
Block a user