Skip to content

Commit

Permalink
Merge pull request #1625 from maryia-deriv/maryia/v2-chart-scrolling-…
Browse files Browse the repository at this point in the history
…issue

[DTRA] Maryia/DTRA-1559/feat: add isVerticalScrollEnabled prop to disable vertical scroll outside Y-axis
  • Loading branch information
balakrishna-deriv authored Aug 8, 2024
2 parents 718449b + ffa0e62 commit ba6908f
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ Props marked with `*` are **mandatory**:
| isConnectionOpened | Sets the connection status. If set, upon reconnection smartcharts will either patch missing tick data or refresh the chart, depending on granularity; if not set, it is assumed that connection is always opened. Defaults to `undefined`. |
| onMessage | SmartCharts will send notifications via this callback, should it be provided. Each notification will have the following structure: `{ text, type, category }`. |
| isAnimationEnabled | Determine whether chart animation is enabled or disabled. It may needs to be disabled for better performance. Defaults to `true`. |
| isVerticalScrollEnabled | Determine whether verticall scroll on the chart outside Y-axis is disabled. It may need to be disabled for mobile app version to scroll the page up or down instead of the chart. Defaults to `true`. |
| showLastDigitStats | Shows last digits stats. Defaults to `false`. |
| scrollToEpoch | Scrolls the chart to the leftmost side and sets the last spot/bar as the first visible spot/bar in the chart. Also, it disables scrolling until the chart reaches the 3/4 of the width of the main pane of the chart. Defaults to `null`. |
|
Expand Down
109 changes: 99 additions & 10 deletions src/store/ChartAdapterStore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { action, makeObservable, observable, when, runInAction } from 'mobx';
import { action, makeObservable, observable, when, runInAction, computed } from 'mobx';
import moment from 'moment';
import debounce from 'lodash.debounce';
import { TFlutterChart, TLoadHistoryParams, TQuote } from 'src/types';
Expand Down Expand Up @@ -40,22 +40,34 @@ export default class ChartAdapterStore {
yLocal: 0,
bottomIndex: 0,
};
touchValues: {
x?: number;
y?: number;
yOnTouchEnd?: number;
} = {};

isOverFlutterCharts = false;
enableVerticalScrollTimer?: ReturnType<typeof setTimeout>;
scrollChartParentOnTouchTimer?: ReturnType<typeof setTimeout>;

constructor(mainStore: MainStore) {
makeObservable(this, {
onMount: action.bound,
onTickHistory: action.bound,
onTouch: action.bound,
onChartLoad: action.bound,
onTick: action.bound,
loadHistory: action.bound,
onVisibleAreaChanged: action.bound,
onQuoteAreaChanged: action.bound,
setMsPerPx: action.bound,
newChart: action.bound,
enableVerticalScrollTimer: observable,
scale: action.bound,
scrollableChartParent: computed,
scrollChartParentOnTouchTimer: observable,
toggleDataFitMode: action.bound,
touchValues: observable,
onCrosshairMove: action.bound,
isDataFitModeEnabled: observable,
isChartLoaded: observable,
Expand Down Expand Up @@ -190,6 +202,9 @@ export default class ChartAdapterStore {
}

window.flutterChartElement?.addEventListener('wheel', this.onWheel, { capture: true });
window.flutterChartElement?.addEventListener('touchstart', this.onTouch, { capture: true });
window.flutterChartElement?.addEventListener('touchmove', this.onTouch, { capture: true });
window.flutterChartElement?.addEventListener('touchend', this.onTouch, { capture: true });
window.flutterChartElement?.addEventListener('dblclick', this.onDoubleClick, { capture: true });
window.addEventListener('mousemove', this.onMouseMove, { capture: true });
}
Expand All @@ -198,8 +213,13 @@ export default class ChartAdapterStore {
window._flutter.initState.isMounted = false;

window.flutterChartElement?.removeEventListener('wheel', this.onWheel, { capture: true });
window.flutterChartElement?.removeEventListener('touchstart', this.onTouch, { capture: true });
window.flutterChartElement?.removeEventListener('touchmove', this.onTouch, { capture: true });
window.flutterChartElement?.removeEventListener('touchend', this.onTouch, { capture: true });
window.flutterChartElement?.removeEventListener('dblclick', this.onDoubleClick, { capture: true });
window.removeEventListener('mousemove', this.onMouseMove, { capture: true });
clearTimeout(this.enableVerticalScrollTimer);
clearTimeout(this.scrollChartParentOnTouchTimer);
}

onChartLoad() {
Expand All @@ -216,15 +236,70 @@ export default class ChartAdapterStore {
}
}

onTouch(e: TouchEvent) {
const chartNode = this.mainStore.chart.chartNode;
// Prevent vertical scroll on the chart for touch devices by forcing scroll on a scrollable parent of the chart:
if (
chartNode &&
this.scrollableChartParent &&
!this.mainStore.state.isVerticalScrollEnabled &&
e.touches.length === 1
) {
const { pageX, screenX, screenY } = e.touches[0];
if (['touchstart', 'touchend'].includes(e.type)) {
this.touchValues = e.type === 'touchstart' ? { x: screenX, y: screenY } : { yOnTouchEnd: screenY };
} else if (e.type === 'touchmove') {
const nonScrollableAreaWidth = chartNode.offsetWidth - this.mainStore.chart.yAxisWidth;
const { left } = chartNode.getBoundingClientRect();

if (this.touchValues.x && this.touchValues.y) {
const deltaX = Math.abs(screenX - this.touchValues.x);
const deltaY = Math.abs(screenY - this.touchValues.y);
const isVerticalScroll = deltaY > deltaX;
const x = pageX - left;
if (x < nonScrollableAreaWidth && isVerticalScroll && !this.scrollChartParentOnTouchTimer) {
this.touchValues.yOnTouchEnd = undefined;
this.scrollChartParentOnTouchTimer = setTimeout(() => {
this.scrollableChartParent?.scrollBy({
top: screenY - Number(this.touchValues.yOnTouchEnd ?? this.touchValues.y),
behavior: 'smooth',
});
this.scrollChartParentOnTouchTimer = undefined;
}, 300);
}
}
this.touchValues = { x: screenX, y: screenY };
}
}
}

onWheel = (e: WheelEvent) => {
e.preventDefault();

// Prevent vertical scroll on the chart on wheel devices by disabling pointer events to make chart parent scrollable:
const chartNode = this.mainStore.chart.chartNode;
if (chartNode && !this.mainStore.state.isVerticalScrollEnabled) {
const nonScrollableAreaWidth = chartNode.offsetWidth - this.mainStore.chart.yAxisWidth;
const { left } = chartNode.getBoundingClientRect();
const isVerticalScroll = e.deltaY && e.deltaX === 0;
const x = e.pageX - left;
if (x < nonScrollableAreaWidth && isVerticalScroll) {
if (this.enableVerticalScrollTimer) return;
chartNode.style.pointerEvents = 'none';
this.enableVerticalScrollTimer = setTimeout(() => {
chartNode.style.pointerEvents = 'auto';
this.enableVerticalScrollTimer = undefined;
}, 300);
return;
}
}

if (e.deltaX === 0 && e.deltaZ === 0) {
const value = (100 - Math.min(10, Math.max(-10, e.deltaY))) / 100;
this.scale(value);
} else {
window.flutterChart?.app.scroll(e.deltaX);
}

return false;
};

Expand Down Expand Up @@ -439,24 +514,24 @@ export default class ChartAdapterStore {
let delta_x, delta_y, ratio;

// Here we interpolate the pixel distance between two adjacent ticks.
if (bar && bar.DT! < date) {
if (bar && (bar.DT as Date) < date) {
const barNext = this.mainStore.chart.feed?.quotes[tickIdx + 1];
const barPrev = tickIdx > 0 ? this.mainStore.chart.feed?.quotes[tickIdx - 1] : null;

if (barNext && barNext.Close && barNext.DT! > date) {
delta_x = this.getXFromEpoch(barNext.DT!.getTime()) - x;
if (barNext && barNext.Close && (barNext.DT as Date) > date) {
delta_x = this.getXFromEpoch((barNext.DT as Date).getTime()) - x;

ratio =
(((date as unknown) as number) - bar.DT!.getTime()) /
(barNext.DT!.getTime() - bar.DT!.getTime());
((date as unknown as number) - (bar.DT as Date).getTime()) /
((barNext.DT as Date).getTime() - (bar.DT as Date).getTime());

if (price) delta_y = barNext.Close - price;
} else if (barPrev && barPrev.Close) {
delta_x = x - this.getXFromEpoch(barPrev.DT!.getTime());
delta_x = x - this.getXFromEpoch((barPrev.DT as Date).getTime());

ratio =
(((date as unknown) as number) - bar.DT!.getTime()) /
(bar.DT!.getTime() - barPrev.DT!.getTime());
((date as unknown as number) - (bar.DT as Date).getTime()) /
((bar.DT as Date).getTime() - (barPrev.DT as Date).getTime());

if (price) delta_y = price - barPrev.Close;
}
Expand Down Expand Up @@ -499,4 +574,18 @@ export default class ChartAdapterStore {
getQuoteFromY(y: number) {
return this.flutterChart?.app.getQuoteFromY(y) ?? 0;
}

get scrollableChartParent() {
const chartNode = this.mainStore.chart.chartNode;
if (!chartNode) return undefined;
let parent = chartNode.parentElement;
while (parent) {
const { overflow } = window.getComputedStyle(parent);
if (overflow.split(' ').every(o => o === 'auto' || o === 'scroll')) {
return parent;
}
parent = parent.parentElement;
}
return document.documentElement;
}
}
4 changes: 4 additions & 0 deletions src/store/ChartState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class ChartState {
shouldDrawTicksFromContractInfo? = false;
has_updated_settings = false;
isAnimationEnabled?: boolean;
isVerticalScrollEnabled? = true;
mainStore: MainStore;
margin?: number;
granularity: TGranularity;
Expand Down Expand Up @@ -108,6 +109,7 @@ class ChartState {
heightFactor: observable,
isConnectionOpened: observable,
isChartReady: observable,
isVerticalScrollEnabled: observable,
chartStatusListener: observable,
debouncedStateChange: action.bound,
stateChangeListener: observable,
Expand Down Expand Up @@ -171,6 +173,7 @@ class ChartState {
isAnimationEnabled = true,
isConnectionOpened,
isStaticChart,
isVerticalScrollEnabled = true,
granularity,
margin = 0,
refreshActiveSymbols,
Expand Down Expand Up @@ -223,6 +226,7 @@ class ChartState {
this.isAnimationEnabled = isAnimationEnabled;
this.isConnectionOpened = isConnectionOpened;
this.isStaticChart = isStaticChart;
this.isVerticalScrollEnabled = isVerticalScrollEnabled;
this.margin = margin;
this.has_updated_settings = !isDeepEqual(this.settings?.whitespace, settings?.whitespace);
this.settings = settings;
Expand Down
1 change: 1 addition & 0 deletions src/types/props.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export type TChartProps = {
isConnectionOpened?: boolean;
onMessage?: (message: TNotification) => void;
isAnimationEnabled?: boolean;
isVerticalScrollEnabled?: boolean;
showLastDigitStats?: boolean;
scrollToEpoch?: number | null;
clearChart?: () => void;
Expand Down

0 comments on commit ba6908f

Please sign in to comment.