@oanda/labs-instruments-price-chart-widget 1.0.13 → 1.0.15
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 +124 -0
- package/codegen.ts +2 -5
- package/dist/main/InstrumentsPriceChartWidget/InstrumentsPriceChartWidget.js +6 -2
- package/dist/main/InstrumentsPriceChartWidget/InstrumentsPriceChartWidget.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/Main.js +32 -16
- package/dist/main/InstrumentsPriceChartWidget/Main.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/components/Chart.js +4 -2
- package/dist/main/InstrumentsPriceChartWidget/components/Chart.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/components/TimeZoneLabel.js +29 -0
- package/dist/main/InstrumentsPriceChartWidget/components/TimeZoneLabel.js.map +1 -0
- package/dist/main/InstrumentsPriceChartWidget/components/formatters.js +50 -11
- package/dist/main/InstrumentsPriceChartWidget/components/formatters.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/components/getOption.js +13 -13
- package/dist/main/InstrumentsPriceChartWidget/components/getOption.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/components/types.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/config.js +14 -7
- package/dist/main/InstrumentsPriceChartWidget/config.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/render.js +10 -2
- package/dist/main/InstrumentsPriceChartWidget/render.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/types.js.map +1 -1
- package/dist/main/InstrumentsPriceChartWidget/utils.js +12 -0
- package/dist/main/InstrumentsPriceChartWidget/utils.js.map +1 -0
- package/dist/main/gql/{mock/getPriceCandles.js → priceCandles.js} +9 -8
- package/dist/main/gql/priceCandles.js.map +1 -0
- package/dist/main/gql/types/gql.js +1 -1
- package/dist/main/gql/types/gql.js.map +1 -1
- package/dist/main/gql/types/graphql.js +53 -24
- package/dist/main/gql/types/graphql.js.map +1 -1
- package/dist/main/translations/sources/en.json +2 -1
- package/dist/module/InstrumentsPriceChartWidget/InstrumentsPriceChartWidget.js +6 -2
- package/dist/module/InstrumentsPriceChartWidget/InstrumentsPriceChartWidget.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/Main.js +33 -17
- package/dist/module/InstrumentsPriceChartWidget/Main.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/components/Chart.js +4 -2
- package/dist/module/InstrumentsPriceChartWidget/components/Chart.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/components/TimeZoneLabel.js +21 -0
- package/dist/module/InstrumentsPriceChartWidget/components/TimeZoneLabel.js.map +1 -0
- package/dist/module/InstrumentsPriceChartWidget/components/formatters.js +47 -9
- package/dist/module/InstrumentsPriceChartWidget/components/formatters.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/components/getOption.js +14 -14
- package/dist/module/InstrumentsPriceChartWidget/components/getOption.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/components/types.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/config.js +14 -7
- package/dist/module/InstrumentsPriceChartWidget/config.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/render.js +11 -3
- package/dist/module/InstrumentsPriceChartWidget/render.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/types.js.map +1 -1
- package/dist/module/InstrumentsPriceChartWidget/utils.js +4 -0
- package/dist/module/InstrumentsPriceChartWidget/utils.js.map +1 -0
- package/dist/module/gql/{mock/getPriceCandles.js → priceCandles.js} +9 -8
- package/dist/module/gql/priceCandles.js.map +1 -0
- package/dist/module/gql/types/gql.js +1 -1
- package/dist/module/gql/types/gql.js.map +1 -1
- package/dist/module/gql/types/graphql.js +52 -23
- package/dist/module/gql/types/graphql.js.map +1 -1
- package/dist/module/translations/sources/en.json +2 -1
- package/dist/types/InstrumentsPriceChartWidget/InstrumentsPriceChartWidget.d.ts +1 -1
- package/dist/types/InstrumentsPriceChartWidget/Main.d.ts +1 -1
- package/dist/types/InstrumentsPriceChartWidget/components/Chart.d.ts +1 -1
- package/dist/types/InstrumentsPriceChartWidget/components/TimeZoneLabel.d.ts +2 -0
- package/dist/types/InstrumentsPriceChartWidget/components/formatters.d.ts +5 -1
- package/dist/types/InstrumentsPriceChartWidget/components/types.d.ts +6 -4
- package/dist/types/InstrumentsPriceChartWidget/config.d.ts +5 -3
- package/dist/types/InstrumentsPriceChartWidget/types.d.ts +5 -1
- package/dist/types/InstrumentsPriceChartWidget/utils.d.ts +3 -0
- package/dist/types/gql/priceCandles.d.ts +2 -0
- package/dist/types/gql/types/gql.d.ts +4 -3
- package/dist/types/gql/types/graphql.d.ts +31 -27
- package/package.json +5 -3
- package/src/InstrumentsPriceChartWidget/InstrumentsPriceChartWidget.tsx +7 -1
- package/src/InstrumentsPriceChartWidget/Main.tsx +45 -19
- package/src/InstrumentsPriceChartWidget/components/Chart.tsx +2 -1
- package/src/InstrumentsPriceChartWidget/components/TimeZoneLabel.tsx +24 -0
- package/src/InstrumentsPriceChartWidget/components/formatters.ts +72 -13
- package/src/InstrumentsPriceChartWidget/components/getOption.ts +20 -21
- package/src/InstrumentsPriceChartWidget/components/types.ts +6 -4
- package/src/InstrumentsPriceChartWidget/config.ts +16 -7
- package/src/InstrumentsPriceChartWidget/render.tsx +12 -2
- package/src/InstrumentsPriceChartWidget/types.ts +5 -1
- package/src/InstrumentsPriceChartWidget/utils.ts +16 -0
- package/src/gql/{mock/getPriceCandles.ts → priceCandles.ts} +8 -7
- package/src/gql/types/gql.ts +4 -4
- package/src/gql/types/graphql.ts +62 -37
- package/src/translations/sources/en.json +2 -1
- package/test/Main.test.tsx +6 -2
- package/test/mocks/chartMock.ts +201 -37
- package/dist/main/InstrumentsPriceChartWidget/getPriceCandlesMock.js +0 -83
- package/dist/main/InstrumentsPriceChartWidget/getPriceCandlesMock.js.map +0 -1
- package/dist/main/gql/mock/getPriceCandles.js.map +0 -1
- package/dist/main/gql/mock/schema.graphqls +0 -62
- package/dist/module/InstrumentsPriceChartWidget/getPriceCandlesMock.js +0 -76
- package/dist/module/InstrumentsPriceChartWidget/getPriceCandlesMock.js.map +0 -1
- package/dist/module/gql/mock/getPriceCandles.js.map +0 -1
- package/dist/module/gql/mock/schema.graphqls +0 -62
- package/dist/types/InstrumentsPriceChartWidget/getPriceCandlesMock.d.ts +0 -9
- package/dist/types/gql/mock/getPriceCandles.d.ts +0 -2
- package/src/InstrumentsPriceChartWidget/getPriceCandlesMock.tsx +0 -75
- package/src/gql/mock/schema.graphqls +0 -62
|
@@ -1,46 +1,72 @@
|
|
|
1
|
+
import { useQuery } from '@apollo/client';
|
|
1
2
|
import {
|
|
3
|
+
ChartError,
|
|
2
4
|
Spinner,
|
|
3
5
|
SpinnerSize,
|
|
4
6
|
TimeUnitSwitch,
|
|
5
7
|
} from '@oanda/labs-widget-common';
|
|
6
8
|
import React, { useState } from 'react';
|
|
7
9
|
|
|
10
|
+
import { priceCandles } from '../gql/priceCandles';
|
|
11
|
+
import {
|
|
12
|
+
type PriceCandlesQuery,
|
|
13
|
+
type PriceCandlesQueryVariables,
|
|
14
|
+
TimeSpan,
|
|
15
|
+
} from '../gql/types/graphql';
|
|
8
16
|
import { Chart } from './components/Chart';
|
|
17
|
+
import { TimeZoneLabel } from './components/TimeZoneLabel';
|
|
9
18
|
import { timeUnitConfig } from './config';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
19
|
+
import { type MainProps } from './types';
|
|
20
|
+
import { getCandles, getGranularityForTimeSpan } from './utils';
|
|
12
21
|
|
|
13
|
-
const Main = ({ division }: MainProps) => {
|
|
14
|
-
const [selectedTimeUnit, setSelectedTimeUnit] = useState(
|
|
15
|
-
TimeSpanSubset.Day_1
|
|
16
|
-
);
|
|
22
|
+
const Main = ({ division, instrument, dataSource }: MainProps) => {
|
|
23
|
+
const [selectedTimeUnit, setSelectedTimeUnit] = useState(TimeSpan.Day_1);
|
|
17
24
|
|
|
18
|
-
const { loading, data, error } =
|
|
25
|
+
const { loading, data, error } = useQuery<
|
|
26
|
+
PriceCandlesQuery,
|
|
27
|
+
PriceCandlesQueryVariables
|
|
28
|
+
>(priceCandles, {
|
|
29
|
+
variables: {
|
|
30
|
+
dataSource,
|
|
31
|
+
division,
|
|
32
|
+
instrument,
|
|
33
|
+
granularity: getGranularityForTimeSpan(selectedTimeUnit),
|
|
34
|
+
timeSpan: selectedTimeUnit,
|
|
35
|
+
},
|
|
36
|
+
fetchPolicy: 'cache-and-network',
|
|
37
|
+
});
|
|
19
38
|
|
|
20
|
-
const
|
|
21
|
-
|
|
39
|
+
const candles = getCandles(data);
|
|
40
|
+
const showError = candles?.length === 0 || !candles || !!error;
|
|
22
41
|
|
|
23
42
|
return (
|
|
24
43
|
<div data-testid="instruments-price-chart-wrapper">
|
|
44
|
+
<div className="lw-flex lw-pt-2">
|
|
45
|
+
<TimeUnitSwitch<TimeSpan>
|
|
46
|
+
callback={setSelectedTimeUnit}
|
|
47
|
+
options={timeUnitConfig}
|
|
48
|
+
selected={selectedTimeUnit}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
25
52
|
{loading && (
|
|
26
|
-
<div className="lw-
|
|
53
|
+
<div className="lw-flex lw-h-[425px] lw-w-full lw-items-center lw-border lw-border-solid lw-border-border-primary">
|
|
27
54
|
<Spinner size={SpinnerSize.lg} />
|
|
28
55
|
</div>
|
|
29
56
|
)}
|
|
30
57
|
|
|
58
|
+
{!loading && showError && (
|
|
59
|
+
<div className="lw-flex lw-h-[425px] lw-w-full lw-items-center lw-border lw-border-solid lw-border-border-primary">
|
|
60
|
+
<ChartError />
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
|
|
31
64
|
{!loading && !showError && (
|
|
32
65
|
<div data-testid="instruments-price-chart-widget">
|
|
33
|
-
<
|
|
34
|
-
<TimeUnitSwitch<TimeSpanSubset>
|
|
35
|
-
callback={setSelectedTimeUnit}
|
|
36
|
-
options={timeUnitConfig}
|
|
37
|
-
selected={selectedTimeUnit}
|
|
38
|
-
/>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<Chart values={data?.getPriceCandles} />
|
|
66
|
+
<Chart timeSpan={selectedTimeUnit} values={candles} />
|
|
42
67
|
</div>
|
|
43
68
|
)}
|
|
69
|
+
<TimeZoneLabel />
|
|
44
70
|
</div>
|
|
45
71
|
);
|
|
46
72
|
};
|
|
@@ -33,7 +33,7 @@ echarts.use([
|
|
|
33
33
|
echarts.registerTheme('dark_theme', getChartTheme(Theme.Dark));
|
|
34
34
|
echarts.registerTheme('light_theme', getChartTheme(Theme.Light));
|
|
35
35
|
|
|
36
|
-
export const Chart = ({ values }: ChartProps) => {
|
|
36
|
+
export const Chart = ({ values, timeSpan }: ChartProps) => {
|
|
37
37
|
const { isDark } = useLayoutProvider();
|
|
38
38
|
const { lang } = useLocale();
|
|
39
39
|
|
|
@@ -46,6 +46,7 @@ export const Chart = ({ values }: ChartProps) => {
|
|
|
46
46
|
values,
|
|
47
47
|
isDark,
|
|
48
48
|
lang,
|
|
49
|
+
timeSpan,
|
|
49
50
|
})}
|
|
50
51
|
/>
|
|
51
52
|
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useLocale } from '@oanda/mono-i18n';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
const getBrowserOffsetLabel = (): string => {
|
|
5
|
+
const parts = new Intl.DateTimeFormat(undefined, {
|
|
6
|
+
timeZoneName: 'longOffset',
|
|
7
|
+
}).formatToParts(new Date());
|
|
8
|
+
|
|
9
|
+
const tzPart = parts.find((p) => p.type === 'timeZoneName');
|
|
10
|
+
return tzPart ? tzPart.value : '';
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const TimeZoneLabel = () => {
|
|
14
|
+
const { lang } = useLocale();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<span
|
|
18
|
+
className="lw-ml-1 lw-font-sans lw-text-xs lw-font-bold"
|
|
19
|
+
data-testid="timezone-label"
|
|
20
|
+
>{`${lang('timezone_display', {
|
|
21
|
+
timezone: getBrowserOffsetLabel(),
|
|
22
|
+
})}`}</span>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -1,21 +1,80 @@
|
|
|
1
|
-
import {
|
|
2
|
-
chartDateTimeFormat,
|
|
3
|
-
getChartDateAndTime,
|
|
4
|
-
} from '@oanda/labs-widget-common';
|
|
5
|
-
|
|
1
|
+
import { TimeSpan } from '../../gql/types/graphql';
|
|
6
2
|
import type { TooltipFormatterParams } from './types';
|
|
7
3
|
|
|
8
|
-
export const
|
|
9
|
-
const
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
export const showSplitLine = (index: number, total: number): boolean => {
|
|
5
|
+
const interval = Math.max(1, Math.floor(total / 6));
|
|
6
|
+
return index % interval === 0;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const showChartIntervalLabel = (
|
|
10
|
+
timeSpan: TimeSpan,
|
|
11
|
+
values: { point: string }[],
|
|
12
|
+
index: number
|
|
13
|
+
): boolean => {
|
|
14
|
+
const date = new Date(values[index].point);
|
|
15
|
+
const labelInterval = Math.max(1, Math.floor(values.length / 6));
|
|
16
|
+
|
|
17
|
+
if (
|
|
18
|
+
timeSpan === TimeSpan.Month_6 ||
|
|
19
|
+
timeSpan === TimeSpan.Year_1 ||
|
|
20
|
+
timeSpan === TimeSpan.Year_5
|
|
21
|
+
) {
|
|
22
|
+
const prev = index > 0 ? new Date(values[index - 1].point) : null;
|
|
23
|
+
const monthChanged =
|
|
24
|
+
!prev ||
|
|
25
|
+
date.getMonth() !== prev.getMonth() ||
|
|
26
|
+
date.getFullYear() !== prev.getFullYear();
|
|
27
|
+
|
|
28
|
+
return monthChanged;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return index % labelInterval === 0;
|
|
14
32
|
};
|
|
15
33
|
|
|
34
|
+
export const labelFormatter =
|
|
35
|
+
(timeSpan: TimeSpan, values: { point: string }[]) =>
|
|
36
|
+
(_: string, index: number): string => {
|
|
37
|
+
const date = new Date(values[index].point);
|
|
38
|
+
|
|
39
|
+
if (!showChartIntervalLabel(timeSpan, values, index)) return '';
|
|
40
|
+
|
|
41
|
+
switch (timeSpan) {
|
|
42
|
+
case TimeSpan.Day_1:
|
|
43
|
+
const hour = date.getHours();
|
|
44
|
+
return `${String(hour).padStart(2, '0')}:00`;
|
|
45
|
+
|
|
46
|
+
case TimeSpan.Week_1:
|
|
47
|
+
case TimeSpan.Month_1:
|
|
48
|
+
return date.toLocaleDateString(undefined, {
|
|
49
|
+
day: '2-digit',
|
|
50
|
+
month: 'short',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
case TimeSpan.Month_6:
|
|
54
|
+
case TimeSpan.Year_1:
|
|
55
|
+
case TimeSpan.Year_5:
|
|
56
|
+
return date.toLocaleDateString(undefined, {
|
|
57
|
+
month: 'short',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
default:
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const chartDateTimeFormat = (iso: string) =>
|
|
66
|
+
new Date(iso).toLocaleString(undefined, {
|
|
67
|
+
year: 'numeric',
|
|
68
|
+
month: '2-digit',
|
|
69
|
+
day: '2-digit',
|
|
70
|
+
hour: '2-digit',
|
|
71
|
+
minute: '2-digit',
|
|
72
|
+
hour12: false,
|
|
73
|
+
});
|
|
74
|
+
|
|
16
75
|
export const tooltipFormatter = ({ lang, data }: TooltipFormatterParams) => {
|
|
17
|
-
const { open, high, close, low,
|
|
18
|
-
const date = chartDateTimeFormat(
|
|
76
|
+
const { open, high, close, low, point } = data;
|
|
77
|
+
const date = chartDateTimeFormat(point);
|
|
19
78
|
|
|
20
79
|
return `
|
|
21
80
|
<div style="display:flex; flex-direction:column;">
|
|
@@ -11,15 +11,16 @@ import {
|
|
|
11
11
|
X_LABEL_SIZE,
|
|
12
12
|
Y_LABEL_SIZE_DESKTOP,
|
|
13
13
|
} from './constants';
|
|
14
|
-
import {
|
|
14
|
+
import { labelFormatter, showSplitLine, tooltipFormatter } from './formatters';
|
|
15
15
|
import type { ChartValue, GetOptionType } from './types';
|
|
16
16
|
|
|
17
|
-
export const getOption: GetOptionType = ({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
export const getOption: GetOptionType = ({
|
|
18
|
+
values,
|
|
19
|
+
isDark,
|
|
20
|
+
lang,
|
|
21
|
+
timeSpan,
|
|
22
|
+
}) => {
|
|
23
|
+
const priceValues = values.map((d) => d.high);
|
|
23
24
|
|
|
24
25
|
const gridLines = getGridLines({
|
|
25
26
|
isDark,
|
|
@@ -31,6 +32,7 @@ export const getOption: GetOptionType = ({ values, isDark, lang }) => {
|
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
return {
|
|
35
|
+
animation: false,
|
|
34
36
|
tooltip: {
|
|
35
37
|
trigger: 'axis',
|
|
36
38
|
axisPointer: {
|
|
@@ -39,7 +41,7 @@ export const getOption: GetOptionType = ({ values, isDark, lang }) => {
|
|
|
39
41
|
formatter: (params) => {
|
|
40
42
|
const p = Array.isArray(params) ? params[0] : params;
|
|
41
43
|
const data = p.data as {
|
|
42
|
-
|
|
44
|
+
point: string;
|
|
43
45
|
open: number;
|
|
44
46
|
high: number;
|
|
45
47
|
low: number;
|
|
@@ -52,19 +54,16 @@ export const getOption: GetOptionType = ({ values, isDark, lang }) => {
|
|
|
52
54
|
xAxis: {
|
|
53
55
|
type: 'category',
|
|
54
56
|
boundaryGap: false,
|
|
55
|
-
data:
|
|
56
|
-
axisTick: {
|
|
57
|
-
show: false,
|
|
58
|
-
},
|
|
57
|
+
data: values.map((d) => d.point),
|
|
58
|
+
axisTick: { show: false },
|
|
59
59
|
axisLabel: {
|
|
60
|
-
|
|
61
|
-
interval:
|
|
62
|
-
formatter: (
|
|
63
|
-
index % labelInterval === 0 ? value : '',
|
|
60
|
+
padding: [0, 0, 0, 30],
|
|
61
|
+
interval: 'auto',
|
|
62
|
+
formatter: labelFormatter(timeSpan, values),
|
|
64
63
|
},
|
|
65
64
|
splitLine: {
|
|
66
65
|
show: true,
|
|
67
|
-
interval: (index: number) => index
|
|
66
|
+
interval: (index: number) => showSplitLine(index, values.length),
|
|
68
67
|
},
|
|
69
68
|
},
|
|
70
69
|
yAxis: {
|
|
@@ -78,7 +77,7 @@ export const getOption: GetOptionType = ({ values, isDark, lang }) => {
|
|
|
78
77
|
axisLine: { show: false },
|
|
79
78
|
axisTick: { show: false },
|
|
80
79
|
axisLabel: {
|
|
81
|
-
margin:
|
|
80
|
+
margin: 10,
|
|
82
81
|
formatter: (value: number) => {
|
|
83
82
|
const dataMin = Math.floor(Math.min(...priceValues) * 100) / 100;
|
|
84
83
|
const dataMax = Math.ceil(Math.max(...priceValues) * 100) / 100;
|
|
@@ -97,9 +96,9 @@ export const getOption: GetOptionType = ({ values, isDark, lang }) => {
|
|
|
97
96
|
name: 'price-close',
|
|
98
97
|
type: 'line',
|
|
99
98
|
data: values.map((d) => ({
|
|
100
|
-
value: d.
|
|
101
|
-
...d
|
|
102
|
-
time: d.
|
|
99
|
+
value: d.high,
|
|
100
|
+
...d,
|
|
101
|
+
time: d.point,
|
|
103
102
|
})),
|
|
104
103
|
smooth: true,
|
|
105
104
|
symbol: 'none',
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { EChartsOption } from 'echarts';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { Candle, TimeSpan } from '../../gql/types/graphql';
|
|
4
4
|
|
|
5
5
|
export interface ChartProps {
|
|
6
|
-
values:
|
|
6
|
+
values: Candle[];
|
|
7
|
+
timeSpan: TimeSpan;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export interface GetOptionProps {
|
|
10
|
-
values:
|
|
11
|
+
values: Candle[];
|
|
11
12
|
isDark: boolean;
|
|
12
13
|
lang: (label: string) => string;
|
|
14
|
+
timeSpan: TimeSpan;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export type GetOptionType = (props: GetOptionProps) => EChartsOption;
|
|
@@ -20,7 +22,7 @@ export interface ChartValue {
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export interface TooltipData {
|
|
23
|
-
|
|
25
|
+
point: string;
|
|
24
26
|
open: number;
|
|
25
27
|
high: number;
|
|
26
28
|
low: number;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Granularity, TimeSpan } from '../gql/types/graphql';
|
|
2
|
+
import type { TimeSpanSubset } from './types';
|
|
2
3
|
|
|
3
4
|
const timeUnitConfig = [
|
|
4
5
|
{
|
|
5
|
-
value:
|
|
6
|
+
value: TimeSpan.Day_1,
|
|
6
7
|
label: '1d',
|
|
7
8
|
tooltipLabel: {
|
|
8
9
|
translationKey: 'day',
|
|
@@ -10,7 +11,7 @@ const timeUnitConfig = [
|
|
|
10
11
|
},
|
|
11
12
|
},
|
|
12
13
|
{
|
|
13
|
-
value:
|
|
14
|
+
value: TimeSpan.Week_1,
|
|
14
15
|
label: '1w',
|
|
15
16
|
tooltipLabel: {
|
|
16
17
|
translationKey: 'week',
|
|
@@ -18,7 +19,7 @@ const timeUnitConfig = [
|
|
|
18
19
|
},
|
|
19
20
|
},
|
|
20
21
|
{
|
|
21
|
-
value:
|
|
22
|
+
value: TimeSpan.Month_1,
|
|
22
23
|
label: '1m',
|
|
23
24
|
tooltipLabel: {
|
|
24
25
|
translationKey: 'month',
|
|
@@ -27,7 +28,7 @@ const timeUnitConfig = [
|
|
|
27
28
|
},
|
|
28
29
|
|
|
29
30
|
{
|
|
30
|
-
value:
|
|
31
|
+
value: TimeSpan.Month_6,
|
|
31
32
|
label: '6m',
|
|
32
33
|
tooltipLabel: {
|
|
33
34
|
translationKey: 'month',
|
|
@@ -35,7 +36,7 @@ const timeUnitConfig = [
|
|
|
35
36
|
},
|
|
36
37
|
},
|
|
37
38
|
{
|
|
38
|
-
value:
|
|
39
|
+
value: TimeSpan.Year_1,
|
|
39
40
|
label: '1y',
|
|
40
41
|
tooltipLabel: {
|
|
41
42
|
translationKey: 'year',
|
|
@@ -44,4 +45,12 @@ const timeUnitConfig = [
|
|
|
44
45
|
},
|
|
45
46
|
];
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
const granularityForTimeSpan: Record<TimeSpanSubset, Granularity> = {
|
|
49
|
+
[TimeSpan.Day_1]: Granularity.H1,
|
|
50
|
+
[TimeSpan.Week_1]: Granularity.H4,
|
|
51
|
+
[TimeSpan.Month_1]: Granularity.D1,
|
|
52
|
+
[TimeSpan.Month_6]: Granularity.D1,
|
|
53
|
+
[TimeSpan.Year_1]: Granularity.D1,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { granularityForTimeSpan, timeUnitConfig };
|
|
@@ -3,7 +3,7 @@ import { validateLocale, validateToolParams } from '@oanda/labs-widget-common';
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { createRoot } from 'react-dom/client';
|
|
5
5
|
|
|
6
|
-
import { Division } from '../gql/types/graphql';
|
|
6
|
+
import { Division, InstrumentDataSource } from '../gql/types/graphql';
|
|
7
7
|
import { InstrumentsPriceChartWidget } from './InstrumentsPriceChartWidget';
|
|
8
8
|
|
|
9
9
|
const { graphqlUrl } = window.widgetsConfig || {};
|
|
@@ -16,13 +16,16 @@ dataInstrumentsPriceChartParamsElements.forEach((element) => {
|
|
|
16
16
|
const root = createRoot(element);
|
|
17
17
|
const params = element.getAttribute('data-instruments-price-chart-params');
|
|
18
18
|
const mode = element.getAttribute('data-mode');
|
|
19
|
-
const { division, locale } = JSON.parse(
|
|
19
|
+
const { division, locale, instrument, dataSource } = JSON.parse(
|
|
20
|
+
params as string
|
|
21
|
+
);
|
|
20
22
|
|
|
21
23
|
const isParamError = validateToolParams(
|
|
22
24
|
{
|
|
23
25
|
division,
|
|
24
26
|
locale,
|
|
25
27
|
graphqlUrl,
|
|
28
|
+
dataSource,
|
|
26
29
|
},
|
|
27
30
|
[
|
|
28
31
|
{
|
|
@@ -37,13 +40,20 @@ dataInstrumentsPriceChartParamsElements.forEach((element) => {
|
|
|
37
40
|
{
|
|
38
41
|
name: 'graphqlUrl',
|
|
39
42
|
},
|
|
43
|
+
{
|
|
44
|
+
name: 'dataSource',
|
|
45
|
+
valueCheck: (value: InstrumentDataSource) =>
|
|
46
|
+
Object.values(InstrumentDataSource).includes(value),
|
|
47
|
+
},
|
|
40
48
|
]
|
|
41
49
|
);
|
|
42
50
|
|
|
43
51
|
root.render(
|
|
44
52
|
<InstrumentsPriceChartWidget
|
|
53
|
+
dataSource={dataSource}
|
|
45
54
|
division={division}
|
|
46
55
|
graphqlUrl={graphqlUrl}
|
|
56
|
+
instrument={instrument}
|
|
47
57
|
isParamError={isParamError}
|
|
48
58
|
locale={locale}
|
|
49
59
|
theme={mode as Theme}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import type { WidgetConfig } from '@oanda/labs-widget-common';
|
|
2
2
|
|
|
3
|
-
import type { Division } from '../gql/types/graphql';
|
|
3
|
+
import type { Division, InstrumentDataSource } from '../gql/types/graphql';
|
|
4
4
|
import { TimeSpan } from '../gql/types/graphql';
|
|
5
5
|
|
|
6
6
|
export interface InstrumentsPriceChartConfig extends WidgetConfig {
|
|
7
7
|
division: Division;
|
|
8
|
+
instrument: string;
|
|
9
|
+
dataSource: InstrumentDataSource;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export interface MainProps {
|
|
11
13
|
division: Division;
|
|
14
|
+
instrument: string;
|
|
15
|
+
dataSource: InstrumentDataSource;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export enum TimeSpanSubset {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Candle,
|
|
3
|
+
Granularity,
|
|
4
|
+
PriceCandlesQuery,
|
|
5
|
+
TimeSpan,
|
|
6
|
+
} from '../gql/types/graphql';
|
|
7
|
+
import { granularityForTimeSpan } from './config';
|
|
8
|
+
import type { TimeSpanSubset } from './types';
|
|
9
|
+
|
|
10
|
+
export const getGranularityForTimeSpan = (timeSpan: TimeSpan): Granularity =>
|
|
11
|
+
granularityForTimeSpan[timeSpan as unknown as TimeSpanSubset];
|
|
12
|
+
|
|
13
|
+
export const getCandles = (
|
|
14
|
+
data: PriceCandlesQuery | undefined
|
|
15
|
+
): Candle[] | undefined =>
|
|
16
|
+
data?.priceCandles.candle.filter((c): c is Candle => c !== null);
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { gql } from '@apollo/client';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
query
|
|
5
|
-
$division: Division
|
|
3
|
+
const priceCandles = gql`
|
|
4
|
+
query PriceCandles(
|
|
5
|
+
$division: Division!
|
|
6
|
+
$dataSource: InstrumentDataSource!
|
|
6
7
|
$instrument: String!
|
|
7
8
|
$granularity: Granularity!
|
|
8
9
|
$timeSpan: TimeSpan!
|
|
9
10
|
) {
|
|
10
|
-
|
|
11
|
+
priceCandles(
|
|
11
12
|
division: $division
|
|
13
|
+
dataSource: $dataSource
|
|
12
14
|
instrument: $instrument
|
|
13
15
|
granularity: $granularity
|
|
14
16
|
timeSpan: $timeSpan
|
|
15
17
|
) {
|
|
16
|
-
time
|
|
17
|
-
unixTime
|
|
18
18
|
candle {
|
|
19
|
+
point
|
|
19
20
|
high
|
|
20
21
|
low
|
|
21
22
|
open
|
|
@@ -26,4 +27,4 @@ const getPriceCandles = gql`
|
|
|
26
27
|
}
|
|
27
28
|
`;
|
|
28
29
|
|
|
29
|
-
export {
|
|
30
|
+
export { priceCandles };
|
package/src/gql/types/gql.ts
CHANGED
|
@@ -13,8 +13,8 @@ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/
|
|
|
13
13
|
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
|
14
14
|
*/
|
|
15
15
|
const documents = {
|
|
16
|
-
'\n query
|
|
17
|
-
types.
|
|
16
|
+
'\n query PriceCandles(\n $division: Division!\n $dataSource: InstrumentDataSource!\n $instrument: String!\n $granularity: Granularity!\n $timeSpan: TimeSpan!\n ) {\n priceCandles(\n division: $division\n dataSource: $dataSource\n instrument: $instrument\n granularity: $granularity\n timeSpan: $timeSpan\n ) {\n candle {\n point\n high\n low\n open\n close\n }\n timeSpan\n }\n }\n':
|
|
17
|
+
types.PriceCandlesDocument,
|
|
18
18
|
'\n query validateInstruments($instruments: [String]!, $division: Division) {\n mapInstrumentNames(instruments: $instruments, division: $division) {\n name\n displayName\n }\n }\n':
|
|
19
19
|
types.ValidateInstrumentsDocument,
|
|
20
20
|
};
|
|
@@ -37,8 +37,8 @@ export function graphql(source: string): unknown;
|
|
|
37
37
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
|
38
38
|
*/
|
|
39
39
|
export function graphql(
|
|
40
|
-
source: '\n query
|
|
41
|
-
): (typeof documents)['\n query
|
|
40
|
+
source: '\n query PriceCandles(\n $division: Division!\n $dataSource: InstrumentDataSource!\n $instrument: String!\n $granularity: Granularity!\n $timeSpan: TimeSpan!\n ) {\n priceCandles(\n division: $division\n dataSource: $dataSource\n instrument: $instrument\n granularity: $granularity\n timeSpan: $timeSpan\n ) {\n candle {\n point\n high\n low\n open\n close\n }\n timeSpan\n }\n }\n'
|
|
41
|
+
): (typeof documents)['\n query PriceCandles(\n $division: Division!\n $dataSource: InstrumentDataSource!\n $instrument: String!\n $granularity: Granularity!\n $timeSpan: TimeSpan!\n ) {\n priceCandles(\n division: $division\n dataSource: $dataSource\n instrument: $instrument\n granularity: $granularity\n timeSpan: $timeSpan\n ) {\n candle {\n point\n high\n low\n open\n close\n }\n timeSpan\n }\n }\n'];
|
|
42
42
|
/**
|
|
43
43
|
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
|
44
44
|
*/
|