@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.
Files changed (75) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +13 -0
  3. package/dist/cjs/Plugin.js +15 -0
  4. package/dist/cjs/Plugin.js.map +1 -0
  5. package/dist/cjs/components/DetailsBox.js +33 -0
  6. package/dist/cjs/components/DetailsBox.js.map +1 -0
  7. package/dist/cjs/components/index.js +5 -0
  8. package/dist/cjs/components/index.js.map +1 -0
  9. package/dist/cjs/index.js +6 -0
  10. package/dist/cjs/index.js.map +1 -0
  11. package/dist/cjs/lib/DataLineStyles.js +10 -0
  12. package/dist/cjs/lib/DataLineStyles.js.map +1 -0
  13. package/dist/cjs/lib/DataPointStyles.js +12 -0
  14. package/dist/cjs/lib/DataPointStyles.js.map +1 -0
  15. package/dist/cjs/lib/ForecastLineChartConfigBuilder.js +133 -0
  16. package/dist/cjs/lib/ForecastLineChartConfigBuilder.js.map +1 -0
  17. package/dist/cjs/lib/MockSourcePayloads.js +24 -0
  18. package/dist/cjs/lib/MockSourcePayloads.js.map +1 -0
  19. package/dist/cjs/lib/SourcePayloads.js +49 -0
  20. package/dist/cjs/lib/SourcePayloads.js.map +1 -0
  21. package/dist/cjs/lib/index.js +7 -0
  22. package/dist/cjs/lib/index.js.map +1 -0
  23. package/dist/docs.json +18568 -0
  24. package/dist/esm/Plugin.js +14 -0
  25. package/dist/esm/Plugin.js.map +1 -0
  26. package/dist/esm/components/DetailsBox.js +27 -0
  27. package/dist/esm/components/DetailsBox.js.map +1 -0
  28. package/dist/esm/components/index.js +2 -0
  29. package/dist/esm/components/index.js.map +1 -0
  30. package/dist/esm/index.js +3 -0
  31. package/dist/esm/index.js.map +1 -0
  32. package/dist/esm/lib/DataLineStyles.js +6 -0
  33. package/dist/esm/lib/DataLineStyles.js.map +1 -0
  34. package/dist/esm/lib/DataPointStyles.js +8 -0
  35. package/dist/esm/lib/DataPointStyles.js.map +1 -0
  36. package/dist/esm/lib/ForecastLineChartConfigBuilder.js +130 -0
  37. package/dist/esm/lib/ForecastLineChartConfigBuilder.js.map +1 -0
  38. package/dist/esm/lib/MockSourcePayloads.js +20 -0
  39. package/dist/esm/lib/MockSourcePayloads.js.map +1 -0
  40. package/dist/esm/lib/SourcePayloads.js +41 -0
  41. package/dist/esm/lib/SourcePayloads.js.map +1 -0
  42. package/dist/esm/lib/index.js +4 -0
  43. package/dist/esm/lib/index.js.map +1 -0
  44. package/dist/types/Plugin.d.ts +3 -0
  45. package/dist/types/Plugin.d.ts.map +1 -0
  46. package/dist/types/components/DetailsBox.d.ts +9 -0
  47. package/dist/types/components/DetailsBox.d.ts.map +1 -0
  48. package/dist/types/components/index.d.ts +2 -0
  49. package/dist/types/components/index.d.ts.map +1 -0
  50. package/dist/types/index.d.ts +3 -0
  51. package/dist/types/index.d.ts.map +1 -0
  52. package/dist/types/lib/DataLineStyles.d.ts +5 -0
  53. package/dist/types/lib/DataLineStyles.d.ts.map +1 -0
  54. package/dist/types/lib/DataPointStyles.d.ts +8 -0
  55. package/dist/types/lib/DataPointStyles.d.ts.map +1 -0
  56. package/dist/types/lib/ForecastLineChartConfigBuilder.d.ts +47 -0
  57. package/dist/types/lib/ForecastLineChartConfigBuilder.d.ts.map +1 -0
  58. package/dist/types/lib/MockSourcePayloads.d.ts +18 -0
  59. package/dist/types/lib/MockSourcePayloads.d.ts.map +1 -0
  60. package/dist/types/lib/SourcePayloads.d.ts +30 -0
  61. package/dist/types/lib/SourcePayloads.d.ts.map +1 -0
  62. package/dist/types/lib/index.d.ts +4 -0
  63. package/dist/types/lib/index.d.ts.map +1 -0
  64. package/package.json +82 -0
  65. package/src/Plugin.ts +15 -0
  66. package/src/components/DetailsBox.stories.tsx +78 -0
  67. package/src/components/DetailsBox.tsx +55 -0
  68. package/src/components/index.ts +1 -0
  69. package/src/index.ts +2 -0
  70. package/src/lib/DataLineStyles.ts +6 -0
  71. package/src/lib/DataPointStyles.ts +7 -0
  72. package/src/lib/ForecastLineChartConfigBuilder.ts +169 -0
  73. package/src/lib/MockSourcePayloads.ts +19 -0
  74. package/src/lib/SourcePayloads.ts +47 -0
  75. 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,2 @@
1
+ export * from './components'
2
+ export * from './Plugin'
@@ -0,0 +1,6 @@
1
+ import { alpha } from '@mui/material'
2
+
3
+ export const DataLineStyles = (color?: string) => ({
4
+ backgroundColor: color ? alpha(color, 0.5) : undefined,
5
+ borderColor: color,
6
+ })
@@ -0,0 +1,7 @@
1
+ export const DataPointStyles = (pointHoverBackgroundColor?: string) => ({
2
+ pointHitRadius: 20,
3
+ pointHoverBackgroundColor,
4
+ pointHoverRadius: 10,
5
+ pointRadius: 5,
6
+ pointStyle: 'circle',
7
+ })
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ export * from './ForecastLineChartConfigBuilder'
2
+ export * from './MockSourcePayloads'
3
+ export * from './SourcePayloads'