@future-grid/fgp-graph
Version:
fgp-graph is a chart lib based on Dygraphs
318 lines (261 loc) • 10.4 kB
text/typescript
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!")
};
}