I'm using REACT-PLOTLY.JS to create a scatter graph. I've got everything working apart from the graph redrawing it self every-time I change a data point when using a list of objects in the array data prop. BUT... when I manually write the array and it's containing objects, my graph does not re-draw when I change a data point, which is how it should work. The 2nd solution is not dynamic and is unusable. Can anyone help please?
re-draws graph when data point changes.
<Plot
data={plotlyData}
does not re-draw graph when data point is changed but is not dynamic and therefore unusable.
<Plot
data={[plotlyData[0],plotlyData[1]]}
I'm using functional components.
How plotData is generated. I'm using an API to get the coordinates for the X and Y axis.
import { React, useState } from "react";
import Plot from "react-plotly.js";
import { useQuery } from "react-query";
import axios from "axios";
const PlotlyGraph = (props) => {
const [plot, setPlot] = useState([]);///not in use
const { datasets } = props;
const QueryUuid = datasets.map((d) => {
// console.log("Individual Dataset:", d);
return `${d.age}-${d.kmax}-${d.frontK}-${d.pachymetry}`;
});
const { error, isLoading, data } = useQuery(
`data-${QueryUuid.join("-")}`,
() =>
axios.post("/api/calculate_cxl", {
age_baseline: datasets.map((d) => d.age),
kmax: datasets.map((d) => d.kmax),
front_k1: datasets.map((d) => d.frontK),
tpt2: datasets.map((d) => d.pachymetry),
color: datasets.map((d) => d.color),
})
);
let plotlyData;
if (error !== null || isLoading === true) {
plotlyData = [];
} else {
plotlyData = data.data.map((d, i) => {
return {
x: d.x,
y: d.y,
type: "scatter",
mode: "lines",
marker: { color: datasets[i].color },
line: {
width: 3,
},
name: `Patient ${i + 1}`,
showlegend: true,
};
});
}
console.log("plot:", plotlyData);
//- Graph Configuration
const config = {
editable: false,
scrollZoom: true,
displayModeBar: true,
displaylogo: false,
};
return (
<>
<Plot
data={plotlyData}
layout={{
yaxis: { range: [0, 1] },
xaxis: { range: [0, 5] },
autoSize: "true",
title: "Patient Comparison",
}}
style={{ width: "100%", height: " 700px" }}
useResizeHandler={true}
config={config}
revision={0}
// onInitialized={plot}
// onUpdate={(plot) => setPlot(plotlyData)}
/>
</>
);
};
export default PlotlyGraph;
I had a similar issue and was able to figure out how to dynamically update my plot with new data after every API fetch (see State Management). Since I don't have access to the API you are using, I've included my own example.
From my API, I fetch an object that looks like this:
{
28AD7D49F6E13C0A: [69.36, 64.11, 68.69, 62.1, ...],
28DDC649F6003C1C: [69.59, 63.18, 60.63, 63.08, ...],
Time: ['20:50:15', '20:50:17', '20:50:19', '20:50:21', ...]
}
Every two seconds, that objects gets updated with a new item in each array. When the state data gets updated with setData, the state object gets updated, which then causes the plot to render with new data.
Full example:
import React, { useState, useEffect } from "react";
import Plot from "react-plotly.js";
function TemperatureGraph() {
const [data, setData] = useState({});
const state = {
data: [
{
x: data["Time"],
y: data["28AD7D49F6E13C0A"],
name: "28AD7D49F6E13C0A",
type: "scatter",
mode: "lines+markers",
marker: {
color: "red",
},
},
{
x: data["Time"],
y: data["28DDC649F6003C1C"],
name: "28DDC649F6003C1C",
type: "scatter",
mode: "lines+markers",
marker: {
color: "black",
},
},
],
layout: {
width: 800,
height: 500,
title: "",
xaxis: {
title: "Time",
},
yaxis: {
title: "Temperature (F)",
},
},
frames: [],
config: {},
};
useEffect(() => {
const timer = setInterval(() => {
fetch("/get-temperatures")
.then((response) => response.json())
.then((data) => setData(data));
}, 2000);
return () => clearInterval(timer);
}, []);
return (
<div>
<Plot data={state.data} layout={state.layout} />
</div>
);
}
export default TemperatureGraph;
Related
I am creating a graph using the react-plotly library. The data from the graph is being queried from an API endpoint.
The service file is shown below
import React from 'react'
export default async function GetStockData(token,ticker,setData, setSuccess) {
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer " + token)
var formdata = new FormData();
formdata.append("Tick", ticker);
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
await fetch("https://abc.azurewebsites.net/api/stocks", requestOptions)
.then(response => response.json())
.then(response => setData(JSON.parse(response.content))
}
I am still the add error handling. I have fixed the ticker to a value that I know works for now.
This service is then called by the component below:
import React, { useState, useContext, useEffect} from 'react'
import GetStockData from '../Services/GetStockData'
import Plot from 'react-plotly.js';
import { sampleData } from './sampleChartdata';
import AuthContext from "../Store/AuthContext";
export default function CandleStick() {
const authCtx = useContext(AuthContext);
const token = authCtx.token
const [chartData, setChartData] = useState(sampleData);
const [chartSuccess, setChartSuccess] = useState(false)
const [preppedData, setpreppedData] = useState(null);
const [layout, setlayout] = useState(null);
const ticker = 'MSFT'
const clickHandler = async()=>{
await GetStockData(token,ticker,setChartData, setChartSuccess)
const {data, preplayout} = dataPrep()
setpreppedData(data)
setlayout(preplayout)
}
const dataPrep =()=>{
var dateData = []
var closeData = []
var openData = []
var lowData = []
var highData = []
for(var prop in chartData["Time Series (Daily)"]){
dateData.push(prop)
for(var prop2 in chartData["Time Series (Daily)"][prop]){
if (prop2=="1. open"){ openData.push(chartData["Time Series (Daily)"][prop][prop2])}
if (prop2=="2. high"){ highData.push(chartData["Time Series (Daily)"][prop][prop2])}
if (prop2=="3. low"){ lowData.push(chartData["Time Series (Daily)"][prop][prop2])}
if (prop2=="5. adjusted close"){ closeData.push(chartData["Time Series (Daily)"][prop][prop2])}
}
}
var trace1 = {
x: dateData,
close:closeData,
increasing: {line: {color: 'green'}},
decreasing: {line: {color: 'red'}},
high: highData,
line: {color: 'rgba(31,119,180,1)'},
low: lowData,
open: openData,
type: 'candlestick',
xaxis: 'x',
yaxis: 'y'
};
var data = [trace1]
var layout = {
dragmode: 'zoom',
margin: {
r: 10,
t: 25,
b: 40,
l: 60
},
showlegend: false,
xaxis: {
autorange: true,
domain: [0, 1],
title: 'Date',
type: 'date'
},
yaxis: {
autorange: true,
domain: [0, 1],
type: 'linear'
}
};
return {data , layout}
} ;
useEffect(() => {
if (preppedData !== null) setIsDataLoaded(true);console.log(preppedData)
}, [preppedData]);
return (
<>
<div>{isDataLoaded?
<Plot
data={preppedData}
layout={layout}>
</Plot>: null}
</div>
<button onClick={()=>clickHandler()}>Refresh</button>
</>
)
}
There are probably a whole host of things that could be improved here. I am using too stateful variables for a start. I currently have a local store of data to use as a sample to prevent an error occurring prior to the request being made. Any comment on how to manage first render when awaiting web based content would be massively appreciated.
My core question relate to setChartData, setChartSuccess. On the press of the button, these are passed to the service file where they are updated simultaneously. However, the chartSuccess variable seems to update prior to ChartData. The jsx conditional triggers and renders the graph but it doesn't contain the latest preppedData. On pressing the button a second time the updated data appears. Am I making an error in the sequencing?
From what I see, you can do 2 things to solve this. Either set success state inside the click handler and if the success state can't be moved to elsewhere as it might be used in some other place, you can have a new state variable and use it for the condition for render or not.
I'm showing you how to do it with the 2nd method here, but you can give 1st method a try if you don't have problem I mentioned.
So, your component code will look like this.
import React, { useState, useContext, useEffect } from 'react';
import GetStockData from '../Services/GetStockData';
import Plot from 'react-plotly.js';
import { sampleData } from './sampleChartdata';
import AuthContext from '../Store/AuthContext';
export default function CandleStick() {
const authCtx = useContext(AuthContext);
const token = authCtx.token;
const [chartData, setChartData] = useState(sampleData);
const [chartSuccess, setChartSuccess] = useState(false);
const [preppedData, setpreppedData] = useState(null);
const [layout, setlayout] = useState(null);
const [isDataLoaded, setIsDataLoaded] = useState(false);
const ticker = 'MSFT';
const clickHandler = async () => {
await GetStockData(token, ticker, setChartData, setChartSuccess);
const { data, preplayout } = dataPrep();
setpreppedData(data);
setlayout(preplayout);
setIsDataLoaded(true);
};
const dataPrep = () => {
var dateData = [];
var closeData = [];
var openData = [];
var lowData = [];
var highData = [];
for (var prop in chartData['Time Series (Daily)']) {
dateData.push(prop);
for (var prop2 in chartData['Time Series (Daily)'][prop]) {
if (prop2 == '1. open') {
openData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '2. high') {
highData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '3. low') {
lowData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '5. adjusted close') {
closeData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
}
}
var trace1 = {
x: dateData,
close: closeData,
increasing: { line: { color: 'green' } },
decreasing: { line: { color: 'red' } },
high: highData,
line: { color: 'rgba(31,119,180,1)' },
low: lowData,
open: openData,
type: 'candlestick',
xaxis: 'x',
yaxis: 'y',
};
var data = [trace1];
var layout = {
dragmode: 'zoom',
margin: {
r: 10,
t: 25,
b: 40,
l: 60,
},
showlegend: false,
xaxis: {
autorange: true,
domain: [0, 1],
title: 'Date',
type: 'date',
},
yaxis: {
autorange: true,
domain: [0, 1],
type: 'linear',
},
};
return { data, layout };
};
return (
<>
<div>
{isDataLoaded ? <Plot data={preppedData} layout={layout}></Plot> : null}
</div>
<button onClick={() => clickHandler()}>Refresh</button>
</>
);
}
UPDATED as per requirement
import React, { useState, useContext, useEffect } from 'react';
import GetStockData from '../Services/GetStockData';
import Plot from 'react-plotly.js';
import { sampleData } from './sampleChartdata';
import AuthContext from '../Store/AuthContext';
export default function CandleStick() {
const authCtx = useContext(AuthContext);
const token = authCtx.token;
const [chartData, setChartData] = useState(sampleData);
const [chartSuccess, setChartSuccess] = useState(false);
const [preppedData, setpreppedData] = useState(null);
const [layout, setlayout] = useState(null);
const [isDataLoaded, setIsDataLoaded] = useState(false);
const ticker = 'MSFT';
const clickHandler = async () => {
await GetStockData(token, ticker, setChartData, setChartSuccess);
const { data, preplayout } = dataPrep();
setpreppedData(data);
setlayout(preplayout);
};
const dataPrep = () => {
var dateData = [];
var closeData = [];
var openData = [];
var lowData = [];
var highData = [];
for (var prop in chartData['Time Series (Daily)']) {
dateData.push(prop);
for (var prop2 in chartData['Time Series (Daily)'][prop]) {
if (prop2 == '1. open') {
openData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '2. high') {
highData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '3. low') {
lowData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
if (prop2 == '5. adjusted close') {
closeData.push(chartData['Time Series (Daily)'][prop][prop2]);
}
}
}
var trace1 = {
x: dateData,
close: closeData,
increasing: { line: { color: 'green' } },
decreasing: { line: { color: 'red' } },
high: highData,
line: { color: 'rgba(31,119,180,1)' },
low: lowData,
open: openData,
type: 'candlestick',
xaxis: 'x',
yaxis: 'y',
};
var data = [trace1];
var layout = {
dragmode: 'zoom',
margin: {
r: 10,
t: 25,
b: 40,
l: 60,
},
showlegend: false,
xaxis: {
autorange: true,
domain: [0, 1],
title: 'Date',
type: 'date',
},
yaxis: {
autorange: true,
domain: [0, 1],
type: 'linear',
},
};
return { data, layout };
};
useEffect(() => {
if (preppedData !== null) setIsDataLoaded(true);
}, [preppedData]);
return (
<>
<div>
{isDataLoaded ? <Plot data={preppedData} layout={layout}></Plot> : null}
</div>
<button onClick={() => clickHandler()}>Refresh</button>
</>
);
}
I today ran into an issue with datagrid in Material UI where despite rows existing, they aren't being shown anywhere. This is completely new to me as everything was working just fine yesterday, all I did between now and then was refactor some code.
Here's the component:
const UserGrid: React.FC = () => {
const reducer = useGrid();
const { state } = useContext(UserContext);
const { loading } = state.crudState;
const { rowIds, sortState, filterState } = reducer.state;
const users = state.users;
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 100, editable: false },
{
field: "name",
headerName: "Name",
flex: 1,
},
{
field: "email",
headerName: "Email",
flex: 1,
},
];
return (
<Box sx={{ width: "100%", display: "flex", height: "100%", p: 5 }}>
<Box sx={{ m: "0 auto", flexGrow: 1, height: "600px" }}>
{users && (
<DataGrid
columns={columns}
loading={loading === "main"}
rows={users}
getRowId={(row) => row.id}
onSelectionModelChange={(newSelection) => {
reducer.dispatch({ type: "rowids", payload: newSelection });
}}
onSortModelChange={(newSortModel) => {
reducer.dispatch({ type: "sort", payload: newSortModel });
}}
sortModel={sortState}
checkboxSelection={true}
filterModel={filterState}
selectionModel={rowIds}
components={{
ColumnSortedAscendingIcon: SortAscIcon,
ColumnSortedDescendingIcon: SortDescIcon,
Toolbar: GridToolbar,
Footer: GridFooter,
Pagination: null,
}}
scrollbarSize={10}
componentsProps={{
toolbar: { rowIds: rowIds },
baseCheckbox: { sx: { color: "primary.main" } },
footer: {
rowIds: rowIds,
},
}}
autoPageSize
disableSelectionOnClick
/>
)}
</Box>
</Box>
);
};
Here's the reducer:
const useUserActions = () => {
const userActionReducer = (state: State, action: Action): State => {
switch (action.type) {
case "main":
return {
...state,
users: action.payload?.body as User[],
crudState: {
...state.crudState,
...(action.payload?.state as CrudState),
},
};
case "remove":
return {
...state,
users: state.users?.filter(
(user) => !action.payload?.ids?.includes(user.id)
),
crudState: {
...state.crudState,
...(action.payload?.state as CrudState),
},
init: undefined,
};
case "add":
return {
...state,
users: [...(state.users || []), action.payload?.body as User],
crudState: {
...state.crudState,
...(action.payload?.state as CrudState),
},
init: undefined,
};
case "init":
return {
...state,
init: action.payload?.init as Init,
};
default:
return state;
}
};
const [state, dispatch] = useReducer(userActionReducer, initialState);
const setInit = (init: Init) => dispatch({ type: "init", payload: { init } });
useEffect(() => {
const api = new ApiClient();
if (state.init) {
state.init.query === "add" && userAddAction(state, dispatch, api);
state.init.query === "remove" && userRemoveAction(state, dispatch, api);
}
}, [state]);
useEffect(() => {
const api = new ApiClient();
const fetchUsers = userFetchAction(dispatch, api);
fetchUsers();
}, []);
return { state: state, setInit: setInit };
};
And here's the relevant fetch function:
export function userFetchAction(
dispatch: React.Dispatch<Action>,
api: ApiClient
) {
return async () => {
dispatch({ type: "main", payload: { state: { loading: "main" } } });
await api
.getUsers()
.then((res) => res.json())
.then((users) => {
dispatch({
type: "main",
payload: { body: users, state: { loading: null } },
});
})
.catch((error) => {
dispatch({
type: "main",
payload: {
state: {
error: [{
message: error.detail,
statusCode: error.statusCode,
...error,
}],
loading: null,
},
},
});
});
};
}
I looked up this issue and found some suggesting to add a state to hold the data as it begins undefined, I did this with a useEffect, then removed it as it didn't work:
const [ rows, setRows ] = useState<User[]>([])
useEffect(()=>{
if (state.users.length) {
setRows(state.users)
}
}, [state])
The rows are clearly there, they just refuse to be shown.
I've tried both setting the height of the container and setting autoHeight on the grid without any results. I don't know what to do about it really, it might just be that I'm missing something very simple but can't find any info anywhere.
So this is slightly embarrassing.
Turns out I had at some point added both autoPageSize and autoHeight to the default props in my theme so no matter what I did they were there and as they don't work together, that is what caused this.
For anyone else showing up here in the future, I had the same problem.
This link ( https://github.com/mui/mui-x/issues/3546 ) helped me figure out that I was not declaring a height boolean on my component.
I'm rendering in a react application like so now and it works great.
<DataGrid columns={columns} rows={Object.values(tabledata[0])} autoHeight pageSize={5} rowsPerPageOptions={[5]} checkboxSelection />
'autoHeight' was the boolean that I added.
Good luck!
I am trying to get the chart data from graphql and render it to the DOM.
All is fine as the data is showing, but is not synchronized with it's real values.
As I had to extract the data into a unique set of arrays, the data is not synchronized with it's underlying values.
How can I improve my component, so the data is in sync with it's labels?
import React, { useEffect, useState, useMemo } from "react";
import { useQuery, gql } from "#apollo/client";
import { Line, Pie, Radar, Bar } from "react-chartjs-2";
import { Chart, registerables } from "chart.js";
Chart.register(...registerables);
const total = gql`
query GetIntell($myLimit: Int!) {
webs(
pagination: { start: 0, limit: $myLimit }
filters: { site: { eq: "nist" } }
) {
data {
id
attributes {
dateAdded
severity
}
}
}
}
`;
export default function Graph({limit}) {
const { loading, error, data } = useQuery(total, {
variables: { myLimit: limit },
});
const [chartData, setChartData] = useState({});
const myLabels = useMemo(
() => (data ? data.webs.data.map((t) => t.attributes.dateAdded) : null),
[data]
);
const myData = useMemo(
() => (data ? data.webs.data.map((t) => t.attributes.severity) : null),
[data]
);
useMemo(() => {
setChartData({
labels: [...new Set(myLabels)], <---I am altering the data with this set. How can I recover the data with it's correct values and still get the unique array of values?
datasets: [
{
label: "Nist",
data: myData,
fill: true,
},
],
});
}, [data]);
let delayed;
if (loading) return <div className="loader center"></div>;
if (error) return <p>`Error! ${error}`</p>;
return (
<div className="container">
<div className="chart">
<Bar
data={chartData}
options={{
responsive: true,
plugins: {
title: {
display: true,
text: "Severity Accumulation",
},
legend: {
display: true,
position: "top",
},
},
}}
/>
</div>
</div>
);
}
Please see my pic bellow. The chart is fine, but still incorrect as it displayes values maybe for other days.
Any feedback is well received! Also if you can see an easier way of improving my code, I would owe you a lot!
I am getting data from my database to display it on the graph. Currently, I will have to refresh the page for the graph to update. I would like to refresh the graph in x interval as my data will be inserted at x interval. Am using ant design for the graph plotting. I am using a 'home' to display my graph and another class for my data fetching.
Home.js
export class Home extends Component {
static displayName = Home.name;
render () {
return (
<div>
<h1>Dashboard</h1>
<h2>
<div className="site-card-wrapper">
Graph1
<Graph />}
</div>
</h2>
</div>
);
}
}
Temp.js
const TempGraph = () => {
const [data, setData] = useState([]);
useEffect(() => {
asyncFetch();
}, []);
const asyncFetch = () => {
fetch('link')
.then((response) => response.json())
.then((json) => setDatajson))
.catch((error) => {
console.log('fetch data failed', error);
});
};
const config = {
data,
xField: 'time',
yField: 'value',
seriesField:'location',
xAxis: {
title: {
text: 'Hours',
}
},
yAxis:{
title:{
text: 'Temperature in °',
}
},
meta: {
time: {
alias: 'hours',
},
value: {
alias: 'temperature',
max: 50,
},
},
};
return <Line {...config} />;
}
export default TempGraph;
You could just add a setInterval in your useEffect to grab the data and update them again. Don't forgot to clear the interval on return:
useEffect(() => {
const interval = setInterval(() => asyncFetch(), 5000)
return () => clearInterval(interval)
}, []}
This example triggers every 5000ms, change the value according to your needs.
My React components pulls data for an API. The options.series.data data for the yAxis is what receives the API data.
The component is enabled to pull the data range for day/hour/minute which comes with a datestamp for when the data was recorded. How do I dynamically set the xAxis min/max range to respect the day/hour/minute duration change?
The HighchartsReact instance receives the data series via the options object that's where I'd like to setup the dynamic xAxis handler method. Perhaps it's setExtemes().
The component code is below.
import React, { Fragment, useState, useEffect } from 'react';
import { connect } from 'react-redux';
import Highcharts from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
import highchartsStockChart from 'highcharts/modules/stock';
import { getDaily, getHourly, getMinute } from '../actions/getData';
import Spinner from './Spinner';
Highcharts.setOptions({
lang: {
thousandsSep: ','
}
});
highchartsStockChart(Highcharts);
const Chart = ({
match,
list: { list, loading },
getDaily,
getHourly,
getMinute,
error
}) => {
const [method, setMethod] = useState(() => getDaily);
useEffect(() => {
method(match.params.currency.toUpperCase(), 30);
}, [match.params.currency, method]);
console.log('Chart.js list:', list);
console.log('Chart.js error:', error);
console.log('Chart.js loading:', loading);
const options = {
title: {
text: 'Close Price'
},
series: [{
name: 'close price',
data: list.map(item => item.close),
tooltip: {
pointFormat: 'close price: ${point.y:,.2f}'
},
animation: false
}],
scrollbar: {
enabled: false
},
navigator: {
enabled: false
},
rangeSelector: {
inputEnabled: false,
allButtonsEnabled: true,
buttonSpacing: 10,
buttonPosition: {
align: 'left'
},
buttons: [{
type: 'day',
count: 1,
text: 'Day',
events: {
click: () => setMethod(() => getDaily)
}
}, {
type: 'hour',
count: 1,
text: 'Hour',
events: {
click: () => setMethod(() => getHourly)
}
}, {
type: 'minute',
count: 1,
text: 'Minute',
events: {
click: () => setMethod(() => getMinute)
}
}]
}
};
let content;
if (error && error.message) {
content = error.message;
} else if (!list.length || loading) {
content = <Spinner />;
} else {
content = (
<Fragment>
{list.map(item => <span key={item.time}>{item.close} </span>)}
<button onClick={() => setMethod(() => getHourly)}>set Hourly</button>
<button onClick={() => setMethod(() => getMinute)}>set Minute</button>
<HighchartsReact
highcharts={Highcharts}
options={options}
constructorType={'stockChart'}
/>
</Fragment>
);
}
return (
<div>
Chart.
{content}
</div>
);
};
const mapStateToProps = state => ({
list: state.data,
error: state.error
});
export default connect(mapStateToProps, { getDaily, getHourly, getMinute })(Chart);
You can:
use chart redraw event callback function and call setExtremes:
chart: {
events: {
redraw: function() {
if (allowChartRedraw) {
allowChartRedraw = false;
this.xAxis[0].setExtremes(0, Math.random() * 3);
}
allowChartRedraw = true;
}
}
}
Live example: http://jsfiddle.net/BlackLabel/wvpnct9h/
API Reference: https://api.highcharts.com/highcharts/chart.events.redraw
keep all of the options in a state and manipulate axis extremes by min and max properties:
componentDidMount() {
this.setState({
chartOptions: {
series: [
{
data: [Math.random() * 3, Math.random() * 3, Math.random() * 3]
}
],
xAxis: {
min: 0,
max: Math.random() * 3
}
}
});
}
Live demo: https://codesandbox.io/s/highcharts-react-demo-jo6nw
get the chart reference and call setExtremes on the xAxis.
Docs: https://github.com/highcharts/highcharts-react#how-to-get-a-chart-instance