I am using React to create an editable chart (via form input to enter chart settings, and then update the chartsettings object state with new values from the form). I have noticed that the visual of the chart does update, but with an overlay of the previous chart appearance remaining. What am I doing wrong?
Here is how the COMPONENTS are working together & then their code:
SCATTERPAGE consumes SCATTERCHART & CHARTSETTINGS;
CHARTSETTINGS consumes various INPUTCOMPONENTs. The input components trigger updates in the the state of the SCATTERPAGE, after their own state is updated
SCATTERCHART is simply passed the settings object state
Settings object state & functions to set it, are passed down via props from the SCATTERPAGE, via the CHARTSETTINGS into the INPUTCOMPONENTs
SCATTERPAGE's code:
import React, { useEffect, useState } from 'react'
import { ScatterChart } from '@components/charts/area-2-0'
import {
Grid,
Box,
Button,
Paper,
Stack,
Title,
createStyles,
Center
} from '@mantine/core'
import { PlayerPlay } from 'tabler-icons-react'
import { ChartSettings } from '@/components/ChartSettings'
import { modelChartSettingsObject } from './modelChartSettingsObject'
const useStyles = createStyles((theme) => ({
ChartContainer: {
width: '100%',
height: '500px',
borderRadius: '15px',
background: '#000000',
p: 2
}
}))
const settingsConfig = [
{
title: 'data',
elements: [
{
title: 'dataset',
typeConfig: {
label: 'Dataset to show in Chart',
placeholder: 'insert your dataset'
},
type: 'data'
}
]
},
{
title: 'axis',
elements: [
{
title: 'xExplanatoryVariable',
type: 'select',
typeConfig: {
label: 'make a selection',
placeholder: 'selections',
data: [
{ value: 'durationDays', label: 'durationDays' },
{ value: 'sizeSqm', label: 'sizeSqm' }
]
}
},
{
title: 'grid',
elements: [
{
title: 'x',
type: 'switcher',
typeConfig: {
label: 'show grid for x axis'
}
},
{
title: 'y',
type: 'switcher',
typeConfig: {
label: 'show grid for y axis'
}
},
{
title: 'stroke',
type: 'color',
typeConfig: {
placeholder: 'pick stroke color',
label: 'pick stroke color'
}
},
{
title: 'lineWidth',
type: 'number',
typeConfig: {
label: 'lineWidth',
min: 0,
max: 100
}
}
]
},
{
title: 'label',
elements: [
{
title: 'fontSize',
type: 'number',
typeConfig: {
label: 'fontSize'
}
},
{
title: 'color',
type: 'color',
typeConfig: {
label: 'label color'
}
}
]
},
{
title: 'tickLineWidth',
type: 'number',
typeConfig: {
label: 'tickLineWidth'
}
},
{
title: 'tickLineLength',
type: 'number',
typeConfig: {
label: 'tickLineLength'
}
},
{
title: 'lineWidth',
type: 'number',
typeConfig: {
label: 'linewidth'
}
},
{
title: 'subTickLineLength',
type: 'number',
typeConfig: {
label: 'subTickLineLength'
}
},
{
title: 'subTickLineLength',
type: 'number',
typeConfig: {
label: 'subTickLineLength'
}
},
{
title: 'subTickLineCount',
type: 'number',
typeConfig: {
label: 'subTickLineCount'
}
},
{
title: 'color',
type: 'color',
typeConfig: {
label: 'color'
}
}
]
},
{
title: 'regressionLine',
elements: [
{
title: 'color',
type: 'color'
},
{
title: 'lineWidth',
type: 'number'
}
]
},
{
title: 'points',
elements: [
{
title: 'number of rooms',
type: 'select',
typeConfig: {
label: 'dots highighted are representative of X rooms:',
placeholder: 'selections',
data: [
{ value: 1, label: '1' },
{ value: 2, label: '2' },
{ value: 3, label: '3' },
{ value: 4, label: '4' }
]
}
},
{
title: 'color',
type: 'color'
},
{
title: 'fillOpacity',
type: 'slider'
},
{
title: 'radius',
type: 'number'
}
]
},
{
title: 'area',
elements: [
{
title: 'color',
type: 'color'
}
]
}
]
type downloadHandlerType = (svg: string) => void
export const Scatter = () => {
const { classes } = useStyles()
const [downloadingState, setDownloadingState] = useState(false)
const downloadSVGHandler: downloadHandlerType = (svg: string) => {
const preface = '<?xml version="1.0" standalone="no"?>
'
const svgBlob = new Blob([preface, svg], {
type: 'image/svg+xml;charset=utf-8'
})
const svgUrl = URL.createObjectURL(svgBlob)
const downloadLink = document.createElement('a')
downloadLink.href = svgUrl
downloadLink.download = 'chart.svg'
document.body.appendChild(downloadLink)
downloadLink.click()
document.body.removeChild(downloadLink)
downloadFinishHandler()
}
const downloadFinishHandler = () => {
setDownloadingState(false)
}
const startDownloadHandler = () => {
setDownloadingState(true)
}
const setX = (a: any) => {
let x: any
a === 'durationDays' ? (x = 'x') : (x = 'x1')
return x
}
const [chartSettingsDirect, setChartSettingsDirect] = useState(
modelChartSettingsObject
)
const applySettings = () => {
let cloneBeforeSet: any = { ...chartSettingsDirect }
cloneBeforeSet.rerender = !cloneBeforeSet.rerender
setChartSettingsDirect(cloneBeforeSet)
}
useEffect(() => {
console.log('chart settings set directly: ', chartSettingsDirect)
}, [chartSettingsDirect])
return (
<Stack>
<Grid gutter={40}>
<Grid.Col span={12}>
<Paper shadow='xs' radius='md' p='xl'>
<Title order={1}>Area 2.0 - Scatter</Title>
<Box
sx={{
display: 'flex',
justifyContent: 'flex-end',
gap: '1rem'
}}
>
<Button
leftIcon={<PlayerPlay size={14} />}
onClick={() => {
applySettings()
}}
>
render chart(s)
</Button>
<Button
onClick={() => {
startDownloadHandler()
}}
>
Download SVG
</Button>
</Box>
<ChartSettings
// loadChartSettingsUp={setState}
setChartSettingsDirect={setChartSettingsDirect}
chartSettingsDirect={chartSettingsDirect}
settingsConfigMain={settingsConfig}
/>
</Paper>
</Grid.Col>
<Grid.Col span={12}>
<Paper
className={classes.ChartContainer}
shadow='xs'
radius='md'
p='xl'
>
<Center sx={{ height: '100%' }}>
<Box sx={{ width: '864px', height: '446px' }}>
<ScatterChart
settings={chartSettingsDirect}
// settings={state}
downloadingState={downloadingState}
handleDownload={downloadSVGHandler}
// x='x1'
x={setX(modelChartSettingsObject.axis.xExplanatoryVariable)}
y='y' //priceEur
/>
</Box>
</Center>
</Paper>
</Grid.Col>
</Grid>
</Stack>
)
}
CHARTSETTINGS' code:
import React, { FC } from 'react'
import { Accordion, Title, Grid, Tabs, Box } from '@mantine/core'
import {
ColorRenderer,
DataRenderer,
NumberInputRenderer,
SliderRenderer,
SwitcherRenderer,
SelectRenderer
} from './renderer'
import { useChartSettings } from '@/context/chartSettingsContext'
const mappingObject: any = {
color: ColorRenderer,
data: DataRenderer,
number: NumberInputRenderer,
slider: SliderRenderer,
switcher: SwitcherRenderer,
select: SelectRenderer
}
const SettingsComponent = ({
settingsConfig,
firstLevel,
parent,
setChartSettingsDirect,
chartSettingsDirect
}: any) => {
const hasChildren = settingsConfig?.elements?.length
const { type, title, typeConfig } = settingsConfig
const SettingsItem = type ? mappingObject[type] : null
return (
<>
{type && (
<SettingsItem
title={title}
typeConfig={typeConfig}
parent={parent}
setChartSettingsDirect={setChartSettingsDirect}
chartSettingsDirect={chartSettingsDirect}
/>
)}
<Box sx={{ padding: 2 }}>
{hasChildren &&
(firstLevel && settingsConfig.elements.length > 1 ? (
<Accordion>
{settingsConfig.elements.map((configItem: any) => (
<Accordion.Item label={configItem.title}>
<SettingsComponent
settingsConfig={configItem}
parent={parent}
setChartSettingsDirect={setChartSettingsDirect}
chartSettingsDirect={chartSettingsDirect}
/>
</Accordion.Item>
))}
</Accordion>
) : (
<Grid>
{settingsConfig.elements.map((configItem: any) => (
<Grid.Col span={settingsConfig.elements.length > 1 ? 6 : 12}>
{firstLevel && <Title order={6}>{configItem.title}</Title>}
<SettingsComponent
setChartSettingsDirect={setChartSettingsDirect}
chartSettingsDirect={chartSettingsDirect}
settingsConfig={configItem}
parent={parent}
/>
</Grid.Col>
))}
</Grid>
))}
</Box>
</>
)
}
export const ChartSettings: FC<any> = ({
settingsConfigMain,
setChartSettingsDirect,
chartSettingsDirect
}) => {
const chartSettings = useChartSettings()
return (
<Box>
<Tabs>
{settingsConfigMain.map((configItem: any) => (
<Tabs.Tab label={configItem.title}>
<SettingsComponent
settingsConfig={configItem}
firstLevel
parent={configItem.title}
setChartSettingsDirect={setChartSettingsDirect}
chartSettingsDirect={chartSettingsDirect}
/>
</Tabs.Tab>
))}
</Tabs>
</Box>
)
}
SAMPLE INPUT COMPONENT's code:
(SELECT)
import React, { FC, useEffect, useState } from 'react'
import { Select, Box } from '@mantine/core'
export const SelectRenderer: FC<any> = ({
title,
parent,
typeConfig,
setChartSettingsDirect,
chartSettingsDirect
}) => {
const [selection, setSelection] = useState('durationDays')
useEffect(() => {
let cloneBeforeSet: any = { ...chartSettingsDirect }
cloneBeforeSet[parent][title] = selection
setChartSettingsDirect(cloneBeforeSet)
}, [selection])
return (
<Box>
<Select value={selection} {...typeConfig} onChange={setSelection} />
</Box>
)
}