@oanda/labs-crowd-view-widget 1.0.46 → 1.0.48
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.
- package/CHANGELOG.md +388 -0
- package/dist/main/CrowdViewWidget/Main.js +10 -6
- package/dist/main/CrowdViewWidget/Main.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/Chart.js +3 -2
- package/dist/main/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js +5 -14
- package/dist/main/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js +12 -6
- package/dist/main/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/types.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js +5 -2
- package/dist/main/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Legend/Legend.js +5 -2
- package/dist/main/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
- package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js +9 -5
- package/dist/main/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
- package/dist/main/CrowdViewWidget/constants.js +10 -2
- package/dist/main/CrowdViewWidget/constants.js.map +1 -1
- package/dist/main/translations/sources/en.json +4 -4
- package/dist/module/CrowdViewWidget/Main.js +10 -6
- package/dist/module/CrowdViewWidget/Main.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/Chart.js +3 -2
- package/dist/module/CrowdViewWidget/components/Chart/Chart.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js +5 -14
- package/dist/module/CrowdViewWidget/components/Chart/ChartWithData.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js +13 -7
- package/dist/module/CrowdViewWidget/components/Chart/chartOptions.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/types.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js +5 -2
- package/dist/module/CrowdViewWidget/components/Chart/utils/chartUtils.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Legend/Legend.js +5 -2
- package/dist/module/CrowdViewWidget/components/Legend/Legend.js.map +1 -1
- package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js +9 -5
- package/dist/module/CrowdViewWidget/components/Legend/LegendBar.js.map +1 -1
- package/dist/module/CrowdViewWidget/constants.js +10 -2
- package/dist/module/CrowdViewWidget/constants.js.map +1 -1
- package/dist/module/translations/sources/en.json +4 -4
- package/dist/types/CrowdViewWidget/components/Chart/Chart.d.ts +1 -1
- package/dist/types/CrowdViewWidget/components/Chart/types.d.ts +2 -1
- package/dist/types/CrowdViewWidget/components/Chart/utils/chartUtils.d.ts +1 -1
- package/dist/types/CrowdViewWidget/components/Legend/Legend.d.ts +2 -1
- package/dist/types/CrowdViewWidget/components/Legend/LegendBar.d.ts +2 -1
- package/dist/types/CrowdViewWidget/constants.d.ts +9 -2
- package/package.json +3 -3
- package/src/CrowdViewWidget/Main.tsx +46 -39
- package/src/CrowdViewWidget/components/Chart/Chart.tsx +2 -2
- package/src/CrowdViewWidget/components/Chart/ChartWithData.tsx +4 -26
- package/src/CrowdViewWidget/components/Chart/chartOptions.ts +21 -5
- package/src/CrowdViewWidget/components/Chart/types.ts +2 -0
- package/src/CrowdViewWidget/components/Chart/utils/chartUtils.ts +17 -6
- package/src/CrowdViewWidget/components/Legend/Legend.tsx +5 -1
- package/src/CrowdViewWidget/components/Legend/LegendBar.tsx +9 -4
- package/src/CrowdViewWidget/constants.ts +11 -2
- package/src/translations/sources/en.json +4 -4
- package/test/components/Chart/utils/chartUtils.test.ts +24 -7
- package/test/components/Legend.test.tsx +46 -2
- package/test/components/LegendBar.test.tsx +76 -4
|
@@ -17,7 +17,7 @@ import { getInstrumentConfigForDivision } from './utils/instrumentUtils';
|
|
|
17
17
|
|
|
18
18
|
const Main = ({ division }: MainProps) => {
|
|
19
19
|
const { lang } = useLocale();
|
|
20
|
-
const { size } = useLayoutProvider();
|
|
20
|
+
const { size, isDark } = useLayoutProvider();
|
|
21
21
|
const isDesktop = size === Size.DESKTOP;
|
|
22
22
|
|
|
23
23
|
const [bookType, setBookType] = useState(BookType.Order);
|
|
@@ -52,48 +52,55 @@ const Main = ({ division }: MainProps) => {
|
|
|
52
52
|
className="lw-text-sm lw-tracking-normal"
|
|
53
53
|
data-testid="crowd-view-widget"
|
|
54
54
|
>
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'lw-w-full': !isDesktop,
|
|
66
|
-
'lw-w-[280px]': isDesktop,
|
|
67
|
-
})}
|
|
68
|
-
>
|
|
69
|
-
<Select
|
|
70
|
-
options={instrumentSelectConfigWithDivision}
|
|
71
|
-
searchPlaceholder={lang('search')}
|
|
72
|
-
selectLabel={lang('instrument')}
|
|
73
|
-
selectedOption={instrument}
|
|
74
|
-
setSelectedOption={(val) =>
|
|
75
|
-
setInstrument(val as { id: InstrumentId; label: string })
|
|
76
|
-
}
|
|
77
|
-
/>
|
|
78
|
-
</div>
|
|
55
|
+
<div>
|
|
56
|
+
<Tabs
|
|
57
|
+
mobileFullWidth
|
|
58
|
+
activeTab={bookType}
|
|
59
|
+
handleClick={(e) =>
|
|
60
|
+
setBookType(e.currentTarget.value as BookType)
|
|
61
|
+
}
|
|
62
|
+
items={navigationConfig}
|
|
63
|
+
labelCallback={lang}
|
|
64
|
+
/>
|
|
79
65
|
<div
|
|
80
|
-
className={cn({
|
|
81
|
-
'lw-
|
|
82
|
-
'lw-w-[280px]': isDesktop,
|
|
66
|
+
className={cn('lw-mb-6 lw-mt-12', {
|
|
67
|
+
'lw-flex': isDesktop,
|
|
83
68
|
})}
|
|
84
69
|
>
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
70
|
+
<div
|
|
71
|
+
className={cn('lw-mr-2', {
|
|
72
|
+
'lw-w-full lw-mb-2': !isDesktop,
|
|
73
|
+
'lw-w-[280px]': isDesktop,
|
|
74
|
+
})}
|
|
75
|
+
>
|
|
76
|
+
<Select
|
|
77
|
+
options={instrumentSelectConfigWithDivision}
|
|
78
|
+
searchPlaceholder={lang('search')}
|
|
79
|
+
selectLabel={lang('instrument')}
|
|
80
|
+
selectedOption={instrument}
|
|
81
|
+
setSelectedOption={(val) =>
|
|
82
|
+
setInstrument(val as { id: InstrumentId; label: string })
|
|
83
|
+
}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
<div
|
|
87
|
+
className={cn({
|
|
88
|
+
'lw-w-full': !isDesktop,
|
|
89
|
+
'lw-w-[280px]': isDesktop,
|
|
90
|
+
})}
|
|
91
|
+
>
|
|
92
|
+
<Select
|
|
93
|
+
options={granularitySelectConfigWithLang}
|
|
94
|
+
searchPlaceholder={lang('search')}
|
|
95
|
+
selectLabel={lang('granularity')}
|
|
96
|
+
selectedOption={granularity}
|
|
97
|
+
setSelectedOption={(val) =>
|
|
98
|
+
setGranularity(val as { id: Granularity; label: string })
|
|
99
|
+
}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
94
102
|
</div>
|
|
95
103
|
</div>
|
|
96
|
-
|
|
97
104
|
<ChartWithData
|
|
98
105
|
bookType={bookType}
|
|
99
106
|
division={division}
|
|
@@ -101,7 +108,7 @@ const Main = ({ division }: MainProps) => {
|
|
|
101
108
|
instrument={instrument.id}
|
|
102
109
|
/>
|
|
103
110
|
|
|
104
|
-
<Legend bookType={bookType} />
|
|
111
|
+
<Legend bookType={bookType} isDark={isDark} />
|
|
105
112
|
</div>
|
|
106
113
|
)}
|
|
107
114
|
</>
|
|
@@ -48,7 +48,7 @@ echarts.use([
|
|
|
48
48
|
echarts.registerTheme('dark_theme', getChartTheme(Theme.Dark));
|
|
49
49
|
echarts.registerTheme('light_theme', getChartTheme(Theme.Light));
|
|
50
50
|
|
|
51
|
-
const Chart = ({ data }: ChartProps) => {
|
|
51
|
+
const Chart = ({ data, isDesktop }: ChartProps) => {
|
|
52
52
|
const { isDark } = useLayoutProvider();
|
|
53
53
|
const { lang } = useLocale();
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ const Chart = ({ data }: ChartProps) => {
|
|
|
58
58
|
echarts={echarts}
|
|
59
59
|
isDark={isDark}
|
|
60
60
|
lazyUpdate={true}
|
|
61
|
-
option={getOption(data, isDark, lang)}
|
|
61
|
+
option={getOption(data, isDark, isDesktop, lang)}
|
|
62
62
|
opts={{ renderer: 'canvas' }}
|
|
63
63
|
onEvents={{
|
|
64
64
|
datazoom: (_params: unknown, instance: EChartsType) => {
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
SpinnerSize,
|
|
6
6
|
useLayoutProvider,
|
|
7
7
|
} from '@oanda/labs-widget-common';
|
|
8
|
-
import classnames from 'classnames';
|
|
9
8
|
import React from 'react';
|
|
10
9
|
|
|
11
10
|
import { Chart } from './Chart';
|
|
@@ -30,41 +29,20 @@ const ChartWithData = ({
|
|
|
30
29
|
|
|
31
30
|
return (
|
|
32
31
|
<>
|
|
33
|
-
<div
|
|
34
|
-
className={classnames('lw-relative lw-w-full', {
|
|
35
|
-
'lw-h-[450px]': isDesktop,
|
|
36
|
-
'lw-h-[390px]': !isDesktop,
|
|
37
|
-
})}
|
|
38
|
-
>
|
|
32
|
+
<div className="lw-relative lw-h-[450px] lw-w-full">
|
|
39
33
|
{error && (
|
|
40
|
-
<div
|
|
41
|
-
className={classnames(
|
|
42
|
-
'lw-absolute lw-left-0 lw-top-0 lw-flex lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary',
|
|
43
|
-
{
|
|
44
|
-
'lw-h-full': isDesktop,
|
|
45
|
-
'lw-h-[calc(100%-40px)]': !isDesktop,
|
|
46
|
-
}
|
|
47
|
-
)}
|
|
48
|
-
>
|
|
34
|
+
<div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-[calc(100%-30px)] lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary">
|
|
49
35
|
<ChartError />
|
|
50
36
|
</div>
|
|
51
37
|
)}
|
|
52
38
|
{loading && (
|
|
53
|
-
<div
|
|
54
|
-
className={classnames(
|
|
55
|
-
'lw-absolute lw-left-0 lw-top-0 lw-flex lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary',
|
|
56
|
-
{
|
|
57
|
-
'lw-h-full': isDesktop,
|
|
58
|
-
'lw-h-[calc(100%-40px)]': !isDesktop,
|
|
59
|
-
}
|
|
60
|
-
)}
|
|
61
|
-
>
|
|
39
|
+
<div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-[calc(100%-30px)] lw-w-full lw-items-center lw-justify-center lw-border lw-border-solid lw-border-border-primary">
|
|
62
40
|
<Spinner size={SpinnerSize.lg} />
|
|
63
41
|
</div>
|
|
64
42
|
)}
|
|
65
43
|
{!loading && !error && !!data && (
|
|
66
44
|
<div className="lw-absolute lw-left-0 lw-top-0 lw-flex lw-h-full lw-w-full">
|
|
67
|
-
<Chart data={data} />
|
|
45
|
+
<Chart data={data} isDesktop={isDesktop} />
|
|
68
46
|
</div>
|
|
69
47
|
)}
|
|
70
48
|
</div>
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
colorPalette,
|
|
3
3
|
getGridLines,
|
|
4
4
|
getLineCommons,
|
|
5
|
+
themeColors,
|
|
5
6
|
} from '@oanda/labs-widget-common';
|
|
6
7
|
|
|
7
8
|
import { CHART_CONFIG } from '../../constants';
|
|
@@ -26,6 +27,7 @@ export const getOption: GetOptionType = (
|
|
|
26
27
|
bookType,
|
|
27
28
|
},
|
|
28
29
|
isDark,
|
|
30
|
+
isDesktop,
|
|
29
31
|
labelCallback
|
|
30
32
|
) => {
|
|
31
33
|
let selectedPrice: number;
|
|
@@ -49,7 +51,9 @@ export const getOption: GetOptionType = (
|
|
|
49
51
|
chartWidth: CHART_CONFIG.WIDTH,
|
|
50
52
|
chartHeight: CHART_CONFIG.HEIGHT,
|
|
51
53
|
xLabelsSize: CHART_CONFIG.X_LABEL_SIZE,
|
|
52
|
-
yLabelSize:
|
|
54
|
+
yLabelSize: isDesktop
|
|
55
|
+
? CHART_CONFIG.Y_LABEL_SIZE_DESKTOP
|
|
56
|
+
: CHART_CONFIG.Y_LABEL_SIZE_MOBILE,
|
|
53
57
|
bottomLeftBox: false,
|
|
54
58
|
});
|
|
55
59
|
|
|
@@ -68,6 +72,9 @@ export const getOption: GetOptionType = (
|
|
|
68
72
|
axisPointer: {
|
|
69
73
|
type: 'cross',
|
|
70
74
|
axis: 'x',
|
|
75
|
+
crossStyle: {
|
|
76
|
+
color: isDark ? colorPalette.orange : themeColors.borderPrimary.light,
|
|
77
|
+
},
|
|
71
78
|
label: {
|
|
72
79
|
formatter: (params) => {
|
|
73
80
|
if (params.axisDimension === 'y') {
|
|
@@ -124,6 +131,7 @@ export const getOption: GetOptionType = (
|
|
|
124
131
|
axisLabel: {
|
|
125
132
|
showMaxLabel: false,
|
|
126
133
|
showMinLabel: false,
|
|
134
|
+
margin: isDesktop ? 4 : 2,
|
|
127
135
|
formatter: (value: number) => value.toFixed(precision - 1),
|
|
128
136
|
},
|
|
129
137
|
},
|
|
@@ -133,8 +141,16 @@ export const getOption: GetOptionType = (
|
|
|
133
141
|
id: 'candlestick',
|
|
134
142
|
data: candlesSeriesData,
|
|
135
143
|
itemStyle: {
|
|
136
|
-
color: colorPalette.raspberryLight,
|
|
137
|
-
color0:
|
|
144
|
+
color: isDark ? colorPalette.orange : colorPalette.raspberryLight,
|
|
145
|
+
color0: isDark
|
|
146
|
+
? colorPalette.bottleGreenDark
|
|
147
|
+
: colorPalette.bottleGreenLight,
|
|
148
|
+
borderColor: isDark
|
|
149
|
+
? colorPalette.orange
|
|
150
|
+
: colorPalette.raspberryLight,
|
|
151
|
+
borderColor0: isDark
|
|
152
|
+
? colorPalette.bottleGreenDark
|
|
153
|
+
: colorPalette.bottleGreenLight,
|
|
138
154
|
},
|
|
139
155
|
|
|
140
156
|
markPoint: {
|
|
@@ -171,7 +187,7 @@ export const getOption: GetOptionType = (
|
|
|
171
187
|
height: rectHeight,
|
|
172
188
|
},
|
|
173
189
|
style: {
|
|
174
|
-
fill: getRectColor(sentiment),
|
|
190
|
+
fill: getRectColor(sentiment, isDark),
|
|
175
191
|
},
|
|
176
192
|
silent: true,
|
|
177
193
|
emphasisDisabled: true,
|
|
@@ -193,7 +209,7 @@ export const getOption: GetOptionType = (
|
|
|
193
209
|
name: 'main-grid',
|
|
194
210
|
top: '0px',
|
|
195
211
|
left: '0px',
|
|
196
|
-
right: `${CHART_CONFIG.Y_LABEL_SIZE_DESKTOP}px`,
|
|
212
|
+
right: `${isDesktop ? CHART_CONFIG.Y_LABEL_SIZE_DESKTOP : CHART_CONFIG.Y_LABEL_SIZE_MOBILE}px`,
|
|
197
213
|
bottom: `${CHART_CONFIG.X_LABEL_SIZE}px`,
|
|
198
214
|
},
|
|
199
215
|
],
|
|
@@ -40,11 +40,13 @@ export interface UseCrowdViewDataReturn {
|
|
|
40
40
|
export type GetOptionType = (
|
|
41
41
|
props: CrowdViewData,
|
|
42
42
|
isDark: boolean,
|
|
43
|
+
isDesktop: boolean,
|
|
43
44
|
labelCallback: (key: string, params?: Record<string, unknown>) => string
|
|
44
45
|
) => EChartsOption;
|
|
45
46
|
|
|
46
47
|
export interface ChartProps {
|
|
47
48
|
data: CrowdViewData;
|
|
49
|
+
isDesktop: boolean;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export interface ChartWithDataProps {
|
|
@@ -87,10 +87,17 @@ const getGradientColor = (
|
|
|
87
87
|
return colorScale(value).hex();
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
export const getRectColor = (sentiment: number) =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
export const getRectColor = (sentiment: number, isDark: boolean) => {
|
|
91
|
+
const colorPalette = isDark ? COLOR_MAP.dark : COLOR_MAP.light;
|
|
92
|
+
|
|
93
|
+
return sentiment < 0
|
|
94
|
+
? getGradientColor(
|
|
95
|
+
sentiment * -1,
|
|
96
|
+
colorPalette.short[0],
|
|
97
|
+
colorPalette.short[1]
|
|
98
|
+
)
|
|
99
|
+
: getGradientColor(sentiment, colorPalette.long[0], colorPalette.long[1]);
|
|
100
|
+
};
|
|
94
101
|
|
|
95
102
|
export const getTooltipFormatter = ({
|
|
96
103
|
params,
|
|
@@ -160,10 +167,14 @@ ${
|
|
|
160
167
|
<p>${
|
|
161
168
|
matchedBucket.sentiment < 0
|
|
162
169
|
? labelCallback(
|
|
163
|
-
bookType === BookType.Order
|
|
170
|
+
bookType === BookType.Order
|
|
171
|
+
? 'sell_overbalance'
|
|
172
|
+
: 'short_overbalance'
|
|
164
173
|
)
|
|
165
174
|
: labelCallback(
|
|
166
|
-
bookType === BookType.Order
|
|
175
|
+
bookType === BookType.Order
|
|
176
|
+
? 'buy_overbalance'
|
|
177
|
+
: 'long_overbalance'
|
|
167
178
|
)
|
|
168
179
|
}: ${Math.abs(matchedBucket.sentiment)}% </p>`
|
|
169
180
|
: ''
|
|
@@ -9,23 +9,27 @@ interface LegendProps {
|
|
|
9
9
|
longValues?: [number, number];
|
|
10
10
|
shortValues?: [number, number];
|
|
11
11
|
bookType: BookType;
|
|
12
|
+
isDark: boolean;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export const Legend = ({
|
|
15
16
|
longValues = [BOOKS_THRESHOLDS.MIN, BOOKS_THRESHOLDS.MAX],
|
|
16
17
|
shortValues = [BOOKS_THRESHOLDS.MIN, BOOKS_THRESHOLDS.MAX],
|
|
17
18
|
bookType,
|
|
19
|
+
isDark,
|
|
18
20
|
}: LegendProps) => {
|
|
19
21
|
const { lang } = useLocale();
|
|
20
22
|
|
|
21
23
|
return (
|
|
22
|
-
<div className="lw-mx-auto lw-flex lw-w-full lw-flex-col lw-items-center lw-space-y-4 lw-
|
|
24
|
+
<div className="lw-mx-auto lw-flex lw-w-full lw-flex-col lw-items-center lw-space-y-4 lw-pb-6 lw-pt-0 sm:lw-max-w-md lg:lw-max-w-xl">
|
|
23
25
|
<LegendBar
|
|
26
|
+
isDark={isDark}
|
|
24
27
|
label={lang(bookType === BookType.Order ? 'buy' : 'long')}
|
|
25
28
|
type="long"
|
|
26
29
|
values={longValues}
|
|
27
30
|
/>
|
|
28
31
|
<LegendBar
|
|
32
|
+
isDark={isDark}
|
|
29
33
|
label={lang(bookType === BookType.Order ? 'sell' : 'short')}
|
|
30
34
|
type="short"
|
|
31
35
|
values={shortValues}
|
|
@@ -8,13 +8,18 @@ interface LegendBarProps {
|
|
|
8
8
|
values: number[];
|
|
9
9
|
type: LegendType;
|
|
10
10
|
label: string;
|
|
11
|
+
isDark: boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export const LegendBar = ({ values, type, label }: LegendBarProps) => {
|
|
14
|
-
const
|
|
14
|
+
export const LegendBar = ({ values, type, label, isDark }: LegendBarProps) => {
|
|
15
|
+
const colorPalette = isDark ? COLOR_MAP.dark : COLOR_MAP.light;
|
|
16
|
+
const colors = type === 'long' ? colorPalette.long : colorPalette.short;
|
|
15
17
|
|
|
16
18
|
return (
|
|
17
19
|
<div className="lw-flex lw-w-full lw-flex-col lw-space-y-1 lw-border-border-primary">
|
|
20
|
+
<span className="lw-whitespace-nowrap lw-font-sans lw-text-xs lw-text-text-primary">
|
|
21
|
+
{label}
|
|
22
|
+
</span>
|
|
18
23
|
<div className="lw-flex lw-h-2 lw-w-full lw-overflow-hidden lw-border lw-border-border-primary">
|
|
19
24
|
<div
|
|
20
25
|
className="lw-h-full lw-flex-1"
|
|
@@ -27,10 +32,10 @@ export const LegendBar = ({ values, type, label }: LegendBarProps) => {
|
|
|
27
32
|
|
|
28
33
|
<div className="lw-flex lw-w-full lw-justify-between">
|
|
29
34
|
<span className="lw-whitespace-nowrap lw-font-sans lw-text-xs lw-text-text-primary">
|
|
30
|
-
{values[0].toFixed(2)}%
|
|
35
|
+
{values[0].toFixed(2)}%
|
|
31
36
|
</span>
|
|
32
37
|
<span className="lw-whitespace-nowrap lw-font-sans lw-text-xs lw-text-text-primary">
|
|
33
|
-
{values[1].toFixed(2)}%
|
|
38
|
+
{`≤ ${values[1].toFixed(2)}`}%
|
|
34
39
|
</span>
|
|
35
40
|
</div>
|
|
36
41
|
</div>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { colorPalette } from '@oanda/labs-widget-common';
|
|
2
|
+
|
|
1
3
|
import { InstrumentId } from './types';
|
|
2
4
|
|
|
3
5
|
export const BOOKS_THRESHOLDS = {
|
|
@@ -19,14 +21,21 @@ export const CHART_CONFIG = {
|
|
|
19
21
|
WIDTH: 9999,
|
|
20
22
|
X_LABEL_SIZE: 40,
|
|
21
23
|
Y_LABEL_SIZE_DESKTOP: 60,
|
|
24
|
+
Y_LABEL_SIZE_MOBILE: 40,
|
|
22
25
|
INITIAL_START_ZOOM: 80,
|
|
23
26
|
INITIAL_END_ZOOM: 100,
|
|
24
27
|
X_AXIS_DATE_PADDING: ' ',
|
|
25
28
|
} as const;
|
|
26
29
|
|
|
27
30
|
export const COLOR_MAP = {
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
dark: {
|
|
32
|
+
long: [colorPalette.darkBlue10, colorPalette.darkBlue90],
|
|
33
|
+
short: [colorPalette.darkYellow10, colorPalette.darkYellow90],
|
|
34
|
+
},
|
|
35
|
+
light: {
|
|
36
|
+
long: [colorPalette.lightBlue10, colorPalette.lightBlue90],
|
|
37
|
+
short: [colorPalette.lightYellow10, colorPalette.lightYellow90],
|
|
38
|
+
},
|
|
30
39
|
} as const;
|
|
31
40
|
|
|
32
41
|
export const INSTRUMENTS_CONFIG: Record<
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"15_minutes": "15 minutes",
|
|
4
4
|
"4_hours": "4 hours",
|
|
5
5
|
"5_minutes": "5 minutes",
|
|
6
|
-
"
|
|
6
|
+
"buy_overbalance": "Buy overbalance",
|
|
7
7
|
"buy": "Buy",
|
|
8
8
|
"candle": "Candle",
|
|
9
9
|
"close_price": "Close price",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"granularity": "Granularity",
|
|
12
12
|
"high": "High",
|
|
13
13
|
"instrument": "Instrument",
|
|
14
|
-
"
|
|
14
|
+
"long_overbalance": "Long overbalance",
|
|
15
15
|
"long": "Long",
|
|
16
16
|
"low": "Low",
|
|
17
17
|
"no_matching_results": "No matching results",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"positions": "Positions",
|
|
24
24
|
"price_range": "Price range",
|
|
25
25
|
"search": "Search",
|
|
26
|
-
"
|
|
26
|
+
"sell_overbalance": "Sell overbalance",
|
|
27
27
|
"sell": "Sell",
|
|
28
28
|
"sentiment": "Sentiment",
|
|
29
|
-
"
|
|
29
|
+
"short_overbalance": "Short overbalance",
|
|
30
30
|
"short": "Short"
|
|
31
31
|
}
|
|
@@ -48,20 +48,37 @@ describe('chartUtils', () => {
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
describe('getRectColor', () => {
|
|
51
|
-
it('uses long color scale for positive sentiment', () => {
|
|
52
|
-
const color = getRectColor(BOOKS_THRESHOLDS.MAX);
|
|
51
|
+
it('uses long color scale for positive sentiment in light mode', () => {
|
|
52
|
+
const color = getRectColor(BOOKS_THRESHOLDS.MAX, false);
|
|
53
53
|
expect(typeof color).toBe('string');
|
|
54
54
|
// At max threshold, should be at or near target color
|
|
55
55
|
expect(color.toLowerCase()).toContain(
|
|
56
|
-
COLOR_MAP.long[1].slice(1).toLowerCase().substring(0, 3)
|
|
56
|
+
COLOR_MAP.light.long[1].slice(1).toLowerCase().substring(0, 3)
|
|
57
57
|
);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it('uses short color scale for negative sentiment', () => {
|
|
61
|
-
const color = getRectColor(-BOOKS_THRESHOLDS.MAX);
|
|
60
|
+
it('uses short color scale for negative sentiment in light mode', () => {
|
|
61
|
+
const color = getRectColor(-BOOKS_THRESHOLDS.MAX, false);
|
|
62
62
|
expect(typeof color).toBe('string');
|
|
63
63
|
expect(color.toLowerCase()).toContain(
|
|
64
|
-
COLOR_MAP.short[1].slice(1).toLowerCase().substring(0, 3)
|
|
64
|
+
COLOR_MAP.light.short[1].slice(1).toLowerCase().substring(0, 3)
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('uses long color scale for positive sentiment in dark mode', () => {
|
|
69
|
+
const color = getRectColor(BOOKS_THRESHOLDS.MAX, true);
|
|
70
|
+
expect(typeof color).toBe('string');
|
|
71
|
+
// At max threshold, should be at or near target color
|
|
72
|
+
expect(color.toLowerCase()).toContain(
|
|
73
|
+
COLOR_MAP.dark.long[1].slice(1).toLowerCase().substring(0, 3)
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('uses short color scale for negative sentiment in dark mode', () => {
|
|
78
|
+
const color = getRectColor(-BOOKS_THRESHOLDS.MAX, true);
|
|
79
|
+
expect(typeof color).toBe('string');
|
|
80
|
+
expect(color.toLowerCase()).toContain(
|
|
81
|
+
COLOR_MAP.dark.short[1].slice(1).toLowerCase().substring(0, 3)
|
|
65
82
|
);
|
|
66
83
|
});
|
|
67
84
|
});
|
|
@@ -152,7 +169,7 @@ describe('chartUtils', () => {
|
|
|
152
169
|
expect(html).toContain('orders');
|
|
153
170
|
expect(html).toContain('price_range');
|
|
154
171
|
// Selected price 1.3306 falls into second bucket 1.3305 - 1.3310 which has negative sentiment
|
|
155
|
-
expect(html).toContain('
|
|
172
|
+
expect(html).toContain('sell_overbalance');
|
|
156
173
|
});
|
|
157
174
|
});
|
|
158
175
|
});
|
|
@@ -6,6 +6,7 @@ import { render } from '@testing-library/react';
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
8
|
import { Legend } from '../../src/CrowdViewWidget/components';
|
|
9
|
+
import { COLOR_MAP } from '../../src/CrowdViewWidget/constants';
|
|
9
10
|
import { BookType } from '../../src/gql/types/graphql';
|
|
10
11
|
|
|
11
12
|
describe('Crowd View Widget', () => {
|
|
@@ -15,13 +16,56 @@ describe('Crowd View Widget', () => {
|
|
|
15
16
|
const { getAllByText } = render(
|
|
16
17
|
<Legend
|
|
17
18
|
bookType={BookType.Position}
|
|
19
|
+
isDark={false}
|
|
18
20
|
longValues={[0.15, 0.55]}
|
|
19
21
|
shortValues={[0.15, 0.55]}
|
|
20
22
|
/>
|
|
21
23
|
);
|
|
22
24
|
|
|
23
|
-
expect(getAllByText(/long/)).toHaveLength(
|
|
24
|
-
expect(getAllByText(/short/)).toHaveLength(
|
|
25
|
+
expect(getAllByText(/long/)).toHaveLength(1);
|
|
26
|
+
expect(getAllByText(/short/)).toHaveLength(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('passes isDark prop to LegendBar components in light mode', () => {
|
|
30
|
+
const { getAllByTestId } = render(
|
|
31
|
+
<Legend
|
|
32
|
+
bookType={BookType.Position}
|
|
33
|
+
isDark={false}
|
|
34
|
+
longValues={[0.15, 0.55]}
|
|
35
|
+
shortValues={[0.15, 0.55]}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
const segments = getAllByTestId('legend-bar-segment');
|
|
39
|
+
expect(segments.length).toBe(2);
|
|
40
|
+
// Check that segments use light mode colors (either long or short)
|
|
41
|
+
const styles = segments.map((segment) => segment.getAttribute('style'));
|
|
42
|
+
const hasLightColors = styles.some(
|
|
43
|
+
(style) =>
|
|
44
|
+
style?.includes(COLOR_MAP.light.long[0]) ||
|
|
45
|
+
style?.includes(COLOR_MAP.light.short[0])
|
|
46
|
+
);
|
|
47
|
+
expect(hasLightColors).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('passes isDark prop to LegendBar components in dark mode', () => {
|
|
51
|
+
const { getAllByTestId } = render(
|
|
52
|
+
<Legend
|
|
53
|
+
bookType={BookType.Position}
|
|
54
|
+
isDark={true}
|
|
55
|
+
longValues={[0.15, 0.55]}
|
|
56
|
+
shortValues={[0.15, 0.55]}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
const segments = getAllByTestId('legend-bar-segment');
|
|
60
|
+
expect(segments.length).toBe(2);
|
|
61
|
+
// Check that segments use dark mode colors (either long or short)
|
|
62
|
+
const styles = segments.map((segment) => segment.getAttribute('style'));
|
|
63
|
+
const hasDarkColors = styles.some(
|
|
64
|
+
(style) =>
|
|
65
|
+
style?.includes(COLOR_MAP.dark.long[0]) ||
|
|
66
|
+
style?.includes(COLOR_MAP.dark.short[0])
|
|
67
|
+
);
|
|
68
|
+
expect(hasDarkColors).toBe(true);
|
|
25
69
|
});
|
|
26
70
|
});
|
|
27
71
|
});
|
|
@@ -6,6 +6,7 @@ import { render } from '@testing-library/react';
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
8
|
import { LegendBar } from '../../src/CrowdViewWidget/components';
|
|
9
|
+
import { COLOR_MAP } from '../../src/CrowdViewWidget/constants';
|
|
9
10
|
|
|
10
11
|
describe('Crowd View Widget', () => {
|
|
11
12
|
describe('components', () => {
|
|
@@ -14,20 +15,91 @@ describe('Crowd View Widget', () => {
|
|
|
14
15
|
|
|
15
16
|
it('renders LegendBar with min and max values', () => {
|
|
16
17
|
const { getByText } = render(
|
|
17
|
-
<LegendBar
|
|
18
|
+
<LegendBar
|
|
19
|
+
isDark={false}
|
|
20
|
+
label="long"
|
|
21
|
+
type="long"
|
|
22
|
+
values={mockValues}
|
|
23
|
+
/>
|
|
18
24
|
);
|
|
19
25
|
|
|
20
|
-
expect(getByText('
|
|
21
|
-
expect(getByText('0.
|
|
26
|
+
expect(getByText('long')).toBeInTheDocument();
|
|
27
|
+
expect(getByText('0.15%')).toBeInTheDocument();
|
|
28
|
+
expect(getByText('≤ 0.55%')).toBeInTheDocument();
|
|
22
29
|
});
|
|
23
30
|
|
|
24
31
|
it('renders exactly 1 segment', () => {
|
|
25
32
|
const { getAllByTestId } = render(
|
|
26
|
-
<LegendBar
|
|
33
|
+
<LegendBar
|
|
34
|
+
isDark={false}
|
|
35
|
+
label="short"
|
|
36
|
+
type="short"
|
|
37
|
+
values={mockValues}
|
|
38
|
+
/>
|
|
27
39
|
);
|
|
28
40
|
const segments = getAllByTestId('legend-bar-segment');
|
|
29
41
|
expect(segments.length).toBe(1);
|
|
30
42
|
});
|
|
43
|
+
|
|
44
|
+
it('uses light mode colors when isDark is false', () => {
|
|
45
|
+
const { getByTestId } = render(
|
|
46
|
+
<LegendBar
|
|
47
|
+
isDark={false}
|
|
48
|
+
label="long"
|
|
49
|
+
type="long"
|
|
50
|
+
values={mockValues}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
const segment = getByTestId('legend-bar-segment');
|
|
54
|
+
const style = segment.getAttribute('style');
|
|
55
|
+
expect(style).toContain(COLOR_MAP.light.long[0]);
|
|
56
|
+
expect(style).toContain(COLOR_MAP.light.long[1]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('uses dark mode colors when isDark is true', () => {
|
|
60
|
+
const { getByTestId } = render(
|
|
61
|
+
<LegendBar
|
|
62
|
+
isDark={true}
|
|
63
|
+
label="long"
|
|
64
|
+
type="long"
|
|
65
|
+
values={mockValues}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
const segment = getByTestId('legend-bar-segment');
|
|
69
|
+
const style = segment.getAttribute('style');
|
|
70
|
+
expect(style).toContain(COLOR_MAP.dark.long[0]);
|
|
71
|
+
expect(style).toContain(COLOR_MAP.dark.long[1]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('uses light mode short colors when isDark is false', () => {
|
|
75
|
+
const { getByTestId } = render(
|
|
76
|
+
<LegendBar
|
|
77
|
+
isDark={false}
|
|
78
|
+
label="short"
|
|
79
|
+
type="short"
|
|
80
|
+
values={mockValues}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
const segment = getByTestId('legend-bar-segment');
|
|
84
|
+
const style = segment.getAttribute('style');
|
|
85
|
+
expect(style).toContain(COLOR_MAP.light.short[0]);
|
|
86
|
+
expect(style).toContain(COLOR_MAP.light.short[1]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('uses dark mode short colors when isDark is true', () => {
|
|
90
|
+
const { getByTestId } = render(
|
|
91
|
+
<LegendBar
|
|
92
|
+
isDark={true}
|
|
93
|
+
label="short"
|
|
94
|
+
type="short"
|
|
95
|
+
values={mockValues}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
const segment = getByTestId('legend-bar-segment');
|
|
99
|
+
const style = segment.getAttribute('style');
|
|
100
|
+
expect(style).toContain(COLOR_MAP.dark.short[0]);
|
|
101
|
+
expect(style).toContain(COLOR_MAP.dark.short[1]);
|
|
102
|
+
});
|
|
31
103
|
});
|
|
32
104
|
});
|
|
33
105
|
});
|