mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Expand layout
This commit is contained in:
parent
9bb62c865a
commit
dffddfda18
9
package-lock.json
generated
9
package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"alpinejs": "^3.12.3",
|
||||
"bootstrap": "^5.3.0",
|
||||
"chart.js": "^4.3.3",
|
||||
"chartjs-chart-sankey": "^0.12.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"store": "^2.0.12"
|
||||
},
|
||||
@ -507,6 +508,14 @@
|
||||
"pnpm": ">=7"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-chart-sankey": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-chart-sankey/-/chartjs-chart-sankey-0.12.0.tgz",
|
||||
"integrity": "sha512-2f0YfDWNTTDqztVALlD2YMdSbpmjxdxHpcpKgBi9cUq3IPWBvHb58h4gIa7GjsYVjMLwX6gusDXgxlh9PMKkkA==",
|
||||
"peerDependencies": {
|
||||
"chart.js": ">=3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
|
@ -17,6 +17,7 @@
|
||||
"alpinejs": "^3.12.3",
|
||||
"bootstrap": "^5.3.0",
|
||||
"chart.js": "^4.3.3",
|
||||
"chartjs-chart-sankey": "^0.12.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"store": "^2.0.12"
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
import {api} from "../../../boot/axios";
|
||||
import format from "date-fns/format";
|
||||
|
||||
export default class Get {
|
||||
|
||||
@ -29,7 +30,7 @@ export default class Get {
|
||||
* @returns {Promise<AxiosResponse<any>>}
|
||||
*/
|
||||
get(identifier, date) {
|
||||
let params = {date: date};
|
||||
let params = {date: format(date, 'y-MM-d').slice(0, 10)};
|
||||
if (!date) {
|
||||
return api.get('/api/v1/accounts/' + identifier);
|
||||
}
|
||||
|
34
resources/assets/v2/api/v2/model/transaction/get.js
Normal file
34
resources/assets/v2/api/v2/model/transaction/get.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* get.js
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import {api} from "../../../../boot/axios";
|
||||
|
||||
export default class Get {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param params
|
||||
* @returns {Promise<AxiosResponse<any>>}
|
||||
*/
|
||||
get(params) {
|
||||
return api.get('/api/v1/transactions', {params: params});
|
||||
}
|
||||
}
|
@ -24,12 +24,13 @@ import boxes from './pages/dashboard/boxes.js';
|
||||
import accounts from './pages/dashboard/accounts.js';
|
||||
import budgets from './pages/dashboard/budgets.js';
|
||||
import categories from './pages/dashboard/categories.js';
|
||||
import sankey from './pages/dashboard/sankey.js';
|
||||
|
||||
const comps = {dates, boxes, accounts, budgets, categories};
|
||||
const comps = {dates, boxes, accounts, budgets, categories, sankey};
|
||||
|
||||
function loadPage(comps) {
|
||||
Object.keys(comps).forEach(comp => {
|
||||
console.log(`Loading ${comp}`);
|
||||
console.log(`Loading page component "${comp}"`);
|
||||
let data = comps[comp]();
|
||||
Alpine.data(comp, () => data);
|
||||
});
|
||||
|
@ -22,129 +22,97 @@
|
||||
import {getVariable} from "../../store/get-variable.js";
|
||||
import {setVariable} from "../../store/set-variable.js";
|
||||
import Dashboard from "../../api/v2/chart/account/dashboard.js";
|
||||
import formatLocal from "../../util/format.js";
|
||||
import {format} from "date-fns";
|
||||
import formatMoney from "../../util/format-money.js";
|
||||
import Get from "../../api/v1/accounts/get.js";
|
||||
import Chart from "chart.js/auto";
|
||||
|
||||
// this is very ugly, but I have no better ideas at the moment to save the currency info
|
||||
// for each series.
|
||||
window.currencies = [];
|
||||
let currencies = [];
|
||||
let chart = null;
|
||||
let chartData = null;
|
||||
|
||||
export default () => ({
|
||||
loading: false,
|
||||
loadingAccounts: false,
|
||||
accountList: [],
|
||||
autoConversion: false,
|
||||
chart: null,
|
||||
chartData: null,
|
||||
chartOptions: null,
|
||||
switchAutoConversion() {
|
||||
this.autoConversion = !this.autoConversion;
|
||||
setVariable('autoConversion', this.autoConversion);
|
||||
this.loadChart();
|
||||
},
|
||||
getFreshData() {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
|
||||
this.chartData = response.data;
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
this.drawChart(this.generateOptions(this.chartData));
|
||||
});
|
||||
},
|
||||
generateOptions(data) {
|
||||
window.currencies = [];
|
||||
currencies = [];
|
||||
let options = {
|
||||
legend: {show: false},
|
||||
chart: {
|
||||
height: 400,
|
||||
type: 'line'
|
||||
},
|
||||
series: [],
|
||||
settings: [],
|
||||
xaxis: {
|
||||
categories: [],
|
||||
labels: {
|
||||
formatter: function (value) {
|
||||
if (undefined === value) {
|
||||
return '';
|
||||
}
|
||||
const date = new Date(value);
|
||||
if (date instanceof Date && !isNaN(date)) {
|
||||
return formatLocal(date, 'PP');
|
||||
}
|
||||
console.error('Could not parse "' + value + '", return "".');
|
||||
return ':(';
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function (value, index) {
|
||||
if (undefined === value) {
|
||||
return value;
|
||||
}
|
||||
if (undefined === index) {
|
||||
return value;
|
||||
}
|
||||
if (typeof index === 'object') {
|
||||
index = index.seriesIndex;
|
||||
}
|
||||
//console.log(index);
|
||||
let currencyCode = window.currencies[index] ?? 'EUR';
|
||||
return formatMoney(value, currencyCode);
|
||||
}
|
||||
}
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: []
|
||||
},
|
||||
};
|
||||
// render data:
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
let entry = [];
|
||||
let dataset = {};
|
||||
let collection = [];
|
||||
|
||||
// if index = 0, push all keys as labels:
|
||||
if (0 === i) {
|
||||
options.data.labels = Object.keys(current.entries);
|
||||
}
|
||||
dataset.label = current.label;
|
||||
|
||||
// use the "native" currency code and use the "native_entries" as array
|
||||
if (this.autoConversion) {
|
||||
window.currencies.push(current.native_code);
|
||||
collection = current.native_entries;
|
||||
currencies.push(current.native_code);
|
||||
collection = Object.values(current.native_entries);
|
||||
}
|
||||
if (!this.autoConversion) {
|
||||
window.currencies.push(current.currency_code);
|
||||
collection = current.entries;
|
||||
currencies.push(current.currency_code);
|
||||
collection = Object.values(current.entries);
|
||||
}
|
||||
dataset.data = collection;
|
||||
|
||||
for (const [ii, value] of Object.entries(collection)) {
|
||||
entry.push({x: format(new Date(ii), 'yyyy-MM-dd'), y: parseFloat(value)});
|
||||
//entry.push({x: format(new Date(ii), 'yyyy-MM-dd'), y: parseFloat(value)});
|
||||
}
|
||||
options.series.push({name: current.label, data: entry});
|
||||
options.data.datasets.push(dataset);
|
||||
//options.series.push({name: current.label, data: entry});
|
||||
}
|
||||
}
|
||||
this.chartOptions = options;
|
||||
|
||||
return options;
|
||||
},
|
||||
loadChart() {
|
||||
if (true === this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (null === this.chartData) {
|
||||
if (null === chartData) {
|
||||
this.getFreshData();
|
||||
return;
|
||||
}
|
||||
if (null !== this.chartData) {
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
this.drawChart(this.generateOptions(chartData));
|
||||
this.loading = false;
|
||||
|
||||
},
|
||||
drawChart() {
|
||||
if (null !== this.chart) {
|
||||
drawChart(options) {
|
||||
if (null !== chart) {
|
||||
// chart already in place, refresh:
|
||||
this.chart.updateOptions(this.chartOptions);
|
||||
}
|
||||
if (null === this.chart) {
|
||||
//this.chart = new ApexCharts(document.querySelector("#account-chart"), this.chartOptions);
|
||||
//this.chart.render();
|
||||
chart.data.datasets = options.data.datasets;
|
||||
chart.update();
|
||||
return;
|
||||
}
|
||||
chart = new Chart(document.querySelector("#account-chart"), options);
|
||||
},
|
||||
loadAccounts() {
|
||||
if (true === this.loadingAccounts) {
|
||||
@ -211,13 +179,12 @@ export default () => ({
|
||||
this.autoConversion = values[1];
|
||||
// main dashboard chart:
|
||||
this.loadChart();
|
||||
// this.loadAccounts();
|
||||
this.loadAccounts();
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
this.chartData = null;
|
||||
this.expenseAccountChart = null;
|
||||
chartData = null;
|
||||
// main dashboard chart:
|
||||
// this.loadChart();
|
||||
this.loadChart();
|
||||
this.loadAccounts();
|
||||
});
|
||||
},
|
||||
|
@ -22,60 +22,51 @@ import Dashboard from "../../api/v2/chart/budget/dashboard.js";
|
||||
// todo optimize
|
||||
import Chart from 'chart.js/auto';
|
||||
import {getDefaultChartSettings} from "../../support/default-chart-settings.js";
|
||||
import formatMoney from "../../util/format-money.js";
|
||||
|
||||
let currencies = [];
|
||||
let chart = null;
|
||||
let chartData = null;
|
||||
|
||||
window.budgetCurrencies = [];
|
||||
export default () => ({
|
||||
loading: false,
|
||||
chart: null,
|
||||
autoConversion: false,
|
||||
chartData: null,
|
||||
chartOptions: null,
|
||||
loadChart() {
|
||||
if (true === this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (null === this.chartData) {
|
||||
this.getFreshData();
|
||||
}
|
||||
if (null !== this.chartData) {
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
|
||||
if (null !== chartData) {
|
||||
this.drawChart(this.generateOptions(chartData));
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
this.getFreshData();
|
||||
},
|
||||
drawChart() {
|
||||
if (null !== this.chart) {
|
||||
// chart already in place, refresh:
|
||||
console.log('refresh');
|
||||
this.chart.data = this.chartOptions.data;
|
||||
//this.chart.updateOptions(this.chartOptions);
|
||||
}
|
||||
if (null === this.chart) {
|
||||
//this.chart = new ApexCharts(document.querySelector("#budget-chart"), this.chartOptions);
|
||||
this.chart = new Chart(document.querySelector("#budget-chart"), this.chartOptions);
|
||||
//this.chart.render();
|
||||
drawChart(options) {
|
||||
if (null !== chart) {
|
||||
chart.data.datasets = options.data.datasets;
|
||||
chart.update();
|
||||
return;
|
||||
}
|
||||
chart = new Chart(document.querySelector("#budget-chart"), options);
|
||||
},
|
||||
getFreshData() {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
|
||||
this.chartData = response.data;
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
chartData = response.data; // save chart data for later.
|
||||
this.drawChart(this.generateOptions(response.data));
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
generateOptions(data) {
|
||||
window.budgetCurrencies = [];
|
||||
currencies = [];
|
||||
let options = getDefaultChartSettings('column');
|
||||
options.options.locale = window.store.get('locale').replace('_', '-');
|
||||
options.options.plugins = {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
// tooltip: function (context) {
|
||||
// //console.log(context);
|
||||
// },
|
||||
title: function (context) {
|
||||
return context.label;
|
||||
},
|
||||
@ -85,12 +76,7 @@ export default () => ({
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
//console.log('label');
|
||||
//console.log(context.label + ' X');
|
||||
//return context.label + ' X';
|
||||
// console.log(context);
|
||||
return label + ' ' + context.parsed.x;
|
||||
// return label + ' ' + formatMoney(context.parsed.y, window.budgetCurrencies[context.parsed.x] ?? 'EUR');
|
||||
return label + ' ' + formatMoney(context.parsed.y, currencies[context.parsed.x] ?? 'EUR');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,7 +112,7 @@ export default () => ({
|
||||
let label = current.label + ' (' + current.currency_code + ')';
|
||||
options.data.labels.push(label);
|
||||
if (this.autoConversion) {
|
||||
window.budgetCurrencies.push(current.native_code);
|
||||
currencies.push(current.native_code);
|
||||
// series 0: spent
|
||||
options.data.datasets[0].data.push(parseFloat(current.native_entries.spent) * -1);
|
||||
// series 1: left
|
||||
@ -135,7 +121,7 @@ export default () => ({
|
||||
options.data.datasets[2].data.push(parseFloat(current.native_entries.overspent));
|
||||
}
|
||||
if (!this.autoConversion) {
|
||||
window.budgetCurrencies.push(current.currency_code);
|
||||
currencies.push(current.currency_code);
|
||||
// series 0: spent
|
||||
options.data.datasets[0].data.push(parseFloat(current.entries.spent) * -1);
|
||||
// series 1: left
|
||||
@ -143,147 +129,30 @@ export default () => ({
|
||||
// series 2: overspent
|
||||
options.data.datasets[2].data.push(parseFloat(current.entries.overspent));
|
||||
}
|
||||
// console.log('Currencies');
|
||||
// console.log(window.budgetCurrencies);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
// options = {
|
||||
// legend: {show: false},
|
||||
// series: [{
|
||||
// name: 'Spent',
|
||||
// data: []
|
||||
// }, {
|
||||
// name: 'Left',
|
||||
// data: []
|
||||
// }, {
|
||||
// name: 'Overspent',
|
||||
// data: []
|
||||
// }],
|
||||
// chart: {
|
||||
// type: 'bar',
|
||||
// height: 400,
|
||||
// stacked: true,
|
||||
// toolbar: {tools: {zoom: false, download: false, pan: false}},
|
||||
// zoom: {
|
||||
// enabled: true
|
||||
// }
|
||||
// },
|
||||
// responsive: [{
|
||||
// breakpoint: 480,
|
||||
// options: {
|
||||
// legend: {
|
||||
// position: 'bottom',
|
||||
// offsetX: -10,
|
||||
// offsetY: 0
|
||||
// }
|
||||
// }
|
||||
// }],
|
||||
// plotOptions: {
|
||||
// bar: {
|
||||
// horizontal: false,
|
||||
// borderRadius: 10,
|
||||
// dataLabels: {
|
||||
// total: {
|
||||
// enabled: true,
|
||||
// // style: {
|
||||
// // fontSize: '13px',
|
||||
// // fontWeight: 900
|
||||
// // },
|
||||
// formatter: function (val, opt) {
|
||||
// let index = 0;
|
||||
// if (typeof opt === 'object') {
|
||||
// index = opt.dataPointIndex; // this is the "category name + currency" index
|
||||
// }
|
||||
// let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
// return formatMoney(val, currencyCode);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// },
|
||||
// yaxis: {
|
||||
// labels: {
|
||||
// formatter: function (value, index) {
|
||||
// if (undefined === value) {
|
||||
// return value;
|
||||
// }
|
||||
// if (undefined === index) {
|
||||
// return value;
|
||||
// }
|
||||
// if (typeof index === 'object') {
|
||||
// index = index.dataPointIndex; // this is the "category name + currency" index
|
||||
// }
|
||||
// let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
// return formatMoney(value, currencyCode);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// xaxis: {
|
||||
// categories: []
|
||||
// },
|
||||
// fill: {
|
||||
// opacity: 0.8
|
||||
// },
|
||||
// dataLabels: {
|
||||
// formatter: function (val, opt) {
|
||||
// let index = 0;
|
||||
// if (typeof opt === 'object') {
|
||||
// index = opt.dataPointIndex; // this is the "category name + currency" index
|
||||
// }
|
||||
// let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
// return formatMoney(val, currencyCode);
|
||||
// },
|
||||
// }
|
||||
// };
|
||||
|
||||
|
||||
// for (const i in data) {
|
||||
// if (data.hasOwnProperty(i)) {
|
||||
// let current = data[i];
|
||||
// // convert to EUR yes no?
|
||||
// let label = current.label + ' (' + current.currency_code + ')';
|
||||
// options.xaxis.categories.push(label);
|
||||
// if (this.autoConversion) {
|
||||
// window.budgetCurrencies.push(current.native_code);
|
||||
//
|
||||
// // series 0: spent
|
||||
// options.series[0].data.push(parseFloat(current.native_entries.spent) * -1);
|
||||
// // series 1: left
|
||||
// options.series[1].data.push(parseFloat(current.native_entries.left));
|
||||
// // series 2: overspent
|
||||
// options.series[2].data.push(parseFloat(current.native_entries.overspent));
|
||||
// }
|
||||
// if (!this.autoConversion) {
|
||||
// window.budgetCurrencies.push(current.currency_code);
|
||||
// // series 0: spent
|
||||
// options.series[0].data.push(parseFloat(current.entries.spent) * -1);
|
||||
// // series 1: left
|
||||
// options.series[1].data.push(parseFloat(current.entries.left));
|
||||
// // series 2: overspent
|
||||
// options.series[2].data.push(parseFloat(current.entries.overspent));
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// }
|
||||
this.chartOptions = options;
|
||||
return options;
|
||||
},
|
||||
|
||||
|
||||
init() {
|
||||
Promise.all([getVariable('autoConversion', false),]).then((values) => {
|
||||
this.autoConversion = values[0];
|
||||
this.loadChart();
|
||||
if (false === this.loading) {
|
||||
this.loadChart();
|
||||
}
|
||||
});
|
||||
// todo the charts don't need to reload from server if the autoConversion value changes.
|
||||
window.store.observe('end', () => {
|
||||
this.chartData = null;
|
||||
this.loadChart();
|
||||
if (false === this.loading) {
|
||||
this.chartData = null;
|
||||
this.loadChart();
|
||||
}
|
||||
});
|
||||
window.store.observe('autoConversion', (newValue) => {
|
||||
this.autoConversion = newValue;
|
||||
this.loadChart();
|
||||
if (false === this.loading) {
|
||||
this.loadChart();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -21,79 +21,22 @@ import {getVariable} from "../../store/get-variable.js";
|
||||
import Dashboard from "../../api/v2/chart/category/dashboard.js";
|
||||
//import ApexCharts from "apexcharts";
|
||||
import formatMoney from "../../util/format-money.js";
|
||||
import {getDefaultChartSettings} from "../../support/default-chart-settings.js";
|
||||
import Chart from "chart.js/auto";
|
||||
|
||||
let currencies = [];
|
||||
|
||||
let chart = null;
|
||||
let chartData = null;
|
||||
|
||||
window.categoryCurrencies = [];
|
||||
export default () => ({
|
||||
loading: false,
|
||||
chart: null,
|
||||
autoConversion: false,
|
||||
chartData: null,
|
||||
chartOptions: null,
|
||||
generateOptions(data) {
|
||||
window.categoryCurrencies = [];
|
||||
let options = {
|
||||
series: [],
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 350
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
columnWidth: '55%',
|
||||
endingShape: 'rounded'
|
||||
},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
stroke: {
|
||||
show: true,
|
||||
width: 2,
|
||||
colors: ['transparent']
|
||||
},
|
||||
xaxis: {
|
||||
categories: [],
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function (value, index) {
|
||||
if (undefined === value) {
|
||||
return value;
|
||||
}
|
||||
if (undefined === index) {
|
||||
return value;
|
||||
}
|
||||
if (typeof index === 'object') {
|
||||
index = index.dataPointIndex; // this is the "category name + currency" index
|
||||
}
|
||||
let currencyCode = window.budgetCurrencies[index] ?? 'EUR';
|
||||
return formatMoney(value, currencyCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
opacity: 1
|
||||
},
|
||||
tooltip: {
|
||||
y: {
|
||||
formatter: function (value, index) {
|
||||
if (undefined === value) {
|
||||
return value;
|
||||
}
|
||||
if (undefined === index) {
|
||||
return value;
|
||||
}
|
||||
if (typeof index === 'object') {
|
||||
index = index.seriesIndex; // this is the currency index.
|
||||
}
|
||||
let currencyCode = window.categoryCurrencies[index] ?? 'EUR';
|
||||
return formatMoney(value, currencyCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// first, collect all currencies and use them as series.
|
||||
currencies = [];
|
||||
let options = getDefaultChartSettings('column');
|
||||
|
||||
// first, create "series" per currency.
|
||||
let series = {};
|
||||
for (const i in data) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
@ -109,11 +52,12 @@ export default () => ({
|
||||
name: code,
|
||||
data: {},
|
||||
};
|
||||
window.categoryCurrencies.push(code);
|
||||
currencies.push(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop data again to add amounts.
|
||||
|
||||
// loop data again to add amounts to each series.
|
||||
for (const i in data) {
|
||||
if (data.hasOwnProperty(i)) {
|
||||
let current = data[i];
|
||||
@ -147,42 +91,40 @@ export default () => ({
|
||||
}
|
||||
}
|
||||
// add label to x-axis, not unimportant.
|
||||
if (!options.xaxis.categories.includes(current.label)) {
|
||||
options.xaxis.categories.push(current.label);
|
||||
if (!options.data.labels.includes(current.label)) {
|
||||
options.data.labels.push(current.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
// loop the series and create Apex-compatible data sets.
|
||||
// loop the series and create ChartJS-compatible data sets.
|
||||
for (const i in series) {
|
||||
let current = {
|
||||
name: i,
|
||||
let dataset = {
|
||||
label: i,
|
||||
data: [],
|
||||
}
|
||||
for (const ii in series[i].data) {
|
||||
current.data.push(series[i].data[ii]);
|
||||
dataset.data.push(series[i].data[ii]);
|
||||
}
|
||||
options.series.push(current);
|
||||
options.data.datasets.push(dataset);
|
||||
}
|
||||
this.chartOptions = options;
|
||||
|
||||
return options;
|
||||
},
|
||||
drawChart() {
|
||||
if (null !== this.chart) {
|
||||
// chart already in place, refresh:
|
||||
this.chart.updateOptions(this.chartOptions);
|
||||
drawChart(options) {
|
||||
if (null !== chart) {
|
||||
chart.data.datasets = options.data.datasets;
|
||||
chart.update();
|
||||
return;
|
||||
}
|
||||
if (null === this.chart) {
|
||||
this.chart = new ApexCharts(document.querySelector("#category-chart"), this.chartOptions);
|
||||
this.chart.render();
|
||||
}
|
||||
this.loading = false;
|
||||
chart = new Chart(document.querySelector("#category-chart"), options);
|
||||
|
||||
},
|
||||
getFreshData() {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.dashboard(new Date(window.store.get('start')), new Date(window.store.get('end')), null).then((response) => {
|
||||
this.chartData = response.data;
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
chartData = response.data; // save chart data for later.
|
||||
this.drawChart(this.generateOptions(response.data));
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
@ -191,28 +133,26 @@ export default () => ({
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
if (null === this.chartData) {
|
||||
this.getFreshData();
|
||||
}
|
||||
if (null !== this.chartData) {
|
||||
this.generateOptions(this.chartData);
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
if (null !== chartData) {
|
||||
this.drawChart(this.generateOptions(chartData));
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
this.getFreshData();
|
||||
},
|
||||
init() {
|
||||
Promise.all([getVariable('autoConversion', false),]).then((values) => {
|
||||
// this.autoConversion = values[0];
|
||||
// this.loadChart();
|
||||
this.autoConversion = values[0];
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
// this.chartData = null;
|
||||
// this.loadChart();
|
||||
this.chartData = null;
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('autoConversion', (newValue) => {
|
||||
// this.autoConversion = newValue;
|
||||
// this.loadChart();
|
||||
this.autoConversion = newValue;
|
||||
this.loadChart();
|
||||
});
|
||||
},
|
||||
|
||||
|
155
resources/assets/v2/pages/dashboard/sankey.js
Normal file
155
resources/assets/v2/pages/dashboard/sankey.js
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* budgets.js
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {getVariable} from "../../store/get-variable.js";
|
||||
import Get from "../../api/v2/model/transaction/get.js";
|
||||
import {getDefaultChartSettings} from "../../support/default-chart-settings.js";
|
||||
import Chart from "chart.js/auto";
|
||||
import {Flow, SankeyController} from 'chartjs-chart-sankey';
|
||||
|
||||
Chart.register(SankeyController, Flow);
|
||||
|
||||
let currencies = [];
|
||||
|
||||
let chart = null;
|
||||
let transactions = [];
|
||||
|
||||
export default () => ({
|
||||
loading: false,
|
||||
autoConversion: false,
|
||||
generateOptions(data) {
|
||||
currencies = [];
|
||||
console.log('generate options');
|
||||
let options = getDefaultChartSettings('sankey');
|
||||
|
||||
// temp code for first sankey
|
||||
const colors = {
|
||||
a: 'red',
|
||||
b: 'green',
|
||||
c: 'blue',
|
||||
d: 'gray'
|
||||
};
|
||||
|
||||
const getColor = (key) => colors[key];
|
||||
// end of temp code for first sankey
|
||||
let dataSet =
|
||||
// sankey chart has one data set.
|
||||
{
|
||||
label: 'My sankey',
|
||||
data: [
|
||||
{from: 'a', to: 'b', flow: 10},
|
||||
{from: 'a', to: 'c', flow: 5},
|
||||
{from: 'b', to: 'c', flow: 10},
|
||||
{from: 'd', to: 'c', flow: 7}
|
||||
],
|
||||
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex].from),
|
||||
colorTo: (c) => getColor(c.dataset.data[c.dataIndex].to),
|
||||
colorMode: 'gradient', // or 'from' or 'to'
|
||||
/* optional labels */
|
||||
labels: {
|
||||
a: 'Label A',
|
||||
b: 'Label B',
|
||||
c: 'Label C',
|
||||
d: 'Label D'
|
||||
},
|
||||
/* optional priority */
|
||||
priority: {
|
||||
b: 1,
|
||||
d: 0
|
||||
},
|
||||
/* optional column overrides */
|
||||
column: {
|
||||
d: 1
|
||||
},
|
||||
size: 'max', // or 'min' if flow overlap is preferred
|
||||
};
|
||||
options.data.datasets.push(dataSet);
|
||||
|
||||
|
||||
return options;
|
||||
},
|
||||
drawChart(options) {
|
||||
if (null !== chart) {
|
||||
chart.data.datasets = options.data.datasets;
|
||||
chart.update();
|
||||
return;
|
||||
}
|
||||
chart = new Chart(document.querySelector("#sankey-chart"), options);
|
||||
|
||||
},
|
||||
getFreshData() {
|
||||
let params = {
|
||||
start: window.store.get('start').slice(0, 10),
|
||||
end: window.store.get('end').slice(0, 10),
|
||||
type: 'withdrawal,deposit',
|
||||
page: 1
|
||||
};
|
||||
this.downloadTransactions(params);
|
||||
},
|
||||
downloadTransactions(params) {
|
||||
console.log('Downloading page ' + params.page + '...');
|
||||
const getter = new Get();
|
||||
getter.get(params).then((response) => {
|
||||
transactions = [...transactions, ...response.data.data];
|
||||
//this.drawChart(this.generateOptions(response.data));
|
||||
//this.loading = false;
|
||||
if (parseInt(response.data.meta.pagination.total_pages) > params.page) {
|
||||
// continue to next page.
|
||||
params.page++;
|
||||
this.downloadTransactions(params);
|
||||
return;
|
||||
}
|
||||
// continue to next step.
|
||||
console.log('Final page!');
|
||||
console.log(transactions);
|
||||
});
|
||||
},
|
||||
|
||||
loadChart() {
|
||||
if (true === this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
if (0 !== transactions.length) {
|
||||
this.drawChart(this.generateOptions());
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
this.getFreshData();
|
||||
},
|
||||
init() {
|
||||
transactions = [];
|
||||
Promise.all([getVariable('autoConversion', false),]).then((values) => {
|
||||
this.autoConversion = values[0];
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('end', () => {
|
||||
this.transactions = [];
|
||||
this.loadChart();
|
||||
});
|
||||
window.store.observe('autoConversion', (newValue) => {
|
||||
this.autoConversion = newValue;
|
||||
this.loadChart();
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
@ -33,7 +33,6 @@ const Basic = () => {
|
||||
*
|
||||
*/
|
||||
const init = () => {
|
||||
console.log('Basic store init')
|
||||
this.loadVariable('viewRange')
|
||||
this.loadVariable('darkMode')
|
||||
this.loadVariable('language')
|
||||
@ -89,7 +88,6 @@ const Basic = () => {
|
||||
const triggerReady = () => {
|
||||
this.count++;
|
||||
if (this.count === this.readyCount) {
|
||||
console.log('Basic store is ready!')
|
||||
// trigger event:
|
||||
const event = new Event("BasicStoreReady");
|
||||
document.dispatchEvent(event);
|
||||
|
@ -36,13 +36,11 @@ export function setVariable(name, value = null) {
|
||||
// post to user preferences (because why not):
|
||||
let putter = new Put();
|
||||
putter.put(name, value).then((response) => {
|
||||
// console.log('Put in API');
|
||||
}).catch(() => {
|
||||
// preference does not exist (yet).
|
||||
// POST it
|
||||
let poster = (new Post);
|
||||
poster.post(name, value).then((response) => {
|
||||
// console.log('Post in API');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -19,10 +19,21 @@
|
||||
*/
|
||||
|
||||
function getDefaultChartSettings(type) {
|
||||
if ('sankey' === type) {
|
||||
return {
|
||||
type: 'sankey',
|
||||
data: {
|
||||
datasets: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
if ('column' === type) {
|
||||
return {
|
||||
type: 'bar',
|
||||
data: {},
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
|
@ -8,6 +8,7 @@
|
||||
<!--begin::Container-->
|
||||
<div class="container-fluid">
|
||||
@include('partials.dashboard.boxes')
|
||||
|
||||
<!-- row with account data -->
|
||||
<div class="row mb-2" x-data="accounts">
|
||||
<div class="col-xl-8 col-lg-12 col-sm-12 col-xs-12">
|
||||
@ -20,7 +21,7 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="account-chart"></div>
|
||||
<canvas id="account-chart"></canvas>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<template x-if="autoConversion">
|
||||
@ -68,7 +69,7 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div id="category-chart"></div>
|
||||
<canvas id="category-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -150,8 +151,8 @@
|
||||
title="{{ route('reports.index') }}">{{ __('firefly.income_and_expense') }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="sankey-chart"></div>
|
||||
<div class="card-body" x-data="sankey">
|
||||
<canvas id="sankey-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user