Various fixes for lovely-chart
This commit is contained in:
parent
fd5bcf6851
commit
03f163e528
@ -57,7 +57,9 @@ export function buildChannelMonetizationStatistics(
|
|||||||
return {
|
return {
|
||||||
// Graphs
|
// Graphs
|
||||||
topHoursGraph: stats.topHoursGraph ? buildGraph(stats.topHoursGraph) : undefined,
|
topHoursGraph: stats.topHoursGraph ? buildGraph(stats.topHoursGraph) : undefined,
|
||||||
revenueGraph: buildGraph(stats.revenueGraph, undefined, true, stats.usdRate),
|
revenueGraph: buildGraph(stats.revenueGraph, undefined, {
|
||||||
|
label: 'USD ≈', multiplier: stats.usdRate, prefix: '$',
|
||||||
|
}),
|
||||||
|
|
||||||
// Statistics overview
|
// Statistics overview
|
||||||
balances: buildChannelMonetizationBalances(stats.status),
|
balances: buildChannelMonetizationBalances(stats.status),
|
||||||
@ -156,7 +158,9 @@ export function buildStoryPublicForwards(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function buildGraph(
|
export function buildGraph(
|
||||||
result: GramJs.TypeStatsGraph, isPercentage?: boolean, isCurrency?: boolean, currencyRate?: number,
|
result: GramJs.TypeStatsGraph, isPercentage?: boolean, secondaryYAxis?: {
|
||||||
|
label: string; multiplier: number; prefix?: string; suffix?: string;
|
||||||
|
},
|
||||||
): TypeStatisticsGraph {
|
): TypeStatisticsGraph {
|
||||||
if (result instanceof GramJs.StatsGraphError) {
|
if (result instanceof GramJs.StatsGraphError) {
|
||||||
return {
|
return {
|
||||||
@ -187,15 +191,15 @@ export function buildGraph(
|
|||||||
hasSecondYAxis,
|
hasSecondYAxis,
|
||||||
isStacked: data.stacked && !hasSecondYAxis,
|
isStacked: data.stacked && !hasSecondYAxis,
|
||||||
isPercentage,
|
isPercentage,
|
||||||
isCurrency,
|
secondaryYAxis,
|
||||||
currencyRate,
|
|
||||||
datasets: y.map((item: any) => {
|
datasets: y.map((item: any) => {
|
||||||
const key = item[0];
|
const key = item[0];
|
||||||
|
const values = item.slice(1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: data.names[key],
|
name: data.names[key],
|
||||||
color: extractColor(data.colors[key]),
|
color: extractColor(data.colors[key]),
|
||||||
values: item.slice(1),
|
values: secondaryYAxis ? values.map((v: number) => v / 1e9) : values,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
...calculateMinimapRange(data.subchart.defaultZoom, x.slice(1)),
|
...calculateMinimapRange(data.subchart.defaultZoom, x.slice(1)),
|
||||||
|
|||||||
@ -91,8 +91,12 @@ export interface StatisticsGraph {
|
|||||||
labels: Array<string | number>;
|
labels: Array<string | number>;
|
||||||
isStacked: boolean;
|
isStacked: boolean;
|
||||||
isPercentage?: boolean;
|
isPercentage?: boolean;
|
||||||
isCurrency?: boolean;
|
secondaryYAxis?: {
|
||||||
currencyRate?: number;
|
label: string;
|
||||||
|
multiplier: number;
|
||||||
|
prefix?: string;
|
||||||
|
suffix?: string;
|
||||||
|
};
|
||||||
hideCaption: boolean;
|
hideCaption: boolean;
|
||||||
hasSecondYAxis: boolean;
|
hasSecondYAxis: boolean;
|
||||||
minimapRange: {
|
minimapRange: {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { GUTTER, AXES_FONT, X_AXIS_HEIGHT, X_AXIS_SHIFT_START, PLOT_TOP_PADDING } from './constants.js';
|
import { GUTTER, AXES_FONT, X_AXIS_HEIGHT, X_AXIS_SHIFT_START, PLOT_TOP_PADDING } from './constants.js';
|
||||||
import { formatCryptoValue, humanize } from './format.js';
|
import { humanize } from './format.js';
|
||||||
import { getCssColor } from './skin.js';
|
import { getCssColor } from './skin.js';
|
||||||
import { applyXEdgeOpacity, applyYEdgeOpacity, xScaleLevelToStep, yScaleLevelToStep } from './formulas.js';
|
import { applyXEdgeOpacity, applyYEdgeOpacity, xScaleLevelToStep, yScaleLevelToStep } from './formulas.js';
|
||||||
import { toPixels } from './Projection.js';
|
import { toPixels } from './Projection.js';
|
||||||
@ -47,8 +47,25 @@ export function createAxes(context, data, plotSize, colors) {
|
|||||||
|
|
||||||
if (data.isPercentage) {
|
if (data.isPercentage) {
|
||||||
_drawYAxisPercents(projection);
|
_drawYAxisPercents(projection);
|
||||||
} else if (data.isCurrency) {
|
} else if (data.secondaryYAxis) {
|
||||||
_drawYAxisCurrency(projection, data);
|
_drawYAxisScaled(
|
||||||
|
state,
|
||||||
|
projection,
|
||||||
|
Math.round(yAxisScaleTo || yAxisScale),
|
||||||
|
yMinViewportTo !== undefined ? yMinViewportTo : yMinViewport,
|
||||||
|
yMaxViewportTo !== undefined ? yMaxViewportTo : yMaxViewport,
|
||||||
|
yAxisScaleFrom ? yAxisScaleProgress : 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
_drawSecondaryYAxis(
|
||||||
|
state,
|
||||||
|
projection,
|
||||||
|
Math.round(yAxisScaleTo || yAxisScale),
|
||||||
|
yMinViewportTo !== undefined ? yMinViewportTo : yMinViewport,
|
||||||
|
yMaxViewportTo !== undefined ? yMaxViewportTo : yMaxViewport,
|
||||||
|
yAxisScaleFrom ? yAxisScaleProgress : 1,
|
||||||
|
data.secondaryYAxis,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
_drawYAxisScaled(
|
_drawYAxisScaled(
|
||||||
state,
|
state,
|
||||||
@ -125,10 +142,14 @@ export function createAxes(context, data, plotSize, colors) {
|
|||||||
? getCssColor(colors, colorKey, textOpacity)
|
? getCssColor(colors, colorKey, textOpacity)
|
||||||
: getCssColor(colors, 'y-axis-text', textOpacity);
|
: getCssColor(colors, 'y-axis-text', textOpacity);
|
||||||
|
|
||||||
|
const label = isSecondary
|
||||||
|
? humanize(value)
|
||||||
|
: `${data.valuePrefix || ''}${humanize(value)}${data.valueSuffix || ''}`;
|
||||||
|
|
||||||
if (!isSecondary) {
|
if (!isSecondary) {
|
||||||
context.fillText(humanize(value), GUTTER, yPx - GUTTER / 2);
|
context.fillText(label, GUTTER, yPx - GUTTER / 2);
|
||||||
} else {
|
} else {
|
||||||
context.fillText(humanize(value), plotSize.width - GUTTER, yPx - GUTTER / 2);
|
context.fillText(label, plotSize.width - GUTTER, yPx - GUTTER / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSecondary) {
|
if (isSecondary) {
|
||||||
@ -171,48 +192,24 @@ export function createAxes(context, data, plotSize, colors) {
|
|||||||
context.stroke();
|
context.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _drawYAxisCurrency(projection, data) {
|
function _drawSecondaryYAxis(state, projection, scaleLevel, yMin, yMax, opacity = 1, secondaryYAxis) {
|
||||||
const formatValue = data.datasets[0].values.map(value => formatCryptoValue(value));
|
const { multiplier, prefix = '', suffix = '' } = secondaryYAxis;
|
||||||
|
const step = yScaleLevelToStep(scaleLevel);
|
||||||
const total = formatValue.reduce((sum, value) => sum + value, 0);
|
const firstVisibleValue = Math.ceil(yMin / step) * step;
|
||||||
const avg1 = total / formatValue.length;
|
const lastVisibleValue = Math.floor(yMax / step) * step;
|
||||||
const avg2 = total / (formatValue.length / 2);
|
|
||||||
const avg3 = total / (formatValue.length / 3);
|
|
||||||
|
|
||||||
const averageRate1 = avg1 * data.currencyRate;
|
|
||||||
const averageRate2 = avg2 * data.currencyRate;
|
|
||||||
const averageRate3 = avg3 * data.currencyRate;
|
|
||||||
|
|
||||||
const totalAvg = [0, avg1, avg2, avg3];
|
|
||||||
const totalRate = [0, averageRate1, averageRate2, averageRate3];
|
|
||||||
|
|
||||||
const [, height] = projection.getSize();
|
|
||||||
|
|
||||||
context.font = AXES_FONT;
|
context.font = AXES_FONT;
|
||||||
context.textAlign = 'left';
|
context.textAlign = 'right';
|
||||||
context.textBaseline = 'bottom';
|
context.textBaseline = 'bottom';
|
||||||
context.lineWidth = 1;
|
|
||||||
|
|
||||||
context.beginPath();
|
for (let value = firstVisibleValue; value <= lastVisibleValue; value += step) {
|
||||||
|
const [, yPx] = toPixels(projection, 0, value);
|
||||||
|
const textOpacity = applyXEdgeOpacity(opacity, yPx);
|
||||||
|
const secondaryValue = value * multiplier;
|
||||||
|
|
||||||
totalAvg.forEach((value, index) => {
|
context.fillStyle = getCssColor(colors, 'y-axis-text', textOpacity);
|
||||||
const yPx = height - height * (value / Math.max(...formatValue)) + PLOT_TOP_PADDING;
|
context.fillText(`${prefix}${humanize(secondaryValue)}${suffix}`, plotSize.width - GUTTER, yPx - GUTTER / 2);
|
||||||
|
}
|
||||||
context.fillStyle = getCssColor(colors, 'y-axis-text', 1);
|
|
||||||
|
|
||||||
context.fillText(`${value.toFixed(2)} TON`, GUTTER, yPx - GUTTER / 4);
|
|
||||||
|
|
||||||
context.textAlign = 'right';
|
|
||||||
context.fillText(`$${totalRate[index].toFixed(2)}`, plotSize.width - GUTTER, yPx - GUTTER / 4);
|
|
||||||
|
|
||||||
context.textAlign = 'left';
|
|
||||||
|
|
||||||
context.moveTo(GUTTER, yPx);
|
|
||||||
context.strokeStyle = getCssColor(colors, 'grid-lines', 1);
|
|
||||||
context.lineTo(plotSize.width - GUTTER, yPx);
|
|
||||||
});
|
|
||||||
|
|
||||||
context.stroke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { drawXAxis, drawYAxis };
|
return { drawXAxis, drawYAxis };
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { createElement } from './minifiers.js';
|
import { createElement } from './minifiers.js';
|
||||||
import { captureEvents } from './captureEvents.js';
|
import { captureEvents } from './captureEvents.js';
|
||||||
|
import { isColorCloseToWhite } from './skin.js';
|
||||||
|
|
||||||
export function createTools(container, data, filterCallback) {
|
export function createTools(container, data, filterCallback) {
|
||||||
let _element;
|
let _element;
|
||||||
@ -35,7 +36,8 @@ export function createTools(container, data, filterCallback) {
|
|||||||
const control = createElement('a');
|
const control = createElement('a');
|
||||||
control.href = '#';
|
control.href = '#';
|
||||||
control.dataset.key = key;
|
control.dataset.key = key;
|
||||||
control.className = `lovely-chart--button lovely-chart--color-${data.colors[key].slice(1)} lovely-chart--state-checked`;
|
const darkContent = isColorCloseToWhite(data.colors[key]) ? ' lovely-chart--dark-content' : '';
|
||||||
|
control.className = `lovely-chart--button lovely-chart--color-${data.colors[key].slice(1)} lovely-chart--state-checked${darkContent}`;
|
||||||
control.innerHTML = `<span class="lovely-chart--button-check"></span><span class="lovely-chart--button-label">${name}</span>`;
|
control.innerHTML = `<span class="lovely-chart--button-check"></span><span class="lovely-chart--button-label">${name}</span>`;
|
||||||
|
|
||||||
control.addEventListener('click', (e) => {
|
control.addEventListener('click', (e) => {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { setupCanvas, clearCanvas } from './canvas.js';
|
import { setupCanvas, clearCanvas } from './canvas.js';
|
||||||
import { BALLOON_OFFSET, X_AXIS_HEIGHT } from './constants.js';
|
import { BALLOON_OFFSET, X_AXIS_HEIGHT, MAX_TOOLTIP_ITEMS } from './constants.js';
|
||||||
import { getPieRadius } from './formulas.js';
|
import { getPieRadius } from './formulas.js';
|
||||||
import {formatCryptoValue, formatInteger, getLabelDate, getLabelTime, statsFormatDayHourFull} from './format.js';
|
import { formatInteger, getLabelDate, getLabelTime, statsFormatDayHourFull } from './format.js';
|
||||||
import { getCssColor } from './skin.js';
|
import { getCssColor, isColorCloseToBackground } from './skin.js';
|
||||||
import { throttle, throttleWithRaf } from './utils.js';
|
import { throttle, throttleWithRaf } from './utils.js';
|
||||||
import { addEventListener, createElement } from './minifiers.js';
|
import { addEventListener, createElement } from './minifiers.js';
|
||||||
import { toPixels } from './Projection.js';
|
import { toPixels } from './Projection.js';
|
||||||
@ -333,12 +333,14 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _insertNewDataSet(dataSetContainer, { name, key, value }, totalValue) {
|
function _insertNewDataSet(dataSetContainer, { name, key, value }, totalValue) {
|
||||||
const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right lovely-chart--color-${data.colors[key].slice(1)}`;
|
const colorHex = data.colors[key];
|
||||||
|
const colorClass = isColorCloseToBackground(colors, colorHex) ? '' : ` lovely-chart--color-${colorHex.slice(1)}`;
|
||||||
|
const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right${colorClass}`;
|
||||||
const newDataSet = createElement();
|
const newDataSet = createElement();
|
||||||
newDataSet.className = 'lovely-chart--tooltip-dataset';
|
newDataSet.className = 'lovely-chart--tooltip-dataset';
|
||||||
newDataSet.setAttribute('data-present', 'true');
|
newDataSet.setAttribute('data-present', 'true');
|
||||||
newDataSet.setAttribute('data-name', name);
|
newDataSet.setAttribute('data-name', name);
|
||||||
newDataSet.innerHTML = `<span class="lovely-chart--dataset-title">${name}</span><span class="${className}">${formatInteger(value)}</span>`;
|
newDataSet.innerHTML = `<span class="lovely-chart--dataset-title">${name}</span><span class="${className}">${_formatValue(value)}</span>`;
|
||||||
_renderPercentageValue(newDataSet, value, totalValue);
|
_renderPercentageValue(newDataSet, value, totalValue);
|
||||||
|
|
||||||
const totalText = dataSetContainer.querySelector(`[data-total="true"]`);
|
const totalText = dataSetContainer.querySelector(`[data-total="true"]`);
|
||||||
@ -352,17 +354,19 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
|||||||
function _updateDataSet(currentDataSet, { key, value } = {}, totalValue) {
|
function _updateDataSet(currentDataSet, { key, value } = {}, totalValue) {
|
||||||
currentDataSet.setAttribute('data-present', 'true');
|
currentDataSet.setAttribute('data-present', 'true');
|
||||||
|
|
||||||
const valueElement = currentDataSet.querySelector(`.lovely-chart--tooltip-dataset-value.lovely-chart--color-${data.colors[key].slice(1)}:not(.lovely-chart--state-hidden)`);
|
const valueElement = currentDataSet.querySelector(`.lovely-chart--tooltip-dataset-value`);
|
||||||
|
|
||||||
if (data.isCurrency) {
|
if (valueElement) {
|
||||||
valueElement.innerHTML = formatCryptoValue(value);
|
valueElement.innerHTML = _formatValue(value);
|
||||||
} else {
|
|
||||||
valueElement.innerHTML = formatInteger(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderPercentageValue(currentDataSet, value, totalValue);
|
_renderPercentageValue(currentDataSet, value, totalValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _formatValue(value) {
|
||||||
|
return `${data.valuePrefix || ''}${formatInteger(value)}${data.valueSuffix || ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
function _renderPercentageValue(dataSet, value, totalValue) {
|
function _renderPercentageValue(dataSet, value, totalValue) {
|
||||||
if (!data.isPercentage) {
|
if (!data.isPercentage) {
|
||||||
return;
|
return;
|
||||||
@ -402,7 +406,10 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
|||||||
|
|
||||||
const totalValue = statistics.reduce((a, x) => a + x.value, 0);
|
const totalValue = statistics.reduce((a, x) => a + x.value, 0);
|
||||||
const pointerVector = getPointerVector();
|
const pointerVector = getPointerVector();
|
||||||
const finalStatistics = data.isPie ? statistics.filter(({ value }, index) => _isPieSectorSelected(statistics, value, totalValue, index, pointerVector)) : statistics;
|
const filteredStatistics = statistics.filter(({ value }) => value !== 0);
|
||||||
|
const sortedStatistics = filteredStatistics.sort((a, b) => b.value - a.value);
|
||||||
|
const limitedStatistics = sortedStatistics.slice(0, MAX_TOOLTIP_ITEMS);
|
||||||
|
const finalStatistics = data.isPie ? limitedStatistics.filter(({ value }, index) => _isPieSectorSelected(statistics, value, totalValue, index, pointerVector)) : limitedStatistics;
|
||||||
|
|
||||||
finalStatistics.forEach((statItem) => {
|
finalStatistics.forEach((statItem) => {
|
||||||
const currentDataSet = dataSetContainer.querySelector(`[data-name="${statItem.name}"]`);
|
const currentDataSet = dataSetContainer.querySelector(`[data-name="${statItem.name}"]`);
|
||||||
@ -411,17 +418,22 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
|||||||
_insertNewDataSet(dataSetContainer, statItem, totalValue);
|
_insertNewDataSet(dataSetContainer, statItem, totalValue);
|
||||||
} else {
|
} else {
|
||||||
_updateDataSet(currentDataSet, statItem, totalValue);
|
_updateDataSet(currentDataSet, statItem, totalValue);
|
||||||
|
dataSetContainer.appendChild(currentDataSet);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if ((data.isBars || data.isSteps) && data.isStacked) {
|
if ((data.isBars || data.isSteps || data.isAreas) && data.isStacked) {
|
||||||
_renderTotal(dataSetContainer, formatInteger(totalValue));
|
_renderTotal(dataSetContainer, _formatValue(totalValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.isCurrency) {
|
if (data.secondaryYAxis) {
|
||||||
_renderCurrencyRate(dataSetContainer, formatCryptoValue(totalValue));
|
_renderSecondaryTotal(dataSetContainer, totalValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-append total rows to keep them at the bottom after sort reordering
|
||||||
|
Array.from(dataSetContainer.querySelectorAll('[data-total="true"]'))
|
||||||
|
.forEach((el) => dataSetContainer.appendChild(el));
|
||||||
|
|
||||||
Array.from(dataSetContainer.querySelectorAll('[data-present="false"]'))
|
Array.from(dataSetContainer.querySelectorAll('[data-present="false"]'))
|
||||||
.forEach((dataSet) => {
|
.forEach((dataSet) => {
|
||||||
dataSet.remove();
|
dataSet.remove();
|
||||||
@ -438,10 +450,10 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
|||||||
const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right`;
|
const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right`;
|
||||||
if (!totalText) {
|
if (!totalText) {
|
||||||
const newTotalText = createElement();
|
const newTotalText = createElement();
|
||||||
newTotalText.className = 'lovely-chart--tooltip-dataset';
|
newTotalText.className = 'lovely-chart--tooltip-dataset lovely-chart--tooltip-dataset-total';
|
||||||
newTotalText.setAttribute('data-present', 'true');
|
newTotalText.setAttribute('data-present', 'true');
|
||||||
newTotalText.setAttribute('data-total', 'true');
|
newTotalText.setAttribute('data-total', 'true');
|
||||||
newTotalText.innerHTML = `<span>All</span><span class="${className}">${totalValue}</span>`;
|
newTotalText.innerHTML = `<span>Total</span><span class="${className}">${totalValue}</span>`;
|
||||||
dataSetContainer.appendChild(newTotalText);
|
dataSetContainer.appendChild(newTotalText);
|
||||||
} else {
|
} else {
|
||||||
totalText.setAttribute('data-present', 'true');
|
totalText.setAttribute('data-present', 'true');
|
||||||
@ -451,24 +463,25 @@ export function createTooltip(container, data, plotSize, colors, onZoom, onFocus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _renderCurrencyRate(dataSetContainer, totalValue) {
|
function _renderSecondaryTotal(dataSetContainer, totalValue) {
|
||||||
|
const { label, multiplier, prefix = '', suffix = '' } = data.secondaryYAxis;
|
||||||
const totalText = dataSetContainer.querySelector(`[data-total="true"]`);
|
const totalText = dataSetContainer.querySelector(`[data-total="true"]`);
|
||||||
const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right`;
|
const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right`;
|
||||||
|
|
||||||
const totalUsd = (parseFloat(totalValue) * data.currencyRate).toFixed(2);
|
const secondaryValue = (totalValue * multiplier).toFixed(2);
|
||||||
|
|
||||||
if (!totalText) {
|
if (!totalText) {
|
||||||
const newTotalText = createElement();
|
const newTotalText = createElement();
|
||||||
newTotalText.className = 'lovely-chart--tooltip-dataset';
|
newTotalText.className = 'lovely-chart--tooltip-dataset lovely-chart--tooltip-dataset-total';
|
||||||
newTotalText.setAttribute('data-present', 'true');
|
newTotalText.setAttribute('data-present', 'true');
|
||||||
newTotalText.setAttribute('data-total', 'true');
|
newTotalText.setAttribute('data-total', 'true');
|
||||||
newTotalText.innerHTML = `<span>USD ≈</span><span class="${className}">$${totalUsd}</span>`;
|
newTotalText.innerHTML = `<span>${label}</span><span class="${className}">${prefix}${secondaryValue}${suffix}</span>`;
|
||||||
dataSetContainer.appendChild(newTotalText);
|
dataSetContainer.appendChild(newTotalText);
|
||||||
} else {
|
} else {
|
||||||
totalText.setAttribute('data-present', 'true');
|
totalText.setAttribute('data-present', 'true');
|
||||||
|
|
||||||
const valueElement = totalText.querySelector(`.lovely-chart--tooltip-dataset-value:not(.lovely-chart--state-hidden)`);
|
const valueElement = totalText.querySelector(`.lovely-chart--tooltip-dataset-value:not(.lovely-chart--state-hidden)`);
|
||||||
valueElement.innerHTML = `$${totalUsd}`;
|
valueElement.innerHTML = `${prefix}${secondaryValue}${suffix}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export const PLOT_BARS_WIDTH_SHIFT = 0.5;
|
|||||||
export const PIE_MINIMUM_VISIBLE_PERCENT = 0.02;
|
export const PIE_MINIMUM_VISIBLE_PERCENT = 0.02;
|
||||||
|
|
||||||
export const BALLOON_OFFSET = 20;
|
export const BALLOON_OFFSET = 20;
|
||||||
|
export const MAX_TOOLTIP_ITEMS = 12;
|
||||||
|
|
||||||
export const AXES_FONT = '300 10px Helvetica, Arial, sans-serif';
|
export const AXES_FONT = '300 10px Helvetica, Arial, sans-serif';
|
||||||
export const AXES_MAX_COLUMN_WIDTH = 45;
|
export const AXES_MAX_COLUMN_WIDTH = 45;
|
||||||
|
|||||||
@ -1,8 +1,17 @@
|
|||||||
import { getMaxMin } from './utils.js';
|
import { getMaxMin } from './utils.js';
|
||||||
import { statsFormatDay, statsFormatDayHour, statsFormatText, statsFormatMin } from './format.js';
|
import { statsFormatDay, statsFormatDayHour, statsFormatText, statsFormatMin } from './format.js';
|
||||||
|
|
||||||
|
const LABEL_TYPE_TO_FORMATTER = {
|
||||||
|
'day': "statsFormat('day')",
|
||||||
|
'hour': "statsFormat('hour')",
|
||||||
|
'5min': "statsFormat('5min')",
|
||||||
|
'dayHour': 'statsFormatDayHour',
|
||||||
|
'text': undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export function analyzeData(data) {
|
export function analyzeData(data) {
|
||||||
const { title, labelFormatter, tooltipFormatter, isStacked, isPercentage, isCurrency, currencyRate, hasSecondYAxis, onZoom, minimapRange, hideCaption, zoomOutLabel } = data;
|
const { title, labelFormatter: labelFormatterRaw, labelType, tooltipFormatter, isStacked, isPercentage, secondaryYAxis, hasSecondYAxis, onZoom, minimapRange, hideCaption, zoomOutLabel, valuePrefix, valueSuffix } = data;
|
||||||
|
const labelFormatter = labelFormatterRaw || (labelType && LABEL_TYPE_TO_FORMATTER[labelType]);
|
||||||
const { datasets, labels } = prepareDatasets(data);
|
const { datasets, labels } = prepareDatasets(data);
|
||||||
|
|
||||||
const colors = {};
|
const colors = {};
|
||||||
@ -20,13 +29,8 @@ export function analyzeData(data) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let effectiveLabelFormatter = labelFormatter;
|
|
||||||
if (isCurrency) {
|
|
||||||
effectiveLabelFormatter = 'statsFormat(\'day\')';
|
|
||||||
}
|
|
||||||
|
|
||||||
let xLabels;
|
let xLabels;
|
||||||
switch (effectiveLabelFormatter) {
|
switch (labelFormatter) {
|
||||||
case 'statsFormatDayHour':
|
case 'statsFormatDayHour':
|
||||||
xLabels = statsFormatDayHour(labels);
|
xLabels = statsFormatDayHour(labels);
|
||||||
break;
|
break;
|
||||||
@ -50,9 +54,10 @@ export function analyzeData(data) {
|
|||||||
datasets,
|
datasets,
|
||||||
isStacked,
|
isStacked,
|
||||||
isPercentage,
|
isPercentage,
|
||||||
isCurrency,
|
secondaryYAxis,
|
||||||
currencyRate,
|
|
||||||
hasSecondYAxis,
|
hasSecondYAxis,
|
||||||
|
valuePrefix,
|
||||||
|
valueSuffix,
|
||||||
onZoom,
|
onZoom,
|
||||||
isLines: data.type === 'line',
|
isLines: data.type === 'line',
|
||||||
isBars: data.type === 'bar',
|
isBars: data.type === 'bar',
|
||||||
|
|||||||
@ -27,18 +27,14 @@ export function drawDatasets(
|
|||||||
let datasetProjection = hasOwnYAxis ? secondaryProjection : projection;
|
let datasetProjection = hasOwnYAxis ? secondaryProjection : projection;
|
||||||
|
|
||||||
if (datasetType === 'area') {
|
if (datasetType === 'area') {
|
||||||
const { yMin, yMax } = projection.getParams();
|
|
||||||
const yHeight = yMax - yMin;
|
|
||||||
const bottomLine = [
|
const bottomLine = [
|
||||||
{ labelIndex: range.from, stackValue: 0 },
|
{ labelIndex: range.from, stackValue: 0 },
|
||||||
{ labelIndex: range.to, stackValue: 0 },
|
{ labelIndex: range.to, stackValue: 0 },
|
||||||
];
|
];
|
||||||
const topLine = [
|
const lowerBoundary = points[i - 1] || bottomLine;
|
||||||
{ labelIndex: range.to, stackValue: yHeight },
|
const upperBoundary = points[i].slice().reverse();
|
||||||
{ labelIndex: range.from, stackValue: yHeight },
|
|
||||||
];
|
|
||||||
|
|
||||||
datasetPoints = mergeArrays([points[i - 1] || bottomLine, topLine]);
|
datasetPoints = mergeArrays([lowerBoundary, upperBoundary]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (datasetType === 'pie') {
|
if (datasetType === 'pie') {
|
||||||
|
|||||||
@ -59,11 +59,20 @@ function keepThreeDigits(value, decimals) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatInteger(n) {
|
export function formatInteger(n) {
|
||||||
return String(n).replace(/\d(?=(\d{3})+$)/g, '$& ');
|
if (!Number.isInteger(n)) {
|
||||||
|
const abs = Math.abs(n);
|
||||||
|
const decimals = (abs > 0 && abs < 1)
|
||||||
|
? Math.max(2, -Math.floor(Math.log10(abs)) + 1)
|
||||||
|
: 2;
|
||||||
|
const [intPart, decPart] = n.toFixed(decimals).split('.');
|
||||||
|
const trimmed = decPart.replace(/0+$/, '');
|
||||||
|
return trimmed ? addThousandSeparators(intPart) + '.' + trimmed : addThousandSeparators(intPart);
|
||||||
|
}
|
||||||
|
return addThousandSeparators(String(n));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatCryptoValue(n) {
|
function addThousandSeparators(s) {
|
||||||
return Number(n / 10 ** 9);
|
return s.replace(/\d(?=(\d{3})+$)/g, '$& ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFullLabelDate(label, { isShort = false } = {}) {
|
export function getFullLabelDate(label, { isShort = false } = {}) {
|
||||||
|
|||||||
@ -59,7 +59,9 @@ export function createColors(datasetColors) {
|
|||||||
|
|
||||||
addCssRule(styleSheet, `.lovely-chart--tooltip-dataset-value${baseClass}-${datasetColors[key].slice(1)}`, `color: ${datasetColors[key]}`);
|
addCssRule(styleSheet, `.lovely-chart--tooltip-dataset-value${baseClass}-${datasetColors[key].slice(1)}`, `color: ${datasetColors[key]}`);
|
||||||
addCssRule(styleSheet, `.lovely-chart--button${baseClass}-${datasetColors[key].slice(1)}`, `border-color: ${datasetColors[key]}; color: ${datasetColors[key]}`);
|
addCssRule(styleSheet, `.lovely-chart--button${baseClass}-${datasetColors[key].slice(1)}`, `border-color: ${datasetColors[key]}; color: ${datasetColors[key]}`);
|
||||||
addCssRule(styleSheet, `.lovely-chart--button.lovely-chart--state-checked${baseClass}-${datasetColors[key].slice(1)}`, `background-color: ${datasetColors[key]}`);
|
|
||||||
|
const checkedBtnSelector = `.lovely-chart--button.lovely-chart--state-checked${baseClass}-${datasetColors[key].slice(1)}`;
|
||||||
|
addCssRule(styleSheet, checkedBtnSelector, `background-color: ${datasetColors[key]}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,6 +87,20 @@ function buildCssColor([r, g, b, a = 1], opacity = 1) {
|
|||||||
return `rgba(${r}, ${g}, ${b}, ${a * opacity})`;
|
return `rgba(${r}, ${g}, ${b}, ${a * opacity})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isColorCloseToBackground(colors, hex) {
|
||||||
|
const bg = colors[skin]['tooltip-background'];
|
||||||
|
const fg = hexToChannels(hex);
|
||||||
|
return colorDistance(bg, fg) < 70;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isColorCloseToWhite(hex) {
|
||||||
|
return colorDistance(hexToChannels(hex), [255, 255, 255]) < 70;
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorDistance([r1, g1, b1], [r2, g2, b2]) {
|
||||||
|
return Math.sqrt((r1 - r2) ** 2 + (g1 - g2) ** 2 + (b1 - b2) ** 2);
|
||||||
|
}
|
||||||
|
|
||||||
function addCssRule(sheet, selector, rule) {
|
function addCssRule(sheet, selector, rule) {
|
||||||
sheet.insertRule(`${selector} { ${rule} }`, sheet.cssRules.length);
|
sheet.insertRule(`${selector} { ${rule} }`, sheet.cssRules.length);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,11 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
transition: opacity 150ms ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
&.lovely-chart--state-checked {
|
&.lovely-chart--state-checked {
|
||||||
background-color: var(--text-color);
|
background-color: var(--text-color);
|
||||||
@ -28,6 +33,14 @@
|
|||||||
transform: translateX(6px);
|
transform: translateX(6px);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.lovely-chart--dark-content .lovely-chart--button-label {
|
||||||
|
color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.lovely-chart--dark-content .lovely-chart--button-check::after {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -256 1792 1792' version='1.1'%0A%3E%3Cg transform='matrix(1,0,0,-1,7.5932203,1217.0847)' id='g3003'%3E%3Cpath d='m 1671,970 q 0,-40 -28,-68 L 919,178 783,42 Q 755,14 715,14 675,14 647,42 L 511,178 149,540 q -28,28 -28,68 0,40 28,68 l 136,136 q 28,28 68,28 40,0 68,-28 l 294,-295 656,657 q 28,28 68,28 40,0 68,-28 l 136,-136 q 28,-28 28,-68 z' style='fill:%23222222'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.lovely-chart--state-shake {
|
&.lovely-chart--state-shake {
|
||||||
@ -64,6 +77,7 @@
|
|||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -256 1792 1792' version='1.1'%0A%3E%3Cg transform='matrix(1,0,0,-1,7.5932203,1217.0847)' id='g3003'%3E%3Cpath d='m 1671,970 q 0,-40 -28,-68 L 919,178 783,42 Q 755,14 715,14 675,14 647,42 L 511,178 149,540 q -28,28 -28,68 0,40 28,68 l 136,136 q 28,28 68,28 40,0 68,-28 l 294,-295 656,657 q 28,28 68,28 40,0 68,-28 l 136,-136 q 28,-28 28,-68 z' style='fill:white'/%3E%3C/g%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -256 1792 1792' version='1.1'%0A%3E%3Cg transform='matrix(1,0,0,-1,7.5932203,1217.0847)' id='g3003'%3E%3Cpath d='m 1671,970 q 0,-40 -28,-68 L 919,178 783,42 Q 755,14 715,14 675,14 647,42 L 511,178 149,540 q -28,28 -28,68 0,40 28,68 l 136,136 q 28,28 68,28 40,0 68,-28 l 294,-295 656,657 q 28,28 68,28 40,0 68,-28 l 136,-136 q 28,-28 28,-68 z' style='fill:white'/%3E%3C/g%3E%3C/svg%3E");
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lovely-chart--button-label {
|
.lovely-chart--button-label {
|
||||||
|
|||||||
@ -1,7 +1,27 @@
|
|||||||
.lovely-chart--container {
|
.lovely-chart--container {
|
||||||
|
--background-color: #ffffff;
|
||||||
|
--text-color: #222222;
|
||||||
|
--minimap-mask: #{rgba(#E2EEF9, 0.6)};
|
||||||
|
--minimap-slider: #C0D1E1;
|
||||||
|
--grid-lines: #{rgba(#182D3B, 0.1)};
|
||||||
|
--zoom-out-text: #108BE3;
|
||||||
|
--tooltip-background: #ffffff;
|
||||||
|
--tooltip-arrow: #D2D5D7;
|
||||||
|
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
|
html.theme-dark & {
|
||||||
|
--background-color: #242F3E;
|
||||||
|
--text-color: #ffffff;
|
||||||
|
--minimap-mask: #{rgba(#304259, 0.6)};
|
||||||
|
--minimap-slider: #56626D;
|
||||||
|
--grid-lines: #{rgba(#FFFFFF, 0.1)};
|
||||||
|
--zoom-out-text: #48AAF0;
|
||||||
|
--tooltip-background: #1c2533;
|
||||||
|
--tooltip-arrow: #D2D5D7;
|
||||||
|
}
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@ -16,13 +16,7 @@
|
|||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
float: left;
|
float: left;
|
||||||
margin: 0 1rem;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
text-transform: lowercase;
|
|
||||||
|
|
||||||
&:first-letter {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-caption {
|
&-caption {
|
||||||
|
|||||||
@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
.lovely-chart--tooltip-title {
|
.lovely-chart--tooltip-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 6px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
@ -89,7 +89,12 @@
|
|||||||
|
|
||||||
.lovely-chart--tooltip-dataset-value {
|
.lovely-chart--tooltip-dataset-value {
|
||||||
float: right;
|
float: right;
|
||||||
margin-left: 5px;
|
margin-left: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.lovely-chart--tooltip-dataset-total {
|
||||||
|
margin-top: 6px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
.lovely-chart--container {
|
|
||||||
--background-color: #ffffff;
|
|
||||||
--text-color: #222222;
|
|
||||||
--minimap-mask: #{rgba(#E2EEF9, 0.6)};
|
|
||||||
--minimap-slider: #C0D1E1;
|
|
||||||
--grid-lines: #{rgba(#182D3B, 0.1)};
|
|
||||||
--zoom-out-text: #108BE3;
|
|
||||||
--tooltip-background: #ffffff;
|
|
||||||
--tooltip-arrow: #D2D5D7;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.theme-dark .lovely-chart--container {
|
|
||||||
--background-color: #242F3E;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--minimap-mask: #{rgba(#304259, 0.6)};
|
|
||||||
--minimap-slider: #56626D;
|
|
||||||
--grid-lines: #{rgba(#FFFFFF, 0.1)};
|
|
||||||
--zoom-out-text: #48AAF0;
|
|
||||||
--tooltip-background: #1c2533;
|
|
||||||
--tooltip-arrow: #D2D5D7;
|
|
||||||
}
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
@use 'variables';
|
|
||||||
@use 'animations';
|
@use 'animations';
|
||||||
@use 'common';
|
@use 'common';
|
||||||
@use 'header';
|
@use 'header';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user