Skip to content

Commit

Permalink
some more burndown chart improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
ghackenberg committed May 29, 2024
1 parent b6b0d2f commit b1d8574
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const DateInput = (props: {class?: string, label: string, change?: (value
const required = props.required

function onChange(event: React.ChangeEvent<HTMLInputElement>) {
props.change && props.change(new Date(event.currentTarget.valueAsNumber))
console.log(event.currentTarget.value)
props.change && props.change(new Date(event.currentTarget.value))
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { UserContext } from '../../contexts/User'
import { useIssue, useProduct } from '../../hooks/entity'
import { useComments, useMembers } from '../../hooks/list'
import { Part, collectParts } from '../../functions/markdown'
import { formatDateTime } from '../../functions/time'
import { formatDateHourMinute } from '../../functions/time'
import { computePath } from '../../functions/path'
import { LegalFooter } from '../snippets/LegalFooter'
import { ProductFooter, ProductFooterItem } from '../snippets/ProductFooter'
Expand Down Expand Up @@ -177,7 +177,7 @@ export const ProductIssueCommentView = () => {
<ProductUserPicture productId={productId} userId={issue.userId} class='icon small round'/>
<span> </span>
<ProductUserName productId={productId} userId={issue.userId}/>
<span> created this issue on <span className='date'>{formatDateTime(new Date(issue.created))}</span></span>
<span> created this issue on <span className='date'>{formatDateHourMinute(new Date(issue.created))}</span></span>
</p>
<p>
<span className={`state badge ${issue.state == 'open' ? 'red' : 'green'}`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { UserContext } from '../../contexts/User'
import { useProduct } from '../../hooks/entity'
import { useAsyncHistory } from '../../hooks/history'
import { useMilestones, useMembers } from '../../hooks/list'
import { formatDateTime } from '../../functions/time'
import { formatDateHourMinute } from '../../functions/time'
import { LegalFooter } from '../snippets/LegalFooter'
import { ProductFooter, ProductFooterItem } from '../snippets/ProductFooter'
import { MilestoneProgressWidget } from '../widgets/MilestoneProgress'
Expand Down Expand Up @@ -67,12 +67,12 @@ export const ProductMilestoneView = () => {
) },
{ label: 'Start', class: 'nowrap center', content: milestone => (
<span className='badge stroke'>
{formatDateTime(new Date(milestone.start))}
{formatDateHourMinute(new Date(milestone.start))}
</span>
) },
{ label: 'End', class: 'nowrap center', content: milestone => (
<span className='badge stroke'>
{formatDateTime(new Date(milestone.end))}
{formatDateHourMinute(new Date(milestone.end))}
</span>
) },
{ label: 'Open', class: 'center', content: milestone => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useAsyncHistory } from '../../hooks/history'
import { useMembers, useIssues } from '../../hooks/list'
import { useIssuesComments } from '../../hooks/map'
import { calculateActual } from '../../functions/burndown'
import { formatDateTime } from '../../functions/time'
import { formatDateHourMinute } from '../../functions/time'
import { PartCount } from '../counts/Parts'
import { LegalFooter } from '../snippets/LegalFooter'
import { ProductFooter, ProductFooterItem } from '../snippets/ProductFooter'
Expand Down Expand Up @@ -200,16 +200,16 @@ export const ProductMilestoneIssueView = () => {
<span> </span>
<ProductUserName productId={productId} userId={milestone.userId}/>
<span> created this milestone on </span>
<span className='date'>{formatDateTime(new Date(milestone.created))}</span>
<span className='date'>{formatDateHourMinute(new Date(milestone.created))}</span>
</p>
<p style={{color: 'gray'}}>
<span>This milestone starts on</span>
<span className='badge stroke'>
{formatDateTime(new Date(milestone.start))}
{formatDateHourMinute(new Date(milestone.start))}
</span>
<span style={{marginLeft: '0.5em'}}>and ends on</span>
<span className='badge stroke'>
{formatDateTime(new Date(milestone.end))}
{formatDateHourMinute(new Date(milestone.end))}
</span>
</p>
{contextUser ? (
Expand Down
122 changes: 111 additions & 11 deletions packages/frontend/src/scripts/components/widgets/BurndownChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,127 @@ import { useState } from 'react'

import { CartesianGrid, Legend, Line, LineChart, ReferenceLine, ResponsiveContainer, XAxis, YAxis } from 'recharts'

/*
import { formatDate, formatDateHour, formatDateHourMinute, formatMonth } from '../../functions/time'

function monthTickFormatter(time: number) {
return formatMonth(new Date(time))
}

function dateTickFormatter(time: number) {
return formatDate(new Date(time))
}

function dateTimeTickFormatter(time: number) {
return formatDateTime(new Date(time))
function dateHourTickFormatter(time: number) {
return formatDateHour(new Date(time))
}

function dateHourMinuteTickFormatter(time: number) {
return formatDateHourMinute(new Date(time))
}
*/

export const BurndownChartWidget = (props: { start: number, end: number, total: number, actual: { time: number, actual: number }[] }) => {
// CONSTANTS

const start = props.start
const end = props.end
let start = props.start
let end = props.end

const span = end - start
const now = Date.now()

const startDate = new Date(start)
const endDate = new Date(end)

let step: number

if (span <= 1000 * 60 * 60) {

step = 1000 * 60

startDate.setMinutes(startDate.getMinutes(), 0, 0)
start = startDate.getTime()

endDate.setMinutes(endDate.getMinutes() + 1, 0, 0)
end = endDate.getTime()

} else if (span <= 1000 * 60 * 60 * 24) {

step = 1000 * 60 * 60

startDate.setHours(startDate.getHours(), 0, 0, 0)
start = startDate.getTime()

endDate.setHours(endDate.getHours() + 1, 0, 0, 0)
end = endDate.getTime()

} else if (span <= 1000 * 60 * 60 * 24 * 48) {

step = 1000 * 60 * 60 * 24

startDate.setDate(startDate.getDate())
startDate.setHours(0, 0, 0, 0)
start = startDate.getTime()

endDate.setDate(endDate.getDate() + 1)
endDate.setHours(0, 0, 0, 0)
end = endDate.getTime()

} else {

startDate.setMonth(startDate.getMonth(), 2)
startDate.setHours(0, 0, 0, 0)
start = startDate.getTime()

endDate.setMonth(endDate.getMonth() + 1, 2)
endDate.setHours(0, 0, 0, 0)
end = endDate.getTime()

}

const total = props.total
const actual = props.actual

const padding = 50

const domain = [start, end]

const ticks = []
if (step) {
for (let i = start; i <= end; i += step) {
ticks.push(i)
}
} else {
while (startDate <= endDate) {
ticks.push(startDate.getTime())
startDate.setMonth(startDate.getMonth() + 1)
}
}

let tickFormatter: (timestamp: number) => string
if (step) {
if (step <= 1000 * 60) {
tickFormatter = dateHourMinuteTickFormatter
} else if (step <= 1000 * 60 * 60) {
tickFormatter = dateHourTickFormatter
} else {
tickFormatter = dateTickFormatter
}
} else {
tickFormatter = monthTickFormatter
}

let height: number
if (step) {
if (step <= 1000 * 60) {
height = 150
} else if (step <= 1000 * 60 * 60) {
height = 130
} else {
height = 90
}
} else {
height = 70
}

// INITIAL STATES

const initialTarget = [{ time: start, target: total }, { time: end, target: 0 }]
Expand All @@ -35,8 +135,8 @@ export const BurndownChartWidget = (props: { start: number, end: number, total:
// EFFECTS

React.useEffect(() => {
setTarget([{ time: start, target: total }, { time: end, target: 0 }])
}, [start, end, total])
setTarget([{ time: props.start, target: total }, { time: props.end, target: 0 }])
}, [props.start, props.end, total])

// RETURN

Expand All @@ -45,14 +145,14 @@ export const BurndownChartWidget = (props: { start: number, end: number, total:
<ResponsiveContainer>
<LineChart>
<CartesianGrid/>
<XAxis name='Time' dataKey='time' type='number' domain={[start, end]} scale='time' interval={0} angle={-45} textAnchor='end' padding={{left: padding, right: padding}}/>
<XAxis name='Time' dataKey='time' type='number' scale='time' domain={domain} ticks={ticks} tickFormatter={tickFormatter} height={height} angle={-45} textAnchor='end' padding={{left: padding, right: padding}}/>
<YAxis name='Open issue count' dataKey='target' domain={[0, total]} allowDecimals={false} interval={0} tickFormatter={value => `${Math.round(value)}`} padding={{top: padding}}/>
<Legend/>
{now >= start && now <= end && (
<ReferenceLine x={now} label={{value: 'Now', position: now <= (start + end) / 2 ? 'right' : 'left', fill: 'black'}} stroke='gray' strokeWidth={2} strokeDasharray='6 6'/>
)}
<ReferenceLine x={start} label={{value: 'Start', position: 'left', fill: 'darkred'}} stroke='red' strokeWidth={2} strokeDasharray='6 6'/>
<ReferenceLine x={end} label={{value: 'End', position: 'right', fill: 'darkred'}} stroke='red' strokeWidth={2} strokeDasharray='6 6'/>
<ReferenceLine x={props.start} label={{value: 'Start', position: 'left', fill: 'darkred'}} stroke='red' strokeWidth={2} strokeDasharray='6 6'/>
<ReferenceLine x={props.end} label={{value: 'End', position: 'right', fill: 'darkred'}} stroke='red' strokeWidth={2} strokeDasharray='6 6'/>
<ReferenceLine y={total} label={{value: 'Total', position: 'top', fill: 'darkred'}} stroke='red' strokeWidth={2} strokeDasharray='6 6'/>
<Line name='Target burndown' isAnimationActive={false} data={target} dataKey='target' stroke='green' strokeWidth={2} strokeDasharray='6 6' dot={{fill: 'rgb(215,215,215)', stroke: 'green', strokeDasharray: ''}}/>
<Line name='Actual burndown' isAnimationActive={false} data={actual} dataKey='actual' stroke='blue' strokeWidth={2} dot={{fill: 'blue', stroke: 'blue'}}/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { VersionContext } from '../../contexts/Version'
import { useComment, useIssue } from '../../hooks/entity'
import { useMembers, useVersions } from '../../hooks/list'
import { collectParts, createProcessor } from '../../functions/markdown'
import { formatDateTime } from '../../functions/time'
import { formatDateHourMinute } from '../../functions/time'
import { computePath } from '../../functions/path'
import { ProductUserPicture } from '../values/ProductUserPicture'
import { ProductUserName } from '../values/ProductUserName'
Expand Down Expand Up @@ -310,7 +310,7 @@ export const CommentView = (props: { productId: string, issueId: string, comment
<p>
{comment ? (
<>
<strong><ProductUserName userId={userId} productId={productId}/></strong> commented on {formatDateTime(new Date(comment.created))} {upload ? 'uploading ...' : action}
<strong><ProductUserName userId={userId} productId={productId}/></strong> commented on {formatDateHourMinute(new Date(comment.created))} {upload ? 'uploading ...' : action}
</>
) : (
<>
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/scripts/functions/burndown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function calculateActual(startDate: number, endDate: number, issues: Issu
actual.push({ time: startDate, actual: 0 })
}
if (actual[actual.length - 1].time < endDate) {
actual.push({ time: endDate, actual: actual[actual.length - 1].actual})
actual.push({ time: Math.min(endDate, Date.now()), actual: actual[actual.length - 1].actual})
}

return actual
Expand Down
10 changes: 9 additions & 1 deletion packages/frontend/src/scripts/functions/time.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
export function formatMonth(date: Date) {
return date.toISOString().substring(0,7)
}

export function formatDate(date: Date) {
return date.toISOString().substring(0,10)
}

export function formatDateTime(date: Date) {
export function formatDateHour(date: Date) {
return `${formatDate(date)} @ ${date.toLocaleTimeString().substring(0,5)}`
}

export function formatDateHourMinute(date: Date) {
return `${formatDate(date)} @ ${date.toLocaleTimeString().substring(0,5)}`
}

0 comments on commit b1d8574

Please sign in to comment.