Commit 826ccb62 authored by Vasily Belolapotkov's avatar Vasily Belolapotkov

refactor: reorganize types and components

parent e6b76d1e
import { makeChart } from '../chart-components/chart';
import { ISrcData, SrcTypes } from '../data/srcData';
import { ChartType, IChartData } from '../chart-components/types';
export function renderCharts(
chartsData: any[],
containerElement: HTMLElement,
): void {
chartsData.forEach(rawChartData => {
const formattedChartData = makeChartData(rawChartData);
const chart = makeChart(formattedChartData);
chart.render(containerElement);
});
}
function makeChartData(rawCharData: ISrcData): IChartData {
const chartData: IChartData = {
xAxis: [],
dataSets: [],
};
const { columns, types, names, colors } = rawCharData;
columns.forEach((column, index) => {
const name = <string>column[0];
const values = <number[]>column.slice(1);
if (types[name] === SrcTypes.x) {
chartData.xAxis = values;
return;
}
chartData.dataSets.push({
values,
id: index,
name: names[name],
type: ChartType.line,
color: colors[name],
isVisible: true,
});
});
return chartData;
}
import { ISrcData } from '../data/srcData';
import { makeHtmlElement } from '../utils/ui';
import { renderThemeSwitcher } from './theme-switcher';
import { renderCharts } from './charts';
export function renderApp(chartsData: ISrcData[]): void {
const rootElement = renderRootElement();
renderThemeSwitcher(rootElement);
renderCharts(chartsData, rootElement);
}
function renderRootElement(): HTMLElement {
const rootElement = createRootElement();
document.body.appendChild(rootElement);
return rootElement;
}
function createRootElement(): HTMLElement {
const rootElement = makeHtmlElement('div', {
attributes: { id: 'root' },
});
rootElement.innerHTML = '<h1>Followers</h1>';
return rootElement;
}
import { makeHtmlElement } from './chart/ui-utils';
import { DOMContainerElement, IRenderable } from './chart/chart-component';
import { makeHtmlElement } from '../utils/ui';
interface IThemeSwitcher {
readonly render: (container: HTMLElement) => void;
}
export function renderThemeSwitcher(container: HTMLElement): void {
const switcher = makeThemeSwitcher();
switcher.render(container);
}
function makeThemeSwitcher(): IRenderable<null> {
function makeThemeSwitcher(): IThemeSwitcher {
let currentTheme = 'light';
let control: HTMLElement;
......@@ -14,7 +17,7 @@ function makeThemeSwitcher(): IRenderable<null> {
render,
});
function render(container: DOMContainerElement) {
function render(container: HTMLElement) {
const switcher = makeHtmlElement('div', {
classList: ['theme-switcher'],
});
......
import { applyElementConfig, makeHtmlElement, toPx } from './ui-utils';
import { ChartComponentFactory } from './chart-component';
import { applyElementConfig, makeHtmlElement, toPx } from '../utils/ui';
import { ChartComponentFactory } from './types';
import { renderPlots } from './plot';
import { makeTransparentImage } from './transparent-image';
......
export type Timestamp = number;
export enum ChartType {
line = 'line',
}
export interface IChartData {
xAxis: Timestamp[];
dataSets?: IDataSet[];
}
export interface IDataSet {
id: number;
type: ChartType;
name: string;
color?: string;
values: number[];
isVisible: boolean;
}
export interface IRange {
start: number;
end: number;
distance: number;
}
export interface IChartState {
readonly xAxis: Timestamp[];
readonly dataSets: IDataSet[];
readonly xTotalRange: IRange;
readonly yTotalRange: IRange;
xScale: number;
xOffset: number;
yScale: number;
yOffset: number;
yLabels: number[];
inspectorIsActive: boolean;
inspectorValues: IInspectorValues;
setXViewRange: (xScale: number, xOffset: number) => void;
toggleDataSetVisibility: (dataSet: IDataSet) => void;
setInspectorVisibility: (isActive: boolean) => void;
moveInspector: (relativeXOffset: number) => void;
}
export interface IInspectorValues {
xValue: Timestamp;
yValues: IInspectorValue[];
}
export interface IInspectorValue {
name: string;
color: string;
value: number;
isVisible: boolean;
}
import {
IChartData,
IChartState,
IChartStateController,
IDataSet,
IInspectorValue,
IInspectorValues,
IRange,
Timestamp,
} from './types';
export function makeChartState(chartData: IChartData): IChartState {
const yLabelsCount = 6;
......@@ -223,18 +179,6 @@ export function makeChartState(chartData: IChartData): IChartState {
export type ChartEventListener = (chartState: IChartState) => void;
export interface IChartStateController {
getState: () => IChartState;
changeXViewRange: (xScale: number, xOffset?: number) => void;
toggleDataSetVisibility: (dataSet: IDataSet) => void;
addListener: (eventName: string, listener: ChartEventListener) => void;
removeListener: (eventName: string, listener: ChartEventListener) => void;
showInspector: () => void;
hideInspector: () => void;
toggleInspector: () => void;
moveInspector: (xOffset: number) => void;
}
export function makeChartStateController(
chartState: IChartState,
): IChartStateController {
......@@ -246,7 +190,6 @@ export function makeChartStateController(
changeXViewRange,
toggleDataSetVisibility,
addListener,
removeListener,
showInspector,
hideInspector,
toggleInspector,
......@@ -263,21 +206,6 @@ export function makeChartStateController(
listeners.set(eventName, eventListeners);
}
function removeListener(eventName: string, listener: ChartEventListener) {
const eventListeners = listeners.get(eventName) || [];
const index = eventListeners.indexOf(listener);
if (index < 0) {
return;
}
const updatedEventListeners = [
...eventListeners.slice(0, index),
...eventListeners.slice(index + 1),
];
listeners.set(eventName, updatedEventListeners);
}
function fireEvent(eventName: string) {
const eventListeners = listeners.get(eventName) || [];
eventListeners.forEach(listener => listener(state));
......
import { makeHtmlElement } from './ui-utils';
import { makeHtmlElement } from '../utils/ui';
import { makeChartPreview } from './chart-preview';
import {
IChartData,
makeChartState,
makeChartStateController,
} from './chart-state';
import { makeChartState, makeChartStateController } from './chart-state';
import { IRenderable } from './chart-component';
import { IChartData, IChartComponent } from './types';
import { makePlotArea } from './plot-area';
import { makeLegend } from './legend';
export function makeChart(chartData: IChartData): IRenderable<any> {
export function makeChart(chartData: IChartData): IChartComponent<any> {
const chartState = makeChartState(chartData);
const chartStateController = makeChartStateController(chartState);
......
import checkSolidSvg from '../assets/check-solid.svg';
import { makeHtmlElement } from './ui-utils';
import { makeHtmlElement } from '../utils/ui';
export function makeCheckIcon(): HTMLElement {
const el = makeHtmlElement('i', {
......
import { ChartComponentFactory, DOMContainerElement } from './chart-component';
import { applyElementConfig, makeHtmlElement } from './ui-utils';
import { IDataSet } from './chart-state';
import { DOMContainerElement, ChartComponentFactory, IDataSet } from './types';
import { applyElementConfig, makeHtmlElement } from '../utils/ui';
import { makeCheckIcon } from './check-icon';
export const makeLegend: ChartComponentFactory<null> = function(
......
import { ChartComponentFactory, DOMContainerElement } from './chart-component';
import { ISize, renderPlots } from './plot';
import { makeHtmlElement } from './ui-utils';
import { DOMContainerElement, ChartComponentFactory, ISize } from './types';
import { renderPlots } from './plot';
import { makeHtmlElement } from '../utils/ui';
interface IPlotAreaProps {
plotWidth: number;
......
import {
IAttributesMap,
applyElementConfig,
IAttributesMap,
makeLinearAnimation,
makeSvgElement,
makeSvgRootElement,
makeLinearAnimation,
} from './ui-utils';
import { DOMContainerElement, IRenderable } from './chart-component';
} from '../utils/ui';
import {
IChartStateController,
DOMContainerElement,
IAxesConfig,
IChartComponent,
ICoordinates,
IDataSet,
IRange,
ISize,
IZoomConfig,
Timestamp,
} from './chart-state';
} from './types';
import { makeYAxis } from './y-axis';
import { makeXAxis } from './x-axis';
import { makePlotsInspector } from './plots-inspector';
......@@ -23,7 +28,7 @@ interface IPlotContainerAttributes extends IAttributesMap {
viewBox: string;
}
interface IPlots extends IRenderable<IPlotsProps> {
interface IPlots extends IChartComponent<IPlotsProps> {
renderDataSets: (xAxis: Timestamp[], dataSets: IDataSet[]) => void;
}
......@@ -32,7 +37,7 @@ interface IPlotsProps {
zoomConfig: IZoomConfig;
}
export interface IPlotContainer extends IRenderable<ISize> {
export interface IPlotContainer extends IChartComponent<ISize> {
renderPlots: () => void;
renderAxes: () => void;
renderInspector: () => void;
......@@ -45,26 +50,6 @@ export interface IPlotsConfig {
enableInspector?: boolean;
}
export interface ISize {
width: number;
height: number;
}
interface IZoomConfig {
x: boolean;
y: boolean;
}
export interface IAxesConfig {
x: boolean;
y: boolean;
}
export interface ICoordinates {
x: number;
y: number;
}
export function renderPlots(
htmlContainer: HTMLElement,
chartStateController: IChartStateController,
......
import { IChartStateController, IInspectorValue } from './chart-state';
import { DOMContainerElement, IRenderable } from './chart-component';
import { ISize } from './plot';
import {
DOMContainerElement,
ISize,
IInspectorValue,
ChartComponentFactory,
} from './types';
import {
applyElementConfig,
IAttributesMap,
makeHtmlElement,
makeSvgElement,
} from './ui-utils';
} from '../utils/ui';
interface IPlotsInspectorProps {
plotSize: ISize;
tooltipContainer: HTMLElement;
}
export function makePlotsInspector(
chartStateController: IChartStateController,
): IRenderable<IPlotsInspectorProps> {
export const makePlotsInspector: ChartComponentFactory<
IPlotsInspectorProps
> = function(chartStateController) {
const stateController = chartStateController;
const svgPointRadius = '5';
......@@ -249,4 +252,4 @@ export function makePlotsInspector(
)
.join('')}</div> `;
}
}
};
import { ChartEventListener } from './chart-state';
export type Timestamp = number;
export enum ChartType {
line = 'line',
}
export interface IChartData {
xAxis: Timestamp[];
dataSets?: IDataSet[];
}
export interface IDataSet {
id: number;
type: ChartType;
name: string;
color?: string;
values: number[];
isVisible: boolean;
}
export interface IRange {
start: number;
end: number;
distance: number;
}
export interface IInspectorValues {
xValue: Timestamp;
yValues: IInspectorValue[];
}
export interface IInspectorValue {
name: string;
color: string;
value: number;
isVisible: boolean;
}
export interface ISize {
width: number;
height: number;
}
export interface IZoomConfig {
x: boolean;
y: boolean;
}
export interface IAxesConfig {
x: boolean;
y: boolean;
}
export interface ICoordinates {
x: number;
y: number;
}
export interface IChartState {
readonly xAxis: Timestamp[];
readonly dataSets: IDataSet[];
readonly xTotalRange: IRange;
readonly yTotalRange: IRange;
xScale: number;
xOffset: number;
yScale: number;
yOffset: number;
yLabels: number[];
inspectorIsActive: boolean;
inspectorValues: IInspectorValues;
setXViewRange: (xScale: number, xOffset: number) => void;
toggleDataSetVisibility: (dataSet: IDataSet) => void;
setInspectorVisibility: (isActive: boolean) => void;
moveInspector: (relativeXOffset: number) => void;
}
export interface IChartStateController {
getState: () => IChartState;
changeXViewRange: (xScale: number, xOffset?: number) => void;
toggleDataSetVisibility: (dataSet: IDataSet) => void;
addListener: (eventName: string, listener: ChartEventListener) => void;
showInspector: () => void;
hideInspector: () => void;
toggleInspector: () => void;
moveInspector: (xOffset: number) => void;
}
export type ChartComponentFactory<TProps> = (
stateController: IChartStateController,
) => IChartComponent<TProps>;
export type DOMContainerElement = HTMLElement | SVGElement;
export interface IChartComponent<TProps> {
render: (container: DOMContainerElement, props?: TProps) => void;
}
import { IChartStateController, Timestamp } from './chart-state';
import { DOMContainerElement, IRenderable } from './chart-component';
import { ISize } from './plot';
import {
DOMContainerElement,
IChartStateController,
ISize,
IChartComponent,
Timestamp,
} from './types';
import {
applyElementConfig,
makeSvgElement,
makeSvgRootElement,
} from './ui-utils';
} from '../utils/ui';
interface IXAxisProps {
plotSize: ISize;
......@@ -14,7 +19,7 @@ interface IXAxisProps {
export function makeXAxis(
chartStateController: IChartStateController,
): IRenderable<IXAxisProps> {
): IChartComponent<IXAxisProps> {
const stateController = chartStateController;
const dayRange = 24 * 60 * 60 * 1000;
const xTextLabelOffset = 25;
......
import { IChartStateController } from './chart-state';
import { DOMContainerElement, IRenderable } from './chart-component';
import {
IChartComponent,
IChartStateController,
DOMContainerElement,
ISize,
} from './types';
import {
applyElementConfig,
formatNumber,
makeLinearAnimation,
makeSvgElement,
} from './ui-utils';
import { ISize } from './plot';
} from '../utils/ui';
interface IYAxisProps {
plotSize: ISize;
......@@ -14,7 +17,7 @@ interface IYAxisProps {
export function makeYAxis(
chartStateController: IChartStateController,
): IRenderable<IYAxisProps> {
): IChartComponent<IYAxisProps> {
const stateController = chartStateController;
let svgAxis: SVGElement;
......
import { IChartStateController } from './chart-state';
export type DOMContainerElement = HTMLElement | SVGElement;
export interface IRenderable<TProps> {
render: (container: DOMContainerElement, props?: TProps) => void;
}
export type ChartComponentFactory<TProps> = (
stateController: IChartStateController,
) => IRenderable<TProps>;
import './styles/index.scss';
import { renderApp } from './app';
import chartsData from './data/chart_data.json';
import { makeChart } from './chart';
import { IChartData, ChartType } from './chart/chart-state';
import { ISrcData, SrcTypes } from './data/srcData';
import { renderThemeSwitcher } from './theme-switcher';
renderApp();
function renderApp(): void {
const rootElement = createRootElement();
document.body.appendChild(rootElement);
renderThemeSwitcher(rootElement);
renderCharts(chartsData, rootElement);
}
function createRootElement(): HTMLElement {
const rootElement = document.createElement('div');
rootElement.id = 'root';
rootElement.innerHTML = '<h1>Followers</h1>';
return rootElement;
}
function renderCharts(chartsData: any[], containerElement: HTMLElement): void {
chartsData.forEach(rawChartData => {
const formattedChartData = makeChartData(rawChartData);
const chart = makeChart(formattedChartData);
chart.render(containerElement);
});
}
function makeChartData(rawCharData: ISrcData): IChartData {
const chartData: IChartData = {
xAxis: [],
dataSets: [],
};
const { columns, types, names, colors } = rawCharData;
columns.forEach((column, index) => {
const name = <string>column[0];