From 21307013fd99202ea61ed379c2519548bfd596f0 Mon Sep 17 00:00:00 2001 From: pakwai Date: Sat, 4 Mar 2023 18:50:25 +0800 Subject: [PATCH 01/17] Scaffold placeholder for analytic. --- src/components/Header.vue | 47 ++++++++++++++++++++++++++++++--------- src/router/index.js | 11 +++++++++ src/views/Analytic.vue | 23 +++++++++++++++++++ 3 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 src/views/Analytic.vue diff --git a/src/components/Header.vue b/src/components/Header.vue index 87c409d..551d7a1 100644 --- a/src/components/Header.vue +++ b/src/components/Header.vue @@ -9,29 +9,55 @@ XRPL Explorer XRP Ledger Explorer - - @@ -137,5 +163,4 @@ export default { } - + diff --git a/src/router/index.js b/src/router/index.js index baa5bad..e4b7ea4 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -11,6 +11,7 @@ import NotFound from '../views/NotFound.vue' import CustomCommand from '../views/CustomCommand.vue' import GenericData from '../components/GenericData.vue' import publicCommands from '../plugins/commands' +import Analytic from '../views/Analytic' Vue.use(VueRouter) @@ -124,6 +125,16 @@ const routes = [ replaceParam: 'hash' } }, + { + path: '/analytic', + name: 'analytic', + component: Analytic + }, + { + path: '/analytic/:ledger_from([0-9]{5,20})/:ledger_to([0-9]{5,20})', + name: 'analytic_ledger', + component: Analytic + }, { path: '/namespace/:account(r[a-zA-Z0-9]{15,})/:namespace_id([a-fA-F0-9]{64})', name: 'hooknamespace', diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue new file mode 100644 index 0000000..eae6545 --- /dev/null +++ b/src/views/Analytic.vue @@ -0,0 +1,23 @@ + + + + + From 7cc5ec285da25f005c77d06a9492391093d12871 Mon Sep 17 00:00:00 2001 From: pakwai Date: Sun, 5 Mar 2023 12:20:16 +0800 Subject: [PATCH 02/17] Added skeleton --- package-lock.json | 264 ++++++++++++++++++++++++++++++++++++ package.json | 3 + src/views/Analytic copy.vue | 65 +++++++++ src/views/Analytic.vue | 53 +++++++- 4 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 src/views/Analytic copy.vue diff --git a/package-lock.json b/package-lock.json index ef8cff6..b91cb0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,15 @@ "name": "hooks-testnet-explorer", "version": "0.1.0", "dependencies": { + "apexcharts": "^3.37.1", "core-js": "^3.6.5", "jsonlint": "^1.6.3", "jsonlint-mod": "^1.7.6", "mitt": "^2.1.0", "vue": "^2.6.11", + "vue-apexcharts": "^1.6.2", "vue-codemirror": "^4.0.6", + "vue-ctk-date-time-picker": "^2.5.0", "vue-json-pretty": "^1.8.1", "vue-router": "^3.2.0", "xrpl-client": "^1.8.0" @@ -3162,6 +3165,19 @@ "node": ">=0.10.0" } }, + "node_modules/apexcharts": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.37.1.tgz", + "integrity": "sha512-fmQ5Updeb/LASl+S1+mIxXUFxzY0Fa7gexfCs4o+OPP9f2NEBNjvybOtPrah44N4roK7U5o5Jis906QeEQu0cA==", + "dependencies": { + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "node_modules/aproba": { "version": "1.2.0", "dev": true, @@ -9550,6 +9566,28 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-range": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/moment-range/-/moment-range-4.0.2.tgz", + "integrity": "sha512-n8sceWwSTjmz++nFHzeNEUsYtDqjgXgcOBzsHi+BoXQU2FW+eU92LUaK8gqOiSu5PG57Q9sYj1Fz4LRDj4FtKA==", + "dependencies": { + "es6-symbol": "^3.1.0" + }, + "engines": { + "node": "*" + }, + "peerDependencies": { + "moment": ">= 2" + } + }, "node_modules/move-concurrently": { "version": "1.0.1", "dev": true, @@ -12953,6 +12991,89 @@ "version": "1.0.0", "dev": true }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "dev": true, @@ -13883,6 +14004,14 @@ "uuid": "bin/uuid" } }, + "node_modules/v-click-outside": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/v-click-outside/-/v-click-outside-2.1.5.tgz", + "integrity": "sha512-VPNCOTZK6WZy73lcWc+R7IW1uaBFEO3/Csrs5CzWVOdvE30V8Y1+BE/BtTlcEmeDGx0eqdE7bSCg55Jj37PMJg==", + "engines": { + "node": ">=6" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "dev": true, @@ -13953,6 +14082,14 @@ "csstype": "^3.1.0" } }, + "node_modules/vue-apexcharts": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.2.tgz", + "integrity": "sha512-9HS3scJwWgKjmkcWIf+ndNDR0WytUJD8Ju0V2ZYcjYtlTLwJAf2SKUlBZaQTkDmwje/zMgulvZRi+MXmi+WkKw==", + "peerDependencies": { + "apexcharts": "^3.26.0" + } + }, "node_modules/vue-codemirror": { "version": "4.0.6", "license": "MIT", @@ -13965,6 +14102,21 @@ "npm": ">= 3.0.0" } }, + "node_modules/vue-ctk-date-time-picker": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/vue-ctk-date-time-picker/-/vue-ctk-date-time-picker-2.5.0.tgz", + "integrity": "sha512-s4AO+5xnPlX+LD5UPQcjLBnv8MwcEJKlKUnkTLQeXRV0xqpH9pWRe6aJ8N2+506mEN6b7iwhlFox6uKKdtK+gw==", + "dependencies": { + "moment": "^2.24.0", + "moment-range": "^4.0.1", + "v-click-outside": "^2.0.2", + "vue": "^2.6.9" + }, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/vue-eslint-parser": { "version": "7.11.0", "dev": true, @@ -16970,6 +17122,19 @@ } } }, + "apexcharts": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.37.1.tgz", + "integrity": "sha512-fmQ5Updeb/LASl+S1+mIxXUFxzY0Fa7gexfCs4o+OPP9f2NEBNjvybOtPrah44N4roK7U5o5Jis906QeEQu0cA==", + "requires": { + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "aproba": { "version": "1.2.0", "dev": true @@ -21282,6 +21447,19 @@ "minimist": "^1.2.6" } }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, + "moment-range": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/moment-range/-/moment-range-4.0.2.tgz", + "integrity": "sha512-n8sceWwSTjmz++nFHzeNEUsYtDqjgXgcOBzsHi+BoXQU2FW+eU92LUaK8gqOiSu5PG57Q9sYj1Fz4LRDj4FtKA==", + "requires": { + "es6-symbol": "^3.1.0" + } + }, "move-concurrently": { "version": "1.0.1", "dev": true, @@ -23613,6 +23791,70 @@ "version": "1.0.0", "dev": true }, + "svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "requires": { + "svg.js": "^2.0.1" + } + }, + "svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "requires": { + "svg.js": ">=2.3.x" + } + }, + "svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "requires": { + "svg.js": "^2.2.5" + } + }, + "svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "requires": { + "svg.js": "^2.4.0" + } + }, + "svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "requires": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "dependencies": { + "svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "requires": { + "svg.js": "^2.2.5" + } + } + } + }, + "svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "requires": { + "svg.js": "^2.6.5" + } + }, "svgo": { "version": "1.3.2", "dev": true, @@ -24268,6 +24510,11 @@ "version": "3.4.0", "dev": true }, + "v-click-outside": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/v-click-outside/-/v-click-outside-2.1.5.tgz", + "integrity": "sha512-VPNCOTZK6WZy73lcWc+R7IW1uaBFEO3/Csrs5CzWVOdvE30V8Y1+BE/BtTlcEmeDGx0eqdE7bSCg55Jj37PMJg==" + }, "v8-compile-cache": { "version": "2.3.0", "dev": true @@ -24318,6 +24565,12 @@ "csstype": "^3.1.0" } }, + "vue-apexcharts": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.2.tgz", + "integrity": "sha512-9HS3scJwWgKjmkcWIf+ndNDR0WytUJD8Ju0V2ZYcjYtlTLwJAf2SKUlBZaQTkDmwje/zMgulvZRi+MXmi+WkKw==", + "requires": {} + }, "vue-codemirror": { "version": "4.0.6", "requires": { @@ -24325,6 +24578,17 @@ "diff-match-patch": "^1.0.0" } }, + "vue-ctk-date-time-picker": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/vue-ctk-date-time-picker/-/vue-ctk-date-time-picker-2.5.0.tgz", + "integrity": "sha512-s4AO+5xnPlX+LD5UPQcjLBnv8MwcEJKlKUnkTLQeXRV0xqpH9pWRe6aJ8N2+506mEN6b7iwhlFox6uKKdtK+gw==", + "requires": { + "moment": "^2.24.0", + "moment-range": "^4.0.1", + "v-click-outside": "^2.0.2", + "vue": "^2.6.9" + } + }, "vue-eslint-parser": { "version": "7.11.0", "dev": true, diff --git a/package.json b/package.json index dded871..332189f 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,15 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "apexcharts": "^3.37.1", "core-js": "^3.6.5", "jsonlint": "^1.6.3", "jsonlint-mod": "^1.7.6", "mitt": "^2.1.0", "vue": "^2.6.11", + "vue-apexcharts": "^1.6.2", "vue-codemirror": "^4.0.6", + "vue-ctk-date-time-picker": "^2.5.0", "vue-json-pretty": "^1.8.1", "vue-router": "^3.2.0", "xrpl-client": "^1.8.0" diff --git a/src/views/Analytic copy.vue b/src/views/Analytic copy.vue new file mode 100644 index 0000000..46bf861 --- /dev/null +++ b/src/views/Analytic copy.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index eae6545..0b83a2e 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -1,21 +1,68 @@ From 9dcdc05fa802f5f9904ba3be2c60a3f464786f15 Mon Sep 17 00:00:00 2001 From: pakwai Date: Wed, 8 Mar 2023 00:09:57 +0800 Subject: [PATCH 03/17] Implemented chart for donut and bar by TransactionType and Account. --- src/plugins/ledger.js | 4 +- src/views/Analytic.vue | 209 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 192 insertions(+), 21 deletions(-) diff --git a/src/plugins/ledger.js b/src/plugins/ledger.js index de2a6e4..364db63 100644 --- a/src/plugins/ledger.js +++ b/src/plugins/ledger.js @@ -42,7 +42,7 @@ export default { if (this.ledgers.filter(l => l.ledgerIndex === ledgerIndex).length > 0) { console.log('Skip hydrating: known', ledger) } else { - console.log('Hydrate', ledger) + // console.log('Hydrate', ledger) const existingRecordIndex = this.ledgers.map(l => l.ledgerIndex).indexOf(ledgerIndex) if (existingRecordIndex < 0) { @@ -55,7 +55,7 @@ export default { ledgerData }) - console.log('Hydrated', ledger) + // console.log('Hydrated', ledger) return ledgerData } } diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index 0b83a2e..e106773 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -1,16 +1,31 @@ From 0df502222e5061c429350e3c9ea9134fe685964a Mon Sep 17 00:00:00 2001 From: pakwai Date: Wed, 8 Mar 2023 22:16:08 +0800 Subject: [PATCH 04/17] Added search ledger index, and load on mount. --- src/views/Analytic.vue | 107 +++++++++++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index e106773..30a0c59 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -14,12 +14,16 @@ placeholder="From Index" aria-label="Search"> - +

Analytic

+
+ + +
@@ -42,17 +46,16 @@ export default { paused: true, fromIndex: null, toIndex: null, + progressbar: { + value: 0, + max: 0 + }, donut: { series: [], chartOptions: { labels: [], title: { - text: 'Count by TransactionType', - align: 'left', - style: { - fontWeight: 'bold', - color: '#263238' - } + text: 'Count by TransactionType' }, legend: { show: true, @@ -81,12 +84,7 @@ export default { categories: [] }, title: { - text: 'Top 15 active accounts', - align: 'left', - style: { - fontWeight: 'bold', - color: '#263238' - } + text: 'Top 15 active accounts' } }, series: [{ @@ -96,22 +94,54 @@ export default { } }, methods: { - addPinLedger (e) { + async addPinLedger (e) { e.preventDefault() - console.log(this.fromIndex, this.toIndex) - this.chart.series[0].data[0] += 5 - this.$refs.chart.updateSeries([{ - data: this.chart.series[0].data - }], true) + try { + const fromIndex = parseInt(this.fromIndex) + const toIndex = parseInt(this.toIndex) + + if (isNaN(fromIndex) || isNaN(toIndex)) { + alert('Invalid! Input is not a number!') + return + } else if (fromIndex > toIndex) { + alert('Invalid! FromIndex > ToIndex') + return + } + + const diff = toIndex - fromIndex + if (diff > 100 && !confirm('You\'re about to load > 100 ledgers\nAre you sure to proceed?')) { + return + } - // this.$refs.chart.appendSeries({ - // name: 'newSeries', - // data: [32, 44, 31, 41, 22] - // }) + console.log('Sideload Ledger', fromIndex, toIndex) + this.progressbar.value = 0 + this.progressbar.max = diff + for (let ledgerIndex = fromIndex; ledgerIndex <= toIndex; ledgerIndex++) { + this.progressbar.value++ + if (this.isLedgerLoaded(ledgerIndex)) { + continue + } + await this.$ledger.hydrate(ledgerIndex) + this.updateCharts(ledgerIndex) + } + } catch (e) { + alert('Error: ' + e) + throw e + } finally { + this.progressbar.value = 0 + this.progressbar.max = 0 + } + }, + isLedgerLoaded (ledgerIndex) { + return this.$ledger.list.indexOf(ledgerIndex) !== -1 }, computeData (ledgerIndex) { const data = this.$ledger.getLedger(ledgerIndex) + if (data.error) { + throw data.error_message + } + return data.ledger.transactions.reduce((sum, ledger) => { const { byAccount, byTransactionType } = sum if (byTransactionType[ledger.TransactionType] === undefined) { @@ -201,8 +231,17 @@ export default { if (this.paused) { return } - await this.$ledger.hydrate(ledger.ledger_index) - this.updateCharts(ledger.ledger_index) + + try { + if (this.isLedgerLoaded(ledger.ledger_index)) { + return + } + await this.$ledger.hydrate(ledger.ledger_index) + this.updateCharts(ledger.ledger_index) + } catch (e) { + alert('Error: ' + e) + throw e + } }, clearAll () { // remove pin ledger @@ -234,6 +273,24 @@ export default { this.$events.on('connected', () => { this.connected = true }) + + setTimeout(() => { + console.log('is mounted', this.$ledger.list.length) + + try { + this.progressbar.value = 0 + this.progressbar.max = this.$ledger.list.length + this.$ledger.list.forEach(ledgerIndex => { + this.progressbar.value++ + this.updateCharts(ledgerIndex) + }) + } catch (e) { + alert('Error: ' + e) + } finally { + this.progressbar.value = 0 + this.progressbar.max = 0 + } + }, 1000) } } From 175cd99857472931c917e12551969c95673ffc8c Mon Sep 17 00:00:00 2001 From: pakwai Date: Thu, 9 Mar 2023 00:45:01 +0800 Subject: [PATCH 05/17] Drill down analytic transactions. --- src/components/JsonRenderer.vue | 8 ++- src/views/Analytic.vue | 103 +++++++++++++++++++++++++++----- 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/src/components/JsonRenderer.vue b/src/components/JsonRenderer.vue index ceb9a4b..e4acc57 100644 --- a/src/components/JsonRenderer.vue +++ b/src/components/JsonRenderer.vue @@ -21,7 +21,7 @@ import { hookHashToLedgerObjectHash } from '../plugins/helpers' export default { name: 'JsonRenderer', - props: ['data'], + props: ['data', '_blank'], components: { VueJsonPretty }, @@ -67,7 +67,11 @@ export default { // Check if not there yet if (this.$router.currentRoute.path !== newRoute) { console.log('Navigate', this.$router.currentRoute.path, newRoute) - this.$router.push(newRoute) + if (this._blank) { + window.open(newRoute, '_blank') + } else { + this.$router.push(newRoute) + } } } } diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index 30a0c59..c6a3fca 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -5,9 +5,9 @@
- - - + + +

Analytic

-
- - +
+ +
+ +
+ +
+
Selected Transactions
+
: {{txViewIndex + 1}} of {{selectedTxs.length}}
+
+ + +
+
+
diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index cce02ad..b09e4d9 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -49,7 +49,7 @@ - + From 18513254357b0dc96989b4989685b9e4585dfaa7 Mon Sep 17 00:00:00 2001 From: pakwai Date: Sat, 11 Mar 2023 16:28:26 +0800 Subject: [PATCH 09/17] Toast error for invalid filter address --- src/views/Analytic.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index b09e4d9..4e57ad0 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -387,6 +387,10 @@ export default { applyFilterAddresses () { if (!this.validateFilterAddresses()) { this.isFilterAddressesError = true + this.$toast.error('Invalid XRP Address to filter!', { + position: 'bottom-right', + timeout: 3000 + }) return } @@ -404,6 +408,7 @@ export default { this.updateCharts(ledgerIndex) }) } catch (e) { + console.log(e) this.$toast.error('Error: ' + e, { position: 'bottom-right', timeout: 3000 From ae31a7b524b1f81f70e823902e13f913c36be53d Mon Sep 17 00:00:00 2001 From: pakwai Date: Sat, 11 Mar 2023 19:18:55 +0800 Subject: [PATCH 10/17] Improve load performance and added chart filtering. --- src/views/Analytic.vue | 199 ++++++++++++++++++++++++----------------- 1 file changed, 117 insertions(+), 82 deletions(-) diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index 4e57ad0..22833f7 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -10,9 +10,9 @@ - - @@ -22,7 +22,7 @@

Analytic

- +
@@ -73,13 +73,14 @@ export default { inputFilterAddresses: null, finalFilterAddresses: [], isFilterAddressesError: false, + tempBarData: { labels: [], series: [] }, selectedTxs: [], txViewIndex: 0, - donutDataPointIndex: null, - barDataPointIndex: null, + donutDataPointText: null, + barDataPointText: null, progressbar: { - value: 0, - max: 0 + value: 2, + max: 10 }, donut: { series: [], @@ -93,16 +94,20 @@ export default { position: 'bottom', horizontalAlign: 'center' }, + noData: { + text: 'No Data is loaded' + }, chart: { events: { dataPointSelection: (event, chartContext, config) => { // The last parameter config contains additional information like `seriesIndex` and `dataPointIndex` for cartesian charts - if (this.donutDataPointIndex === config.dataPointIndex) { - this.donutDataPointIndex = null + const transactionType = this.donut.chartOptions.labels[config.dataPointIndex] + if (this.donutDataPointText === transactionType) { + this.donutDataPointText = null } else { - this.donutDataPointIndex = config.dataPointIndex + this.donutDataPointText = transactionType } - this.onAnalyticSelected() + this.onAnalyticSelected('donut', config.dataPointIndex) } } } @@ -110,23 +115,26 @@ export default { }, bar: { options: { + noData: { + text: 'No Data is loaded' + }, chart: { events: { dataPointSelection: (event, chartContext, config) => { // The last parameter config contains additional information like `seriesIndex` and `dataPointIndex` for cartesian charts - if (this.barDataPointIndex === config.dataPointIndex) { - this.barDataPointIndex = null + const address = this.bar.options.xaxis.categories[config.dataPointIndex] + if (this.barDataPointText === address) { + this.barDataPointText = null } else { - this.barDataPointIndex = config.dataPointIndex + this.barDataPointText = address - const address = this.bar.options.xaxis.categories[this.barDataPointIndex] navigator.clipboard.writeText(address) this.$toast('Address copied to clipboard', { position: 'bottom-right', timeout: 1000 }) } - this.onAnalyticSelected() + this.onAnalyticSelected('bar', config.dataPointIndex) } } }, @@ -165,20 +173,28 @@ export default { }, computeData (ledgerIndex) { const transactions = this.getLedgerTransactions(ledgerIndex) + const filterByAccount = this.barDataPointText + const filterByTransactionType = this.donutDataPointText return transactions.reduce((sum, ledger) => { const { byAccount, byTransactionType } = sum - if (byTransactionType[ledger.TransactionType] === undefined) { - byTransactionType[ledger.TransactionType] = 1 - } else { - byTransactionType[ledger.TransactionType]++ + + if (filterByAccount === null || filterByAccount === ledger.Account) { + if (byTransactionType[ledger.TransactionType] === undefined) { + byTransactionType[ledger.TransactionType] = 1 + } else { + byTransactionType[ledger.TransactionType]++ + } } - if (byAccount[ledger.Account] === undefined) { - byAccount[ledger.Account] = 1 - } else { - byAccount[ledger.Account]++ + if (filterByTransactionType === null || filterByTransactionType === ledger.TransactionType) { + if (byAccount[ledger.Account] === undefined) { + byAccount[ledger.Account] = 1 + } else { + byAccount[ledger.Account]++ + } } + return sum }, { byAccount: {}, byTransactionType: {} }) }, @@ -251,6 +267,48 @@ export default { this.updateDonutChart(ledgerIndex, data) this.updateBarChart(ledgerIndex, data) }, + reloadSelectedCharts (chartType) { + try { + this.clearChart(chartType) + + this.progressbar.value = 0 + this.progressbar.max = this.$ledger.list.length + + const accumulateData = { byAccount: {}, byTransactionType: {} } + this.$ledger.list.forEach(ledgerIndex => { + const data = this.computeData(ledgerIndex) + + for (const [key, value] of Object.entries(data.byTransactionType)) { + if (accumulateData.byTransactionType[key] === undefined) { + accumulateData.byTransactionType[key] = 0 + } + accumulateData.byTransactionType[key] += value + } + for (const [key, value] of Object.entries(data.byAccount)) { + if (accumulateData.byAccount[key] === undefined) { + accumulateData.byAccount[key] = 0 + } + accumulateData.byAccount[key] += value + } + }) + + if (chartType === undefined || chartType === 'donut') { + this.updateDonutChart(null, accumulateData) + } + + if (chartType === undefined || chartType === 'bar') { + this.updateBarChart(null, accumulateData) + } + } catch (e) { + this.$toast.error('Error: ' + e, { + position: 'bottom-right', + timeout: 3000 + }) + } finally { + this.progressbar.value = 0 + this.progressbar.max = 0 + } + }, async onNewLedger (ledger) { if (this.paused) { return @@ -318,16 +376,20 @@ export default { this.progressbar.max = 0 } }, - clearChart () { - // remove donut, keeping original reference - this.donut.series.length = 0 - this.donut.chartOptions.labels.length = 0 - this.$refs.donut.updateSeries([]) - - // remove bar, keeping original reference - this.bar.series[0].data.length = 0 - this.bar.options.xaxis.categories.length = 0 - this.$refs.bar.updateSeries([{ data: [] }]) + clearChart (chartType) { + if (chartType === undefined || chartType === 'donut') { + // remove donut, keeping original reference + this.donut.series.length = 0 + this.donut.chartOptions.labels.length = 0 + this.$refs.donut.updateSeries([]) + } + + if (chartType === undefined || chartType === 'bar') { + // remove bar, keeping original reference + this.bar.series[0].data.length = 0 + this.bar.options.xaxis.categories.length = 0 + this.$refs.bar.updateSeries([{ data: [] }]) + } }, clearAll () { // remove pin ledger @@ -337,21 +399,20 @@ export default { } this.clearChart() + + // other related to chart + this.selectedTxs.length = 0 + this.selectedTxs.push() }, - onAnalyticSelected () { - const filterByTransactionType = this.donutDataPointIndex !== null - ? this.donut.chartOptions.labels[this.donutDataPointIndex] - : null - const filterByAccount = this.barDataPointIndex !== null - ? this.bar.options.xaxis.categories[this.barDataPointIndex] - : null + onAnalyticSelected (chartType, dataPointIndex) { + const filterByTransactionType = this.donutDataPointText + const filterByAccount = this.barDataPointText console.log(filterByTransactionType, filterByAccount) this.selectedTxs.length = 0 this.txViewIndex = 0 if (filterByTransactionType === null && filterByAccount === null) { this.selectedTxs.push() - return } this.$ledger.list.forEach(ledgerIndex => { @@ -362,6 +423,14 @@ export default { ) this.selectedTxs.push(...filtered) }) + + if (chartType === 'donut') { + this.reloadSelectedCharts('bar') + } else if (chartType === 'bar') { + this.reloadSelectedCharts('donut') + } else { + this.reloadSelectedCharts() + } }, transactionNext () { if (this.txViewIndex + 1 === this.selectedTxs.length) { @@ -394,30 +463,12 @@ export default { return } - try { - this.isFilterAddressesError = false - this.finalFilterAddresses = this.inputFilterAddresses === null || this.inputFilterAddresses === '' - ? [] - : this.inputFilterAddresses.split('\n') - this.clearChart() + this.isFilterAddressesError = false + this.finalFilterAddresses = this.inputFilterAddresses === null || this.inputFilterAddresses === '' + ? [] + : this.inputFilterAddresses.split('\n') - this.progressbar.value = 0 - this.progressbar.max = this.$ledger.list.length - this.$ledger.list.forEach(ledgerIndex => { - this.progressbar.value++ - this.updateCharts(ledgerIndex) - }) - } catch (e) { - console.log(e) - this.$toast.error('Error: ' + e, { - position: 'bottom-right', - timeout: 3000 - }) - throw e - } finally { - this.progressbar.value = 0 - this.progressbar.max = 0 - } + this.reloadSelectedCharts() } }, async mounted () { @@ -435,23 +486,7 @@ export default { setTimeout(() => { console.log('is mounted', this.$ledger.list.length) - - try { - this.progressbar.value = 0 - this.progressbar.max = this.$ledger.list.length - this.$ledger.list.forEach(ledgerIndex => { - this.progressbar.value++ - this.updateCharts(ledgerIndex) - }) - } catch (e) { - this.$toast.error('Error: ' + e, { - position: 'bottom-right', - timeout: 3000 - }) - } finally { - this.progressbar.value = 0 - this.progressbar.max = 0 - } + this.reloadSelectedCharts() }, 1000) } } From 85ecae36a22d202c621bfc983eeef63df07f7fa5 Mon Sep 17 00:00:00 2001 From: pakwai Date: Sun, 12 Mar 2023 23:58:10 +0800 Subject: [PATCH 11/17] Added analytic for currency issuer --- src/components/JsonRenderer.vue | 2 +- src/views/Analytic.vue | 290 ++++++++++++++++++++++++-------- 2 files changed, 220 insertions(+), 72 deletions(-) diff --git a/src/components/JsonRenderer.vue b/src/components/JsonRenderer.vue index cfa8bba..572fe16 100644 --- a/src/components/JsonRenderer.vue +++ b/src/components/JsonRenderer.vue @@ -67,7 +67,7 @@ export default { console.log('Hook Namespace', { newRoute }) } - if (fieldName.match(/memodata/) && value.length) { + if (fieldName.match(/memo/) && String(value).match(/^[A-F0-9]{6,}$/)) { humanizeMemo = decodeHex(value) } diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index 22833f7..e158f85 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -20,23 +20,27 @@

Analytic

+ +
+
+
+ Filter by Accounts or Issuers + One address per line only +
+ +
+ +
+
-
+ +
-
-
-
- Filter by Address - One address per line only -
- -
- -
+

@@ -58,6 +62,19 @@ import VueApexCharts from 'vue-apexcharts' import JsonRenderer from '../components/JsonRenderer.vue' +const BAR_ACCOUNT_SERIES = { + name: 'Account', + data: [] +} +const BAR_ISSUER_PAYS_SERIES = { + name: 'Pays', + data: [] +} +const BAR_ISSUER_GETS_SERIES = { + name: 'Gets', + data: [] +} + export default { name: 'Analytic', components: { @@ -78,6 +95,8 @@ export default { txViewIndex: 0, donutDataPointText: null, barDataPointText: null, + bar2DataPointText: null, + bar2DataPointText1: null, progressbar: { value: 2, max: 10 @@ -85,18 +104,14 @@ export default { donut: { series: [], chartOptions: { + title: { text: 'Count by TransactionType' }, + noData: { text: 'No Data is loaded' }, labels: [], - title: { - text: 'Count by TransactionType' - }, legend: { show: true, position: 'bottom', horizontalAlign: 'center' }, - noData: { - text: 'No Data is loaded' - }, chart: { events: { dataPointSelection: (event, chartContext, config) => { @@ -115,9 +130,9 @@ export default { }, bar: { options: { - noData: { - text: 'No Data is loaded' - }, + title: { text: 'Top 20 active accounts' }, + noData: { text: 'No Data is loaded' }, + dataLabels: { enabled: false }, chart: { events: { dataPointSelection: (event, chartContext, config) => { @@ -127,12 +142,6 @@ export default { this.barDataPointText = null } else { this.barDataPointText = address - - navigator.clipboard.writeText(address) - this.$toast('Address copied to clipboard', { - position: 'bottom-right', - timeout: 1000 - }) } this.onAnalyticSelected('bar', config.dataPointIndex) } @@ -144,16 +153,49 @@ export default { horizontal: true } }, - xaxis: { - categories: [] - }, - title: { - text: 'Top 15 active accounts' - } + xaxis: { categories: [] } }, + series: [BAR_ACCOUNT_SERIES] + }, + bar2: { series: [{ + name: 'Pays', + data: [] + }, { + name: 'Gets', data: [] - }] + }], + options: { + title: { text: 'Top 20 active issuers' }, + noData: { text: 'No Data is loaded' }, + dataLabels: { enabled: false }, + chart: { + type: 'bar', + stacked: true, + events: { + dataPointSelection: (event, chartContext, config) => { + // The last parameter config contains additional information like `seriesIndex` and `dataPointIndex` for cartesian charts + const series = this.bar2.series[config.seriesIndex].name + const address = this.bar2.options.xaxis.categories[config.dataPointIndex].split('.')[1] + const selector = series + ':' + address + if (this.bar2DataPointText === selector) { + this.bar2DataPointText = null + } else { + this.bar2DataPointText = selector + } + this.onAnalyticSelected('bar2', config.dataPointIndex) + } + } + }, + colors: ['#FF4560', '#008FFB'], + legend: { show: false }, + plotOptions: { + bar: { horizontal: true } + }, + xaxis: { + categories: [] + } + } } } }, @@ -168,18 +210,31 @@ export default { } return data.ledger.transactions - .filter(ledger => this.finalFilterAddresses.length === 0 || - this.finalFilterAddresses.indexOf(ledger.Account) !== -1) + .filter(ledger => + this.finalFilterAddresses.length === 0 || + this.finalFilterAddresses.indexOf(ledger.Account) !== -1 || + this.finalFilterAddresses.indexOf(ledger?.TakerGets?.issuer ?? '') !== -1 || + this.finalFilterAddresses.indexOf(ledger?.TakerPays?.issuer ?? '') !== -1 + ) }, computeData (ledgerIndex) { const transactions = this.getLedgerTransactions(ledgerIndex) const filterByAccount = this.barDataPointText const filterByTransactionType = this.donutDataPointText + const filterByIssuerPays = (this.bar2DataPointText ?? '').substring(0, 4) === BAR_ISSUER_PAYS_SERIES.name + ? this.bar2DataPointText.split(':')[1] : null + const filterByIssuerGets = (this.bar2DataPointText ?? '').substring(0, 4) === BAR_ISSUER_GETS_SERIES.name + ? this.bar2DataPointText.split(':')[1] : null return transactions.reduce((sum, ledger) => { - const { byAccount, byTransactionType } = sum + const { byAccount, byTransactionType, byIssuer } = sum + + const isAccount = filterByAccount === null || filterByAccount === ledger.Account + const isTxType = filterByTransactionType === null || filterByTransactionType === ledger.TransactionType + const isIssuerPays = filterByIssuerPays === null || filterByIssuerPays === ledger?.TakerPays?.issuer + const isIssuerGets = filterByIssuerGets === null || filterByIssuerGets === ledger?.TakerGets?.issuer - if (filterByAccount === null || filterByAccount === ledger.Account) { + if (isAccount && isIssuerPays && isIssuerGets) { if (byTransactionType[ledger.TransactionType] === undefined) { byTransactionType[ledger.TransactionType] = 1 } else { @@ -187,7 +242,7 @@ export default { } } - if (filterByTransactionType === null || filterByTransactionType === ledger.TransactionType) { + if (isTxType && isIssuerPays && isIssuerGets) { if (byAccount[ledger.Account] === undefined) { byAccount[ledger.Account] = 1 } else { @@ -195,25 +250,45 @@ export default { } } + if (isAccount && isTxType && isIssuerGets) { + const address = ledger?.TakerPays?.issuer + if (address !== undefined) { + if (byIssuer[address] === undefined) { + byIssuer[address] = { pays: 1, gets: 0, currency: ledger?.TakerPays?.currency } + } else { + byIssuer[address].pays++ + } + } + } + + if (isAccount && isTxType && isIssuerPays) { + const address = ledger?.TakerGets?.issuer + if (address !== undefined) { + if (byIssuer[address] === undefined) { + byIssuer[address] = { pays: 0, gets: 1, currency: ledger?.TakerGets?.currency } + } else { + byIssuer[address].gets++ + } + } + } + return sum - }, { byAccount: {}, byTransactionType: {} }) + }, { byAccount: {}, byTransactionType: {}, byIssuer: {} }) }, sortArrayPair (sortingArray, pairIndexArray) { if (sortingArray.length !== pairIndexArray.length) { return // not match, skip } - const mappedArray = sortingArray.map((o, i) => ({ sortValue: o, pairValue: pairIndexArray[i] })) + const mappedArray = sortingArray.map((o, i) => ({ sortValue: o.value ?? o, sortSrc: o, pairSrc: pairIndexArray[i] })) mappedArray.sort((m, n) => n.sortValue - m.sortValue) sortingArray.length = 0 pairIndexArray.length = 0 - sortingArray.push(...mappedArray.map(o => o.sortValue)) - pairIndexArray.push(...mappedArray.map(o => o.pairValue)) + sortingArray.push(...mappedArray.map(o => o.sortSrc)) + pairIndexArray.push(...mappedArray.map(o => o.pairSrc)) }, updateDonutChart (ledgerIndex, data) { - // const startTime = new Date().getTime() - const donutLabels = [...this.donut.chartOptions.labels] const donutSeries = [...this.donut.series] for (const [key, value] of Object.entries(data.byTransactionType)) { @@ -227,16 +302,12 @@ export default { } this.sortArrayPair(donutSeries, donutLabels) - // console.log('updateDonutChart computed', ledgerIndex, new Date().getTime() - startTime) this.donut.chartOptions.labels.length = 0 // hacky workaround to keep the label reference this.donut.chartOptions.labels.push(...donutLabels) this.donut.series = donutSeries // hacky workaround to update the series value this.$refs.donut.updateSeries(donutSeries) - // console.log('updateDonutChart render', ledgerIndex, new Date().getTime() - startTime) }, updateBarChart (ledgerIndex, data) { - // const startTime = new Date().getTime() - const barCategories = [...this.bar.options.xaxis.categories] const barSeries = [...this.bar.series[0].data] for (const [key, value] of Object.entries(data.byAccount)) { @@ -250,31 +321,77 @@ export default { } this.sortArrayPair(barSeries, barCategories) - barSeries.splice(15) - barCategories.splice(15) + barSeries.splice(20) + barCategories.splice(20) - // console.log('updateBarChart computed', ledgerIndex, new Date().getTime() - startTime) this.bar.options.xaxis.categories.length = 0 // hacky workaround to keep the label reference this.bar.options.xaxis.categories.push(...barCategories) this.bar.series[0].data = barSeries // hacky workaround to update the series value this.$refs.bar.updateSeries([{ data: barSeries }]) - // console.log('updateBarChart render', ledgerIndex, new Date().getTime() - startTime) + }, + updateBar2Chart (ledgerIndex, data) { + const bar2Categories = [...this.bar2.options.xaxis.categories] + const bar2Series0 = [...this.bar2.series[0].data] + const bar2Series1 = [...this.bar2.series[1].data] + for (const [key, value] of Object.entries(data.byIssuer)) { + const issuerLabel = value.currency.substring(0, 5) + '.' + key + const bar2Index = bar2Categories.indexOf(issuerLabel) + if (bar2Index !== -1) { + bar2Series0[bar2Index] -= value.pays + bar2Series1[bar2Index] += value.gets + } else { + bar2Categories.push(issuerLabel) + bar2Series0.push(-value.pays) + bar2Series1.push(value.gets) + } + } + + const bar2Sort = bar2Series0.map((s0, i) => ({ + series0: s0, + series1: bar2Series1[i], + value: bar2Series1[i] - s0 // because s0 is -ve value + })) + + this.sortArrayPair(bar2Sort, bar2Categories) + bar2Series0.length = 0 + bar2Series1.length = 0 + bar2Series0.push(...bar2Sort.map(o => o.series0)) + bar2Series1.push(...bar2Sort.map(o => o.series1)) + + bar2Series0.splice(20) + bar2Series1.splice(20) + bar2Categories.splice(20) + + this.bar2.options.xaxis.categories.length = 0 // hacky workaround to keep the label reference + this.bar2.options.xaxis.categories.push(...bar2Categories) + this.bar2.series[0].data = bar2Series0 // hacky workaround to update the series value + this.bar2.series[1].data = bar2Series1 // hacky workaround to update the series value + this.$refs.bar2.updateSeries([{ + data: bar2Series0 + }, { + data: bar2Series1 + }]) }, updateCharts (ledgerIndex) { const data = this.computeData(ledgerIndex) this.updateDonutChart(ledgerIndex, data) this.updateBarChart(ledgerIndex, data) + this.updateBar2Chart(ledgerIndex, data) }, - reloadSelectedCharts (chartType) { + reloadSelectedCharts (chartTypes = []) { try { - this.clearChart(chartType) + if (chartTypes.length) { + chartTypes.map(this.clearChart) + } else { + this.clearChart() + } this.progressbar.value = 0 this.progressbar.max = this.$ledger.list.length - const accumulateData = { byAccount: {}, byTransactionType: {} } + const accumulateData = { byAccount: {}, byTransactionType: {}, byIssuer: {} } this.$ledger.list.forEach(ledgerIndex => { const data = this.computeData(ledgerIndex) @@ -290,20 +407,32 @@ export default { } accumulateData.byAccount[key] += value } + for (const [key, value] of Object.entries(data.byIssuer)) { + if (accumulateData.byIssuer[key] === undefined) { + accumulateData.byIssuer[key] = { pays: 0, gets: 0, currency: value.currency } + } + accumulateData.byIssuer[key].pays += value.pays + accumulateData.byIssuer[key].gets += value.gets + } }) - if (chartType === undefined || chartType === 'donut') { + if (chartTypes.indexOf('donut') !== -1 || chartTypes.length === 0) { this.updateDonutChart(null, accumulateData) } - if (chartType === undefined || chartType === 'bar') { + if (chartTypes.indexOf('bar') !== -1 || chartTypes.length === 0) { this.updateBarChart(null, accumulateData) } + + if (chartTypes.indexOf('bar2') !== -1 || chartTypes.length === 0) { + this.updateBar2Chart(null, accumulateData) + } } catch (e) { this.$toast.error('Error: ' + e, { position: 'bottom-right', timeout: 3000 }) + throw e } finally { this.progressbar.value = 0 this.progressbar.max = 0 @@ -388,7 +517,15 @@ export default { // remove bar, keeping original reference this.bar.series[0].data.length = 0 this.bar.options.xaxis.categories.length = 0 - this.$refs.bar.updateSeries([{ data: [] }]) + this.$refs.bar.updateSeries([BAR_ACCOUNT_SERIES]) + } + + if (chartType === undefined || chartType === 'bar2') { + // remove bar, keeping original reference + this.bar2.series[0].data.length = 0 + this.bar2.series[1].data.length = 0 + this.bar2.options.xaxis.categories.length = 0 + this.$refs.bar2.updateSeries([BAR_ISSUER_PAYS_SERIES, BAR_ISSUER_GETS_SERIES]) } }, clearAll () { @@ -407,27 +544,38 @@ export default { onAnalyticSelected (chartType, dataPointIndex) { const filterByTransactionType = this.donutDataPointText const filterByAccount = this.barDataPointText - console.log(filterByTransactionType, filterByAccount) + const filterByIssuerPays = (this.bar2DataPointText ?? '').substring(0, 4) === BAR_ISSUER_PAYS_SERIES.name + ? this.bar2DataPointText.split(':')[1] : null + const filterByIssuerGets = (this.bar2DataPointText ?? '').substring(0, 4) === BAR_ISSUER_GETS_SERIES.name + ? this.bar2DataPointText.split(':')[1] : null + + console.log('Filter:', { + filterByTransactionType, filterByAccount, filterByIssuerPays, filterByIssuerGets + }) this.selectedTxs.length = 0 this.txViewIndex = 0 - if (filterByTransactionType === null && filterByAccount === null) { + if (filterByTransactionType === null && filterByAccount === null && filterByIssuerPays === null && filterByIssuerGets === null) { this.selectedTxs.push() + } else { + this.$ledger.list.forEach(ledgerIndex => { + const transactions = this.getLedgerTransactions(ledgerIndex) + const filtered = transactions.filter(transaction => + (filterByTransactionType === null || filterByTransactionType === transaction.TransactionType) && + (filterByAccount === null || filterByAccount === transaction.Account) && + (filterByIssuerGets === null || filterByIssuerGets === transaction?.TakerGets?.issuer) && + (filterByIssuerPays === null || filterByIssuerPays === transaction?.TakerPays?.issuer) + ) + this.selectedTxs.push(...filtered) + }) } - this.$ledger.list.forEach(ledgerIndex => { - const transactions = this.getLedgerTransactions(ledgerIndex) - const filtered = transactions.filter(transaction => - (transaction.Account === filterByAccount || filterByAccount === null) && - (transaction.TransactionType === filterByTransactionType || filterByTransactionType === null) - ) - this.selectedTxs.push(...filtered) - }) - if (chartType === 'donut') { - this.reloadSelectedCharts('bar') + this.reloadSelectedCharts(['bar', 'bar2']) } else if (chartType === 'bar') { - this.reloadSelectedCharts('donut') + this.reloadSelectedCharts(['donut', 'bar2']) + } else if (chartType === 'bar2') { + this.reloadSelectedCharts(['donut', 'bar']) } else { this.reloadSelectedCharts() } From 9a7106b77d8dbf29d57398d1bc810a7dae924167 Mon Sep 17 00:00:00 2001 From: pakwai Date: Mon, 13 Mar 2023 00:06:17 +0800 Subject: [PATCH 12/17] Fix throw error --- src/views/Analytic.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index e158f85..8b724a3 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -98,8 +98,8 @@ export default { bar2DataPointText: null, bar2DataPointText1: null, progressbar: { - value: 2, - max: 10 + value: null, + max: null }, donut: { series: [], @@ -634,7 +634,9 @@ export default { setTimeout(() => { console.log('is mounted', this.$ledger.list.length) - this.reloadSelectedCharts() + if (this.$ledger.list.length) { + this.reloadSelectedCharts() + } }, 1000) } } From 71d942234aaa36081e2b9ef60307bb350fd3522f Mon Sep 17 00:00:00 2001 From: pakwai Date: Tue, 14 Mar 2023 23:42:05 +0800 Subject: [PATCH 13/17] Set engine to use node 16 --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 4fe59af..87e5e01 100644 --- a/package.json +++ b/package.json @@ -37,5 +37,8 @@ "sass": "^1.26.5", "sass-loader": "^8.0.2", "vue-template-compiler": "^2.6.11" + }, + "engines": { + "node": "16.x" } } From 55ef216715621bb30d264a021950d04aafa8d9e1 Mon Sep 17 00:00:00 2001 From: pakwai Date: Thu, 23 Mar 2023 22:02:54 +0800 Subject: [PATCH 14/17] Remove duplicate file --- src/views/Analytic copy.vue | 65 ------------------------------------- 1 file changed, 65 deletions(-) delete mode 100644 src/views/Analytic copy.vue diff --git a/src/views/Analytic copy.vue b/src/views/Analytic copy.vue deleted file mode 100644 index 46bf861..0000000 --- a/src/views/Analytic copy.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - - - From 5d203b51646949eeb3b2fb127c558761da780aa6 Mon Sep 17 00:00:00 2001 From: pakwai Date: Sun, 2 Apr 2023 18:49:45 +0800 Subject: [PATCH 15/17] Lazy load vue-apexcharts to reduce bundle size, and fix build other warning. --- src/views/Analytic.vue | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index 8b724a3..ba4ae6a 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -59,8 +59,9 @@ From afcd87a7b318d754b3873295978427d7edf907e1 Mon Sep 17 00:00:00 2001 From: pakwai Date: Sun, 2 Apr 2023 20:07:58 +0800 Subject: [PATCH 17/17] Throttle bulk load. --- src/plugins/utils.js | 7 +++++++ src/views/Analytic.vue | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/plugins/utils.js diff --git a/src/plugins/utils.js b/src/plugins/utils.js new file mode 100644 index 0000000..116b118 --- /dev/null +++ b/src/plugins/utils.js @@ -0,0 +1,7 @@ +const delay = async (ms) => { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +export { + delay +} diff --git a/src/views/Analytic.vue b/src/views/Analytic.vue index b188122..542d0fc 100644 --- a/src/views/Analytic.vue +++ b/src/views/Analytic.vue @@ -59,10 +59,12 @@