Custom visualizations and the New Relic One SDK

Now that you have a basic visualization, it's time to customize it! You can use all of the New Relic One SDK components in a Custom Visualization the same way you can use them in a Nerdlet. In this guide, you'll add a new chart type and a SegmentedControl component. You'll be able to dynamically swap between the Radar chart and the new chart right from the browser.

This guide builds off the Build a custom visualization for dashboards guide.

Before you begin

To get started, follow the Build a custom visualization for dashboards guide to get set up with your New Relic account, install the New Relic One CLI, and create your first visualization. You'll use the visualization you create in that guide as a starting point for this guide.

Set up the visualization component state

In this first set of steps you'll add component state to your visualization template from the previous guide referenced above.

Step 1 of 1

From the root of the Nerdpack project you created in the previous guide, navigate to /visualizations/<your-visualization>/index.js. You'll continue to work in the index.js file for the rest of this guide.

Add a chart types constant above the first line of the class and add the component state property with an initial state 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
} from 'recharts';
10
import {
11
Card,
12
CardBody,
13
HeadingText,
14
NrqlQuery,
15
Spinner,
16
AutoSizer,
17
} from 'nr1';
18
19
const CHART_TYPES = {
20
Radar: 'radar',
21
Treemap: 'treemap',
22
};
23
24
export default class YourAwesomeVisualization extends React.Component {
25
// Custom props you wish to be configurable in the UI must also be defined in
26
// the nr1.json file for the visualization. See docs for more details.
27
static propTypes = {
28
/**
29
* A fill color to override the default fill color. This is an example of
30
* a custom chart configuration.
31
*/
32
fill: PropTypes.string,
33
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
51
state = {
52
selectedChart: CHART_TYPES.Radar,
53
};
54
55
/**
56
* Restructure the data for a non-time-series, facet-based NRQL query into a
57
* form accepted by the Recharts library's RadarChart.
58
* (https://recharts.org/api/RadarChart).
59
*/
60
transformData = (rawData) => {
61
return rawData.map((entry) => ({
62
name: entry.metadata.name,
63
// Only grabbing the first data value because this is not time-series data.
64
value: entry.data[0].y,
65
}));
66
};
67
68
/**
69
* Format the given axis tick's numeric value into a string for display.
70
*/
71
formatTick = (value) => {
72
return value.toLocaleString();
73
};
74
75
render() {
76
const { nrqlQueries, stroke, fill } = this.props;
77
78
const nrqlQueryPropsAvailable =
79
nrqlQueries &&
80
nrqlQueries[0] &&
81
nrqlQueries[0].accountId &&
82
nrqlQueries[0].query;
83
84
if (!nrqlQueryPropsAvailable) {
85
return <EmptyState />;
86
}
87
88
return (
89
<AutoSizer>
90
{({ width, height }) => (
91
<NrqlQuery
92
query={nrqlQueries[0].query}
93
accountId={parseInt(nrqlQueries[0].accountId)}
94
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
95
>
96
{({ data, loading, error }) => {
97
if (loading) {
98
return <Spinner />;
99
}
100
101
if (error) {
102
return <ErrorState />;
103
}
104
105
const transformedData = this.transformData(data);
106
107
return (
108
<RadarChart
109
width={width}
110
height={height}
111
data={transformedData}
112
>
113
<PolarGrid />
114
<PolarAngleAxis dataKey="name" />
115
<PolarRadiusAxis tickFormatter={this.formatTick} />
116
<Radar
117
dataKey="value"
118
stroke={stroke || '#51C9B7'}
119
fill={fill || '#51C9B7'}
120
fillOpacity={0.6}
121
/>
122
</RadarChart>
123
);
124
}}
125
</NrqlQuery>
126
)}
127
</AutoSizer>
128
);
129
}
130
}
131
132
const EmptyState = () => (
133
<Card className="EmptyState">
134
<CardBody className="EmptyState-cardBody">
135
<HeadingText
136
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
137
type={HeadingText.TYPE.HEADING_3}
138
>
139
Please provide at least one NRQL query & account ID pair
140
</HeadingText>
141
<HeadingText
142
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
143
type={HeadingText.TYPE.HEADING_4}
144
>
145
An example NRQL query you can try is:
146
</HeadingText>
147
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
148
</CardBody>
149
</Card>
150
);
151
const ErrorState = () => (
152
<Card className="ErrorState">
153
<CardBody className="ErrorState-cardBody">
154
<HeadingText
155
className="ErrorState-headingText"
156
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
157
type={HeadingText.TYPE.HEADING_3}
158
>
159
Oops! Something went wrong.
160
</HeadingText>
161
</CardBody>
162
</Card>
163
);
visualizations/your-visualization/index.js

Add the SegmentedControl components

To let the consumers of your visualization decide what type of chart they want to display, you'll use SegmentedControl and SegmentedControlItem, which allow users to switch between a set of provided options. To learn more about the components available from New Relic, go to our Intro to New Relic One SDK.

Step 1 of 2

Add SegmentedControl and SegmentedControlItem to the import from nr1:

1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
Radar,
5
RadarChart,
6
PolarGrid,
7
PolarAngleAxis,
8
PolarRadiusAxis,
9
} from 'recharts';
10
import {
11
Card,
12
CardBody,
13
HeadingText,
14
NrqlQuery,
15
SegmentedControl,
16
SegmentedControlItem,
17
Spinner,
18
AutoSizer
19
} from 'nr1';
20
21
const CHART_TYPES = {
22
'Radar': 'radar',
23
'Treemap': 'treemap'
24
}
25
26
export default class YourAwesomeVisualization extends React.Component {
27
// Custom props you wish to be configurable in the UI must also be defined in
28
// the nr1.json file for the visualization. See docs for more details.
29
static propTypes = {
30
/**
31
* A fill color to override the default fill color. This is an example of
32
* a custom chart configuration.
33
*/
34
fill: PropTypes.string,
35
36
/**
37
* A stroke color to override the default stroke color. This is an example of
38
* a custom chart configuration.
39
*/
40
stroke: PropTypes.string,
41
/**
42
* An array of objects consisting of a nrql `query` and `accountId`.
43
* This should be a standard prop for any NRQL based visualizations.
44
*/
45
nrqlQueries: PropTypes.arrayOf(
46
PropTypes.shape({
47
accountId: PropTypes.number,
48
query: PropTypes.string,
49
})
50
),
51
};
52
53
state = {
54
selectedChart: CHART_TYPES.Radar
55
}
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
/**
71
* Format the given axis tick's numeric value into a string for display.
72
*/
73
formatTick = (value) => {
74
return value.toLocaleString();
75
};
76
77
render() {
78
const {nrqlQueries, stroke, fill} = this.props;
79
80
const nrqlQueryPropsAvailable =
81
nrqlQueries &&
82
nrqlQueries[0] &&
83
nrqlQueries[0].accountId &&
84
nrqlQueries[0].query;
85
86
if (!nrqlQueryPropsAvailable) {
87
return <EmptyState />;
88
}
89
90
return (
91
<AutoSizer>
92
{({width, height}) => (
93
<NrqlQuery
94
query={nrqlQueries[0].query}
95
accountId={parseInt(nrqlQueries[0].accountId)}
96
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
97
>
98
{({data, loading, error}) => {
99
if (loading) {
100
return <Spinner />;
101
}
102
103
if (error) {
104
return <ErrorState />;
105
}
106
107
const transformedData = this.transformData(data);
108
109
return (
110
<RadarChart
111
width={width}
112
height={height}
113
data={transformedData}
114
>
115
<PolarGrid />
116
<PolarAngleAxis dataKey="name" />
117
<PolarRadiusAxis tickFormatter={this.formatTick} />
118
<Radar
119
dataKey="value"
120
stroke={stroke || '#51C9B7'}
121
fill={fill || '#51C9B7'}
122
fillOpacity={0.6}
123
/>
124
</RadarChart>
125
);
126
}}
127
</NrqlQuery>
128
)}
129
</AutoSizer>
130
);
131
}
132
}
133
134
135
const EmptyState = () => (
136
<Card className="EmptyState">
137
<CardBody className="EmptyState-cardBody">
138
<HeadingText
139
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
140
type={HeadingText.TYPE.HEADING_3}
141
>
142
Please provide at least one NRQL query & account ID pair
143
</HeadingText>
144
<HeadingText
145
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
146
type={HeadingText.TYPE.HEADING_4}
147
>
148
An example NRQL query you can try is:
149
</HeadingText>
150
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
151
</CardBody>
152
</Card>
153
);
154
const ErrorState = () => (
155
<Card className="ErrorState">
156
<CardBody className="ErrorState-cardBody">
157
<HeadingText
158
className="ErrorState-headingText"
159
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
160
type={HeadingText.TYPE.HEADING_3}
161
>
162
Oops! Something went wrong.
163
</HeadingText>
164
</CardBody>
165
</Card>
166
);
visualizations/your-visualization/index.js
Step 2 of 2

In the render function, wrap RadarChart in <React.Fragment> ... </React.Fragment>. Then add the SegmentedControl and SegmentedControlItem above the RadarChart and set the value and label:

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

If you run your visualization locally, your visualization should match the following image and changing the SegmentedControl should not have any effect on the visualization:

Visualization with SegmenetedControl

Connect component state with the new UI controls

In the next steps, you're going to add a function to update state and connect that with the SegmentedControl component.

Step 1 of 2

Add a component method that updates state to your visualization class:

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

Pass updateSelectedChart as the SegmentedControl component's onChange prop:

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

Add your new chart option

In the next couple of steps, you'll add the Treemap chart from Recharts and get the correct values passed into it.

Tip

This guide uses Recharts chart components but you can explore any other JavaScript charting libraries that are compatible with the current React version on the New Relic One Platform.
Step 1 of 2

Add Treemap to the import from recharts:

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
state = {
51
selectedChart: CHART_TYPES.Radar,
52
};
53
/**
54
* Restructure the data for a non-time-series, facet-based NRQL query into a
55
* form accepted by the Recharts library's RadarChart.
56
* (https://recharts.org/api/RadarChart).
57
*/
58
transformData = (rawData) => {
59
return rawData.map((entry) => ({
60
name: entry.metadata.name,
61
// Only grabbing the first data value because this is not time-series data.
62
value: entry.data[0].y,
63
}));
64
};
65
/**
66
* Format the given axis tick's numeric value into a string for display.
67
*/
68
formatTick = (value) => {
69
return value.toLocaleString();
70
};
71
72
updateSelectedChart = (evt, value) => {
73
this.setState({ selectedChart: value });
74
};
75
76
render() {
77
const { nrqlQueries, stroke, fill } = this.props;
78
const nrqlQueryPropsAvailable =
79
nrqlQueries &&
80
nrqlQueries[0] &&
81
nrqlQueries[0].accountId &&
82
nrqlQueries[0].query;
83
if (!nrqlQueryPropsAvailable) {
84
return <EmptyState />;
85
}
86
return (
87
<AutoSizer>
88
{({ width, height }) => (
89
<NrqlQuery
90
query={nrqlQueries[0].query}
91
accountId={parseInt(nrqlQueries[0].accountId)}
92
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
93
>
94
{({ data, loading, error }) => {
95
if (loading) {
96
return <Spinner />;
97
}
98
if (error) {
99
return <ErrorState />;
100
}
101
const transformedData = this.transformData(data);
102
return (
103
<React.Fragment>
104
<SegmentedControl onChange={this.updateSelectedChart}>
105
<SegmentedControlItem
106
value={CHART_TYPES.Radar}
107
label="Radar chart"
108
/>
109
<SegmentedControlItem
110
value={CHART_TYPES.Treemap}
111
label="Treemap chart"
112
/>
113
</SegmentedControl>
114
<RadarChart
115
width={width}
116
height={height}
117
data={transformedData}
118
>
119
<PolarGrid />
120
<PolarAngleAxis dataKey="name" />
121
<PolarRadiusAxis tickFormatter={this.formatTick} />
122
<Radar
123
dataKey="value"
124
stroke={stroke || '#51C9B7'}
125
fill={fill || '#51C9B7'}
126
fillOpacity={0.6}
127
/>
128
</RadarChart>
129
</React.Fragment>
130
);
131
}}
132
</NrqlQuery>
133
)}
134
</AutoSizer>
135
);
136
}
137
}
138
139
const EmptyState = () => (
140
<Card className="EmptyState">
141
<CardBody className="EmptyState-cardBody">
142
<HeadingText
143
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
144
type={HeadingText.TYPE.HEADING_3}
145
>
146
Please provide at least one NRQL query & account ID pair
147
</HeadingText>
148
<HeadingText
149
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
150
type={HeadingText.TYPE.HEADING_4}
151
>
152
An example NRQL query you can try is:
153
</HeadingText>
154
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
155
</CardBody>
156
</Card>
157
);
158
const ErrorState = () => (
159
<Card className="ErrorState">
160
<CardBody className="ErrorState-cardBody">
161
<HeadingText
162
className="ErrorState-headingText"
163
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
164
type={HeadingText.TYPE.HEADING_3}
165
>
166
Oops! Something went wrong.
167
</HeadingText>
168
</CardBody>
169
</Card>
170
);
visualizations/your-visualization/index.js

Further reading

To learn more about the Treemap chart component, go to the Recharts Treemap example or the Recharts Treemap API docs.
Step 2 of 2

Under the RadarChart closing tag in render, add the Treemap component and the necessary props:

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

Now render the visualization in local development to see the new Treemap chart below your Radar chart. If you scroll down a bit in your visualization, it should look something like the following:

Custom visualization scrolled to show both Radar and Treemap charts

Use component state to switch between charts

In the final steps, you'll use the selectedChart state value to determine which chart to show, the Radar chart or the Treemap chart.

Step 1 of 2

Destructure selectedChart from state near the top of the render() function:

1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
Radar,
5
RadarChart,
6
PolarGrid,
7
PolarAngleAxis,
8
PolarRadiusAxis,
9
} from 'recharts';
10
import {
11
Card,
12
CardBody,
13
HeadingText,
14
NrqlQuery,
15
SegmentedControl,
16
SegmentedControlItem,
17
Spinner,
18
AutoSizer,
19
} from 'nr1';
20
const CHART_TYPES = {
21
Radar: 'radar',
22
Treemap: 'treemap',
23
};
24
export default class YourAwesomeVisualization extends React.Component {
25
// Custom props you wish to be configurable in the UI must also be defined in
26
// the nr1.json file for the visualization. See docs for more details.
27
static propTypes = {
28
/**
29
* A fill color to override the default fill color. This is an example of
30
* a custom chart configuration.
31
*/
32
fill: PropTypes.string,
33
/**
34
* A stroke color to override the default stroke color. This is an example of
35
* a custom chart configuration.
36
*/
37
stroke: PropTypes.string,
38
/**
39
* An array of objects consisting of a nrql `query` and `accountId`.
40
* This should be a standard prop for any NRQL based visualizations.
41
*/
42
nrqlQueries: PropTypes.arrayOf(
43
PropTypes.shape({
44
accountId: PropTypes.number,
45
query: PropTypes.string,
46
})
47
),
48
};
49
state = {
50
selectedChart: CHART_TYPES.Radar,
51
};
52
/**
53
* Restructure the data for a non-time-series, facet-based NRQL query into a
54
* form accepted by the Recharts library's RadarChart.
55
* (https://recharts.org/api/RadarChart).
56
*/
57
transformData = (rawData) => {
58
return rawData.map((entry) => ({
59
name: entry.metadata.name,
60
// Only grabbing the first data value because this is not time-series data.
61
value: entry.data[0].y,
62
}));
63
};
64
/**
65
* Format the given axis tick's numeric value into a string for display.
66
*/
67
formatTick = (value) => {
68
return value.toLocaleString();
69
};
70
71
updateSelectedChart = (evt, value) => {
72
this.setState({ selectedChart: value });
73
};
74
75
render() {
76
const { nrqlQueries, stroke, fill } = this.props;
77
const { selectedChart } = this.state;
78
const nrqlQueryPropsAvailable =
79
nrqlQueries &&
80
nrqlQueries[0] &&
81
nrqlQueries[0].accountId &&
82
nrqlQueries[0].query;
83
if (!nrqlQueryPropsAvailable) {
84
return <EmptyState />;
85
}
86
return (
87
<AutoSizer>
88
{({ width, height }) => (
89
<NrqlQuery
90
query={nrqlQueries[0].query}
91
accountId={parseInt(nrqlQueries[0].accountId)}
92
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
93
>
94
{({ data, loading, error }) => {
95
if (loading) {
96
return <Spinner />;
97
}
98
if (error) {
99
return <ErrorState />;
100
}
101
const transformedData = this.transformData(data);
102
return (
103
<React.Fragment>
104
<SegmentedControl onChange={this.updateSelectedChart}>
105
<SegmentedControlItem
106
value={CHART_TYPES.Radar}
107
label="Radar chart"
108
/>
109
<SegmentedControlItem
110
value={CHART_TYPES.Treemap}
111
label="Treemap chart"
112
/>
113
</SegmentedControl>
114
<RadarChart
115
width={width}
116
height={height}
117
data={transformedData}
118
>
119
<PolarGrid />
120
<PolarAngleAxis dataKey="name" />
121
<PolarRadiusAxis tickFormatter={this.formatTick} />
122
<Radar
123
dataKey="value"
124
stroke={stroke || '#51C9B7'}
125
fill={fill || '#51C9B7'}
126
fillOpacity={0.6}
127
/>
128
</RadarChart>
129
<Treemap
130
width={width}
131
height={height}
132
data={transformedData}
133
dataKey="value"
134
ratio={4 / 3}
135
stroke="#fff"
136
fill="#8884d8"
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
Step 2 of 2

Here, you compare selectedChart to CHART_TYPES.Radar. If selectedChart is a Radar, you render a RadarChart. Otherwise, you render a Treemap. You can achieve this by wrapping the RadarChart and Treemap in a ternary using the selectedChart state:

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

Go to the browser and try it out! Run your visualization locally and view it in the Custom Visualization app in New Relic. Click on "Treemap chart" in the SegmentedControl. You should see your Treemap chart render instead of the Radar chart:

Final custom visualization outcome from this guide

Radar chart selected

Final custom visualization outcome from this guide

Treemap chart selected

Summary

Congratulations on completing the steps in this example! You've learned how to:

  • Customize your visualization using New Relic One SDK components
  • Add a new chart type to your visualization
  • Create a user interaction in your visualization

Additional resources