I am using React ApexChart in order to show off some data, and I have a switch that shows and hides the data labels. The options are changing without problems, but the zoom level is resetting every time. Is there a way to keep the zoom level when changing the options?
Here is a small preview of the component:
import Chart from "react-apexcharts";
import React, { useEffect, useState } from 'react';
import { Layout } from "antd";
interface Props {
series?: ApexAxisChartSeries;
chartOptiosn?: Record<string, any>;
[ key: string ]: any;
}
export const DynamicChart: React.FC<Props> = React.memo( ( props: Props ) => {
const { Content } = Layout;
const { series, chartOptions, uuid } = props;
const [ initialRerender, setInitialRerender ] = useState( false );
useEffect( () => {
if ( !initialRerender ) {
setInitialRerender( true );
}
}, [ initialRerender ] );
useEffect( () => {
}, [] );
useEffect( () => {
console.log( chartOptions );
}, [ chartOptions ] );
return (
<Layout>
<Content>
<Chart
options={ { ...structuredClone( chartOptions || {} ), chart: { ...chartOptions.chart, id: uuid } } }
series={ series }
type="area"
height={ 350 }
/>
</Content>
</Layout>
);
} );
Here are the options:
export const defaultOptions: ApexCharts.ApexOptions = {
series: [],
chart: {
animations: {
enabled: true,
easing: "linear",
dynamicAnimation: {
speed: 300
}
},
toolbar: {
show: true,
tools: {
download: `<svg viewBox="64 64 896 896" focusable: "false" name= "download" theme="outlined"><path d="M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"></path></svg>`,
selection: false,
zoom: true,
zoomin: true,
zoomout: true,
pan: true,
customIcons: []
},
},
zoom: {
enabled: true
}
},
dataLabels: {
enabled: false
},
yaxis: {
labels: {
style: {
fontSize: "14px",
fontWeight: 600,
colors: [ "#8c8c8c" ],
},
},
},
xaxis: {
type: "datetime"
}
};
Here is a demo for more depth of the problem that I am facing.
Just zoom in between the 3 seconds interval and after the labels reappear, the zoom is refreshed. Is there a way to stop that in react, or should I seek a vanilla approach and ditch the react solution all together?
Related
I'm new here, because I have decided to dive into programming, so I can fill free time between treatments in the hospital. I'm absolutely new in the programming field with no previous coding background.
The summary:
I am working on a simple page, where I fetch data from a Postgre database that is visualized using chart.js. The page is a built-in cube.js playground, using a Reactjs template. Currently, I can display various charts depending on my criteria. Like display monthly sales of a certain product in Australia. Or, I can display a second chart with daily sales in the countries I choose. Or ignore all sales that were in a certain currency. Right now, every new criterion means I have to use cube.js playground and generate a new chart on the page.
What I would like to achieve is to be able to filter already rendered charts (by a dropdown button outside the chart or inside the chart, it doesn't matter too much) and having the chart updated. Something like the pictures here, where the OP can filter charts based on the date, factory, etc.
I've tried Chart.js Example with Dynamic Dataset, chart.js tutorial on
Updating Charts and various others. But I can't seem to be able to implement any of those solutions in my code.
Here is my current code:
ChartRenderer.js
import React from "react";
import PropTypes from "prop-types";
import { useCubeQuery } from "#cubejs-client/react";
import Row from "react-bootstrap/Row";
import Spin from "react-bootstrap/Spinner";
import Col from "react-bootstrap/Col";
import { Statistic, Table } from "antd";
import { Line, Bar, Pie } from "react-chartjs-2";
const COLORS_SERIES = [
"#931F1D",
"#141446",
"#7A77FF",
];
const commonOptions = {
maintainAspectRatio: true,
};
const TypeToChartComponent = {
line: ({ resultSet }) => {
const data = {
labels: resultSet.categories().map((c) => c.category),
datasets: resultSet.series().map((s, index) => ({
label: s.title,
data: s.series.map((r) => r.value),
borderColor: COLORS_SERIES[index],
backgroundColor: COLORS_SERIES[index],
fill: false,
tension: 0.4,
})),
};
const options = { ...commonOptions };
return <Line data={data} options={options} />;
},
bar: ({ resultSet }) => {
const data = {
labels: resultSet.categories().map((c) => c.category),
datasets: resultSet.series().map((s, index) => ({
label: s.title,
data: s.series.map((r) => r.value),
backgroundColor: COLORS_SERIES[index],
fill: false,
})),
};
const options = {
...commonOptions,
scales: {
xAxes: [
{
stacked: true,
},
],
},
};
return <Bar data={data} options={options} />;
},
area: ({ resultSet }) => {
const data = {
labels: resultSet.categories().map((c) => c.category),
datasets: resultSet.series().map((s, index) => ({
label: s.title,
data: s.series.map((r) => r.value),
backgroundColor: COLORS_SERIES[index],
fill: true,
})),
};
const options = {
...commonOptions,
scales: {
yAxes: [
{
stacked: true,
},
],
},
};
return <Line data={data} options={options} />;
},
pie: ({ resultSet }) => {
const data = {
labels: resultSet.categories().map((c) => c.category),
datasets: resultSet.series().map((s) => ({
label: s.title,
data: s.series.map((r) => r.value),
backgroundColor: COLORS_SERIES,
hoverBackgroundColor: COLORS_SERIES,
borderColor: COLORS_SERIES,
hoverBorderColor: "white",
hoverOffset: 10,
})),
};
const options = { ...commonOptions };
return <Pie data={data} options={options} />;
},
number: ({ resultSet }) => {
return (
<Row
type="flex"
justify="space-around"
align="middle"
style={{ height: "100%" }}
>
<Col align="left">
{resultSet.seriesNames().map((s) => (
<Statistic value={resultSet.totalRow()[s.key]} />
))}
</Col>
</Row>
);
},
table: ({ resultSet, pivotConfig }) => {
return (
<Table
pagination={false}
columns={resultSet.tableColumns(pivotConfig)}
dataSource={resultSet.tablePivot(pivotConfig)}
/>
);
},
};
const TypeToMemoChartComponent = Object.keys(TypeToChartComponent)
.map((key) => ({
[key]: React.memo(TypeToChartComponent[key]),
}))
.reduce((a, b) => ({ ...a, ...b }));
const renderChart =
(Component) =>
({ resultSet, error }) =>
(resultSet && <Component resultSet={resultSet} />) ||
(error && error.toString()) || <Spin animation="grow text-primary" />;
const ChartRenderer = ({ vizState }) => {
const { query, chartType } = vizState;
const component = TypeToMemoChartComponent[chartType];
const renderProps = useCubeQuery(query);
return component && renderChart(component)(renderProps);
};
ChartRenderer.propTypes = {
vizState: PropTypes.object,
cubejsApi: PropTypes.object,
};
ChartRenderer.defaultProps = {
vizState: {},
cubejsApi: null,
};
export default ChartRenderer;
DashBoardPage.js
import React from "react";
import Col from "react-bootstrap/Col";
import DateRangePicker from 'react-bootstrap-daterangepicker';
import ChartRenderer from "../components/ChartRenderer";
import Dashboard from "../components/Dashboard";
import DashboardItem from "../components/DashboardItem";
const DashboardItems = [
{
id: 0,
name: "Sold by customers today",
vizState: {
query: {
measures: ["PostgreSqlTable.amount"],
timeDimensions: [
{
dimension: "PostgreSqlTable.added",
granularity: "day",
dateRange: "Today",
},
],
order: {},
dimensions: [],
filters: [
{
member: "PostgreSqlTable.operation",
operator: "contains",
values: ["Sell"],
},
],
},
chartType: "number",
},
},
{
id: 1,
name: "Bought by customers today",
vizState: {
query: {
measures: ["PostgreSqlTable.amount"],
timeDimensions: [
{
dimension: "PostgreSqlTable.added",
dateRange: "Today",
},
],
order: {},
filters: [
{
member: "PostgreSqlTable.operation",
operator: "contains",
values: ["Buy"],
},
],
},
chartType: "number",
},
},
{
id: 2,
name: "Money in the wallet",
vizState: {
query: {
measures: ["PostgreSqlTable.amount"],
timeDimensions: [
{
dimension: "PostgreSqlTable.added",
},
],
order: {
"PostgreSqlTable.amount": "desc",
},
dimensions: ["PostgreSqlTable.currency"],
filters: [
{
member: "PostgreSqlTable.currency",
operator: "equals",
values: ["EUR"],
},
],
},
chartType: "number",
},
},
{
id: 3,
name: "Monthly sales filtered by week",
vizState: {
query: {
measures: ["PostgreSqlTable.amount"],
timeDimensions: [
{
dimension: "PostgreSqlTable.added",
granularity: "week",
dateRange: "This month",
},
],
order: {
"PostgreSqlTable.amount": "desc",
},
dimensions: ["PostgreSqlTable.operation"],
filters: [
{
member: "PostgreSqlTable.operation",
operator: "notContains",
values: ["Register"],
},
],
limit: 5000,
},
chartType: "line",
},
},
{
id: 4,
name: "Countries with most customers",
vizState: {
query: {
measures: ["PostgreSqlTable.count"],
timeDimensions: [
{
dimension: "PostgreSqlTable.added",
},
],
order: {
"PostgreSqlTable.count": "desc",
},
dimensions: ["PostgreSqlTable.country"],
limit: 5,
},
chartType: "pie",
},
},
];
const DashboardPage = () => {
const dashboardItem = (item) => (
<Col className="col-4">
<DashboardItem title={item.name}>
<ChartRenderer vizState={item.vizState} />
</DashboardItem>
</Col>
);
const Empty = () => (
<div
style={{
textAlign: "center",
padding: 12,
}}
>
<h2>
No items added
</h2>
</div>
);
return DashboardItems.length ? (
<Dashboard dashboardItems={DashboardItems}>
{DashboardItems.map(dashboardItem)}
</Dashboard>
) : (
<Empty />
);
};
export default DashboardPage;
At this moment, I have no clue how to implement the filter in react.js+chart.js. I have also tried to update the array, but no success (I followed also this tutorial)
I would be most grateful for any help.
Thank you in advance, stay healthy.
Tatsu
I'd recommend using the <QueryBuilder/> component available in the Cube.js-React integration; this component provides a similar interface as that in the Developer Playground.
I am trying to implement the draggable columns with react-data-grid based on this example: https://github.com/adazzle/react-data-grid/blob/canary/stories/demos/ColumnsReordering.tsx
I see that this example requires creating a DraggableHeaderRenderer file, so I have copied the following file into my project and converted it to React: https://github.com/adazzle/react-data-grid/blob/canary/stories/demos/components/HeaderRenderers/DraggableHeaderRenderer.tsx
My issue is that I do not know where to import useCombinedRefs from. It is not exported from react-data-grid. I see in the repo that it resides in src/hooks.
I have tried the following:
import {useCombinedRefs} from 'react-data-grid'
// Error: Attempted import error: 'useCombinedRefs' is not exported from 'react-data-grid'.
import {useCombinedRefs} from 'react-data-grid/lib/hooks';
// Error: Module not found: Can't resolve 'react-data-grid/lib/hooks' in 'C:\Users\Liam\Desktop\Work\MyProject\src\ReactDataGrid'
import useCombinedRefs from 'react-data-grid/lib/hooks/useCombinedRefs';
// Error: Module not found: Can't resolve 'react-data-grid/lib/hooks/useCombinedRefs' in 'C:\Users\Liam\Desktop\Work\MyProject\src\ReactDataGrid'
Thanks to anyone who can help.
Here is my code:
DraggableHeaderRenderer.js
import { useDrag, useDrop } from 'react-dnd';
import React from 'react'
import { SortableHeaderCell } from 'react-data-grid';
import useCombinedRefs from 'react-data-grid/lib/hooks/useCombinedRefs';
export function DraggableHeaderRenderer({ onColumnsReorder, column, sortColumn, sortDirection, onSort }) {
const [{ isDragging }, drag] = useDrag({
item: { key: column.key, type: 'COLUMN_DRAG' },
collect: monitor => ({
isDragging: !!monitor.isDragging()
})
});
const [{ isOver }, drop] = useDrop({
accept: 'COLUMN_DRAG',
drop({ key, type }) {
if (type === 'COLUMN_DRAG') {
onColumnsReorder(key, column.key);
}
},
collect: monitor => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop()
})
});
return (
<div
ref={useCombinedRefs(drag, drop)}
style={{
opacity: isDragging ? 0.5 : 1,
backgroundColor: isOver ? '#ececec' : 'inherit',
cursor: 'move'
}}
>
<SortableHeaderCell
column={column}
sortColumn={sortColumn}
sortDirection={sortDirection}
onSort={onSort}
>
{column.name}
</SortableHeaderCell>
</div>
);
}
TestDataGrid.js
import React from 'react';
import DataGrid from 'react-data-grid';
import {DraggableHeaderRenderer} from './DraggableHeaderRenderer';
import { useState, useCallback, useMemo } from 'react';
import 'react-data-grid/dist/react-data-grid.css';
const createRows = () => {
const rows = [];
for (let i = 1; i < 500; i++) {
rows.push({
id: i,
task: `Task ${i}`,
complete: Math.min(100, Math.round(Math.random() * 110)),
priority: ['Critical', 'High', 'Medium', 'Low'][Math.round(Math.random() * 3)],
issueType: ['Bug', 'Improvement', 'Epic', 'Story'][Math.round(Math.random() * 3)]
});
}
return rows;
}
const createColumns = () => {
return [
{
key: 'id',
name: 'ID',
width: 80,
},
{
key: 'task',
name: 'Title',
resizable: true,
sortable: true,
draggable: true
},
{
key: 'priority',
name: 'Priority',
resizable: true,
sortable: true,
draggable: true
},
{
key: 'issueType',
name: 'Issue Type',
resizable: true,
sortable: true,
draggable: true
},
{
key: 'complete',
name: '% Complete',
resizable: true,
sortable: true,
draggable: true
}
];
}
export default function TestDataGrid() {
const [rows] = useState(createRows)
const [columns, setColumns] = useState(createColumns)
const draggableColumns = useMemo(() => {
const HeaderRenderer = (props) => {
return <DraggableHeaderRenderer {...props} onColumnsReorder={handleColumnsReorder}/>
}
const handleColumnsReorder = (sourceKey, targetKey) => {
const sourceColumnIndex = columns.findIndex(c => c.key === sourceKey);
const targetColumnIndex = columns.findIndex(c => c.key === targetKey);
const reorderedColumns = [...columns];
reorderedColumns.splice(
targetColumnIndex,
0,
reorderedColumns.splice(sourceColumnIndex, 1)[0]
);
setColumns(reorderedColumns);
}
return columns.map(c => {
if(c.key === "id") return c;
return {...c, HeaderRenderer}
});
}, [columns])
return (
<DataGrid
columns={draggableColumns}
rows={rows}
/>
);
}
I have a MUI Table Implementation such that the options object viewColumns is set to true, which is supposed to display the popper to choose the columns to be displayed in vertical list of checkbox as shown below:
, but I am getting it horizontally as displayed in screenshot below:
The options to be passed in MUIDataTable components is defined as:
const options = {
filter: true,
filterType: "dropdown",
print: false,
viewColumns: true,
selectableRows: false,
onRowClick: (rowData) => {
console.log("RowClicked->", rowData);
},
responsive: "stacked",
fixedHeaderOptions: {
xAxis: false,
yAxis: true,
},
};
columns defines as:
export const DEAL_GRID_COLUMNS = [
{
name: "someReference",
label: "Some Reference",
options: {
filter: true,
sort: true,
},
},
{
name: "businessTeam",
label: "Business Teams",
options: {
filter: true,
sort: true,
},
},
{
name: "keyContact",
label: "Key Contact Lead",
options: {
filter: true,
sort: true,
},
},
.....
.....
.....
.....
];
and the component is consumed as
<MUIDataTable data={gridData} columns={DEAL_GRID_COLUMNS} options={options} />
here gridData is recieved from Api response
I made sample example using your options and I found viewColumns popper as vertical. I assume there is mui-datatable version issue. FYI, I am using "mui-datatables": "^2.14.0"
Here is the example:
import React, {useEffect, useState} from "react";
import MUIDataTable from "mui-datatables";
import axios from 'axios';
export default function DataTable() {
const [posts, setPost] = useState([]);
let signal = axios.CancelToken.source();
useEffect(() => {
let isSubscribed = true;
axios.get(`https://jsonplaceholder.typicode.com/posts`, {
cancelToken: signal.token,
})
.then(res => {
const posts = res.data;
setPost(posts);
}).catch(err => {
console.log(err);
});
return function cleanup() {
isSubscribed = false;
signal.cancel('Api is being canceled');
}
}, []);
const columns = ["id", "title", "body"];
const options = {
filter: true,
filterType: "dropdown",
print: false,
viewColumns: true,
selectableRows: 'none',
onRowClick: (rowData) => {
console.log("RowClicked->", rowData);
},
responsive: "stacked",
fixedHeaderOptions: {
xAxis: false,
yAxis: true,
},
};
return (
<MUIDataTable
title={"Posts"}
data={posts}
columns={columns}
options={options}
/>
);
}
You can check this output screen
I fixed this by overriding its maxWidth as
MuiPopover: {
paper: {
maxWidth: "16%",
},
}
I am using react apex chart to create a chart that will display the average response time for each agent.
I have managed to get the result in a timestamp format but i am unable to convert that into hours, minutes and seconds to display that in yaxis, i have checked the documentation docs link but they are giving examples for date time only.
here is the result that i am getting with the component bellow
import React, { useState } from 'react';
import Chart from 'react-apexcharts';
const AvgResponseTimeChart = (props) => {
const { prod_data } = props;
const [ data, setData ] = useState([
{
x: 'Agent one',
y: 1589670005
},
{
x: 'Agent one',
y: 1589670307
}
]);
const [ series, setSeries ] = useState([ { data } ]);
const [ options, setOptions ] = useState({
chart: {
type: 'bar',
height: 350
},
plotOptions: {
bar: {
horizontal: false,
columnWidth: '25%',
endingShape: 'rounded'
}
},
dataLabels: {
enabled: false
},
stroke: {
show: true,
width: 2,
colors: [ 'transparent' ]
},
xaxis: {
type: 'category'
},
yaxis: {
labels: {
datetimeFormatter: {
formatter: function(value, timestamp) {
return new Date(timestamp).toLocaleTimeString();
}
}
}
},
fill: {
opacity: 1
},
tooltip: {
y: {
formatter: function(value, timestamp) {
return new Date(timestamp);
}
}
}
});
return (
<div id="chart">
<Chart options={options} series={series} type="bar" height={350} />
</div>
);
};
export default AvgResponseTimeChart;
I have searched for similar issues without success if, someone can help me with that i will be really grateful
Try to add lables to yaxis in chartOptions this way:
labels: {
show: true,
formatter: (val) => { return new Date(val); }
}
And remove the tooltip as well.
I am trying to populate chart data from my backend.
Although I am fetching data and pushing data in componentDidMount, the Bars or Scatters are not loaded on page load.
If I change my screen width in inspect mode in google dev tools, it starts loading which leads me to believe this is a lifecyle problem.
However, changing it to componentWillMount did not change anything. Putting a if statement before render like below just stops loading the chart altogether.
if(this.state.data.datasets[0].data.length ===0){
return null;
}
Any way to fix this problem?
import React, { Component } from "react";
import axios from "axios";
import { Bar, Scatter } from "react-chartjs-2";
export class Data extends Component {
constructor(props) {
super(props);
this.state = {
provider: [],
data: {
labels: ["Action", "Anime", "Children"],
datasets: [
{
label: "Total",
backgroundColor: "rgba(255, 159, 64, 0.4)",
borderColor: "white",
borderWidth: 1,
stack: 1,
hoverBackgroundColor: "rgba(255,99,132,0.4)",
hoverBorderColor: "rgba(255,99,132,1)",
data: []
},
{
label: "Above ⭐️8.5",
backgroundColor: "white",
type: "scatter",
showLine: false,
stack: 1,
data: []
}
]
}
};
}
componentDidMount() {
axios.get("http://localhost:8001/provider").then(res =>
this.setState({ provider: res.data }, () => {
this.pushAction();
})
);
}
pushAction() {
const dataState = this.state.data;
const oldDataTotal = this.state.data.datasets[0].data;
const oldDataGood = this.state.data.datasets[1].data;
oldDataTotal.push(this.state.provider[0].huluAction);
oldDataTotal.push(this.state.provider[0].huluAnime);
oldDataTotal.push(this.state.provider[0].huluChildren);
oldDataGood.push(this.state.provider[0].Action);
oldDataGood.push(this.state.provider[0].Anime);
oldDataGood.push(this.state.provider[0].Children);
}
render() {
console.log(this.state.musicalData);
const options = {
responsive: true,
maintainAspectRatio: false,
legend: {
display: true
},
type: "bar",
scales: {
xAxes: [
{
stacked: true
}
],
yAxes: [
{
stacked: true
}
]
}
};
return (
<div>
<Bar data={this.state.data} height={300} options={options} />
</div>
);
}
}
export default Data;
you have to update the state after changing the datasets,
pushAction = () => {
const dataState = this.state.data;
const oldDataTotal = this.state.data.datasets[0].data;
const oldDataGood = this.state.data.datasets[1].data;
oldDataTotal.push(this.state.provider[0].huluAction);
oldDataTotal.push(this.state.provider[0].huluAnime);
oldDataTotal.push(this.state.provider[0].huluChildren);
oldDataGood.push(this.state.provider[0].Action);
oldDataGood.push(this.state.provider[0].Anime);
oldDataGood.push(this.state.provider[0].Children);
this.setState({data: {...dataState, datasets : [...oldDataTotal, oldDataGood]}});
}