@future-grid/fgp-graph
Version:
fgp-graph is a chart lib based on Dygraphs
219 lines (199 loc) • 8.68 kB
text/typescript
import Dygraph from 'dygraphs';
export class Synchronizer {
args: Array<any>;
graphs: Array<Dygraph>;
constructor(graphs: any[]) {
this.graphs = graphs;
this.args = graphs.concat([{
zoom: true,
selection: false
}]);
}
synchronize = () => {
if (this.args.length === 0) {
throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
}
let OPTIONS = ['selection', 'zoom', 'range'];
let opts: any = {
selection: true,
zoom: true,
range: false
};
let dygraphs: any[] = [];
let prevCallbacks: any[] = [];
let parseOpts = function (obj: any) {
if (!(obj instanceof Object)) {
throw 'Last argument must be either Dygraph or Object.';
} else {
for (let i = 0; i < OPTIONS.length; i++) {
let optName = OPTIONS[i];
if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
}
}
};
let arraysAreEqual = (a: any, b: any) => {
if (!Array.isArray(a) || !Array.isArray(b)) return false;
let i = a.length;
if (i !== b.length) return false;
while (i--) {
if (a[i] !== b[i]) return false;
}
return true;
};
let attachZoomHandlers = (gs: Dygraph[], syncOpts: any, prevCallbacks: any) => {
let block = false;
for (let i = 0; i < gs.length; i++) {
let g = gs[i];
g.updateOptions({
drawCallback: function (me: Dygraph, initial: boolean) {
if (block || initial) return;
block = true;
let opts: { dateWindow: any, valueRange?: any } = {
dateWindow: me.xAxisRange()
};
if (syncOpts.range) {
opts.valueRange = me.yAxisRange();
}
for (let j = 0; j < gs.length; j++) {
if (gs[j] == me) {
if (prevCallbacks[j] && prevCallbacks[j].drawCallback) {
prevCallbacks[j].drawCallback.apply(this, arguments);
}
continue;
}
// Only redraw if there are new options (in this case only dateWindow changed!)
//&& arraysAreEqual(opts.valueRange, gs[j].getOption('valueRange'))
if (arraysAreEqual(opts.dateWindow, gs[j].getOption('dateWindow'))) {
continue;
}
gs[j].updateOptions(opts);
}
block = false;
}
}, true /* no need to redraw */);
}
}
let attachSelectionHandlers = (gs: any[], prevCallbacks: any) => {
let block = false;
for (let i = 0; i < gs.length; i++) {
let g = gs[i];
g.updateOptions({
highlightCallback: function (event: Event, x: any, points: any[], row: any, seriesName: any) {
if (block) return;
block = true;
let me: any = this;
for (let i = 0; i < gs.length; i++) {
if (me == gs[i]) {
if (prevCallbacks[i] && prevCallbacks[i].highlightCallback) {
prevCallbacks[i].highlightCallback.apply(this, arguments);
}
continue;
}
let idx = gs[i].getRowForX(x);
if (idx !== null) {
gs[i].setSelection(idx, seriesName);
}
}
block = false;
},
unhighlightCallback: function (e: Event) {
if (block) return;
block = true;
let me = this;
for (let i = 0; i < gs.length; i++) {
if (me == gs[i]) {
if (prevCallbacks[i] && prevCallbacks[i].unhighlightCallback) {
prevCallbacks[i].unhighlightCallback.apply(this, arguments);
}
continue;
}
gs[i].clearSelection();
}
block = false;
}
}, true /* no need to redraw */);
}
};
if (this.args[0] instanceof Dygraph) {
// Arguments are Dygraph objects.
let i;
for (i = 0; i < this.args.length; i++) {
if (this.args[i] instanceof Dygraph) {
dygraphs.push(this.args[i]);
} else {
break;
}
}
if (i < this.args.length - 1) {
throw 'Invalid invocation of Dygraph.synchronize(). ' +
'All but the last argument must be Dygraph objects.';
} else if (i == this.args.length - 1) {
parseOpts(this.args[this.args.length - 1]);
}
} else if (this.args[0].length) {
// Invoked w/ list of dygraphs, options
for (let i = 0; i < this.args[0].length; i++) {
dygraphs.push(this.args[0][i]);
}
if (this.args.length == 2) {
parseOpts(this.args[1]);
} else if (this.args.length > 2) {
throw 'Invalid invocation of Dygraph.synchronize(). ' +
'Expected two arguments: array and optional options argument.';
} // otherwise arguments.length == 1, which is fine.
} else {
throw 'Invalid invocation of Dygraph.synchronize(). ' +
'First parameter must be either Dygraph or list of Dygraphs.';
}
if (dygraphs.length < 2) {
throw 'Invalid invocation of Dygraph.synchronize(). ' +
'Need two or more dygraphs to synchronize.';
}
let readycount = dygraphs.length;
for (let i = 0; i < dygraphs.length; i++) {
let g = dygraphs[i];
g.ready(function () {
if (--readycount == 0) {
// store original callbacks
let callBackTypes = ['drawCallback', 'highlightCallback', 'unhighlightCallback'];
for (let j = 0; j < dygraphs.length; j++) {
if (!prevCallbacks[j]) {
prevCallbacks[j] = {};
}
for (let k = callBackTypes.length - 1; k >= 0; k--) {
prevCallbacks[j][callBackTypes[k]] = dygraphs[j].getFunctionOption(callBackTypes[k]);
}
}
// Listen for draw, highlight, unhighlight callbacks.
if (opts.zoom) {
attachZoomHandlers(dygraphs, opts, prevCallbacks);
}
if (opts.selection) {
attachSelectionHandlers(dygraphs, prevCallbacks);
}
}
});
}
return {
detach: function () {
for (let i = 0; i < dygraphs.length; i++) {
let g = dygraphs[i];
if (opts.zoom) {
g.updateOptions({ drawCallback: prevCallbacks[i].drawCallback });
}
if (opts.selection) {
g.updateOptions({
highlightCallback: prevCallbacks[i].highlightCallback,
unhighlightCallback: prevCallbacks[i].unhighlightCallback
});
}
}
// release references & make subsequent calls throw.
dygraphs = [];
opts = null;
prevCallbacks = [];
},
graphs: [this.graphs]
};
};
}