952 lines
27 KiB
JavaScript
952 lines
27 KiB
JavaScript
import Graphics from './Graphics'
|
|
import Utils from './../utils/Utils'
|
|
import Toolbar from './Toolbar'
|
|
import { Box } from '@svgdotjs/svg.js'
|
|
|
|
/**
|
|
* ApexCharts Zoom Class for handling zooming and panning on axes based charts.
|
|
*
|
|
* @module ZoomPanSelection
|
|
**/
|
|
|
|
export default class ZoomPanSelection extends Toolbar {
|
|
constructor(ctx) {
|
|
super(ctx)
|
|
|
|
this.ctx = ctx
|
|
this.w = ctx.w
|
|
|
|
this.dragged = false
|
|
this.graphics = new Graphics(this.ctx)
|
|
|
|
this.eventList = [
|
|
'mousedown',
|
|
'mouseleave',
|
|
'mousemove',
|
|
'touchstart',
|
|
'touchmove',
|
|
'mouseup',
|
|
'touchend',
|
|
'wheel',
|
|
]
|
|
|
|
this.clientX = 0
|
|
this.clientY = 0
|
|
this.startX = 0
|
|
this.endX = 0
|
|
this.dragX = 0
|
|
this.startY = 0
|
|
this.endY = 0
|
|
this.dragY = 0
|
|
this.moveDirection = 'none'
|
|
|
|
this.debounceTimer = null
|
|
this.debounceDelay = 100
|
|
this.wheelDelay = 400
|
|
}
|
|
|
|
init({ xyRatios }) {
|
|
let w = this.w
|
|
let me = this
|
|
|
|
this.xyRatios = xyRatios
|
|
|
|
this.zoomRect = this.graphics.drawRect(0, 0, 0, 0)
|
|
this.selectionRect = this.graphics.drawRect(0, 0, 0, 0)
|
|
|
|
this.gridRect = w.globals.dom.baseEl.querySelector('.apexcharts-grid')
|
|
this.constraints = new Box(0, 0, w.globals.gridWidth, w.globals.gridHeight)
|
|
|
|
this.zoomRect.node.classList.add('apexcharts-zoom-rect')
|
|
this.selectionRect.node.classList.add('apexcharts-selection-rect')
|
|
w.globals.dom.Paper.add(this.zoomRect)
|
|
w.globals.dom.Paper.add(this.selectionRect)
|
|
|
|
if (w.config.chart.selection.type === 'x') {
|
|
this.slDraggableRect = this.selectionRect
|
|
.draggable({
|
|
minX: 0,
|
|
minY: 0,
|
|
maxX: w.globals.gridWidth,
|
|
maxY: w.globals.gridHeight,
|
|
})
|
|
.on('dragmove.namespace', this.selectionDragging.bind(this, 'dragging'))
|
|
} else if (w.config.chart.selection.type === 'y') {
|
|
this.slDraggableRect = this.selectionRect
|
|
.draggable({
|
|
minX: 0,
|
|
maxX: w.globals.gridWidth,
|
|
})
|
|
.on('dragmove.namespace', this.selectionDragging.bind(this, 'dragging'))
|
|
} else {
|
|
this.slDraggableRect = this.selectionRect
|
|
.draggable()
|
|
.on('dragmove.namespace', this.selectionDragging.bind(this, 'dragging'))
|
|
}
|
|
this.preselectedSelection()
|
|
|
|
this.hoverArea = w.globals.dom.baseEl.querySelector(
|
|
`${w.globals.chartClass} .apexcharts-svg`
|
|
)
|
|
this.hoverArea.classList.add('apexcharts-zoomable')
|
|
|
|
this.eventList.forEach((event) => {
|
|
this.hoverArea.addEventListener(
|
|
event,
|
|
me.svgMouseEvents.bind(me, xyRatios),
|
|
{
|
|
capture: false,
|
|
passive: true,
|
|
}
|
|
)
|
|
})
|
|
|
|
if (
|
|
w.config.chart.zoom.enabled &&
|
|
w.config.chart.zoom.allowMouseWheelZoom
|
|
) {
|
|
this.hoverArea.addEventListener('wheel', me.mouseWheelEvent.bind(me), {
|
|
capture: false,
|
|
passive: false,
|
|
})
|
|
}
|
|
}
|
|
|
|
// remove the event listeners which were previously added on hover area
|
|
destroy() {
|
|
if (this.slDraggableRect) {
|
|
this.slDraggableRect.draggable(false)
|
|
this.slDraggableRect.off()
|
|
this.selectionRect.off()
|
|
}
|
|
|
|
this.selectionRect = null
|
|
this.zoomRect = null
|
|
this.gridRect = null
|
|
}
|
|
|
|
svgMouseEvents(xyRatios, e) {
|
|
let w = this.w
|
|
const toolbar = this.ctx.toolbar
|
|
|
|
let zoomtype = w.globals.zoomEnabled
|
|
? w.config.chart.zoom.type
|
|
: w.config.chart.selection.type
|
|
|
|
const autoSelected = w.config.chart.toolbar.autoSelected
|
|
|
|
if (e.shiftKey) {
|
|
this.shiftWasPressed = true
|
|
toolbar.enableZoomPanFromToolbar(autoSelected === 'pan' ? 'zoom' : 'pan')
|
|
} else {
|
|
if (this.shiftWasPressed) {
|
|
toolbar.enableZoomPanFromToolbar(autoSelected)
|
|
this.shiftWasPressed = false
|
|
}
|
|
}
|
|
|
|
if (!e.target) return
|
|
|
|
const tc = e.target.classList
|
|
let pc
|
|
if (e.target.parentNode && e.target.parentNode !== null) {
|
|
pc = e.target.parentNode.classList
|
|
}
|
|
const falsePositives =
|
|
tc.contains('apexcharts-legend-marker') ||
|
|
tc.contains('apexcharts-legend-text') ||
|
|
(pc && pc.contains('apexcharts-toolbar'))
|
|
|
|
if (falsePositives) return
|
|
|
|
this.clientX =
|
|
e.type === 'touchmove' || e.type === 'touchstart'
|
|
? e.touches[0].clientX
|
|
: e.type === 'touchend'
|
|
? e.changedTouches[0].clientX
|
|
: e.clientX
|
|
this.clientY =
|
|
e.type === 'touchmove' || e.type === 'touchstart'
|
|
? e.touches[0].clientY
|
|
: e.type === 'touchend'
|
|
? e.changedTouches[0].clientY
|
|
: e.clientY
|
|
|
|
if ((e.type === 'mousedown' && e.which === 1) || e.type === 'touchstart') {
|
|
let gridRectDim = this.gridRect.getBoundingClientRect()
|
|
|
|
this.startX =
|
|
this.clientX - gridRectDim.left - w.globals.barPadForNumericAxis
|
|
this.startY = this.clientY - gridRectDim.top
|
|
|
|
this.dragged = false
|
|
this.w.globals.mousedown = true
|
|
}
|
|
|
|
if ((e.type === 'mousemove' && e.which === 1) || e.type === 'touchmove') {
|
|
this.dragged = true
|
|
|
|
if (w.globals.panEnabled) {
|
|
w.globals.selection = null
|
|
if (this.w.globals.mousedown) {
|
|
this.panDragging({
|
|
context: this,
|
|
zoomtype,
|
|
xyRatios,
|
|
})
|
|
}
|
|
} else {
|
|
if (
|
|
(this.w.globals.mousedown && w.globals.zoomEnabled) ||
|
|
(this.w.globals.mousedown && w.globals.selectionEnabled)
|
|
) {
|
|
this.selection = this.selectionDrawing({
|
|
context: this,
|
|
zoomtype,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
if (
|
|
e.type === 'mouseup' ||
|
|
e.type === 'touchend' ||
|
|
e.type === 'mouseleave'
|
|
) {
|
|
this.handleMouseUp({ zoomtype })
|
|
}
|
|
|
|
this.makeSelectionRectDraggable()
|
|
}
|
|
|
|
handleMouseUp({ zoomtype, isResized }) {
|
|
const w = this.w
|
|
// we will be calling getBoundingClientRect on each mousedown/mousemove/mouseup
|
|
let gridRectDim = this.gridRect?.getBoundingClientRect()
|
|
|
|
if (gridRectDim && (this.w.globals.mousedown || isResized)) {
|
|
// user released the drag, now do all the calculations
|
|
this.endX =
|
|
this.clientX - gridRectDim.left - w.globals.barPadForNumericAxis
|
|
this.endY = this.clientY - gridRectDim.top
|
|
this.dragX = Math.abs(this.endX - this.startX)
|
|
this.dragY = Math.abs(this.endY - this.startY)
|
|
|
|
if (w.globals.zoomEnabled || w.globals.selectionEnabled) {
|
|
this.selectionDrawn({
|
|
context: this,
|
|
zoomtype,
|
|
})
|
|
}
|
|
|
|
// if (w.globals.panEnabled && w.config.xaxis.convertedCatToNumeric) {
|
|
// this.delayedPanScrolled()
|
|
// }
|
|
}
|
|
|
|
if (w.globals.zoomEnabled) {
|
|
this.hideSelectionRect(this.selectionRect)
|
|
}
|
|
|
|
this.dragged = false
|
|
this.w.globals.mousedown = false
|
|
}
|
|
|
|
mouseWheelEvent(e) {
|
|
const w = this.w
|
|
e.preventDefault()
|
|
|
|
const now = Date.now()
|
|
|
|
// Execute immediately if it's the first action or enough time has passed
|
|
if (now - w.globals.lastWheelExecution > this.wheelDelay) {
|
|
this.executeMouseWheelZoom(e)
|
|
w.globals.lastWheelExecution = now
|
|
}
|
|
|
|
if (this.debounceTimer) clearTimeout(this.debounceTimer)
|
|
|
|
this.debounceTimer = setTimeout(() => {
|
|
if (now - w.globals.lastWheelExecution > this.wheelDelay) {
|
|
this.executeMouseWheelZoom(e)
|
|
w.globals.lastWheelExecution = now
|
|
}
|
|
}, this.debounceDelay)
|
|
}
|
|
|
|
executeMouseWheelZoom(e) {
|
|
const w = this.w
|
|
this.minX = w.globals.isRangeBar ? w.globals.minY : w.globals.minX
|
|
this.maxX = w.globals.isRangeBar ? w.globals.maxY : w.globals.maxX
|
|
|
|
// Calculate the relative position of the mouse on the chart
|
|
const gridRectDim = this.gridRect?.getBoundingClientRect()
|
|
if (!gridRectDim) return
|
|
|
|
const mouseX = (e.clientX - gridRectDim.left) / gridRectDim.width
|
|
|
|
const currentMinX = this.minX
|
|
const currentMaxX = this.maxX
|
|
const totalX = currentMaxX - currentMinX
|
|
|
|
// Determine zoom factor
|
|
const zoomFactorIn = 0.5
|
|
const zoomFactorOut = 1.5
|
|
let zoomRange
|
|
|
|
let newMinX, newMaxX
|
|
if (e.deltaY < 0) {
|
|
// Zoom In
|
|
zoomRange = zoomFactorIn * totalX
|
|
const midPoint = currentMinX + mouseX * totalX
|
|
newMinX = midPoint - zoomRange / 2
|
|
newMaxX = midPoint + zoomRange / 2
|
|
} else {
|
|
// Zoom Out
|
|
zoomRange = zoomFactorOut * totalX
|
|
newMinX = currentMinX - zoomRange / 2
|
|
newMaxX = currentMaxX + zoomRange / 2
|
|
}
|
|
|
|
// Constrain within original chart bounds
|
|
if (!w.globals.isRangeBar) {
|
|
newMinX = Math.max(newMinX, w.globals.initialMinX)
|
|
newMaxX = Math.min(newMaxX, w.globals.initialMaxX)
|
|
|
|
// Ensure minimum range
|
|
const minRange = (w.globals.initialMaxX - w.globals.initialMinX) * 0.01
|
|
if (newMaxX - newMinX < minRange) {
|
|
const midPoint = (newMinX + newMaxX) / 2
|
|
newMinX = midPoint - minRange / 2
|
|
newMaxX = midPoint + minRange / 2
|
|
}
|
|
}
|
|
|
|
const newMinXMaxX = this._getNewMinXMaxX(newMinX, newMaxX)
|
|
|
|
// Apply zoom if valid
|
|
if (!isNaN(newMinXMaxX.minX) && !isNaN(newMinXMaxX.maxX)) {
|
|
this.zoomUpdateOptions(newMinXMaxX.minX, newMinXMaxX.maxX)
|
|
}
|
|
}
|
|
|
|
makeSelectionRectDraggable() {
|
|
const w = this.w
|
|
|
|
if (!this.selectionRect) return
|
|
|
|
const rectDim = this.selectionRect.node.getBoundingClientRect()
|
|
if (rectDim.width > 0 && rectDim.height > 0) {
|
|
this.selectionRect.select(false).resize(false)
|
|
this.selectionRect
|
|
.select({
|
|
createRot: () => {},
|
|
updateRot: () => {},
|
|
createHandle: (group, p, index, pointArr, handleName) => {
|
|
if (handleName === 'l' || handleName === 'r')
|
|
return group
|
|
.circle(8)
|
|
.css({ 'stroke-width': 1, stroke: '#333', fill: '#fff' })
|
|
return group.circle(0)
|
|
},
|
|
updateHandle: (group, p) => {
|
|
return group.center(p[0], p[1])
|
|
},
|
|
})
|
|
.resize()
|
|
.on('resize', () => {
|
|
let zoomtype = w.globals.zoomEnabled
|
|
? w.config.chart.zoom.type
|
|
: w.config.chart.selection.type
|
|
|
|
this.handleMouseUp({ zoomtype, isResized: true })
|
|
})
|
|
}
|
|
}
|
|
|
|
preselectedSelection() {
|
|
const w = this.w
|
|
const xyRatios = this.xyRatios
|
|
|
|
if (!w.globals.zoomEnabled) {
|
|
if (
|
|
typeof w.globals.selection !== 'undefined' &&
|
|
w.globals.selection !== null
|
|
) {
|
|
this.drawSelectionRect({
|
|
...w.globals.selection,
|
|
translateX: w.globals.translateX,
|
|
translateY: w.globals.translateY,
|
|
})
|
|
} else {
|
|
if (
|
|
w.config.chart.selection.xaxis.min !== undefined &&
|
|
w.config.chart.selection.xaxis.max !== undefined
|
|
) {
|
|
let x =
|
|
(w.config.chart.selection.xaxis.min - w.globals.minX) /
|
|
xyRatios.xRatio
|
|
let width =
|
|
w.globals.gridWidth -
|
|
(w.globals.maxX - w.config.chart.selection.xaxis.max) /
|
|
xyRatios.xRatio -
|
|
x
|
|
if (w.globals.isRangeBar) {
|
|
// rangebars put datetime data in y axis
|
|
x =
|
|
(w.config.chart.selection.xaxis.min -
|
|
w.globals.yAxisScale[0].niceMin) /
|
|
xyRatios.invertedYRatio
|
|
width =
|
|
(w.config.chart.selection.xaxis.max -
|
|
w.config.chart.selection.xaxis.min) /
|
|
xyRatios.invertedYRatio
|
|
}
|
|
let selectionRect = {
|
|
x,
|
|
y: 0,
|
|
width,
|
|
height: w.globals.gridHeight,
|
|
translateX: w.globals.translateX,
|
|
translateY: w.globals.translateY,
|
|
selectionEnabled: true,
|
|
}
|
|
this.drawSelectionRect(selectionRect)
|
|
this.makeSelectionRectDraggable()
|
|
if (typeof w.config.chart.events.selection === 'function') {
|
|
w.config.chart.events.selection(this.ctx, {
|
|
xaxis: {
|
|
min: w.config.chart.selection.xaxis.min,
|
|
max: w.config.chart.selection.xaxis.max,
|
|
},
|
|
yaxis: {},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
drawSelectionRect({ x, y, width, height, translateX = 0, translateY = 0 }) {
|
|
const w = this.w
|
|
const zoomRect = this.zoomRect
|
|
const selectionRect = this.selectionRect
|
|
if (this.dragged || w.globals.selection !== null) {
|
|
let scalingAttrs = {
|
|
transform: 'translate(' + translateX + ', ' + translateY + ')',
|
|
}
|
|
|
|
// change styles based on zoom or selection
|
|
// zoom is Enabled and user has dragged, so draw blue rect
|
|
if (w.globals.zoomEnabled && this.dragged) {
|
|
if (width < 0) width = 1 // fixes apexcharts.js#1168
|
|
zoomRect.attr({
|
|
x,
|
|
y,
|
|
width,
|
|
height,
|
|
fill: w.config.chart.zoom.zoomedArea.fill.color,
|
|
'fill-opacity': w.config.chart.zoom.zoomedArea.fill.opacity,
|
|
stroke: w.config.chart.zoom.zoomedArea.stroke.color,
|
|
'stroke-width': w.config.chart.zoom.zoomedArea.stroke.width,
|
|
'stroke-opacity': w.config.chart.zoom.zoomedArea.stroke.opacity,
|
|
})
|
|
Graphics.setAttrs(zoomRect.node, scalingAttrs)
|
|
}
|
|
|
|
// selection is enabled
|
|
if (w.globals.selectionEnabled) {
|
|
selectionRect.attr({
|
|
x,
|
|
y,
|
|
width: width > 0 ? width : 0,
|
|
height: height > 0 ? height : 0,
|
|
fill: w.config.chart.selection.fill.color,
|
|
'fill-opacity': w.config.chart.selection.fill.opacity,
|
|
stroke: w.config.chart.selection.stroke.color,
|
|
'stroke-width': w.config.chart.selection.stroke.width,
|
|
'stroke-dasharray': w.config.chart.selection.stroke.dashArray,
|
|
'stroke-opacity': w.config.chart.selection.stroke.opacity,
|
|
})
|
|
|
|
Graphics.setAttrs(selectionRect.node, scalingAttrs)
|
|
}
|
|
}
|
|
}
|
|
|
|
hideSelectionRect(rect) {
|
|
if (rect) {
|
|
rect.attr({
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
selectionDrawing({ context, zoomtype }) {
|
|
const w = this.w
|
|
let me = context
|
|
|
|
let gridRectDim = this.gridRect.getBoundingClientRect()
|
|
|
|
let startX = me.startX - 1
|
|
let startY = me.startY
|
|
let inversedX = false
|
|
let inversedY = false
|
|
|
|
const left = me.clientX - gridRectDim.left - w.globals.barPadForNumericAxis
|
|
const top = me.clientY - gridRectDim.top
|
|
|
|
let selectionWidth = left - startX
|
|
let selectionHeight = top - startY
|
|
|
|
let selectionRect = {
|
|
translateX: w.globals.translateX,
|
|
translateY: w.globals.translateY,
|
|
}
|
|
|
|
if (Math.abs(selectionWidth + startX) > w.globals.gridWidth) {
|
|
// user dragged the mouse outside drawing area to the right
|
|
selectionWidth = w.globals.gridWidth - startX
|
|
} else if (left < 0) {
|
|
// user dragged the mouse outside drawing area to the left
|
|
selectionWidth = startX
|
|
}
|
|
|
|
// inverse selection X
|
|
if (startX > left) {
|
|
inversedX = true
|
|
selectionWidth = Math.abs(selectionWidth)
|
|
}
|
|
|
|
// inverse selection Y
|
|
if (startY > top) {
|
|
inversedY = true
|
|
selectionHeight = Math.abs(selectionHeight)
|
|
}
|
|
|
|
if (zoomtype === 'x') {
|
|
selectionRect = {
|
|
x: inversedX ? startX - selectionWidth : startX,
|
|
y: 0,
|
|
width: selectionWidth,
|
|
height: w.globals.gridHeight,
|
|
}
|
|
} else if (zoomtype === 'y') {
|
|
selectionRect = {
|
|
x: 0,
|
|
y: inversedY ? startY - selectionHeight : startY,
|
|
width: w.globals.gridWidth,
|
|
height: selectionHeight,
|
|
}
|
|
} else {
|
|
selectionRect = {
|
|
x: inversedX ? startX - selectionWidth : startX,
|
|
y: inversedY ? startY - selectionHeight : startY,
|
|
width: selectionWidth,
|
|
height: selectionHeight,
|
|
}
|
|
}
|
|
|
|
selectionRect = {
|
|
...selectionRect,
|
|
translateX: w.globals.translateX,
|
|
translateY: w.globals.translateY,
|
|
}
|
|
|
|
me.drawSelectionRect(selectionRect)
|
|
me.selectionDragging('resizing')
|
|
return selectionRect
|
|
}
|
|
|
|
selectionDragging(type, e) {
|
|
const w = this.w
|
|
if (!e) return
|
|
|
|
e.preventDefault()
|
|
|
|
const { handler, box } = e.detail
|
|
|
|
let { x, y } = box
|
|
|
|
if (x < this.constraints.x) {
|
|
x = this.constraints.x
|
|
}
|
|
|
|
if (y < this.constraints.y) {
|
|
y = this.constraints.y
|
|
}
|
|
|
|
if (box.x2 > this.constraints.x2) {
|
|
x = this.constraints.x2 - box.w
|
|
}
|
|
|
|
if (box.y2 > this.constraints.y2) {
|
|
y = this.constraints.y2 - box.h
|
|
}
|
|
|
|
handler.move(x, y)
|
|
|
|
const xyRatios = this.xyRatios
|
|
|
|
const selRect = this.selectionRect
|
|
|
|
let timerInterval = 0
|
|
|
|
if (type === 'resizing') {
|
|
timerInterval = 30
|
|
}
|
|
|
|
// update selection when selection rect is dragged
|
|
const getSelAttr = (attr) => {
|
|
return parseFloat(selRect.node.getAttribute(attr))
|
|
}
|
|
const draggedProps = {
|
|
x: getSelAttr('x'),
|
|
y: getSelAttr('y'),
|
|
width: getSelAttr('width'),
|
|
height: getSelAttr('height'),
|
|
}
|
|
|
|
w.globals.selection = draggedProps
|
|
// update selection ends
|
|
|
|
if (
|
|
typeof w.config.chart.events.selection === 'function' &&
|
|
w.globals.selectionEnabled
|
|
) {
|
|
// a small debouncer is required when resizing to avoid freezing the chart
|
|
clearTimeout(this.w.globals.selectionResizeTimer)
|
|
this.w.globals.selectionResizeTimer = window.setTimeout(() => {
|
|
const gridRectDim = this.gridRect.getBoundingClientRect()
|
|
const selectionRect = selRect.node.getBoundingClientRect()
|
|
|
|
let minX, maxX, minY, maxY
|
|
|
|
if (!w.globals.isRangeBar) {
|
|
// normal XY charts
|
|
minX =
|
|
w.globals.xAxisScale.niceMin +
|
|
(selectionRect.left - gridRectDim.left) * xyRatios.xRatio
|
|
maxX =
|
|
w.globals.xAxisScale.niceMin +
|
|
(selectionRect.right - gridRectDim.left) * xyRatios.xRatio
|
|
|
|
minY =
|
|
w.globals.yAxisScale[0].niceMin +
|
|
(gridRectDim.bottom - selectionRect.bottom) * xyRatios.yRatio[0]
|
|
maxY =
|
|
w.globals.yAxisScale[0].niceMax -
|
|
(selectionRect.top - gridRectDim.top) * xyRatios.yRatio[0]
|
|
} else {
|
|
// rangeBars use y for datetime
|
|
minX =
|
|
w.globals.yAxisScale[0].niceMin +
|
|
(selectionRect.left - gridRectDim.left) * xyRatios.invertedYRatio
|
|
maxX =
|
|
w.globals.yAxisScale[0].niceMin +
|
|
(selectionRect.right - gridRectDim.left) * xyRatios.invertedYRatio
|
|
|
|
minY = 0
|
|
maxY = 1
|
|
}
|
|
|
|
const xyAxis = {
|
|
xaxis: {
|
|
min: minX,
|
|
max: maxX,
|
|
},
|
|
yaxis: {
|
|
min: minY,
|
|
max: maxY,
|
|
},
|
|
}
|
|
w.config.chart.events.selection(this.ctx, xyAxis)
|
|
|
|
if (
|
|
w.config.chart.brush.enabled &&
|
|
w.config.chart.events.brushScrolled !== undefined
|
|
) {
|
|
w.config.chart.events.brushScrolled(this.ctx, xyAxis)
|
|
}
|
|
}, timerInterval)
|
|
}
|
|
}
|
|
|
|
selectionDrawn({ context, zoomtype }) {
|
|
const w = this.w
|
|
const me = context
|
|
const xyRatios = this.xyRatios
|
|
const toolbar = this.ctx.toolbar
|
|
|
|
// Use boundingRect for final selection area
|
|
const selRect = w.globals.zoomEnabled
|
|
? me.zoomRect.node.getBoundingClientRect()
|
|
: me.selectionRect.node.getBoundingClientRect()
|
|
const gridRectDim = me.gridRect.getBoundingClientRect()
|
|
|
|
// Local coords in the chart's grid
|
|
const localStartX =
|
|
selRect.left - gridRectDim.left - w.globals.barPadForNumericAxis
|
|
const localEndX =
|
|
selRect.right - gridRectDim.left - w.globals.barPadForNumericAxis
|
|
const localStartY = selRect.top - gridRectDim.top
|
|
const localEndY = selRect.bottom - gridRectDim.top
|
|
|
|
// Convert those local coords to actual data values
|
|
let xLowestValue, xHighestValue
|
|
|
|
if (!w.globals.isRangeBar) {
|
|
xLowestValue =
|
|
w.globals.xAxisScale.niceMin + localStartX * xyRatios.xRatio
|
|
xHighestValue = w.globals.xAxisScale.niceMin + localEndX * xyRatios.xRatio
|
|
} else {
|
|
xLowestValue =
|
|
w.globals.yAxisScale[0].niceMin + localStartX * xyRatios.invertedYRatio
|
|
xHighestValue =
|
|
w.globals.yAxisScale[0].niceMin + localEndX * xyRatios.invertedYRatio
|
|
}
|
|
|
|
// For Y values, pick from the first y-axis, but handle multi-axis
|
|
let yHighestValue = []
|
|
let yLowestValue = []
|
|
|
|
w.config.yaxis.forEach((yaxe, index) => {
|
|
// pick whichever series is mapped to this y-axis
|
|
let seriesIndex = w.globals.seriesYAxisMap[index][0]
|
|
let highestVal =
|
|
w.globals.yAxisScale[index].niceMax -
|
|
xyRatios.yRatio[seriesIndex] * localStartY
|
|
let lowestVal =
|
|
w.globals.yAxisScale[index].niceMax -
|
|
xyRatios.yRatio[seriesIndex] * localEndY
|
|
|
|
yHighestValue.push(highestVal)
|
|
yLowestValue.push(lowestVal)
|
|
})
|
|
|
|
// Only apply if user actually dragged far enough to consider it a selection
|
|
if (
|
|
me.dragged &&
|
|
(me.dragX > 10 || me.dragY > 10) &&
|
|
xLowestValue !== xHighestValue
|
|
) {
|
|
if (w.globals.zoomEnabled) {
|
|
let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
|
|
let xaxis = Utils.clone(w.globals.initialConfig.xaxis)
|
|
|
|
w.globals.zoomed = true
|
|
|
|
if (w.config.xaxis.convertedCatToNumeric) {
|
|
xLowestValue = Math.floor(xLowestValue)
|
|
xHighestValue = Math.floor(xHighestValue)
|
|
|
|
if (xLowestValue < 1) {
|
|
xLowestValue = 1
|
|
xHighestValue = w.globals.dataPoints
|
|
}
|
|
|
|
if (xHighestValue - xLowestValue < 2) {
|
|
xHighestValue = xLowestValue + 1
|
|
}
|
|
}
|
|
|
|
if (zoomtype === 'xy' || zoomtype === 'x') {
|
|
xaxis = {
|
|
min: xLowestValue,
|
|
max: xHighestValue,
|
|
}
|
|
}
|
|
|
|
if (zoomtype === 'xy' || zoomtype === 'y') {
|
|
yaxis.forEach((yaxe, index) => {
|
|
yaxis[index].min = yLowestValue[index]
|
|
yaxis[index].max = yHighestValue[index]
|
|
})
|
|
}
|
|
|
|
if (toolbar) {
|
|
let beforeZoomRange = toolbar.getBeforeZoomRange(xaxis, yaxis)
|
|
if (beforeZoomRange) {
|
|
xaxis = beforeZoomRange.xaxis ? beforeZoomRange.xaxis : xaxis
|
|
yaxis = beforeZoomRange.yaxis ? beforeZoomRange.yaxis : yaxis
|
|
}
|
|
}
|
|
|
|
let options = {
|
|
xaxis,
|
|
}
|
|
|
|
if (!w.config.chart.group) {
|
|
// if chart in a group, prevent yaxis update here
|
|
// fix issue #650
|
|
options.yaxis = yaxis
|
|
}
|
|
me.ctx.updateHelpers._updateOptions(
|
|
options,
|
|
false,
|
|
me.w.config.chart.animations.dynamicAnimation.enabled
|
|
)
|
|
|
|
if (typeof w.config.chart.events.zoomed === 'function') {
|
|
toolbar.zoomCallback(xaxis, yaxis)
|
|
}
|
|
} else if (w.globals.selectionEnabled) {
|
|
let yaxis = null
|
|
let xaxis = null
|
|
xaxis = {
|
|
min: xLowestValue,
|
|
max: xHighestValue,
|
|
}
|
|
if (zoomtype === 'xy' || zoomtype === 'y') {
|
|
yaxis = Utils.clone(w.config.yaxis)
|
|
yaxis.forEach((yaxe, index) => {
|
|
yaxis[index].min = yLowestValue[index]
|
|
yaxis[index].max = yHighestValue[index]
|
|
})
|
|
}
|
|
|
|
w.globals.selection = me.selection
|
|
if (typeof w.config.chart.events.selection === 'function') {
|
|
w.config.chart.events.selection(me.ctx, {
|
|
xaxis,
|
|
yaxis,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
panDragging({ context }) {
|
|
const w = this.w
|
|
let me = context
|
|
|
|
// check to make sure there is data to compare against
|
|
if (typeof w.globals.lastClientPosition.x !== 'undefined') {
|
|
// get the change from last position to this position
|
|
const deltaX = w.globals.lastClientPosition.x - me.clientX
|
|
const deltaY = w.globals.lastClientPosition.y - me.clientY
|
|
|
|
// check which direction had the highest amplitude
|
|
if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
|
|
this.moveDirection = 'left'
|
|
} else if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
|
|
this.moveDirection = 'right'
|
|
} else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY > 0) {
|
|
this.moveDirection = 'up'
|
|
} else if (Math.abs(deltaY) > Math.abs(deltaX) && deltaY < 0) {
|
|
this.moveDirection = 'down'
|
|
}
|
|
}
|
|
|
|
// set the new last position to the current for next time (to get the position of drag)
|
|
w.globals.lastClientPosition = {
|
|
x: me.clientX,
|
|
y: me.clientY,
|
|
}
|
|
|
|
let xLowestValue = w.globals.isRangeBar ? w.globals.minY : w.globals.minX
|
|
|
|
let xHighestValue = w.globals.isRangeBar ? w.globals.maxY : w.globals.maxX
|
|
|
|
// removed delayedPanScrolled as it doesn't seem to cause bugs anymore in convertedCatToNumeric
|
|
// if (!w.config.xaxis.convertedCatToNumeric) {
|
|
me.panScrolled(xLowestValue, xHighestValue)
|
|
// }
|
|
}
|
|
|
|
// delayedPanScrolled() {
|
|
// const w = this.w
|
|
|
|
// let newMinX = w.globals.minX
|
|
// let newMaxX = w.globals.maxX
|
|
// const centerX = (w.globals.maxX - w.globals.minX) / 2
|
|
|
|
// if (this.moveDirection === 'left') {
|
|
// newMinX = w.globals.minX + centerX
|
|
// newMaxX = w.globals.maxX + centerX
|
|
// } else if (this.moveDirection === 'right') {
|
|
// newMinX = w.globals.minX - centerX
|
|
// newMaxX = w.globals.maxX - centerX
|
|
// }
|
|
|
|
// newMinX = Math.floor(newMinX)
|
|
// newMaxX = Math.floor(newMaxX)
|
|
// this.updateScrolledChart(
|
|
// { xaxis: { min: newMinX, max: newMaxX } },
|
|
// newMinX,
|
|
// newMaxX
|
|
// )
|
|
// }
|
|
|
|
panScrolled(xLowestValue, xHighestValue) {
|
|
const w = this.w
|
|
|
|
const xyRatios = this.xyRatios
|
|
let yaxis = Utils.clone(w.globals.initialConfig.yaxis)
|
|
|
|
let xRatio = xyRatios.xRatio
|
|
let minX = w.globals.minX
|
|
let maxX = w.globals.maxX
|
|
if (w.globals.isRangeBar) {
|
|
xRatio = xyRatios.invertedYRatio
|
|
minX = w.globals.minY
|
|
maxX = w.globals.maxY
|
|
}
|
|
|
|
if (this.moveDirection === 'left') {
|
|
xLowestValue = minX + (w.globals.gridWidth / 15) * xRatio
|
|
xHighestValue = maxX + (w.globals.gridWidth / 15) * xRatio
|
|
} else if (this.moveDirection === 'right') {
|
|
xLowestValue = minX - (w.globals.gridWidth / 15) * xRatio
|
|
xHighestValue = maxX - (w.globals.gridWidth / 15) * xRatio
|
|
}
|
|
|
|
if (!w.globals.isRangeBar) {
|
|
if (
|
|
xLowestValue < w.globals.initialMinX ||
|
|
xHighestValue > w.globals.initialMaxX
|
|
) {
|
|
xLowestValue = minX
|
|
xHighestValue = maxX
|
|
}
|
|
}
|
|
|
|
let xaxis = {
|
|
min: xLowestValue,
|
|
max: xHighestValue,
|
|
}
|
|
|
|
let options = {
|
|
xaxis,
|
|
}
|
|
|
|
if (!w.config.chart.group) {
|
|
// if chart in a group, prevent yaxis update here
|
|
// fix issue #650
|
|
options.yaxis = yaxis
|
|
}
|
|
|
|
this.updateScrolledChart(options, xLowestValue, xHighestValue)
|
|
}
|
|
|
|
updateScrolledChart(options, xLowestValue, xHighestValue) {
|
|
const w = this.w
|
|
|
|
this.ctx.updateHelpers._updateOptions(options, false, false)
|
|
|
|
if (typeof w.config.chart.events.scrolled === 'function') {
|
|
const args = {
|
|
xaxis: {
|
|
min: xLowestValue,
|
|
max: xHighestValue,
|
|
},
|
|
}
|
|
w.config.chart.events.scrolled(this.ctx, args)
|
|
this.ctx.events.fireEvent('scrolled', args)
|
|
}
|
|
}
|
|
}
|