190 lines
5.8 KiB
JavaScript
190 lines
5.8 KiB
JavaScript
import {
|
|
format,
|
|
isWithinRange,
|
|
addMonths,
|
|
isSameMonth,
|
|
getMonth,
|
|
} from 'date-fns'
|
|
import { abbreviateDollars, formatDollars } from '../../lib/dollars'
|
|
|
|
const TOP_OFFSET = 20
|
|
const BOTTOM_OFFSET = 70
|
|
const CHART_HEIGHT = 360
|
|
|
|
export default {
|
|
name: 'budget-chart',
|
|
props: {
|
|
currentMonth: String,
|
|
expirationDate: String,
|
|
months: Object,
|
|
budget: String,
|
|
},
|
|
|
|
data: function() {
|
|
const heightScale =
|
|
this.budget / (CHART_HEIGHT - TOP_OFFSET - BOTTOM_OFFSET)
|
|
return {
|
|
numMonths: 10,
|
|
focusedMonthPosition: 4,
|
|
height: CHART_HEIGHT,
|
|
heightScale,
|
|
budgetHeight: CHART_HEIGHT - BOTTOM_OFFSET - this.budget / heightScale,
|
|
baseHeight: CHART_HEIGHT - BOTTOM_OFFSET,
|
|
width: 0,
|
|
displayedMonths: [],
|
|
spendPath: '',
|
|
projectedPath: '',
|
|
displayBudget: formatDollars(parseFloat(this.budget)),
|
|
}
|
|
},
|
|
|
|
mounted: function() {
|
|
this._setDisplayedMonths()
|
|
this._setMetrics()
|
|
addEventListener('load', this._setMetrics)
|
|
addEventListener('resize', this._setMetrics)
|
|
},
|
|
|
|
methods: {
|
|
_setMetrics: function() {
|
|
this.width = this.$refs.panel.clientWidth
|
|
this.spendPath = ''
|
|
this.projectedPath = ''
|
|
|
|
let lastSpend = 0
|
|
let lastSpendPoint = ''
|
|
|
|
for (let i = 0; i < this.numMonths; i++) {
|
|
const {
|
|
metrics,
|
|
budget,
|
|
rollingAverage,
|
|
cumulativeTotal,
|
|
} = this.displayedMonths[i]
|
|
const blockWidth = this.width / this.numMonths
|
|
const blockX = blockWidth * i
|
|
const spend = budget && budget.spend ? budget.spend : rollingAverage
|
|
const barHeight = spend / this.heightScale
|
|
lastSpend = spend
|
|
const cumulativeY =
|
|
this.height - cumulativeTotal / this.heightScale - BOTTOM_OFFSET
|
|
const cumulativeX = blockX + blockWidth / 2
|
|
const cumulativePoint = `${cumulativeX} ${cumulativeY}`
|
|
|
|
this.displayedMonths[i].metrics = Object.assign(metrics, {
|
|
blockWidth,
|
|
blockX,
|
|
barHeight,
|
|
barWidth: 30,
|
|
barX: blockX + (blockWidth / 2 - 15),
|
|
barY: this.height - barHeight - BOTTOM_OFFSET,
|
|
cumulativeR: 2.5,
|
|
cumulativeY,
|
|
cumulativeX,
|
|
})
|
|
|
|
if (budget && budget.spend) {
|
|
this.spendPath += this.spendPath === '' ? 'M' : ' L'
|
|
this.spendPath += cumulativePoint
|
|
lastSpendPoint = cumulativePoint
|
|
} else if (lastSpendPoint !== '') {
|
|
this.projectedPath +=
|
|
this.projectedPath === '' ? `M${lastSpendPoint} L` : ' L'
|
|
this.projectedPath += cumulativePoint
|
|
}
|
|
}
|
|
},
|
|
|
|
_setDisplayedMonths: function() {
|
|
const [month, year] = this.currentMonth.split('/')
|
|
const [expYear, expMonth, expDate] = this.expirationDate.split('-') // assumes format 'YYYY-MM-DD'
|
|
const monthsRange = []
|
|
const monthsBack = this.focusedMonthPosition + 1
|
|
const monthsForward = this.numMonths - this.focusedMonthPosition - 1
|
|
|
|
// currently focused date
|
|
const current = new Date(year, month)
|
|
|
|
// starting date of the chart
|
|
const start = addMonths(current, -monthsBack)
|
|
|
|
// ending date of the chart
|
|
const end = addMonths(start, this.numMonths + 1)
|
|
|
|
// expiration date
|
|
const expires = new Date(expYear, expMonth - 1, expDate)
|
|
|
|
// is the expiration date within the displayed date range?
|
|
const expirationWithinRange = isWithinRange(expires, start, end)
|
|
|
|
let rollingAverage = 0
|
|
let cumulativeTotal = 0
|
|
|
|
for (let i = 0; i < this.numMonths; i++) {
|
|
const date = addMonths(start, i)
|
|
const dateMinusOne = addMonths(date, -1)
|
|
const dateMinusTwo = addMonths(date, -2)
|
|
const dateMinusThree = addMonths(date, -3)
|
|
|
|
const index = format(date, 'MM/YYYY')
|
|
const indexMinusOne = format(dateMinusOne, 'MM/YYYY')
|
|
const indexMinusTwo = format(dateMinusTwo, 'MM/YYYY')
|
|
const indexMinusThree = format(dateMinusThree, 'MM/YYYY')
|
|
|
|
const budget = this.months[index] || null
|
|
const spendAmount = budget ? budget.spend : rollingAverage
|
|
const spendMinusOne = this.months[indexMinusOne]
|
|
? this.months[indexMinusOne].spend
|
|
: rollingAverage
|
|
const spendMinusTwo = this.months[indexMinusTwo]
|
|
? this.months[indexMinusTwo].spend
|
|
: rollingAverage
|
|
const spendMinusThree = this.months[indexMinusThree]
|
|
? this.months[indexMinusThree].spend
|
|
: rollingAverage
|
|
|
|
const isExpirationMonth = isSameMonth(date, expires)
|
|
|
|
if (budget && budget.cumulative) {
|
|
cumulativeTotal = budget.cumulative
|
|
} else {
|
|
cumulativeTotal += spendAmount
|
|
}
|
|
|
|
rollingAverage =
|
|
(spendAmount + spendMinusOne + spendMinusTwo + spendMinusThree) / 4
|
|
|
|
monthsRange.push({
|
|
budget,
|
|
rollingAverage,
|
|
cumulativeTotal,
|
|
isExpirationMonth,
|
|
spendAmount: formatDollars(spendAmount),
|
|
abbreviatedSpend: abbreviateDollars(spendAmount),
|
|
cumulativeAmount: formatDollars(cumulativeTotal),
|
|
abbreviatedCumulative: abbreviateDollars(cumulativeTotal),
|
|
date: {
|
|
monthIndex: format(date, 'M'),
|
|
month: format(date, 'MMM'),
|
|
year: format(date, 'YYYY'),
|
|
},
|
|
showYear: isExpirationMonth || i === 0 || getMonth(date) === 0,
|
|
isHighlighted: this.currentMonth === index,
|
|
metrics: {
|
|
blockWidth: 0,
|
|
blockX: 0,
|
|
barHeight: 0,
|
|
barWidth: 0,
|
|
barX: 0,
|
|
barY: 0,
|
|
cumulativeY: 0,
|
|
cumulativeX: 0,
|
|
cumulativeR: 0,
|
|
},
|
|
})
|
|
}
|
|
this.displayedMonths = monthsRange
|
|
},
|
|
},
|
|
}
|