Я использую FlexibleWidthXYPlot
внутри компонента, который отображается условно, если компонент должен быть видимым и данные (через реквизиты) определены.
Но react-vis
вычисляет ширину диаграммы до width=0
(поэтому диаграмма не отображается). Я могу заставить его правильно рассчитать ширину, перерисовав следующим образом:
width
prop с фиксированным значением.Я не могу сделать 2. график должен быть отзывчивым. Итак, я попытался вызвать изменение размера на componentDidMount
, но тоже безуспешно:
public componentDidMount() {
window.dispatchEvent(new Event('resize'));
}
Я устанавливаю xDomain
вручную, поэтому попробовал dontCheckIfEmpty = {true}
, но он тоже не работает ...
Почему FlexibleWidthXYPlot
вычисляет ширину по сравнению с 0
? И как это исправить?
EconomyOverview.tsx
import React from 'react'
import { Theme, createStyles, withStyles, WithStyles, Card } from '@material-ui/core'
import SelectableTabs from './SelectableTabs'
import PaymentsChart from './PaymentsChart'
import MileageChart from './MileageChart'
import { IMileageChart, IPaymentChart } from '@omnicar/sam-types'
import * as api from 'api/api'
import notify from 'utils/notify/notify'
interface IProps {
id: string
}
type TProps = IProps & WithStyles<typeof styles>
export type TTab = 'payments' | 'mileage'
interface IState {
activeTab: TTab
paymentChartData: IPaymentChart | undefined
mileageChartData: IMileageChart | undefined
}
const styles = ({ spacing }: Theme) =>
createStyles({
root: {
display: 'flex',
},
tabs: {
width: '35%',
display: 'flex',
},
content: {
width: '65%',
padding: spacing.unit * 2,
},
})
class ContractDetailsEconomyOverview extends React.Component<TProps, IState> {
public state: IState = {
activeTab: 'payments',
paymentChartData: undefined,
mileageChartData: undefined,
}
public componentDidMount() {
this.loadData()
}
public render() {
const { classes } = this.props
const { activeTab, paymentChartData, mileageChartData } = this.state
return (
<Card className = {classes.root}>
<div className = {classes.tabs}>
<SelectableTabs
data = {{ paymentChartData, mileageChartData }}
activeTab = {activeTab}
onTabSelect = {this.handleTabSelect}
/>
</div>
<div className = {classes.content}>
{activeTab === 'payments' && <PaymentsChart data = {paymentChartData} />}
{activeTab === 'mileage' && <MileageChart data = {mileageChartData} />}
</div>
</Card>
)
}
private handleTabSelect = (activeTab: TTab) => {
this.setState({ activeTab })
}
private loadData = async () => {
const res = await api.getContractDetailsChartData(this.props.id)
if (res.errorData || res.networkError) {
notify.error()
} else if (res.data) {
const { paymentChart, mileageChart } = res.data
this.setState({ paymentChartData: paymentChart, mileageChartData: mileageChart })
}
}
}
export default withStyles(styles)(ContractDetailsEconomyOverview)
PaymentsChart
import React, { Component } from 'react'
import { withStyles, createStyles, WithStyles } from '@material-ui/core/styles'
import {
FlexibleWidthXYPlot,
YAxis,
XAxis,
HorizontalGridLines,
LineMarkSeries,
LineSeries,
DiscreteColorLegend,
Hint,
} from 'react-vis'
import 'react-vis/dist/style.css'
import { theme } from 'theme'
import { t } from '@omnicar/sam-translate'
import { IPaymentChart, IChartDateValue } from '@omnicar/sam-types'
import { formatDate } from '@omnicar/sam-format'
import Typography from 'components/Typography'
import ChartTooltip from 'components/admin/ChartTooltip'
interface IProps extends WithStyles<typeof styles> {
data: IPaymentChart
}
interface IState {
tooltipValue: IChartDateValue | undefined
}
const styles = () =>
createStyles({
root: {},
tooltip: {
display: 'flex',
},
exVat: {
textAlign: 'right',
color: theme.palette.text.light,
fontStyle: 'italic',
},
legend: {
display: 'flex',
justifyContent: 'space-between',
},
})
class PaymentsChart extends Component<IProps, IState> {
public state: IState = {
tooltipValue: undefined,
}
public legendItems = [
{
title: t('Payments'),
color: '#40A695',
strokeWidth: 3,
},
{
title: t('Expected payments'),
color: '#ccc',
strokeStyle: 'dashed',
strokeWidth: 3,
},
{
title: t('Costs'),
color: '#ED7C33',
strokeWidth: 3,
},
]
public render() {
const { classes, data } = this.props
const { tooltipValue } = this.state
const curveName = 'curveMonotoneX'
// transform data
const earnings = data ? this.transform(data.seriesEarnings) : []
const costs = data ? this.transform(data.seriesCosts) : []
const expectedEarnings = data ? this.transform(data.seriesExpectedEarnings) : []
return (
<div className = {classes.root}>
{data && (
<>
<DiscreteColorLegend items = {this.legendItems} orientation = "horizontal" />
<FlexibleWidthXYPlot
margin = {{ left: 60 }}
height = {300}
xType = "time"
xDomain = {[new Date(data.contractStartDate), new Date(data.contractEndDate)]}
>
<YAxis />
<XAxis />
<HorizontalGridLines />
<LineSeries
data = {expectedEarnings}
stroke = "#ccc"
fill = "#ccc"
strokeWidth = {3}
strokeStyle = "dashed"
size = {3}
curve = {curveName}
/>
<LineMarkSeries
data = {costs}
stroke = "#ED7C33"
fill = "#ED7C33"
strokeWidth = {3}
size = {3}
curve = {curveName}
// tslint:disable-next-line jsx-no-lambda
onValueMouseOver = {(value: IChartDateValue) => {
this.setState({ tooltipValue: value })
}}
// tslint:disable-next-line jsx-no-lambda
onValueMouseOut = {() => {
this.setState({ tooltipValue: undefined })
}}
/>
<LineMarkSeries
data = {earnings}
stroke = "#40A695"
fill = "#40A695"
strokeWidth = {3}
size = {3}
curve = {curveName}
// tslint:disable-next-line jsx-no-lambda
onValueMouseOver = {(value: IChartDateValue) => {
this.setState({ tooltipValue: value })
}}
// tslint:disable-next-line jsx-no-lambda
onValueMouseOut = {() => {
this.setState({ tooltipValue: undefined })
}}
/>
{tooltipValue && (
<Hint value = {tooltipValue}>
<ChartTooltip value = {tooltipValue} />
</Hint>
)}
</FlexibleWidthXYPlot>
<div className = {classes.legend}>
<div>
<Typography variant = "subheading">{t('Startdate')}</Typography>
<Typography variant = "body2">{formatDate(data.contractStartDate)}</Typography>
</div>
<div>
<Typography variant = "subheading">{t('Duration')}</Typography>
<Typography variant = "body2">{`${data.duration.current} / ${data.duration.total}`}</Typography>
</div>
<div>
<Typography variant = "subheading">{t('Enddate')}</Typography>
<Typography variant = "body2">{formatDate(data.contractEndDate)}</Typography>
</div>
</div>
</>
)}
</div>
)
}
/**
* Transforms x from string > Date
*/
private transform = (list: IChartDateValue[]): IChartDateValue[] =>
list.map(value => ({
...value,
x: new Date(value.x),
}))
}
export default withStyles(styles)(PaymentsChart)
У меня сработало добавление реагировать виртуализированный автоматический размер, например:
import React from 'react'
import { makeStyles } from '@material-ui/core/styles'
import { XYPlot, XAxis, YAxis, HorizontalGridLines, LineSeries } from 'react-vis'
import clsx from 'clsx'
import AutoSizer from 'react-virtualized-auto-sizer'
const useStyles = makeStyles({
plot: {
height: 300,
width: '100%'
},
hide: {
display: 'none'
}
})
function TestChart({ data, hidden }) {
const classes = useStyles()
return (
<div
className = {clsx(classes.plot, {
[classes.hide]: hidden
})}
>
<AutoSizer>
{({ width, height }) => (
<XYPlot height = {height} width = {width}>
<HorizontalGridLines />
<LineSeries data = {data} />
<XAxis />
<YAxis />
</XYPlot>
)}
</AutoSizer>
</div>
)
}
export default TestChart
Какими бы ни были ширина и высота родительского div
, размер диаграммы автоматически изменится соответствующим образом.