Sfoglia il codice sorgente

improve stock price history chart

Daniel Bohry 3 mesi fa
parent
commit
0c8cb33d2f
1 ha cambiato i file con 111 aggiunte e 15 eliminazioni
  1. 111 15
      src/components/StockPriceHistory.svelte

+ 111 - 15
src/components/StockPriceHistory.svelte

@@ -43,19 +43,42 @@
 	}
 
 	function renderChart(data) {
+		// Dynamic label formatting based on range and data length
 		const labels = data.map((item) => {
 			const date = new Date(item.createdAt);
-			const dd = String(date.getDate()).padStart(2, '0');
-			const mm = String(date.getMonth() + 1).padStart(2, '0');
-			const yyyy = date.getFullYear();
-			return `${dd}/${mm}/${yyyy}`;
+
+			// For longer ranges, use more concise date formats
+			if (selectedRange === '6m' || selectedRange === '1y') {
+				const month = date.toLocaleDateString('en-US', { month: 'short' });
+				const year = date.getFullYear();
+				return `${month} ${year}`;
+			} else if (selectedRange === '30d') {
+				const day = date.getDate();
+				const month = date.toLocaleDateString('en-US', { month: 'short' });
+				return `${day} ${month}`;
+			} else {
+				// 5d range - show day and month
+				const dd = String(date.getDate()).padStart(2, '0');
+				const mm = String(date.getMonth() + 1).padStart(2, '0');
+				return `${dd}/${mm}`;
+			}
 		});
+
 		const prices = data.map((item) => item.price);
 
+		// Dynamic point visibility based on data density
+		const shouldShowPoints = data.length <= 30; // Hide points if more than 30 data points
+		const pointRadius = shouldShowPoints ? 5 : 0;
+		const pointHoverRadius = shouldShowPoints ? 7 : 5;
+
 		if (chartInstance) {
 			chartInstance.destroy();
 		}
 
+		const gradientFill = chartCanvas.getContext('2d').createLinearGradient(0, 0, 0, 400);
+		gradientFill.addColorStop(0, 'rgba(59, 130, 246, 0.3)');
+		gradientFill.addColorStop(1, 'rgba(59, 130, 246, 0.05)');
+
 		chartInstance = new Chart(chartCanvas, {
 			type: 'line',
 			data: {
@@ -64,31 +87,84 @@
 					{
 						label: `Price (${data[0]?.currency || ''})`,
 						data: prices,
-						fill: false,
+						fill: true,
+						backgroundColor: gradientFill,
                         borderColor: 'rgb(59, 130, 246)',
                         borderWidth: 3,
                         pointBackgroundColor: 'rgb(59, 130, 246)',
                         pointBorderColor: 'white',
                         pointBorderWidth: 2,
-                        pointRadius: 5,
-                        pointHoverRadius: 7,
+                        pointRadius: pointRadius,
+                        pointHoverRadius: pointHoverRadius,
                         tension: 0.4
 					}
 				]
 			},
 			options: {
 				responsive: true,
+				maintainAspectRatio: false,
+				interaction: {
+					intersect: false,
+					mode: 'index'
+				},
+				// Add decimation for performance with large datasets
+				datasets: {
+					line: {
+						pointRadius: pointRadius, // Apply to all line datasets
+						pointHoverRadius: pointHoverRadius
+					}
+				},
+				elements: {
+					line: {
+						tension: 0.4
+					}
+				},
+				// Performance optimization for large datasets
+				animation: {
+					duration: data.length > 100 ? 0 : 750 // Disable animation for large datasets
+				},
 				scales: {
 					x: {
 						title: {
 							display: true,
-							text: 'Date'
+							text: 'Date',
+							color: 'rgb(107, 114, 128)',
+							font: {
+								size: 12,
+								weight: 'bold'
+							}
+						},
+						grid: {
+							color: 'rgba(107, 114, 128, 0.1)'
+						},
+						ticks: {
+							color: 'rgb(107, 114, 128)',
+							// Smart label skipping based on data length
+							maxTicksLimit: data.length > 50 ? 8 : data.length > 20 ? 12 : undefined,
+							// Auto-skip for better readability
+							autoSkip: true,
+							maxRotation: 45,
+							minRotation: 0
 						}
 					},
 					y: {
 						title: {
 							display: true,
-							text: 'Price'
+							text: 'Price',
+							color: 'rgb(107, 114, 128)',
+							font: {
+								size: 12,
+								weight: 'bold'
+							}
+						},
+						grid: {
+							color: 'rgba(107, 114, 128, 0.1)'
+						},
+						ticks: {
+							color: 'rgb(107, 114, 128)',
+							callback: function (value) {
+								return value.toFixed(2);
+							}
 						}
 					}
 				},
@@ -97,21 +173,39 @@
 						display: false
 					},
 					tooltip: {
+						backgroundColor: 'rgba(0, 0, 0, 0.8)',
+						titleColor: 'white',
+						bodyColor: 'white',
+						borderColor: 'rgb(59, 130, 246)',
+						borderWidth: 1,
+						cornerRadius: 8,
+						displayColors: false,
 						callbacks: {
 							title: (context) => {
 								const idx = context[0].dataIndex;
 								const raw = data[idx];
 								const dt = new Date(raw.createdAt);
-								const dd = String(dt.getDate()).padStart(2, '0');
-								const mm = String(dt.getMonth() + 1).padStart(2, '0');
-								const yyyy = dt.getFullYear();
-								return `${dd}/${mm}/${yyyy}`;
+								return dt.toLocaleDateString('en-US', {
+									year: 'numeric',
+									month: 'long',
+									day: 'numeric'
+								});
 							},
-							label: (context) => context.parsed.y.toFixed(2)
+							label: (context) => {
+								const value = context.parsed.y;
+								const currency = data[context.dataIndex]?.currency || '';
+								return `Price: ${currency}${value.toFixed(2)}`;
+							}
 						}
 					},
 					datalabels: {
 						display: false
+					},
+					// Add decimation for large datasets
+					decimation: {
+						enabled: data.length > 100,
+						algorithm: 'lttb', // Largest Triangle Three Buckets algorithm
+						samples: 100 // Reduce to max 100 samples for performance
 					}
 				}
 			}
@@ -196,7 +290,9 @@
 				})}
 			</p>
 		{/if}
-		<canvas bind:this={chartCanvas}></canvas>
+		<div class="relative h-80">
+			<canvas bind:this={chartCanvas}></canvas>
+		</div>
 
 		<div class="flex justify-center gap-4 mt-6">
 			{#each ranges as range}