@oanda/labs-crowd-view-widget 1.0.35 → 1.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +300 -0
  2. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +38 -74
  3. package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js +3 -18
  5. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/getOption.js +29 -53
  7. package/dist/main/CrowdViewWidget/components/Chart/getOption.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/getOrderPositionDataMock.js +47 -0
  9. package/dist/main/CrowdViewWidget/components/Chart/getOrderPositionDataMock.js.map +1 -0
  10. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  11. package/dist/main/CrowdViewWidget/components/Chart/utils.js +24 -22
  12. package/dist/main/CrowdViewWidget/components/Chart/utils.js.map +1 -1
  13. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +39 -76
  14. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  15. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +3 -18
  16. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  17. package/dist/module/CrowdViewWidget/components/Chart/getOption.js +29 -52
  18. package/dist/module/CrowdViewWidget/components/Chart/getOption.js.map +1 -1
  19. package/dist/module/CrowdViewWidget/components/Chart/getOrderPositionDataMock.js +40 -0
  20. package/dist/module/CrowdViewWidget/components/Chart/getOrderPositionDataMock.js.map +1 -0
  21. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  22. package/dist/module/CrowdViewWidget/components/Chart/utils.js +24 -22
  23. package/dist/module/CrowdViewWidget/components/Chart/utils.js.map +1 -1
  24. package/dist/types/CrowdViewWidget/components/Chart/getOption.d.ts +1 -114
  25. package/dist/types/CrowdViewWidget/components/Chart/getOrderPositionDataMock.d.ts +14 -0
  26. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +1 -6
  27. package/package.json +3 -3
  28. package/src/CrowdViewWidget/components/Chart/Chart.tsx +32 -90
  29. package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +5 -22
  30. package/src/CrowdViewWidget/components/Chart/getOption.ts +45 -70
  31. package/src/CrowdViewWidget/components/Chart/getOrderPositionDataMock.ts +66 -0
  32. package/src/CrowdViewWidget/components/Chart/types.ts +4 -6
  33. package/src/CrowdViewWidget/components/Chart/utils.ts +23 -20
@@ -1,10 +1,10 @@
1
- import type { BaseChartRef } from '@oanda/labs-widget-common';
2
1
  import {
3
2
  BaseChart,
4
3
  getChartTheme,
5
4
  Theme,
6
5
  useLayoutProvider,
7
6
  } from '@oanda/labs-widget-common';
7
+ import type { EChartsType } from 'echarts';
8
8
  import { BarChart, CandlestickChart, CustomChart } from 'echarts/charts';
9
9
  import {
10
10
  DataZoomInsideComponent,
@@ -15,10 +15,10 @@ import {
15
15
  } from 'echarts/components';
16
16
  import * as echarts from 'echarts/core';
17
17
  import { CanvasRenderer } from 'echarts/renderers';
18
- import React, { useEffect, useRef } from 'react';
18
+ import React from 'react';
19
19
 
20
- import { CHART_HEIGHT, MAX_LABELS_COUNT } from './constants';
21
- import { getDesktopOption, getOption } from './getOption';
20
+ import { CHART_HEIGHT } from './constants';
21
+ import { getOption } from './getOption';
22
22
  import type { ChartProps } from './types';
23
23
  import { getLabelData, isDifferenceGreaterThanTwoWeeks } from './utils';
24
24
 
@@ -39,77 +39,33 @@ echarts.registerTheme('light_theme', getChartTheme(Theme.Light));
39
39
 
40
40
  const Chart = ({ data }: ChartProps) => {
41
41
  const { isDark } = useLayoutProvider();
42
- const echartRef = useRef<BaseChartRef | null>(null);
43
42
 
44
- useEffect(() => {
45
- if (echartRef.current) {
46
- const echartInstance = echartRef.current.getEchartsInstance();
47
-
48
- echartInstance.setOption(getDesktopOption({ isDark }));
49
-
50
- echartInstance.off('datazoom');
51
- echartInstance.on('datazoom', () => {
52
- const {
53
- dataZoom,
54
- // @ts-ignore
55
- title: { text },
56
- } = echartInstance.getOption();
57
-
58
- const { start, end } = (
59
- dataZoom as { start: number; end: number }[]
60
- )[0];
61
- const prevInterval = Number((text as string).split(',')[0]);
62
- const prevIsGreaterThanTwoWeeks = Boolean(
63
- Number((text as string).split(',')[1])
64
- );
65
-
66
- const visibleItemsCount = Math.round(
67
- (data.xAxisData.length * (end - start)) / 100
68
- );
69
-
70
- const interval =
71
- visibleItemsCount > MAX_LABELS_COUNT
72
- ? Math.floor(visibleItemsCount / MAX_LABELS_COUNT)
73
- : 0;
74
-
75
- if (interval !== prevInterval) {
76
- echartInstance.setOption({
77
- xAxis: {
78
- axisLabel: {
79
- interval,
80
- },
81
- },
82
- });
83
- }
43
+ return (
44
+ <BaseChart
45
+ chartHeight={CHART_HEIGHT}
46
+ echarts={echarts}
47
+ isDark={isDark}
48
+ lazyUpdate={true}
49
+ option={getOption(data, isDark)}
50
+ onEvents={{
51
+ datazoom: (_params: unknown, instance: EChartsType) => {
52
+ const { dataZoom } = instance.getOption();
84
53
 
85
- const visibleXAxisData = data.xAxisData.slice(
86
- (data.xAxisData.length * start) / 100,
87
- (data.xAxisData.length * end) / 100
88
- );
54
+ const { startValue, endValue } = (
55
+ dataZoom as { startValue: number; endValue: number }[]
56
+ )[0];
89
57
 
90
- const isGreaterThanTwoWeeks = isDifferenceGreaterThanTwoWeeks(
91
- visibleXAxisData[0],
92
- visibleXAxisData[visibleXAxisData.length - 1]
93
- );
58
+ const isGreaterThanTwoWeeks = isDifferenceGreaterThanTwoWeeks(
59
+ data.xAxisData[startValue],
60
+ data.xAxisData[endValue]
61
+ );
94
62
 
95
- if (isGreaterThanTwoWeeks !== prevIsGreaterThanTwoWeeks) {
96
63
  const labelsData = getLabelData({
97
64
  xAxisData: data.xAxisData,
98
65
  isGreaterThanTwoWeeks,
99
66
  });
100
67
 
101
- echartInstance.setOption({
102
- series: [
103
- {
104
- id: 'candlestick',
105
- markPoint: {
106
- data: labelsData,
107
- },
108
- },
109
- ],
110
- });
111
-
112
- echartInstance.setOption({
68
+ instance.setOption({
113
69
  xAxis: {
114
70
  axisLabel: {
115
71
  formatter: (value: string) => {
@@ -125,31 +81,17 @@ const Chart = ({ data }: ChartProps) => {
125
81
  },
126
82
  },
127
83
  },
84
+ series: [
85
+ {
86
+ id: 'candlestick',
87
+ markPoint: {
88
+ data: labelsData,
89
+ },
90
+ },
91
+ ],
128
92
  });
129
- }
130
-
131
- if (
132
- interval !== prevInterval ||
133
- isGreaterThanTwoWeeks !== prevIsGreaterThanTwoWeeks
134
- ) {
135
- echartInstance.setOption({
136
- title: {
137
- text: `${interval},${isGreaterThanTwoWeeks ? 1 : 0}`,
138
- },
139
- });
140
- }
141
- });
142
- }
143
- }, [echartRef, isDark, data]);
144
-
145
- return (
146
- <BaseChart
147
- ref={echartRef}
148
- chartHeight={CHART_HEIGHT}
149
- echarts={echarts}
150
- isDark={isDark}
151
- lazyUpdate={true}
152
- option={getOption(data)}
93
+ },
94
+ }}
153
95
  />
154
96
  );
155
97
  };
@@ -1,8 +1,3 @@
1
- import { useQuery } from '@apollo/client';
2
- import type {
3
- GetOrderPositionBooksQuery,
4
- GetOrderPositionBooksQueryVariables,
5
- } from '@oanda/labs-order-book-widget/src/gql/types/graphql';
6
1
  import {
7
2
  ChartError,
8
3
  Size,
@@ -13,7 +8,6 @@ import {
13
8
  import classnames from 'classnames';
14
9
  import React, { useMemo } from 'react';
15
10
 
16
- import { getOrderPositionBooks } from '../../../gql/getOrderPositionBooks';
17
11
  import { Chart } from './Chart';
18
12
  import type { ChartWithDataProps } from './types';
19
13
  import { transformDataForChart } from './utils';
@@ -26,24 +20,13 @@ const ChartWithData = ({
26
20
  const { size } = useLayoutProvider();
27
21
  const isDesktop = size === Size.DESKTOP;
28
22
 
29
- const { loading, data, error } = useQuery<
30
- GetOrderPositionBooksQuery,
31
- GetOrderPositionBooksQueryVariables
32
- >(getOrderPositionBooks, {
33
- variables: {
34
- instrument,
35
- bookType,
36
- recentHours: 1,
37
- },
38
- fetchPolicy: 'network-only',
39
- });
40
-
41
- const isError = !!error;
23
+ const isError = false;
24
+ const loading = false;
42
25
 
43
26
  const transformedData = useMemo(() => {
44
- if (!data) return undefined;
45
- return transformDataForChart({ data, granularity });
46
- }, [data, granularity]);
27
+ return transformDataForChart({ granularity });
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps
29
+ }, [instrument, granularity, bookType]);
47
30
 
48
31
  return (
49
32
  <>
@@ -10,70 +10,26 @@ import {
10
10
  CHART_WIDTH,
11
11
  INITIAL_END_ZOOM,
12
12
  INITIAL_START_ZOOM,
13
- MAX_LABELS_COUNT,
14
13
  X_LABEL_SIZE,
15
14
  Y_LABEL_SIZE_DESKTOP,
16
15
  } from './constants';
17
- import type { GetOptionType, GetResponsiveOptionsProps } from './types';
16
+ import type { GetOptionType } from './types';
18
17
  import {
19
18
  getLabelData,
20
19
  getRectColor,
21
20
  isDifferenceGreaterThanTwoWeeks,
22
21
  } from './utils';
23
22
 
24
- export const getDesktopOption = ({ isDark }: GetResponsiveOptionsProps) => {
25
- const desktopGridLines = getGridLines({
26
- isDark,
27
- chartWidth: CHART_WIDTH,
28
- chartHeight: CHART_HEIGHT,
29
- xLabelsSize: X_LABEL_SIZE,
30
- yLabelSize: Y_LABEL_SIZE_DESKTOP,
31
- bottomLeftBox: false,
32
- });
33
-
34
- return {
35
- yAxis: {
36
- nameTextStyle: {
37
- align: 'left',
38
- padding: [0, 0, 0, 8],
39
- },
40
- axisLabel: {
41
- margin: 8,
42
- },
43
- },
44
- grid: [
45
- {
46
- name: 'main-grid',
47
- top: '0px',
48
- left: '0px',
49
- right: `${Y_LABEL_SIZE_DESKTOP}px`,
50
- bottom: `${X_LABEL_SIZE}px`,
51
- },
52
- ],
53
- graphic: [
54
- ...desktopGridLines,
55
- {
56
- ...getLineCommons(isDark),
57
- top: 0,
58
- right: 0,
59
- shape: {
60
- x1: 0,
61
- y1: 0,
62
- x2: 0,
63
- y2: 0,
64
- },
65
- },
66
- ],
67
- };
68
- };
69
-
70
- // @ts-ignore
71
- export const getOption: GetOptionType = ({
72
- xAxisData,
73
- ordersPositionsChartData,
74
- ordersPositionsBucketWidth,
75
- candlesSeriesData,
76
- }) => {
23
+ // @ts-expect-error
24
+ export const getOption: GetOptionType = (
25
+ {
26
+ xAxisData,
27
+ ordersPositionsChartData,
28
+ ordersPositionsBucketWidth,
29
+ candlesSeriesData,
30
+ },
31
+ isDark
32
+ ) => {
77
33
  const visibleXAxisData = xAxisData.slice(
78
34
  (xAxisData.length * INITIAL_START_ZOOM) / 100,
79
35
  (xAxisData.length * INITIAL_END_ZOOM) / 100
@@ -83,25 +39,22 @@ export const getOption: GetOptionType = ({
83
39
  visibleXAxisData[0],
84
40
  visibleXAxisData[visibleXAxisData.length - 1]
85
41
  );
42
+
86
43
  const labelsData = getLabelData({
87
44
  xAxisData,
88
45
  isGreaterThanTwoWeeks,
89
46
  });
90
47
 
91
- const visibleItemsCount = Math.round(
92
- (xAxisData.length * (INITIAL_END_ZOOM - INITIAL_START_ZOOM)) / 100
93
- );
94
-
95
- const initialInterval =
96
- visibleItemsCount > MAX_LABELS_COUNT
97
- ? Math.floor(visibleItemsCount / MAX_LABELS_COUNT)
98
- : 0;
48
+ const gridLines = getGridLines({
49
+ isDark,
50
+ chartWidth: CHART_WIDTH,
51
+ chartHeight: CHART_HEIGHT,
52
+ xLabelsSize: X_LABEL_SIZE,
53
+ yLabelSize: Y_LABEL_SIZE_DESKTOP,
54
+ bottomLeftBox: false,
55
+ });
99
56
 
100
57
  return {
101
- title: {
102
- show: false,
103
- text: `${initialInterval},${isGreaterThanTwoWeeks}`,
104
- },
105
58
  animation: false,
106
59
  dataZoom: [
107
60
  {
@@ -114,7 +67,7 @@ export const getOption: GetOptionType = ({
114
67
  tooltip: {
115
68
  trigger: 'axis',
116
69
  formatter: (val) => {
117
- // @ts-ignore
70
+ // @ts-expect-error
118
71
  const timestamp = val[0].axisValue as string;
119
72
 
120
73
  return `${new Date(timestamp).toLocaleTimeString(undefined, {
@@ -137,9 +90,8 @@ export const getOption: GetOptionType = ({
137
90
  axisLabel: {
138
91
  padding: [8, 16, 8, 16],
139
92
  margin: 0,
140
- interval: initialInterval,
141
93
  formatter: (value) => {
142
- const date = new Date(value);
94
+ const date = new Date(value as string);
143
95
  return isGreaterThanTwoWeeks
144
96
  ? `${date.toLocaleTimeString(undefined, {
145
97
  hour: '2-digit',
@@ -221,5 +173,28 @@ export const getOption: GetOptionType = ({
221
173
  data: ordersPositionsChartData,
222
174
  },
223
175
  ],
176
+ grid: [
177
+ {
178
+ name: 'main-grid',
179
+ top: '0px',
180
+ left: '0px',
181
+ right: `${Y_LABEL_SIZE_DESKTOP}px`,
182
+ bottom: `${X_LABEL_SIZE}px`,
183
+ },
184
+ ],
185
+ graphic: [
186
+ ...gridLines,
187
+ {
188
+ ...getLineCommons(isDark as boolean),
189
+ top: 0,
190
+ right: 0,
191
+ shape: {
192
+ x1: 0,
193
+ y1: 0,
194
+ x2: 0,
195
+ y2: 0,
196
+ },
197
+ },
198
+ ],
224
199
  };
225
200
  };
@@ -0,0 +1,66 @@
1
+ export interface OrderPositionData {
2
+ shortCountPercent: number;
3
+ longCountPercent: number;
4
+ price: number;
5
+ }
6
+
7
+ interface GetOrderPositionDataMockParams {
8
+ min?: number;
9
+ max?: number;
10
+ bucketWidth?: number;
11
+ baseValue: number;
12
+ crossoverValue?: number;
13
+ }
14
+
15
+ export const getOrderPositionDataMock = ({
16
+ min = 1.3,
17
+ max = 2.1,
18
+ bucketWidth = 0.005,
19
+ baseValue,
20
+ crossoverValue = baseValue,
21
+ }: GetOrderPositionDataMockParams): OrderPositionData[] => {
22
+ const results: OrderPositionData[] = [];
23
+
24
+ const maxDistance = Math.max(baseValue - min, max - baseValue);
25
+
26
+ for (
27
+ let currentPrice = min;
28
+ currentPrice < max;
29
+ currentPrice += bucketWidth
30
+ ) {
31
+ const distance = Math.abs(currentPrice - baseValue);
32
+
33
+ const proximityFactor = 1 - distance / maxDistance;
34
+ const strongProximityFactor = Math.pow(proximityFactor, 2);
35
+
36
+ let shortCountPercent: number;
37
+ let longCountPercent: number;
38
+
39
+ if (currentPrice === baseValue) {
40
+ shortCountPercent = Math.random() * 0.5;
41
+ longCountPercent = Math.random() * 0.5;
42
+ } else {
43
+ const maxDifference = 0.5;
44
+ const targetDifference =
45
+ Math.random() * maxDifference * strongProximityFactor;
46
+
47
+ const baseRandom = Math.random() * (0.5 - targetDifference);
48
+
49
+ if (currentPrice < crossoverValue) {
50
+ shortCountPercent = baseRandom;
51
+ longCountPercent = baseRandom + targetDifference;
52
+ } else {
53
+ longCountPercent = baseRandom;
54
+ shortCountPercent = baseRandom + targetDifference;
55
+ }
56
+ }
57
+
58
+ results.push({
59
+ shortCountPercent: Math.round(shortCountPercent * 10000) / 10000,
60
+ longCountPercent: Math.round(longCountPercent * 10000) / 10000,
61
+ price: currentPrice,
62
+ });
63
+ }
64
+
65
+ return results;
66
+ };
@@ -1,4 +1,3 @@
1
- import type { GetOrderPositionBooksQuery } from '@oanda/labs-order-book-widget/src/gql/types/graphql';
2
1
  import type { EChartsOption } from 'echarts';
3
2
 
4
3
  import type { BookType } from '../../../gql/types/graphql';
@@ -18,10 +17,10 @@ interface ChartOptionsProps {
18
17
  candlesSeriesData: [number, number, number, number][];
19
18
  }
20
19
 
21
- export type GetOptionType = (props: ChartOptionsProps) => EChartsOption;
22
- export interface GetResponsiveOptionsProps {
23
- isDark: boolean;
24
- }
20
+ export type GetOptionType = (
21
+ props: ChartOptionsProps,
22
+ isDark: boolean
23
+ ) => EChartsOption;
25
24
 
26
25
  export interface ChartProps {
27
26
  data: ChartOptionsProps;
@@ -34,7 +33,6 @@ export interface ChartWithDataProps {
34
33
  }
35
34
 
36
35
  interface TransformDataForChartTypeProps {
37
- data: GetOrderPositionBooksQuery;
38
36
  granularity: GranularityId;
39
37
  }
40
38
 
@@ -1,7 +1,7 @@
1
- import type { GetOrderPositionBooksQuery } from '../../../gql/types/graphql';
2
1
  import { colorMap } from '../../config';
3
2
  import { TimeSpanId } from '../../types';
4
3
  import { CHART_HEIGHT } from './constants';
4
+ import { getOrderPositionDataMock } from './getOrderPositionDataMock';
5
5
  import { getPriceCandlesMock } from './getPriceCandlesMock';
6
6
  import type { GetLabelsDataProps, TransformDataForChartType } from './types';
7
7
 
@@ -70,17 +70,14 @@ const temporaryGranularityTimeSpanMap: Record<GranularityId, TimeSpanId> = {
70
70
  [GranularityId.DAY_1]: TimeSpanId.MONTH_6,
71
71
  };
72
72
 
73
- const mockOrderPositionDataForHistoricalData = (
74
- data: GetOrderPositionBooksQuery,
75
- granularityId: GranularityId
76
- ) => {
73
+ const mockXAxisData = (granularityId: GranularityId) => {
77
74
  const granularity = granularityInMilliseconds[granularityId];
78
75
  const timeSpan =
79
76
  timeSpansInMilliseconds[temporaryGranularityTimeSpanMap[granularityId]];
80
77
 
81
- const currentTime = data.orderPositionBooks[0]!.time;
78
+ const currentTime = new Date();
82
79
 
83
- const startDate = new Date(currentTime as string);
80
+ const startDate = currentTime;
84
81
  const totalSteps = timeSpan / granularity;
85
82
 
86
83
  const orderPositionDataForHistorical = new Array(totalSteps)
@@ -88,11 +85,7 @@ const mockOrderPositionDataForHistoricalData = (
88
85
  .map((_, index) => {
89
86
  const newDate = new Date(startDate.getTime() - index * granularity);
90
87
 
91
- return {
92
- time: `${newDate.toISOString().slice(0, 19)}Z`,
93
- buckets: data.orderPositionBooks[0]!.buckets,
94
- bucketWidth: data.orderPositionBooks[0]!.bucketWidth,
95
- };
88
+ return `${newDate.toISOString().slice(0, 19)}Z`;
96
89
  })
97
90
  .reverse();
98
91
 
@@ -151,13 +144,25 @@ export const isDifferenceGreaterThanTwoWeeks = (
151
144
  };
152
145
 
153
146
  export const transformDataForChart: TransformDataForChartType = ({
154
- data,
155
147
  granularity,
156
148
  }) => {
157
- const mockedData = mockOrderPositionDataForHistoricalData(data, granularity);
149
+ const bucketWidth = 0.005;
150
+ const mockedXAxisData = mockXAxisData(granularity);
151
+ const mockedCandles = getPriceCandlesMock(mockedXAxisData.length);
152
+
153
+ const orderPositionData = mockedCandles.map((item, index) => {
154
+ return {
155
+ time: mockedXAxisData[index],
156
+ buckets: getOrderPositionDataMock({
157
+ baseValue: item.high,
158
+ bucketWidth,
159
+ crossoverValue: item.high + (Math.random() - 0.5) * 0.3,
160
+ }),
161
+ bucketWidth: 0.005,
162
+ };
163
+ });
158
164
 
159
- const xAxisData = mockedData.map(({ time }) => time);
160
- const ordersPositionsChartData = mockedData
165
+ const ordersPositionsChartData = orderPositionData
161
166
  .flatMap(
162
167
  ({ buckets, time }) =>
163
168
  buckets.map((bucket) => [
@@ -168,8 +173,6 @@ export const transformDataForChart: TransformDataForChartType = ({
168
173
  )
169
174
  .filter((item) => Math.abs(item[2]) > 0.15);
170
175
 
171
- const mockedCandles = getPriceCandlesMock(xAxisData.length);
172
-
173
176
  const candlesSeriesData: [number, number, number, number][] =
174
177
  mockedCandles.map(({ close, open, low, high }) => [
175
178
  +close.toFixed(5),
@@ -179,9 +182,9 @@ export const transformDataForChart: TransformDataForChartType = ({
179
182
  ]);
180
183
 
181
184
  return {
182
- xAxisData,
185
+ xAxisData: mockedXAxisData,
183
186
  ordersPositionsChartData,
184
- ordersPositionsBucketWidth: mockedData[0]!.bucketWidth,
187
+ ordersPositionsBucketWidth: bucketWidth,
185
188
  candlesSeriesData,
186
189
  granularity,
187
190
  };