Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DTRA] Maryia/DTRA-1559/feat: add isVerticalScrollEnabled prop to disable vertical scroll outside Y-axis #1625

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just fixing 'Forbidden non-null assertion' TS warning

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
Loading