前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Dygraph 结合 Angular 实现多图表同步

Dygraph 结合 Angular 实现多图表同步

作者头像
Jimmy_is_jimmy
发布2023-08-11 16:28:10
1860
发布2023-08-11 16:28:10
举报
文章被收录于专栏:call_me_Rcall_me_R

本文,我们来谈谈,Dygraph 结合 Angular 将多个图表进行同步~

安装依赖

假设读者通过 angular 脚手架新建了项目。那么,我们进入根目录,执行 npm install dygraphs

整合

下面,我们在 angular 项目中新开一个 demo 组件,进行整合。

绘制图形
代码语言:javascript
复制
// demo.component.ts
import { Component, OnInit, ViewChild } from '@angular/core';
const Dygraph = require('dygraphs').default; // 引入依赖

@Component({
  selector: 'demo',
  templateUrl: './demo.component.html',
  styleUrls: ['./demo.component.css']
})
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.div2Ref.nativeElement,
        data,
        {
          labels: ['Date', 'Temperature'],
          ylabel: 'Div 02'
        }
      );
    });
  }
}

绘制的图形如下,当鼠标移动到 Div01 上时候,其右上角会展示值,而 Div02 图右上角并未展示值 - 此时我们并未做同步处理。

图形的 css 样式,读者可以自行调整,其 html 很简单,如下:

代码语言:javascript
复制
<!--  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

代码语言:javascript
复制
// 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 &amp; 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] &amp;&amp; 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] &amp;&amp; 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')) &amp;&amp;
              (!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] &amp;&amp; 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] &amp;&amp; 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 文件内容,如下:

代码语言:javascript
复制
// 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 为例:

代码语言:javascript
复制
let div01Graph = new Dygraph(
  this.div1Ref.nativeElement,
  data,
  {
    labels: ['Date', 'Temperature'],
    ylabel: 'Div 01',
    axes: {
      x: {
        drawAxis: false, // 这里是不绘制 x 轴的坐标值
        drawGrid: false
      }
    }
  }
);

如下:

还有更多的操作,等待你我解锁🔓~

参考

推荐阅读

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 安装依赖
  • 整合
    • 绘制图形
      • 联动图形
      • 题外话:隐藏 Grid
      • 参考
      • 推荐阅读
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档