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

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 (168) hide show
  1. package/CHANGELOG.md +224 -0
  2. package/dist/main/CrowdViewWidget/components/Chart/Chart.js +37 -56
  3. package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  4. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js +20 -7
  5. package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  6. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getDataZoomConfig.js +15 -9
  7. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getDataZoomConfig.js.map +1 -1
  8. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js +6 -6
  9. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js.map +1 -1
  10. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js +41 -13
  11. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js.map +1 -1
  12. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js +159 -0
  13. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js.map +1 -0
  14. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getSeriesCandlestickConfig.js +7 -14
  15. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getSeriesCandlestickConfig.js.map +1 -1
  16. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getSeriesHeatmapConfig.js +20 -2
  17. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getSeriesHeatmapConfig.js.map +1 -1
  18. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getSeriesSentimentConfig.js +13 -5
  19. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getSeriesSentimentConfig.js.map +1 -1
  20. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js +10 -3
  21. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js.map +1 -1
  22. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js +20 -5
  23. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js.map +1 -1
  24. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js +19 -28
  25. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js.map +1 -1
  26. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/index.js +11 -0
  27. package/dist/main/CrowdViewWidget/components/Chart/chartOptions/index.js.map +1 -1
  28. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js +18 -0
  29. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js.map +1 -0
  30. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/getLabelData.js +1 -20
  31. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/getLabelData.js.map +1 -1
  32. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js +21 -9
  33. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js.map +1 -1
  34. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js +59 -0
  35. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js.map +1 -0
  36. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/handleTooltipUpdate.js +34 -0
  37. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/handleTooltipUpdate.js.map +1 -0
  38. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/index.js +33 -0
  39. package/dist/main/CrowdViewWidget/components/Chart/chartUtils/index.js.map +1 -1
  40. package/dist/main/CrowdViewWidget/components/Chart/dataUtils/getMultiplayerForTimeSpan.js +19 -0
  41. package/dist/main/CrowdViewWidget/components/Chart/dataUtils/getMultiplayerForTimeSpan.js.map +1 -0
  42. package/dist/main/CrowdViewWidget/components/Chart/dataUtils/index.js +11 -0
  43. package/dist/main/CrowdViewWidget/components/Chart/dataUtils/index.js.map +1 -1
  44. package/dist/main/CrowdViewWidget/components/Chart/getOption.js +33 -21
  45. package/dist/main/CrowdViewWidget/components/Chart/getOption.js.map +1 -1
  46. package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
  47. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js +7 -4
  48. package/dist/main/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  49. package/dist/main/CrowdViewWidget/components/Legend/Legend.js +1 -1
  50. package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  51. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js +11 -1
  52. package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  53. package/dist/main/CrowdViewWidget/constants.js +13 -5
  54. package/dist/main/CrowdViewWidget/constants.js.map +1 -1
  55. package/dist/main/CrowdViewWidget/selectConfig.js +0 -6
  56. package/dist/main/CrowdViewWidget/selectConfig.js.map +1 -1
  57. package/dist/main/translations/sources/en.json +2 -1
  58. package/dist/module/CrowdViewWidget/components/Chart/Chart.js +38 -56
  59. package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
  60. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +19 -6
  61. package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
  62. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getDataZoomConfig.js +15 -9
  63. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getDataZoomConfig.js.map +1 -1
  64. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js +6 -6
  65. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.js.map +1 -1
  66. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js +41 -13
  67. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getGridLines.js.map +1 -1
  68. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js +151 -0
  69. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.js.map +1 -0
  70. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getSeriesCandlestickConfig.js +7 -14
  71. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getSeriesCandlestickConfig.js.map +1 -1
  72. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getSeriesHeatmapConfig.js +20 -2
  73. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getSeriesHeatmapConfig.js.map +1 -1
  74. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getSeriesSentimentConfig.js +13 -5
  75. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getSeriesSentimentConfig.js.map +1 -1
  76. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js +10 -3
  77. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.js.map +1 -1
  78. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js +21 -6
  79. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.js.map +1 -1
  80. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js +19 -28
  81. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.js.map +1 -1
  82. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/index.js +1 -0
  83. package/dist/module/CrowdViewWidget/components/Chart/chartOptions/index.js.map +1 -1
  84. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js +11 -0
  85. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.js.map +1 -0
  86. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/getLabelData.js +1 -20
  87. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/getLabelData.js.map +1 -1
  88. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js +21 -9
  89. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.js.map +1 -1
  90. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js +52 -0
  91. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.js.map +1 -0
  92. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/handleTooltipUpdate.js +27 -0
  93. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/handleTooltipUpdate.js.map +1 -0
  94. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/index.js +3 -0
  95. package/dist/module/CrowdViewWidget/components/Chart/chartUtils/index.js.map +1 -1
  96. package/dist/module/CrowdViewWidget/components/Chart/dataUtils/getMultiplayerForTimeSpan.js +12 -0
  97. package/dist/module/CrowdViewWidget/components/Chart/dataUtils/getMultiplayerForTimeSpan.js.map +1 -0
  98. package/dist/module/CrowdViewWidget/components/Chart/dataUtils/index.js +1 -0
  99. package/dist/module/CrowdViewWidget/components/Chart/dataUtils/index.js.map +1 -1
  100. package/dist/module/CrowdViewWidget/components/Chart/getOption.js +34 -22
  101. package/dist/module/CrowdViewWidget/components/Chart/getOption.js.map +1 -1
  102. package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
  103. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js +8 -5
  104. package/dist/module/CrowdViewWidget/components/Chart/useCrowdViewData.js.map +1 -1
  105. package/dist/module/CrowdViewWidget/components/Legend/Legend.js +1 -1
  106. package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
  107. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js +11 -1
  108. package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
  109. package/dist/module/CrowdViewWidget/constants.js +13 -5
  110. package/dist/module/CrowdViewWidget/constants.js.map +1 -1
  111. package/dist/module/CrowdViewWidget/selectConfig.js +0 -6
  112. package/dist/module/CrowdViewWidget/selectConfig.js.map +1 -1
  113. package/dist/module/translations/sources/en.json +2 -1
  114. package/dist/types/CrowdViewWidget/components/Chart/Chart.d.ts +1 -1
  115. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getDataZoomConfig.d.ts +5 -1
  116. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getGridLines.d.ts +2 -1
  117. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.d.ts +8 -0
  118. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getSeriesCandlestickConfig.d.ts +2 -3
  119. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getSeriesHeatmapConfig.d.ts +2 -1
  120. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getSeriesSentimentConfig.d.ts +2 -1
  121. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.d.ts +3 -1
  122. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.d.ts +1 -3
  123. package/dist/types/CrowdViewWidget/components/Chart/chartOptions/index.d.ts +1 -0
  124. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.d.ts +1 -0
  125. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/getLabelData.d.ts +1 -16
  126. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.d.ts +3 -1
  127. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.d.ts +4 -0
  128. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/handleTooltipUpdate.d.ts +3 -0
  129. package/dist/types/CrowdViewWidget/components/Chart/chartUtils/index.d.ts +3 -0
  130. package/dist/types/CrowdViewWidget/components/Chart/dataUtils/getMultiplayerForTimeSpan.d.ts +2 -0
  131. package/dist/types/CrowdViewWidget/components/Chart/dataUtils/index.d.ts +1 -0
  132. package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +5 -0
  133. package/dist/types/CrowdViewWidget/constants.d.ts +12 -4
  134. package/package.json +3 -3
  135. package/src/CrowdViewWidget/components/Chart/Chart.tsx +70 -79
  136. package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +31 -13
  137. package/src/CrowdViewWidget/components/Chart/chartOptions/getDataZoomConfig.ts +11 -2
  138. package/src/CrowdViewWidget/components/Chart/chartOptions/getGridConfig.ts +6 -6
  139. package/src/CrowdViewWidget/components/Chart/chartOptions/getGridLines.ts +48 -6
  140. package/src/CrowdViewWidget/components/Chart/chartOptions/getLabelsConfig.ts +191 -0
  141. package/src/CrowdViewWidget/components/Chart/chartOptions/getSeriesCandlestickConfig.ts +7 -15
  142. package/src/CrowdViewWidget/components/Chart/chartOptions/getSeriesHeatmapConfig.ts +22 -1
  143. package/src/CrowdViewWidget/components/Chart/chartOptions/getSeriesSentimentConfig.ts +13 -4
  144. package/src/CrowdViewWidget/components/Chart/chartOptions/getTooltipConfig.ts +12 -1
  145. package/src/CrowdViewWidget/components/Chart/chartOptions/getXAxisConfig.ts +55 -30
  146. package/src/CrowdViewWidget/components/Chart/chartOptions/getYAxisConfig.ts +13 -30
  147. package/src/CrowdViewWidget/components/Chart/chartOptions/index.ts +1 -0
  148. package/src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel.ts +13 -0
  149. package/src/CrowdViewWidget/components/Chart/chartUtils/getLabelData.ts +11 -35
  150. package/src/CrowdViewWidget/components/Chart/chartUtils/getTooltipFormatter.ts +40 -11
  151. package/src/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate.ts +75 -0
  152. package/src/CrowdViewWidget/components/Chart/chartUtils/handleTooltipUpdate.ts +37 -0
  153. package/src/CrowdViewWidget/components/Chart/chartUtils/index.ts +3 -0
  154. package/src/CrowdViewWidget/components/Chart/dataUtils/getMultiplayerForTimeSpan.ts +13 -0
  155. package/src/CrowdViewWidget/components/Chart/dataUtils/index.ts +1 -0
  156. package/src/CrowdViewWidget/components/Chart/getOption.ts +41 -17
  157. package/src/CrowdViewWidget/components/Chart/types.ts +5 -0
  158. package/src/CrowdViewWidget/components/Chart/useCrowdViewData.ts +12 -5
  159. package/src/CrowdViewWidget/components/Legend/Legend.tsx +1 -1
  160. package/src/CrowdViewWidget/components/Legend/LegendBar.tsx +17 -1
  161. package/src/CrowdViewWidget/constants.ts +12 -4
  162. package/src/CrowdViewWidget/selectConfig.ts +0 -8
  163. package/src/translations/sources/en.json +2 -1
  164. package/test/components/Chart/utils/chartUtils.test.ts +13 -2
  165. package/test/components/Chart/utils/handleLabelUpdate.test.ts +419 -0
  166. package/test/components/Chart/utils/handleTooltipUpdate.test.ts +140 -0
  167. package/test/components/Legend.test.tsx +3 -3
  168. package/test/components/LegendBar.test.tsx +22 -20
@@ -15,6 +15,7 @@ import type {
15
15
  import { BookType, DataSource, Division } from '../../../gql/types/graphql';
16
16
  import { BUCKET_CONFIG, INSTRUMENTS_CONFIG } from '../../constants';
17
17
  import {
18
+ getMultiplayerForTimeSpan,
18
19
  getTimeSpanForGranularity,
19
20
  processOrderPositionBooks,
20
21
  processPriceCandles,
@@ -83,7 +84,7 @@ export const useCrowdViewData = ({
83
84
  granularity,
84
85
  maxBookPrice: maxPrice,
85
86
  minBookPrice: minPrice,
86
- bucketMultiplier: 2,
87
+ bucketMultiplier: getMultiplayerForTimeSpan(granularity),
87
88
  bucketMargin: BUCKET_CONFIG.PRICE_MARGIN_MULTIPLIER,
88
89
  },
89
90
  fetchPolicy: 'no-cache',
@@ -161,7 +162,7 @@ export const useCrowdViewData = ({
161
162
  ]);
162
163
 
163
164
  const data = useMemo(() => {
164
- if (!priceCandlesData || !orderPositionData || !sentimentsData || error) {
165
+ if (!priceCandlesData || error) {
165
166
  return null;
166
167
  }
167
168
 
@@ -178,7 +179,10 @@ export const useCrowdViewData = ({
178
179
  sentimentLongs,
179
180
  },
180
181
  additionalData: {
181
- bucketWidth,
182
+ bucketWidth:
183
+ bucketWidth ||
184
+ INSTRUMENTS_CONFIG[instrument].defaultBucketWidth *
185
+ getMultiplayerForTimeSpan(granularity),
182
186
  buckets,
183
187
  displayPrecision: pipsLocation,
184
188
  bookType,
@@ -188,8 +192,6 @@ export const useCrowdViewData = ({
188
192
  };
189
193
  }, [
190
194
  priceCandlesData,
191
- orderPositionData,
192
- sentimentsData,
193
195
  error,
194
196
  dates,
195
197
  candlesOpen,
@@ -201,6 +203,8 @@ export const useCrowdViewData = ({
201
203
  sentimentShorts,
202
204
  sentimentLongs,
203
205
  bucketWidth,
206
+ granularity,
207
+ instrument,
204
208
  buckets,
205
209
  pipsLocation,
206
210
  bookType,
@@ -211,6 +215,9 @@ export const useCrowdViewData = ({
211
215
  return {
212
216
  mainData: data?.mainData,
213
217
  additionalData: data?.additionalData,
218
+ priceCandlesLoading,
219
+ orderPositionLoading,
220
+ sentimentsLoading,
214
221
  loading,
215
222
  error: !!error,
216
223
  };
@@ -17,7 +17,7 @@ export const Legend = ({ bookType }: LegendProps) => {
17
17
  <div className="lw-my-4 lw-w-full">
18
18
  <div className="lw-mb-1 lw-flex lw-w-full lw-flex-row lw-justify-between">
19
19
  <LegendBar isDark={isDark} type="long" />
20
- <div className="lw-w-[250px]" />
20
+ <div className="lw-w-[200px]" />
21
21
  <LegendBar isDark={isDark} type="short" />
22
22
  </div>
23
23
  <div className="lw-flex lw-w-full lw-flex-row lw-justify-between lw-pb-2 lw-font-sans lw-text-xs lw-text-text-primary">
@@ -1,3 +1,4 @@
1
+ import { cn } from '@oanda/labs-widget-common';
1
2
  import React from 'react';
2
3
 
3
4
  import { COLOR_MAP } from '../../constants';
@@ -15,9 +16,17 @@ export const LegendBar = ({ type, isDark }: LegendBarProps) => {
15
16
  type === 'long' ? colorPalette.long[1] : colorPalette.short[0];
16
17
  const endColor =
17
18
  type === 'long' ? colorPalette.long[0] : colorPalette.short[1];
19
+ const endGradient =
20
+ type === 'long'
21
+ ? `linear-gradient(90deg,${endColor} 0%, transparent 100%)`
22
+ : `linear-gradient(270deg,${startColor} 0%, transparent 100%)`;
18
23
 
19
24
  return (
20
- <div className="lw-flex lw-h-2 lw-w-full lw-overflow-hidden">
25
+ <div
26
+ className={cn('lw-flex lw-h-2 lw-w-full lw-overflow-hidden', {
27
+ 'lw-flex-row-reverse': type === 'short',
28
+ })}
29
+ >
21
30
  <div
22
31
  className="lw-h-full lw-flex-1"
23
32
  data-testid="legend-bar-segment"
@@ -25,6 +34,13 @@ export const LegendBar = ({ type, isDark }: LegendBarProps) => {
25
34
  background: `linear-gradient(90deg,${startColor} 0%, ${endColor} 100%)`,
26
35
  }}
27
36
  />
37
+ <div
38
+ className="lw-h-full lw-w-[10px]"
39
+ data-testid="legend-bar-segment"
40
+ style={{
41
+ background: endGradient,
42
+ }}
43
+ />
28
44
  </div>
29
45
  );
30
46
  };
@@ -4,7 +4,10 @@ import chroma from 'chroma-js';
4
4
  import { InstrumentId } from './types';
5
5
 
6
6
  export const BUCKET_CONFIG = {
7
- MULTIPLIER: 4,
7
+ MULTIPLIER_5M: 1,
8
+ MULTIPLIER_15M: 1,
9
+ MULTIPLIER_1H: 2,
10
+ MULTIPLIER_4H: 4,
8
11
  PRICE_MARGIN_MULTIPLIER: 2,
9
12
  } as const;
10
13
 
@@ -19,14 +22,19 @@ export const CHART_CONFIG = {
19
22
  SENTIMENT_HEIGHT: 120,
20
23
  WIDTH: 9999,
21
24
  X_LABEL_SIZE: 40,
22
- Y_LABEL_SIZE_DESKTOP: 60,
25
+ Y_LABEL_SIZE_DESKTOP: 50,
23
26
  Y_LABEL_SIZE_MOBILE: 40,
24
- Y_SENTIMENT_LABEL_SIZE: 40,
25
- INITIAL_START_ZOOM: 90,
27
+ Y_SENTIMENT_LABEL_DESKTOP_SIZE: 40,
28
+ Y_SENTIMENT_LABEL_MOBILE_SIZE: 30,
29
+ INITIAL_START_ZOOM_DESKTOP: 70,
30
+ INITIAL_START_ZOOM_MOBILE: 85,
26
31
  INITIAL_END_ZOOM: 100,
27
32
  X_AXIS_DATE_PADDING: ' ',
28
33
  SENTIMENT_MIN: 0,
29
34
  SENTIMENT_MAX: 100,
35
+ TOP_MARGIN_DESKTOP: 0,
36
+ TOP_MARGIN_MOBILE: 30,
37
+ TOOLTIP_OFFSET: 5,
30
38
  } as const;
31
39
 
32
40
  export const COLOR_MAP = {
@@ -92,14 +92,6 @@ const granularitySelectConfig = [
92
92
  id: Granularity.H4,
93
93
  label: '4_hours',
94
94
  },
95
- {
96
- id: Granularity.M5,
97
- label: '5_minutes',
98
- },
99
- {
100
- id: Granularity.M15,
101
- label: '15_minutes',
102
- },
103
95
  ];
104
96
 
105
97
  export {
@@ -28,5 +28,6 @@
28
28
  "sentiment": "Sentiment",
29
29
  "short_overbalance": "Short overbalance",
30
30
  "short": "Short",
31
- "even_market_demand": "Even market demand"
31
+ "even_market_demand": "Even market demand",
32
+ "price": "Price"
32
33
  }
@@ -138,7 +138,7 @@ describe('chartUtils', () => {
138
138
  });
139
139
  // First change happens between 1st and 2nd
140
140
  expect(labels.length).toBeGreaterThanOrEqual(2);
141
- expect(labels[0]).toHaveProperty('xAxis', '2025-03-02T00:00:00Z');
141
+ expect(labels[0]).toBe('2025-03-02T00:00:00Z');
142
142
  });
143
143
 
144
144
  it('emits label when month changes for >= two weeks case', () => {
@@ -152,7 +152,7 @@ describe('chartUtils', () => {
152
152
  isGreaterThanTwoWeeks: false,
153
153
  });
154
154
  expect(labels.length).toBe(1);
155
- expect(labels[0]).toHaveProperty('xAxis', '2025-02-01T00:00:00Z');
155
+ expect(labels[0]).toBe('2025-02-01T00:00:00Z');
156
156
  });
157
157
  });
158
158
 
@@ -165,6 +165,7 @@ describe('chartUtils', () => {
165
165
  seriesId: 'candlestick' as const,
166
166
  axisValue: '2025-03-15T10:30:00Z',
167
167
  value: [0, 1.11111, 1.22222, 1.00001, 1.33333],
168
+ dataIndex: 0,
168
169
  },
169
170
  {
170
171
  seriesId: 'heatmap' as const,
@@ -186,6 +187,8 @@ describe('chartUtils', () => {
186
187
  selectedPrice: 1.3306,
187
188
  bookType: BookType.Order,
188
189
  labelCallback,
190
+ sentimentLongs: [],
191
+ sentimentShorts: [],
189
192
  });
190
193
  expect(html).toBeDefined();
191
194
  expect(html).toContain('candle');
@@ -205,6 +208,7 @@ describe('chartUtils', () => {
205
208
  seriesId: 'candlestick' as const,
206
209
  axisValue: '2025-03-15T10:30:00Z',
207
210
  value: [0, 1.11111, 1.22222, 1.00001, 1.33333],
211
+ dataIndex: 0,
208
212
  },
209
213
  {
210
214
  seriesId: 'sentiment' as const,
@@ -221,6 +225,8 @@ describe('chartUtils', () => {
221
225
  selectedPrice: 1.3306,
222
226
  bookType: BookType.Order,
223
227
  labelCallback,
228
+ sentimentLongs: [30.5],
229
+ sentimentShorts: [69.5],
224
230
  });
225
231
  expect(html).toBeDefined();
226
232
  expect(html).toContain('candle');
@@ -236,6 +242,7 @@ describe('chartUtils', () => {
236
242
  {
237
243
  seriesId: 'heatmap' as const,
238
244
  value: ['2025-03-15T10:30:00Z', 1.33333, 0],
245
+ dataIndex: 0,
239
246
  },
240
247
  ];
241
248
 
@@ -248,6 +255,8 @@ describe('chartUtils', () => {
248
255
  selectedPrice: 1.3306,
249
256
  bookType: BookType.Order,
250
257
  labelCallback,
258
+ sentimentLongs: [],
259
+ sentimentShorts: [],
251
260
  });
252
261
  expect(html).toBe('');
253
262
  });
@@ -260,6 +269,8 @@ describe('chartUtils', () => {
260
269
  selectedPrice: 1.3306,
261
270
  bookType: BookType.Order,
262
271
  labelCallback,
272
+ sentimentLongs: [],
273
+ sentimentShorts: [],
263
274
  });
264
275
  expect(html).toBe('');
265
276
  });
@@ -0,0 +1,419 @@
1
+ import type { EChartsType } from 'echarts';
2
+
3
+ import { formatXAxisAdditionalLabel } from '../../../../src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel';
4
+ import { formatXAxisLabel } from '../../../../src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel';
5
+ import { getLabelData } from '../../../../src/CrowdViewWidget/components/Chart/chartUtils/getLabelData';
6
+ import { handleLabelUpdate } from '../../../../src/CrowdViewWidget/components/Chart/chartUtils/handleLabelUpdate';
7
+ import { isDifferenceGreaterThanTwoWeeks } from '../../../../src/CrowdViewWidget/components/Chart/chartUtils/isDifferenceGreaterThanTwoWeeks';
8
+ import type { ChartProps } from '../../../../src/CrowdViewWidget/components/Chart/types';
9
+
10
+ // Mock dependencies
11
+ jest.mock(
12
+ '../../../../src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisAdditionalLabel'
13
+ );
14
+ jest.mock(
15
+ '../../../../src/CrowdViewWidget/components/Chart/chartUtils/formatXAxisLabel'
16
+ );
17
+ jest.mock(
18
+ '../../../../src/CrowdViewWidget/components/Chart/chartUtils/getLabelData'
19
+ );
20
+ jest.mock(
21
+ '../../../../src/CrowdViewWidget/components/Chart/chartUtils/isDifferenceGreaterThanTwoWeeks'
22
+ );
23
+
24
+ const mockFormatXAxisLabel = formatXAxisLabel as jest.MockedFunction<
25
+ typeof formatXAxisLabel
26
+ >;
27
+ const mockFormatXAxisAdditionalLabel =
28
+ formatXAxisAdditionalLabel as jest.MockedFunction<
29
+ typeof formatXAxisAdditionalLabel
30
+ >;
31
+ const mockGetLabelData = getLabelData as jest.MockedFunction<
32
+ typeof getLabelData
33
+ >;
34
+ const mockIsDifferenceGreaterThanTwoWeeks =
35
+ isDifferenceGreaterThanTwoWeeks as jest.MockedFunction<
36
+ typeof isDifferenceGreaterThanTwoWeeks
37
+ >;
38
+
39
+ describe('handleLabelUpdate', () => {
40
+ let mockInstance: jest.Mocked<EChartsType>;
41
+ let labelTimerRef: { current: NodeJS.Timeout | null };
42
+ let isGreaterThanTwoWeeksRef: { current: boolean | null };
43
+ let mainData: ChartProps['mainData'];
44
+
45
+ beforeEach(() => {
46
+ jest.useFakeTimers();
47
+ jest.clearAllMocks();
48
+
49
+ mockInstance = {
50
+ getOption: jest.fn(),
51
+ setOption: jest.fn(),
52
+ } as unknown as jest.Mocked<EChartsType>;
53
+
54
+ labelTimerRef = { current: null };
55
+ isGreaterThanTwoWeeksRef = { current: null };
56
+
57
+ mainData = {
58
+ dates: [
59
+ '2025-01-01T00:00:00Z',
60
+ '2025-01-02T00:00:00Z',
61
+ '2025-01-03T00:00:00Z',
62
+ '2025-01-04T00:00:00Z',
63
+ '2025-01-05T00:00:00Z',
64
+ ],
65
+ candlesOpen: [1.1, 1.2, 1.3, 1.4, 1.5],
66
+ candlesClose: [1.15, 1.25, 1.35, 1.45, 1.55],
67
+ candlesLow: [1.05, 1.15, 1.25, 1.35, 1.45],
68
+ candlesHigh: [1.2, 1.3, 1.4, 1.5, 1.6],
69
+ sentimentShorts: [10, 20, 30, 40, 50],
70
+ sentimentLongs: [90, 80, 70, 60, 50],
71
+ };
72
+
73
+ mockFormatXAxisLabel.mockReturnValue('formatted-label');
74
+ mockFormatXAxisAdditionalLabel.mockReturnValue('formatted-additional');
75
+ mockGetLabelData.mockReturnValue([
76
+ '2025-01-02T00:00:00Z',
77
+ '2025-01-04T00:00:00Z',
78
+ ]);
79
+ mockIsDifferenceGreaterThanTwoWeeks.mockReturnValue(false);
80
+ });
81
+
82
+ afterEach(() => {
83
+ jest.runOnlyPendingTimers();
84
+ jest.useRealTimers();
85
+ });
86
+
87
+ describe('when dataZoom is invalid', () => {
88
+ it('should return early if dataZoom is not an array', () => {
89
+ mockInstance.getOption.mockReturnValue({
90
+ dataZoom: null,
91
+ xAxis: [],
92
+ });
93
+
94
+ handleLabelUpdate(
95
+ mockInstance,
96
+ mainData,
97
+ labelTimerRef,
98
+ isGreaterThanTwoWeeksRef
99
+ );
100
+
101
+ jest.advanceTimersByTime(50);
102
+
103
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
104
+ });
105
+
106
+ it('should return early if dataZoom array is empty', () => {
107
+ mockInstance.getOption.mockReturnValue({
108
+ dataZoom: [],
109
+ xAxis: [],
110
+ });
111
+
112
+ handleLabelUpdate(
113
+ mockInstance,
114
+ mainData,
115
+ labelTimerRef,
116
+ isGreaterThanTwoWeeksRef
117
+ );
118
+
119
+ jest.advanceTimersByTime(50);
120
+
121
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
122
+ });
123
+
124
+ it('should return early if dataZoom[0] is null', () => {
125
+ mockInstance.getOption.mockReturnValue({
126
+ dataZoom: [null],
127
+ xAxis: [],
128
+ });
129
+
130
+ handleLabelUpdate(
131
+ mockInstance,
132
+ mainData,
133
+ labelTimerRef,
134
+ isGreaterThanTwoWeeksRef
135
+ );
136
+
137
+ jest.advanceTimersByTime(50);
138
+
139
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
140
+ });
141
+
142
+ it('should return early if startValue is not a number', () => {
143
+ mockInstance.getOption.mockReturnValue({
144
+ dataZoom: [{ startValue: 'invalid', endValue: 3 }],
145
+ xAxis: [],
146
+ });
147
+
148
+ handleLabelUpdate(
149
+ mockInstance,
150
+ mainData,
151
+ labelTimerRef,
152
+ isGreaterThanTwoWeeksRef
153
+ );
154
+
155
+ jest.advanceTimersByTime(50);
156
+
157
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
158
+ });
159
+
160
+ it('should return early if endValue is not a number', () => {
161
+ mockInstance.getOption.mockReturnValue({
162
+ dataZoom: [{ startValue: 0, endValue: 'invalid' }],
163
+ xAxis: [],
164
+ });
165
+
166
+ handleLabelUpdate(
167
+ mockInstance,
168
+ mainData,
169
+ labelTimerRef,
170
+ isGreaterThanTwoWeeksRef
171
+ );
172
+
173
+ jest.advanceTimersByTime(50);
174
+
175
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
176
+ });
177
+ });
178
+
179
+ describe('when dates are invalid', () => {
180
+ it('should return early if startValue date is missing', () => {
181
+ mockInstance.getOption.mockReturnValue({
182
+ dataZoom: [{ startValue: 10, endValue: 3 }],
183
+ xAxis: [],
184
+ });
185
+
186
+ handleLabelUpdate(
187
+ mockInstance,
188
+ mainData,
189
+ labelTimerRef,
190
+ isGreaterThanTwoWeeksRef
191
+ );
192
+
193
+ jest.advanceTimersByTime(50);
194
+
195
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
196
+ });
197
+
198
+ it('should return early if endValue date is missing', () => {
199
+ mockInstance.getOption.mockReturnValue({
200
+ dataZoom: [{ startValue: 0, endValue: 10 }],
201
+ xAxis: [],
202
+ });
203
+
204
+ handleLabelUpdate(
205
+ mockInstance,
206
+ mainData,
207
+ labelTimerRef,
208
+ isGreaterThanTwoWeeksRef
209
+ );
210
+
211
+ jest.advanceTimersByTime(50);
212
+
213
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
214
+ });
215
+ });
216
+
217
+ describe('when state changes', () => {
218
+ it('should update xAxis when isGreaterThanTwoWeeks changes from null to true', () => {
219
+ isGreaterThanTwoWeeksRef.current = null;
220
+ mockInstance.getOption.mockReturnValue({
221
+ dataZoom: [{ startValue: 0, endValue: 3 }],
222
+ xAxis: [],
223
+ });
224
+ mockIsDifferenceGreaterThanTwoWeeks.mockReturnValue(true);
225
+
226
+ handleLabelUpdate(
227
+ mockInstance,
228
+ mainData,
229
+ labelTimerRef,
230
+ isGreaterThanTwoWeeksRef
231
+ );
232
+
233
+ jest.advanceTimersByTime(50);
234
+
235
+ expect(mockInstance.setOption).toHaveBeenCalledWith({
236
+ xAxis: [
237
+ {
238
+ id: 'main-xAxis',
239
+ axisLabel: {
240
+ formatter: expect.any(Function),
241
+ },
242
+ },
243
+ {
244
+ id: 'additional-xAxis',
245
+ axisLabel: {
246
+ customValues: ['2025-01-02T00:00:00Z', '2025-01-04T00:00:00Z'],
247
+ formatter: expect.any(Function),
248
+ },
249
+ },
250
+ ],
251
+ });
252
+ expect(isGreaterThanTwoWeeksRef.current).toBe(true);
253
+ });
254
+
255
+ it('should update xAxis when isGreaterThanTwoWeeks changes from false to true', () => {
256
+ isGreaterThanTwoWeeksRef.current = false;
257
+ mockInstance.getOption.mockReturnValue({
258
+ dataZoom: [{ startValue: 0, endValue: 3 }],
259
+ xAxis: [],
260
+ });
261
+ mockIsDifferenceGreaterThanTwoWeeks.mockReturnValue(true);
262
+
263
+ handleLabelUpdate(
264
+ mockInstance,
265
+ mainData,
266
+ labelTimerRef,
267
+ isGreaterThanTwoWeeksRef
268
+ );
269
+
270
+ jest.advanceTimersByTime(50);
271
+
272
+ expect(mockInstance.setOption).toHaveBeenCalled();
273
+ expect(isGreaterThanTwoWeeksRef.current).toBe(true);
274
+ });
275
+
276
+ it('should not update xAxis when isGreaterThanTwoWeeks does not change', () => {
277
+ isGreaterThanTwoWeeksRef.current = false;
278
+ mockInstance.getOption.mockReturnValue({
279
+ dataZoom: [{ startValue: 0, endValue: 3 }],
280
+ xAxis: [],
281
+ });
282
+ mockIsDifferenceGreaterThanTwoWeeks.mockReturnValue(false);
283
+
284
+ handleLabelUpdate(
285
+ mockInstance,
286
+ mainData,
287
+ labelTimerRef,
288
+ isGreaterThanTwoWeeksRef
289
+ );
290
+
291
+ jest.advanceTimersByTime(50);
292
+
293
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
294
+ expect(isGreaterThanTwoWeeksRef.current).toBe(false);
295
+ });
296
+ });
297
+
298
+ describe('timer management', () => {
299
+ it('should clear existing timeout before setting new one', () => {
300
+ const existingTimeout = setTimeout(() => {}, 1000);
301
+ labelTimerRef.current = existingTimeout;
302
+ const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
303
+
304
+ mockInstance.getOption.mockReturnValue({
305
+ dataZoom: [{ startValue: 0, endValue: 3 }],
306
+ xAxis: [],
307
+ });
308
+
309
+ handleLabelUpdate(
310
+ mockInstance,
311
+ mainData,
312
+ labelTimerRef,
313
+ isGreaterThanTwoWeeksRef
314
+ );
315
+
316
+ expect(clearTimeoutSpy).toHaveBeenCalledWith(existingTimeout);
317
+ clearTimeoutSpy.mockRestore();
318
+ });
319
+
320
+ it('should delay execution by 50ms', () => {
321
+ mockInstance.getOption.mockReturnValue({
322
+ dataZoom: [{ startValue: 0, endValue: 3 }],
323
+ xAxis: [],
324
+ });
325
+ mockIsDifferenceGreaterThanTwoWeeks.mockReturnValue(true);
326
+ isGreaterThanTwoWeeksRef.current = false;
327
+
328
+ handleLabelUpdate(
329
+ mockInstance,
330
+ mainData,
331
+ labelTimerRef,
332
+ isGreaterThanTwoWeeksRef
333
+ );
334
+
335
+ // Should not be called before timeout
336
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
337
+
338
+ // Fast-forward 49ms
339
+ jest.advanceTimersByTime(49);
340
+ expect(mockInstance.setOption).not.toHaveBeenCalled();
341
+
342
+ // Fast-forward 1ms more
343
+ jest.advanceTimersByTime(1);
344
+ expect(mockInstance.setOption).toHaveBeenCalled();
345
+ });
346
+ });
347
+
348
+ describe('formatter functions', () => {
349
+ it('should call formatXAxisLabel with correct parameters', () => {
350
+ isGreaterThanTwoWeeksRef.current = false;
351
+ mockInstance.getOption.mockReturnValue({
352
+ dataZoom: [{ startValue: 0, endValue: 3 }],
353
+ xAxis: [],
354
+ });
355
+ mockIsDifferenceGreaterThanTwoWeeks.mockReturnValue(true);
356
+
357
+ handleLabelUpdate(
358
+ mockInstance,
359
+ mainData,
360
+ labelTimerRef,
361
+ isGreaterThanTwoWeeksRef
362
+ );
363
+
364
+ jest.advanceTimersByTime(50);
365
+
366
+ const setOptionCall = mockInstance.setOption.mock.calls[0][0] as {
367
+ xAxis: Array<{
368
+ id: string;
369
+ axisLabel: {
370
+ formatter: (value: string) => string;
371
+ };
372
+ }>;
373
+ };
374
+ const mainAxisFormatter = setOptionCall.xAxis[0].axisLabel.formatter;
375
+ mainAxisFormatter('2025-01-01T00:00:00Z');
376
+
377
+ expect(mockFormatXAxisLabel).toHaveBeenCalledWith(
378
+ '2025-01-01T00:00:00Z',
379
+ true
380
+ );
381
+ });
382
+
383
+ it('should call formatXAxisAdditionalLabel with correct parameters', () => {
384
+ isGreaterThanTwoWeeksRef.current = false;
385
+ mockInstance.getOption.mockReturnValue({
386
+ dataZoom: [{ startValue: 0, endValue: 3 }],
387
+ xAxis: [],
388
+ });
389
+ mockIsDifferenceGreaterThanTwoWeeks.mockReturnValue(true);
390
+
391
+ handleLabelUpdate(
392
+ mockInstance,
393
+ mainData,
394
+ labelTimerRef,
395
+ isGreaterThanTwoWeeksRef
396
+ );
397
+
398
+ jest.advanceTimersByTime(50);
399
+
400
+ const setOptionCall = mockInstance.setOption.mock.calls[0][0] as {
401
+ xAxis: Array<{
402
+ id: string;
403
+ axisLabel: {
404
+ formatter: (value: unknown) => string;
405
+ customValues?: string[];
406
+ };
407
+ }>;
408
+ };
409
+ const additionalAxisFormatter =
410
+ setOptionCall.xAxis[1].axisLabel.formatter;
411
+ additionalAxisFormatter('2025-01-02T00:00:00Z');
412
+
413
+ expect(mockFormatXAxisAdditionalLabel).toHaveBeenCalledWith(
414
+ '2025-01-02T00:00:00Z',
415
+ true
416
+ );
417
+ });
418
+ });
419
+ });