Following is the code I'm using to return the Plotly graph, What I need to do in here is I need to return a message or different layout when the graph data is empty or there is nothing to show on the graph. use a default content like no data available if no data for charts. How do I do that?
import React, { useEffect } from "react";
import Plot from "react-plotly.js";
import PropTypes from "prop-types";
let dataArray;
let index;
let obj;
function PlotlyStackedChart({ labels, data, xTitle, yTitle, mainTitle, preHeading }) {
useEffect(() => {
dataArray = [];
for (index = 0; index < data.length; index += 1) {
obj = {
x: labels,
y: data[index].data,
type: "bar",
name: data[index].name,
};
dataArray.push(obj);
}
}, [data]);
return (
<>
<Plot
data={dataArray}
layout={{
barmode: "stack",
autosize: true,
title: {
text: preHeading + mainTitle,
y: 0.9,
},
margin: {
t: 225,
},
xaxis: {
// all "layout.xaxis" attributes: #layout-xaxis
title: {
text: xTitle,
font: {
family: "Arial Black",
},
}, // more about "layout.xaxis.title": #layout-xaxis-title
// dtick: 1,
},
yaxis: {
// all "layout.yaxis" attributes: #layout-yaxis
title: {
text: yTitle,
font: {
family: "Arial Black",
},
}, // more about "layout.yaxis.title": #layout-yaxis-title
},
font: {
// family: "Courier New, monospace",
size: 12,
color: "#7f7f7f",
},
legend: {
bgcolor: "transparent",
x: 0,
y: 1.4,
xanchor: "auto",
traceorder: "normal",
orientation: "h",
},
marker: { size: 40 },
}}
useResizeHandler
style={{ width: "100%", height: 600 }}
/>
</>
);
}
export default PlotlyStackedChart;
PlotlyStackedChart.defaultProps = {
xTitle: "",
yTitle: "",
mainTitle: "",
preHeading: "",
};
// Typechecking props for the MDDatePicker
PlotlyStackedChart.propTypes = {
labels: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired,
xTitle: PropTypes.string,
yTitle: PropTypes.string,
mainTitle: PropTypes.string,
preHeading: PropTypes.string,
data: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.object]).isRequired,
};
explaining further, If there is no data to plot in the graph I want to return the following code else the current one which retrieves the data and plot in the graph.
return {
"layout": {
"xaxis": {
"visible": false
},
"yaxis": {
"visible": false
},
"annotations": [
{
"text": "No matching data found",
"xref": "paper",
"yref": "paper",
"showarrow": false,
"font": {
"size": 28
}
}
]
}
}
You can try returning the empty content template if there is no content.
import React, { useEffect } from "react";
import Plot from "react-plotly.js";
import PropTypes from "prop-types";
let dataArray;
let index;
let obj;
function PlotlyStackedChart({ labels, data, xTitle, yTitle, mainTitle, preHeading }) {
useEffect(() => {
if(data?.length){ // <--- place condition here
dataArray = [];
for (index = 0; index < data.length; index += 1) {
obj = {
x: labels,
y: data[index].data,
type: "bar",
name: data[index].name,
};
dataArray.push(obj);
}
}
}, [data]);
// Place this conditional return
if(!data?.length) {
return <>No data found</>
}
return (
<>
<Plot
data={dataArray}
layout={{
barmode: "stack",
autosize: true,
title: {
text: preHeading + mainTitle,
y: 0.9,
},
margin: {
t: 225,
},
xaxis: {
// all "layout.xaxis" attributes: #layout-xaxis
title: {
text: xTitle,
font: {
family: "Arial Black",
},
}, // more about "layout.xaxis.title": #layout-xaxis-title
// dtick: 1,
},
yaxis: {
// all "layout.yaxis" attributes: #layout-yaxis
title: {
text: yTitle,
font: {
family: "Arial Black",
},
}, // more about "layout.yaxis.title": #layout-yaxis-title
},
font: {
// family: "Courier New, monospace",
size: 12,
color: "#7f7f7f",
},
legend: {
bgcolor: "transparent",
x: 0,
y: 1.4,
xanchor: "auto",
traceorder: "normal",
orientation: "h",
},
marker: { size: 40 },
}}
useResizeHandler
style={{ width: "100%", height: 600 }}
/>
</>
);
}
export default PlotlyStackedChart;
useEffect is entirely irrelevant to the problem you describe.
If you want to render something different when the array is empty, then do it in the render logic, not as an effect.
if (data.length === 0) {
return <Loading />
} else {
return <Plot .../>
}
That said, your effect logic is broken. You can't just assign a new array to a variable and have React render it. You've done nothing to tell React that it needs to do a re-render. Make data a state variable (with useState).
Related
I am using the react wrapper for high charts btw.
What I have currently.
What I'm aiming for with but stacked waterfall chart
Just started using high charts and I love it but this one has me stumped. I figured out the data that I need to put that but now I just need to be able to place it in there..
Possible avenues of approach:
Is there a way to add custom HTML only for the top series?
Could I alter the data set to maybe re-render with the top label?
Could I just make the labels show but add an additional label above with the points I wants and make the rest transparent?
import React from "react";
import Highcharts from "highcharts";
import HighchartsReact from "highcharts-react-official";
import HC_more from "highcharts/highcharts-more";
HC_more(Highcharts);
let categories = ["bar 1", "bar 2", "bar 3", "bar 4", "total bar"];
let series = [
{
data: [
20,
10,
-15,
30,
{
isSum: true,
},
],
name: "custom series 1",
},
{
data: [
20,
50,
-25,
10,
{
isSum: true,
},
],
name: "custom series 2",
lineWidth: 0,
},
{
data: [
5,
10,
-5,
10,
{
isSum: true,
},
],
name: "custom series 3",
lineWidth: 0,
},
];
let grandTotal = true;
function _createLabelValuePairs(s, c) {
let collection = {};
//Step 0 - iterate over data to get the series
s.map((dataSet) => {
//since we have total bars. Something that shouldn't be calc into the dataset we still need to figure out how many series it has
//for all the set values
dataSet.data.map((item, idx) => {
if (typeof item == "number") {
if (collection[c[idx]]) {
collection[c[idx]]["values"].push(item);
} else {
collection[c[idx]] = {};
collection[c[idx]]["values"] = [];
collection[c[idx]]["values"].push(item);
}
}
});
});
//Step 1 - Get my totals for each dataset
for (const a in collection) {
collection[a]["barTotal"] = collection[a].values.reduce(
(partialSum, a) => partialSum + a,
0,
);
}
//Step 2 - Get grand total numbers of all datasets.
if (grandTotal) {
//Step 2a - Totals for each bar
let sum = 0;
for (const item in collection) {
sum = sum + collection[item].barTotal;
if (collection[c[c.length - 1]]) {
collection[c[c.length - 1]]["values"].push(collection[item].barTotal);
} else {
collection[c[c.length - 1]] = {};
collection[c[c.length - 1]]["values"] = [];
collection[c[c.length - 1]]["values"].push(collection[item].barTotal);
}
}
//grand total bar will be the last entry in the set you pass in
collection[c[c.length - 1]]["barTotal"] = sum;
//Step 2b - Totals for each series
options.series.map((item) => {
if (collection[c[c.length - 1]]["seriesTotal"]) {
collection[c[c.length - 1]]["seriesTotal"][
item["name"]
] = item.data.reduce((partialSum, a) => {
if (typeof partialSum == "number") {
return Number(partialSum) + a;
}
}, 0);
} else {
collection[c[c.length - 1]]["seriesTotal"] = {};
collection[c[c.length - 1]]["seriesTotal"][item["name"]] = 0;
collection[c[c.length - 1]]["seriesTotal"][
item["name"]
] = item.data.reduce((partialSum, a) => {
if (typeof partialSum == "number") {
return Number(partialSum) + a;
}
}, 0);
}
if (
collection[c[c.length - 1]]["seriesTotal"][item["name"]].indexOf(
"[object Object]",
)
) {
collection[c[c.length - 1]]["seriesTotal"][item["name"]] = Number(
collection[c[c.length - 1]]["seriesTotal"][item["name"]].split(
"[object Object]",
)[0],
);
}
});
}
return collection;
}
function _getDelta(key, sets) {
console.log(sets);
//not passing anything back into the datalabel
return null;
}
const options = {
chart: {
type: "waterfall",
className: "test",
showAxes: false,
},
colors: ["#00A9F4", "#B3B3B3", "#000000"],
legend: {
align: "right",
symbolRadius: 0,
verticalAlign: "top",
},
title: {
text: "Stacked waterfall example",
},
plotOptions: {
series: {
stacking: "normal",
},
waterfall: {
borderColor: "none",
dashStyle: "Solid",
dataLabels: {
useHTML: true,
className: "waterfall-label",
enabled: true,
formatter: function(a) {
return _getDelta(
this.key,
_createLabelValuePairs(series, categories),
);
},
inside: false,
},
states: {
hover: {
animation: {
duration: 0,
},
enabled: false,
},
inactive: {
enabled: false,
},
select: {
enabled: false,
},
},
},
},
xAxis: {
categories: categories,
labels: {
style: {
color: "#000000",
cursor: "default",
fontSize: "14px",
fontFamily: "Mckinsey Sans Regular",
width: "75px",
whiteSpace: "normal", //set to normal
},
},
},
yAxis: {
visible: false,
},
series: series,
credits: {
enabled: false,
},
tooltip: {
animation: false,
backgroundColor: "#333333",
borderColor: "inherit",
borderRadius: 0,
formatter: function() {
return this.x + ": " + this.y;
},
hideDelay: 0,
shadow: false,
style: {
color: "white",
},
},
};
function Waterfall() {
return <HighchartsReact highcharts={Highcharts} options={options} />;
}
export default Waterfall;
And if you want to know what all that code was for _createLabelPairs it's just to get the totals of the series and that of the bars as well( If I need to use those later). I can figure out the percentages with barTotals and total property in the total bar
result from _createLabelPairs function
I am trying to hide the legend of my chart created with Chart.js.
According to the official documentation (https://www.chartjs.org/docs/latest/configuration/legend.html), to hide the legend, the display property of the options.display object must be set to false.
I have tried to do it in the following way:
const options = {
legend: {
display: false,
}
};
But it doesn't work, my legend is still there. I even tried this other way, but unfortunately, without success.
const options = {
legend: {
display: false,
labels: {
display: false
}
}
}
};
This is my full code.
import React, { useEffect, useState } from 'react';
import { Line } from "react-chartjs-2";
import numeral from 'numeral';
const options = {
legend: {
display: false,
},
elements: {
point: {
radius: 1,
},
},
maintainAspectRatio: false,
tooltips: {
mode: "index",
intersect: false,
callbacks: {
label: function (tooltipItem, data) {
return numeral(tooltipItem.value).format("+0,000");
},
},
},
scales: {
xAxes: [
{
type: "time",
time: {
format: "DD/MM/YY",
tooltipFormat: "ll",
},
},
],
yAxes: [
{
gridLines: {
display: false,
},
ticks: {
callback: function(value, index, values) {
return numeral(value).format("0a");
},
},
},
],
},
};
const buildChartData = (data, casesType = "cases") => {
let chartData = [];
let lastDataPoint;
for(let date in data.cases) {
if (lastDataPoint) {
let newDataPoint = {
x: date,
y: data[casesType][date] - lastDataPoint
}
chartData.push(newDataPoint);
}
lastDataPoint = data[casesType][date];
}
return chartData;
};
function LineGraph({ casesType }) {
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async() => {
await fetch("https://disease.sh/v3/covid-19/historical/all?lastdays=120")
.then ((response) => {
return response.json();
})
.then((data) => {
let chartData = buildChartData(data, casesType);
setData(chartData);
});
};
fetchData();
}, [casesType]);
return (
<div>
{data?.length > 0 && (
<Line
data={{
datasets: [
{
backgroundColor: "rgba(204, 16, 52, 0.5)",
borderColor: "#CC1034",
data: data
},
],
}}
options={options}
/>
)}
</div>
);
}
export default LineGraph;
Could someone help me? Thank you in advance!
PD: Maybe is useful to try to find a solution, but I get 'undefined' in the text of my legend and when I try to change the text like this, the text legend still appearing as 'Undefindex'.
const options = {
legend: {
display: true,
text: 'Hello!'
}
};
As described in the documentation you linked the namespace where the legend is configured is: options.plugins.legend, if you put it there it will work:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'pink'
}
]
},
options: {
plugins: {
legend: {
display: false
}
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.js"></script>
</body>
On another note, a big part of your options object is wrong, its in V2 syntax while you are using v3, please take a look at the migration guide
Reason why you get undefined as text in your legend is, is because you dont supply any label argument in your dataset.
in the newest versions this code works fine
const options = {
plugins: {
legend: {
display: false,
},
},
};
return <Doughnut data={data} options={options} />;
Import your options value inside the charts component like so:
const options = {
legend: {
display: false
}
};
<Line data={data} options={options} />
(Requirement: The bar has to start from the first tick(i.e label "a" below) in x axis whereas the line has to start from third tick(label "c" below)).
I have tried the following way.
import React from "react";
import Chart from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
class Barchart extends React.Component {
//chart= null;
componentDidMount(){
this.configureChart();
}
configureChart = ()=>{
let bardata=[7, 3, 2];
let linedata=[ 0,0,0,75, 55, 80, 65];
// xaxislabel=["a","b","c"]
// xaxislabelline=["d","e","f","g"]
const node=this.node;
new Chart(node,{
plugins: [ChartDataLabels],
type:'',
data:{
datasets:[
{
yAxisID:'A',
label: "Bar Dataset",
data: bardata ,
type: "bar",
backgroundColor: "#DE924B",
order:1
},
{
yAxisID:'B',
label: "Line Dataset 2",
data: linedata,
type: "line",
fill: false,
borderColor: 'rgb(75, 192, 192)',
order:2
},
],
labels:["a","b","c","d","e","f","g"]
},
options:{
scales:{
yAxes:[
{ id:'A',
display:true,
ticks:{
beginAtZero:true
}
},
{ id:'B',
display:true,
ticks:{
beginAtZero:true
}
}
],
xAxes:[
{ id:'C',
display: true,
barThickness: 25,
ticks: {
beginAtZero: true,
}
},
{ id:'D',
display: true,
ticks: {
beginAtZero:true,
min:'c',
}
}
},
]
}
}
})
}
render(){
return(
<div>
<canvas
style={{ width: 650, height: 165 }}
ref={node => (this.node = node)}
/>
</div>
);
}
}
export default Barchart;
Below is the attached result I got.
I am not sure how to have the line graph start from label "c" of the main label(or have single label for both graphs).
Found the solution.
changed
let linedata=[ 0,0,0,75, 55, 80, 65]-->
let linedata=[ null,null,null,75, 55, 80, 65];
For the past couple days, I was struggling to use this highchart map type in my react project
https://jsfiddle.net/26tbkjov//
Can some one please help me out?
Please check what I achieved until now:
https://codesandbox.io/s/highcharts-react-demo-0m5ux
I am using those highcharts npm packages
"highcharts": "^7.1.2",
"highcharts-react-official": "^2.2.2",
I have tried many things and ended up in a dead path.. the following is the last thing i have tried:
import React from "react";
import mapData from '../../api/mapData';
import Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
require('highcharts/modules/map')(Highcharts);
class MyMap extends React.Component {
constructor(props) {
super(props);
this.state = {
mapValues: [],
modalClassic: false,
};
this.mapData = new mapData();
// preparing the config of map with empty data
this.options = {
title: {
text: 'Widget click by location',
style: {
color: '#fff'
},
},
chart:{
backgroundColor: 'transparent',
type: 'map',
map: null,
},
mapNavigation: {
enabled: true,
enableButtons: false
},
credits: {
enabled: false
},
colorAxis: {
dataClasses: [
{
from: 1,
color: '#C40401',
name: 'widget name one'
}, {
from: 2,
color: '#0200D0',
name: 'widget name two'
}
]
},
tooltip: {
pointFormatter: function() {
return this.name;
}
},
legend: {
align: 'right',
verticalAlign: 'top',
x: -100,
y: 70,
floating: true,
layout: 'vertical',
valueDecimals: 0,
backgroundColor: ( // theme
Highcharts.defaultOptions &&
Highcharts.defaultOptions.legend &&
Highcharts.defaultOptions.legend.backgroundColor
) || 'rgba(255, 255, 255, 0.85)'
},
series: [{
name: 'world map',
dataLabels: {
enabled: true,
color: '#FFFFFF',
format: '{point.postal-code}',
style: {
textTransform: 'uppercase'
}
},
tooltip: {
ySuffix: ' %'
},
cursor: 'pointer',
joinBy: 'postal-code',
data: [],
point: {
events: {
click: function(r){
console.log('click - to open popup as 2nd step');
console.log(r);
}
}
}
}]
};
}
/*
* Before mounting the component,
* update the highchart map options with the needed map data and series data
* */
componentWillMount = () => {
this.mapData.getWorld().then((r)=>{
this.setState({'mapData': r.data}, ()=>{
this.options.series[0].data = []; //make sure data is empty before fill
this.options['chart']['map'] = this.state.mapData; // set the map data of the graph (using the world graph)
// filling up some dummy data with values 1 and 2
for(let i in this.state.mapData['features']){
let mapInfo = this.state.mapData['features'][i];
if (mapInfo['id']) {
var postalCode = mapInfo['id'];
var name = mapInfo['properties']['name'];
var value = i%2 + 1;
var type = (value === 1)? "widget name one" : "widget name two";
var row = i;
this.options.series[0].data.push({
value: value,
name: name,
'postal-code': postalCode,
row: row
});
}
}
// updating the map options
this.setState({mapOptions: this.options});
});
});
}
render() {
return (
<div>
{(this.state.mapData)?
<HighchartsReact
highcharts={Highcharts}
constructorType={'mapChart'}
options={(this.state.mapOptions)? this.state.mapOptions: this.options}
/>
: ''}
</div>
);
}
}
export default MyMap;
If you want to use the USA map, you need to change the url to: "https://code.highcharts.com/mapdata/countries/us/us-all.geo.json" and the postal-code from US.MA to MA:
this.mapData.getWorld().then(r => {
...
for (let i in this.state.mapData["features"]) {
...
var postalCode = mapInfo.properties["postal-code"];
...
}
...
});
Live demo: https://codesandbox.io/s/highcharts-react-demo-jmu5h
To use the word map, you need to also change the part related with the postal-code and joinBy property:
series: [{
joinBy: ['iso-a2', 'code'],
...
}]
this.mapData.getWorld().then(r => {
...
for (let i in this.state.mapData["features"]) {
let mapInfo = this.state.mapData["features"][i];
if (mapInfo["id"]) {
var code = mapInfo["id"];
...
this.options.series[0].data.push({
"code": code,
...
});
}
}
...
});
Live demo: https://codesandbox.io/s/highcharts-react-demo-sxfr2
API Reference: https://api.highcharts.com/highmaps/series.map.joinBy
kind of stuck in a hole here. I have a stacked Highchart that I'm trying to re-render when you click on a button. Here is what it looks like for now:
Clicking on any of the buttons will trigger a designated event handler that helps me generate a new series of data for that particular category. The data is organized in a way that bar-charts can consume.
For instance, clicking on the "Asset Class" button will return an output of:
(4) [{…}, {…}, {…}, {…}]
0: {name: "Cash", data: Array(1)}
1: {name: "Equity", data: Array(1)}
2: {name: "Fixed Income", data: Array(1)}
3: {name: "Fund", data: Array(1)}
length: 4
The problem I'm having is that the chart never seems to update even though I'm updating the series data. (this.chart.options.series = myNewSeries)
Some events will return more than 4 items (could be anywhere from 4 to 30 values) and I need them to stack as well.
Here is my code with the updating logic near the bottom:
export class ChartComponent{
constructor(){
|| block of script logic ||
this.options = {
chart: {
type: 'column',
height: 500,
width: 500,
style: {
fontFamily: "Arial"
},
events: {
redraw: function (){
alert("The chart is being redrawn")
}
}
},
title: {
text: ""
},
xAxis: {
categories: this.seriesData.category,
labels: {
style: {
fontSize: "14px"
}
}
},
yAxis: {
min: 0,
title: {
text: ""
},
labels: {
formatter: function () {
let valueString = (
this.value > 999.99 && this.value <= 999999.99 ?
"$" + (this.value / 1000).toFixed(0) + "K" : this.value > 999999.99 ?
"$" + (this.value / 1000000).toFixed(1) + "M" : this.value
)
return valueString
},
style: {
fontSize: "14px",
}
}
},
legend: {
x: 0,
y: 0,
verticalAlign: "top",
align: "right",
layout: "vertical",
itemStyle: {
fontSize: "16px",
color: "#6c6c6c",
},
symbolPadding: 8,
itemMarginTop: 10,
shadow: false,
labelFormatter: function () {
return `${this.name}`
}
},
tooltip: {
formatter: function () {
let name = this.series.name
let value = this.y
let valueString = `$${value.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}`
let total = this.point.stackTotal
let percentage = ((value / total) * 100).toFixed(2)
let percentageString = `(${percentage})%`
return `<b>${name}</b> <br> ${valueString} ${percentageString}`
},
style: {
fontSize: "14px",
},
backgroundColor: "#ffffff"
},
plotOptions: {
column: {
stacking: 'normal',
dataLabels: {
enabled: false
}
},
series: {
pointWidth: 100,
borderColor: "rgba(0, 0, 0, 0)"
}
},
series: this.seriesData.series
}
}
options: Object
saveInstance(chartInstance): void {
this.chart = chartInstance;
}
updateSeriesData = (data: Array<any>, title): void => {
this.chart.options.series = data
this.chart.xAxis[0].update({categories: title})
}
// event handlers
getIndustryData = (e) => {
let newSeries = this.getSeriesTotals("Industry", "SecuritySectorLevel1", "SecuritySectorLevel2")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
getSectorData = (e) => {
let newSeries = this.getSeriesTotals("Sector", "SecuritySectorLevel2", "SecuritySectorLevel1")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
getAssetClassData = (e) =>{
let newSeries = this.getSeriesTotals("Asset Class", "AssetClassLevel1", "SecuritySectorLevel1")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
getRegionData = (e) => {
let newSeries = this.getSeriesTotals("Region", "CountryOfRisk", "CountryOfIssuance")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
getCurrencyData = (e) =>{
let newSeries = this.getSeriesTotals("Currency", "LocalCCY", "LocalCCYDescription")
this.updateSeriesData([...newSeries.series], newSeries.category)
}
}
Generally speaking, for the next person who surfs here:
In your HTML-Element you'll have something like:
<highcharts-chart
...
[(update)]="updateFlag">
</highcharts-chart>
And in the corresponding Typescript file you have a
updateFlag = false;
and after the section where you've changed something, you do:
this.updateFlag = true;