UNPKG

@future-grid/fgp-graph

Version:

fgp-graph is a chart lib based on Dygraphs

318 lines (261 loc) 10.4 kB
import Dygraph from "dygraphs"; import {DomAttrs, ViewConfig, ViewOptions} from "./metadata/configurations"; import {ResizeObserver, ResizeObserverEntry} from '@juggle/resize-observer'; import {EventHandlers} from './metadata/graphoptions'; import {DomElementOperator, GraphOperator} from "./widgets/GraphRenderer"; export default class FgpGraph { graphContainer: HTMLElement; body: HTMLElement; public graph!: Dygraph; private rangeBarGraph!: Dygraph; viewConfigs: Array<ViewConfig>; private parentDom: HTMLElement; public syncLegend: boolean = false; intervalLabelsArea: HTMLElement; public children: Array<FgpGraph> = []; public currentDateWindow: number[] = []; id: string; public operator!: GraphOperator; eventListeners?: EventHandlers; private graphDateWindow: [number, number]; private readonly syncViews: boolean = false; // private isReady: boolean = false; /** *Creates an instance of FgpGraph. * @param {HTMLElement} dom * graph container * @param {Array<ViewConfig>} viewConfigs * graph configuration * @memberof FgpGraph */ constructor(dom: HTMLElement, viewConfigs: Array<ViewConfig>, eventHandlers?: EventHandlers, syncViews?: boolean) { this.parentDom = dom; this.graphDateWindow = [0, 0]; if (eventHandlers) { this.eventListeners = eventHandlers; } console.log(`need to sync views? ${syncViews}`); if (syncViews) { this.syncViews = true; } this.id = (Math.random() * 10000 | 0) + 1 + ''; // if id exist then change id to id if (this.parentDom.getAttribute('id')) { this.id = this.parentDom.id; } // if (this.parentDom.getAttribute("fgp-graph-id")) { this.id = <string>this.parentDom.getAttribute("fgp-graph-id"); } let intervalsLabelsAttrs: Array<DomAttrs> = [{key: 'class', value: "fgp-interval-labels"}]; this.intervalLabelsArea = DomElementOperator.createElement('div', intervalsLabelsAttrs); let buttonsAttrs: Array<DomAttrs> = [{key: 'class', value: "fgp-buttons"}]; const buttonsArea = DomElementOperator.createElement('div', buttonsAttrs); let filterAttrs: Array<DomAttrs> = [{key: 'class', value: "fgp-filter-buttons"}]; const filterArea = DomElementOperator.createElement('div', filterAttrs); let toolbarAreaAttrs: Array<DomAttrs> = [{key: 'class', value: "fgp-toolbar-area"}]; const toolbarArea = DomElementOperator.createElement('div', toolbarAreaAttrs); // create doms let containerAttrs: Array<DomAttrs> = [{key: 'class', value: 'fgp-graph-container noselect'}]; this.graphContainer = DomElementOperator.createElement('div', containerAttrs); let bodyAttrs: Array<DomAttrs> = [{key: 'class', value: 'fgp-graph-body'}]; this.body = DomElementOperator.createElement('div', bodyAttrs); this.graphContainer.appendChild(this.body); this.parentDom.appendChild(this.graphContainer); this.viewConfigs = viewConfigs; // listening for div resizing....... const divResizeRo = new ResizeObserver((roes: ResizeObserverEntry[], observer) => { roes.forEach((domObserverEntry) => { if (this.graph && domObserverEntry.target.className == 'fgp-graph-body') { console.log("resizing dom: ", domObserverEntry.target.className, 'if someone see a infinite loop here, please report it to author!'); if (isNaN(domObserverEntry.contentRect.width) || isNaN(domObserverEntry.contentRect.height)) { } else { // resize graph manually, because dygraph resizing base on window object. console.log(`new size is: ${domObserverEntry.contentRect.width} ${domObserverEntry.contentRect.height}`); let evt = window.document.createEvent('UIEvents'); evt.initEvent('resize', false, false); window.dispatchEvent(evt); } } else { console.log("resizing not support for: ", domObserverEntry.target.className); } }); }); divResizeRo.observe(this.body); } /** *update datewindow for children graphs * @param datewindow * @param currentView * @private * @memberof FgpGraph */ private dateWindowHandler = (dateWindow: [number, number], currentView?: ViewConfig) => { if (this.syncViews) { // store data this.graphDateWindow = dateWindow; } this.currentDateWindow = dateWindow; this.children.forEach(graph => { // call updateDatewinow if (graph.id != this.id) { graph.updateDatewinowInside(dateWindow); } }); }; /** * func for switching view * @param view */ public changeView = (view: string) => { // change view // find view this.viewConfigs.forEach(config => { config.show = false; if (config.name === view) { // update show attribute config.show = true; if (this.syncViews && this.graphDateWindow) { config.initRange = {start: this.graphDateWindow[0], end: this.graphDateWindow[1]}; } this.operator.init(config, (graph: Dygraph) => { this.graph = graph; this.children.forEach(graph => { // call updateDatewinow if (graph.id != this.id) { // update data graph.operator.refresh(); } }); }, () => { this.children.forEach(graph => { // call updateDatewinow if (graph.id != this.id) { // update data graph.operator.refresh(); } }); }); // check if we need to tell others the view changed. if (this.eventListeners && this.eventListeners.onViewChange) { //f call this.eventListeners.onViewChange(this, config); } } }); }; /** * init graph with configuration * * @private * @memberof FgpGraph */ public initGraph = (ready?: (g: FgpGraph) => void, needSync?: boolean) => { this.operator = new GraphOperator(this.graph, this.rangeBarGraph, this.graphContainer, this.body, this.dateWindowHandler, this, this.eventListeners, this.id, needSync); // which "view" should be shown first? device or scatter? if (this.viewConfigs) { let showView: ViewConfig | undefined; // check if showView is undefined if (!showView && this.viewConfigs.length > 0) { showView = this.viewConfigs[0]; } else if (!showView && this.viewConfigs.length === 0) { console.error("view config not found!"); return false; } if (showView) { this.operator.init(showView, (graph: Dygraph) => { this.graph = graph; if (ready) { ready(this); } // this.isReady = true; }, () => { this.children.forEach(graph => { // call updateDatewinow if (graph.id != this.id) { // update data graph.operator.refresh(); } }); }); } } }; /** *update currrent graph datewindow * @param datewindow * @memberof FgpGraph */ public updateDatewinow = (datewindow: [number, number]) => { // console.log(`new datewindow: ${datewindow}`); // update graph if (this.graph) { const range: Array<number> = this.graph.xAxisRange(); // if datewindow same then ignore that if (range[0] != datewindow[0] || range[1] != datewindow[1]) { // reload data for current graph this.operator.update(undefined, undefined, true, datewindow); // get all children graphs then run update debugger this.children.forEach(child => { child.updateDatewinowInside(datewindow, true); }); } } }; updateDatewinowInside = (datewindow: [number, number], forceReload?: boolean) => { // update graph if (this.graph) { // update current date-window this.graph.updateOptions({ dateWindow: datewindow }); if (forceReload) { this.operator.update(undefined, undefined, true, datewindow); } } }; /** *bind children graphs * @param graphs * children graphs * @memberof FgpGraph */ public setChildren = (graphs: Array<FgpGraph>) => { this.children = this.children.concat(graphs); }; /** * highlight line on graph * @param series * name of lines * @param duration * unhighlight after <duration> seconds 0 means highlight forever * * @memberof FgpGraph */ public highlightSeries = (series: string[], duration: number, type?: string) => { // this.operator.highlightSeries(series, duration, type); }; /** * reload data for graph. base on series not changed! */ public reloadData = () => { this.operator.update(undefined, undefined, true); }; /** * do it later * @param config */ public updateConfig = (config: ViewOptions) => { // return "not enabled in this version"; }; /** * clear graph */ public clear = () => { console.warn("under developing!") }; }