diff --git a/src/charts/ChartTimeline/ChartTimeline.js b/src/charts/ChartTimeline/ChartTimeline.js new file mode 100644 index 00000000..2432d4b9 --- /dev/null +++ b/src/charts/ChartTimeline/ChartTimeline.js @@ -0,0 +1,103 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@mui/styles'; +import dayjs from 'dayjs'; +import { LineChart, Line, XAxis, ResponsiveContainer, ReferenceDot, ReferenceArea } from 'recharts'; + +const useStyles = makeStyles({ + container: { + backgroundColor: '#1C1C20', + width: '100%', + height: 64, + }, +}); + +const ChartTimeline = ({ chartData, markers, currentTimestep, setCurrentTimestep, startDate, endDate }) => { + const classes = useStyles(); + + const data = chartData.map((value, index) => ({ + index, + value, + })); + + const totalSteps = chartData.length; + const numberOfTicks = 12; + + const ticks = Array.from({ length: numberOfTicks }, (_, i) => + Math.floor((i * (totalSteps - 1)) / (numberOfTicks - 1)) + ); + + const formatDateTick = (index) => { + if (!startDate || !endDate || !chartData.length) return ''; + + const start = dayjs(startDate); + const end = dayjs(endDate); + const totalSteps = chartData.length; + + if (index < 0 || index >= totalSteps) return ''; + + const totalDurationMs = end.valueOf() - start.valueOf(); + const msPerStep = totalDurationMs / (totalSteps - 1); + const dateForTick = start.add(msPerStep * index, 'millisecond'); + return dateForTick.isValid() ? dateForTick.format('MMM') : ''; + }; + + const handleClick = (e) => { + if (e?.activeLabel != null) { + setCurrentTimestep(e.activeLabel); + } + }; + + return ( +
+ + + + + + + {markers.map((markerIndex) => { + const markerData = data[markerIndex]; + if (!markerData) return null; + return ( + } + /> + ); + })} + + + + +
+ ); +}; + +ChartTimeline.propTypes = { + chartData: PropTypes.arrayOf(PropTypes.number).isRequired, + markers: PropTypes.arrayOf(PropTypes.number), + currentTimestep: PropTypes.number.isRequired, + setCurrentTimestep: PropTypes.func.isRequired, + startDate: PropTypes.instanceOf(Date).isRequired, + endDate: PropTypes.instanceOf(Date).isRequired, +}; + +ChartTimeline.defaultProps = { + markers: [], +}; + +export default ChartTimeline; diff --git a/src/charts/ChartTimeline/index.js b/src/charts/ChartTimeline/index.js new file mode 100644 index 00000000..ebab267f --- /dev/null +++ b/src/charts/ChartTimeline/index.js @@ -0,0 +1 @@ +export { default as ChartTimeline } from './ChartTimeline'; diff --git a/src/charts/index.js b/src/charts/index.js index f7c28cb8..87ac5803 100644 --- a/src/charts/index.js +++ b/src/charts/index.js @@ -5,3 +5,4 @@ export { CytoViz } from './CytoViz'; export { Dashboard } from './Dashboard'; export { ScenarioManagerTreeList } from './ScenarioManagerTreeList'; export { SimplePowerBIReportEmbed } from './SimplePowerBIReportEmbed'; +export { ChartTimeline } from './ChartTimeline'; diff --git a/src/index.js b/src/index.js index 73e9a742..5b4737dd 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,7 @@ export { CreateScenarioButton, SelfDestructLinkButton, SignInButton, RolesEditionButton } from './buttons'; export { ScenarioNode, ResourceCard } from './cards'; -export { CytoViz, Dashboard, ScenarioManagerTreeList, SimplePowerBIReportEmbed } from './charts'; +export { CytoViz, Dashboard, ScenarioManagerTreeList, SimplePowerBIReportEmbed, ChartTimeline } from './charts'; export { SimpleTwoActionsDialog, DontAskAgainDialog } from './dialogs'; export { HierarchicalComboBox,