-
Notifications
You must be signed in to change notification settings - Fork 327
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refactor to logical entities * Remove monolith * Complete refactoring * Add tolerance * Fix drag-zoom, complete let/const * Better helper names, shorten wheel * Remove duplicate drag rect calculations
- Loading branch information
Showing
13 changed files
with
695 additions
and
674 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,4 @@ globals: | |
rules: | ||
indent: ["error", "tab", {flatTernaryExpressions: true}] | ||
no-new: "off" | ||
no-var: "off" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import {each, callback as call} from 'chart.js/helpers'; | ||
import {panFunctions, zoomFunctions} from './scale.types'; | ||
import {directionEnabled, getEnabledScalesByPoint} from './utils'; | ||
|
||
function storeOriginalOptions(chart) { | ||
const originalOptions = chart.$zoom._originalOptions; | ||
each(chart.scales, function(scale) { | ||
if (!originalOptions[scale.id]) { | ||
originalOptions[scale.id] = {min: scale.options.min, max: scale.options.max}; | ||
} | ||
}); | ||
each(originalOptions, function(opt, key) { | ||
if (!chart.scales[key]) { | ||
delete originalOptions[key]; | ||
} | ||
}); | ||
} | ||
|
||
function zoomScale(scale, zoom, center, zoomOptions) { | ||
call(zoomFunctions[scale.type], [scale, zoom, center, zoomOptions]); | ||
} | ||
|
||
/** | ||
* @param chart The chart instance | ||
* @param {number} percentZoomX The zoom percentage in the x direction | ||
* @param {number} percentZoomY The zoom percentage in the y direction | ||
* @param {{x: number, y: number}} focalPoint The x and y coordinates of zoom focal point. The point which doesn't change while zooming. E.g. the location of the mouse cursor when "drag: false" | ||
* @param {object} zoomOptions The zoom options | ||
* @param {string} [whichAxes] `xy`, 'x', or 'y' | ||
* @param {boolean} [useTransition] Whether to use `zoom` transition | ||
*/ | ||
export function doZoom(chart, percentZoomX, percentZoomY, focalPoint, zoomOptions, whichAxes, useTransition) { | ||
const ca = chart.chartArea; | ||
if (!focalPoint) { | ||
focalPoint = { | ||
x: (ca.left + ca.right) / 2, | ||
y: (ca.top + ca.bottom) / 2, | ||
}; | ||
} | ||
|
||
if (zoomOptions.enabled) { | ||
storeOriginalOptions(chart); | ||
// Do the zoom here | ||
const zoomMode = typeof zoomOptions.mode === 'function' ? zoomOptions.mode({chart: chart}) : zoomOptions.mode; | ||
|
||
// Which axes should be modified when fingers were used. | ||
let _whichAxes; | ||
if (zoomMode === 'xy' && whichAxes !== undefined) { | ||
// based on fingers positions | ||
_whichAxes = whichAxes; | ||
} else { | ||
// no effect | ||
_whichAxes = 'xy'; | ||
} | ||
|
||
const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint.x, focalPoint.y, chart); | ||
each(enabledScales || chart.scales, function(scale) { | ||
if (scale.isHorizontal() && directionEnabled(zoomMode, 'x', chart) && directionEnabled(_whichAxes, 'x', chart)) { | ||
zoomOptions.scaleAxes = 'x'; | ||
zoomScale(scale, percentZoomX, focalPoint, zoomOptions); | ||
} else if (!scale.isHorizontal() && directionEnabled(zoomMode, 'y', chart) && directionEnabled(_whichAxes, 'y', chart)) { | ||
// Do Y zoom | ||
zoomOptions.scaleAxes = 'y'; | ||
zoomScale(scale, percentZoomY, focalPoint, zoomOptions); | ||
} | ||
}); | ||
|
||
chart.update(useTransition ? 'zoom' : 'none'); | ||
|
||
call(zoomOptions.onZoom, [chart]); | ||
} | ||
} | ||
|
||
export function resetZoom(chart) { | ||
storeOriginalOptions(chart); | ||
const originalOptions = chart.$zoom._originalOptions; | ||
each(chart.scales, function(scale) { | ||
|
||
const scaleOptions = scale.options; | ||
if (originalOptions[scale.id]) { | ||
scaleOptions.min = originalOptions[scale.id].min; | ||
scaleOptions.max = originalOptions[scale.id].max; | ||
} else { | ||
delete scaleOptions.min; | ||
delete scaleOptions.max; | ||
} | ||
}); | ||
chart.update(); | ||
} | ||
|
||
function panScale(scale, delta, panOptions) { | ||
call(panFunctions[scale.type], [scale, delta, panOptions]); | ||
} | ||
|
||
export function doPan(chart, deltaX, deltaY, panOptions, panningScales) { | ||
storeOriginalOptions(chart); | ||
if (panOptions.enabled) { | ||
const panMode = typeof panOptions.mode === 'function' ? panOptions.mode({chart}) : panOptions.mode; | ||
|
||
each(panningScales || chart.scales, function(scale) { | ||
if (scale.isHorizontal() && directionEnabled(panMode, 'x', chart) && deltaX !== 0) { | ||
panOptions.scaleAxes = 'x'; | ||
panScale(scale, deltaX, panOptions); | ||
} else if (!scale.isHorizontal() && directionEnabled(panMode, 'y', chart) && deltaY !== 0) { | ||
panOptions.scaleAxes = 'y'; | ||
panScale(scale, deltaY, panOptions); | ||
} | ||
}); | ||
|
||
chart.update('none'); | ||
|
||
call(panOptions.onPan, [chart]); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import Hammer from 'hammerjs'; | ||
import {doPan, doZoom} from './core'; | ||
import {getEnabledScalesByPoint} from './utils'; | ||
|
||
function createEnabler(chart, panOptions) { | ||
return function(recognizer, event) { | ||
if (!panOptions || !panOptions.enabled) { | ||
return false; | ||
} | ||
if (!event || !event.srcEvent) { // Sometimes Hammer queries this with a null event. | ||
return true; | ||
} | ||
const requireModifier = panOptions.modifierKey | ||
&& (event.pointerType === 'mouse'); | ||
if (requireModifier && !event.srcEvent[panOptions.modifierKey + 'Key']) { | ||
if (typeof panOptions.onPanRejected === 'function') { | ||
panOptions.onPanRejected({ | ||
chart: chart, | ||
event: event | ||
}); | ||
} | ||
return false; | ||
} | ||
return true; | ||
}; | ||
} | ||
|
||
export function startHammer(chart, options) { | ||
const node = chart.canvas; | ||
const {pan: panOptions, zoom: zoomOptions} = options; | ||
|
||
const mc = new Hammer.Manager(node); | ||
if (zoomOptions && zoomOptions.enabled) { | ||
mc.add(new Hammer.Pinch()); | ||
} | ||
if (panOptions && panOptions.enabled) { | ||
mc.add(new Hammer.Pan({ | ||
threshold: panOptions.threshold, | ||
enable: createEnabler(chart, panOptions) | ||
})); | ||
} | ||
|
||
// Hammer reports the total scaling. We need the incremental amount | ||
let currentPinchScaling; | ||
const handlePinch = function(e) { | ||
const diff = 1 / (currentPinchScaling) * e.scale; | ||
const rect = e.target.getBoundingClientRect(); | ||
const offsetX = e.center.x - rect.left; | ||
const offsetY = e.center.y - rect.top; | ||
const center = { | ||
x: offsetX, | ||
y: offsetY | ||
}; | ||
|
||
// fingers position difference | ||
const x = Math.abs(e.pointers[0].clientX - e.pointers[1].clientX); | ||
const y = Math.abs(e.pointers[0].clientY - e.pointers[1].clientY); | ||
|
||
// diagonal fingers will change both (xy) axes | ||
const p = x / y; | ||
let xy; | ||
if (p > 0.3 && p < 1.7) { | ||
xy = 'xy'; | ||
} else if (x > y) { | ||
xy = 'x'; // x axis | ||
} else { | ||
xy = 'y'; // y axis | ||
} | ||
|
||
doZoom(chart, diff, diff, center, zoomOptions, xy); | ||
|
||
if (typeof zoomOptions.onZoom === 'function') { | ||
zoomOptions.onZoom({chart: chart}); | ||
} | ||
|
||
// Keep track of overall scale | ||
currentPinchScaling = e.scale; | ||
}; | ||
|
||
mc.on('pinchstart', function() { | ||
currentPinchScaling = 1; // reset tracker | ||
}); | ||
mc.on('pinch', handlePinch); | ||
mc.on('pinchend', function(e) { | ||
handlePinch(e); | ||
currentPinchScaling = null; // reset | ||
if (typeof zoomOptions.onZoomComplete === 'function') { | ||
zoomOptions.onZoomComplete({chart: chart}); | ||
} | ||
}); | ||
|
||
let currentDeltaX = null; | ||
let currentDeltaY = null; | ||
let panning = false; | ||
let panningScales = null; | ||
const handlePan = function(e) { | ||
if (currentDeltaX !== null && currentDeltaY !== null) { | ||
panning = true; | ||
const deltaX = e.deltaX - currentDeltaX; | ||
const deltaY = e.deltaY - currentDeltaY; | ||
currentDeltaX = e.deltaX; | ||
currentDeltaY = e.deltaY; | ||
doPan(chart, deltaX, deltaY, panOptions, panningScales); | ||
} | ||
}; | ||
|
||
mc.on('panstart', function(e) { | ||
if (panOptions.enabled) { | ||
const rect = e.target.getBoundingClientRect(); | ||
const x = e.center.x - rect.left; | ||
const y = e.center.y - rect.top; | ||
panningScales = getEnabledScalesByPoint(panOptions, x, y, chart); | ||
} | ||
|
||
currentDeltaX = 0; | ||
currentDeltaY = 0; | ||
handlePan(e); | ||
}); | ||
mc.on('panmove', handlePan); | ||
mc.on('panend', function() { | ||
currentDeltaX = null; | ||
currentDeltaY = null; | ||
setTimeout(function() { | ||
panning = false; | ||
}, 500); | ||
if (typeof panOptions.onPanComplete === 'function') { | ||
panOptions.onPanComplete({chart: chart}); | ||
} | ||
}); | ||
|
||
chart.$zoom._ghostClickHandler = function(e) { | ||
if (panning && e.cancelable) { | ||
e.stopImmediatePropagation(); | ||
e.preventDefault(); | ||
} | ||
}; | ||
node.addEventListener('click', chart.$zoom._ghostClickHandler); | ||
|
||
chart._mc = mc; | ||
} | ||
|
||
export function stopHammer(chart) { | ||
const mc = chart._mc; | ||
if (mc) { | ||
mc.remove('pinchstart'); | ||
mc.remove('pinch'); | ||
mc.remove('pinchend'); | ||
mc.remove('panstart'); | ||
mc.remove('pan'); | ||
mc.remove('panend'); | ||
mc.destroy(); | ||
} | ||
} |
Oops, something went wrong.