abc-charts
The library for visualisation data via charts.
Install
npm i abc-charts --save
Exported modules
-
abc-charts
- almost all the modules, except below -
abc-charts/widgetFactory
- factory to create widgets -
abc-charts/widgetConfig
- config to provide to the widget factory -
abc-charts/dataProvider
- data provider for GraphQL -
abc-charts/constants
- constants for using in selectors only -
abc-charts/settings
- widget settings, helping functions and controls -
abc-charts/types
- general types
Usage
Importing needed classes
import {WidgetConfig} from 'abc-charts/widgetConfig';
import {WidgetFactory} from 'abc-charts/widgetFactory'; // Фабрика
import {DataProvider} from "abc-charts/dataProvider"; // Провайдер данных
import {Constants} from "abc-charts/constants"; // Значения литеральных типов и перечислений
import {SomeInterface} from 'abc-charts/interfaces';
import {SomeType} from 'abc-charts/types';
If your intergration system does not includes "material design" or needed fonts you can import these from the "abc-charts".
For example:
TypeScript:
require('abc-charts/styles.css');
Less/Scss:
@import '~abc-charts/styles.css';
Running WidgetFactory to draw widget from template. Also you can use Promise for receiving signals about complete of loading widget or errors.
(Looking example below...)
Interface IChart has method for getting available variables for EventBus:
interface IChart {
getVariables(): IWidgetVariables;
...
}
where IWidgetVariables is
{
<VAR_NAME>: {
description?: string;
}
}
Full example with massive sending
Service:
class EventBusService {
private eventBus: EventBus = null;
constructor() {
this.eventBus = new EventBus();
this.eventBus.useStateDeferredMerging = false;
}
getWrapper(): EventBusWrapper {
return new EventBusWrapper(this.eventBus);
}
appendWidgetVariables(eventBusWrapper: EventBusWrapper, widgetVars: IWidgetVariables): void {
eventBusWrapper.varAliases = [...Object.keys(widgetVars), '_cb']
.reduce((obj, key) => {
obj[key] = {listen: key, trigger: key};
return obj;
}, {})
}
async sendMassive(eventBusWrapper: EventBusWrapper, widgetVars: IWidgetVariables, values: NameValue[]): Promise<void> {
return new Promise(resolve => {
let params: IEventData = {
_cb: {
func: resolve
}
};
values.forEach((v: NameValue) => {
const regExp: RegExp = new RegExp(`^${v.name}`, 'g')
params = {
...params,
...Object.keys(widgetVars)
.filter(v => regExp.test(v))
.reduce((obj, key) => {
obj[key] = v.value;
return obj;
}, {})
};
});
eventBusWrapper.triggerStateChange(params);
}
}
}
Main code:
class Example {
private widget: IChart = null;
create() {
const eventBusWrapper: EventBusWrapper = eventBusService.getWrapper();
this.widgetConfig = new WidgetConfig();
this.widgetConfig.apiUrl = <API URL>;
this.widgetConfig.eventBus = eventBus;
this.widgetConfig.templateId = <TEMPLATE ID>;
this.widgetConfig.element = <HTMLElement>;
this.widgetConfig.afterCreate = async (widget: IChart): Promise<void> => {
// Function call after create before the first render
const widgetVars: IWidgetVariables = widget.getVariables();
eventBusService.appendWidgetVariables(eventBusWrapper, widgetVars);
// First init of widget variables
await eventBusService.sendMassive(eventBusWrapper, widgetVars, [
{name: 'start date', value: DateHelper.yyyymmdd(new Date())},
{name: 'finish date', value: DateHelper.yyyymmdd(new Date())},
]);
}
(new WidgetFactory).run(this.widgetConfig, <options?: WidgetOptions>).then(
(widget: IChart) => this.widget = widget,
(err) => <ERROR FUNCTION>
);
}
destroy() {
if (this.widget) {
this.widget.destroy(); // Unsubscribe
this.widget = null
}
}
}
Basic auth
The authorization hash stored in the LocalStorage. The integration product must control authentication on its own and store it in LocalStorage using the “authToken” key.
localStorage.setItem('authToken', btoa('login:pass'))
Prevent logs
You can hide logs specify WidgetOptions when creating the widget via WidgetFactory.
(new WidgetFactory).run(config: WidgetConfig, options: WidgetOptions);
where
WidgetOptions {
logs?: {
render?: boolean; // Render logs (default: true)
eventBus?: boolean; // Event bus logs (default: true)
};
}
Local development
Use npm link
for making reference to package widget-render
from your package.
For example:
# cd widget-render/lib
# npm link
# cd YOUR_PACKAGE
# npm link abc-charts
Don't forget local link will be reset after any npm-operations in your package.
Добавление нового виджета
NOTE: Все названия директорий и компонентов в lowerCamelCase
NOTE: CodeStyle регламентируется файлом tslint.json
. Требуется настроить свою IDE для работы с правилами линтера.
- Создаем (копируем из существующего виджета) директорию видa
/src/widgets/<widgetName>/
⎣ index.ts
⎣ <widgetName>.ts
⎣ <widgetName>.less
⎣ settings.ts
Уникальное имя файла нужно для того, чтобы имена классов собирались изолировано.
Текущее правило для css-классов [name]-[local]. В противном случае классы виджетов пересекуться и использовать их на одной странице станет затруднительно.
- Добавляем в /src/widgets/index.ts экспорт.
export * from './<widgetName>';
В WidgetFactory
классы автоматически импортируются в скоуп widgets.
- Обрабатываем в WidgetFactory
private createWidget(...) {
...
const widgetsArr: WidgetsArr = {
...
"WIDGET_TYPE": () => widgets.YourWidget,
}
}
- Добавляем новый тип виджета в файл
src/models/types.ts
type WidgetType = ... | 'WIDGET_TYPE'
Deploy
- Меняем версию в
package-lib.json
- Корректно вносим изменения в
CHANGELOG.md
- Вызываем
npm run npm:publish
Структура виджета
<widgetName>.ts
Компонент виджета наследуется от класса Chart
, который, в свою очередь, реализует интерфейс IChart
, для управления виджетом из вызывающего проекта.
В компоненте необходимо реализовать все abstract методы, в частности метод run
,
Реализация метода run
не регламентирована ничем, кроме CodeStyle, и каждый виджет может быть написан с использованием уникальных для него подходов (нр, HTML-верстка, SVG, echarts и т.д.).
run()
Запуск виджета.
Принимает конфигуратор с DOM-элементом, куда записывает результат своей работы.
destroy()
Уничтожает виджет, в частности все обработчики, которые на него повешаны.
redraw()
Перерисовать виджет с текущими данными без пересоздания самого виджета.
getVariables(): IWidgetVariables
Возвращает переменные для общения по шине. NOTE: Реализовано через generic, чтобы гарантировать правильное использование. Не менять!
getVariables(): IWidgetVariables {
const res: IWidgetVariables = [];
const addVar = this.addVar(res);
addVar(<dataSourceIndex>, 'varName', 'varDesc', 'varHint');
return res;
}
Если по шине передаются данные привязанные к конкретным dimensions, надо ОБЯЗАТЕЛЬНО проверять, есть ли такие dimensions (нр, organizationUnit) и не добавлять внешнее событие через addVar, если dimensions нет.
onEventBus: (ev: EventBusEvent, eventObj: IEventData) => void
Обработчик сообщений от шины.
NOTE: Реализован через переменную, для сохранения контекста вызова
getSettings: IWidgetSettings
Возвращает настройки конкретного виджета для доступа в базовом классе. Сделано через метод, чтобы нельзя было забыть про возврат этих значений (нр, в противовес вызову super.run())
getTemplate(): string | null
Получить шаблон. Если не перегружена (null), то шаблонизатор не используется.
onResize: (width: number, height: number) => void
Обработчик изменения размера.
NOTE: Реализован через переменную, для сохранения контекста вызова
<widgetName>.less
Файл стилей, который будет собран в изолированное пространство css-классов, исходя из уникальности имени файла.
settings.ts
Файл с настройками, уникальными для конкретного виджета. См. ниже "Добавление нового типа настройки"
Шаблонизатор
В проекте используется шаблонизатор hogan.js синтаксис которого базируется на mustache.js
Пример использования
Если нужно рендерить вручную, то можно не переопределять метод getTemplate
.
class Widget extends Chart {
run() {
const output = this.renderTemplate({
var1: value1,
var2: value2
});
this.config.element.innerHTML = output;
}
getTemplate(): string {
return `Your template {{var1}} {{var2}}`;
}
}
Добавление нового типа настройки
Все необходимые файлы лежат в папке /widgetInfo
- добавляем новый тип setting к
WidgetSettingsTypes
. - в
/widgetInfo/settings
в файле<YourType>Setting.ts
описываем структуру данных и функцию созданияmake<YourType>(...): SettingFunc
- добавляем новый тип данных к типу
WidgetInfoSettingsItem
- Все! Можно использовать вашу функцию для создания новой настройки в конфиге
Пример создания настройки MyTypeSetting.ts
:
>>> 1. задаем тип дефолтного значения
type DefaultType = boolean;
export interface MyTypeSetting extends BaseSetting<DefaultType> {
}
>>> 2. дописываем интерфейс настройки MyTypeSetting к общему WidgetInfoSettingsItem в types.ts
export function makeBoolean(name: string, label: string, def: DefaultType): SettingFunc {
// Через функцию, чтобы гарантировать правильность структуры setting
return (): BooleanSetting => ({
name,
label,
type: 'boolean', >>> 3. Определяем новый тип настройки
default: def
});
}
Пример конфигурации widgets\<MyWidget>\settings.ts
:
Примечание: Конфиг задается через функции makeSettings и make, чтобы гарантировать правильность структуры данных.
Например, чтобы избежать попыток записать в конфиг несуществующие переменные foo и boo: settings: [ {name, value, foo, boo} ]
export const settings: IWidgetInfo = makeSettings({
settings: [
makeString('title', ''),
],
dataSet: {
initDataSets: [{viewType: 'DYNAMIC'}, {viewType: 'DYNAMIC'}],
canAdd: true,
settings: [
makeColor('color', null),
]
}
});
DataProvider
Для доступа к данным из GraphQL используется класс DataProvider
из библиотеки.
Класс можно заменить собственной реализацией, расширив интерфейс IDataProvider
и передав его через конфигурацию:
class MyDataProvider implements IDataProvider {
...
}
const config: WidgetConfig = WidgetConfig();
config.dataProvider = MyDataProvier()