Merge pull request #303 from dod-ccpo/to-expiration-projection

Task order expiration projection
This commit is contained in:
andrewdds 2018-09-20 10:08:45 -04:00 committed by GitHub
commit 6dba46af66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 295 additions and 150 deletions

View File

@ -4,151 +4,141 @@ from itertools import groupby
MONTHLY_SPEND_AARDVARK = { MONTHLY_SPEND_AARDVARK = {
"LC04": { "LC04": {
"Integ": { "Integ": {
"10/2018": 284, "02/2018": 284,
"11/2018": 1210, "03/2018": 1210,
"12/2018": 1430, "04/2018": 1430,
"01/2019": 1366, "05/2018": 1366,
"02/2019": 1169, "06/2018": 1169,
"03/2019": 991, "07/2018": 991,
"04/2019": 978, "08/2018": 978,
"05/2019": 737, "09/2018": 737,
}, },
"PreProd": { "PreProd": {
"10/2018": 812, "02/2018": 812,
"11/2018": 1389, "03/2018": 1389,
"12/2018": 1425, "04/2018": 1425,
"01/2019": 1306, "05/2018": 1306,
"02/2019": 1112, "06/2018": 1112,
"03/2019": 936, "07/2018": 936,
"04/2019": 921, "08/2018": 921,
"05/2019": 694, "09/2018": 694,
}, },
"Prod": { "Prod": {
"10/2018": 1742, "02/2018": 1742,
"11/2018": 1716, "03/2018": 1716,
"12/2018": 1866, "04/2018": 1866,
"01/2019": 1809, "05/2018": 1809,
"02/2019": 1839, "06/2018": 1839,
"03/2019": 1633, "07/2018": 1633,
"04/2019": 1654, "08/2018": 1654,
"05/2019": 1103, "09/2018": 1103,
}, },
}, },
"SF18": { "SF18": {
"Integ": { "Integ": {
"12/2018": 1498, "04/2018": 1498,
"01/2019": 1400, "05/2018": 1400,
"02/2019": 1394, "06/2018": 1394,
"03/2019": 1171, "07/2018": 1171,
"04/2019": 1200, "08/2018": 1200,
"05/2019": 963, "09/2018": 963,
}, },
"PreProd": { "PreProd": {
"12/2018": 1780, "04/2018": 1780,
"01/2019": 1667, "05/2018": 1667,
"02/2019": 1703, "06/2018": 1703,
"03/2019": 1474, "07/2018": 1474,
"04/2019": 1441, "08/2018": 1441,
"05/2019": 933, "09/2018": 933,
}, },
"Prod": { "Prod": {
"12/2018": 1686, "04/2018": 1686,
"01/2019": 1779, "05/2018": 1779,
"02/2019": 1792, "06/2018": 1792,
"03/2019": 1570, "07/2018": 1570,
"04/2019": 1539, "08/2018": 1539,
"05/2019": 986, "09/2018": 986,
}, },
}, },
"Canton": { "Canton": {
"Prod": { "Prod": {
"01/2019": 28699, "05/2018": 28699,
"02/2019": 26766, "06/2018": 26766,
"03/2019": 22619, "07/2018": 22619,
"04/2019": 24090, "08/2018": 24090,
"05/2019": 16719, "09/2018": 16719,
} }
}, },
"BD04": { "BD04": {
"Integ": {}, "Integ": {},
"PreProd": { "PreProd": {
"10/2018": 7019, "02/2018": 7019,
"11/2018": 3004, "03/2018": 3004,
"12/2018": 2691, "04/2018": 2691,
"01/2019": 2901, "05/2018": 2901,
"02/2019": 3463, "06/2018": 3463,
"03/2019": 3314, "07/2018": 3314,
"04/2019": 3432, "08/2018": 3432,
"05/2019": 723, "09/2018": 723,
}, },
}, },
"SCV18": {"Dev": {"05/2019": 9797}}, "SCV18": {"Dev": {"05/2019": 9797}},
"Crown": { "Crown": {
"CR Portal Dev": { "CR Portal Dev": {
"11/2018": 208, "03/2018": 208,
"12/2018": 457, "04/2018": 457,
"01/2019": 671, "05/2018": 671,
"02/2019": 136, "06/2018": 136,
"03/2019": 1524, "07/2018": 1524,
"04/2019": 2077, "08/2018": 2077,
"05/2019": 1858, "09/2018": 1858,
}, },
"CR Staging": { "CR Staging": {
"11/2018": 208, "03/2018": 208,
"12/2018": 457, "04/2018": 457,
"01/2019": 671, "05/2018": 671,
"02/2019": 136, "06/2018": 136,
"03/2019": 1524, "07/2018": 1524,
"04/2019": 2077, "08/2018": 2077,
"05/2019": 1858, "09/2018": 1858,
}, },
"CR Portal Test 1": {"03/2019": 806, "04/2019": 1966, "05/2019": 2597}, "CR Portal Test 1": {"07/2018": 806, "08/2018": 1966, "09/2018": 2597},
"Jewels Prod": {"03/2019": 806, "04/2019": 1966, "05/2019": 2597}, "Jewels Prod": {"07/2018": 806, "08/2018": 1966, "09/2018": 2597},
"Jewels Dev": { "Jewels Dev": {
"11/2018": 145, "03/2018": 145,
"12/2018": 719, "04/2018": 719,
"01/2019": 1243, "05/2018": 1243,
"02/2019": 2214, "06/2018": 2214,
"03/2019": 2959, "07/2018": 2959,
"04/2019": 4151, "08/2018": 4151,
"05/2019": 4260, "09/2018": 4260,
}, },
}, },
} }
CUMULATIVE_BUDGET_AARDVARK = { CUMULATIVE_BUDGET_AARDVARK = {
"10/2018": {"spend": 9857, "cumulative": 9857}, "02/2018": {"spend": 9857, "cumulative": 9857},
"11/2018": {"spend": 7881, "cumulative": 17738}, "03/2018": {"spend": 7881, "cumulative": 17738},
"12/2018": {"spend": 14010, "cumulative": 31748}, "04/2018": {"spend": 14010, "cumulative": 31748},
"01/2019": {"spend": 43510, "cumulative": 75259}, "05/2018": {"spend": 43510, "cumulative": 75259},
"02/2019": {"spend": 41725, "cumulative": 116984}, "06/2018": {"spend": 41725, "cumulative": 116984},
"03/2019": {"spend": 41328, "cumulative": 158312}, "07/2018": {"spend": 41328, "cumulative": 158312},
"04/2019": {"spend": 47491, "cumulative": 205803}, "08/2018": {"spend": 47491, "cumulative": 205803},
"05/2019": {"spend": 45826, "cumulative": 251629}, "09/2018": {"spend": 45826, "cumulative": 251629},
"06/2019": {"projected": 296511},
"07/2019": {"projected": 341393},
"08/2019": {"projected": 386274},
"09/2019": {"projected": 431156},
} }
MONTHLY_SPEND_BELUGA = { MONTHLY_SPEND_BELUGA = {
"NP02": { "NP02": {
"Integ": {"02/2019": 284, "03/2019": 1210}, "Integ": {"08/2018": 284, "09/2018": 1210},
"PreProd": {"02/2019": 812, "03/2019": 1389}, "PreProd": {"08/2018": 812, "09/2018": 1389},
"Prod": {"02/2019": 3742, "03/2019": 4716}, "Prod": {"08/2018": 3742, "09/2018": 4716},
}, },
"FM": {"Integ": {"03/2019": 1498}, "Prod": {"03/2019": 5686}}, "FM": {"Integ": {"08/2018": 1498}, "Prod": {"09/2018": 5686}},
} }
CUMULATIVE_BUDGET_BELUGA = { CUMULATIVE_BUDGET_BELUGA = {
"02/2019": {"spend": 4838, "cumulative": 4838}, "08/2018": {"spend": 4838, "cumulative": 4838},
"03/2019": {"spend": 14500, "cumulative": 19338}, "09/2018": {"spend": 14500, "cumulative": 19338},
"04/2019": {"projected": 29007},
"05/2019": {"projected": 38676},
"06/2019": {"projected": 48345},
"07/2019": {"projected": 58014},
"08/2019": {"projected": 67683},
"09/2019": {"projected": 77352},
} }

View File

@ -106,6 +106,11 @@ def workspace_reports(workspace_id):
prev_month = current_month - timedelta(days=28) prev_month = current_month - timedelta(days=28)
two_months_ago = prev_month - timedelta(days=28) two_months_ago = prev_month - timedelta(days=28)
# lets just say it expires on Christmas... ho ho ho
expiration_date = date(2018, 12, 25)
remaining_difference = expiration_date - today
remaining_days = remaining_difference.days
return render_template( return render_template(
"workspaces/reports/index.html", "workspaces/reports/index.html",
cumulative_budget=Reports.cumulative_budget(alternate_reports), cumulative_budget=Reports.cumulative_budget(alternate_reports),
@ -114,6 +119,8 @@ def workspace_reports(workspace_id):
current_month=current_month, current_month=current_month,
prev_month=prev_month, prev_month=prev_month,
two_months_ago=two_months_ago, two_months_ago=two_months_ago,
expiration_date=expiration_date,
remaining_days=remaining_days,
) )

View File

@ -1,14 +1,15 @@
import { format } from 'date-fns' import { format, isWithinRange, addMonths, isSameMonth, getMonth } from 'date-fns'
import { abbreviateDollars, formatDollars } from '../../lib/dollars' import { abbreviateDollars, formatDollars } from '../../lib/dollars'
const TOP_OFFSET = 20 const TOP_OFFSET = 20
const BOTTOM_OFFSET = 60 const BOTTOM_OFFSET = 70
const CHART_HEIGHT = 360 const CHART_HEIGHT = 360
export default { export default {
name: 'budget-chart', name: 'budget-chart',
props: { props: {
currentMonth: String, currentMonth: String,
expirationDate: String,
months: Object, months: Object,
budget: String budget: String
}, },
@ -46,18 +47,15 @@ export default {
let lastSpendPoint = '' let lastSpendPoint = ''
for (let i = 0; i < this.numMonths; i++) { for (let i = 0; i < this.numMonths; i++) {
const { metrics, budget } = this.displayedMonths[i] const { metrics, budget, rollingAverage, cumulativeTotal } = this.displayedMonths[i]
const blockWidth = (this.width / this.numMonths) const blockWidth = (this.width / this.numMonths)
const blockX = blockWidth * i const blockX = blockWidth * i
const spend = budget const spend = budget && budget.spend
? budget.spend || lastSpend ? budget.spend
: 0 : rollingAverage
const cumulative = budget
? budget.cumulative || budget.projected
: 0
const barHeight = spend / this.heightScale const barHeight = spend / this.heightScale
lastSpend = spend lastSpend = spend
const cumulativeY = this.height - (cumulative / this.heightScale) - BOTTOM_OFFSET const cumulativeY = this.height - (cumulativeTotal / this.heightScale) - BOTTOM_OFFSET
const cumulativeX = blockX + blockWidth/2 const cumulativeX = blockX + blockWidth/2
const cumulativePoint = `${cumulativeX} ${cumulativeY}` const cumulativePoint = `${cumulativeX} ${cumulativeY}`
@ -77,9 +75,7 @@ export default {
this.spendPath += this.spendPath === '' ? 'M' : ' L' this.spendPath += this.spendPath === '' ? 'M' : ' L'
this.spendPath += cumulativePoint this.spendPath += cumulativePoint
lastSpendPoint = cumulativePoint lastSpendPoint = cumulativePoint
} } else {
if (budget && budget.projected) {
this.projectedPath += this.projectedPath === '' ? `M${lastSpendPoint} L` : ' L' this.projectedPath += this.projectedPath === '' ? `M${lastSpendPoint} L` : ' L'
this.projectedPath += cumulativePoint this.projectedPath += cumulativePoint
} }
@ -88,32 +84,76 @@ export default {
_setDisplayedMonths: function () { _setDisplayedMonths: function () {
const [month, year] = this.currentMonth.split('/') const [month, year] = this.currentMonth.split('/')
const [expYear, expMonth, expDate] = this.expirationDate.split('-') // assumes format 'YYYY-MM-DD'
const monthsRange = [] const monthsRange = []
const monthsBack = this.focusedMonthPosition const monthsBack = this.focusedMonthPosition + 1
const monthsForward = this.numMonths - this.focusedMonthPosition - 1 const monthsForward = this.numMonths - this.focusedMonthPosition - 1
const start = new Date(year, month - 1 - monthsBack)
let previousAmount = 0 // 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++) { for (let i = 0; i < this.numMonths; i++) {
const date = new Date(start.getFullYear(), start.getMonth() + 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 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 budget = this.months[index] || null
const spendAmount = budget ? budget.spend || previousAmount : 0 const spendAmount = budget ? budget.spend : rollingAverage
const cumulativeAmount = budget ? budget.cumulative || budget.projected : 0 const spendMinusOne = this.months[indexMinusOne] ? this.months[indexMinusOne].spend : rollingAverage
previousAmount = spendAmount 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({ monthsRange.push({
budget, budget,
rollingAverage,
cumulativeTotal,
isExpirationMonth,
spendAmount: formatDollars(spendAmount), spendAmount: formatDollars(spendAmount),
abbreviatedSpend: abbreviateDollars(spendAmount), abbreviatedSpend: abbreviateDollars(spendAmount),
cumulativeAmount: formatDollars(cumulativeAmount), cumulativeAmount: formatDollars(cumulativeTotal),
abbreviatedCumulative: abbreviateDollars(cumulativeAmount), abbreviatedCumulative: abbreviateDollars(cumulativeTotal),
date: { date: {
monthIndex: format(date, 'M'), monthIndex: format(date, 'M'),
month: format(date, 'MMM'), month: format(date, 'MMM'),
year: format(date,'YYYY') year: format(date,'YYYY')
}, },
showYear: isExpirationMonth || (i === 0) || getMonth(date) === 0,
isHighlighted: this.currentMonth === index, isHighlighted: this.currentMonth === index,
metrics: { metrics: {
blockWidth: 0, blockWidth: 0,

View File

@ -60,21 +60,39 @@
} }
.budget-chart__block { .budget-chart__block {
fill: $color-white; fill: transparent;
cursor: pointer; cursor: pointer;
&--highlighted { &--highlighted {
fill: $color-aqua-lightest; fill: rgba($color-aqua, .15);
}
&--is-expiration {
border-left: 2px dotted $color-gray;
} }
&:hover { &:hover {
fill: $color-aqua-lightest; fill: rgba($color-aqua, .15);
} }
} }
svg { svg {
display: block; display: block;
.filter__text-background {
feFlood {
flood-color: $color-white;
flood-opacity: 1;
}
&--highlighted {
feFlood {
flood-color: $color-aqua-lightest;
flood-opacity: 1;
}
}
}
a { a {
text-decoration: none; text-decoration: none;
&:focus { &:focus {
@ -82,6 +100,15 @@
stroke: $color-gray-light; stroke: $color-gray-light;
stroke-dasharray: 2px; stroke-dasharray: 2px;
} }
&:hover {
.filter__text-background {
feFlood {
flood-color: $color-aqua-lightest;
flood-opacity: 1;
}
}
}
} }
} }
@ -96,6 +123,12 @@
} }
} }
.budget-chart__expiration-line {
stroke-width: 2px;
stroke: $color-gray-light;
stroke-dasharray: 4px;
}
.budget-chart__cumulative__dot { .budget-chart__cumulative__dot {
fill: $color-gold; fill: $color-gold;
} }
@ -122,6 +155,7 @@
.budget-chart__label { .budget-chart__label {
@include small-label; @include small-label;
fill: $color-gray; fill: $color-gray;
pointer-events: none;
&--strong { &--strong {
fill: $color-black; fill: $color-black;

View File

@ -63,12 +63,17 @@
<dl> <dl>
<div> <div>
<dt>Expires</dt> <dt>Expires</dt>
<dd>November 1, 2019</dd> <dd>
<local-datetime
timestamp='{{ expiration_date }}'
format='MMMM D, YYYY'>
</local-datetime>
</dd>
</div> </div>
<div> <div>
<dt>Remaining</dt> <dt>Remaining</dt>
<dd>200 days</dd> <dd>{{ remaining_days }} days</dd>
</div> </div>
</dl> </dl>
@ -97,7 +102,12 @@
{% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %} {% set two_months_ago_index = two_months_ago.strftime('%m/%Y') %}
{% set reports_url = url_for("workspaces.workspace_reports", workspace_id=workspace.id) %} {% set reports_url = url_for("workspaces.workspace_reports", workspace_id=workspace.id) %}
<budget-chart budget={{ budget }} current-month='{{ current_month_index }}' v-bind:months='{{ cumulative_budget.months | tojson }}' inline-template> <budget-chart
budget={{ budget }}
current-month='{{ current_month_index }}'
expiration-date='{{ expiration_date }}'
v-bind:months='{{ cumulative_budget.months | tojson }}'
inline-template>
<div class='budget-chart panel' ref='panel'> <div class='budget-chart panel' ref='panel'>
<header class='budget-chart__header panel__heading panel__heading--tight'> <header class='budget-chart__header panel__heading panel__heading--tight'>
<h2 class='h3'>Cumulative Budget</h2> <h2 class='h3'>Cumulative Budget</h2>
@ -127,18 +137,54 @@
</header> </header>
<svg v-bind:height='height' v-bind:width='width'> <svg v-bind:height='height' v-bind:width='width'>
<defs>
<filter x="-0.04" y="0" width="1.08" height="1" class='filter__text-background' id="text-background">
<feFlood/>
<feComposite in="SourceGraphic"/>
</filter>
</defs>
{# spend/projected budget path lines #}
<path class='budget-chart__projected-path' v-bind:d='projectedPath'></path>
<path class='budget-chart__spend-path' v-bind:d='spendPath'></path>
{# max budget line #}
<line
class='budget-chart__budget-line'
x1='0'
v-bind:x2='width'
v-bind:y1='budgetHeight'
v-bind:y2='budgetHeight'></line>
<g v-for='month in displayedMonths' > <g v-for='month in displayedMonths' >
{# make this clickable to focus on that month #} {# make this clickable to focus on that month #}
<a v-bind:href='"{{ reports_url }}?month=" + month.date.monthIndex + "&year=" + month.date.year'> <a v-bind:href='"{{ reports_url }}?month=" + month.date.monthIndex + "&year=" + month.date.year'>
<defs>
<filter
x="-0.04"
y="0"
width="1.08"
height="1"
class='filter__text-background'
v-bind:class='{ "filter__text-background--highlighted": month.isHighlighted }'
v-bind:id="'text-background__' +month.date.month + month.date.year">
<feFlood/>
<feComposite in="SourceGraphic"/>
</filter>
</defs>
<title> <title>
<span v-html='month.date.month + " " + month.date.year'></span>&nbsp;|&nbsp;<!-- <span v-html='month.date.month + " " + month.date.year'></span>&nbsp;|&nbsp;<!--
--><template v-if='month.budget'><!-- --><template v-if='month.cumulativeTotal'><!--
--><template v-if='month.budget.spend'>Spend:</template><!-- --><template v-if='month.budget && month.budget.spend'>Spend:</template><!--
--><template v-if='month.budget.projected'>Projected Spend:</template><!-- --><template v-else>Projected Spend:</template><!--
--><span v-html='month.spendAmount'></span><!-- --><span v-html='month.spendAmount'></span><!--
-->&nbsp;|&nbsp;<!-- -->&nbsp;|&nbsp;<!--
--><template v-if='month.budget.cumulative'>Total:</template><!-- --><template v-if='month.budget'>Total:</template><!--
--><template v-if='month.budget.projected'>Projected Total:</template><!-- --><template v-else>Projected Total:</template><!--
--><span v-html='month.cumulativeAmount'></span><!-- --><span v-html='month.cumulativeAmount'></span><!--
--></template><!-- --></template><!--
@ -148,7 +194,7 @@
{# container block #} {# container block #}
<rect <rect
class='budget-chart__block' class='budget-chart__block'
v-bind:class='{ "budget-chart__block--highlighted": month.isHighlighted }' v-bind:class='{ "budget-chart__block--highlighted": month.isHighlighted, "budget-chart__block-is-expiration": month.isExpirationMonth }'
v-bind:width='month.metrics.blockWidth' v-bind:width='month.metrics.blockWidth'
v-bind:x='month.metrics.blockX' v-bind:x='month.metrics.blockX'
v-bind:height='height'></rect> v-bind:height='height'></rect>
@ -163,9 +209,36 @@
v-bind:x='month.metrics.barX' v-bind:x='month.metrics.barX'
v-bind:y='month.metrics.barY'></rect> v-bind:y='month.metrics.barY'></rect>
{# projected budget bar #}
<rect
v-if='!month.budget'
class='budget-chart__bar budget-chart__bar--projected'
v-bind:width='month.metrics.barWidth'
v-bind:height='month.metrics.barHeight'
v-bind:x='month.metrics.barX'
v-bind:y='month.metrics.barY'></rect>
{# task order expiration line #}
<line
v-if='month.isExpirationMonth'
class='budget-chart__expiration-line'
v-bind:x1='month.metrics.cumulativeX'
v-bind:x2='month.metrics.cumulativeX'
y1='0'
v-bind:y2='baseHeight'></line>
{# task order expiration label #}
<text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-if='month.isExpirationMonth'
text-anchor='middle'
v-bind:x='month.metrics.cumulativeX'
v-bind:y='budgetHeight + 20'
class='budget-chart__label'>T.O. Expires</text>
{# cumulative dot #} {# cumulative dot #}
<circle <circle
v-if='month.budget' v-if='month.cumulativeTotal'
class='budget-chart__cumulative__dot' class='budget-chart__cumulative__dot'
v-bind:r='month.metrics.cumulativeR' v-bind:r='month.metrics.cumulativeR'
v-bind:cx='month.metrics.cumulativeX' v-bind:cx='month.metrics.cumulativeX'
@ -173,7 +246,8 @@
{# abbreviated cumulative label #} {# abbreviated cumulative label #}
<text <text
v-if='month.budget' v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-if='month.cumulativeTotal'
v-bind:x='month.metrics.cumulativeX' v-bind:x='month.metrics.cumulativeX'
v-bind:y='month.metrics.cumulativeY - 10' v-bind:y='month.metrics.cumulativeY - 10'
text-anchor='middle' text-anchor='middle'
@ -182,6 +256,7 @@
{# abbreviated spend label #} {# abbreviated spend label #}
<text <text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-bind:x='month.metrics.cumulativeX' v-bind:x='month.metrics.cumulativeX'
v-bind:y='baseHeight + 20' v-bind:y='baseHeight + 20'
text-anchor='middle' text-anchor='middle'
@ -190,26 +265,25 @@
{# month label #} {# month label #}
<text <text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-bind:x='month.metrics.cumulativeX' v-bind:x='month.metrics.cumulativeX'
v-bind:y='baseHeight + 40' v-bind:y='baseHeight + 40'
text-anchor='middle' text-anchor='middle'
class='budget-chart__label budget-chart__label--strong' class='budget-chart__label budget-chart__label--strong'
v-html='month.date.month'></text> v-html='month.date.month'></text>
{# year label #}
<text
v-bind:filter="'url(#text-background__' + month.date.month + month.date.year + ')'"
v-if='month.showYear'
v-bind:x='month.metrics.cumulativeX'
v-bind:y='baseHeight + 55'
text-anchor='middle'
class='budget-chart__label budget-chart__label--strong'
v-html='month.date.year'></text>
</g> </g>
</a> </a>
{# spend/projected budget path lines #}
<path class='budget-chart__projected-path' v-bind:d='projectedPath'></path>
<path class='budget-chart__spend-path' v-bind:d='spendPath'></path>
{# max budget line #}
<line
class='budget-chart__budget-line'
x1='0'
v-bind:x2='width'
v-bind:y1='budgetHeight'
v-bind:y2='budgetHeight'></line>
<text <text
x='20' x='20'
v-bind:y='budgetHeight + 20' v-bind:y='budgetHeight + 20'