|
@@ -0,0 +1,116 @@
|
|
|
|
|
+<script>
|
|
|
|
|
+ import { ArcElement, Chart, DoughnutController, Legend, Title, Tooltip } from 'chart.js';
|
|
|
|
|
+ import { onDestroy } from 'svelte';
|
|
|
|
|
+
|
|
|
|
|
+ export let data = {};
|
|
|
|
|
+ export let currency = 'USD';
|
|
|
|
|
+
|
|
|
|
|
+ let chartContainer;
|
|
|
|
|
+ let chartInstance;
|
|
|
|
|
+
|
|
|
|
|
+ function formatCurrency(value, fmtCurrency) {
|
|
|
|
|
+ return value.toLocaleString('en-US', {
|
|
|
|
|
+ style: 'currency',
|
|
|
|
|
+ currency: fmtCurrency
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Chart.register(DoughnutController, ArcElement, Tooltip, Legend, Title);
|
|
|
|
|
+
|
|
|
|
|
+ const groupByMarket = (stocks) => {
|
|
|
|
|
+ const groups = {
|
|
|
|
|
+ Brazil: 0,
|
|
|
|
|
+ Germany: 0,
|
|
|
|
|
+ USA: 0,
|
|
|
|
|
+ Others: 0
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ stocks.forEach(stock => {
|
|
|
|
|
+ const code = stock.code;
|
|
|
|
|
+
|
|
|
|
|
+ if (code.startsWith('BVMF:')) {
|
|
|
|
|
+ groups.Brazil += stock.total;
|
|
|
|
|
+ } else if (code.startsWith('FRA:')) {
|
|
|
|
|
+ groups.Germany += stock.total;
|
|
|
|
|
+ } else if (code.startsWith('ETR:')) {
|
|
|
|
|
+ groups.Others += stock.total;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ groups.USA += stock.total;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return groups;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ function updateChart() {
|
|
|
|
|
+ if (chartInstance) {
|
|
|
|
|
+ chartInstance.destroy();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!data || !data.stocks || data.stocks.length === 0) {
|
|
|
|
|
+ chartInstance = null;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const grouped = groupByMarket(data.stocks);
|
|
|
|
|
+ const labels = Object.keys(grouped);
|
|
|
|
|
+ const values = Object.values(grouped);
|
|
|
|
|
+
|
|
|
|
|
+ const chartData = {
|
|
|
|
|
+ labels,
|
|
|
|
|
+ datasets: [{
|
|
|
|
|
+ data: values,
|
|
|
|
|
+ backgroundColor: ['#3498db', '#2ecc71', '#f39c12', '#9b59b6'],
|
|
|
|
|
+ borderColor: '#fff',
|
|
|
|
|
+ borderWidth: 1
|
|
|
|
|
+ }]
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const chartOptions = {
|
|
|
|
|
+ responsive: true,
|
|
|
|
|
+ maintainAspectRatio: false,
|
|
|
|
|
+ plugins: {
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ display: false
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ callbacks: {
|
|
|
|
|
+ label: function(tooltipItem) {
|
|
|
|
|
+ const value = tooltipItem.raw;
|
|
|
|
|
+ const label = tooltipItem.label;
|
|
|
|
|
+ return `${label}: ${formatCurrency(value, currency)}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ chartInstance = new Chart(chartContainer, {
|
|
|
|
|
+ type: 'doughnut',
|
|
|
|
|
+ data: chartData,
|
|
|
|
|
+ options: chartOptions
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $: if (chartContainer && data?.stocks?.length) {
|
|
|
|
|
+ updateChart();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ onDestroy(() => {
|
|
|
|
|
+ if (chartInstance) {
|
|
|
|
|
+ chartInstance.destroy();
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<!-- Chart container -->
|
|
|
|
|
+<div class="max-w-md mx-auto relative" style="height: 400px;">
|
|
|
|
|
+ {#if data?.stocks?.length}
|
|
|
|
|
+ <p class="text-center font-semibold text-gray-700 dark:text-gray-300 mb-2 text-base">
|
|
|
|
|
+ Market Distribution
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <canvas bind:this={chartContainer} class="w-full h-full"></canvas>
|
|
|
|
|
+ {:else}
|
|
|
|
|
+ <p class="text-center text-gray-500 dark:text-gray-400">No data available.</p>
|
|
|
|
|
+ {/if}
|
|
|
|
|
+</div>
|