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.
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
:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9} from 'recharts';10import {11 Card,12 CardBody,13 HeadingText,14 NrqlQuery,15 Spinner,16 AutoSizer,17} from 'nr1';1819const CHART_TYPES = {20 Radar: 'radar',21 Treemap: 'treemap',22};2324export default class YourAwesomeVisualization extends React.Component {25 // Custom props you wish to be configurable in the UI must also be defined in26 // 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 of30 * a custom chart configuration.31 */32 fill: PropTypes.string,3334 /**35 * A stroke color to override the default stroke color. This is an example of36 * 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 };5051 state = {52 selectedChart: CHART_TYPES.Radar,53 };5455 /**56 * Restructure the data for a non-time-series, facet-based NRQL query into a57 * 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 };6768 /**69 * Format the given axis tick's numeric value into a string for display.70 */71 formatTick = (value) => {72 return value.toLocaleString();73 };7475 render() {76 const { nrqlQueries, stroke, fill } = this.props;7778 const nrqlQueryPropsAvailable =79 nrqlQueries &&80 nrqlQueries[0] &&81 nrqlQueries[0].accountId &&82 nrqlQueries[0].query;8384 if (!nrqlQueryPropsAvailable) {85 return <EmptyState />;86 }8788 return (89 <AutoSizer>90 {({ width, height }) => (91 <NrqlQuery92 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 }100101 if (error) {102 return <ErrorState />;103 }104105 const transformedData = this.transformData(data);106107 return (108 <RadarChart109 width={width}110 height={height}111 data={transformedData}112 >113 <PolarGrid />114 <PolarAngleAxis dataKey="name" />115 <PolarRadiusAxis tickFormatter={this.formatTick} />116 <Radar117 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}131132const EmptyState = () => (133 <Card className="EmptyState">134 <CardBody className="EmptyState-cardBody">135 <HeadingText136 spacingType={[HeadingText.SPACING_TYPE.LARGE]}137 type={HeadingText.TYPE.HEADING_3}138 >139 Please provide at least one NRQL query & account ID pair140 </HeadingText>141 <HeadingText142 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);151const ErrorState = () => (152 <Card className="ErrorState">153 <CardBody className="ErrorState-cardBody">154 <HeadingText155 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);
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.
Add SegmentedControl
and SegmentedControlItem
to the import from nr1
:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9} from 'recharts';10import {11 Card,12 CardBody,13 HeadingText,14 NrqlQuery,15 SegmentedControl,16 SegmentedControlItem,17 Spinner,18 AutoSizer19} from 'nr1';2021const CHART_TYPES = {22 'Radar': 'radar',23 'Treemap': 'treemap'24}2526export default class YourAwesomeVisualization extends React.Component {27 // Custom props you wish to be configurable in the UI must also be defined in28 // 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 of32 * a custom chart configuration.33 */34 fill: PropTypes.string,3536 /**37 * A stroke color to override the default stroke color. This is an example of38 * 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 };5253 state = {54 selectedChart: CHART_TYPES.Radar55 }5657 /**58 * Restructure the data for a non-time-series, facet-based NRQL query into a59 * 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 };6970 /**71 * Format the given axis tick's numeric value into a string for display.72 */73 formatTick = (value) => {74 return value.toLocaleString();75 };7677 render() {78 const {nrqlQueries, stroke, fill} = this.props;7980 const nrqlQueryPropsAvailable =81 nrqlQueries &&82 nrqlQueries[0] &&83 nrqlQueries[0].accountId &&84 nrqlQueries[0].query;8586 if (!nrqlQueryPropsAvailable) {87 return <EmptyState />;88 }8990 return (91 <AutoSizer>92 {({width, height}) => (93 <NrqlQuery94 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 }102103 if (error) {104 return <ErrorState />;105 }106107 const transformedData = this.transformData(data);108109 return (110 <RadarChart111 width={width}112 height={height}113 data={transformedData}114 >115 <PolarGrid />116 <PolarAngleAxis dataKey="name" />117 <PolarRadiusAxis tickFormatter={this.formatTick} />118 <Radar119 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}133134135const EmptyState = () => (136 <Card className="EmptyState">137 <CardBody className="EmptyState-cardBody">138 <HeadingText139 spacingType={[HeadingText.SPACING_TYPE.LARGE]}140 type={HeadingText.TYPE.HEADING_3}141 >142 Please provide at least one NRQL query & account ID pair143 </HeadingText>144 <HeadingText145 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);154const ErrorState = () => (155 <Card className="ErrorState">156 <CardBody className="ErrorState-cardBody">157 <HeadingText158 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);
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:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9} from 'recharts';10import {11 Card,12 CardBody,13 HeadingText,14 NrqlQuery,15 SegmentedControl,16 SegmentedControlItem,17 Spinner,18 AutoSizer,19} from 'nr1';20const CHART_TYPES = {21 Radar: 'radar',22 Treemap: 'treemap',23};24export default class YourAwesomeVisualization extends React.Component {25 // Custom props you wish to be configurable in the UI must also be defined in26 // 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 of30 * a custom chart configuration.31 */32 fill: PropTypes.string,33 /**34 * A stroke color to override the default stroke color. This is an example of35 * 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 a54 * 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 <NrqlQuery84 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 <SegmentedControl99 onChange={(event, value) => console.log(value)}100 >101 <SegmentedControlItem102 value={CHART_TYPES.Radar}103 label="Radar chart"104 />105 <SegmentedControlItem106 value={CHART_TYPES.Treemap}107 label="Treemap chart"108 />109 </SegmentedControl>110 <RadarChart111 width={width}112 height={height}113 data={transformedData}114 >115 <PolarGrid />116 <PolarAngleAxis dataKey="name" />117 <PolarRadiusAxis tickFormatter={this.formatTick} />118 <Radar119 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}134135const EmptyState = () => (136 <Card className="EmptyState">137 <CardBody className="EmptyState-cardBody">138 <HeadingText139 spacingType={[HeadingText.SPACING_TYPE.LARGE]}140 type={HeadingText.TYPE.HEADING_3}141 >142 Please provide at least one NRQL query & account ID pair143 </HeadingText>144 <HeadingText145 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);154const ErrorState = () => (155 <Card className="ErrorState">156 <CardBody className="ErrorState-cardBody">157 <HeadingText158 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);
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:

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.
Add a component method that updates state to your visualization class:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9} from 'recharts';10import {11 Card,12 CardBody,13 HeadingText,14 NrqlQuery,15 SegmentedControl,16 SegmentedControlItem,17 Spinner,18 AutoSizer19} from 'nr1';20const CHART_TYPES = {21 'Radar': 'radar',22 'Treemap': 'treemap'23}24export default class YourAwesomeVisualization extends React.Component {25 // Custom props you wish to be configurable in the UI must also be defined in26 // 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 of30 * a custom chart configuration.31 */32 fill: PropTypes.string,33 /**34 * A stroke color to override the default stroke color. This is an example of35 * 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.Radar51 }52 /**53 * Restructure the data for a non-time-series, facet-based NRQL query into a54 * 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 };7071 updateSelectedChart = (evt, value) => {72 this.setState({ selectedChart: value })73 };7475 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 <NrqlQuery89 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 <SegmentedControl104 onChange={(event, value) => console.log(value)}105 >106 <SegmentedControlItem107 value={CHART_TYPES.Radar}108 label="Radar chart"109 />110 <SegmentedControlItem111 value={CHART_TYPES.Treemap}112 label="Treemap chart"113 />114 </SegmentedControl>115 <RadarChart116 width={width}117 height={height}118 data={transformedData}119 >120 <PolarGrid />121 <PolarAngleAxis dataKey="name" />122 <PolarRadiusAxis tickFormatter={this.formatTick} />123 <Radar124 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}139140141const EmptyState = () => (142 <Card className="EmptyState">143 <CardBody className="EmptyState-cardBody">144 <HeadingText145 spacingType={[HeadingText.SPACING_TYPE.LARGE]}146 type={HeadingText.TYPE.HEADING_3}147 >148 Please provide at least one NRQL query & account ID pair149 </HeadingText>150 <HeadingText151 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);160const ErrorState = () => (161 <Card className="ErrorState">162 <CardBody className="ErrorState-cardBody">163 <HeadingText164 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);
Pass updateSelectedChart
as the SegmentedControl
component's onChange prop:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9} from 'recharts';10import {11 Card,12 CardBody,13 HeadingText,14 NrqlQuery,15 SegmentedControl,16 SegmentedControlItem,17 Spinner,18 AutoSizer,19} from 'nr1';20const CHART_TYPES = {21 Radar: 'radar',22 Treemap: 'treemap',23};24export default class YourAwesomeVisualization extends React.Component {25 // Custom props you wish to be configurable in the UI must also be defined in26 // 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 of30 * a custom chart configuration.31 */32 fill: PropTypes.string,33 /**34 * A stroke color to override the default stroke color. This is an example of35 * 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 a54 * 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 };7071 updateSelectedChart = (evt, value) => {72 this.setState({ selectedChart: value });73 };7475 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 <NrqlQuery89 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 <SegmentedControlItem105 value={CHART_TYPES.Radar}106 label="Radar chart"107 />108 <SegmentedControlItem109 value={CHART_TYPES.Treemap}110 label="Treemap chart"111 />112 </SegmentedControl>113 <RadarChart114 width={width}115 height={height}116 data={transformedData}117 >118 <PolarGrid />119 <PolarAngleAxis dataKey="name" />120 <PolarRadiusAxis tickFormatter={this.formatTick} />121 <Radar122 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}137138const EmptyState = () => (139 <Card className="EmptyState">140 <CardBody className="EmptyState-cardBody">141 <HeadingText142 spacingType={[HeadingText.SPACING_TYPE.LARGE]}143 type={HeadingText.TYPE.HEADING_3}144 >145 Please provide at least one NRQL query & account ID pair146 </HeadingText>147 <HeadingText148 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);157const ErrorState = () => (158 <Card className="ErrorState">159 <CardBody className="ErrorState-cardBody">160 <HeadingText161 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);
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.Add Treemap
to the import from recharts:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9 Treemap,10} from 'recharts';11import {12 Card,13 CardBody,14 HeadingText,15 NrqlQuery,16 SegmentedControl,17 SegmentedControlItem,18 Spinner,19 AutoSizer,20} from 'nr1';21const CHART_TYPES = {22 Radar: 'radar',23 Treemap: 'treemap',24};25export default class YourAwesomeVisualization extends React.Component {26 // Custom props you wish to be configurable in the UI must also be defined in27 // 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 of31 * a custom chart configuration.32 */33 fill: PropTypes.string,34 /**35 * A stroke color to override the default stroke color. This is an example of36 * 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 a55 * 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 };7172 updateSelectedChart = (evt, value) => {73 this.setState({ selectedChart: value });74 };7576 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 <NrqlQuery90 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 <SegmentedControlItem106 value={CHART_TYPES.Radar}107 label="Radar chart"108 />109 <SegmentedControlItem110 value={CHART_TYPES.Treemap}111 label="Treemap chart"112 />113 </SegmentedControl>114 <RadarChart115 width={width}116 height={height}117 data={transformedData}118 >119 <PolarGrid />120 <PolarAngleAxis dataKey="name" />121 <PolarRadiusAxis tickFormatter={this.formatTick} />122 <Radar123 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}138139const EmptyState = () => (140 <Card className="EmptyState">141 <CardBody className="EmptyState-cardBody">142 <HeadingText143 spacingType={[HeadingText.SPACING_TYPE.LARGE]}144 type={HeadingText.TYPE.HEADING_3}145 >146 Please provide at least one NRQL query & account ID pair147 </HeadingText>148 <HeadingText149 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);158const ErrorState = () => (159 <Card className="ErrorState">160 <CardBody className="ErrorState-cardBody">161 <HeadingText162 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);
Further reading
To learn more about the Treemap chart component, go to the Recharts Treemap example or the Recharts Treemap API docs.Under the RadarChart
closing tag in render, add the Treemap
component and the necessary props:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9} from 'recharts';10import {11 Card,12 CardBody,13 HeadingText,14 NrqlQuery,15 SegmentedControl,16 SegmentedControlItem,17 Spinner,18 AutoSizer,19} from 'nr1';20const CHART_TYPES = {21 Radar: 'radar',22 Treemap: 'treemap',23};24export default class YourAwesomeVisualization extends React.Component {25 // Custom props you wish to be configurable in the UI must also be defined in26 // 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 of30 * a custom chart configuration.31 */32 fill: PropTypes.string,33 /**34 * A stroke color to override the default stroke color. This is an example of35 * 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 a54 * 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 };7071 updateSelectedChart = (evt, value) => {72 this.setState({ selectedChart: value });73 };7475 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 <NrqlQuery89 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 <SegmentedControlItem105 value={CHART_TYPES.Radar}106 label="Radar chart"107 />108 <SegmentedControlItem109 value={CHART_TYPES.Treemap}110 label="Treemap chart"111 />112 </SegmentedControl>113 <RadarChart114 width={width}115 height={height}116 data={transformedData}117 >118 <PolarGrid />119 <PolarAngleAxis dataKey="name" />120 <PolarRadiusAxis tickFormatter={this.formatTick} />121 <Radar122 dataKey="value"123 stroke={stroke || '#51C9B7'}124 fill={fill || '#51C9B7'}125 fillOpacity={0.6}126 />127 </RadarChart>128 <Treemap129 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}146147const EmptyState = () => (148 <Card className="EmptyState">149 <CardBody className="EmptyState-cardBody">150 <HeadingText151 spacingType={[HeadingText.SPACING_TYPE.LARGE]}152 type={HeadingText.TYPE.HEADING_3}153 >154 Please provide at least one NRQL query & account ID pair155 </HeadingText>156 <HeadingText157 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);166const ErrorState = () => (167 <Card className="ErrorState">168 <CardBody className="ErrorState-cardBody">169 <HeadingText170 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);
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:

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.
Destructure selectedChart
from state near the top of the render()
function:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9} from 'recharts';10import {11 Card,12 CardBody,13 HeadingText,14 NrqlQuery,15 SegmentedControl,16 SegmentedControlItem,17 Spinner,18 AutoSizer,19} from 'nr1';20const CHART_TYPES = {21 Radar: 'radar',22 Treemap: 'treemap',23};24export default class YourAwesomeVisualization extends React.Component {25 // Custom props you wish to be configurable in the UI must also be defined in26 // 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 of30 * a custom chart configuration.31 */32 fill: PropTypes.string,33 /**34 * A stroke color to override the default stroke color. This is an example of35 * 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 a54 * 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 };7071 updateSelectedChart = (evt, value) => {72 this.setState({ selectedChart: value });73 };7475 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 <NrqlQuery90 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 <SegmentedControlItem106 value={CHART_TYPES.Radar}107 label="Radar chart"108 />109 <SegmentedControlItem110 value={CHART_TYPES.Treemap}111 label="Treemap chart"112 />113 </SegmentedControl>114 <RadarChart115 width={width}116 height={height}117 data={transformedData}118 >119 <PolarGrid />120 <PolarAngleAxis dataKey="name" />121 <PolarRadiusAxis tickFormatter={this.formatTick} />122 <Radar123 dataKey="value"124 stroke={stroke || '#51C9B7'}125 fill={fill || '#51C9B7'}126 fillOpacity={0.6}127 />128 </RadarChart>129 <Treemap130 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}147148const EmptyState = () => (149 <Card className="EmptyState">150 <CardBody className="EmptyState-cardBody">151 <HeadingText152 spacingType={[HeadingText.SPACING_TYPE.LARGE]}153 type={HeadingText.TYPE.HEADING_3}154 >155 Please provide at least one NRQL query & account ID pair156 </HeadingText>157 <HeadingText158 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);167const ErrorState = () => (168 <Card className="ErrorState">169 <CardBody className="ErrorState-cardBody">170 <HeadingText171 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);
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:
1import React from 'react';2import PropTypes from 'prop-types';3import {4 Radar,5 RadarChart,6 PolarGrid,7 PolarAngleAxis,8 PolarRadiusAxis,9} from 'recharts';10import {11 Card,12 CardBody,13 HeadingText,14 NrqlQuery,15 SegmentedControl,16 SegmentedControlItem,17 Spinner,18 AutoSizer,19} from 'nr1';20const CHART_TYPES = {21 Radar: 'radar',22 Treemap: 'treemap',23};24export default class YourAwesomeVisualization extends React.Component {25 // Custom props you wish to be configurable in the UI must also be defined in26 // 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 of30 * a custom chart configuration.31 */32 fill: PropTypes.string,33 /**34 * A stroke color to override the default stroke color. This is an example of35 * 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 a54 * 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 };7071 updateSelectedChart = (evt, value) => {72 this.setState({ selectedChart: value });73 };7475 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 <NrqlQuery90 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 <SegmentedControlItem106 value={CHART_TYPES.Radar}107 label="Radar chart"108 />109 <SegmentedControlItem110 value={CHART_TYPES.Treemap}111 label="Treemap chart"112 />113 </SegmentedControl>114 {selectedChart === CHART_TYPES.Radar ? (115 <RadarChart116 width={width}117 height={height}118 data={transformedData}119 >120 <PolarGrid />121 <PolarAngleAxis dataKey="name" />122 <PolarRadiusAxis tickFormatter={this.formatTick} />123 <Radar124 dataKey="value"125 stroke={stroke || '#51C9B7'}126 fill={fill || '#51C9B7'}127 fillOpacity={0.6}128 />129 </RadarChart>130 ) : (131 <Treemap132 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}150151const EmptyState = () => (152 <Card className="EmptyState">153 <CardBody className="EmptyState-cardBody">154 <HeadingText155 spacingType={[HeadingText.SPACING_TYPE.LARGE]}156 type={HeadingText.TYPE.HEADING_3}157 >158 Please provide at least one NRQL query & account ID pair159 </HeadingText>160 <HeadingText161 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);170const ErrorState = () => (171 <Card className="ErrorState">172 <CardBody className="ErrorState-cardBody">173 <HeadingText174 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);
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:

Radar chart selected

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
- New Relic Quick Tips video: Dashboards and Custom Visualizations (6 minutes)
- New Relic NerdBytes video: Configuring custom visualizations for dashboards (7 minutes)
- New Relic Nerdlog live stream: Custom Data Visualizations on New Relic (30 minutes)
- New Relic One SDK components: Intro to New Relic One SDK Component library