Skip to content
Snippets Groups Projects
Commit a8b93f06 authored by Yoan VALLET's avatar Yoan VALLET
Browse files

feat: change chartduel to duelchart

parent abd5985a
Branches
Tags
1 merge request!163Features/us283 new chart duel
......@@ -11,6 +11,7 @@ interface AxisRightProps {
width: number
marginRight: number
marginTop: number
displayMultifluid: boolean
}
const AxisRight = ({
......@@ -19,12 +20,16 @@ const AxisRight = ({
width,
marginRight,
marginTop,
displayMultifluid,
}: AxisRightProps) => {
const { t } = useI18n()
const isHome: boolean = !window.location.hash.split('/')[2] ? true : false
const fluidStyle =
fluidTypes.length > 1 || isHome ? 'MULTIFLUID' : FluidType[fluidTypes[0]]
fluidTypes.length > 1 || displayMultifluid
? 'MULTIFLUID'
: FluidType[fluidTypes[0]]
const yAxisRef = useRef<SVGGElement>(null)
const newMarginRight =
fluidTypes.length > 1 || displayMultifluid ? marginRight - 10 : marginRight
const drawYAxis = () => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
......@@ -35,7 +40,11 @@ const AxisRight = ({
.tickSizeOuter(0)
.tickFormat(d =>
d >= 1000
? `${d / 1000} ${t('FLUID.' + fluidStyle + '.MEGAUNIT')}`
? typeof d === 'number'
? `${d / 1000} ${t('FLUID.' + fluidStyle + '.MEGAUNIT')}`
: `${d.valueOf() / 1000} ${t(
'FLUID.' + fluidStyle + '.MEGAUNIT'
)}`
: d === 0
? `${d}`
: `${d} ${t('FLUID.' + fluidStyle + '.UNIT')}`
......@@ -53,7 +62,7 @@ const AxisRight = ({
<g
className="axis y"
ref={yAxisRef}
transform={`translate(${width - marginRight}, ${marginTop})`}
transform={`translate(${width - newMarginRight}, ${marginTop})`}
/>
)
}
......
......@@ -127,23 +127,14 @@ const BarChart: React.FC<BarChartProps> = (props: BarChartProps) => {
return (
<svg width={width} height={height}>
{fluidTypes.length > 1 ? (
<AxisRight
fluidTypes={fluidTypes}
yScale={yScale}
width={width}
marginRight={marginRight - 10}
marginTop={marginTop}
/>
) : (
<AxisRight
fluidTypes={fluidTypes}
yScale={yScale}
width={width}
marginRight={marginRight}
marginTop={marginTop}
/>
)}
<AxisRight
fluidTypes={fluidTypes}
yScale={yScale}
width={width}
marginRight={marginRight}
marginTop={marginTop}
displayMultifluid={isHome}
/>
<g transform={`translate(${marginLeft},${marginTop})`}>
{chartData.actualData.map((d, index) => (
......
......@@ -33,7 +33,7 @@ const ModalGRDF: React.FC<ModalGRDFProps> = ({
useEffect(() => {
status && status === 'idle' && handleCloseClick
}, [status])
}, [status, handleCloseClick])
return (
<>
<Modal open={open} handleCloseClick={handleCloseClick}>
......
import React, { useState, useEffect } from 'react'
import './chartduel.scss'
import ConsumptionService from 'services/consumption.service'
import { TimeStep } from 'enum/timeStep.enum'
import StyledSpinner from 'components/CommonKit/Spinner/StyledSpinner'
import { Datachart, TimePeriod, UserChallenge } from 'models'
import { useClient } from 'cozy-client'
import { DateTime } from 'luxon'
import { FluidType } from 'enum/fluid.enum'
import BarDuel from './BarDuel'
interface ChartReportProps {
userChallenge: UserChallenge
}
const ChartDuel: React.FC<ChartReportProps> = ({
userChallenge,
}: ChartReportProps) => {
const client = useClient()
const timeStep = TimeStep.DAY
const fluidTypes: FluidType[] = [0, 1, 2]
const [width, setWidth] = useState(600)
const [chartData, setChartData] = useState<Datachart>()
const [average, setAverage] = useState<number>(0)
const [uncomingValue, setUncomingValue] = useState<number[]>([])
const [isLoaded, setIsLoaded] = useState<boolean>(false)
const [selectedDate, setSelectedDate] = useState<DateTime>(DateTime.local())
let timePeriod: TimePeriod
const height = 225
const handleClickData = () => {
return false
}
useEffect(() => {
const subscribed = true
if (userChallenge.boss.startDate != null) {
const endDate = DateTime.fromISO(userChallenge.boss.startDate).plus(
userChallenge.boss.duration
)
timePeriod = {
startDate: DateTime.fromISO(userChallenge.boss.startDate),
endDate: endDate,
}
}
async function loadData() {
const consumptionService = new ConsumptionService(client)
const graphData = await consumptionService.getGraphData(
timePeriod,
timeStep,
fluidTypes
)
if (graphData && graphData.actualData.length > 0) {
let i = 0
let total = 0
const localUncomingValues: number[] = []
graphData.actualData.map((data, index) => {
if (data.value !== 0) {
total += data.value
i++
} else {
const dateDiff = DateTime.local()
.diff(data.date, 'days')
.toObject()
if (0 < dateDiff.days && dateDiff.days < 3)
localUncomingValues.push(index)
}
})
setUncomingValue(localUncomingValues)
setAverage(total / i)
}
if (subscribed && graphData && graphData.actualData.length > 0) {
setChartData(graphData)
setIsLoaded(true)
}
}
setIsLoaded(false)
loadData()
}, [])
return (
<div className="">
{!isLoaded ? (
<div className="chart-loading">
<StyledSpinner size="5em" fluidTypes={fluidTypes} isHome={false} />
</div>
) : (
<BarDuel
chartData={chartData}
fluidTypes={fluidTypes}
average={average}
uncomingValues={uncomingValue}
timeStep={TimeStep.DAY}
multiFluid={false}
selectedDate={selectedDate}
showCompare={false}
handleClickData={handleClickData}
height={height}
width={width}
isSwitching={false}
isHome={false}
/>
)}
</div>
)
}
export default ChartDuel
import React from 'react'
import { useSelector } from 'react-redux'
import { AppStore } from 'store'
import { scaleBand, ScaleBand, scaleLinear, ScaleLinear } from 'd3-scale'
import { DateTime } from 'luxon'
import { FluidType } from 'enum/fluid.enum'
import { TimeStep } from 'enum/timeStep.enum'
import { Datachart, Dataload } from 'models'
import { Dataload, UserChallenge } from 'models'
import Bar from 'components/Charts/Bar'
import AxisBottom from 'components/Charts/AxisBottom'
......@@ -12,15 +14,9 @@ import AxisRight from 'components/Charts/AxisRight'
import UncomingBar from 'components/Charts/UncomingBar'
export interface BarChartProps {
chartData: Datachart
fluidTypes: FluidType[]
userChallenge: UserChallenge
average: number
uncomingValues: number[]
timeStep: TimeStep
handleClickData: (
dataload: Dataload,
compareDataload: Dataload | null
) => void
width?: number
height?: number
marginLeft?: number
......@@ -40,14 +36,11 @@ interface DefaultProps {
type PropsWithDefaults = BarChartProps & DefaultProps
const BarDuel: React.FC<BarChartProps> = (props: BarChartProps) => {
const DuelBar: React.FC<BarChartProps> = (props: BarChartProps) => {
const {
chartData,
userChallenge,
timeStep,
fluidTypes,
average,
uncomingValues,
handleClickData,
width,
height,
marginLeft,
......@@ -55,7 +48,12 @@ const BarDuel: React.FC<BarChartProps> = (props: BarChartProps) => {
marginTop,
marginBottom,
} = props as PropsWithDefaults
const { currentDataload } = useSelector(
(state: AppStore) => state.ecolyo.challenge
)
const falseDateTime = DateTime.fromISO('1000-10-01T00:00:00.000')
const getContentWidth = () => {
return width - marginLeft - marginRight
}
......@@ -65,15 +63,15 @@ const BarDuel: React.FC<BarChartProps> = (props: BarChartProps) => {
}
const getMaxLoad = () => {
const maxLoad = chartData.actualData
? Math.max(...chartData.actualData.map(d => d.value))
const maxLoad = currentDataload
? Math.max(...currentDataload.map((d: Dataload) => d.value))
: 0
return maxLoad
}
const xScale: ScaleBand<string> = scaleBand()
.domain(
chartData.actualData.map(d =>
currentDataload.map((d: Dataload) =>
d.date.toLocaleString(DateTime.DATETIME_SHORT)
)
)
......@@ -81,28 +79,42 @@ const BarDuel: React.FC<BarChartProps> = (props: BarChartProps) => {
.padding(0.2)
const yScale: ScaleLinear<number, number> = scaleLinear()
.domain([0, getMaxLoad()])
.domain([0, getMaxLoad() > average ? getMaxLoad() : average + 2])
.range([getContentHeight(), 0])
const isUpcoming = (dataload: Dataload): boolean => {
let completedValueDetail = true
if (dataload.valueDetail) {
dataload.valueDetail.forEach(value => {
if (!value) completedValueDetail = false
})
}
if (!dataload.valueDetail || !completedValueDetail) {
const dateDiff = DateTime.local()
.setZone('utc', { keepLocalTime: true })
.diff(dataload.date, 'days')
.toObject()
if (
dateDiff.days !== undefined &&
userChallenge !== undefined &&
0 < dateDiff.days
) {
return true
}
}
return false
}
return (
<svg width={width} height={height}>
{fluidTypes.length > 1 ? (
<AxisRight
fluidTypes={fluidTypes}
yScale={yScale}
width={width}
marginRight={marginRight - 10}
marginTop={marginTop}
/>
) : (
<AxisRight
fluidTypes={fluidTypes}
yScale={yScale}
width={width}
marginRight={marginRight}
marginTop={marginTop}
/>
)}
<AxisRight
fluidTypes={userChallenge.boss.fluidTypes}
yScale={yScale}
width={width}
marginRight={marginRight - 10}
marginTop={marginTop}
displayMultifluid={true}
/>
<g transform={`translate(${marginLeft},${marginTop})`}>
<line
......@@ -114,24 +126,20 @@ const BarDuel: React.FC<BarChartProps> = (props: BarChartProps) => {
y2="0"
className="bar-average"
/>
{chartData.actualData.map((d, index) => {
if (!uncomingValues.includes(index)) {
{currentDataload.map((d: Dataload, index: number) => {
if (!isUpcoming(d)) {
return (
<Bar
key={index}
index={index}
dataload={d}
compareDataload={
chartData.comparisonData && chartData.comparisonData[index]
? chartData.comparisonData[index]
: null
}
fluidTypes={fluidTypes}
compareDataload={null}
fluidTypes={userChallenge.boss.fluidTypes}
timeStep={timeStep}
multiFluid={false}
selectedDate={falseDateTime}
showCompare={false}
handleClickData={handleClickData}
handleClickData={() => false}
xScale={xScale}
yScale={yScale}
height={getContentHeight()}
......@@ -155,7 +163,7 @@ const BarDuel: React.FC<BarChartProps> = (props: BarChartProps) => {
})}
</g>
<AxisBottom
data={chartData.actualData}
data={currentDataload}
timeStep={timeStep}
xScale={xScale}
height={height}
......@@ -168,7 +176,7 @@ const BarDuel: React.FC<BarChartProps> = (props: BarChartProps) => {
)
}
BarDuel.defaultProps = {
DuelBar.defaultProps = {
width: 600,
height: 400,
marginLeft: 10,
......@@ -177,4 +185,4 @@ BarDuel.defaultProps = {
marginBottom: 50,
}
export default BarDuel
export default DuelBar
import React, { useState, useEffect } from 'react'
import './duelChart.scss'
import { TimeStep } from 'enum/timeStep.enum'
import { UserChallenge } from 'models'
import DuelBar from 'components/Duel/DuelBar'
interface DuelChartProps {
userChallenge: UserChallenge
width: number
height: number
}
const DuelChart: React.FC<DuelChartProps> = ({
userChallenge,
width,
height,
}: DuelChartProps) => {
const [average, setAverage] = useState<number>(0)
useEffect(() => {
setAverage(userChallenge.boss.threshold / userChallenge.boss.duration.days)
}, [userChallenge.boss.duration.days, userChallenge.boss.threshold])
return (
<div className="fs-slide">
<DuelBar
userChallenge={userChallenge}
average={average}
timeStep={TimeStep.DAY}
height={height}
width={width}
/>
</div>
)
}
export default DuelChart
import React, { useCallback, useEffect, useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useHistory } from 'react-router-dom'
import './duelOngoing.scss'
import { Client, useClient } from 'cozy-client'
......@@ -19,7 +19,7 @@ import CaptionAverageIcon from 'assets/icons/visu/duel/captionAverage.svg'
import CaptionConsumptionIcon from 'assets/icons/visu/duel/captionConsumption.svg'
import CaptionIncomingIcon from 'assets/icons/visu/duel/captionIncoming.svg'
import ChallengeService from 'services/challenge.service'
import ChartDuel from 'components/Duel/ChartDuel'
import DuelChart from 'components/Duel/DuelChart'
import DuelResultModal from 'components/Duel/DuelResultModal'
interface DuelOngoingProps {
......@@ -38,6 +38,9 @@ const DuelOngoing: React.FC<DuelOngoingProps> = ({
const history = useHistory()
const [resultModal, setResultModal] = useState<boolean>(false)
const [winChallenge, setWinChallenge] = useState<boolean>(false)
const [width, setWidth] = useState<number>(0)
const [height, setHeight] = useState<number>(0)
const chartContainer = useRef<HTMLDivElement>(null)
const boss: UserBoss = userChallenge.boss
const title: string = boss.title
......@@ -62,6 +65,28 @@ const DuelOngoing: React.FC<DuelOngoingProps> = ({
history.push('/challenges')
}, [client, dispatch, userChallenge, history, winChallenge])
useEffect(() => {
function handleResize() {
const maxWidth = 940
const maxHeight = 300
const _width = chartContainer.current
? chartContainer.current.offsetWidth > maxWidth
? maxWidth
: chartContainer.current.offsetWidth
: 400
setWidth(_width)
const _height = chartContainer.current
? chartContainer.current.offsetHeight > maxHeight
? maxHeight
: chartContainer.current.offsetHeight
: 300
setHeight(_height)
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
useEffect(() => {
const challengeService = new ChallengeService(client)
let subscribed = true
......@@ -96,8 +121,12 @@ const DuelOngoing: React.FC<DuelOngoingProps> = ({
<span className="consumption">{userConsumption}</span>
{` / ${average} €`}
</div>
<div className="boss-chart">
<ChartDuel userChallenge={userChallenge} />
<div className="boss-chart fs-root" ref={chartContainer}>
<DuelChart
userChallenge={userChallenge}
width={width}
height={height}
/>
</div>
<div className="boss-chart-caption text-15-normal">
<div className="boss-caption">
......
......@@ -28,6 +28,7 @@
display: flex;
align-items: center;
justify-content: center;
width: 80%;
}
.boss-chart-caption{
display: flex;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment