ezmetrics 2.0.1 → 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +41 -0
- data/LICENSE +21 -674
- data/README.md +68 -143
- data/_config.yml +1 -0
- data/ezmetrics.gemspec +21 -0
- data/lib/ezmetrics.rb +7 -281
- data/lib/ezmetrics/benchmark.rb +2 -2
- data/lib/ezmetrics/dashboard/app/assets/config/dashboard_manifest.js +2 -0
- data/lib/ezmetrics/dashboard/app/assets/images/dashboard/.keep +0 -0
- data/lib/ezmetrics/dashboard/app/assets/javascripts/dashboard/application.js +15 -0
- data/lib/ezmetrics/dashboard/app/assets/javascripts/dashboard/main.js +1 -0
- data/lib/ezmetrics/dashboard/app/assets/stylesheets/dashboard/application.css +16 -0
- data/lib/ezmetrics/dashboard/app/assets/stylesheets/dashboard/main.css +6 -0
- data/lib/ezmetrics/dashboard/app/controllers/dashboard/application_controller.rb +5 -0
- data/lib/ezmetrics/dashboard/app/controllers/dashboard/metrics_controller.rb +41 -0
- data/lib/ezmetrics/dashboard/app/views/dashboard/metrics/index.html.erb +3 -0
- data/lib/ezmetrics/dashboard/app/views/layouts/dashboard/application.html.erb +12 -0
- data/lib/ezmetrics/dashboard/bin/rails +14 -0
- data/lib/ezmetrics/dashboard/config/routes.rb +5 -0
- data/lib/ezmetrics/dashboard/lib/dashboard.rb +4 -0
- data/lib/ezmetrics/dashboard/lib/dashboard/ezmetrics.rb +6 -0
- data/lib/ezmetrics/dashboard/lib/dashboard/version.rb +3 -0
- data/lib/ezmetrics/dashboard/react-dashboard/.gitignore +23 -0
- data/lib/ezmetrics/dashboard/react-dashboard/.rescriptsrc.js +9 -0
- data/lib/ezmetrics/dashboard/react-dashboard/README.md +0 -0
- data/lib/ezmetrics/dashboard/react-dashboard/package-lock.json +15472 -0
- data/lib/ezmetrics/dashboard/react-dashboard/package.json +49 -0
- data/lib/ezmetrics/dashboard/react-dashboard/public/index.html +18 -0
- data/lib/ezmetrics/dashboard/react-dashboard/public/manifest.json +10 -0
- data/lib/ezmetrics/dashboard/react-dashboard/public/robots.txt +2 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/App.tsx +216 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/Constants.tsx +74 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/Graph.tsx +71 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/Metric.tsx +21 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/MetricsBlock.tsx +22 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/Nav.tsx +105 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/RequestsBlock.tsx +78 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/index.css +82 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/index.tsx +9 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/react-app-env.d.ts +1 -0
- data/lib/ezmetrics/dashboard/react-dashboard/src/setupTests.ts +5 -0
- data/lib/ezmetrics/dashboard/react-dashboard/tsconfig.json +25 -0
- data/lib/ezmetrics/dashboard/react-dashboard/yarn.lock +11651 -0
- data/lib/ezmetrics/storage.rb +289 -0
- data/lib/ezmetrics/version.rb +3 -0
- data/lib/generators/ezmetrics/initializer_generator.rb +39 -0
- data/scripts/compile_assets +20 -0
- metadata +51 -3
@@ -0,0 +1,49 @@
|
|
1
|
+
{
|
2
|
+
"name": "react-dashboard",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"private": true,
|
5
|
+
"dependencies": {
|
6
|
+
"@material-ui/core": "^4.9.0",
|
7
|
+
"@material-ui/lab": "^4.0.0-alpha.40",
|
8
|
+
"@nivo/line": "^0.61.1",
|
9
|
+
"@testing-library/jest-dom": "^4.2.4",
|
10
|
+
"@testing-library/react": "^9.3.2",
|
11
|
+
"@testing-library/user-event": "^7.1.2",
|
12
|
+
"@types/jest": "^24.0.0",
|
13
|
+
"@types/node": "^12.0.0",
|
14
|
+
"@types/react": "^16.9.0",
|
15
|
+
"@types/react-dom": "^16.9.0",
|
16
|
+
"bootstrap": "^4.4.1",
|
17
|
+
"nivo": "^0.31.0",
|
18
|
+
"react": "^16.12.0",
|
19
|
+
"react-dom": "^16.12.0",
|
20
|
+
"react-scripts": "3.3.0",
|
21
|
+
"store": "^2.0.12",
|
22
|
+
"typescript": "~3.7.2"
|
23
|
+
},
|
24
|
+
"scripts": {
|
25
|
+
"start": "react-scripts start",
|
26
|
+
"build": "react-scripts build",
|
27
|
+
"build:clean": "rescripts build",
|
28
|
+
"test": "react-scripts test",
|
29
|
+
"eject": "react-scripts eject"
|
30
|
+
},
|
31
|
+
"eslintConfig": {
|
32
|
+
"extends": "react-app"
|
33
|
+
},
|
34
|
+
"browserslist": {
|
35
|
+
"production": [
|
36
|
+
">0.2%",
|
37
|
+
"not dead",
|
38
|
+
"not op_mini all"
|
39
|
+
],
|
40
|
+
"development": [
|
41
|
+
"last 1 chrome version",
|
42
|
+
"last 1 firefox version",
|
43
|
+
"last 1 safari version"
|
44
|
+
]
|
45
|
+
},
|
46
|
+
"devDependencies": {
|
47
|
+
"@rescripts/cli": "^0.0.13"
|
48
|
+
}
|
49
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8" />
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
6
|
+
<meta name="theme-color" content="#000000" />
|
7
|
+
<meta
|
8
|
+
name="description"
|
9
|
+
content="Ezmetrics | Dashboard"
|
10
|
+
/>
|
11
|
+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
12
|
+
<title>Ezmetrics | Dashboard</title>
|
13
|
+
</head>
|
14
|
+
<body>
|
15
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
16
|
+
<div id="ezmetrics-dashboard"></div>
|
17
|
+
</body>
|
18
|
+
</html>
|
@@ -0,0 +1,216 @@
|
|
1
|
+
import React, { Component } from 'react';
|
2
|
+
|
3
|
+
import Nav from './Nav';
|
4
|
+
import Graph from './Graph';
|
5
|
+
import RequestsBlock from './RequestsBlock'
|
6
|
+
import MetricsBlock from './MetricsBlock';
|
7
|
+
import {allMetricsList, allRefreshIntervals, allTimeframes, defaultOverviewMetrics, defaultGraphMetrics} from './Constants';
|
8
|
+
|
9
|
+
interface AppProps {}
|
10
|
+
|
11
|
+
export interface AppState {
|
12
|
+
fullscreen: Boolean;
|
13
|
+
showNav: Boolean;
|
14
|
+
metrics: any;
|
15
|
+
frequency: number;
|
16
|
+
timeframe: number;
|
17
|
+
partition: String;
|
18
|
+
metricsUrl: String;
|
19
|
+
graphMetricsList: any[];
|
20
|
+
overviewMetricsList: any[];
|
21
|
+
timeoutFunction: any;
|
22
|
+
error: String;
|
23
|
+
}
|
24
|
+
|
25
|
+
export function metricValueToLabel(value: String): String | undefined {
|
26
|
+
const foundMetric = allMetricsList.find(metricHash => metricHash.value === value)
|
27
|
+
if (foundMetric) {
|
28
|
+
return foundMetric.label
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
export function metricLabelToValue(label: String): String | undefined {
|
33
|
+
const foundMetric = allMetricsList.find(metricHash => metricHash.label === label)
|
34
|
+
if (foundMetric) {
|
35
|
+
return foundMetric.value
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
var store = require('store');
|
41
|
+
|
42
|
+
export default class App extends Component<AppProps, AppState> {
|
43
|
+
|
44
|
+
constructor(props: AppProps) {
|
45
|
+
super(props);
|
46
|
+
this.state = {
|
47
|
+
error: "",
|
48
|
+
showNav: false,
|
49
|
+
metrics: { simple: {}, partitioned: {} },
|
50
|
+
timeoutFunction: setTimeout(() => this.fetchByTimeout(), allRefreshIntervals[0].value),
|
51
|
+
fullscreen: store.get("fullscreen"),
|
52
|
+
metricsUrl: store.get("metricsUrl") || "/dashboard/metrics/aggregate",
|
53
|
+
frequency: store.get("frequency") || allRefreshIntervals[0].value,
|
54
|
+
timeframe: store.get("timeframe") || allTimeframes[3].value,
|
55
|
+
partition: store.get("partition") || "minute",
|
56
|
+
overviewMetricsList: store.get("overviewMetricsList") || defaultOverviewMetrics,
|
57
|
+
graphMetricsList: store.get("graphMetricsList") || defaultGraphMetrics,
|
58
|
+
};
|
59
|
+
|
60
|
+
this.fetchData = this.fetchData.bind(this)
|
61
|
+
this.changeFrequency = this.changeFrequency.bind(this)
|
62
|
+
this.changeTimeframe = this.changeTimeframe.bind(this)
|
63
|
+
this.changeMetricsUrl = this.changeMetricsUrl.bind(this)
|
64
|
+
this.toggleFullscreen = this.toggleFullscreen.bind(this)
|
65
|
+
this.toggleNav = this.toggleNav.bind(this)
|
66
|
+
this.changeOverviewMetricsList = this.changeOverviewMetricsList.bind(this)
|
67
|
+
this.changeGraphMetricsList = this.changeGraphMetricsList.bind(this)
|
68
|
+
}
|
69
|
+
|
70
|
+
componentWillUnmount(){
|
71
|
+
this.clearTimeoutFunction()
|
72
|
+
}
|
73
|
+
|
74
|
+
render() {
|
75
|
+
let metricsBlockData = Object.assign({}, this.state.metrics.simple);
|
76
|
+
delete metricsBlockData.requests;
|
77
|
+
|
78
|
+
const metricsBlocks = Object.entries(metricsBlockData).map(
|
79
|
+
([name, value]) => {
|
80
|
+
return <MetricsBlock key={name} name={name} metrics={value}/>
|
81
|
+
}
|
82
|
+
)
|
83
|
+
|
84
|
+
let graphData = null;
|
85
|
+
|
86
|
+
let partitionedMetricsSize = Object.keys(this.state.metrics.partitioned).length
|
87
|
+
|
88
|
+
if (partitionedMetricsSize > 0) {
|
89
|
+
|
90
|
+
let lines = this.intersection(Object.keys(this.state.metrics.partitioned[0]), this.state.graphMetricsList.map(m => m.value))
|
91
|
+
|
92
|
+
graphData = lines.map((line: any) => {
|
93
|
+
return {
|
94
|
+
id: metricValueToLabel(line),
|
95
|
+
data: this.state.metrics.partitioned.map((hash: any) => {
|
96
|
+
return {
|
97
|
+
x: this.formatTimestamp(hash.timestamp),
|
98
|
+
y: hash[line]
|
99
|
+
}
|
100
|
+
}
|
101
|
+
).filter((hash: any) => typeof(hash.y) === "number")
|
102
|
+
}
|
103
|
+
});
|
104
|
+
}
|
105
|
+
|
106
|
+
return (
|
107
|
+
<div className="App">
|
108
|
+
<div className={this.state.fullscreen ? "container-fluid" : "container"}>
|
109
|
+
<div className={this.state.error.length ? "alert alert-danger error-message" : "hidden"}>{this.state.error}</div>
|
110
|
+
<nav className="navbar navbar-light bg-light rounded">
|
111
|
+
<span className="navbar-brand">Ezmetrics | Dashboard</span>
|
112
|
+
<div className="col"><button className="btn btn-outline-secondary fullscreen" onClick={this.toggleFullscreen}>↹</button></div>
|
113
|
+
<button onClick={this.toggleNav} className="btn btn-outline-secondary" type="submit">Settings</button>
|
114
|
+
<Nav
|
115
|
+
changeFrequency={this.changeFrequency}
|
116
|
+
changeTimeframe={this.changeTimeframe}
|
117
|
+
changeMetricsUrl={this.changeMetricsUrl}
|
118
|
+
toggleFullscreen={this.toggleFullscreen}
|
119
|
+
changeGraphMetricsList={this.changeGraphMetricsList}
|
120
|
+
changeOverviewMetricsList={this.changeOverviewMetricsList}
|
121
|
+
state={this.state}
|
122
|
+
/>
|
123
|
+
</nav>
|
124
|
+
{this.state.metrics.simple.requests && <RequestsBlock data={this.state.metrics.simple.requests} state={this.state}/>}
|
125
|
+
<div className="row">{metricsBlocks}</div>
|
126
|
+
<div className="row">
|
127
|
+
<div className="col">
|
128
|
+
{graphData && graphData.length !== 0 && <Graph data={graphData} state={this.state}/> }
|
129
|
+
</div>
|
130
|
+
</div>
|
131
|
+
</div>
|
132
|
+
</div>
|
133
|
+
);
|
134
|
+
}
|
135
|
+
|
136
|
+
formatTimestamp(timestamp: number): string {
|
137
|
+
return new Date(timestamp * 1000).toLocaleTimeString("en-GB")
|
138
|
+
}
|
139
|
+
|
140
|
+
fetchByTimeout() {
|
141
|
+
this.clearTimeoutFunction()
|
142
|
+
this.fetchData()
|
143
|
+
this.setState({timeoutFunction: setTimeout(() => this.fetchByTimeout(), this.state.frequency)});
|
144
|
+
}
|
145
|
+
|
146
|
+
fetchData() {
|
147
|
+
const overviewMetrics = this.state.overviewMetricsList.map(m => m.value).join(",")
|
148
|
+
const graphMetrics = this.state.graphMetricsList.map(m => m.value).join(",")
|
149
|
+
|
150
|
+
fetch(`${this.state.metricsUrl}?interval=${this.state.timeframe}&partition=${this.state.partition}&overview_metrics=${overviewMetrics}&graph_metrics=${graphMetrics}`)
|
151
|
+
.then(res => res.json())
|
152
|
+
.then((data) => this.setState({metrics: data, error: ""}))
|
153
|
+
.catch(error => this.setState({error: "Fetching error: " + error.message + "."}))
|
154
|
+
}
|
155
|
+
|
156
|
+
changeFrequency(frequencyValue: number) {
|
157
|
+
store.set("frequency", frequencyValue);
|
158
|
+
this.setState({frequency: frequencyValue});
|
159
|
+
this.fetchByTimeout()
|
160
|
+
}
|
161
|
+
|
162
|
+
changeTimeframe(timeframeValue: number) {
|
163
|
+
var partition = "minute";
|
164
|
+
|
165
|
+
if (timeframeValue < 121) {
|
166
|
+
partition = "second";
|
167
|
+
} else if (timeframeValue < 3601) {
|
168
|
+
partition = "minute";
|
169
|
+
} else {
|
170
|
+
partition = "hour";
|
171
|
+
}
|
172
|
+
|
173
|
+
store.set("timeframe", timeframeValue);
|
174
|
+
store.set("partition", partition);
|
175
|
+
|
176
|
+
this.setState({timeframe: timeframeValue, partition: partition});
|
177
|
+
this.fetchByTimeout()
|
178
|
+
}
|
179
|
+
|
180
|
+
changeOverviewMetricsList(metricsList: String[]) {
|
181
|
+
store.set("overviewMetricsList", metricsList)
|
182
|
+
this.setState({overviewMetricsList: metricsList});
|
183
|
+
}
|
184
|
+
|
185
|
+
changeGraphMetricsList(metricsList: String[]) {
|
186
|
+
store.set("graphMetricsList", metricsList)
|
187
|
+
this.setState({graphMetricsList: metricsList});
|
188
|
+
}
|
189
|
+
|
190
|
+
changeMetricsUrl(metricsUrl: String) {
|
191
|
+
store.set("metricsUrl", metricsUrl);
|
192
|
+
this.setState({metricsUrl: metricsUrl});
|
193
|
+
}
|
194
|
+
|
195
|
+
toggleFullscreen() {
|
196
|
+
store.set("fullscreen", !this.state.fullscreen)
|
197
|
+
|
198
|
+
this.setState(prevState => ({
|
199
|
+
fullscreen: !prevState.fullscreen
|
200
|
+
}));
|
201
|
+
}
|
202
|
+
|
203
|
+
toggleNav() {
|
204
|
+
this.setState(prevState => ({
|
205
|
+
showNav: !prevState.showNav
|
206
|
+
}));
|
207
|
+
}
|
208
|
+
|
209
|
+
clearTimeoutFunction(){
|
210
|
+
window.clearTimeout(this.state.timeoutFunction);
|
211
|
+
}
|
212
|
+
|
213
|
+
intersection(a1: String[], a2: String[]) {
|
214
|
+
return a1.filter(x => a2.indexOf(x) > -1)
|
215
|
+
}
|
216
|
+
}
|
@@ -0,0 +1,74 @@
|
|
1
|
+
export const allMetricsList = [
|
2
|
+
{ value: "requests_all", label: "requests: all" },
|
3
|
+
{ value: "requests_2xx", label: "requests: 2xx" },
|
4
|
+
{ value: "requests_3xx", label: "requests: 3xx" },
|
5
|
+
{ value: "requests_4xx", label: "requests: 4xx" },
|
6
|
+
{ value: "requests_5xx", label: "requests: 5xx" },
|
7
|
+
|
8
|
+
{ value: "duration_avg", label: "duration: avg" },
|
9
|
+
{ value: "duration_max", label: "duration: max" },
|
10
|
+
{ value: "duration_percentile_90", label: "duration: 90%" },
|
11
|
+
{ value: "duration_percentile_95", label: "duration: 95%" },
|
12
|
+
{ value: "duration_percentile_99", label: "duration: 99%" },
|
13
|
+
|
14
|
+
{ value: "views_avg", label: "views: avg" },
|
15
|
+
{ value: "views_max", label: "views: max" },
|
16
|
+
{ value: "views_percentile_90", label: "views: 90%" },
|
17
|
+
{ value: "views_percentile_95", label: "views: 95%" },
|
18
|
+
{ value: "views_percentile_99", label: "views: 99%" },
|
19
|
+
|
20
|
+
{ value: "db_avg", label: "db: avg" },
|
21
|
+
{ value: "db_max", label: "db: max" },
|
22
|
+
{ value: "db_percentile_90", label: "db: 90%" },
|
23
|
+
{ value: "db_percentile_95", label: "db: 95%" },
|
24
|
+
{ value: "db_percentile_99", label: "db: 99%" },
|
25
|
+
|
26
|
+
{ value: "queries_avg", label: "queries: avg" },
|
27
|
+
{ value: "queries_max", label: "queries: max" },
|
28
|
+
{ value: "queries_percentile_90", label: "queries: 90%" },
|
29
|
+
{ value: "queries_percentile_95", label: "queries: 95%" },
|
30
|
+
{ value: "queries_percentile_99", label: "queries: 99%" }
|
31
|
+
]
|
32
|
+
|
33
|
+
export const allRefreshIntervals = [
|
34
|
+
{ label: "1 second", value: 1000 },
|
35
|
+
{ label: "5 seconds", value: 5000 },
|
36
|
+
{ label: "10 seconds", value: 10000 },
|
37
|
+
{ label: "30 seconds", value: 30000 },
|
38
|
+
{ label: "1 minute", value: 60000 }
|
39
|
+
]
|
40
|
+
|
41
|
+
export const allTimeframes = [
|
42
|
+
{ label: "1 minute", value: 60 },
|
43
|
+
{ label: "2 minutes", value: 120 },
|
44
|
+
{ label: "30 minutes", value: 1800 },
|
45
|
+
{ label: "1 hour", value: 3600 },
|
46
|
+
{ label: "3 hours", value: 10800 },
|
47
|
+
{ label: "6 hours", value: 21600 },
|
48
|
+
{ label: "12 hours", value: 43200 },
|
49
|
+
{ label: "24 hours", value: 86400 }
|
50
|
+
]
|
51
|
+
|
52
|
+
export const defaultOverviewMetrics = [
|
53
|
+
{ value: "requests_2xx", label: "requests: 2xx" },
|
54
|
+
{ value: "requests_3xx", label: "requests: 3xx" },
|
55
|
+
{ value: "requests_4xx", label: "requests: 4xx" },
|
56
|
+
{ value: "requests_5xx", label: "requests: 5xx" },
|
57
|
+
{ value: "duration_avg", label: "duration: avg" },
|
58
|
+
{ value: "duration_max", label: "duration: max" },
|
59
|
+
{ value: "views_avg", label: "views: avg" },
|
60
|
+
{ value: "views_max", label: "views: max" },
|
61
|
+
{ value: "db_avg", label: "db: avg" },
|
62
|
+
{ value: "db_max", label: "db: max" },
|
63
|
+
{ value: "queries_avg", label: "queries: avg" },
|
64
|
+
{ value: "queries_max", label: "queries: max" }
|
65
|
+
]
|
66
|
+
|
67
|
+
export const defaultGraphMetrics = [
|
68
|
+
{ value: "requests_all", label: "requests: all" },
|
69
|
+
{ value: "requests_5xx", label: "requests: 5xx" },
|
70
|
+
{ value: "duration_avg", label: "duration: avg" },
|
71
|
+
{ value: "duration_max", label: "duration: max" },
|
72
|
+
{ value: "db_avg", label: "db: avg" },
|
73
|
+
{ value: "db_max", label: "db: max" }
|
74
|
+
]
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
|
3
|
+
import { ResponsiveLine } from '@nivo/line';
|
4
|
+
|
5
|
+
export default function Graph(props: any): JSX.Element {
|
6
|
+
return (
|
7
|
+
<div className="nivo">
|
8
|
+
<ResponsiveLine
|
9
|
+
data={props.data}
|
10
|
+
margin={{ top: 20, right: 120, bottom: 64, left: 70 }}
|
11
|
+
xScale={{ type: 'point' }}
|
12
|
+
yScale={{ type: 'linear', min: 'auto', max: 'auto', stacked: true, reverse: false }}
|
13
|
+
axisTop={null}
|
14
|
+
axisRight={null}
|
15
|
+
axisBottom={{
|
16
|
+
orient: 'bottom',
|
17
|
+
tickSize: 5,
|
18
|
+
tickPadding: 5,
|
19
|
+
tickRotation: -60,
|
20
|
+
legend: 'time',
|
21
|
+
legendOffset: 58,
|
22
|
+
legendPosition: 'middle'
|
23
|
+
}}
|
24
|
+
axisLeft={{
|
25
|
+
orient: 'left',
|
26
|
+
tickSize: 2,
|
27
|
+
tickPadding: 5,
|
28
|
+
tickRotation: 0,
|
29
|
+
legend: 'value',
|
30
|
+
legendOffset: -50,
|
31
|
+
legendPosition: 'middle',
|
32
|
+
}}
|
33
|
+
colors={{ scheme: 'nivo' }}
|
34
|
+
pointSize={5}
|
35
|
+
pointColor={{ theme: 'background' }}
|
36
|
+
pointBorderWidth={1}
|
37
|
+
pointBorderColor={{ from: 'serieColor' }}
|
38
|
+
pointLabel="y"
|
39
|
+
pointLabelYOffset={-12}
|
40
|
+
animate={props.state.timeframe === 60}
|
41
|
+
useMesh={true}
|
42
|
+
legends={[
|
43
|
+
{
|
44
|
+
anchor: 'bottom-right',
|
45
|
+
direction: 'column',
|
46
|
+
justify: false,
|
47
|
+
translateX: 100,
|
48
|
+
translateY: 0,
|
49
|
+
itemsSpacing: 0,
|
50
|
+
itemDirection: 'left-to-right',
|
51
|
+
itemWidth: 80,
|
52
|
+
itemHeight: 20,
|
53
|
+
itemOpacity: 0.75,
|
54
|
+
symbolSize: 10,
|
55
|
+
symbolShape: 'circle',
|
56
|
+
symbolBorderColor: 'rgba(0, 0, 0, .5)',
|
57
|
+
effects: [
|
58
|
+
{
|
59
|
+
on: 'hover',
|
60
|
+
style: {
|
61
|
+
itemBackground: 'rgba(0, 0, 0, .03)',
|
62
|
+
itemOpacity: 1
|
63
|
+
}
|
64
|
+
}
|
65
|
+
]
|
66
|
+
}
|
67
|
+
]}
|
68
|
+
/>
|
69
|
+
</div>
|
70
|
+
);
|
71
|
+
}
|