HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //home/arjun/projects/buyercall/node_modules/apexcharts/src/modules/Toolbar.js
import Graphics from './Graphics'
import Exports from './Exports'
import Scales from './Scales'
import Utils from './../utils/Utils'
import icoPan from './../assets/ico-pan-hand.svg'
import icoZoom from './../assets/ico-zoom-in.svg'
import icoReset from './../assets/ico-home.svg'
import icoZoomIn from './../assets/ico-plus.svg'
import icoZoomOut from './../assets/ico-minus.svg'
import icoSelect from './../assets/ico-select.svg'
import icoMenu from './../assets/ico-menu.svg'

/**
 * ApexCharts Toolbar Class for creating toolbar in axis based charts.
 *
 * @module Toolbar
 **/

export default class Toolbar {
  constructor(ctx) {
    this.ctx = ctx
    this.w = ctx.w
    const w = this.w

    this.ev = this.w.config.chart.events
    this.selectedClass = 'apexcharts-selected'

    this.localeValues = this.w.globals.locale.toolbar

    this.minX = w.globals.minX
    this.maxX = w.globals.maxX
  }

  createToolbar() {
    let w = this.w

    const createDiv = () => {
      return document.createElement('div')
    }
    const elToolbarWrap = createDiv()
    elToolbarWrap.setAttribute('class', 'apexcharts-toolbar')
    elToolbarWrap.style.top = w.config.chart.toolbar.offsetY + 'px'
    elToolbarWrap.style.right = -w.config.chart.toolbar.offsetX + 3 + 'px'
    w.globals.dom.elWrap.appendChild(elToolbarWrap)

    this.elZoom = createDiv()
    this.elZoomIn = createDiv()
    this.elZoomOut = createDiv()
    this.elPan = createDiv()
    this.elSelection = createDiv()
    this.elZoomReset = createDiv()
    this.elMenuIcon = createDiv()
    this.elMenu = createDiv()
    this.elCustomIcons = []

    this.t = w.config.chart.toolbar.tools

    if (Array.isArray(this.t.customIcons)) {
      for (let i = 0; i < this.t.customIcons.length; i++) {
        this.elCustomIcons.push(createDiv())
      }
    }

    let toolbarControls = []

    const appendZoomControl = (type, el, ico) => {
      const tool = type.toLowerCase()
      if (this.t[tool] && w.config.chart.zoom.enabled) {
        toolbarControls.push({
          el,
          icon: typeof this.t[tool] === 'string' ? this.t[tool] : ico,
          title: this.localeValues[type],
          class: `apexcharts-${tool}-icon`,
        })
      }
    }

    appendZoomControl('zoomIn', this.elZoomIn, icoZoomIn)
    appendZoomControl('zoomOut', this.elZoomOut, icoZoomOut)

    const zoomSelectionCtrls = (z) => {
      if (this.t[z] && w.config.chart[z].enabled) {
        toolbarControls.push({
          el: z === 'zoom' ? this.elZoom : this.elSelection,
          icon:
            typeof this.t[z] === 'string'
              ? this.t[z]
              : z === 'zoom'
              ? icoZoom
              : icoSelect,
          title:
            this.localeValues[z === 'zoom' ? 'selectionZoom' : 'selection'],
          class: w.globals.isTouchDevice
            ? 'apexcharts-element-hidden'
            : `apexcharts-${z}-icon`,
        })
      }
    }
    zoomSelectionCtrls('zoom')
    zoomSelectionCtrls('selection')

    if (this.t.pan && w.config.chart.zoom.enabled) {
      toolbarControls.push({
        el: this.elPan,
        icon: typeof this.t.pan === 'string' ? this.t.pan : icoPan,
        title: this.localeValues.pan,
        class: w.globals.isTouchDevice
          ? 'apexcharts-element-hidden'
          : 'apexcharts-pan-icon',
      })
    }

    appendZoomControl('reset', this.elZoomReset, icoReset)

    if (this.t.download) {
      toolbarControls.push({
        el: this.elMenuIcon,
        icon: typeof this.t.download === 'string' ? this.t.download : icoMenu,
        title: this.localeValues.menu,
        class: 'apexcharts-menu-icon',
      })
    }

    for (let i = 0; i < this.elCustomIcons.length; i++) {
      toolbarControls.push({
        el: this.elCustomIcons[i],
        icon: this.t.customIcons[i].icon,
        title: this.t.customIcons[i].title,
        index: this.t.customIcons[i].index,
        class: 'apexcharts-toolbar-custom-icon ' + this.t.customIcons[i].class,
      })
    }

    toolbarControls.forEach((t, index) => {
      if (t.index) {
        Utils.moveIndexInArray(toolbarControls, index, t.index)
      }
    })

    for (let i = 0; i < toolbarControls.length; i++) {
      Graphics.setAttrs(toolbarControls[i].el, {
        class: toolbarControls[i].class,
        title: toolbarControls[i].title,
      })

      toolbarControls[i].el.innerHTML = toolbarControls[i].icon
      elToolbarWrap.appendChild(toolbarControls[i].el)
    }

    this._createHamburgerMenu(elToolbarWrap)

    if (w.globals.zoomEnabled) {
      this.elZoom.classList.add(this.selectedClass)
    } else if (w.globals.panEnabled) {
      this.elPan.classList.add(this.selectedClass)
    } else if (w.globals.selectionEnabled) {
      this.elSelection.classList.add(this.selectedClass)
    }

    this.addToolbarEventListeners()
  }

  _createHamburgerMenu(parent) {
    this.elMenuItems = []
    parent.appendChild(this.elMenu)

    Graphics.setAttrs(this.elMenu, {
      class: 'apexcharts-menu',
    })

    const menuItems = [
      {
        name: 'exportSVG',
        title: this.localeValues.exportToSVG,
      },
      {
        name: 'exportPNG',
        title: this.localeValues.exportToPNG,
      },
      {
        name: 'exportCSV',
        title: this.localeValues.exportToCSV,
      },
    ]

    for (let i = 0; i < menuItems.length; i++) {
      this.elMenuItems.push(document.createElement('div'))
      this.elMenuItems[i].innerHTML = menuItems[i].title
      Graphics.setAttrs(this.elMenuItems[i], {
        class: `apexcharts-menu-item ${menuItems[i].name}`,
        title: menuItems[i].title,
      })
      this.elMenu.appendChild(this.elMenuItems[i])
    }
  }

  addToolbarEventListeners() {
    this.elZoomReset.addEventListener('click', this.handleZoomReset.bind(this))
    this.elSelection.addEventListener(
      'click',
      this.toggleZoomSelection.bind(this, 'selection')
    )
    this.elZoom.addEventListener(
      'click',
      this.toggleZoomSelection.bind(this, 'zoom')
    )
    this.elZoomIn.addEventListener('click', this.handleZoomIn.bind(this))
    this.elZoomOut.addEventListener('click', this.handleZoomOut.bind(this))
    this.elPan.addEventListener('click', this.togglePanning.bind(this))
    this.elMenuIcon.addEventListener('click', this.toggleMenu.bind(this))
    this.elMenuItems.forEach((m) => {
      if (m.classList.contains('exportSVG')) {
        m.addEventListener('click', this.handleDownload.bind(this, 'svg'))
      } else if (m.classList.contains('exportPNG')) {
        m.addEventListener('click', this.handleDownload.bind(this, 'png'))
      } else if (m.classList.contains('exportCSV')) {
        m.addEventListener('click', this.handleDownload.bind(this, 'csv'))
      }
    })
    for (let i = 0; i < this.t.customIcons.length; i++) {
      this.elCustomIcons[i].addEventListener(
        'click',
        this.t.customIcons[i].click.bind(this, this.ctx, this.ctx.w)
      )
    }
  }

  toggleZoomSelection(type) {
    const charts = this.ctx.getSyncedCharts()

    charts.forEach((ch) => {
      ch.ctx.toolbar.toggleOtherControls()

      let el =
        type === 'selection'
          ? ch.ctx.toolbar.elSelection
          : ch.ctx.toolbar.elZoom
      let enabledType =
        type === 'selection' ? 'selectionEnabled' : 'zoomEnabled'

      ch.w.globals[enabledType] = !ch.w.globals[enabledType]

      if (!el.classList.contains(ch.ctx.toolbar.selectedClass)) {
        el.classList.add(ch.ctx.toolbar.selectedClass)
      } else {
        el.classList.remove(ch.ctx.toolbar.selectedClass)
      }
    })
  }

  getToolbarIconsReference() {
    const w = this.w
    if (!this.elZoom) {
      this.elZoom = w.globals.dom.baseEl.querySelector('.apexcharts-zoom-icon')
    }
    if (!this.elPan) {
      this.elPan = w.globals.dom.baseEl.querySelector('.apexcharts-pan-icon')
    }
    if (!this.elSelection) {
      this.elSelection = w.globals.dom.baseEl.querySelector(
        '.apexcharts-selection-icon'
      )
    }
  }

  enableZoomPanFromToolbar(type) {
    this.toggleOtherControls()

    type === 'pan'
      ? (this.w.globals.panEnabled = true)
      : (this.w.globals.zoomEnabled = true)

    const el = type === 'pan' ? this.elPan : this.elZoom
    const el2 = type === 'pan' ? this.elZoom : this.elPan
    if (el) {
      el.classList.add(this.selectedClass)
    }
    if (el2) {
      el2.classList.remove(this.selectedClass)
    }
  }

  togglePanning() {
    const charts = this.ctx.getSyncedCharts()

    charts.forEach((ch) => {
      ch.ctx.toolbar.toggleOtherControls()
      ch.w.globals.panEnabled = !ch.w.globals.panEnabled

      if (
        !ch.ctx.toolbar.elPan.classList.contains(ch.ctx.toolbar.selectedClass)
      ) {
        ch.ctx.toolbar.elPan.classList.add(ch.ctx.toolbar.selectedClass)
      } else {
        ch.ctx.toolbar.elPan.classList.remove(ch.ctx.toolbar.selectedClass)
      }
    })
  }

  toggleOtherControls() {
    const w = this.w
    w.globals.panEnabled = false
    w.globals.zoomEnabled = false
    w.globals.selectionEnabled = false

    this.getToolbarIconsReference()

    const toggleEls = [this.elPan, this.elSelection, this.elZoom]
    toggleEls.forEach((el) => {
      if (el) {
        el.classList.remove(this.selectedClass)
      }
    })
  }

  handleZoomIn() {
    const w = this.w

    if (w.globals.isRangeBar) {
      this.minX = w.globals.minY
      this.maxX = w.globals.maxY
    }

    const centerX = (this.minX + this.maxX) / 2
    let newMinX = (this.minX + centerX) / 2
    let newMaxX = (this.maxX + centerX) / 2

    const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX)

    if (!w.globals.disableZoomIn) {
      this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX)
    }
  }

  handleZoomOut() {
    const w = this.w

    if (w.globals.isRangeBar) {
      this.minX = w.globals.minY
      this.maxX = w.globals.maxY
    }

    // avoid zooming out beyond 1000 which may result in NaN values being printed on x-axis
    if (
      w.config.xaxis.type === 'datetime' &&
      new Date(this.minX).getUTCFullYear() < 1000
    ) {
      return
    }

    const centerX = (this.minX + this.maxX) / 2
    let newMinX = this.minX - (centerX - this.minX)
    let newMaxX = this.maxX - (centerX - this.maxX)

    const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX)

    if (!w.globals.disableZoomOut) {
      this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX)
    }
  }

  _getNewMinXMaxX(newMinX, newMaxX) {
    const shouldFloor = this.w.config.xaxis.convertedCatToNumeric
    return {
      minX: shouldFloor ? Math.floor(newMinX) : newMinX,
      maxX: shouldFloor ? Math.floor(newMaxX) : newMaxX,
    }
  }

  zoomUpdateOptions(newMinX, newMaxX) {
    const w = this.w

    if (newMinX === undefined && newMaxX === undefined) {
      this.handleZoomReset()
      return
    }

    if (w.config.xaxis.convertedCatToNumeric) {
      // in category charts, avoid zooming out beyond min and max
      if (newMinX < 1) {
        newMinX = 1
        newMaxX = w.globals.dataPoints
      }

      if (newMaxX - newMinX < 2) {
        return
      }
    }

    let xaxis = {
      min: newMinX,
      max: newMaxX,
    }

    const beforeZoomRange = this.getBeforeZoomRange(xaxis)
    if (beforeZoomRange) {
      xaxis = beforeZoomRange.xaxis
    }

    let options = {
      xaxis,
    }

    let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
    if (w.config.chart.zoom.autoScaleYaxis) {
      const scale = new Scales(this.ctx)
      yaxis = scale.autoScaleY(this.ctx, yaxis, {
        xaxis,
      })
    }

    if (!w.config.chart.group) {
      // if chart in a group, prevent yaxis update here
      // fix issue #650
      options.yaxis = yaxis
    }

    this.w.globals.zoomed = true

    this.ctx.updateHelpers._updateOptions(
      options,
      false,
      this.w.config.chart.animations.dynamicAnimation.enabled
    )

    this.zoomCallback(xaxis, yaxis)
  }

  zoomCallback(xaxis, yaxis) {
    if (typeof this.ev.zoomed === 'function') {
      this.ev.zoomed(this.ctx, { xaxis, yaxis })
    }
  }

  getBeforeZoomRange(xaxis, yaxis) {
    let newRange = null
    if (typeof this.ev.beforeZoom === 'function') {
      newRange = this.ev.beforeZoom(this, { xaxis, yaxis })
    }

    return newRange
  }

  toggleMenu() {
    window.setTimeout(() => {
      if (this.elMenu.classList.contains('apexcharts-menu-open')) {
        this.elMenu.classList.remove('apexcharts-menu-open')
      } else {
        this.elMenu.classList.add('apexcharts-menu-open')
      }
    }, 0)
  }

  handleDownload(type) {
    const w = this.w
    const exprt = new Exports(this.ctx)
    switch (type) {
      case 'svg':
        exprt.exportToSVG(this.ctx)
        break
      case 'png':
        exprt.exportToPng(this.ctx)
        break
      case 'csv':
        exprt.exportToCSV({
          series: w.config.series,
          columnDelimiter: w.config.chart.toolbar.export.csv.columnDelimiter,
        })
        break
    }
  }

  handleZoomReset(e) {
    const charts = this.ctx.getSyncedCharts()

    charts.forEach((ch) => {
      let w = ch.w

      // forget lastXAxis min/max as reset button isn't resetting the x-axis completely if zoomX is called before
      w.globals.lastXAxis.min = w.globals.initialConfig.xaxis.min
      w.globals.lastXAxis.max = w.globals.initialConfig.xaxis.max

      ch.updateHelpers.revertDefaultAxisMinMax()

      if (typeof w.config.chart.events.beforeResetZoom === 'function') {
        // here, user get an option to control xaxis and yaxis when resetZoom is called
        // at this point, whatever is returned from w.config.chart.events.beforeResetZoom
        // is set as the new xaxis/yaxis min/max
        const resetZoomRange = w.config.chart.events.beforeResetZoom(ch, w)

        if (resetZoomRange) {
          ch.updateHelpers.revertDefaultAxisMinMax(resetZoomRange)
        }
      }

      if (typeof w.config.chart.events.zoomed === 'function') {
        ch.ctx.toolbar.zoomCallback({
          min: w.config.xaxis.min,
          max: w.config.xaxis.max,
        })
      }

      w.globals.zoomed = false

      // if user has some series collapsed before hitting zoom reset button,
      // those series should stay collapsed
      let series = ch.ctx.series.emptyCollapsedSeries(
        Utils.clone(w.globals.initialSeries)
      )

      ch.updateHelpers._updateSeries(
        series,
        w.config.chart.animations.dynamicAnimation.enabled
      )
    })
  }

  destroy() {
    this.elZoom = null
    this.elZoomIn = null
    this.elZoomOut = null
    this.elPan = null
    this.elSelection = null
    this.elZoomReset = null
    this.elMenuIcon = null
  }
}