1import {Controller} from '@hotwired/stimulus'; 2import { 3 Chart, 4 BarController, 5 BarElement, 6 LineController, 7 LineElement, 8 PointElement, 9 CategoryScale, 10 LinearScale, 11 Tooltip, 12 Legend, 13 Filler, 14} from 'chart.js'; 15 16Chart.register( 17 BarController, 18 BarElement, 19 LineController, 20 LineElement, 21 PointElement, 22 CategoryScale, 23 LinearScale, 24 Tooltip, 25 Legend, 26 Filler, 27); 28 29export default class extends Controller { 30 static values = { 31 trends: Array, 32 periodKey: String, 33 }; 34 35 static targets = ['trendsCanvas']; 36 37 connect() { 38 if (this.hasTrendsCanvasTarget && this.trendsValue?.length) { 39 this._renderTrendsChart(); 40 } 41 } 42 43 disconnect() { 44 if (this.trendsChart) { 45 this.trendsChart.destroy(); 46 } 47 } 48 49 _renderTrendsChart() { 50 const ctx = this.trendsCanvasTarget.getContext('2d'); 51 const data = this.trendsValue; 52 const periodKey = this.periodKeyValue || 'date'; 53 54 const labels = data.map(row => row[periodKey]); 55 const wantCounts = data.map(row => row.total_wants || 0); 56 const convertedCounts = data.map(row => row.converted || 0); 57 const deletedCounts = data.map(row => row.deleted || 0); 58 const conversionRates = data.map(row => row.conversion_rate || 0); 59 60 this.trendsChart = new Chart(ctx, { 61 type: 'bar', 62 data: { 63 labels, 64 datasets: [ 65 { 66 label: 'Wantæ°', 67 data: wantCounts, 68 backgroundColor: 'rgba(59, 130, 246, 0.7)', 69 borderColor: 'rgba(59, 130, 246, 1)', 70 borderWidth: 1, 71 yAxisID: 'y', 72 order: 3, 73 }, 74 { 75 label: 'è»¢ææ°', 76 data: convertedCounts, 77 backgroundColor: 'rgba(34, 197, 94, 0.7)', 78 borderColor: 'rgba(34, 197, 94, 1)', 79 borderWidth: 1, 80 yAxisID: 'y', 81 order: 2, 82 }, 83 { 84 label: 'å餿°', 85 data: deletedCounts, 86 backgroundColor: 'rgba(239, 68, 68, 0.5)', 87 borderColor: 'rgba(239, 68, 68, 1)', 88 borderWidth: 1, 89 yAxisID: 'y', 90 order: 4, 91 }, 92 { 93 label: '転æç (%)', 94 data: conversionRates, 95 type: 'line', 96 borderColor: 'rgba(168, 85, 247, 1)', 97 backgroundColor: 'rgba(168, 85, 247, 0.1)', 98 fill: true, 99 tension: 0.3, 100 pointRadius: 3, 101 pointHoverRadius: 5, 102 yAxisID: 'y1', 103 order: 1, 104 }, 105 ], 106 }, 107 options: { 108 responsive: true, 109 maintainAspectRatio: false, 110 interaction: { 111 mode: 'index', 112 intersect: false, 113 }, 114 plugins: { 115 legend: { 116 position: 'top', 117 labels: { 118 boxWidth: 12, 119 padding: 15, 120 }, 121 }, 122 tooltip: { 123 callbacks: { 124 label(context) { 125 const label = context.dataset.label || ''; 126 const value = context.parsed.y; 127 128 if (label.includes('%')) { 129 return `${label}: ${value}%`; 130 } 131 132 return `${label}: ${value.toLocaleString()}`; 133 }, 134 }, 135 }, 136 }, 137 scales: { 138 x: { 139 grid: { 140 display: false, 141 }, 142 ticks: { 143 maxRotation: 45, 144 minRotation: 0, 145 }, 146 }, 147 y: { 148 type: 'linear', 149 display: true, 150 position: 'left', 151 beginAtZero: true, 152 title: { 153 display: true, 154 text: 'ä»¶æ°', 155 }, 156 grid: { 157 color: 'rgba(0, 0, 0, 0.05)', 158 }, 159 }, 160 y1: { 161 type: 'linear', 162 display: true, 163 position: 'right', 164 beginAtZero: true, 165 max: 100, 166 title: { 167 display: true, 168 text: '転æç (%)', 169 }, 170 grid: { 171 drawOnChartArea: false, 172 }, 173 }, 174 }, 175 }, 176 }); 177 } 178}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.