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

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 (123) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +8 -5
  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 +28 -14
  7. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js +19 -7
  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 +44 -5
  13. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js.map +1 -1
  14. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js +14 -6
  15. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js.map +1 -1
  16. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js +11 -4
  17. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js.map +1 -1
  18. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js +2 -2
  19. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js.map +1 -1
  20. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.js +6 -5
  21. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.js.map +1 -1
  22. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js +72 -14
  23. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js.map +1 -1
  24. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js +3 -3
  25. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js.map +1 -1
  26. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/index.js +11 -0
  27. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/index.js.map +1 -1
  28. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.js +11 -0
  29. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.js.map +1 -0
  30. package/dist/main/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.js +1 -1
  31. package/dist/main/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.js.map +1 -1
  32. package/dist/main/CrowdViewWidget/components/Chart/getOption.js +9 -3
  33. package/dist/main/CrowdViewWidget/components/Chart/getOption.js.map +1 -1
  34. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  35. package/dist/main/CrowdViewWidget/components/Chart/useResizeObserver.js +47 -0
  36. package/dist/main/CrowdViewWidget/components/Chart/useResizeObserver.js.map +1 -0
  37. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js +1 -1
  38. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  39. package/dist/main/CrowdViewWidget/constants.js +20 -7
  40. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  41. package/dist/main/CrowdViewWidget/selectConfig.js +3 -3
  42. package/dist/main/CrowdViewWidget/selectConfig.js.map +1 -1
  43. package/dist/main/translations/sources/en.json +10 -9
  44. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +9 -6
  45. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  46. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +15 -3
  47. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  48. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js +28 -14
  49. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js.map +1 -1
  50. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js +19 -7
  51. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js.map +1 -1
  52. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js +20 -8
  53. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js.map +1 -1
  54. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js +43 -5
  55. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js.map +1 -1
  56. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js +14 -6
  57. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js.map +1 -1
  58. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js +12 -5
  59. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js.map +1 -1
  60. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js +2 -2
  61. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js.map +1 -1
  62. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.js +6 -5
  63. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.js.map +1 -1
  64. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js +72 -14
  65. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js.map +1 -1
  66. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js +3 -3
  67. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js.map +1 -1
  68. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/index.js +1 -0
  69. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/index.js.map +1 -1
  70. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.js +4 -0
  71. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.js.map +1 -0
  72. package/dist/module/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.js +1 -1
  73. package/dist/module/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.js.map +1 -1
  74. package/dist/module/CrowdViewWidget/components/Chart/getOption.js +9 -3
  75. package/dist/module/CrowdViewWidget/components/Chart/getOption.js.map +1 -1
  76. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  77. package/dist/module/CrowdViewWidget/components/Chart/useResizeObserver.js +40 -0
  78. package/dist/module/CrowdViewWidget/components/Chart/useResizeObserver.js.map +1 -0
  79. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js +1 -1
  80. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  81. package/dist/module/CrowdViewWidget/constants.js +20 -7
  82. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  83. package/dist/module/CrowdViewWidget/selectConfig.js +3 -3
  84. package/dist/module/CrowdViewWidget/selectConfig.js.map +1 -1
  85. package/dist/module/translations/sources/en.json +10 -9
  86. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.d.ts +4 -1
  87. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.d.ts +2 -1
  88. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.d.ts +2 -1
  89. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.d.ts +1 -1
  90. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.d.ts +1 -1
  91. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.d.ts +4 -1
  92. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.d.ts +1 -1
  93. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/index.d.ts +1 -0
  94. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.d.ts +1 -0
  95. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +1 -0
  96. package/dist/types/CrowdViewWidget/components/Chart/useResizeObserver.d.ts +5 -0
  97. package/dist/types/CrowdViewWidget/constants.d.ts +24 -19
  98. package/package.json +3 -3
  99. package/src/CrowdViewWidget/components/Chart/Chart.tsx +12 -5
  100. package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +22 -3
  101. package/src/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.ts +34 -19
  102. package/src/CrowdViewWidget/components/Chart/chartOptions/getGridLines.ts +27 -6
  103. package/src/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.ts +26 -10
  104. package/src/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.ts +86 -43
  105. package/src/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.ts +12 -5
  106. package/src/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.ts +12 -6
  107. package/src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.ts +3 -2
  108. package/src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel.ts +6 -4
  109. package/src/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.ts +119 -22
  110. package/src/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.ts +8 -3
  111. package/src/CrowdViewWidget/components/Chart/chartUtils/index.ts +1 -0
  112. package/src/CrowdViewWidget/components/Chart/chartUtils/normalizeLocale.ts +3 -0
  113. package/src/CrowdViewWidget/components/Chart/dataUtils/processPriceCandles.ts +1 -1
  114. package/src/CrowdViewWidget/components/Chart/getOption.ts +6 -0
  115. package/src/CrowdViewWidget/components/Chart/types.ts +1 -0
  116. package/src/CrowdViewWidget/components/Chart/useResizeObserver.ts +51 -0
  117. package/src/CrowdViewWidget/components/Legend/LegendBar.tsx +3 -2
  118. package/src/CrowdViewWidget/constants.ts +24 -5
  119. package/src/CrowdViewWidget/selectConfig.ts +4 -4
  120. package/src/translations/sources/en.json +10 -9
  121. package/test/components/Chart/utils/chartUtils.test.ts +16 -4
  122. package/test/components/Chart/utils/handleLabelUpdate.test.ts +32 -16
  123. package/test/utils/processPriceCandles.test.ts +4 -4
@@ -1,17 +1,28 @@
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
5
 
4
6
  const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
5
- hour: '2-digit',
6
- minute: '2-digit',
7
- year: 'numeric',
8
7
  day: 'numeric',
9
8
  month: 'numeric',
10
- timeZoneName: 'short',
9
+ year: 'numeric',
10
+
11
+ hour: 'numeric',
12
+ minute: '2-digit',
13
+ hour12: false,
14
+
15
+ timeZoneName: 'shortOffset',
11
16
  };
12
17
 
13
18
  const SENTIMENT_DISPLAY_PRECISION = 2;
14
19
 
20
+ const TOOLTIP_STYLES = {
21
+ MOBILE_COLUMN_WIDTH: '33.3%',
22
+ PADDING_BOTTOM: '4px',
23
+ FULL_WIDTH: '100%',
24
+ } as const;
25
+
15
26
  const calculateBucketDisplayPrecision = (bucketWidth: number): number => {
16
27
  return bucketWidth.toString().split('.')[1]?.length || 0;
17
28
  };
@@ -59,11 +70,17 @@ const formatCandleData = (
59
70
  labelCallback: (key: string) => string
60
71
  ): string => {
61
72
  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>
73
+ const candleLabel = labelCallback('candle');
74
+ const openLabel = labelCallback('open');
75
+ const closeLabel = labelCallback('close');
76
+ const lowLabel = labelCallback('low');
77
+ const highLabel = labelCallback('high');
78
+
79
+ return `<div><b>${candleLabel}:</b></div>
80
+ <div>${openLabel}: ${open} </div>
81
+ <div>${closeLabel}: ${close} </div>
82
+ <div>${lowLabel}: ${low} </div>
83
+ <div>${highLabel}: ${high} </div>
67
84
  `;
68
85
  };
69
86
 
@@ -73,14 +90,16 @@ const formatBookData = ({
73
90
  bucketDisplayPrecision,
74
91
  bookType,
75
92
  labelCallback,
93
+ isDesktop,
76
94
  }: {
77
95
  matchedBucket: Bucket;
78
96
  bucketWidth: number;
79
97
  bucketDisplayPrecision: number;
80
98
  bookType: BookType;
81
99
  labelCallback: (key: string) => string;
100
+ isDesktop: boolean;
82
101
  }): string => {
83
- const bookLabelKey = bookType === BookType.Order ? 'orders' : 'positions';
102
+ const bookLabelKey = bookType === BookType.Order ? 'orders' : 'trades';
84
103
  const priceRangeStart = matchedBucket.price.toFixed(bucketDisplayPrecision);
85
104
  const priceRangeEnd = (matchedBucket.price + bucketWidth).toFixed(
86
105
  bucketDisplayPrecision
@@ -89,13 +108,17 @@ const formatBookData = ({
89
108
  matchedBucket.sentiment,
90
109
  bookType
91
110
  );
92
- const sentimentValue = Math.abs(matchedBucket.sentiment).toFixed(
93
- bucketDisplayPrecision
94
- );
111
+ const sentimentValue = Math.abs(matchedBucket.sentiment);
95
112
 
96
- return `<br /><p><b>${labelCallback(bookLabelKey)}:</b></p>
97
- <p>${labelCallback('price_range')}: ${priceRangeStart} - ${priceRangeEnd} </p>
98
- <p>${labelCallback(sentimentLabel)}: ${sentimentValue}% </p>`;
113
+ return isDesktop
114
+ ? `<div><b>${labelCallback(bookLabelKey)}:</b></div>
115
+ <div>${labelCallback('price_range')}: ${priceRangeStart} - ${priceRangeEnd} </div>
116
+ <div>${labelCallback(sentimentLabel)}: ${sentimentValue}% </div>`
117
+ : `<div><b>${labelCallback(bookLabelKey)}:</b></div>
118
+ <div>${labelCallback('price_range')}:</div>
119
+ <div>${priceRangeStart} - ${priceRangeEnd} </div>
120
+ <div>${labelCallback(sentimentLabel)}:</div>
121
+ <div>${sentimentValue}% </div>`;
99
122
  };
100
123
 
101
124
  const formatSentimentData = (
@@ -103,9 +126,9 @@ const formatSentimentData = (
103
126
  sentimentShort: number,
104
127
  labelCallback: (key: string) => string
105
128
  ): 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>`;
129
+ return `<div><b>${labelCallback('sentiment')}:</b></div>
130
+ <div>${labelCallback('long')}: ${sentimentLong.toFixed(SENTIMENT_DISPLAY_PRECISION)}% </div>
131
+ <div>${labelCallback('short')}: ${sentimentShort.toFixed(SENTIMENT_DISPLAY_PRECISION)}% </div>`;
109
132
  };
110
133
 
111
134
  const hasValidCandleData = (
@@ -142,6 +165,9 @@ export const getTooltipFormatter = ({
142
165
  labelCallback,
143
166
  sentimentLongs,
144
167
  sentimentShorts,
168
+ isDesktop,
169
+ locale,
170
+ isDark,
145
171
  }: {
146
172
  params: TooltipParam[];
147
173
  buckets: Bucket[][];
@@ -151,6 +177,9 @@ export const getTooltipFormatter = ({
151
177
  labelCallback: (key: string) => string;
152
178
  sentimentLongs: (number | null)[];
153
179
  sentimentShorts: (number | null)[];
180
+ isDesktop: boolean;
181
+ locale: string;
182
+ isDark: boolean;
154
183
  }) => {
155
184
  if (!params || !Array.isArray(params) || params.length === 0) {
156
185
  return '';
@@ -185,7 +214,9 @@ export const getTooltipFormatter = ({
185
214
  return '';
186
215
  }
187
216
 
188
- const timeFormatted = time.toLocaleString(undefined, DATE_FORMAT_OPTIONS);
217
+ const timeFormatted = time
218
+ .toLocaleString(locale, DATE_FORMAT_OPTIONS)
219
+ .replace(/\//g, '.');
189
220
  const candleSection = showCandles
190
221
  ? formatCandleData(candleData, labelCallback)
191
222
  : '';
@@ -197,6 +228,7 @@ export const getTooltipFormatter = ({
197
228
  bucketDisplayPrecision,
198
229
  bookType,
199
230
  labelCallback,
231
+ isDesktop,
200
232
  })
201
233
  : '';
202
234
 
@@ -205,8 +237,73 @@ export const getTooltipFormatter = ({
205
237
  ? formatSentimentData(sentimentLong, sentimentShort, labelCallback)
206
238
  : '';
207
239
 
208
- return `<p>${timeFormatted}</p><br />
240
+ if (isDesktop) {
241
+ return buildDesktopTooltip({
242
+ timeFormatted,
243
+ candleSection,
244
+ sentimentSection,
245
+ bookSection,
246
+ hasMatchedBucket: !!matchedBucket,
247
+ });
248
+ }
249
+
250
+ return buildMobileTooltip({
251
+ timeFormatted,
252
+ candleSection,
253
+ sentimentSection,
254
+ bookSection,
255
+ isDark,
256
+ });
257
+ };
258
+
259
+ interface TooltipSections {
260
+ timeFormatted: string;
261
+ candleSection: string;
262
+ sentimentSection: string;
263
+ bookSection: string;
264
+ }
265
+
266
+ const buildDesktopTooltip = ({
267
+ timeFormatted,
268
+ candleSection,
269
+ sentimentSection,
270
+ bookSection,
271
+ hasMatchedBucket,
272
+ }: TooltipSections & { hasMatchedBucket: boolean }): string => {
273
+ const padding = TOOLTIP_STYLES.PADDING_BOTTOM;
274
+ const sentimentPadding = hasMatchedBucket ? padding : '0px';
275
+
276
+ return `<div style="padding-bottom: ${padding};">${timeFormatted}</div>
277
+ <div style="padding-bottom: ${padding};">${candleSection}</div>
278
+ <div style="padding-bottom: ${sentimentPadding};">${sentimentSection}</div>
279
+ <div>${bookSection}</div>
280
+ `;
281
+ };
282
+
283
+ const buildMobileTooltip = ({
284
+ timeFormatted,
285
+ candleSection,
286
+ sentimentSection,
287
+ bookSection,
288
+ isDark,
289
+ }: TooltipSections & { isDark: boolean }): string => {
290
+ const backgroundColor = isDark ? colorPalette.black : colorPalette.white;
291
+ const columnWidth = TOOLTIP_STYLES.MOBILE_COLUMN_WIDTH;
292
+ const fullWidth = TOOLTIP_STYLES.FULL_WIDTH;
293
+ const padding = TOOLTIP_STYLES.PADDING_BOTTOM;
294
+
295
+ return `<div style="width: ${fullWidth}; background-color: ${backgroundColor};">
296
+ <div style="padding-bottom: ${padding};">${timeFormatted}</div>
297
+ <div style="width: ${fullWidth}; display: flex; justify-content: space-between;">
298
+ <div style="width: ${columnWidth};">
209
299
  ${candleSection}
300
+ </div>
301
+ <div style="width: ${columnWidth};">
302
+ ${sentimentSection}
303
+ </div>
304
+ <div style="width: ${columnWidth};">
210
305
  ${bookSection}
211
- ${sentimentSection}`;
306
+ </div>
307
+ </div>
308
+ </div>`;
212
309
  };
@@ -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
  ],
@@ -8,3 +8,4 @@ export * from './getTooltipFormatter';
8
8
  export * from './handleLabelUpdate';
9
9
  export * from './handleTooltipUpdate';
10
10
  export * from './isDifferenceGreaterThanTwoWeeks';
11
+ export * from './normalizeLocale';
@@ -0,0 +1,3 @@
1
+ export const normalizeLocale = (locale: string): string => {
2
+ return locale === 'en' ? 'en-GB' : locale;
3
+ };
@@ -60,7 +60,7 @@ export const processPriceCandles = (
60
60
  candlesClose,
61
61
  candlesLow,
62
62
  candlesHigh,
63
- dates,
63
+ dates: [...dates, new Date().toISOString()],
64
64
  hasValidCandles: true,
65
65
  pipsLocation: priceCandlesData.priceCandles.pipsLocation,
66
66
  };
@@ -40,6 +40,7 @@ export const getOption: GetOptionType = ({
40
40
  isDark,
41
41
  isDesktop,
42
42
  labelCallback,
43
+ locale,
43
44
  }) => {
44
45
  const styles = getChartStyles(isDark);
45
46
  const selectedPriceRef = { current: 0 };
@@ -94,13 +95,18 @@ export const getOption: GetOptionType = ({
94
95
  tooltipLinesColor: styles.tooltipLinesColor,
95
96
  sentimentLongs,
96
97
  sentimentShorts,
98
+ isDesktop,
99
+ isDark,
100
+ locale,
97
101
  }),
98
102
  grid: getGridConfig({ isDesktop }),
99
103
  xAxis: getXAxisConfig({
100
104
  dates,
101
105
  isGreaterThanTwoWeeks,
106
+ locale,
102
107
  }),
103
108
  yAxis: getYAxisConfig({
109
+ isDesktop,
104
110
  bucketWidth,
105
111
  displayPrecision,
106
112
  }),
@@ -57,6 +57,7 @@ interface GetOptionProps {
57
57
  isDesktop: boolean;
58
58
  isLoading: boolean;
59
59
  labelCallback: (key: string, params?: Record<string, unknown>) => string;
60
+ locale: string;
60
61
  }
61
62
 
62
63
  export type GetOptionType = (props: GetOptionProps) => EChartsOption;
@@ -0,0 +1,51 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ interface UseResizeObserverOptions {
4
+ debounceDelay?: number;
5
+ }
6
+
7
+ export const useResizeObserver = (
8
+ options: UseResizeObserverOptions = {}
9
+ ): [React.RefObject<HTMLDivElement>, boolean] => {
10
+ const { debounceDelay = 150 } = options;
11
+ const [isResizing, setIsResizing] = useState(false);
12
+ const containerRef = useRef<HTMLDivElement>(null);
13
+ const resizeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
14
+
15
+ useEffect(() => {
16
+ const container = containerRef.current;
17
+ if (!container) {
18
+ return undefined;
19
+ }
20
+
21
+ if (typeof ResizeObserver === 'undefined') {
22
+ console.warn('ResizeObserver is not supported in this browser');
23
+ return undefined;
24
+ }
25
+
26
+ const resizeObserver = new ResizeObserver(() => {
27
+ setIsResizing(true);
28
+
29
+ if (resizeTimeoutRef.current) {
30
+ clearTimeout(resizeTimeoutRef.current);
31
+ }
32
+
33
+ resizeTimeoutRef.current = setTimeout(() => {
34
+ setIsResizing(false);
35
+ resizeTimeoutRef.current = null;
36
+ }, debounceDelay);
37
+ });
38
+
39
+ resizeObserver.observe(container);
40
+
41
+ return () => {
42
+ resizeObserver.disconnect();
43
+ if (resizeTimeoutRef.current) {
44
+ clearTimeout(resizeTimeoutRef.current);
45
+ resizeTimeoutRef.current = null;
46
+ }
47
+ };
48
+ }, [debounceDelay]);
49
+
50
+ return [containerRef, isResizing];
51
+ };
@@ -12,14 +12,15 @@ interface LegendBarProps {
12
12
 
13
13
  export const LegendBar = ({ type, isDark }: LegendBarProps) => {
14
14
  const colorPalette = isDark ? COLOR_MAP.dark : COLOR_MAP.light;
15
+
15
16
  const startColor =
16
17
  type === 'long' ? colorPalette.long[1] : colorPalette.short[0];
17
18
  const endColor =
18
19
  type === 'long' ? colorPalette.long[0] : colorPalette.short[1];
19
20
  const endGradient =
20
21
  type === 'long'
21
- ? `linear-gradient(90deg,${endColor} 0%, transparent 100%)`
22
- : `linear-gradient(270deg,${startColor} 0%, transparent 100%)`;
22
+ ? `linear-gradient(90deg,${endColor} 0%, ${colorPalette.backgroundColor} 100%)`
23
+ : `linear-gradient(270deg,${startColor} 0%, ${colorPalette.backgroundColor} 100%)`;
23
24
 
24
25
  return (
25
26
  <div
@@ -15,9 +15,9 @@ export const TIME_THRESHOLDS = {
15
15
  TWO_WEEKS_MS: 14 * 24 * 60 * 60 * 1000,
16
16
  } as const;
17
17
 
18
- export const CHART_CONFIG = {
19
- HEIGHT: 440,
20
- MAIN_HEIGHT: 400,
18
+ const CHART_CONFIG_STATIC = {
19
+ MAIN_HEIGHT_DESKTOP: 410,
20
+ MAIN_HEIGHT_MOBILE: 330,
21
21
  MARGIN_BETWEEN: 50,
22
22
  SENTIMENT_HEIGHT: 120,
23
23
  WIDTH: 9999,
@@ -33,8 +33,25 @@ export const CHART_CONFIG = {
33
33
  SENTIMENT_MIN: 0,
34
34
  SENTIMENT_MAX: 100,
35
35
  TOP_MARGIN_DESKTOP: 0,
36
- TOP_MARGIN_MOBILE: 30,
37
- TOOLTIP_OFFSET: 5,
36
+ TOP_MARGIN_MOBILE: 24,
37
+ MOBILE_TOOLTIP_HEIGHT: 85,
38
+ TOOLTIP_OFFSET: 8,
39
+ };
40
+
41
+ const CHART_CONFIG_CALCULATED = {
42
+ HEIGHT_DESKTOP:
43
+ CHART_CONFIG_STATIC.MAIN_HEIGHT_DESKTOP +
44
+ CHART_CONFIG_STATIC.TOP_MARGIN_DESKTOP +
45
+ CHART_CONFIG_STATIC.X_LABEL_SIZE,
46
+ HEIGHT_MOBILE:
47
+ CHART_CONFIG_STATIC.MAIN_HEIGHT_MOBILE +
48
+ CHART_CONFIG_STATIC.X_LABEL_SIZE +
49
+ CHART_CONFIG_STATIC.TOP_MARGIN_MOBILE +
50
+ CHART_CONFIG_STATIC.MOBILE_TOOLTIP_HEIGHT,
51
+ };
52
+ export const CHART_CONFIG = {
53
+ ...CHART_CONFIG_STATIC,
54
+ ...CHART_CONFIG_CALCULATED,
38
55
  } as const;
39
56
 
40
57
  export const COLOR_MAP = {
@@ -47,6 +64,7 @@ export const COLOR_MAP = {
47
64
  chroma(colorPalette.darkYellow10).shade(0.5).hex(),
48
65
  chroma(colorPalette.darkYellow90).shade(0.5).hex(),
49
66
  ],
67
+ backgroundColor: colorPalette.black,
50
68
  },
51
69
  light: {
52
70
  long: [
@@ -57,6 +75,7 @@ export const COLOR_MAP = {
57
75
  chroma(colorPalette.lightYellow10).tint(0.5).hex(),
58
76
  chroma(colorPalette.lightYellow90).tint(0.5).hex(),
59
77
  ],
78
+ backgroundColor: colorPalette.white,
60
79
  },
61
80
  } as const;
62
81
 
@@ -13,6 +13,10 @@ const navigationConfig = [
13
13
  ];
14
14
 
15
15
  const instrumentSelectConfigOC = [
16
+ {
17
+ id: InstrumentId.EUR_USD,
18
+ label: 'EUR/USD',
19
+ },
16
20
  {
17
21
  id: InstrumentId.EUR_AUD,
18
22
  label: 'EUR/AUD',
@@ -25,10 +29,6 @@ const instrumentSelectConfigOC = [
25
29
  id: InstrumentId.EUR_JPY,
26
30
  label: 'EUR/JPY',
27
31
  },
28
- {
29
- id: InstrumentId.EUR_USD,
30
- label: 'EUR/USD',
31
- },
32
32
  {
33
33
  id: InstrumentId.EUR_CHF,
34
34
  label: 'EUR/CHF',
@@ -3,31 +3,32 @@
3
3
  "15_minutes": "15 minutes",
4
4
  "4_hours": "4 hours",
5
5
  "5_minutes": "5 minutes",
6
- "buy_overbalance": "Buy overbalance",
7
6
  "buy": "Buy",
7
+ "buy_overbalance": "Buy overbalance",
8
8
  "candle": "Candle",
9
- "close_price": "Close price",
9
+ "close": "Close",
10
10
  "data_unavailable": "Data unavailable",
11
+ "even_market_demand": "Even market demand",
11
12
  "granularity": "Granularity",
12
13
  "high": "High",
13
14
  "instrument": "Instrument",
14
- "long_overbalance": "Long overbalance",
15
15
  "long": "Long",
16
+ "long_overbalance": "Long overbalance",
16
17
  "low": "Low",
17
18
  "no_matching_results": "No matching results",
18
- "open_price": "Open price",
19
+ "open": "Open",
19
20
  "order_book": "Order book",
20
21
  "orders": "Orders",
21
22
  "pagination_entries_range": "{{firstItemOnPage}}-{{lastItemOnPage}} of {{itemCount}} entries",
22
23
  "position_book": "Position book",
23
- "positions": "Positions",
24
+ "price": "Price",
24
25
  "price_range": "Price range",
25
26
  "search": "Search",
26
- "sell_overbalance": "Sell overbalance",
27
27
  "sell": "Sell",
28
+ "sell_overbalance": "Sell overbalance",
28
29
  "sentiment": "Sentiment",
29
- "short_overbalance": "Short overbalance",
30
30
  "short": "Short",
31
- "even_market_demand": "Even market demand",
32
- "price": "Price"
31
+ "short_overbalance": "Short overbalance",
32
+ "tap_chart_to_see_more_details": "Tap chart to see more details",
33
+ "trades": "Trades"
33
34
  }
@@ -110,13 +110,13 @@ describe('chartUtils', () => {
110
110
  const sampleIso = '2025-03-15T10:30:00Z';
111
111
 
112
112
  it('formats time when flag is true', () => {
113
- const result = formatXAxisLabel(sampleIso, true);
113
+ const result = formatXAxisLabel(sampleIso, true, 'en-US');
114
114
  expect(typeof result).toBe('string');
115
115
  expect(result).toContain(':');
116
116
  });
117
117
 
118
118
  it('formats day when flag is false', () => {
119
- const result = formatXAxisLabel(sampleIso, false);
119
+ const result = formatXAxisLabel(sampleIso, false, 'en-US');
120
120
  expect(typeof result).toBe('string');
121
121
  // Contains day of month (15) with surrounding spaces per implementation
122
122
  expect(result).toMatch(/\s15\s/);
@@ -189,11 +189,14 @@ describe('chartUtils', () => {
189
189
  labelCallback,
190
190
  sentimentLongs: [],
191
191
  sentimentShorts: [],
192
+ isDesktop: true,
193
+ locale: 'en-US',
194
+ isDark: false,
192
195
  });
193
196
  expect(html).toBeDefined();
194
197
  expect(html).toContain('candle');
195
- expect(html).toContain('open_price');
196
- expect(html).toContain('close_price');
198
+ expect(html).toContain('open');
199
+ expect(html).toContain('close');
197
200
  expect(html).toContain('low');
198
201
  expect(html).toContain('high');
199
202
  expect(html).toContain('orders');
@@ -227,6 +230,9 @@ describe('chartUtils', () => {
227
230
  labelCallback,
228
231
  sentimentLongs: [30.5],
229
232
  sentimentShorts: [69.5],
233
+ isDesktop: true,
234
+ locale: 'en-US',
235
+ isDark: false,
230
236
  });
231
237
  expect(html).toBeDefined();
232
238
  expect(html).toContain('candle');
@@ -257,6 +263,9 @@ describe('chartUtils', () => {
257
263
  labelCallback,
258
264
  sentimentLongs: [],
259
265
  sentimentShorts: [],
266
+ isDesktop: true,
267
+ locale: 'en-US',
268
+ isDark: false,
260
269
  });
261
270
  expect(html).toBe('');
262
271
  });
@@ -271,6 +280,9 @@ describe('chartUtils', () => {
271
280
  labelCallback,
272
281
  sentimentLongs: [],
273
282
  sentimentShorts: [],
283
+ isDesktop: true,
284
+ locale: 'en-US',
285
+ isDark: false,
274
286
  });
275
287
  expect(html).toBe('');
276
288
  });