TelegramPWA/src/lib/lovely-chart/LovelyChart.js
2022-03-19 21:20:04 +01:00

216 lines
6.2 KiB
JavaScript

import { createStateManager } from './StateManager';
import { createHeader } from './Header';
import { createAxes } from './Axes';
import { createMinimap } from './Minimap';
import { createTooltip } from './Tooltip';
import { createTools } from './Tools';
import { createZoomer } from './Zoomer';
import { createColors } from './skin';
import { analyzeData } from './data';
import { setupCanvas, clearCanvas } from './canvas';
import { preparePoints } from './preparePoints';
import { createProjection } from './Projection';
import { drawDatasets } from './drawDatasets';
import { createElement } from './minifiers';
import { getFullLabelDate, getLabelDate } from './format';
import {
X_AXIS_HEIGHT,
GUTTER,
PLOT_TOP_PADDING,
PLOT_HEIGHT,
PLOT_LINE_WIDTH,
SIMPLIFIER_PLOT_FACTOR,
} from './constants';
import { getSimplificationDelta, isDataRange } from './formulas';
import { debounce } from './utils';
import './styles/index.scss';
function create(container, originalData) {
let _stateManager;
let _element;
let _plot;
let _context;
let _plotSize;
let _header;
let _axes;
let _minimap;
let _tooltip;
let _tools;
let _zoomer;
let _state;
let _windowWidth = window.innerWidth;
const _data = analyzeData(originalData);
const _colors = createColors(_data.colors);
const _redrawDebounced = debounce(_redraw, 500, false, true);
_setupComponents();
_setupGlobalListeners();
function _setupComponents() {
_setupContainer();
_header = createHeader(_element, _data.title, _data.zoomOutLabel, _onZoomOut);
_setupPlotCanvas();
_stateManager = createStateManager(_data, _plotSize, _onStateUpdate);
_axes = createAxes(_context, _data, _plotSize, _colors);
_minimap = createMinimap(_element, _data, _colors, _onRangeChange);
_tooltip = createTooltip(_element, _data, _plotSize, _colors, _onZoomIn, _onFocus);
_tools = createTools(_element, _data, _onFilterChange);
_zoomer = _data.isZoomable && createZoomer(_data, originalData, _colors, _stateManager, _element, _header, _minimap, _tooltip, _tools);
// hideOnScroll(_element);
}
function _setupContainer() {
_element = createElement();
_element.className = `lovely-chart--container${_data.shouldZoomToPie ? ' lovely-chart--container-type-pie' : ''}`;
container.appendChild(_element);
}
function _setupPlotCanvas() {
const { canvas, context } = setupCanvas(_element, {
width: _element.clientWidth,
height: PLOT_HEIGHT,
});
_plot = canvas;
_context = context;
_plotSize = {
width: _plot.offsetWidth,
height: _plot.offsetHeight,
};
}
function _onStateUpdate(state) {
_state = state;
const { datasets } = _data;
const range = {
from: state.labelFromIndex,
to: state.labelToIndex,
};
const boundsAndParams = {
begin: state.begin,
end: state.end,
totalXWidth: state.totalXWidth,
yMin: state.yMinViewport,
yMax: state.yMaxViewport,
availableWidth: _plotSize.width,
availableHeight: _plotSize.height - X_AXIS_HEIGHT,
xPadding: GUTTER,
yPadding: PLOT_TOP_PADDING,
};
const visibilities = datasets.map(({ key }) => state[`opacity#${key}`]);
const points = preparePoints(_data, datasets, range, visibilities, boundsAndParams);
const projection = createProjection(boundsAndParams);
let secondaryPoints = null;
let secondaryProjection = null;
if (_data.hasSecondYAxis) {
const secondaryDataset = datasets.find((d) => d.hasOwnYAxis);
const bounds = {
yMin: state.yMinViewportSecond,
yMax: state.yMaxViewportSecond,
};
secondaryPoints = preparePoints(_data, [secondaryDataset], range, visibilities, bounds)[0];
secondaryProjection = projection.copy(bounds);
}
if (!_data.hideCaption) {
_header.setCaption(_getCaption(state));
}
clearCanvas(_plot, _context);
const totalPoints = points.reduce((a, p) => a + p.length, 0);
const simplification = getSimplificationDelta(totalPoints) * SIMPLIFIER_PLOT_FACTOR;
drawDatasets(
_context, state, _data,
range, points, projection, secondaryPoints, secondaryProjection,
PLOT_LINE_WIDTH, visibilities, _colors, false, simplification,
);
if (!_data.isPie) {
_axes.drawYAxis(state, projection, secondaryProjection);
// TODO check isChanged
_axes.drawXAxis(state, projection);
}
_minimap.update(state);
_tooltip.update(state, points, projection, secondaryPoints, secondaryProjection);
}
function _onRangeChange(range) {
_stateManager.update({ range });
}
function _onFilterChange(filter) {
_stateManager.update({ filter });
}
function _onFocus(focusOn) {
if (_data.isBars || _data.isPie || _data.isSteps) {
// TODO animate
_stateManager.update({ focusOn });
}
}
function _onZoomIn(labelIndex) {
_zoomer.zoomIn(_state, labelIndex);
}
function _onZoomOut() {
_zoomer.zoomOut(_state);
}
function _setupGlobalListeners() {
document.documentElement.addEventListener('darkmode', () => {
_stateManager.update();
});
window.addEventListener('resize', () => {
if (window.innerWidth !== _windowWidth) {
_windowWidth = window.innerWidth;
_redrawDebounced();
}
});
window.addEventListener('orientationchange', () => {
_redrawDebounced();
});
}
function _redraw() {
Object.assign(_data, analyzeData(originalData));
_element.remove();
_setupComponents();
}
function _getCaption(state) {
let startIndex;
let endIndex;
if (_zoomer && _zoomer.isZoomed()) {
// TODO Fix label
startIndex = state.labelFromIndex === 0 ? 0 : state.labelFromIndex + 1;
endIndex = state.labelToIndex === state.totalXWidth - 1 ? state.labelToIndex : state.labelToIndex - 1;
} else {
startIndex = state.labelFromIndex;
endIndex = state.labelToIndex;
}
return isDataRange(_data.xLabels[startIndex], _data.xLabels[endIndex])
? (
`${getLabelDate(_data.xLabels[startIndex])}` +
' — ' +
`${getLabelDate(_data.xLabels[endIndex])}`
)
: getFullLabelDate(_data.xLabels[startIndex]);
}
}
export { create };