@oanda/labs-crowd-view-widget 1.0.55 → 1.0.57

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 (129) hide show
  1. package/CHANGELOG.md +460 -0
  2. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +8 -10
  3. package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js +15 -3
  5. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js +29 -14
  7. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js +28 -9
  9. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js.map +1 -1
  10. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js +20 -8
  11. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js.map +1 -1
  12. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js +21 -16
  13. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js.map +1 -1
  14. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js +29 -6
  15. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js.map +1 -1
  16. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js +24 -4
  17. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js.map +1 -1
  18. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatFullDate.js +27 -0
  19. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatFullDate.js.map +1 -0
  20. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js +2 -2
  21. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js.map +1 -1
  22. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.js +6 -5
  23. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.js.map +1 -1
  24. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js +71 -22
  25. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js.map +1 -1
  26. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js +3 -3
  27. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js.map +1 -1
  28. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/index.js +22 -0
  29. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/index.js.map +1 -1
  30. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.js +11 -0
  31. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.js.map +1 -0
  32. package/dist/main/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.js +1 -1
  33. package/dist/main/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.js.map +1 -1
  34. package/dist/main/CrowdViewWidget/components/Chart/getOption.js +11 -5
  35. package/dist/main/CrowdViewWidget/components/Chart/getOption.js.map +1 -1
  36. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  37. package/dist/main/CrowdViewWidget/components/Chart/useResizeObserver.js +47 -0
  38. package/dist/main/CrowdViewWidget/components/Chart/useResizeObserver.js.map +1 -0
  39. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js +1 -1
  40. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  41. package/dist/main/CrowdViewWidget/constants.js +22 -8
  42. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  43. package/dist/main/CrowdViewWidget/selectConfig.js +3 -3
  44. package/dist/main/CrowdViewWidget/selectConfig.js.map +1 -1
  45. package/dist/main/translations/sources/en.json +11 -9
  46. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +9 -11
  47. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  48. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +15 -3
  49. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  50. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js +29 -14
  51. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js.map +1 -1
  52. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js +28 -9
  53. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js.map +1 -1
  54. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js +20 -8
  55. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js.map +1 -1
  56. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js +21 -16
  57. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js.map +1 -1
  58. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js +30 -7
  59. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js.map +1 -1
  60. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js +25 -5
  61. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js.map +1 -1
  62. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatFullDate.js +20 -0
  63. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatFullDate.js.map +1 -0
  64. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js +2 -2
  65. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js.map +1 -1
  66. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.js +6 -5
  67. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.js.map +1 -1
  68. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js +71 -22
  69. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js.map +1 -1
  70. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js +3 -3
  71. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js.map +1 -1
  72. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/index.js +2 -0
  73. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/index.js.map +1 -1
  74. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.js +4 -0
  75. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.js.map +1 -0
  76. package/dist/module/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.js +1 -1
  77. package/dist/module/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.js.map +1 -1
  78. package/dist/module/CrowdViewWidget/components/Chart/getOption.js +11 -5
  79. package/dist/module/CrowdViewWidget/components/Chart/getOption.js.map +1 -1
  80. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  81. package/dist/module/CrowdViewWidget/components/Chart/useResizeObserver.js +40 -0
  82. package/dist/module/CrowdViewWidget/components/Chart/useResizeObserver.js.map +1 -0
  83. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js +1 -1
  84. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  85. package/dist/module/CrowdViewWidget/constants.js +22 -8
  86. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  87. package/dist/module/CrowdViewWidget/selectConfig.js +3 -3
  88. package/dist/module/CrowdViewWidget/selectConfig.js.map +1 -1
  89. package/dist/module/translations/sources/en.json +11 -9
  90. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.d.ts +4 -2
  91. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.d.ts +3 -1
  92. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.d.ts +4 -1
  93. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/formatFullDate.d.ts +1 -0
  94. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.d.ts +1 -1
  95. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.d.ts +1 -1
  96. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.d.ts +4 -1
  97. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.d.ts +1 -1
  98. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/index.d.ts +2 -0
  99. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.d.ts +1 -0
  100. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +1 -0
  101. package/dist/types/CrowdViewWidget/components/Chart/useResizeObserver.d.ts +5 -0
  102. package/dist/types/CrowdViewWidget/constants.d.ts +25 -19
  103. package/package.json +3 -3
  104. package/src/CrowdViewWidget/components/Chart/Chart.tsx +12 -11
  105. package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +22 -3
  106. package/src/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.ts +35 -19
  107. package/src/CrowdViewWidget/components/Chart/chartOptions/getGridLines.ts +41 -11
  108. package/src/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.ts +31 -11
  109. package/src/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.ts +24 -22
  110. package/src/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.ts +36 -5
  111. package/src/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.ts +29 -6
  112. package/src/CrowdViewWidget/components/Chart/chartUtils/formatFullDate.ts +26 -0
  113. package/src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.ts +3 -2
  114. package/src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.ts +6 -4
  115. package/src/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.ts +143 -32
  116. package/src/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.ts +8 -3
  117. package/src/CrowdViewWidget/components/Chart/chartUtils/index.ts +2 -0
  118. package/src/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.ts +3 -0
  119. package/src/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.ts +1 -1
  120. package/src/CrowdViewWidget/components/Chart/getOption.ts +7 -1
  121. package/src/CrowdViewWidget/components/Chart/types.ts +1 -0
  122. package/src/CrowdViewWidget/components/Chart/useResizeObserver.ts +51 -0
  123. package/src/CrowdViewWidget/components/Legend/LegendBar.tsx +3 -2
  124. package/src/CrowdViewWidget/constants.ts +27 -6
  125. package/src/CrowdViewWidget/selectConfig.ts +4 -4
  126. package/src/translations/sources/en.json +11 -9
  127. package/test/components/Chart/utils/chartUtils.test.ts +16 -4
  128. package/test/components/Chart/utils/handleLabelUpdate.test.ts +32 -16
  129. package/test/utils/processPriceCandles.test.ts +4 -4
@@ -40,8 +40,12 @@ export const getLabelsConfig = ({
40
40
  const priceVerticalOffset = priceEstimatedWidth / 2;
41
41
 
42
42
  const topMargin = isDesktop
43
- ? CHART_CONFIG.TOP_MARGIN_DESKTOP
44
- : CHART_CONFIG.TOP_MARGIN_MOBILE;
43
+ ? CHART_CONFIG.TOP_LABEL_SPACE_DESKTOP + CHART_CONFIG.TOOLTIP_HEIGHT_DESKTOP
44
+ : CHART_CONFIG.TOP_LABEL_SPACE_MOBILE + CHART_CONFIG.TOOLTIP_HEIGHT_MOBILE;
45
+
46
+ const mainHeight = isDesktop
47
+ ? CHART_CONFIG.MAIN_HEIGHT_DESKTOP
48
+ : CHART_CONFIG.MAIN_HEIGHT_MOBILE;
45
49
 
46
50
  return [
47
51
  {
@@ -87,7 +91,7 @@ export const getLabelsConfig = ({
87
91
  {
88
92
  type: 'group',
89
93
  left: `${ySentimentLabelSize + 5}px'`,
90
- top: `${CHART_CONFIG.MAIN_HEIGHT - 24 - 5}px`,
94
+ top: `${topMargin + mainHeight - 24 - 5}px`,
91
95
  silent: true,
92
96
  children: [
93
97
  {
@@ -131,8 +135,8 @@ export const getLabelsConfig = ({
131
135
  rotation: isDesktop ? Math.PI / 2 : 0,
132
136
  left: `5px`,
133
137
  top: isDesktop
134
- ? `${topMargin + CHART_CONFIG.MAIN_HEIGHT / 2 - sentimentVerticalOffset}px`
135
- : `${topMargin - 18}px`,
138
+ ? `${topMargin + mainHeight / 2 - sentimentVerticalOffset}px`
139
+ : `${topMargin - 16}px`,
136
140
  silent: true,
137
141
  style: {
138
142
  fontSize: 10,
@@ -148,8 +152,8 @@ export const getLabelsConfig = ({
148
152
  rotation: isDesktop ? -Math.PI / 2 : 0,
149
153
  right: `5px`,
150
154
  top: isDesktop
151
- ? `${topMargin + CHART_CONFIG.MAIN_HEIGHT / 2 - priceVerticalOffset}px`
152
- : `${topMargin - 18}px`,
155
+ ? `${topMargin + mainHeight / 2 - priceVerticalOffset}px`
156
+ : `${topMargin - 16}px`,
153
157
  silent: true,
154
158
  style: {
155
159
  fontSize: 10,
@@ -163,10 +167,10 @@ export const getLabelsConfig = ({
163
167
  type: 'rect',
164
168
  z: 20,
165
169
  right: '0px',
166
- top: '0px',
170
+ top: `${topMargin}px`,
167
171
  shape: {
168
172
  width: yMainLabelSize,
169
- height: CHART_CONFIG.MAIN_HEIGHT,
173
+ height: mainHeight,
170
174
  },
171
175
  silent: true,
172
176
  style: {
@@ -177,15 +181,31 @@ export const getLabelsConfig = ({
177
181
  type: 'rect',
178
182
  z: 20,
179
183
  left: '0px',
180
- top: '0px',
184
+ top: `${topMargin}px`,
181
185
  shape: {
182
186
  width: ySentimentLabelSize,
183
- height: CHART_CONFIG.MAIN_HEIGHT,
187
+ height: mainHeight,
184
188
  },
185
189
  silent: true,
186
190
  style: {
187
191
  fill: backgroundColor,
188
192
  },
189
193
  },
194
+ {
195
+ type: 'text',
196
+ z: 20,
197
+ left: 'center',
198
+ top: isDesktop
199
+ ? CHART_CONFIG.TOOLTIP_HEIGHT_DESKTOP / 2 - 5
200
+ : CHART_CONFIG.TOOLTIP_HEIGHT_MOBILE / 2 - 5,
201
+ silent: true,
202
+ style: {
203
+ fontSize: isDesktop ? 12 : 11,
204
+ fill: textColor,
205
+ text: isDesktop
206
+ ? labelCallback('hover_chart_to_see_more_details')
207
+ : labelCallback('tap_chart_to_see_more_details'),
208
+ },
209
+ },
190
210
  ];
191
211
  };
@@ -1,7 +1,6 @@
1
1
  import type { TooltipComponentOption } from 'echarts';
2
2
 
3
3
  import type { BookType } from '../../../../gql/types/graphql';
4
- import { CHART_CONFIG } from '../../../constants';
5
4
  import { getTooltipFormatter } from '../chartUtils';
6
5
  import type { Bucket, TooltipParam } from '../types';
7
6
 
@@ -9,25 +8,42 @@ interface GetTooltipConfigParams {
9
8
  bookType: BookType;
10
9
  bucketWidth: number;
11
10
  buckets: Bucket[][];
12
- displayPrecision: number;
13
11
  labelCallback: (key: string, params?: Record<string, unknown>) => string;
14
12
  selectedPriceRef: { current: number };
15
13
  tooltipLinesColor: string;
16
14
  sentimentLongs: (number | null)[];
17
15
  sentimentShorts: (number | null)[];
16
+ isDesktop: boolean;
17
+ isDark: boolean;
18
+ locale: string;
18
19
  }
19
20
 
20
21
  export const getTooltipConfig = ({
21
22
  bookType,
22
23
  bucketWidth,
23
24
  buckets,
24
- displayPrecision,
25
25
  labelCallback,
26
26
  selectedPriceRef,
27
27
  tooltipLinesColor,
28
28
  sentimentLongs,
29
29
  sentimentShorts,
30
+ isDesktop,
31
+ isDark,
32
+ locale,
30
33
  }: GetTooltipConfigParams): TooltipComponentOption => ({
34
+ backgroundColor: 'transparent',
35
+ shadowColor: 'transparent',
36
+ extraCssText: 'width: 100%; z-index: 5;',
37
+ padding: 4,
38
+ textStyle: isDesktop
39
+ ? {
40
+ fontSize: 12,
41
+ }
42
+ : {
43
+ fontSize: 10,
44
+ lineHeight: 12,
45
+ },
46
+ alwaysShowContent: true,
31
47
  trigger: 'axis' as const,
32
48
  formatter: (params) =>
33
49
  getTooltipFormatter({
@@ -39,6 +55,9 @@ export const getTooltipConfig = ({
39
55
  selectedPrice: selectedPriceRef.current,
40
56
  sentimentLongs,
41
57
  sentimentShorts,
58
+ isDesktop,
59
+ locale,
60
+ isDark,
42
61
  }),
43
62
  hideDelay: 0,
44
63
  axisPointer: {
@@ -50,25 +69,8 @@ export const getTooltipConfig = ({
50
69
  crossStyle: {
51
70
  color: tooltipLinesColor,
52
71
  },
53
- label: {
54
- padding: 0,
55
- lineHeight: 24,
56
- formatter: (params: {
57
- axisDimension?: string;
58
- axisIndex?: number;
59
- value: unknown;
60
- }) => {
61
- if (params.axisDimension === 'y' && params.axisIndex === 0) {
62
- selectedPriceRef.current = Number(params.value);
63
- return ` ${Number(params.value).toFixed(displayPrecision)} `;
64
- }
65
- return '';
66
- },
67
- },
72
+ z: 30,
68
73
  },
69
74
  confine: true,
70
- position: (point) => [
71
- point[0] + CHART_CONFIG.TOOLTIP_OFFSET,
72
- point[1] + CHART_CONFIG.TOOLTIP_OFFSET,
73
- ],
75
+ position: [0, 0],
74
76
  });
@@ -1,6 +1,7 @@
1
1
  import type { XAXisComponentOption } from 'echarts';
2
2
 
3
3
  import {
4
+ formatFullDate,
4
5
  formatXAxisAdditionalLabel,
5
6
  formatXAxisLabel,
6
7
  getLabelData,
@@ -9,11 +10,15 @@ import {
9
10
  interface GetXAxisConfigParams {
10
11
  dates: string[];
11
12
  isGreaterThanTwoWeeks: boolean;
13
+ locale: string;
14
+ isDesktop: boolean;
12
15
  }
13
16
 
14
17
  export const getXAxisConfig = ({
15
18
  dates,
16
19
  isGreaterThanTwoWeeks,
20
+ locale,
21
+ isDesktop,
17
22
  }: GetXAxisConfigParams): XAXisComponentOption[] => {
18
23
  const labelsData = getLabelData({
19
24
  dates,
@@ -24,22 +29,44 @@ export const getXAxisConfig = ({
24
29
  {
25
30
  type: 'category',
26
31
  id: 'main-xAxis',
27
- name: isGreaterThanTwoWeeks
28
- ? 'xAxis-greater-than-two-weeks'
29
- : 'xAxis-less-than-two-weeks',
30
32
  nameTextStyle: {
31
33
  fontSize: 0,
32
34
  },
33
35
  axisTick: {
34
36
  show: false,
35
37
  },
38
+ splitLine: {
39
+ lineStyle: {
40
+ opacity: 0.5,
41
+ },
42
+ },
36
43
  axisLabel: {
37
44
  padding: [8, 16, 8, 16],
38
45
  margin: 0,
39
46
  formatter: (value: unknown) =>
40
- formatXAxisLabel(value, isGreaterThanTwoWeeks),
47
+ value === dates[dates.length - 1]
48
+ ? ''
49
+ : formatXAxisLabel(value, isGreaterThanTwoWeeks, locale),
41
50
  },
42
51
  boundaryGap: false,
52
+ z: 2,
53
+ axisPointer: {
54
+ label: {
55
+ lineHeight: 22,
56
+ margin: -22,
57
+ padding: [0, 8, 0, 8],
58
+ formatter: (params: {
59
+ axisDimension?: string;
60
+ axisIndex?: number;
61
+ value: unknown;
62
+ }) => {
63
+ const date = new Date(params.value as string);
64
+
65
+ return isDesktop ? formatFullDate(date, locale) : '';
66
+ },
67
+ },
68
+ z: 100,
69
+ },
43
70
  },
44
71
  {
45
72
  type: 'category',
@@ -59,10 +86,14 @@ export const getXAxisConfig = ({
59
86
  hideOverlap: true,
60
87
  customValues: labelsData,
61
88
  formatter: (value: unknown) =>
62
- formatXAxisAdditionalLabel(value, isGreaterThanTwoWeeks),
89
+ formatXAxisAdditionalLabel(value, isGreaterThanTwoWeeks, locale),
90
+ },
91
+ axisPointer: {
92
+ show: false,
63
93
  },
64
94
  data: dates,
65
95
  boundaryGap: false,
96
+ z: 2,
66
97
  },
67
98
  ];
68
99
  };
@@ -1,24 +1,30 @@
1
1
  import type { YAXisComponentOption } from 'echarts';
2
2
 
3
- import { BUCKET_CONFIG, CHART_CONFIG } from '../../../constants';
3
+ import { CHART_CONFIG } from '../../../constants';
4
4
 
5
5
  interface GetYAxisConfigParams {
6
6
  bucketWidth: number;
7
7
  displayPrecision: number;
8
+ selectedPriceRef: { current: number };
8
9
  }
9
10
 
10
11
  export const getYAxisConfig = ({
11
12
  bucketWidth,
12
13
  displayPrecision,
14
+ selectedPriceRef,
13
15
  }: GetYAxisConfigParams): YAXisComponentOption[] => [
14
16
  {
15
17
  type: 'value',
16
18
  gridIndex: 0,
17
19
  position: 'right',
18
- min: (val: { min: number }) =>
19
- val.min - bucketWidth * BUCKET_CONFIG.PRICE_MARGIN_MULTIPLIER,
20
- max: (val: { max: number }) =>
21
- val.max + bucketWidth * BUCKET_CONFIG.PRICE_MARGIN_MULTIPLIER,
20
+ min: (val: { min: number }) => {
21
+ const adjustedMin = val.min - bucketWidth;
22
+ return Math.floor(adjustedMin / bucketWidth) * bucketWidth;
23
+ },
24
+ max: (val: { max: number }) => {
25
+ const adjustedMax = val.max + bucketWidth;
26
+ return Math.ceil(adjustedMax / bucketWidth) * bucketWidth;
27
+ },
22
28
  axisLine: { show: false },
23
29
  axisTick: { show: false },
24
30
  splitLine: { show: false },
@@ -29,6 +35,23 @@ export const getYAxisConfig = ({
29
35
  verticalAlignMaxLabel: 'top',
30
36
  formatter: (value: number) => value.toFixed(displayPrecision),
31
37
  },
38
+ axisPointer: {
39
+ label: {
40
+ padding: [4, 4, 4, 4],
41
+ margin: -1,
42
+ formatter: (params: {
43
+ axisDimension?: string;
44
+ axisIndex?: number;
45
+ value: unknown;
46
+ }) => {
47
+ if (params.axisDimension === 'y' && params.axisIndex === 0) {
48
+ selectedPriceRef.current = Number(params.value);
49
+ return Number(params.value).toFixed(displayPrecision);
50
+ }
51
+ return '';
52
+ },
53
+ },
54
+ },
32
55
  z: 30,
33
56
  },
34
57
  {
@@ -45,7 +68,7 @@ export const getYAxisConfig = ({
45
68
  margin: 4,
46
69
  lineHeight: 20,
47
70
  verticalAlignMinLabel: 'bottom',
48
- verticalAlignMaxLabel: 'middle',
71
+ verticalAlignMaxLabel: 'top',
49
72
  customValues: [
50
73
  CHART_CONFIG.SENTIMENT_MIN,
51
74
  CHART_CONFIG.SENTIMENT_MAX / 5,
@@ -0,0 +1,26 @@
1
+ export const formatFullDate = (date: Date, locale: string): string => {
2
+ const formattedDate = date
3
+ .toLocaleDateString(locale, {
4
+ day: 'numeric',
5
+ month: '2-digit',
6
+ year: '2-digit',
7
+ })
8
+ .replace(/\//g, '.');
9
+
10
+ const formattedTime = date.toLocaleTimeString(locale, {
11
+ hour: 'numeric',
12
+ minute: '2-digit',
13
+ hour12: false,
14
+ });
15
+
16
+ const formatter = new Intl.DateTimeFormat('en-US', {
17
+ timeZoneName: 'shortOffset',
18
+ });
19
+
20
+ const parts = formatter.formatToParts(date);
21
+ const timeZoneOffset = parts.find(
22
+ (part) => part.type === 'timeZoneName'
23
+ )?.value;
24
+
25
+ return `${formattedDate} ${formattedTime} ${timeZoneOffset}`;
26
+ };
@@ -1,12 +1,13 @@
1
1
  export const formatXAxisAdditionalLabel = (
2
2
  value: unknown,
3
- isGreaterThanTwoWeeks: boolean
3
+ isGreaterThanTwoWeeks: boolean,
4
+ locale: string
4
5
  ) => {
5
6
  const date = new Date(value as string);
6
7
  if (isNaN(date.getTime())) {
7
8
  return '';
8
9
  }
9
- return `${date.toLocaleDateString(undefined, {
10
+ return `${date.toLocaleDateString(locale, {
10
11
  month: isGreaterThanTwoWeeks ? 'short' : 'long',
11
12
  day: isGreaterThanTwoWeeks ? 'numeric' : undefined,
12
13
  })}`;
@@ -2,15 +2,17 @@ import { CHART_CONFIG } from '../../../constants';
2
2
 
3
3
  export const formatXAxisLabel = (
4
4
  value: unknown,
5
- isGreaterThanTwoWeeks: boolean
5
+ isGreaterThanTwoWeeks: boolean,
6
+ locale: string
6
7
  ) => {
7
8
  const date = new Date(value as string);
8
9
  return isGreaterThanTwoWeeks
9
- ? `${date.toLocaleTimeString(undefined, {
10
- hour: '2-digit',
10
+ ? `${date.toLocaleTimeString(locale, {
11
+ hour: 'numeric',
11
12
  minute: '2-digit',
13
+ hour12: false,
12
14
  })}`
13
- : `${CHART_CONFIG.X_AXIS_DATE_PADDING}${date.toLocaleDateString(undefined, {
15
+ : `${CHART_CONFIG.X_AXIS_DATE_PADDING}${date.toLocaleDateString(locale, {
14
16
  day: 'numeric',
15
17
  })}${CHART_CONFIG.X_AXIS_DATE_PADDING}`;
16
18
  };
@@ -1,17 +1,17 @@
1
+ import { colorPalette } from '@oanda/labs-widget-common';
2
+
1
3
  import { BookType } from '../../../../gql/types/graphql';
2
4
  import type { Bucket, TooltipParam } from '../types';
3
-
4
- const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
5
- hour: '2-digit',
6
- minute: '2-digit',
7
- year: 'numeric',
8
- day: 'numeric',
9
- month: 'numeric',
10
- timeZoneName: 'short',
11
- };
5
+ import { formatFullDate } from './formatFullDate';
12
6
 
13
7
  const SENTIMENT_DISPLAY_PRECISION = 2;
14
8
 
9
+ const TOOLTIP_STYLES = {
10
+ MOBILE_COLUMN_WIDTH: '33.3%',
11
+ PADDING_BOTTOM: '4px',
12
+ FULL_WIDTH: '100%',
13
+ } as const;
14
+
15
15
  const calculateBucketDisplayPrecision = (bucketWidth: number): number => {
16
16
  return bucketWidth.toString().split('.')[1]?.length || 0;
17
17
  };
@@ -56,15 +56,29 @@ const getSentimentOverbalanceLabel = (
56
56
 
57
57
  const formatCandleData = (
58
58
  candleData: [number, number, number, number, number],
59
- labelCallback: (key: string) => string
59
+ labelCallback: (key: string) => string,
60
+ isDesktop: boolean
60
61
  ): string => {
61
62
  const [, open, close, low, high] = candleData;
62
- return `<p><b>${labelCallback('candle')}:</b></p>
63
- <p>${labelCallback('open_price')}: ${open} </p>
64
- <p>${labelCallback('close_price')}: ${close} </p>
65
- <p>${labelCallback('low')}: ${low} </p>
66
- <p>${labelCallback('high')}: ${high} </p>
67
- `;
63
+ const candleLabel = labelCallback('candle');
64
+ const openLabel = labelCallback('open');
65
+ const closeLabel = labelCallback('close');
66
+ const lowLabel = labelCallback('low');
67
+ const highLabel = labelCallback('high');
68
+
69
+ return isDesktop
70
+ ? `<div><b>${candleLabel}:</b></div>
71
+ <div style="display: flex; gap: 4px; margin-right: 4px;">
72
+ <div>${openLabel}: ${open} </div>
73
+ <div>${closeLabel}: ${close} </div>
74
+ <div>${lowLabel}: ${low} </div>
75
+ <div>${highLabel}: ${high} </div>
76
+ </div>`
77
+ : `<div><b>${candleLabel}:</b></div>
78
+ <div>${openLabel}: ${open} </div>
79
+ <div>${closeLabel}: ${close} </div>
80
+ <div>${lowLabel}: ${low} </div>
81
+ <div>${highLabel}: ${high} </div>`;
68
82
  };
69
83
 
70
84
  const formatBookData = ({
@@ -73,14 +87,16 @@ const formatBookData = ({
73
87
  bucketDisplayPrecision,
74
88
  bookType,
75
89
  labelCallback,
90
+ isDesktop,
76
91
  }: {
77
92
  matchedBucket: Bucket;
78
93
  bucketWidth: number;
79
94
  bucketDisplayPrecision: number;
80
95
  bookType: BookType;
81
96
  labelCallback: (key: string) => string;
97
+ isDesktop: boolean;
82
98
  }): string => {
83
- const bookLabelKey = bookType === BookType.Order ? 'orders' : 'positions';
99
+ const bookLabelKey = bookType === BookType.Order ? 'orders' : 'trades';
84
100
  const priceRangeStart = matchedBucket.price.toFixed(bucketDisplayPrecision);
85
101
  const priceRangeEnd = (matchedBucket.price + bucketWidth).toFixed(
86
102
  bucketDisplayPrecision
@@ -89,23 +105,32 @@ const formatBookData = ({
89
105
  matchedBucket.sentiment,
90
106
  bookType
91
107
  );
92
- const sentimentValue = Math.abs(matchedBucket.sentiment).toFixed(
93
- bucketDisplayPrecision
94
- );
108
+ const sentimentValue = Math.abs(matchedBucket.sentiment);
95
109
 
96
- return `<br /><p><b>${labelCallback(bookLabelKey)}:</b></p>
97
- <p>${labelCallback('price_range')}: ${priceRangeStart} - ${priceRangeEnd} </p>
98
- <p>${labelCallback(sentimentLabel)}: ${sentimentValue}% </p>`;
110
+ return isDesktop
111
+ ? `<div><b>${labelCallback(bookLabelKey)}:</b></div>
112
+ <span style="margin-right: 4px;">${labelCallback('price_range')}: ${priceRangeStart} - ${priceRangeEnd} </span>
113
+ <span style="margin-right: 4px;">${labelCallback(sentimentLabel)}: ${sentimentValue}% </span>`
114
+ : `<div><b>${labelCallback(bookLabelKey)}:</b></div>
115
+ <div>${labelCallback('price_range')}:</div>
116
+ <div>${priceRangeStart} - ${priceRangeEnd} </div>
117
+ <div>${labelCallback(sentimentLabel)}:</div>
118
+ <div>${sentimentValue}% </div>`;
99
119
  };
100
120
 
101
121
  const formatSentimentData = (
102
122
  sentimentLong: number,
103
123
  sentimentShort: number,
104
- labelCallback: (key: string) => string
124
+ labelCallback: (key: string) => string,
125
+ isDesktop: boolean
105
126
  ): string => {
106
- return `<br /><p><b>${labelCallback('sentiment')}:</b></p>
107
- <p>${labelCallback('long')}: ${sentimentLong.toFixed(SENTIMENT_DISPLAY_PRECISION)}% </p>
108
- <p>${labelCallback('short')}: ${sentimentShort.toFixed(SENTIMENT_DISPLAY_PRECISION)}% </p>`;
127
+ return isDesktop
128
+ ? `<div><b>${labelCallback('sentiment')}:</b></div>
129
+ <span style="margin-right: 4px;">${labelCallback('long')}: ${sentimentLong.toFixed(SENTIMENT_DISPLAY_PRECISION)}% </span>
130
+ <span style="margin-right: 4px;">${labelCallback('short')}: ${sentimentShort.toFixed(SENTIMENT_DISPLAY_PRECISION)}% </span>`
131
+ : `<div><b>${labelCallback('sentiment')}:</b></div>
132
+ <div>${labelCallback('long')}: ${sentimentLong.toFixed(SENTIMENT_DISPLAY_PRECISION)}% </div>
133
+ <div>${labelCallback('short')}: ${sentimentShort.toFixed(SENTIMENT_DISPLAY_PRECISION)}% </div>`;
109
134
  };
110
135
 
111
136
  const hasValidCandleData = (
@@ -142,6 +167,9 @@ export const getTooltipFormatter = ({
142
167
  labelCallback,
143
168
  sentimentLongs,
144
169
  sentimentShorts,
170
+ isDesktop,
171
+ locale,
172
+ isDark,
145
173
  }: {
146
174
  params: TooltipParam[];
147
175
  buckets: Bucket[][];
@@ -151,6 +179,9 @@ export const getTooltipFormatter = ({
151
179
  labelCallback: (key: string) => string;
152
180
  sentimentLongs: (number | null)[];
153
181
  sentimentShorts: (number | null)[];
182
+ isDesktop: boolean;
183
+ locale: string;
184
+ isDark: boolean;
154
185
  }) => {
155
186
  if (!params || !Array.isArray(params) || params.length === 0) {
156
187
  return '';
@@ -185,9 +216,9 @@ export const getTooltipFormatter = ({
185
216
  return '';
186
217
  }
187
218
 
188
- const timeFormatted = time.toLocaleString(undefined, DATE_FORMAT_OPTIONS);
219
+ const timeFormatted = formatFullDate(time, locale);
189
220
  const candleSection = showCandles
190
- ? formatCandleData(candleData, labelCallback)
221
+ ? formatCandleData(candleData, labelCallback, isDesktop)
191
222
  : '';
192
223
 
193
224
  const bookSection = matchedBucket
@@ -197,16 +228,96 @@ export const getTooltipFormatter = ({
197
228
  bucketDisplayPrecision,
198
229
  bookType,
199
230
  labelCallback,
231
+ isDesktop,
200
232
  })
201
233
  : '';
202
234
 
203
235
  const sentimentSection =
204
236
  showSentiment && sentimentLong && sentimentShort
205
- ? formatSentimentData(sentimentLong, sentimentShort, labelCallback)
237
+ ? formatSentimentData(
238
+ sentimentLong,
239
+ sentimentShort,
240
+ labelCallback,
241
+ isDesktop
242
+ )
206
243
  : '';
207
244
 
208
- return `<p>${timeFormatted}</p><br />
245
+ if (isDesktop) {
246
+ return buildDesktopTooltip({
247
+ timeFormatted,
248
+ candleSection,
249
+ sentimentSection,
250
+ bookSection,
251
+ isDark,
252
+ });
253
+ }
254
+
255
+ return buildMobileTooltip({
256
+ timeFormatted,
257
+ candleSection,
258
+ sentimentSection,
259
+ bookSection,
260
+ isDark,
261
+ });
262
+ };
263
+
264
+ interface TooltipSections {
265
+ timeFormatted: string;
266
+ candleSection: string;
267
+ sentimentSection: string;
268
+ bookSection: string;
269
+ }
270
+
271
+ const buildDesktopTooltip = ({
272
+ candleSection,
273
+ sentimentSection,
274
+ bookSection,
275
+ isDark,
276
+ }: TooltipSections & {
277
+ isDark: boolean;
278
+ }): string => {
279
+ const backgroundColor = isDark ? colorPalette.black : colorPalette.white;
280
+ const fullWidth = TOOLTIP_STYLES.FULL_WIDTH;
281
+
282
+ return `<div style="width: ${fullWidth}; background-color: ${backgroundColor};">
283
+ <div style="width: ${fullWidth}; display: flex; justify-content: space-between;">
284
+ <div style="width: 40%;">
285
+ ${candleSection}
286
+ </div>
287
+ <div style="width: 22%;">
288
+ ${sentimentSection}
289
+ </div>
290
+ <div style="width: 38%;">
291
+ ${bookSection}
292
+ </div>
293
+ </div>
294
+ </div>`;
295
+ };
296
+
297
+ const buildMobileTooltip = ({
298
+ timeFormatted,
299
+ candleSection,
300
+ sentimentSection,
301
+ bookSection,
302
+ isDark,
303
+ }: TooltipSections & { isDark: boolean }): string => {
304
+ const backgroundColor = isDark ? colorPalette.black : colorPalette.white;
305
+ const columnWidth = TOOLTIP_STYLES.MOBILE_COLUMN_WIDTH;
306
+ const fullWidth = TOOLTIP_STYLES.FULL_WIDTH;
307
+ const padding = TOOLTIP_STYLES.PADDING_BOTTOM;
308
+
309
+ return `<div style="width: ${fullWidth}; background-color: ${backgroundColor};">
310
+ <div style="padding-bottom: ${padding};">${timeFormatted}</div>
311
+ <div style="width: ${fullWidth}; display: flex; justify-content: space-between;">
312
+ <div style="width: ${columnWidth};">
209
313
  ${candleSection}
314
+ </div>
315
+ <div style="width: ${columnWidth};">
316
+ ${sentimentSection}
317
+ </div>
318
+ <div style="width: ${columnWidth};">
210
319
  ${bookSection}
211
- ${sentimentSection}`;
320
+ </div>
321
+ </div>
322
+ </div>`;
212
323
  };
@@ -11,7 +11,8 @@ export const handleLabelUpdate = (
11
11
  instance: EChartsType,
12
12
  mainData: ChartProps['mainData'],
13
13
  labelTimerRef: MutableRefObject<NodeJS.Timeout | null>,
14
- isGreaterThanTwoWeeksRef: MutableRefObject<boolean | null>
14
+ isGreaterThanTwoWeeksRef: MutableRefObject<boolean | null>,
15
+ locale: string
15
16
  ): void => {
16
17
  if (labelTimerRef.current) {
17
18
  clearTimeout(labelTimerRef.current);
@@ -55,7 +56,7 @@ export const handleLabelUpdate = (
55
56
  id: 'main-xAxis',
56
57
  axisLabel: {
57
58
  formatter: (value: string) =>
58
- formatXAxisLabel(value, isGreaterThanTwoWeeks),
59
+ formatXAxisLabel(value, isGreaterThanTwoWeeks, locale),
59
60
  },
60
61
  },
61
62
  {
@@ -63,7 +64,11 @@ export const handleLabelUpdate = (
63
64
  axisLabel: {
64
65
  customValues: labelsData,
65
66
  formatter: (value: unknown) =>
66
- formatXAxisAdditionalLabel(value, isGreaterThanTwoWeeks),
67
+ formatXAxisAdditionalLabel(
68
+ value,
69
+ isGreaterThanTwoWeeks,
70
+ locale
71
+ ),
67
72
  },
68
73
  },
69
74
  ],