Customize visualizations with configuration

This guide builds off of the previous Custom visualizations and the New Relic One SDK guide. If you haven't followed that guide, please start there as this guide assumes you have the code you built there to get started.

Following the previous guide, your current visualization should be able to switch between two charts types. Your current implementation takes up some space in the visualization but offers your users the choice to switch between two chart types at any point in the visualization lifecycle. What if you only need to be able to select an option once, when adding it to the dashboard? In that case, you can use the configuration key in your visualization's nr1.json file. In the following steps, you'll replace the SegmentedControl component with an option in the configuration array.

Before you begin

To get started, make sure you follow the Custom visualizations and the New Relic One SDK guide. You should have that code as your starting point for this guide.

Replace the SegmentedControl with configuration

Step 1 of 6

In you visualization's root folder, open the nr1.json file. Add an enum configuration object for selectedChart:

1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
Radar,
5
RadarChart,
6
PolarGrid,
7
PolarAngleAxis,
8
PolarRadiusAxis,
9
Treemap,
10
} from 'recharts';
11
import {
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
AutoSizer,
20
} from 'nr1';
21
const CHART_TYPES = {
22
Radar: 'radar',
23
Treemap: 'treemap',
24
};
25
export default class YourAwesomeVisualization extends React.Component {
26
// Custom props you wish to be configurable in the UI must also be defined in
27
// the nr1.json file for the visualization. See docs for more details.
28
static propTypes = {
29
/**
30
* A fill color to override the default fill color. This is an example of
31
* a custom chart configuration.
32
*/
33
fill: PropTypes.string,
34
/**
35
* A stroke color to override the default stroke color. This is an example of
36
* a custom chart configuration.
37
*/
38
stroke: PropTypes.string,
39
/**
40
* An array of objects consisting of a nrql `query` and `accountId`.
41
* This should be a standard prop for any NRQL based visualizations.
42
*/
43
nrqlQueries: PropTypes.arrayOf(
44
PropTypes.shape({
45
accountId: PropTypes.number,
46
query: PropTypes.string,
47
})
48
),
49
/**
50
* A chart type configuration to switch between "Radar" and "Treemap" chart types
51
*/
52
selectedChart: PropTypes.string,
53
};
54
state = {
55
selectedChart: CHART_TYPES.Radar,
56
};
57
/**
58
* Restructure the data for a non-time-series, facet-based NRQL query into a
59
* form accepted by the Recharts library's RadarChart.
60
* (https://recharts.org/api/RadarChart).
61
*/
62
transformData = (rawData) => {
63
return rawData.map((entry) => ({
64
name: entry.metadata.name,
65
// Only grabbing the first data value because this is not time-series data.
66
value: entry.data[0].y,
67
}));
68
};
69
/**
70
* Format the given axis tick's numeric value into a string for display.
71
*/
72
formatTick = (value) => {
73
return value.toLocaleString();
74
};
75
76
updateSelectedChart = (evt, value) => {
77
this.setState({ selectedChart: value });
78
};
79
80
render() {
81
const { nrqlQueries, stroke, fill } = this.props;
82
const { selectedChart } = this.state;
83
const nrqlQueryPropsAvailable =
84
nrqlQueries &&
85
nrqlQueries[0] &&
86
nrqlQueries[0].accountId &&
87
nrqlQueries[0].query;
88
if (!nrqlQueryPropsAvailable) {
89
return <EmptyState />;
90
}
91
return (
92
<AutoSizer>
93
{({ width, height }) => (
94
<NrqlQuery
95
query={nrqlQueries[0].query}
96
accountId={parseInt(nrqlQueries[0].accountId)}
97
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
98
>
99
{({ data, loading, error }) => {
100
if (loading) {
101
return <Spinner />;
102
}
103
if (error) {
104
return <ErrorState />;
105
}
106
const transformedData = this.transformData(data);
107
return (
108
<React.Fragment>
109
<SegmentedControl onChange={this.updateSelectedChart}>
110
<SegmentedControlItem
111
value={CHART_TYPES.Radar}
112
label="Radar chart"
113
/>
114
<SegmentedControlItem
115
value={CHART_TYPES.Treemap}
116
label="Treemap chart"
117
/>
118
</SegmentedControl>
119
{selectedChart === CHART_TYPES.Radar ? (
120
<RadarChart
121
width={width}
122
height={height}
123
data={transformedData}
124
>
125
<PolarGrid />
126
<PolarAngleAxis dataKey="name" />
127
<PolarRadiusAxis tickFormatter={this.formatTick} />
128
<Radar
129
dataKey="value"
130
stroke={stroke || '#51C9B7'}
131
fill={fill || '#51C9B7'}
132
fillOpacity={0.6}
133
/>
134
</RadarChart>
135
) : (
136
<Treemap
137
width={width}
138
height={height}
139
data={transformedData}
140
dataKey="value"
141
ratio={4 / 3}
142
stroke="#fff"
143
fill="#8884d8"
144
/>
145
)}
146
</React.Fragment>
147
);
148
}}
149
</NrqlQuery>
150
)}
151
</AutoSizer>
152
);
153
}
154
}
155
156
const EmptyState = () => (
157
<Card className="EmptyState">
158
<CardBody className="EmptyState-cardBody">
159
<HeadingText
160
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
161
type={HeadingText.TYPE.HEADING_3}
162
>
163
Please provide at least one NRQL query & account ID pair
164
</HeadingText>
165
<HeadingText
166
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
167
type={HeadingText.TYPE.HEADING_4}
168
>
169
An example NRQL query you can try is:
170
</HeadingText>
171
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
172
</CardBody>
173
</Card>
174
);
175
const ErrorState = () => (
176
<Card className="ErrorState">
177
<CardBody className="ErrorState-cardBody">
178
<HeadingText
179
className="ErrorState-headingText"
180
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
181
type={HeadingText.TYPE.HEADING_3}
182
>
183
Oops! Something went wrong.
184
</HeadingText>
185
</CardBody>
186
</Card>
187
);
visualizations/your-visualization/index.js
1
{
2
"schemaType": "VISUALIZATION",
3
"id": "your-awesome-visualization",
4
"displayName": "Your Awesome Visualization",
5
"description": "",
6
"configuration": [
7
{
8
"name": "selectedChart",
9
"title": "Select chart",
10
"description": "Select which chart to display",
11
"type": "enum",
12
"items": [
13
{
14
"title": "Radar",
15
"value": "radar"
16
},
17
{
18
"title": "Treemap",
19
"value": "treemap"
20
}
21
]
22
},
23
{
24
"name": "nrqlQueries",
25
"title": "NRQL Queries",
26
"type": "collection",
27
"items": [
28
{
29
"name": "accountId",
30
"title": "Account ID",
31
"description": "Account ID to be associated with the query",
32
"type": "number"
33
},
34
{
35
"name": "query",
36
"title": "Query",
37
"description": "NRQL query for visualization",
38
"type": "nrql"
39
}
40
]
41
},
42
{
43
"name": "fill",
44
"title": "Fill color",
45
"description": "A fill color to override the default fill color",
46
"type": "string"
47
},
48
{
49
"name": "stroke",
50
"title": "Stroke color",
51
"description": "A stroke color to override the default stroke color",
52
"type": "string"
53
}
54
]
55
}
visualizations/your-visualization/nr1.json

This should look familiar to the component state options you added in the previous guide.

Step 2 of 6

In your visualization's root folder, open index.js. You'll be working in this file for the rest of the guide:

1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
Radar,
5
RadarChart,
6
PolarGrid,
7
PolarAngleAxis,
8
PolarRadiusAxis,
9
Treemap,
10
} from 'recharts';
11
import {
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
AutoSizer,
20
} from 'nr1';
21
const CHART_TYPES = {
22
Radar: 'radar',
23
Treemap: 'treemap',
24
};
25
export default class YourAwesomeVisualization extends React.Component {
26
// Custom props you wish to be configurable in the UI must also be defined in
27
// the nr1.json file for the visualization. See docs for more details.
28
static propTypes = {
29
/**
30
* A fill color to override the default fill color. This is an example of
31
* a custom chart configuration.
32
*/
33
fill: PropTypes.string,
34
/**
35
* A stroke color to override the default stroke color. This is an example of
36
* a custom chart configuration.
37
*/
38
stroke: PropTypes.string,
39
/**
40
* An array of objects consisting of a nrql `query` and `accountId`.
41
* This should be a standard prop for any NRQL based visualizations.
42
*/
43
nrqlQueries: PropTypes.arrayOf(
44
PropTypes.shape({
45
accountId: PropTypes.number,
46
query: PropTypes.string,
47
})
48
),
49
/**
50
* A chart type configuration to switch between "Radar" and "Treemap" chart types
51
*/
52
selectedChart: PropTypes.string,
53
};
54
state = {
55
selectedChart: CHART_TYPES.Radar,
56
};
57
/**
58
* Restructure the data for a non-time-series, facet-based NRQL query into a
59
* form accepted by the Recharts library's RadarChart.
60
* (https://recharts.org/api/RadarChart).
61
*/
62
transformData = (rawData) => {
63
return rawData.map((entry) => ({
64
name: entry.metadata.name,
65
// Only grabbing the first data value because this is not time-series data.
66
value: entry.data[0].y,
67
}));
68
};
69
/**
70
* Format the given axis tick's numeric value into a string for display.
71
*/
72
formatTick = (value) => {
73
return value.toLocaleString();
74
};
75
76
updateSelectedChart = (evt, value) => {
77
this.setState({ selectedChart: value });
78
};
79
80
render() {
81
const { nrqlQueries, stroke, fill } = this.props;
82
const { selectedChart } = this.state;
83
const nrqlQueryPropsAvailable =
84
nrqlQueries &&
85
nrqlQueries[0] &&
86
nrqlQueries[0].accountId &&
87
nrqlQueries[0].query;
88
if (!nrqlQueryPropsAvailable) {
89
return <EmptyState />;
90
}
91
return (
92
<AutoSizer>
93
{({ width, height }) => (
94
<NrqlQuery
95
query={nrqlQueries[0].query}
96
accountId={parseInt(nrqlQueries[0].accountId)}
97
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
98
>
99
{({ data, loading, error }) => {
100
if (loading) {
101
return <Spinner />;
102
}
103
if (error) {
104
return <ErrorState />;
105
}
106
const transformedData = this.transformData(data);
107
return (
108
<React.Fragment>
109
<SegmentedControl onChange={this.updateSelectedChart}>
110
<SegmentedControlItem
111
value={CHART_TYPES.Radar}
112
label="Radar chart"
113
/>
114
<SegmentedControlItem
115
value={CHART_TYPES.Treemap}
116
label="Treemap chart"
117
/>
118
</SegmentedControl>
119
{selectedChart === CHART_TYPES.Radar ? (
120
<RadarChart
121
width={width}
122
height={height}
123
data={transformedData}
124
>
125
<PolarGrid />
126
<PolarAngleAxis dataKey="name" />
127
<PolarRadiusAxis tickFormatter={this.formatTick} />
128
<Radar
129
dataKey="value"
130
stroke={stroke || '#51C9B7'}
131
fill={fill || '#51C9B7'}
132
fillOpacity={0.6}
133
/>
134
</RadarChart>
135
) : (
136
<Treemap
137
width={width}
138
height={height}
139
data={transformedData}
140
dataKey="value"
141
ratio={4 / 3}
142
stroke="#fff"
143
fill="#8884d8"
144
/>
145
)}
146
</React.Fragment>
147
);
148
}}
149
</NrqlQuery>
150
)}
151
</AutoSizer>
152
);
153
}
154
}
155
156
const EmptyState = () => (
157
<Card className="EmptyState">
158
<CardBody className="EmptyState-cardBody">
159
<HeadingText
160
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
161
type={HeadingText.TYPE.HEADING_3}
162
>
163
Please provide at least one NRQL query & account ID pair
164
</HeadingText>
165
<HeadingText
166
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
167
type={HeadingText.TYPE.HEADING_4}
168
>
169
An example NRQL query you can try is:
170
</HeadingText>
171
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
172
</CardBody>
173
</Card>
174
);
175
const ErrorState = () => (
176
<Card className="ErrorState">
177
<CardBody className="ErrorState-cardBody">
178
<HeadingText
179
className="ErrorState-headingText"
180
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
181
type={HeadingText.TYPE.HEADING_3}
182
>
183
Oops! Something went wrong.
184
</HeadingText>
185
</CardBody>
186
</Card>
187
);
visualizations/your-visualization/index.js
1
{
2
"schemaType": "VISUALIZATION",
3
"id": "your-awesome-visualization",
4
"displayName": "Your Awesome Visualization",
5
"description": "",
6
"configuration": [
7
{
8
"name": "selectedChart",
9
"title": "Select chart",
10
"description": "Select which chart to display",
11
"type": "enum",
12
"items": [
13
{
14
"title": "Radar",
15
"value": "radar"
16
},
17
{
18
"title": "Treemap",
19
"value": "treemap"
20
}
21
]
22
},
23
{
24
"name": "nrqlQueries",
25
"title": "NRQL Queries",
26
"type": "collection",
27
"items": [
28
{
29
"name": "accountId",
30
"title": "Account ID",
31
"description": "Account ID to be associated with the query",
32
"type": "number"
33
},
34
{
35
"name": "query",
36
"title": "Query",
37
"description": "NRQL query for visualization",
38
"type": "nrql"
39
}
40
]
41
},
42
{
43
"name": "fill",
44
"title": "Fill color",
45
"description": "A fill color to override the default fill color",
46
"type": "string"
47
},
48
{
49
"name": "stroke",
50
"title": "Stroke color",
51
"description": "A stroke color to override the default stroke color",
52
"type": "string"
53
}
54
]
55
}
visualizations/your-visualization/nr1.json
Step 3 of 6

In render(), add selectedChart to the prop descrtructuring and remove the state reference:

1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
Radar,
5
RadarChart,
6
PolarGrid,
7
PolarAngleAxis,
8
PolarRadiusAxis,
9
Treemap,
10
} from 'recharts';
11
import {
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
AutoSizer,
20
} from 'nr1';
21
const CHART_TYPES = {
22
Radar: 'radar',
23
Treemap: 'treemap',
24
};
25
export default class YourAwesomeVisualization extends React.Component {
26
// Custom props you wish to be configurable in the UI must also be defined in
27
// the nr1.json file for the visualization. See docs for more details.
28
static propTypes = {
29
/**
30
* A fill color to override the default fill color. This is an example of
31
* a custom chart configuration.
32
*/
33
fill: PropTypes.string,
34
/**
35
* A stroke color to override the default stroke color. This is an example of
36
* a custom chart configuration.
37
*/
38
stroke: PropTypes.string,
39
/**
40
* An array of objects consisting of a nrql `query` and `accountId`.
41
* This should be a standard prop for any NRQL based visualizations.
42
*/
43
nrqlQueries: PropTypes.arrayOf(
44
PropTypes.shape({
45
accountId: PropTypes.number,
46
query: PropTypes.string,
47
})
48
),
49
/**
50
* A chart type configuration to switch between "Radar" and "Treemap" chart types
51
*/
52
selectedChart: PropTypes.string,
53
};
54
state = {
55
selectedChart: CHART_TYPES.Radar,
56
};
57
/**
58
* Restructure the data for a non-time-series, facet-based NRQL query into a
59
* form accepted by the Recharts library's RadarChart.
60
* (https://recharts.org/api/RadarChart).
61
*/
62
transformData = (rawData) => {
63
return rawData.map((entry) => ({
64
name: entry.metadata.name,
65
// Only grabbing the first data value because this is not time-series data.
66
value: entry.data[0].y,
67
}));
68
};
69
/**
70
* Format the given axis tick's numeric value into a string for display.
71
*/
72
formatTick = (value) => {
73
return value.toLocaleString();
74
};
75
76
updateSelectedChart = (evt, value) => {
77
this.setState({ selectedChart: value });
78
};
79
80
render() {
81
const { nrqlQueries, stroke, fill, selectedChart } = this.props;
82
// const { selectedChart } = this.state;
83
const nrqlQueryPropsAvailable =
84
nrqlQueries &&
85
nrqlQueries[0] &&
86
nrqlQueries[0].accountId &&
87
nrqlQueries[0].query;
88
if (!nrqlQueryPropsAvailable) {
89
return <EmptyState />;
90
}
91
return (
92
<AutoSizer>
93
{({ width, height }) => (
94
<NrqlQuery
95
query={nrqlQueries[0].query}
96
accountId={parseInt(nrqlQueries[0].accountId)}
97
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
98
>
99
{({ data, loading, error }) => {
100
if (loading) {
101
return <Spinner />;
102
}
103
if (error) {
104
return <ErrorState />;
105
}
106
const transformedData = this.transformData(data);
107
return (
108
<React.Fragment>
109
<SegmentedControl onChange={this.updateSelectedChart}>
110
<SegmentedControlItem
111
value={CHART_TYPES.Radar}
112
label="Radar chart"
113
/>
114
<SegmentedControlItem
115
value={CHART_TYPES.Treemap}
116
label="Treemap chart"
117
/>
118
</SegmentedControl>
119
{selectedChart === CHART_TYPES.Radar ? (
120
<RadarChart
121
width={width}
122
height={height}
123
data={transformedData}
124
>
125
<PolarGrid />
126
<PolarAngleAxis dataKey="name" />
127
<PolarRadiusAxis tickFormatter={this.formatTick} />
128
<Radar
129
dataKey="value"
130
stroke={stroke || '#51C9B7'}
131
fill={fill || '#51C9B7'}
132
fillOpacity={0.6}
133
/>
134
</RadarChart>
135
) : (
136
<Treemap
137
width={width}
138
height={height}
139
data={transformedData}
140
dataKey="value"
141
ratio={4 / 3}
142
stroke="#fff"
143
fill="#8884d8"
144
/>
145
)}
146
</React.Fragment>
147
);
148
}}
149
</NrqlQuery>
150
)}
151
</AutoSizer>
152
);
153
}
154
}
155
156
const EmptyState = () => (
157
<Card className="EmptyState">
158
<CardBody className="EmptyState-cardBody">
159
<HeadingText
160
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
161
type={HeadingText.TYPE.HEADING_3}
162
>
163
Please provide at least one NRQL query & account ID pair
164
</HeadingText>
165
<HeadingText
166
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
167
type={HeadingText.TYPE.HEADING_4}
168
>
169
An example NRQL query you can try is:
170
</HeadingText>
171
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
172
</CardBody>
173
</Card>
174
);
175
const ErrorState = () => (
176
<Card className="ErrorState">
177
<CardBody className="ErrorState-cardBody">
178
<HeadingText
179
className="ErrorState-headingText"
180
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
181
type={HeadingText.TYPE.HEADING_3}
182
>
183
Oops! Something went wrong.
184
</HeadingText>
185
</CardBody>
186
</Card>
187
);
visualizations/your-visualization/index.js
1
{
2
"schemaType": "VISUALIZATION",
3
"id": "your-awesome-visualization",
4
"displayName": "Your Awesome Visualization",
5
"description": "",
6
"configuration": [
7
{
8
"name": "selectedChart",
9
"title": "Select chart",
10
"description": "Select which chart to display",
11
"type": "enum",
12
"items": [
13
{
14
"title": "Radar",
15
"value": "radar"
16
},
17
{
18
"title": "Treemap",
19
"value": "treemap"
20
}
21
]
22
},
23
{
24
"name": "nrqlQueries",
25
"title": "NRQL Queries",
26
"type": "collection",
27
"items": [
28
{
29
"name": "accountId",
30
"title": "Account ID",
31
"description": "Account ID to be associated with the query",
32
"type": "number"
33
},
34
{
35
"name": "query",
36
"title": "Query",
37
"description": "NRQL query for visualization",
38
"type": "nrql"
39
}
40
]
41
},
42
{
43
"name": "fill",
44
"title": "Fill color",
45
"description": "A fill color to override the default fill color",
46
"type": "string"
47
},
48
{
49
"name": "stroke",
50
"title": "Stroke color",
51
"description": "A stroke color to override the default stroke color",
52
"type": "string"
53
}
54
]
55
}
visualizations/your-visualization/nr1.json
Step 4 of 6

You no longer have a default chart defined at this point, but you still want the Radar chart to be the default. Add more to the ternary in the render to achieve the same default state as you had previously:

1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
Radar,
5
RadarChart,
6
PolarGrid,
7
PolarAngleAxis,
8
PolarRadiusAxis,
9
Treemap,
10
} from 'recharts';
11
import {
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
AutoSizer,
20
} from 'nr1';
21
const CHART_TYPES = {
22
Radar: 'radar',
23
Treemap: 'treemap',
24
};
25
export default class YourAwesomeVisualization extends React.Component {
26
// Custom props you wish to be configurable in the UI must also be defined in
27
// the nr1.json file for the visualization. See docs for more details.
28
static propTypes = {
29
/**
30
* A fill color to override the default fill color. This is an example of
31
* a custom chart configuration.
32
*/
33
fill: PropTypes.string,
34
/**
35
* A stroke color to override the default stroke color. This is an example of
36
* a custom chart configuration.
37
*/
38
stroke: PropTypes.string,
39
/**
40
* An array of objects consisting of a nrql `query` and `accountId`.
41
* This should be a standard prop for any NRQL based visualizations.
42
*/
43
nrqlQueries: PropTypes.arrayOf(
44
PropTypes.shape({
45
accountId: PropTypes.number,
46
query: PropTypes.string,
47
})
48
),
49
/**
50
* A chart type configuration to switch between "Radar" and "Treemap" chart types
51
*/
52
selectedChart: PropTypes.string,
53
};
54
state = {
55
selectedChart: CHART_TYPES.Radar,
56
};
57
/**
58
* Restructure the data for a non-time-series, facet-based NRQL query into a
59
* form accepted by the Recharts library's RadarChart.
60
* (https://recharts.org/api/RadarChart).
61
*/
62
transformData = (rawData) => {
63
return rawData.map((entry) => ({
64
name: entry.metadata.name,
65
// Only grabbing the first data value because this is not time-series data.
66
value: entry.data[0].y,
67
}));
68
};
69
/**
70
* Format the given axis tick's numeric value into a string for display.
71
*/
72
formatTick = (value) => {
73
return value.toLocaleString();
74
};
75
76
updateSelectedChart = (evt, value) => {
77
this.setState({ selectedChart: value });
78
};
79
80
render() {
81
const { nrqlQueries, stroke, fill, selectedChart } = this.props;
82
// const { selectedChart } = this.state;
83
const nrqlQueryPropsAvailable =
84
nrqlQueries &&
85
nrqlQueries[0] &&
86
nrqlQueries[0].accountId &&
87
nrqlQueries[0].query;
88
if (!nrqlQueryPropsAvailable) {
89
return <EmptyState />;
90
}
91
return (
92
<AutoSizer>
93
{({ width, height }) => (
94
<NrqlQuery
95
query={nrqlQueries[0].query}
96
accountId={parseInt(nrqlQueries[0].accountId)}
97
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
98
>
99
{({ data, loading, error }) => {
100
if (loading) {
101
return <Spinner />;
102
}
103
if (error) {
104
return <ErrorState />;
105
}
106
const transformedData = this.transformData(data);
107
return (
108
<React.Fragment>
109
<SegmentedControl onChange={this.updateSelectedChart}>
110
<SegmentedControlItem
111
value={CHART_TYPES.Radar}
112
label="Radar chart"
113
/>
114
<SegmentedControlItem
115
value={CHART_TYPES.Treemap}
116
label="Treemap chart"
117
/>
118
</SegmentedControl>
119
{selectedChart === CHART_TYPES.Radar ||
120
!Boolean(selectedChart) ? (
121
<RadarChart
122
width={width}
123
height={height}
124
data={transformedData}
125
>
126
<PolarGrid />
127
<PolarAngleAxis dataKey="name" />
128
<PolarRadiusAxis tickFormatter={this.formatTick} />
129
<Radar
130
dataKey="value"
131
stroke={stroke || '#51C9B7'}
132
fill={fill || '#51C9B7'}
133
fillOpacity={0.6}
134
/>
135
</RadarChart>
136
) : (
137
<Treemap
138
width={width}
139
height={height}
140
data={transformedData}
141
dataKey="value"
142
ratio={4 / 3}
143
stroke="#fff"
144
fill="#8884d8"
145
/>
146
)}
147
</React.Fragment>
148
);
149
}}
150
</NrqlQuery>
151
)}
152
</AutoSizer>
153
);
154
}
155
}
156
157
const EmptyState = () => (
158
<Card className="EmptyState">
159
<CardBody className="EmptyState-cardBody">
160
<HeadingText
161
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
162
type={HeadingText.TYPE.HEADING_3}
163
>
164
Please provide at least one NRQL query & account ID pair
165
</HeadingText>
166
<HeadingText
167
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
168
type={HeadingText.TYPE.HEADING_4}
169
>
170
An example NRQL query you can try is:
171
</HeadingText>
172
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
173
</CardBody>
174
</Card>
175
);
176
const ErrorState = () => (
177
<Card className="ErrorState">
178
<CardBody className="ErrorState-cardBody">
179
<HeadingText
180
className="ErrorState-headingText"
181
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
182
type={HeadingText.TYPE.HEADING_3}
183
>
184
Oops! Something went wrong.
185
</HeadingText>
186
</CardBody>
187
</Card>
188
);
visualizations/your-visualization/index.js
1
{
2
"schemaType": "VISUALIZATION",
3
"id": "your-awesome-visualization",
4
"displayName": "Your Awesome Visualization",
5
"description": "",
6
"configuration": [
7
{
8
"name": "selectedChart",
9
"title": "Select chart",
10
"description": "Select which chart to display",
11
"type": "enum",
12
"items": [
13
{
14
"title": "Radar",
15
"value": "radar"
16
},
17
{
18
"title": "Treemap",
19
"value": "treemap"
20
}
21
]
22
},
23
{
24
"name": "nrqlQueries",
25
"title": "NRQL Queries",
26
"type": "collection",
27
"items": [
28
{
29
"name": "accountId",
30
"title": "Account ID",
31
"description": "Account ID to be associated with the query",
32
"type": "number"
33
},
34
{
35
"name": "query",
36
"title": "Query",
37
"description": "NRQL query for visualization",
38
"type": "nrql"
39
}
40
]
41
},
42
{
43
"name": "fill",
44
"title": "Fill color",
45
"description": "A fill color to override the default fill color",
46
"type": "string"
47
},
48
{
49
"name": "stroke",
50
"title": "Stroke color",
51
"description": "A stroke color to override the default stroke color",
52
"type": "string"
53
}
54
]
55
}
visualizations/your-visualization/nr1.json
Step 5 of 6

You no longer need SegmentedControl, so remove it from render():

1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
Radar,
5
RadarChart,
6
PolarGrid,
7
PolarAngleAxis,
8
PolarRadiusAxis,
9
Treemap,
10
} from 'recharts';
11
import {
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
AutoSizer,
20
} from 'nr1';
21
const CHART_TYPES = {
22
Radar: 'radar',
23
Treemap: 'treemap',
24
};
25
export default class YourAwesomeVisualization extends React.Component {
26
// Custom props you wish to be configurable in the UI must also be defined in
27
// the nr1.json file for the visualization. See docs for more details.
28
static propTypes = {
29
/**
30
* A fill color to override the default fill color. This is an example of
31
* a custom chart configuration.
32
*/
33
fill: PropTypes.string,
34
/**
35
* A stroke color to override the default stroke color. This is an example of
36
* a custom chart configuration.
37
*/
38
stroke: PropTypes.string,
39
/**
40
* An array of objects consisting of a nrql `query` and `accountId`.
41
* This should be a standard prop for any NRQL based visualizations.
42
*/
43
nrqlQueries: PropTypes.arrayOf(
44
PropTypes.shape({
45
accountId: PropTypes.number,
46
query: PropTypes.string,
47
})
48
),
49
/**
50
* A chart type configuration to switch between "Radar" and "Treemap" chart types
51
*/
52
selectedChart: PropTypes.string,
53
};
54
state = {
55
selectedChart: CHART_TYPES.Radar,
56
};
57
/**
58
* Restructure the data for a non-time-series, facet-based NRQL query into a
59
* form accepted by the Recharts library's RadarChart.
60
* (https://recharts.org/api/RadarChart).
61
*/
62
transformData = (rawData) => {
63
return rawData.map((entry) => ({
64
name: entry.metadata.name,
65
// Only grabbing the first data value because this is not time-series data.
66
value: entry.data[0].y,
67
}));
68
};
69
/**
70
* Format the given axis tick's numeric value into a string for display.
71
*/
72
formatTick = (value) => {
73
return value.toLocaleString();
74
};
75
76
updateSelectedChart = (evt, value) => {
77
this.setState({ selectedChart: value });
78
};
79
80
render() {
81
const { nrqlQueries, stroke, fill, selectedChart } = this.props;
82
// const { selectedChart } = this.state;
83
const nrqlQueryPropsAvailable =
84
nrqlQueries &&
85
nrqlQueries[0] &&
86
nrqlQueries[0].accountId &&
87
nrqlQueries[0].query;
88
if (!nrqlQueryPropsAvailable) {
89
return <EmptyState />;
90
}
91
return (
92
<AutoSizer>
93
{({ width, height }) => (
94
<NrqlQuery
95
query={nrqlQueries[0].query}
96
accountId={parseInt(nrqlQueries[0].accountId)}
97
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
98
>
99
{({ data, loading, error }) => {
100
if (loading) {
101
return <Spinner />;
102
}
103
if (error) {
104
return <ErrorState />;
105
}
106
const transformedData = this.transformData(data);
107
return (
108
<React.Fragment>
109
/* CAN REMOVE
110
<SegmentedControl onChange={this.updateSelectedChart}>
111
<SegmentedControlItem
112
value={CHART_TYPES.Radar}
113
label="Radar chart"
114
/>
115
<SegmentedControlItem
116
value={CHART_TYPES.Treemap}
117
label="Treemap chart"
118
/>
119
</SegmentedControl>
120
*/
121
{selectedChart === CHART_TYPES.Radar ||
122
!Boolean(selectedChart) ? (
123
<RadarChart
124
width={width}
125
height={height}
126
data={transformedData}
127
>
128
<PolarGrid />
129
<PolarAngleAxis dataKey="name" />
130
<PolarRadiusAxis tickFormatter={this.formatTick} />
131
<Radar
132
dataKey="value"
133
stroke={stroke || '#51C9B7'}
134
fill={fill || '#51C9B7'}
135
fillOpacity={0.6}
136
/>
137
</RadarChart>
138
) : (
139
<Treemap
140
width={width}
141
height={height}
142
data={transformedData}
143
dataKey="value"
144
ratio={4 / 3}
145
stroke="#fff"
146
fill="#8884d8"
147
/>
148
)}
149
</React.Fragment>
150
);
151
}}
152
</NrqlQuery>
153
)}
154
</AutoSizer>
155
);
156
}
157
}
158
159
const EmptyState = () => (
160
<Card className="EmptyState">
161
<CardBody className="EmptyState-cardBody">
162
<HeadingText
163
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
164
type={HeadingText.TYPE.HEADING_3}
165
>
166
Please provide at least one NRQL query & account ID pair
167
</HeadingText>
168
<HeadingText
169
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
170
type={HeadingText.TYPE.HEADING_4}
171
>
172
An example NRQL query you can try is:
173
</HeadingText>
174
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
175
</CardBody>
176
</Card>
177
);
178
const ErrorState = () => (
179
<Card className="ErrorState">
180
<CardBody className="ErrorState-cardBody">
181
<HeadingText
182
className="ErrorState-headingText"
183
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
184
type={HeadingText.TYPE.HEADING_3}
185
>
186
Oops! Something went wrong.
187
</HeadingText>
188
</CardBody>
189
</Card>
190
);
visualizations/your-visualization/index.js
1
{
2
"schemaType": "VISUALIZATION",
3
"id": "your-awesome-visualization",
4
"displayName": "Your Awesome Visualization",
5
"description": "",
6
"configuration": [
7
{
8
"name": "selectedChart",
9
"title": "Select chart",
10
"description": "Select which chart to display",
11
"type": "enum",
12
"items": [
13
{
14
"title": "Radar",
15
"value": "radar"
16
},
17
{
18
"title": "Treemap",
19
"value": "treemap"
20
}
21
]
22
},
23
{
24
"name": "nrqlQueries",
25
"title": "NRQL Queries",
26
"type": "collection",
27
"items": [
28
{
29
"name": "accountId",
30
"title": "Account ID",
31
"description": "Account ID to be associated with the query",
32
"type": "number"
33
},
34
{
35
"name": "query",
36
"title": "Query",
37
"description": "NRQL query for visualization",
38
"type": "nrql"
39
}
40
]
41
},
42
{
43
"name": "fill",
44
"title": "Fill color",
45
"description": "A fill color to override the default fill color",
46
"type": "string"
47
},
48
{
49
"name": "stroke",
50
"title": "Stroke color",
51
"description": "A stroke color to override the default stroke color",
52
"type": "string"
53
}
54
]
55
}
visualizations/your-visualization/nr1.json
Step 6 of 6

Delete all of the component state code:

1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
Radar,
5
RadarChart,
6
PolarGrid,
7
PolarAngleAxis,
8
PolarRadiusAxis,
9
Treemap,
10
} from 'recharts';
11
import {
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
AutoSizer,
20
} from 'nr1';
21
const CHART_TYPES = {
22
Radar: 'radar',
23
Treemap: 'treemap',
24
};
25
export default class YourAwesomeVisualization extends React.Component {
26
// Custom props you wish to be configurable in the UI must also be defined in
27
// the nr1.json file for the visualization. See docs for more details.
28
static propTypes = {
29
/**
30
* A fill color to override the default fill color. This is an example of
31
* a custom chart configuration.
32
*/
33
fill: PropTypes.string,
34
/**
35
* A stroke color to override the default stroke color. This is an example of
36
* a custom chart configuration.
37
*/
38
stroke: PropTypes.string,
39
/**
40
* An array of objects consisting of a nrql `query` and `accountId`.
41
* This should be a standard prop for any NRQL based visualizations.
42
*/
43
nrqlQueries: PropTypes.arrayOf(
44
PropTypes.shape({
45
accountId: PropTypes.number,
46
query: PropTypes.string,
47
})
48
),
49
/**
50
* A chart type configuration to switch between "Radar" and "Treemap" chart types
51
*/
52
selectedChart: PropTypes.string,
53
};
54
/* state = {
55
selectedChart: CHART_TYPES.Radar
56
} */
57
/**
58
* Restructure the data for a non-time-series, facet-based NRQL query into a
59
* form accepted by the Recharts library's RadarChart.
60
* (https://recharts.org/api/RadarChart).
61
*/
62
transformData = (rawData) => {
63
return rawData.map((entry) => ({
64
name: entry.metadata.name,
65
// Only grabbing the first data value because this is not time-series data.
66
value: entry.data[0].y,
67
}));
68
};
69
/**
70
* Format the given axis tick's numeric value into a string for display.
71
*/
72
formatTick = (value) => {
73
return value.toLocaleString();
74
};
75
/*
76
updateSelectedChart = (evt, value) => {
77
this.setState({ selectedChart: value })
78
};
79
*/
80
81
render() {
82
const { nrqlQueries, stroke, fill, selectedChart } = this.props;
83
// const { selectedChart } = this.state;
84
const nrqlQueryPropsAvailable =
85
nrqlQueries &&
86
nrqlQueries[0] &&
87
nrqlQueries[0].accountId &&
88
nrqlQueries[0].query;
89
if (!nrqlQueryPropsAvailable) {
90
return <EmptyState />;
91
}
92
return (
93
<AutoSizer>
94
{({ width, height }) => (
95
<NrqlQuery
96
query={nrqlQueries[0].query}
97
accountId={parseInt(nrqlQueries[0].accountId)}
98
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
99
>
100
{({ data, loading, error }) => {
101
if (loading) {
102
return <Spinner />;
103
}
104
if (error) {
105
return <ErrorState />;
106
}
107
const transformedData = this.transformData(data);
108
return (
109
<React.Fragment>
110
{selectedChart === CHART_TYPES.Radar ||
111
!Boolean(selectedChart) ? (
112
<RadarChart
113
width={width}
114
height={height}
115
data={transformedData}
116
>
117
<PolarGrid />
118
<PolarAngleAxis dataKey="name" />
119
<PolarRadiusAxis tickFormatter={this.formatTick} />
120
<Radar
121
dataKey="value"
122
stroke={stroke || '#51C9B7'}
123
fill={fill || '#51C9B7'}
124
fillOpacity={0.6}
125
/>
126
</RadarChart>
127
) : (
128
<Treemap
129
width={width}
130
height={height}
131
data={transformedData}
132
dataKey="value"
133
ratio={4 / 3}
134
stroke="#fff"
135
fill="#8884d8"
136
/>
137
)}
138
</React.Fragment>
139
);
140
}}
141
</NrqlQuery>
142
)}
143
</AutoSizer>
144
);
145
}
146
}
147
148
const EmptyState = () => (
149
<Card className="EmptyState">
150
<CardBody className="EmptyState-cardBody">
151
<HeadingText
152
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
153
type={HeadingText.TYPE.HEADING_3}
154
>
155
Please provide at least one NRQL query & account ID pair
156
</HeadingText>
157
<HeadingText
158
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
159
type={HeadingText.TYPE.HEADING_4}
160
>
161
An example NRQL query you can try is:
162
</HeadingText>
163
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
164
</CardBody>
165
</Card>
166
);
167
const ErrorState = () => (
168
<Card className="ErrorState">
169
<CardBody className="ErrorState-cardBody">
170
<HeadingText
171
className="ErrorState-headingText"
172
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
173
type={HeadingText.TYPE.HEADING_3}
174
>
175
Oops! Something went wrong.
176
</HeadingText>
177
</CardBody>
178
</Card>
179
);
visualizations/your-visualization/index.js
1
{
2
"schemaType": "VISUALIZATION",
3
"id": "your-awesome-visualization",
4
"displayName": "Your Awesome Visualization",
5
"description": "",
6
"configuration": [
7
{
8
"name": "selectedChart",
9
"title": "Select chart",
10
"description": "Select which chart to display",
11
"type": "enum",
12
"items": [
13
{
14
"title": "Radar",
15
"value": "radar"
16
},
17
{
18
"title": "Treemap",
19
"value": "treemap"
20
}
21
]
22
},
23
{
24
"name": "nrqlQueries",
25
"title": "NRQL Queries",
26
"type": "collection",
27
"items": [
28
{
29
"name": "accountId",
30
"title": "Account ID",
31
"description": "Account ID to be associated with the query",
32
"type": "number"
33
},
34
{
35
"name": "query",
36
"title": "Query",
37
"description": "NRQL query for visualization",
38
"type": "nrql"
39
}
40
]
41
},
42
{
43
"name": "fill",
44
"title": "Fill color",
45
"description": "A fill color to override the default fill color",
46
"type": "string"
47
},
48
{
49
"name": "stroke",
50
"title": "Stroke color",
51
"description": "A stroke color to override the default stroke color",
52
"type": "string"
53
}
54
]
55
}
visualizations/your-visualization/nr1.json

Run your visualization locally, and view it in the Custom Visualizations app in New Relic. Select a chart type from the dropdown in the configuration sidebar, and see your visualization update to show the matching chart type:

Final custom visualization outcome from this guide

Summary

Congratulations on completing the steps in this example! You've learned how to customize your visualization using nr1.json configuration.

Additional resources