本文,我们来谈谈,Dygraph
结合 Angular
将多个图表进行同步~
安装依赖
假设读者通过 angular
脚手架新建了项目。那么,我们进入根目录,执行 npm install dygraphs
:
整合
下面,我们在 angular
项目中新开一个 demo
组件,进行整合。
绘制图形
// demo.component.ts
const Dygraph = require('dygraphs').default; // 引入依赖
export class DemoComponent implements OnInit {
@ViewChild('div1') div1Ref: any;
@ViewChild('div2') div2Ref: any;
ngOnInit(): void {
// 等待 DOM 加载完成,绘制图形
Dygraph.onDOMready(() => {
let data = [];
data.push([new Date('2008-05-07'), 75]);
data.push([new Date('2008-05-08'), 70]);
data.push([new Date('2008-05-09'), 100]);
data.push([new Date('2008-05-10'), 110]);
data.push([new Date('2008-05-11'), 90]);
// div01 元素的绘图
let div01Graph = new Dygraph(
this.div1Ref.nativeElement,
data,
{
labels: ['Date', 'Temperature'],
ylabel: 'Div 01'
}
);
// div02 元素绘制
let div02Graph = new Dygraph(
this.div1Ref.nativeElement,
data,
{
labels: ['Date', 'Temperature'],
ylabel: 'Div 02'
}
);
});
}
}
绘制的图形如下,当鼠标移动到 Div01
上时候,其右上角会展示值,而 Div02
图右上角并未展示值 – 此时我们并未做同步处理。
图形的
css
样式,读者可以自行调整,其html
很简单,如下:
<!-- demo.component.html -->
<div class="container">
<div class="graph-canvas div01" #div01></div>
<div class="graph-canvas div02" #div02></div>
</div>
联动图形
这里,我们就需要用到 synchronize
的功能。我们在 demo
组件的文件夹下添加文件 synchronize.ts
:
// synchronize.ts
/**
* @license
* Part of dygraphs, see top-level LICENSE.txt file
* MIT-licenced: https://opensource.org/licenses/MIT
*/
/**
* Synchronize zooming and/or selections between a set of dygraphs.
*
* Usage:
*
* var g1 = new Dygraph(...),
* g2 = new Dygraph(...),
* ...;
* var sync = Dygraph.synchronize(g1, g2, ...);
* // charts are now synchronized
* sync.detach();
* // charts are no longer synchronized
*
* You can set options using the last parameter, for example:
*
* var sync = Dygraph.synchronize(g1, g2, g3, {
* selection: true,
* zoom: true
* });
*
* The default is to synchronize both of these.
*
* Instead of passing one Dygraph object as each parameter, you may also pass an
* array of dygraphs:
* 使用方法,数组传递 Dygraph 实例,并可选其他的参数
* var sync = Dygraph.synchronize([g1, g2, g3], {
* selection: false,
* zoom: true
* });
*
* You may also set `range: false` if you wish to only sync the x-axis.
* The `range` option has no effect unless `zoom` is true (the default).
*/
/* loader wrapper to allow browser use and ES6 imports */
let Dygraph = require('dygraphs').default; // 引入
var synchronize = function synchronize(/* dygraphs..., opts */) {
if (arguments.length === 0) {
throw 'Invalid invocation of Dygraph.synchronize(). Need >= 1 argument.';
}
var OPTIONS = ['selection', 'zoom', 'range'];
var opts = {
selection: true,
zoom: true,
range: true
};
var dygraphs = [];
var prevCallbacks = [];
var parseOpts = function parseOpts(obj) {
if (!(obj instanceof Object)) {
throw 'Last argument must be either Dygraph or Object.';
} else {
for (var i = 0; i < OPTIONS.length; i++) {
var optName = OPTIONS[i];
if (obj.hasOwnProperty(optName)) opts[optName] = obj[optName];
}
}
};
if (arguments[0] instanceof Dygraph) {
// Arguments are Dygraph objects.
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof Dygraph) {
dygraphs.push(arguments[i]);
} else {
break;
}
}
if (i < arguments.length - 1) {
throw 'Invalid invocation of Dygraph.synchronize(). ' +
'All but the last argument must be Dygraph objects.';
} else if (i == arguments.length - 1) {
parseOpts(arguments[arguments.length - 1]);
}
} else if (arguments[0].length) {
// Invoked w/ list of dygraphs, options
for (var i = 0; i < arguments[0].length; i++) {
dygraphs.push(arguments[0][i]);
}
if (arguments.length == 2) {
parseOpts(arguments[1]);
} else if (arguments.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.';
}
var readycount = dygraphs.length;
for (var i = 0; i < dygraphs.length; i++) {
var g = dygraphs[i];
g.ready(function onReady_() {
if (--readycount == 0) {
// store original callbacks
var callBackTypes = ['drawCallback', 'highlightCallback', 'unhighlightCallback'];
for (var j = 0; j < dygraphs.length; j++) {
if (!prevCallbacks[j]) {
prevCallbacks[j] = {};
}
for (var 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 detach() {
for (var i = 0; i < dygraphs.length; i++) {
var 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 = null;
opts = null;
prevCallbacks = null;
}
};
};
function arraysAreEqual(a, b) {
if (!Array.isArray(a) || !Array.isArray(b)) return false;
var i = a.length;
if (i !== b.length) return false;
while (i--) {
if (a[i] !== b[i]) return false;
}
return true;
}
function attachZoomHandlers(gs, syncOpts, prevCallbacks) {
var block = false;
for (var i = 0; i < gs.length; i++) {
var g = gs[i];
g.updateOptions({
drawCallback: function synchronizer_drawCallback(me, initial) {
if (block || initial) {
// call the user’s drawCallback even if we are blocked
for (let j = 0; j < gs.length; j++) {
if (gs[j] == me) {
if (prevCallbacks[j] && prevCallbacks[j].drawCallback) {
prevCallbacks[j].drawCallback.apply(this, arguments);
}
break;
}
}
return;
}
block = true;
var opts: any = {
dateWindow: me.xAxisRange()
};
if (!me.isZoomed('x'))
opts.dateWindow = null;
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
if (arraysAreEqual(opts.dateWindow, gs[j].getOption('dateWindow')) &&
(!syncOpts.range ||
arraysAreEqual(opts.valueRange, gs[j].getOption('valueRange')))) {
continue;
}
gs[j].updateOptions(opts);
}
block = false;
}
}, true /* no need to redraw */);
}
}
function attachSelectionHandlers(gs, prevCallbacks) {
var block = false;
for (var i = 0; i < gs.length; i++) {
var g = gs[i];
g.updateOptions({
highlightCallback: function synchronizer_highlightCallback(event, x, points, row, seriesName) {
if (block) return;
block = true;
var me = this;
for (var i = 0; i < gs.length; i++) {
if (me == gs[i]) {
if (prevCallbacks[i] && prevCallbacks[i].highlightCallback) {
prevCallbacks[i].highlightCallback.apply(this, arguments);
}
continue;
}
var idx = gs[i].getRowForX(x);
if (idx !== null) {
gs[i].setSelection(idx, seriesName, undefined, true);
}
}
block = false;
},
unhighlightCallback: function synchronizer_unhighlightCallback(event) {
if (block) return;
block = true;
var me = this;
for (var 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 */);
}
}
Dygraph.synchronize = synchronize; // 挂在 Dygraph 类上
export default Dygraph; // 暴露 Dygraph 类
然后,我们更改下 demo.component.ts
文件内容,如下:
// demo.component.ts
- const Dygraph = require('dygraphs').default; // 引入依赖
+ import Dygraph from './synchronize'; // 引入依赖
export class DemoComponent implements OnInit {
ngOnInit(): void {
Dygraph.onDOMready(() => {
// 之前的代码保留
// 后面是追加
let gs = [];
gs.push(
div01Graph,
div02Graph
);
// 同步
Dygraph.synchronize(gs);
});
}
}
当你在 Div 01
或者 Div 02
上移动的时候,两个图形的右上角都会有该 X
轴值及其对应的 Y
轴的值。如下图:
题外话:隐藏 Grid
Dygraph
提供了丰富的参数来满足你的需求,比如:隐藏 Grid
。我们以 Div 01
为例:
let div01Graph = new Dygraph(
this.div1Ref.nativeElement,
data,
{
labels: ['Date', 'Temperature'],
ylabel: 'Div 01',
axes: {
x: {
drawAxis: false, // 这里是不绘制 x 轴的坐标值
drawGrid: false
}
}
}
);
如下:
还有更多的操作,等待你我解锁?~
参考
推荐阅读
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END