UNPKG

budget-view-chart

Version:

A React chart component specialising in display budget for personal finance.

371 lines (370 loc) 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChartRenders = void 0; /* eslint-disable @typescript-eslint/restrict-plus-operands */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ const Constants_1 = require("./Constants"); class ChartRenders { budgetNames; totalBudget; fullWidth; fullHeight; lowestY; numberFormatter; monthLabelGetter; budgetColorGetter; constructor(budgetNames, totalBudget, fullWidth, fullHeight, lowestY, locale, currency) { this.budgetNames = budgetNames; this.totalBudget = totalBudget; this.fullWidth = fullWidth; this.fullHeight = fullHeight; this.lowestY = lowestY; this.numberFormatter = (amount) => { const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency }); return formatter.format(amount); }; this.monthLabelGetter = (month) => { return Constants_1.monthLabels[month]; }; this.budgetColorGetter = (budgetName) => { return Constants_1.defaultColorPalettes[this.budgetNames.indexOf(budgetName) % Constants_1.defaultColorPalettes.length]; }; } /* Budget */ renderBudgetLabel = (params, api) => { return { type: 'group', children: [ this.renderBudgetBlock(params, api), this.renderBudgetText(params, api) ], focus: 'self', blurScope: 'series' }; }; renderBudgetText = (params, api) => { const start = api.coord([api.value('xStart'), 0]); const size = api.size([api.value('xLength'), 0]); const baseStyle = { text: `${api.value('name')}`, textAlign: 'left', textVerticalAlign: 'middle', opacity: 1 }; return { type: 'text', x: start[0] + size[0] / 2, y: api.coord([0, Math.floor(this.lowestY / 25) * 25])[1] + 30, rotation: -Math.PI / 2, style: baseStyle, blur: { style: { opacity: 1 } }, emphasis: { style: { fontWeight: 600 } } }; }; renderBudgetBlock = (params, api) => { const fill = this.budgetColorGetter(api.value('name')); const yValue = api.value('yLength'); const y = yValue < 0 ? api.value('yStart') : yValue + api.value('yStart'); const start = api.coord([api.value('xStart'), y]); const size = api.size([api.value('xLength'), Math.abs(yValue)]); const baseStyle = { fill, opacity: 0.3 }; if (yValue < 0) { baseStyle.fill = 'rgba(0, 0, 0, 0)'; baseStyle.decal = { symbol: 'rect', dashArrayX: [2, 0], dashArrayY: [3, 5], rotation: -Math.PI / 4, color: fill }; } return { type: 'rect', shape: { x: start[0], y: start[1], width: size[0], height: size[1] }, style: baseStyle, emphasis: { style: { opacity: 0.4 } }, blur: { style: { opacity: 0.2 } } }; }; /* Spending - Breakdown */ renderMonthlyBreakdown = (param, api) => { if (api.value('type') === 'aggregate') { return this.renderMonthLegendWithFocus(param, api); } return this.renderBreakdownBlock(param, api); }; /* Spending - Aggregate */ renderMonthlyAggregate = (param, api) => { return { type: 'group', ignore: api.value('type') !== 'aggregate', children: [ this.renderMonthLegendWithFocus(param, api), this.renderMonthlyAggregateBlock(param, api) ], focus: 'self', blurScope: 'series' }; }; renderMonthLegend = (params, api) => { const month = api.value('month'); const boxWidthPx = 30; const boxHeightVal = this.fullHeight / Constants_1.MONTH_PER_YEAR; const monthSize = api.size([0, boxHeightVal]); const monthStart = api.coord([0, boxHeightVal * (month + 1)]); const baseBoxStyle = { fill: '#444444', opacity: 0.8 }; const baseTextStyle = { fill: '#c7c7c7', text: `${this.monthLabelGetter(month)}` }; return { type: 'rect', id: `month-legend-${month}`, shape: { x: monthStart[0] - boxWidthPx, y: monthStart[1], width: boxWidthPx, height: monthSize[1] }, style: baseBoxStyle, emphasis: { style: { opacity: 1 } }, blur: { style: { opacity: 0.8 } }, textConfig: { position: 'inside', inside: true }, textContent: { style: baseTextStyle, emphasisDisabled: false, emphasis: { style: { fontWeight: '600', fill: '#ffffff' } }, blur: { style: { fontWeight: '600', fill: '#ffffff', opacity: 1 } } }, morph: false }; }; renderMonthLegendWithFocus = (params, api) => { return { ...this.renderMonthLegend(params, api), focus: 'series', blurScope: 'global' }; }; renderBreakdownBlock = (params, api) => { const fill = this.budgetColorGetter(api.value('name')); const yValue = api.value('yLength'); const y = yValue < 0 ? api.value('yStart') : yValue + api.value('yStart'); const start = api.coord([api.value('xStart'), y]); const size = api.size([api.value('xLength'), Math.abs(yValue)]); const baseBoxStyle = { fill }; if (yValue < 0) { baseBoxStyle.fill = 'rgba(0, 0, 0, 0)'; baseBoxStyle.decal = { symbol: 'rect', dashArrayX: [2, 0], dashArrayY: [3, 5], rotation: -Math.PI / 4, color: fill }; } return { type: 'rect', shape: { x: start[0], y: start[1], width: size[0], height: size[1] }, style: baseBoxStyle, emphasis: { style: { stroke: '#000', lineWidth: 1 } }, blur: { style: { opacity: 0.3 } }, focus: 'self', blurScope: 'global' }; }; renderMonthlyAggregateBlock = (param, api) => { const month = api.value('month'); const yValue = api.value('yLength'); const y = yValue < 0 ? api.value('yStart') : yValue + api.value('yStart'); const start = api.coord([api.value('xStart'), y]); const size = api.size([api.value('xLength'), Math.abs(yValue)]); const baseBoxStyle = { fill: '#444444', opacity: 0.8 }; const baseTextStyle = { fill: '#c7c7c7', text: `${this.monthLabelGetter(month)}` }; if (yValue < 0) { baseBoxStyle.fill = 'rgba(0, 0, 0, 0)'; baseBoxStyle.decal = { symbol: 'rect', dashArrayX: [2, 0], dashArrayY: [3, 5], rotation: -Math.PI / 4, color: '#444444' }; } return { type: 'rect', shape: { x: start[0], y: start[1], width: size[0], height: size[1] }, style: baseBoxStyle, emphasis: { style: { opacity: 1 } }, blur: { style: { opacity: 0.8 } }, textConfig: { position: 'inside', inside: true }, textContent: { style: baseTextStyle, emphasis: { style: { fontWeight: '600', fill: '#ffffff' } } }, ignore: yValue === 0 }; }; renderHorizontalLine = (valueFunction, param, api) => { const h = valueFunction(api) / this.totalBudget * this.fullHeight; const start = api.coord([0, h]); const end = api.coord([this.fullWidth, h]); return { type: 'line', transition: ['shape'], shape: { x1: start[0], x2: end[0], y1: start[1], y2: end[1] }, style: { fill: null, stroke: '#e43', lineWidth: 2 } }; }; budgetDataTooltipFormatter = (data) => { return ` <b>${data.name}</b> <p>${data.description}</p> <hr/> <div style="display: block">Annual Budget: <b style="float: right; margin-left:10px">${this.numberFormatter(data.monthlyBudget * Constants_1.MONTH_PER_YEAR)}</b></div> <div style="display: block">Annual Amount: <b style="float: right; margin-left:10px">${this.numberFormatter(data.amount)}</b></div> <div style="display: block">Left to Spend: <b style="float: right; margin-left:10px">${this.numberFormatter(data.monthlyBudget * Constants_1.MONTH_PER_YEAR - data.amount)}</b></div> `; }; chartDataTooltipFormatter = (data) => { if (data.type === 'aggregate') { return ` <b>${this.monthLabelGetter(data.month)}</b> <hr/> <div style="display: block">Monthly Total Budget: <b style="float: right; margin-left:10px">${this.numberFormatter(data.monthlyBudget)}</b></div> <div style="display: block">Monthly Total Amount: <b style="float: right; margin-left:10px">${this.numberFormatter(data.amount)}</b></div> `; } else if (data.type === 'breakdown') { return ` <b>${data.name}</b> <br/> <b>${this.monthLabelGetter(data.month)}</b> <hr/> <div style="display: block">Monthly Budget: <b style="float: right; margin-left:10px">${this.numberFormatter(data.monthlyBudget)}</b></div> <div style="display: block">Monthly Amount: <b style="float: right; margin-left:10px">${this.numberFormatter(data.amount)}</b></div> `; } else { throw new Error('Unknown type'); } }; totalLineTooltipFormatter = (totalBudget, totalAmount) => { return ` <b>Total</b> <hr/> <div style="display: block">Annual Budget: <b style="float: right; margin-left:10px">${this.numberFormatter(totalBudget)}</b></div> <div style="display: block">Annual Amount: <b style="float: right; margin-left:10px">${this.numberFormatter(totalAmount)}</b></div> <div style="display: block">Left to Spend: <b style="float: right; margin-left:10px">${this.numberFormatter(totalBudget - totalAmount)}</b></div> `; }; currentMonthEndLineTooltipFormatter = (budgetToMonthEnd, amountToMonthEnd) => { return ` <b>Current</b> <hr/> <div style="display: block">Current Budget: <b style="float: right; margin-left:10px">${this.numberFormatter(budgetToMonthEnd)}</b></div> <div style="display: block">Current Amount: <b style="float: right; margin-left:10px">${this.numberFormatter(amountToMonthEnd)}</b></div> <div style="display: block">Left to Spend: <b style="float: right; margin-left:10px">${this.numberFormatter(budgetToMonthEnd - amountToMonthEnd)}</b></div> `; }; } exports.ChartRenders = ChartRenders;