event/cl.event.js

/**
 * Copyright (c) 2018-present clchart Contributors.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */

// 定义事件监听接口

// 鼠标事件
//  onMouseMove // 移动
//  onMouseIn  // 进入
//  monMouseOut // 离开
//  onMousewheel // 滚动
//  onMouseUp  //
//  onMouseDown //
// 键盘事件
//  onKeyUp
//  onKeyDown
// 触摸事件
//  onTouchStart // 触摸开始
//  onTouchEnd   // 触摸结束
//  onTouchMove  // 触摸移动
// 其他事件
//  onClick // 点击
//  onLongPress // 长按
//  onPinch // 两指放大或缩小
//  onRotate // 旋转
//  onSwipe //左滑或右滑
import {
  copyJsonOfDeep,
  inRect
} from '../util/cl.tool'
import ClEventHandler from './cl.event.handler'

export const EVENT_DEFINE = [
  'onMouseMove',
  // 'onMouseIn',
  'onMouseOut',
  'onMousewheel',
  'onMouseUp',
  'onMouseDown',
  'onKeyUp',
  'onKeyDown',
  'onClick',
  'onDoubleClick',
  'onLongPress',
  'onPinch',
  'onRotate',
  'onSwipe'
]

export function buildMinaTouchEvent (e) {
  const eventObj = {}
  if (e && Array.isArray(e.touches)) {
    eventObj.touches = []
    for (let i = 0; i < e.touches.length; i++) {
      const point = e.touches[i]
      eventObj.touches.push({
        pageX: point.x,
        pageY: point.y,
        offsetX: point.x,
        offsetY: point.y
      })
    }
  }
  if (e && Array.isArray(e.changedTouches)) {
    eventObj.changedTouches = []
    for (let i = 0; i < e.changedTouches.length; i++) {
      const point = e.changedTouches[i]
      eventObj.changedTouches.push({
        pageX: point.x,
        pageY: point.y,
        offsetX: point.x,
        offsetY: point.y
      })
    }
  }
  return eventObj
}

/**
 * Class representing ClEvent
 * @export
 * @class ClEvent
 */
export default class ClEvent {
  /**
   * Creates an instance of ClEvent.
   * @param {Object} syscfg
   * @constructor
   */
  constructor (syscfg) {
    // this.eventCanvas = syscfg.mainCanvas.canvas // 对web来说就是虚拟接收事件的canvas
    this.eventCanvas = syscfg.cursorCanvas.canvas
    this.eventPlatform = syscfg.eventPlatform || 'web'
    this.scale = syscfg.scale

    const eventCfg = {
      father: this
    }
    if (this.eventPlatform === 'react-native') {
      eventCfg.isTouch = true
    } else if (this.eventPlatform === 'web') {
      eventCfg.isTouch = 'ontouchend' in document
    } else if (this.eventPlatform === 'mina') {
      eventCfg.isTouch = true
      eventCfg.eventBuild = buildMinaTouchEvent
    }
    this.eventSource = new ClEventHandler(eventCfg)
    this.eventSource.bindEvent()
  }
  // 只需要绑定一个原始ClChart就可以了,子图的事件通过childCharts来判断获取
  // 每个chart如果自己定义了对应事件就会分发,未定义不分发,分发后以返回值判断是否继续传递到下一个符合条件的chart
  /**
   * bind event
   * @param {Object} source
   * @memberof ClEvent
   */
  bindChart (source) {
    this.firstChart = source
    this.HotWin = undefined
  }
  /**
   * clear event
   * @memberof ClEvent
   */
  clearEvent () {
    this.eventSource.clearEvent()
  }
  // 下面是接收事件后,根据热点位置来判断归属于哪一个chart,并分发事件;
  // config 必须包含鼠标位置 {offsetX:offsetY:}
  /**
   * emit event
   * @param {String} eventName
   * @param {Object} config
   * @memberof ClEvent
   */
  emitEvent (eventName, config) {
    // .....这里需要特殊分解处理Out的事件
    if (eventName === 'onMouseOut' || eventName === 'onMouseMove') {
      this.boardEvent(this.firstChart, eventName, config)
      this.HotWin = undefined
      return
    }
    const mousePos = this.getMousePos(config)
    const chartPath = []
    for (const name in this.firstChart.childCharts) {
      // 判断鼠标在哪个区域
      if (inRect(this.firstChart.childCharts[name].rectMain, mousePos)) {
        // 取得事件冒泡顺序
        this.findEventPath(chartPath, this.firstChart.childCharts[name], mousePos)
        // 只处理符合条件的一个区域,重叠区域不考虑
        break
      }
    }
    if (chartPath.length < 1) return
    // 继承最初始的传入参数,从最顶层开始处理
    const event = copyJsonOfDeep(config)
    for (let k = chartPath.length - 1; k >= 0; k--) {
      if (chartPath[k][eventName] !== undefined) {
        // 这里生成一个相对鼠标位置
        event.mousePos = {
          // x: mousePos.x - chartPath[k].rectMain.left,
          // y: mousePos.y - chartPath[k].rectMain.top
          x: mousePos.x,
          y: mousePos.y
        }
        // 执行事件函数
        chartPath[k][eventName](event)
        if (event.break) break // 跳出循环
      }
    }
  }
  // 用于鼠标联动时,向childCharts同一级别画图区域广播事件
  //
  /**
   * board event
   * @param {Object} chart
   * @param {String} eventName
   * @param {Object} config
   * @param {Object} ignore
   * @memberof ClEvent
   */
  boardEvent (chart, eventName, config, ignore) {
    const event = copyJsonOfDeep(config)
    const mousePos = this.getMousePos(config)

    for (const name in chart.childCharts) {
      if (chart.childCharts[name] === ignore) continue
      if (chart.childCharts[name][eventName] === undefined) continue

      event.mousePos = {
        // x: mousePos.x - chart.childCharts[name].rectMain.left,
        // y: mousePos.y - chart.childCharts[name].rectMain.top
        x: mousePos.x,
        y: mousePos.y
      }
      chart.childCharts[name][eventName](event)
      if (event.break) break
    }
  }
  /**
   * find event path
   * @param {Array} path
   * @param {Object} chart
   * @param {Object} mousePos
   * @memberof ClEvent
   */
  findEventPath (path, chart, mousePos) {
    path.push(chart)
    if (chart.childCharts === undefined) return

    for (const name in chart.childCharts) {
      if (inRect(chart.childCharts[name].rectMain, mousePos)) {
        this.findEventPath(path, chart.childCharts[name], mousePos)
      }
    }
  }
  /**
   * get mouse position info
   * @param {Object} event
   * @return {Object} mouse position info
   * @memberof ClEvent
   */
  getMousePos (event) {
    const mouseX = event.offsetX * this.scale
    const mouseY = event.offsetY * this.scale
    return {
      x: mouseX,
      y: mouseY
    }
  }
}