@xyo-network/react-price-forecast-plugin 2.48.8
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/LICENSE +165 -0
- package/README.md +13 -0
- package/dist/cjs/Plugin.js +15 -0
- package/dist/cjs/Plugin.js.map +1 -0
- package/dist/cjs/components/DetailsBox.js +33 -0
- package/dist/cjs/components/DetailsBox.js.map +1 -0
- package/dist/cjs/components/index.js +5 -0
- package/dist/cjs/components/index.js.map +1 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib/DataLineStyles.js +10 -0
- package/dist/cjs/lib/DataLineStyles.js.map +1 -0
- package/dist/cjs/lib/DataPointStyles.js +12 -0
- package/dist/cjs/lib/DataPointStyles.js.map +1 -0
- package/dist/cjs/lib/ForecastLineChartConfigBuilder.js +133 -0
- package/dist/cjs/lib/ForecastLineChartConfigBuilder.js.map +1 -0
- package/dist/cjs/lib/MockSourcePayloads.js +24 -0
- package/dist/cjs/lib/MockSourcePayloads.js.map +1 -0
- package/dist/cjs/lib/SourcePayloads.js +49 -0
- package/dist/cjs/lib/SourcePayloads.js.map +1 -0
- package/dist/cjs/lib/index.js +7 -0
- package/dist/cjs/lib/index.js.map +1 -0
- package/dist/docs.json +18568 -0
- package/dist/esm/Plugin.js +14 -0
- package/dist/esm/Plugin.js.map +1 -0
- package/dist/esm/components/DetailsBox.js +27 -0
- package/dist/esm/components/DetailsBox.js.map +1 -0
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/components/index.js.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/DataLineStyles.js +6 -0
- package/dist/esm/lib/DataLineStyles.js.map +1 -0
- package/dist/esm/lib/DataPointStyles.js +8 -0
- package/dist/esm/lib/DataPointStyles.js.map +1 -0
- package/dist/esm/lib/ForecastLineChartConfigBuilder.js +130 -0
- package/dist/esm/lib/ForecastLineChartConfigBuilder.js.map +1 -0
- package/dist/esm/lib/MockSourcePayloads.js +20 -0
- package/dist/esm/lib/MockSourcePayloads.js.map +1 -0
- package/dist/esm/lib/SourcePayloads.js +41 -0
- package/dist/esm/lib/SourcePayloads.js.map +1 -0
- package/dist/esm/lib/index.js +4 -0
- package/dist/esm/lib/index.js.map +1 -0
- package/dist/types/Plugin.d.ts +3 -0
- package/dist/types/Plugin.d.ts.map +1 -0
- package/dist/types/components/DetailsBox.d.ts +9 -0
- package/dist/types/components/DetailsBox.d.ts.map +1 -0
- package/dist/types/components/index.d.ts +2 -0
- package/dist/types/components/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lib/DataLineStyles.d.ts +5 -0
- package/dist/types/lib/DataLineStyles.d.ts.map +1 -0
- package/dist/types/lib/DataPointStyles.d.ts +8 -0
- package/dist/types/lib/DataPointStyles.d.ts.map +1 -0
- package/dist/types/lib/ForecastLineChartConfigBuilder.d.ts +47 -0
- package/dist/types/lib/ForecastLineChartConfigBuilder.d.ts.map +1 -0
- package/dist/types/lib/MockSourcePayloads.d.ts +18 -0
- package/dist/types/lib/MockSourcePayloads.d.ts.map +1 -0
- package/dist/types/lib/SourcePayloads.d.ts +30 -0
- package/dist/types/lib/SourcePayloads.d.ts.map +1 -0
- package/dist/types/lib/index.d.ts +4 -0
- package/dist/types/lib/index.d.ts.map +1 -0
- package/package.json +82 -0
- package/src/Plugin.ts +15 -0
- package/src/components/DetailsBox.stories.tsx +78 -0
- package/src/components/DetailsBox.tsx +55 -0
- package/src/components/index.ts +1 -0
- package/src/index.ts +2 -0
- package/src/lib/DataLineStyles.ts +6 -0
- package/src/lib/DataPointStyles.ts +7 -0
- package/src/lib/ForecastLineChartConfigBuilder.ts +169 -0
- package/src/lib/MockSourcePayloads.ts +19 -0
- package/src/lib/SourcePayloads.ts +47 -0
- package/src/lib/index.ts +3 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Button, ButtonGroup, Typography } from '@mui/material'
|
|
2
|
+
import { Meta, StoryFn } from '@storybook/react'
|
|
3
|
+
import { FlexCol } from '@xylabs/react-flexbox'
|
|
4
|
+
import { ForecastPayloadSchema } from '@xyo-network/diviner-forecasting-model'
|
|
5
|
+
import { RefObject, useRef, useState } from 'react'
|
|
6
|
+
|
|
7
|
+
import { MockSourcePayloads } from '../lib'
|
|
8
|
+
import { PriceForecastDetailsBox } from './DetailsBox'
|
|
9
|
+
|
|
10
|
+
const tenMin = 600000
|
|
11
|
+
|
|
12
|
+
const ForecastingDivinerPayload = {
|
|
13
|
+
schema: ForecastPayloadSchema,
|
|
14
|
+
values: [1, 2, 3, 4, 5, 6, 7, 8].map((item) => ({
|
|
15
|
+
error: 0,
|
|
16
|
+
timestamp: Date.now() + tenMin * item,
|
|
17
|
+
value: 100 * item,
|
|
18
|
+
})),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const StorybookEntry = {
|
|
22
|
+
argTypes: {},
|
|
23
|
+
component: PriceForecastDetailsBox,
|
|
24
|
+
parameters: {
|
|
25
|
+
docs: {
|
|
26
|
+
page: null,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
title: 'plugin/price-forecast/DetailsBox',
|
|
30
|
+
} as Meta<typeof PriceForecastDetailsBox>
|
|
31
|
+
|
|
32
|
+
const Template: StoryFn<typeof PriceForecastDetailsBox> = (args) => {
|
|
33
|
+
const [showPayloads, setShowPayloads] = useState(false)
|
|
34
|
+
const forecastPayloadRef = useRef<HTMLParagraphElement>(null)
|
|
35
|
+
const sourcePayloadsRef = useRef<HTMLParagraphElement>(null)
|
|
36
|
+
const handleClick = (ref: RefObject<HTMLParagraphElement>) => {
|
|
37
|
+
setShowPayloads(!showPayloads)
|
|
38
|
+
if (ref.current) ref.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
39
|
+
}
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<PriceForecastDetailsBox mb={3} {...args} />
|
|
43
|
+
<FlexCol>
|
|
44
|
+
<ButtonGroup>
|
|
45
|
+
<Button variant="contained" onClick={() => handleClick(forecastPayloadRef)}>
|
|
46
|
+
Forecast Payload
|
|
47
|
+
</Button>
|
|
48
|
+
<Button variant="contained" onClick={() => handleClick(sourcePayloadsRef)}>
|
|
49
|
+
Source Payloads
|
|
50
|
+
</Button>
|
|
51
|
+
</ButtonGroup>
|
|
52
|
+
</FlexCol>
|
|
53
|
+
<pre>
|
|
54
|
+
<Typography ref={forecastPayloadRef}>
|
|
55
|
+
ForecastPayload: <code>{JSON.stringify(args.payload, null, 2)}</code>
|
|
56
|
+
</Typography>
|
|
57
|
+
</pre>
|
|
58
|
+
<pre>
|
|
59
|
+
<Typography ref={sourcePayloadsRef}>
|
|
60
|
+
SourcePayloads: <pre>{JSON.stringify(MockSourcePayloads(), null, 2)}</pre>
|
|
61
|
+
</Typography>
|
|
62
|
+
</pre>
|
|
63
|
+
</>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const Default = Template.bind({})
|
|
68
|
+
Default.args = {}
|
|
69
|
+
|
|
70
|
+
const WithData = Template.bind({})
|
|
71
|
+
WithData.args = {
|
|
72
|
+
payload: ForecastingDivinerPayload,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { Default, WithData }
|
|
76
|
+
|
|
77
|
+
// eslint-disable-next-line import/no-default-export
|
|
78
|
+
export default StorybookEntry
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import 'chartjs-adapter-luxon'
|
|
2
|
+
|
|
3
|
+
import { useTheme } from '@mui/material'
|
|
4
|
+
import { useAsyncEffect } from '@xylabs/react-async-effect'
|
|
5
|
+
import { FlexBoxProps, FlexCol } from '@xylabs/react-flexbox'
|
|
6
|
+
import { ForecastPayload } from '@xyo-network/diviner-forecasting-model'
|
|
7
|
+
import { Payload } from '@xyo-network/payload-model'
|
|
8
|
+
import {
|
|
9
|
+
CategoryScale,
|
|
10
|
+
Chart as ChartJS,
|
|
11
|
+
ChartData,
|
|
12
|
+
ChartOptions,
|
|
13
|
+
Legend,
|
|
14
|
+
LinearScale,
|
|
15
|
+
LineElement,
|
|
16
|
+
PointElement,
|
|
17
|
+
TimeScale,
|
|
18
|
+
Title,
|
|
19
|
+
Tooltip,
|
|
20
|
+
} from 'chart.js'
|
|
21
|
+
import { useState } from 'react'
|
|
22
|
+
import { Line } from 'react-chartjs-2'
|
|
23
|
+
|
|
24
|
+
import { ForecastLineChartConfigBuilder } from '../lib'
|
|
25
|
+
|
|
26
|
+
ChartJS.register(CategoryScale, TimeScale, PointElement, LineElement, LinearScale, Title, Tooltip, Legend)
|
|
27
|
+
|
|
28
|
+
export interface PriceForecastDetailsBoxProps extends FlexBoxProps {
|
|
29
|
+
payload?: Payload
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const PriceForecastDetailsBox: React.FC<PriceForecastDetailsBoxProps> = ({ payload, ...props }) => {
|
|
33
|
+
const priceForecastPayload = payload as ForecastPayload | undefined
|
|
34
|
+
const theme = useTheme()
|
|
35
|
+
const [data, setData] = useState<ChartData<'line'>>({ datasets: [] })
|
|
36
|
+
const [options, setOptions] = useState<ChartOptions<'line'>>({})
|
|
37
|
+
|
|
38
|
+
useAsyncEffect(
|
|
39
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
40
|
+
async (mounted) => {
|
|
41
|
+
const { data, options } = await ForecastLineChartConfigBuilder.create(theme, priceForecastPayload, { fetch: true })
|
|
42
|
+
if (mounted()) {
|
|
43
|
+
setData(data)
|
|
44
|
+
setOptions(options)
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
[priceForecastPayload, theme],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<FlexCol {...props} busy={priceForecastPayload === undefined} minHeight="25vh">
|
|
52
|
+
{priceForecastPayload ? <Line options={options} data={data} /> : null}
|
|
53
|
+
</FlexCol>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './DetailsBox'
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Theme } from '@mui/material'
|
|
2
|
+
import { ForecastPayload } from '@xyo-network/diviner-forecasting-model'
|
|
3
|
+
import { ChartData, ChartDataset, ChartOptions, LegendOptions, Point, ScaleChartOptions } from 'chart.js'
|
|
4
|
+
// eslint-disable-next-line import/no-unresolved
|
|
5
|
+
import { _DeepPartialObject } from 'chart.js/dist/types/utils'
|
|
6
|
+
|
|
7
|
+
import { DataLineStyles } from './DataLineStyles'
|
|
8
|
+
import { DataPointStyles } from './DataPointStyles'
|
|
9
|
+
import { SourcePayloads } from './SourcePayloads'
|
|
10
|
+
|
|
11
|
+
interface SourcePayloadConfig {
|
|
12
|
+
fetch: boolean
|
|
13
|
+
sampleSize?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ThemeColors {
|
|
17
|
+
dataSetColorPrimary: string
|
|
18
|
+
dataSetColorSecondary: string
|
|
19
|
+
gridColor: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaultOptions: () => ChartOptions<'line'> = () => ({
|
|
23
|
+
plugins: {
|
|
24
|
+
legend: {
|
|
25
|
+
position: 'top' as const,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
responsive: true,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export class ForecastLineChartConfigBuilder {
|
|
32
|
+
data: ChartData<'line'> = {
|
|
33
|
+
datasets: [],
|
|
34
|
+
}
|
|
35
|
+
options: ChartOptions<'line'> = defaultOptions()
|
|
36
|
+
themeColors: ThemeColors | undefined
|
|
37
|
+
|
|
38
|
+
constructor(theme: Theme, private payload?: ForecastPayload) {
|
|
39
|
+
this.themeColors = this.parseTheme(theme)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get forecastPayload() {
|
|
43
|
+
if (this.payload) {
|
|
44
|
+
return this.payload
|
|
45
|
+
} else {
|
|
46
|
+
throw Error('ForecastPayload was not defined')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static async create(theme: Theme, payload?: ForecastPayload, sourcePayloadConfig?: SourcePayloadConfig) {
|
|
51
|
+
const instance = new ForecastLineChartConfigBuilder(theme, payload)
|
|
52
|
+
|
|
53
|
+
await instance.build(sourcePayloadConfig?.fetch)
|
|
54
|
+
|
|
55
|
+
instance.refreshValues()
|
|
56
|
+
|
|
57
|
+
return instance
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async build(includeSources?: boolean) {
|
|
61
|
+
this.buildOptions()
|
|
62
|
+
await this.buildData(includeSources)
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async buildData(includeSources?: boolean) {
|
|
67
|
+
const forecastData = this.generateDataSetForecastData()
|
|
68
|
+
|
|
69
|
+
const datasets: ChartDataset<'line'>[] = [forecastData]
|
|
70
|
+
|
|
71
|
+
if (includeSources) {
|
|
72
|
+
// build data from sources in forecastPayload
|
|
73
|
+
const sourceData = await this.generateDataSetSourcePayloads()
|
|
74
|
+
datasets.unshift(sourceData)
|
|
75
|
+
|
|
76
|
+
// add last source point as first item in prediction to connect the lines
|
|
77
|
+
const lastSourceDataItem = sourceData.data.at(-1) as Point
|
|
78
|
+
forecastData.data.unshift(lastSourceDataItem)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.data = {
|
|
82
|
+
datasets,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return this
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
buildOptions() {
|
|
89
|
+
if (this.options.plugins) {
|
|
90
|
+
this.options.plugins.title = this.generateTitle()
|
|
91
|
+
this.options.plugins.legend = this.generateLegend()
|
|
92
|
+
}
|
|
93
|
+
this.options.scales = this.generateScales()
|
|
94
|
+
|
|
95
|
+
return this
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
refreshValues() {
|
|
99
|
+
this.data = { ...this.data }
|
|
100
|
+
this.options = { ...this.options }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
protected generateLegend(): _DeepPartialObject<LegendOptions<'line'>> {
|
|
104
|
+
return {
|
|
105
|
+
labels: {
|
|
106
|
+
pointStyle: 'circle',
|
|
107
|
+
usePointStyle: true,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
protected generateScales(): _DeepPartialObject<ScaleChartOptions<'line'>['scales']> {
|
|
113
|
+
return {
|
|
114
|
+
x: {
|
|
115
|
+
grid: {
|
|
116
|
+
color: this.themeColors?.gridColor,
|
|
117
|
+
},
|
|
118
|
+
time: {
|
|
119
|
+
unit: 'minute',
|
|
120
|
+
},
|
|
121
|
+
type: 'time',
|
|
122
|
+
},
|
|
123
|
+
y: {
|
|
124
|
+
grid: {
|
|
125
|
+
color: this.themeColors?.gridColor,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected generateTitle() {
|
|
132
|
+
return {
|
|
133
|
+
display: true,
|
|
134
|
+
text: `Gas Price Forecaster (GWEI over time from ${
|
|
135
|
+
this.forecastPayload?.values[0].timestamp ? new Date(this.forecastPayload.values[0].timestamp).toLocaleDateString() : ''
|
|
136
|
+
})`,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
protected parseTheme(theme: Theme) {
|
|
141
|
+
const dark = theme.palette.mode === 'dark'
|
|
142
|
+
return {
|
|
143
|
+
dataSetColorPrimary: theme.palette.primary.light,
|
|
144
|
+
dataSetColorSecondary: theme.palette.secondary.light,
|
|
145
|
+
gridColor: dark ? theme.palette.grey[800] : theme.palette.grey[300],
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private generateDataSetForecastData(): ChartDataset<'line'> {
|
|
150
|
+
return {
|
|
151
|
+
borderDash: [5],
|
|
152
|
+
borderDashOffset: 0.5,
|
|
153
|
+
data: this.forecastPayload.values.map((price) => ({ x: price.timestamp ?? 0, y: price.value })),
|
|
154
|
+
label: 'Forecast Price',
|
|
155
|
+
...DataPointStyles(this.themeColors?.dataSetColorPrimary),
|
|
156
|
+
...DataLineStyles(this.themeColors?.dataSetColorPrimary),
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private async generateDataSetSourcePayloads(): Promise<ChartDataset<'line'>> {
|
|
161
|
+
const { sourcePrices } = await SourcePayloads.build('feePerGas.medium')
|
|
162
|
+
return {
|
|
163
|
+
data: sourcePrices,
|
|
164
|
+
label: 'Source Prices',
|
|
165
|
+
...DataLineStyles(this.themeColors?.dataSetColorSecondary),
|
|
166
|
+
...DataPointStyles(this.themeColors?.dataSetColorSecondary),
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const MockSourcePayloads = () => {
|
|
2
|
+
const tenMin = 600000
|
|
3
|
+
return [
|
|
4
|
+
{
|
|
5
|
+
baseFee: 38.90155387825,
|
|
6
|
+
feePerGas: { high: 47.9945864396, low: 39.006868093, medium: 39.306868093, veryHigh: 44.45384380525 },
|
|
7
|
+
priorityFeePerGas: { high: 1.0266666666666666, low: -0.41000000000000003, medium: 0.38, veryHigh: 1.3900000000000001 },
|
|
8
|
+
schema: 'network.xyo.blockchain.ethereum.gas',
|
|
9
|
+
timestamp: Date.now() - tenMin,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
baseFee: 38.90155387825,
|
|
13
|
+
feePerGas: { high: 47.9945864396, low: 39.006868093, medium: 100, veryHigh: 44.45384380525 },
|
|
14
|
+
priorityFeePerGas: { high: 1.0266666666666666, low: -0.41000000000000003, medium: 0.38, veryHigh: 1.3900000000000001 },
|
|
15
|
+
schema: 'network.xyo.blockchain.ethereum.gas',
|
|
16
|
+
timestamp: Date.now(),
|
|
17
|
+
},
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Payload } from '@xyo-network/payload-model'
|
|
2
|
+
import { Point } from 'chart.js'
|
|
3
|
+
|
|
4
|
+
import { MockSourcePayloads } from './MockSourcePayloads'
|
|
5
|
+
|
|
6
|
+
export class SourcePayloads {
|
|
7
|
+
sourcePrices: Point[] = []
|
|
8
|
+
|
|
9
|
+
constructor(public sourcePayloads: Payload[]) {}
|
|
10
|
+
|
|
11
|
+
get payloads() {
|
|
12
|
+
return this.sourcePayloads
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static async build(jsonPath: string) {
|
|
16
|
+
const sourcePayloads = await this.fetchSourcePayloads()
|
|
17
|
+
const instance = new this(sourcePayloads)
|
|
18
|
+
|
|
19
|
+
const paths = jsonPath.split('.')
|
|
20
|
+
instance.sourcePrices = sourcePayloads.map((payload) => {
|
|
21
|
+
return { x: payload.timestamp, y: instance.jsonPathTraverser(payload, paths) }
|
|
22
|
+
})
|
|
23
|
+
return instance
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// TODO - fetch from archivist
|
|
27
|
+
static async fetchSourcePayloads() {
|
|
28
|
+
const payloads = await Promise.resolve(MockSourcePayloads())
|
|
29
|
+
return payloads
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
jsonPathTraverser(obj: Payload, path: string[]) {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
let result: any = obj
|
|
35
|
+
for (const key of path) {
|
|
36
|
+
if (key in result) {
|
|
37
|
+
const foundKey = key as keyof typeof result
|
|
38
|
+
result = result[foundKey]
|
|
39
|
+
} else {
|
|
40
|
+
result = undefined
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/lib/index.ts
ADDED