UNPKG

@nativescript-community/ui-chart

Version:

A powerful chart / graph plugin, supporting line, bar, pie, radar, bubble, and candlestick charts as well as scaling, panning and animations.

282 lines (281 loc) 11.3 kB
import { GestureHandlerStateEvent, GestureHandlerTouchEvent, GestureState, HandlerType, Manager } from '@nativescript-community/gesturehandler'; import { time } from '@nativescript/core/profiling'; import { ChartGesture, ChartTouchListener } from './ChartTouchListener'; let TAP_HANDLER_TAG = 11232000; let ROTATION_HANDLER_TAG = 11231000; class AngularVelocitySample { constructor(time, angle) { this.time = time; this.angle = angle; } } /** * TouchListener for Pie- and RadarChart with handles all * touch interaction. * */ export class PieRadarChartTouchListener extends ChartTouchListener { constructor(chart) { super(chart); /** * the angle where the dragging started */ this.mStartAngle = 0; this._velocitySamples = []; this.mDecelerationLastTime = 0; this.mDecelerationAngularVelocity = 0; this.TAP_HANDLER_TAG = TAP_HANDLER_TAG++; this.ROTATION_HANDLER_TAG = ROTATION_HANDLER_TAG++; } getTapGestureOptions() { return { gestureTag: this.TAP_HANDLER_TAG, ...(this.chart.tapGestureOptions || {}) }; } getRotationGestureOptions() { return { gestureTag: this.ROTATION_HANDLER_TAG, ...(this.chart.rotationGestureOptions || {}) }; } getOrCreateRotationGestureHandler() { if (!this.rotationGestureHandler) { const manager = Manager.getInstance(); const options = this.getRotationGestureOptions(); this.rotationGestureHandler = manager .createGestureHandler(HandlerType.ROTATION, options.gestureTag, {}) .on(GestureHandlerStateEvent, this.onRotationGesture, this) .on(GestureHandlerTouchEvent, this.onRotationGestureTouch, this); } return this.rotationGestureHandler; } getOrCreateTapGestureHandler() { if (!this.tapGestureHandler) { const manager = Manager.getInstance(); const options = this.getTapGestureOptions(); this.tapGestureHandler = manager.createGestureHandler(HandlerType.TAP, options.gestureTag, {}).on(GestureHandlerStateEvent, this.onTapGesture, this); } return this.tapGestureHandler; } setRotation(enabled) { if (enabled) { this.getOrCreateRotationGestureHandler().attachToView(this.chart); } else if (this.rotationGestureHandler) { this.rotationGestureHandler.detachFromView(this.chart); } } setTap(enabled) { if (enabled) { this.getOrCreateTapGestureHandler().attachToView(this.chart); } else if (this.tapGestureHandler) { this.tapGestureHandler.detachFromView(this.chart); } } dispose() { super.dispose(); const chart = this.chart; this.rotationGestureHandler && this.rotationGestureHandler.detachFromView(chart); this.tapGestureHandler && this.tapGestureHandler.detachFromView(chart); // this.longpressGestureHandler.detachFromView(chart); } init() { super.init(); if (this.chart.rotationEnabled) { this.setRotation(true); } if (this.chart.highlightPerTapEnabled) { this.setTap(true); } // this.longpressGestureHandler.attachToView(chart); } /** * Returns the correct translation depending on the provided x and y touch * points * * @param x * @param y * @return */ getTrans(x, y) { const vph = this.chart.viewPortHandler; const xTrans = x - vph.offsetLeft; const yTrans = -(vph.chartHeight - y - vph.offsetBottom); return { x: xTrans, y: yTrans }; } /** * sets the starting angle of the rotation, this is only used by the touch * listener, x and y is the touch position * * @param x * @param y */ setGestureStartAngle(x, y) { this.mStartAngle = this.chart.getAngleForPoint(x, y) - this.chart.rawRotationAngle; } /** * updates the view rotation depending on the given touch position, also * takes the starting angle into consideration * * @param x * @param y */ updateGestureRotation(x, y) { this.chart.rotationAngle = this.chart.getAngleForPoint(x, y) - this.mStartAngle; } /** * ################ ################ ################ ################ */ /** GESTURE RECOGNITION BELOW */ onRotationGesture(event) { console.log('onRotationGesture', event.data.prevState, event.data.state); const chart = this.chart; const x = event.data.extraData.x; const y = event.data.extraData.y; if (event.data.state === GestureState.BEGAN || event.data.state === GestureState.ACTIVE) { this.mLastGesture = ChartGesture.ROTATE; chart.disableScroll(); if (chart.hasListeners('rotate')) { chart.notify({ eventName: 'rotate', data: event.data, object: chart }); } if (event.data.state === GestureState.BEGAN) { this.stopDeceleration(); this.resetVelocity(); if (chart.dragDecelerationEnabled) { this.sampleVelocity(x, y); } this.setGestureStartAngle(x, y); } else { if (chart.dragDecelerationEnabled) { this.sampleVelocity(x, y); } this.updateGestureRotation(x, y); } } else if (event.data.state === GestureState.CANCELLED || event.data.state === GestureState.END) { this.mDecelerationAngularVelocity = this.calculateVelocity(); if (this.mDecelerationAngularVelocity !== 0) { this.mDecelerationLastTime = time(); // this.mDecelerationLastTime = AnimationUtils.currentAnimationTimeMillis(); // Utils.postInvalidateOnAnimation(chart); // This causes computeScroll to fire, recommended for this by Google this.chart.invalidate(); } if (chart.dragDecelerationEnabled) { this.sampleVelocity(x, y); } chart.enableScroll(); } } onRotationGestureTouch(event) { const chart = this.chart; const x = event.data.extraData.x; const y = event.data.extraData.y; if (event.data.state === GestureState.ACTIVE) { this.mLastGesture = ChartGesture.ROTATE; chart.disableScroll(); if (chart.hasListeners('rotate')) { chart.notify({ eventName: 'rotate', data: event.data, object: chart }); } if (chart.dragDecelerationEnabled) { this.sampleVelocity(x, y); } this.updateGestureRotation(x, y); this.chart.invalidate(); } } onTapGesture(event) { if (event.data.state === GestureState.END && event.data.prevState === GestureState.ACTIVE) { this.mLastGesture = ChartGesture.SINGLE_TAP; const chart = this.chart; const h = chart.getHighlightByTouchPoint(event.data.extraData.x, event.data.extraData.y); if (chart.hasListeners('tap')) { chart.notify({ eventName: 'tap', data: event.data, object: chart, highlight: h }); } if (!chart.highlightPerTapEnabled) { return; } this.performHighlight(h); } } resetVelocity() { this._velocitySamples = []; } sampleVelocity(touchLocationX, touchLocationY) { // const currentTime = AnimationUtils.currentAnimationTimeMillis(); const currentTime = time(); this._velocitySamples.push(new AngularVelocitySample(currentTime, this.chart.getAngleForPoint(touchLocationX, touchLocationY))); // Remove samples older than our sample time - 1 seconds for (let i = 0, count = this._velocitySamples.length; i < count - 2; i++) { if (currentTime - this._velocitySamples[i].time > 1000) { this._velocitySamples.shift(); i--; count--; } else { break; } } } calculateVelocity() { if (this._velocitySamples.length === 0) { return 0; } const firstSample = this._velocitySamples[0]; const lastSample = this._velocitySamples[this._velocitySamples.length - 1]; // Look for a sample that's closest to the latest sample, but not the same, so we can deduce the direction let beforeLastSample = firstSample; for (let i = this._velocitySamples.length - 1; i >= 0; i--) { beforeLastSample = this._velocitySamples[i]; if (beforeLastSample.angle !== lastSample.angle) { break; } } // Calculate the sampling time let timeDelta = (lastSample.time - firstSample.time) / 1000; if (timeDelta === 0) { timeDelta = 0.1; } // Calculate clockwise/ccw by choosing two values that should be closest to each other, // so if the angles are two far from each other we know they are inverted "for sure" let clockwise = lastSample.angle >= beforeLastSample.angle; if (Math.abs(lastSample.angle - beforeLastSample.angle) > 270.0) { clockwise = !clockwise; } // Now if the "gesture" is over a too big of an angle - then we know the angles are inverted, and we need to move them closer to each other from both sides of the 360.0 wrapping point if (lastSample.angle - firstSample.angle > 180.0) { firstSample.angle += 360.0; } else if (firstSample.angle - lastSample.angle > 180.0) { lastSample.angle += 360.0; } // The velocity let velocity = Math.abs((lastSample.angle - firstSample.angle) / timeDelta); // Direction? if (!clockwise) { velocity = -velocity; } return velocity; } /** * Sets the deceleration-angular-velocity to 0f */ stopDeceleration() { this.mDecelerationAngularVelocity = 0; } computeScroll() { if (this.mDecelerationAngularVelocity === 0) { return; } // There's no deceleration in progress // const currentTime = AnimationUtils.currentAnimationTimeMillis(); const currentTime = time(); this.mDecelerationAngularVelocity *= this.chart.dragDecelerationFrictionCoef; const timeInterval = (currentTime - this.mDecelerationLastTime) / 1000; this.chart.rotationAngle = this.chart.rotationAngle + this.mDecelerationAngularVelocity * timeInterval; this.mDecelerationLastTime = currentTime; if (Math.abs(this.mDecelerationAngularVelocity) >= 0.001) { // Utils.postInvalidateOnAnimation(this.chart); this.chart.invalidate(); } // This causes computeScroll to fire, recommended for this by Google else { this.stopDeceleration(); } } } //# sourceMappingURL=PieRadarChartTouchListener.js.map