• Log inStart now

Customize your visualization with SDK components

25 min

Course

This lesson is part of a course that teaches you how to build a custom visualization on the New Relic platform.

Use New Relic custom visualizations to display your data, whether it's from New Relic's database or an external source, in unique ways that are distinct from the charts offered by the New Relic platform.

In this lesson, you build a visualization that displays your data in one of two chart types: RadarChart or Treemap. You then implement a SegmentedControl component from the New Relic One SDK, which allows you to alternate between the two chart types. Ultimately, this gives you freedom to view your data in a dynamic way that isn't possible with New Relic's base offerings.

Tip

If you get lost in the code project and would like to see what the files should look like when you're done with each lesson, check out the course project on Github.

Before you begin

Explore our custom visualization guides and build your first visualization. After you're done, you'll have a better foundation for building more complex visualizations, such as the one you'll build in this course.

Finally, if you haven't already:

Create your visualization

Step 1 of 2

Ensure you're working with the latest version of the New Relic One CLI:

bash
$
nr1 update
Step 2 of 2

Create a visualization, called radar-or-treemap, in a Nerdpack, called alternate-viz:

bash
$
nr1 create --type visualization --name radar-or-treemap
You’re trying to create a visualization outside of a Nerdpack. We’ll create a Nerdpack for you—what do you want to name it? … alternate-viz
nerdpack created successfully!
nerdpack alternate-viz is available at "./alternate-viz"
visualization created successfully!
visualization radar-or-treemap is available at "./alternate-viz/visualizations/radar-or-treemap"

Tip

If you receive a RequestError for a self-signed certificate when you run nr1 create, you may need to add a certificate to Node's certificate chain. Read more about this and other advanced configurations in Enable advanced configurations for your Nerdpack.

As a result, you have a new visualizations/radar-or-treemap directory under alternate-viz:

bash
$
cd alternate-viz
$
ls visualizations/radar-or-treemap
index.js nr1.json styles.scss

Set up your component state

Add component state to the default visualization template that nr1 created for you.

Step 1 of 3

Navigate to alternate-viz/visualizations/radar-or-treemap/index.js. You'll work in here for the rest of this lesson.

Step 2 of 3

Add a constant called CHART_TYPES:

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

CHART_TYPES enumerates the two chart types you'll alternate between in your visualization.

Step 3 of 3

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

This state value stores the chart type in which you want to show your data.

Now that you've created an object which enumerates the chart type options for your visualization, and you've initialized state.selectedChart, you're ready to implement a control UI for switching between the two chart types.

Add SegmentedControl components

state.selectedChart isn't useful unless your visualization's users can actually select a chart type. Use SegmentedControl and SegmentedControlItem to switch between the two chart types.

Tip

To learn more about the components available in the New Relic One SDK, go to our Intro to New Relic One SDK.

Step 1 of 7

Import SegmentedControl and SegmentedControlItem 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
AutoSizer,
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
} from 'nr1';
20
21
const CHART_TYPES = {
22
'Radar': 'radar',
23
'Treemap': 'treemap'
24
}
25
26
export default class RadarOrTreemapVisualization 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
const EmptyState = () => (
135
<Card className="EmptyState">
136
<CardBody className="EmptyState-cardBody">
137
<HeadingText
138
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
139
type={HeadingText.TYPE.HEADING_3}
140
>
141
Please provide at least one NRQL query & account ID pair
142
</HeadingText>
143
<HeadingText
144
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
145
type={HeadingText.TYPE.HEADING_4}
146
>
147
An example NRQL query you can try is:
148
</HeadingText>
149
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
150
</CardBody>
151
</Card>
152
);
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/radar-or-treemap/index.js
Step 2 of 7

In render(), wrap RadarChart in a React.Fragment:

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
AutoSizer,
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
} from 'nr1';
20
21
const CHART_TYPES = {
22
'Radar': 'radar',
23
'Treemap': 'treemap'
24
}
25
26
export default class RadarOrTreemapVisualization 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
<React.Fragment>
111
<RadarChart
112
width={width}
113
height={height}
114
data={transformedData}
115
>
116
<PolarGrid />
117
<PolarAngleAxis dataKey="name" />
118
<PolarRadiusAxis tickFormatter={this.formatTick} />
119
<Radar
120
dataKey="value"
121
stroke={stroke || '#51C9B7'}
122
fill={fill || '#51C9B7'}
123
fillOpacity={0.6}
124
/>
125
</RadarChart>
126
</React.Fragment>
127
);
128
}}
129
</NrqlQuery>
130
)}
131
</AutoSizer>
132
);
133
}
134
}
135
136
const EmptyState = () => (
137
<Card className="EmptyState">
138
<CardBody className="EmptyState-cardBody">
139
<HeadingText
140
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
141
type={HeadingText.TYPE.HEADING_3}
142
>
143
Please provide at least one NRQL query & account ID pair
144
</HeadingText>
145
<HeadingText
146
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
147
type={HeadingText.TYPE.HEADING_4}
148
>
149
An example NRQL query you can try is:
150
</HeadingText>
151
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
152
</CardBody>
153
</Card>
154
);
155
156
const ErrorState = () => (
157
<Card className="ErrorState">
158
<CardBody className="ErrorState-cardBody">
159
<HeadingText
160
className="ErrorState-headingText"
161
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
162
type={HeadingText.TYPE.HEADING_3}
163
>
164
Oops! Something went wrong.
165
</HeadingText>
166
</CardBody>
167
</Card>
168
);
visualizations/radar-or-treemap/index.js

This allows you to return multiple components from the same render().

Step 3 of 7

Add a SegmentedControl and two SegmentedControlItem components, each with a value and a 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
AutoSizer,
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
} from 'nr1';
20
21
const CHART_TYPES = {
22
'Radar': 'radar',
23
'Treemap': 'treemap'
24
}
25
26
export default class RadarOrTreemapVisualization 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
<React.Fragment>
111
<SegmentedControl
112
onChange={(event, value) => console.log(value)}
113
>
114
<SegmentedControlItem
115
value={CHART_TYPES.Radar}
116
label="Radar chart"
117
/>
118
<SegmentedControlItem
119
value={CHART_TYPES.Treemap}
120
label="Treemap chart"
121
/>
122
</SegmentedControl>
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
</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
168
const ErrorState = () => (
169
<Card className="ErrorState">
170
<CardBody className="ErrorState-cardBody">
171
<HeadingText
172
className="ErrorState-headingText"
173
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
174
type={HeadingText.TYPE.HEADING_3}
175
>
176
Oops! Something went wrong.
177
</HeadingText>
178
</CardBody>
179
</Card>
180
);
visualizations/radar-or-treemap/index.js

Here, your SegmentedControl logs the SegmentedControlItem.value to the console when you change your selection. The values you've defined for your SegmentedControlItem components correspond to the two CHART_TYPES you created in a previous step.

Step 4 of 7

Navigate to the root of your Nerdpack at alternate-viz.

Step 5 of 7
bash
$
nr1 nerdpack:serve
Step 6 of 7

Open the link to your visualization that's shown in the terminal when the Node server starts:

bash
Visualizations:
radar-or-treemap https://one.nr/012ab3cd4Ef
Step 7 of 7

Configure your visualization with an account ID and a query:

Visualization with SegmentedControl

With some required data for your chart to process, you now see a RadarChart with the SegmentedControl at the top of the view.

Look at your browser's console to see your SegmentedControl logs:

SegmentedControl logs

Connect your component's state to the SegmentedControl

Add a method to update state and connect that method with the SegmentedControl you added in the last section.

Step 1 of 2

Add a component method, called updateSelectedChart():

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
AutoSizer,
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
} from 'nr1';
20
21
const CHART_TYPES = {
22
'Radar': 'radar',
23
'Treemap': 'treemap'
24
}
25
26
export default class RadarOrTreemapVisualization 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
updateSelectedChart = (evt, value) => {
78
this.setState({ selectedChart: value })
79
};
80
81
render() {
82
const {nrqlQueries, stroke, fill} = this.props;
83
84
const nrqlQueryPropsAvailable =
85
nrqlQueries &&
86
nrqlQueries[0] &&
87
nrqlQueries[0].accountId &&
88
nrqlQueries[0].query;
89
90
if (!nrqlQueryPropsAvailable) {
91
return <EmptyState />;
92
}
93
94
return (
95
<AutoSizer>
96
{({width, height}) => (
97
<NrqlQuery
98
query={nrqlQueries[0].query}
99
accountId={parseInt(nrqlQueries[0].accountId)}
100
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
101
>
102
{({data, loading, error}) => {
103
if (loading) {
104
return <Spinner />;
105
}
106
107
if (error) {
108
return <ErrorState />;
109
}
110
111
const transformedData = this.transformData(data);
112
113
return (
114
<React.Fragment>
115
<SegmentedControl
116
onChange={(event, value) => console.log(value)}
117
>
118
<SegmentedControlItem
119
value={CHART_TYPES.Radar}
120
label="Radar chart"
121
/>
122
<SegmentedControlItem
123
value={CHART_TYPES.Treemap}
124
label="Treemap chart"
125
/>
126
</SegmentedControl>
127
<RadarChart
128
width={width}
129
height={height}
130
data={transformedData}
131
>
132
<PolarGrid />
133
<PolarAngleAxis dataKey="name" />
134
<PolarRadiusAxis tickFormatter={this.formatTick} />
135
<Radar
136
dataKey="value"
137
stroke={stroke || '#51C9B7'}
138
fill={fill || '#51C9B7'}
139
fillOpacity={0.6}
140
/>
141
</RadarChart>
142
</React.Fragment>
143
);
144
}}
145
</NrqlQuery>
146
)}
147
</AutoSizer>
148
);
149
}
150
}
151
152
const EmptyState = () => (
153
<Card className="EmptyState">
154
<CardBody className="EmptyState-cardBody">
155
<HeadingText
156
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
157
type={HeadingText.TYPE.HEADING_3}
158
>
159
Please provide at least one NRQL query & account ID pair
160
</HeadingText>
161
<HeadingText
162
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
163
type={HeadingText.TYPE.HEADING_4}
164
>
165
An example NRQL query you can try is:
166
</HeadingText>
167
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
168
</CardBody>
169
</Card>
170
);
171
172
const ErrorState = () => (
173
<Card className="ErrorState">
174
<CardBody className="ErrorState-cardBody">
175
<HeadingText
176
className="ErrorState-headingText"
177
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
178
type={HeadingText.TYPE.HEADING_3}
179
>
180
Oops! Something went wrong.
181
</HeadingText>
182
</CardBody>
183
</Card>
184
);
visualizations/radar-or-treemap/index.js

This new method takes a value argument and sets state.selectedChart to that value.

Step 2 of 2

Set SegmentedControl.onChange to updateSelectedChart():

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
AutoSizer,
12
Card,
13
CardBody,
14
HeadingText,
15
NrqlQuery,
16
SegmentedControl,
17
SegmentedControlItem,
18
Spinner,
19
} from 'nr1';
20
21
const CHART_TYPES = {
22
'Radar': 'radar',
23
'Treemap': 'treemap'
24
}
25
26
export default class RadarOrTreemapVisualization 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
updateSelectedChart = (evt, value) => {
78
this.setState({ selectedChart: value })
79
};
80
81
render() {
82
const {nrqlQueries, stroke, fill} = this.props;
83
84
const nrqlQueryPropsAvailable =
85
nrqlQueries &&
86
nrqlQueries[0] &&
87
nrqlQueries[0].accountId &&
88
nrqlQueries[0].query;
89
90
if (!nrqlQueryPropsAvailable) {
91
return <EmptyState />;
92
}
93
94
return (
95
<AutoSizer>
96
{({width, height}) => (
97
<NrqlQuery
98
query={nrqlQueries[0].query}
99
accountId={parseInt(nrqlQueries[0].accountId)}
100
pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}
101
>
102
{({data, loading, error}) => {
103
if (loading) {
104
return <Spinner />;
105
}
106
107
if (error) {
108
return <ErrorState />;
109
}
110
111
const transformedData = this.transformData(data);
112
113
return (
114
<React.Fragment>
115
<SegmentedControl
116
onChange={this.updateSelectedChart}
117
>
118
<SegmentedControlItem
119
value={CHART_TYPES.Radar}
120
label="Radar chart"
121
/>
122
<SegmentedControlItem
123
value={CHART_TYPES.Treemap}
124
label="Treemap chart"
125
/>
126
</SegmentedControl>
127
<RadarChart
128
width={width}
129
height={height}
130
data={transformedData}
131
>
132
<PolarGrid />
133
<PolarAngleAxis dataKey="name" />
134
<PolarRadiusAxis tickFormatter={this.formatTick} />
135
<Radar
136
dataKey="value"
137
stroke={stroke || '#51C9B7'}
138
fill={fill || '#51C9B7'}
139
fillOpacity={0.6}
140
/>
141
</RadarChart>
142
</React.Fragment>
143
);
144
}}
145
</NrqlQuery>
146
)}
147
</AutoSizer>
148
);
149
}
150
}
151
152
const EmptyState = () => (
153
<Card className="EmptyState">
154
<CardBody className="EmptyState-cardBody">
155
<HeadingText
156
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
157
type={HeadingText.TYPE.HEADING_3}
158
>
159
Please provide at least one NRQL query & account ID pair
160
</HeadingText>
161
<HeadingText
162
spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}
163
type={HeadingText.TYPE.HEADING_4}
164
>
165
An example NRQL query you can try is:
166
</HeadingText>
167
<code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>
168
</CardBody>
169
</Card>
170
);
171
172
const ErrorState = () => (
173
<Card className="ErrorState">
174
<CardBody className="ErrorState-cardBody">
175
<HeadingText
176
className="ErrorState-headingText"
177
spacingType={[HeadingText.SPACING_TYPE.LARGE]}
178
type={HeadingText.TYPE.HEADING_3}
179
>
180
Oops! Something went wrong.
181
</HeadingText>
182
</CardBody>
183
</Card>
184
);
visualizations/radar-or-treemap/index.js

Now, when you change your selection in the SegmentedControl, your selection will be set in state.

Implement a Treemap option

Add a Treemap to your visualization. This map will be an alternative to the existing RadarChart.

Technical detail

This guide uses Recharts components for third-party charts, but you can use any other JavaScript charting libraries that are compatible with the current React version when you build New Relic visualizations and apps.

Step 1 of 3

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

Now, you can use Treemap in your visualization component.

Step 2 of 3

In render(), add a Treemap component:

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

Here, you've defined a new Treemap component with some props, including height, width, fill, and stroke.

Step 3 of 3

With your Nerdpack served locally, view your visualization. The SegmentedControl and RadarChart are at the top of the view, but if you scroll down, you'll see your new Treemap:

Treemap and RadarChart together

Switch between charts with your component's state

Use state.selectedChart to determine which chart to show: the RadarChart or the Treemap.

Step 1 of 1

Destructure this.state to access selectedChart as a separate constant. Then, compare selectedChart to CHART_TYPES.Radar. If they are the same, render a RadarChart. Otherwise, render a Treemap:

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

Here, you used a ternary expression to render a RadarChart or a Treemap. The rendered chart is determined by the value of selectedChart.

With your Nerdpack served locally, view your visualization.

Select Radar chart from the SegmentedControl:

RadarChart selected

Select Treemap chart from the SegmentedControl:

Treemap selected

Summary

Congratulations! In this lesson, you 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

Course

This lesson is part of a course that teaches you how to build a custom visualization on the New Relic platform. When you're ready, continue on to the next lesson: Customize visualizations with configuration.

Copyright © 2024 New Relic Inc.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.