This guide builds off of the previous Custom visualizations and the New Relic One SDK guide. If you haven't followed that guide, please start there as this guide assumes you have the code you built there to get started.
Following the previous guide, your current visualization should be able to switch between two charts types. Your current implementation takes up some space in the visualization but offers your users the choice to switch between two chart types at any point in the visualization lifecycle. What if you only need to be able to select an option once, when adding it to the dashboard? In that case, you can use the configuration
key in your visualization's nr1.json file. In the following steps, you'll replace the SegmentedControl
component with an option in the configuration
array.
Before you begin
To get started, make sure you follow the Custom visualizations and the New Relic One SDK guide. You should have that code as your starting point for this guide.
Replace the SegmentedControl with configuration
In you visualization's root folder, open the nr1.json
file. Add an enum
configuration object for selectedChart
:
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 * A chart type configuration to switch between "Radar" and "Treemap" chart types51 */52 selectedChart: PropTypes.string,53 };54 state = {55 selectedChart: CHART_TYPES.Radar,56 };57 /**58 * Restructure the data for a non-time-series, facet-based NRQL query into 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 };69 /**70 * Format the given axis tick's numeric value into a string for display.71 */72 formatTick = (value) => {73 return value.toLocaleString();74 };7576 updateSelectedChart = (evt, value) => {77 this.setState({ selectedChart: value });78 };7980 render() {81 const { nrqlQueries, stroke, fill } = this.props;82 const { selectedChart } = this.state;83 const nrqlQueryPropsAvailable =84 nrqlQueries &&85 nrqlQueries[0] &&86 nrqlQueries[0].accountId &&87 nrqlQueries[0].query;88 if (!nrqlQueryPropsAvailable) {89 return <EmptyState />;90 }91 return (92 <AutoSizer>93 {({ width, height }) => (94 <NrqlQuery95 query={nrqlQueries[0].query}96 accountId={parseInt(nrqlQueries[0].accountId)}97 pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}98 >99 {({ data, loading, error }) => {100 if (loading) {101 return <Spinner />;102 }103 if (error) {104 return <ErrorState />;105 }106 const transformedData = this.transformData(data);107 return (108 <React.Fragment>109 <SegmentedControl onChange={this.updateSelectedChart}>110 <SegmentedControlItem111 value={CHART_TYPES.Radar}112 label="Radar chart"113 />114 <SegmentedControlItem115 value={CHART_TYPES.Treemap}116 label="Treemap chart"117 />118 </SegmentedControl>119 {selectedChart === CHART_TYPES.Radar ? (120 <RadarChart121 width={width}122 height={height}123 data={transformedData}124 >125 <PolarGrid />126 <PolarAngleAxis dataKey="name" />127 <PolarRadiusAxis tickFormatter={this.formatTick} />128 <Radar129 dataKey="value"130 stroke={stroke || '#51C9B7'}131 fill={fill || '#51C9B7'}132 fillOpacity={0.6}133 />134 </RadarChart>135 ) : (136 <Treemap137 width={width}138 height={height}139 data={transformedData}140 dataKey="value"141 ratio={4 / 3}142 stroke="#fff"143 fill="#8884d8"144 />145 )}146 </React.Fragment>147 );148 }}149 </NrqlQuery>150 )}151 </AutoSizer>152 );153 }154}155156const EmptyState = () => (157 <Card className="EmptyState">158 <CardBody className="EmptyState-cardBody">159 <HeadingText160 spacingType={[HeadingText.SPACING_TYPE.LARGE]}161 type={HeadingText.TYPE.HEADING_3}162 >163 Please provide at least one NRQL query & account ID pair164 </HeadingText>165 <HeadingText166 spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}167 type={HeadingText.TYPE.HEADING_4}168 >169 An example NRQL query you can try is:170 </HeadingText>171 <code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>172 </CardBody>173 </Card>174);175const ErrorState = () => (176 <Card className="ErrorState">177 <CardBody className="ErrorState-cardBody">178 <HeadingText179 className="ErrorState-headingText"180 spacingType={[HeadingText.SPACING_TYPE.LARGE]}181 type={HeadingText.TYPE.HEADING_3}182 >183 Oops! Something went wrong.184 </HeadingText>185 </CardBody>186 </Card>187);
1{2 "schemaType": "VISUALIZATION",3 "id": "your-awesome-visualization",4 "displayName": "Your Awesome Visualization",5 "description": "",6 "configuration": [7 {8 "name": "selectedChart",9 "title": "Select chart",10 "description": "Select which chart to display",11 "type": "enum",12 "items": [13 {14 "title": "Radar",15 "value": "radar"16 },17 {18 "title": "Treemap",19 "value": "treemap"20 }21 ]22 },23 {24 "name": "nrqlQueries",25 "title": "NRQL Queries",26 "type": "collection",27 "items": [28 {29 "name": "accountId",30 "title": "Account ID",31 "description": "Account ID to be associated with the query",32 "type": "number"33 },34 {35 "name": "query",36 "title": "Query",37 "description": "NRQL query for visualization",38 "type": "nrql"39 }40 ]41 },42 {43 "name": "fill",44 "title": "Fill color",45 "description": "A fill color to override the default fill color",46 "type": "string"47 },48 {49 "name": "stroke",50 "title": "Stroke color",51 "description": "A stroke color to override the default stroke color",52 "type": "string"53 }54 ]55}
This should look familiar to the component state options you added in the previous guide.
In your visualization's root folder, open index.js
. You'll be working in this file for the rest of the guide:
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 * A chart type configuration to switch between "Radar" and "Treemap" chart types51 */52 selectedChart: PropTypes.string,53 };54 state = {55 selectedChart: CHART_TYPES.Radar,56 };57 /**58 * Restructure the data for a non-time-series, facet-based NRQL query into 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 };69 /**70 * Format the given axis tick's numeric value into a string for display.71 */72 formatTick = (value) => {73 return value.toLocaleString();74 };7576 updateSelectedChart = (evt, value) => {77 this.setState({ selectedChart: value });78 };7980 render() {81 const { nrqlQueries, stroke, fill } = this.props;82 const { selectedChart } = this.state;83 const nrqlQueryPropsAvailable =84 nrqlQueries &&85 nrqlQueries[0] &&86 nrqlQueries[0].accountId &&87 nrqlQueries[0].query;88 if (!nrqlQueryPropsAvailable) {89 return <EmptyState />;90 }91 return (92 <AutoSizer>93 {({ width, height }) => (94 <NrqlQuery95 query={nrqlQueries[0].query}96 accountId={parseInt(nrqlQueries[0].accountId)}97 pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}98 >99 {({ data, loading, error }) => {100 if (loading) {101 return <Spinner />;102 }103 if (error) {104 return <ErrorState />;105 }106 const transformedData = this.transformData(data);107 return (108 <React.Fragment>109 <SegmentedControl onChange={this.updateSelectedChart}>110 <SegmentedControlItem111 value={CHART_TYPES.Radar}112 label="Radar chart"113 />114 <SegmentedControlItem115 value={CHART_TYPES.Treemap}116 label="Treemap chart"117 />118 </SegmentedControl>119 {selectedChart === CHART_TYPES.Radar ? (120 <RadarChart121 width={width}122 height={height}123 data={transformedData}124 >125 <PolarGrid />126 <PolarAngleAxis dataKey="name" />127 <PolarRadiusAxis tickFormatter={this.formatTick} />128 <Radar129 dataKey="value"130 stroke={stroke || '#51C9B7'}131 fill={fill || '#51C9B7'}132 fillOpacity={0.6}133 />134 </RadarChart>135 ) : (136 <Treemap137 width={width}138 height={height}139 data={transformedData}140 dataKey="value"141 ratio={4 / 3}142 stroke="#fff"143 fill="#8884d8"144 />145 )}146 </React.Fragment>147 );148 }}149 </NrqlQuery>150 )}151 </AutoSizer>152 );153 }154}155156const EmptyState = () => (157 <Card className="EmptyState">158 <CardBody className="EmptyState-cardBody">159 <HeadingText160 spacingType={[HeadingText.SPACING_TYPE.LARGE]}161 type={HeadingText.TYPE.HEADING_3}162 >163 Please provide at least one NRQL query & account ID pair164 </HeadingText>165 <HeadingText166 spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}167 type={HeadingText.TYPE.HEADING_4}168 >169 An example NRQL query you can try is:170 </HeadingText>171 <code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>172 </CardBody>173 </Card>174);175const ErrorState = () => (176 <Card className="ErrorState">177 <CardBody className="ErrorState-cardBody">178 <HeadingText179 className="ErrorState-headingText"180 spacingType={[HeadingText.SPACING_TYPE.LARGE]}181 type={HeadingText.TYPE.HEADING_3}182 >183 Oops! Something went wrong.184 </HeadingText>185 </CardBody>186 </Card>187);
1{2 "schemaType": "VISUALIZATION",3 "id": "your-awesome-visualization",4 "displayName": "Your Awesome Visualization",5 "description": "",6 "configuration": [7 {8 "name": "selectedChart",9 "title": "Select chart",10 "description": "Select which chart to display",11 "type": "enum",12 "items": [13 {14 "title": "Radar",15 "value": "radar"16 },17 {18 "title": "Treemap",19 "value": "treemap"20 }21 ]22 },23 {24 "name": "nrqlQueries",25 "title": "NRQL Queries",26 "type": "collection",27 "items": [28 {29 "name": "accountId",30 "title": "Account ID",31 "description": "Account ID to be associated with the query",32 "type": "number"33 },34 {35 "name": "query",36 "title": "Query",37 "description": "NRQL query for visualization",38 "type": "nrql"39 }40 ]41 },42 {43 "name": "fill",44 "title": "Fill color",45 "description": "A fill color to override the default fill color",46 "type": "string"47 },48 {49 "name": "stroke",50 "title": "Stroke color",51 "description": "A stroke color to override the default stroke color",52 "type": "string"53 }54 ]55}
In render()
, add selectedChart
to the prop descrtructuring and remove the state reference:
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 * A chart type configuration to switch between "Radar" and "Treemap" chart types51 */52 selectedChart: PropTypes.string,53 };54 state = {55 selectedChart: CHART_TYPES.Radar,56 };57 /**58 * Restructure the data for a non-time-series, facet-based NRQL query into 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 };69 /**70 * Format the given axis tick's numeric value into a string for display.71 */72 formatTick = (value) => {73 return value.toLocaleString();74 };7576 updateSelectedChart = (evt, value) => {77 this.setState({ selectedChart: value });78 };7980 render() {81 const { nrqlQueries, stroke, fill, selectedChart } = this.props;82 // const { selectedChart } = this.state;83 const nrqlQueryPropsAvailable =84 nrqlQueries &&85 nrqlQueries[0] &&86 nrqlQueries[0].accountId &&87 nrqlQueries[0].query;88 if (!nrqlQueryPropsAvailable) {89 return <EmptyState />;90 }91 return (92 <AutoSizer>93 {({ width, height }) => (94 <NrqlQuery95 query={nrqlQueries[0].query}96 accountId={parseInt(nrqlQueries[0].accountId)}97 pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}98 >99 {({ data, loading, error }) => {100 if (loading) {101 return <Spinner />;102 }103 if (error) {104 return <ErrorState />;105 }106 const transformedData = this.transformData(data);107 return (108 <React.Fragment>109 <SegmentedControl onChange={this.updateSelectedChart}>110 <SegmentedControlItem111 value={CHART_TYPES.Radar}112 label="Radar chart"113 />114 <SegmentedControlItem115 value={CHART_TYPES.Treemap}116 label="Treemap chart"117 />118 </SegmentedControl>119 {selectedChart === CHART_TYPES.Radar ? (120 <RadarChart121 width={width}122 height={height}123 data={transformedData}124 >125 <PolarGrid />126 <PolarAngleAxis dataKey="name" />127 <PolarRadiusAxis tickFormatter={this.formatTick} />128 <Radar129 dataKey="value"130 stroke={stroke || '#51C9B7'}131 fill={fill || '#51C9B7'}132 fillOpacity={0.6}133 />134 </RadarChart>135 ) : (136 <Treemap137 width={width}138 height={height}139 data={transformedData}140 dataKey="value"141 ratio={4 / 3}142 stroke="#fff"143 fill="#8884d8"144 />145 )}146 </React.Fragment>147 );148 }}149 </NrqlQuery>150 )}151 </AutoSizer>152 );153 }154}155156const EmptyState = () => (157 <Card className="EmptyState">158 <CardBody className="EmptyState-cardBody">159 <HeadingText160 spacingType={[HeadingText.SPACING_TYPE.LARGE]}161 type={HeadingText.TYPE.HEADING_3}162 >163 Please provide at least one NRQL query & account ID pair164 </HeadingText>165 <HeadingText166 spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}167 type={HeadingText.TYPE.HEADING_4}168 >169 An example NRQL query you can try is:170 </HeadingText>171 <code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>172 </CardBody>173 </Card>174);175const ErrorState = () => (176 <Card className="ErrorState">177 <CardBody className="ErrorState-cardBody">178 <HeadingText179 className="ErrorState-headingText"180 spacingType={[HeadingText.SPACING_TYPE.LARGE]}181 type={HeadingText.TYPE.HEADING_3}182 >183 Oops! Something went wrong.184 </HeadingText>185 </CardBody>186 </Card>187);
1{2 "schemaType": "VISUALIZATION",3 "id": "your-awesome-visualization",4 "displayName": "Your Awesome Visualization",5 "description": "",6 "configuration": [7 {8 "name": "selectedChart",9 "title": "Select chart",10 "description": "Select which chart to display",11 "type": "enum",12 "items": [13 {14 "title": "Radar",15 "value": "radar"16 },17 {18 "title": "Treemap",19 "value": "treemap"20 }21 ]22 },23 {24 "name": "nrqlQueries",25 "title": "NRQL Queries",26 "type": "collection",27 "items": [28 {29 "name": "accountId",30 "title": "Account ID",31 "description": "Account ID to be associated with the query",32 "type": "number"33 },34 {35 "name": "query",36 "title": "Query",37 "description": "NRQL query for visualization",38 "type": "nrql"39 }40 ]41 },42 {43 "name": "fill",44 "title": "Fill color",45 "description": "A fill color to override the default fill color",46 "type": "string"47 },48 {49 "name": "stroke",50 "title": "Stroke color",51 "description": "A stroke color to override the default stroke color",52 "type": "string"53 }54 ]55}
You no longer have a default chart defined at this point, but you still want the Radar chart to be the default. Add more to the ternary in the render to achieve the same default state as you had previously:
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 * A chart type configuration to switch between "Radar" and "Treemap" chart types51 */52 selectedChart: PropTypes.string,53 };54 state = {55 selectedChart: CHART_TYPES.Radar,56 };57 /**58 * Restructure the data for a non-time-series, facet-based NRQL query into 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 };69 /**70 * Format the given axis tick's numeric value into a string for display.71 */72 formatTick = (value) => {73 return value.toLocaleString();74 };7576 updateSelectedChart = (evt, value) => {77 this.setState({ selectedChart: value });78 };7980 render() {81 const { nrqlQueries, stroke, fill, selectedChart } = this.props;82 // const { selectedChart } = this.state;83 const nrqlQueryPropsAvailable =84 nrqlQueries &&85 nrqlQueries[0] &&86 nrqlQueries[0].accountId &&87 nrqlQueries[0].query;88 if (!nrqlQueryPropsAvailable) {89 return <EmptyState />;90 }91 return (92 <AutoSizer>93 {({ width, height }) => (94 <NrqlQuery95 query={nrqlQueries[0].query}96 accountId={parseInt(nrqlQueries[0].accountId)}97 pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}98 >99 {({ data, loading, error }) => {100 if (loading) {101 return <Spinner />;102 }103 if (error) {104 return <ErrorState />;105 }106 const transformedData = this.transformData(data);107 return (108 <React.Fragment>109 <SegmentedControl onChange={this.updateSelectedChart}>110 <SegmentedControlItem111 value={CHART_TYPES.Radar}112 label="Radar chart"113 />114 <SegmentedControlItem115 value={CHART_TYPES.Treemap}116 label="Treemap chart"117 />118 </SegmentedControl>119 {selectedChart === CHART_TYPES.Radar ||120 !Boolean(selectedChart) ? (121 <RadarChart122 width={width}123 height={height}124 data={transformedData}125 >126 <PolarGrid />127 <PolarAngleAxis dataKey="name" />128 <PolarRadiusAxis tickFormatter={this.formatTick} />129 <Radar130 dataKey="value"131 stroke={stroke || '#51C9B7'}132 fill={fill || '#51C9B7'}133 fillOpacity={0.6}134 />135 </RadarChart>136 ) : (137 <Treemap138 width={width}139 height={height}140 data={transformedData}141 dataKey="value"142 ratio={4 / 3}143 stroke="#fff"144 fill="#8884d8"145 />146 )}147 </React.Fragment>148 );149 }}150 </NrqlQuery>151 )}152 </AutoSizer>153 );154 }155}156157const EmptyState = () => (158 <Card className="EmptyState">159 <CardBody className="EmptyState-cardBody">160 <HeadingText161 spacingType={[HeadingText.SPACING_TYPE.LARGE]}162 type={HeadingText.TYPE.HEADING_3}163 >164 Please provide at least one NRQL query & account ID pair165 </HeadingText>166 <HeadingText167 spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}168 type={HeadingText.TYPE.HEADING_4}169 >170 An example NRQL query you can try is:171 </HeadingText>172 <code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>173 </CardBody>174 </Card>175);176const ErrorState = () => (177 <Card className="ErrorState">178 <CardBody className="ErrorState-cardBody">179 <HeadingText180 className="ErrorState-headingText"181 spacingType={[HeadingText.SPACING_TYPE.LARGE]}182 type={HeadingText.TYPE.HEADING_3}183 >184 Oops! Something went wrong.185 </HeadingText>186 </CardBody>187 </Card>188);
1{2 "schemaType": "VISUALIZATION",3 "id": "your-awesome-visualization",4 "displayName": "Your Awesome Visualization",5 "description": "",6 "configuration": [7 {8 "name": "selectedChart",9 "title": "Select chart",10 "description": "Select which chart to display",11 "type": "enum",12 "items": [13 {14 "title": "Radar",15 "value": "radar"16 },17 {18 "title": "Treemap",19 "value": "treemap"20 }21 ]22 },23 {24 "name": "nrqlQueries",25 "title": "NRQL Queries",26 "type": "collection",27 "items": [28 {29 "name": "accountId",30 "title": "Account ID",31 "description": "Account ID to be associated with the query",32 "type": "number"33 },34 {35 "name": "query",36 "title": "Query",37 "description": "NRQL query for visualization",38 "type": "nrql"39 }40 ]41 },42 {43 "name": "fill",44 "title": "Fill color",45 "description": "A fill color to override the default fill color",46 "type": "string"47 },48 {49 "name": "stroke",50 "title": "Stroke color",51 "description": "A stroke color to override the default stroke color",52 "type": "string"53 }54 ]55}
You no longer need SegmentedControl
, so remove it from render()
:
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 * A chart type configuration to switch between "Radar" and "Treemap" chart types51 */52 selectedChart: PropTypes.string,53 };54 state = {55 selectedChart: CHART_TYPES.Radar,56 };57 /**58 * Restructure the data for a non-time-series, facet-based NRQL query into 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 };69 /**70 * Format the given axis tick's numeric value into a string for display.71 */72 formatTick = (value) => {73 return value.toLocaleString();74 };7576 updateSelectedChart = (evt, value) => {77 this.setState({ selectedChart: value });78 };7980 render() {81 const { nrqlQueries, stroke, fill, selectedChart } = this.props;82 // const { selectedChart } = this.state;83 const nrqlQueryPropsAvailable =84 nrqlQueries &&85 nrqlQueries[0] &&86 nrqlQueries[0].accountId &&87 nrqlQueries[0].query;88 if (!nrqlQueryPropsAvailable) {89 return <EmptyState />;90 }91 return (92 <AutoSizer>93 {({ width, height }) => (94 <NrqlQuery95 query={nrqlQueries[0].query}96 accountId={parseInt(nrqlQueries[0].accountId)}97 pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}98 >99 {({ data, loading, error }) => {100 if (loading) {101 return <Spinner />;102 }103 if (error) {104 return <ErrorState />;105 }106 const transformedData = this.transformData(data);107 return (108 <React.Fragment>109 /* CAN REMOVE110 <SegmentedControl onChange={this.updateSelectedChart}>111 <SegmentedControlItem112 value={CHART_TYPES.Radar}113 label="Radar chart"114 />115 <SegmentedControlItem116 value={CHART_TYPES.Treemap}117 label="Treemap chart"118 />119 </SegmentedControl>120 */121 {selectedChart === CHART_TYPES.Radar ||122 !Boolean(selectedChart) ? (123 <RadarChart124 width={width}125 height={height}126 data={transformedData}127 >128 <PolarGrid />129 <PolarAngleAxis dataKey="name" />130 <PolarRadiusAxis tickFormatter={this.formatTick} />131 <Radar132 dataKey="value"133 stroke={stroke || '#51C9B7'}134 fill={fill || '#51C9B7'}135 fillOpacity={0.6}136 />137 </RadarChart>138 ) : (139 <Treemap140 width={width}141 height={height}142 data={transformedData}143 dataKey="value"144 ratio={4 / 3}145 stroke="#fff"146 fill="#8884d8"147 />148 )}149 </React.Fragment>150 );151 }}152 </NrqlQuery>153 )}154 </AutoSizer>155 );156 }157}158159const EmptyState = () => (160 <Card className="EmptyState">161 <CardBody className="EmptyState-cardBody">162 <HeadingText163 spacingType={[HeadingText.SPACING_TYPE.LARGE]}164 type={HeadingText.TYPE.HEADING_3}165 >166 Please provide at least one NRQL query & account ID pair167 </HeadingText>168 <HeadingText169 spacingType={[HeadingText.SPACING_TYPE.MEDIUM]}170 type={HeadingText.TYPE.HEADING_4}171 >172 An example NRQL query you can try is:173 </HeadingText>174 <code>FROM NrUsage SELECT sum(usage) FACET metric SINCE 1 week ago</code>175 </CardBody>176 </Card>177);178const ErrorState = () => (179 <Card className="ErrorState">180 <CardBody className="ErrorState-cardBody">181 <HeadingText182 className="ErrorState-headingText"183 spacingType={[HeadingText.SPACING_TYPE.LARGE]}184 type={HeadingText.TYPE.HEADING_3}185 >186 Oops! Something went wrong.187 </HeadingText>188 </CardBody>189 </Card>190);
1{2 "schemaType": "VISUALIZATION",3 "id": "your-awesome-visualization",4 "displayName": "Your Awesome Visualization",5 "description": "",6 "configuration": [7 {8 "name": "selectedChart",9 "title": "Select chart",10 "description": "Select which chart to display",11 "type": "enum",12 "items": [13 {14 "title": "Radar",15 "value": "radar"16 },17 {18 "title": "Treemap",19 "value": "treemap"20 }21 ]22 },23 {24 "name": "nrqlQueries",25 "title": "NRQL Queries",26 "type": "collection",27 "items": [28 {29 "name": "accountId",30 "title": "Account ID",31 "description": "Account ID to be associated with the query",32 "type": "number"33 },34 {35 "name": "query",36 "title": "Query",37 "description": "NRQL query for visualization",38 "type": "nrql"39 }40 ]41 },42 {43 "name": "fill",44 "title": "Fill color",45 "description": "A fill color to override the default fill color",46 "type": "string"47 },48 {49 "name": "stroke",50 "title": "Stroke color",51 "description": "A stroke color to override the default stroke color",52 "type": "string"53 }54 ]55}
Delete all of the component state code:
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 * A chart type configuration to switch between "Radar" and "Treemap" chart types51 */52 selectedChart: PropTypes.string,53 };54 /* state = {55 selectedChart: CHART_TYPES.Radar56 } */57 /**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 };69 /**70 * Format the given axis tick's numeric value into a string for display.71 */72 formatTick = (value) => {73 return value.toLocaleString();74 };75 /*76 updateSelectedChart = (evt, value) => {77 this.setState({ selectedChart: value })78 };79 */8081 render() {82 const { nrqlQueries, stroke, fill, selectedChart } = this.props;83 // const { selectedChart } = this.state;84 const nrqlQueryPropsAvailable =85 nrqlQueries &&86 nrqlQueries[0] &&87 nrqlQueries[0].accountId &&88 nrqlQueries[0].query;89 if (!nrqlQueryPropsAvailable) {90 return <EmptyState />;91 }92 return (93 <AutoSizer>94 {({ width, height }) => (95 <NrqlQuery96 query={nrqlQueries[0].query}97 accountId={parseInt(nrqlQueries[0].accountId)}98 pollInterval={NrqlQuery.AUTO_POLL_INTERVAL}99 >100 {({ data, loading, error }) => {101 if (loading) {102 return <Spinner />;103 }104 if (error) {105 return <ErrorState />;106 }107 const transformedData = this.transformData(data);108 return (109 <React.Fragment>110 {selectedChart === CHART_TYPES.Radar ||111 !Boolean(selectedChart) ? (112 <RadarChart113 width={width}114 height={height}115 data={transformedData}116 >117 <PolarGrid />118 <PolarAngleAxis dataKey="name" />119 <PolarRadiusAxis tickFormatter={this.formatTick} />120 <Radar121 dataKey="value"122 stroke={stroke || '#51C9B7'}123 fill={fill || '#51C9B7'}124 fillOpacity={0.6}125 />126 </RadarChart>127 ) : (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 )}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);
1{2 "schemaType": "VISUALIZATION",3 "id": "your-awesome-visualization",4 "displayName": "Your Awesome Visualization",5 "description": "",6 "configuration": [7 {8 "name": "selectedChart",9 "title": "Select chart",10 "description": "Select which chart to display",11 "type": "enum",12 "items": [13 {14 "title": "Radar",15 "value": "radar"16 },17 {18 "title": "Treemap",19 "value": "treemap"20 }21 ]22 },23 {24 "name": "nrqlQueries",25 "title": "NRQL Queries",26 "type": "collection",27 "items": [28 {29 "name": "accountId",30 "title": "Account ID",31 "description": "Account ID to be associated with the query",32 "type": "number"33 },34 {35 "name": "query",36 "title": "Query",37 "description": "NRQL query for visualization",38 "type": "nrql"39 }40 ]41 },42 {43 "name": "fill",44 "title": "Fill color",45 "description": "A fill color to override the default fill color",46 "type": "string"47 },48 {49 "name": "stroke",50 "title": "Stroke color",51 "description": "A stroke color to override the default stroke color",52 "type": "string"53 }54 ]55}
Run your visualization locally, and view it in the Custom Visualizations app in New Relic. Select a chart type from the dropdown in the configuration sidebar, and see your visualization update to show the matching chart type:

Summary
Congratulations on completing the steps in this example! You've learned how to customize your visualization using nr1.json configuration.
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