1// Scatter chart for resolved % vs model release date 2function renderResolvedVsReleaseDateChart(ctx, selected, colors, backgroundPlugin) { 3 // Filter models with release dates 4 const modelsWithDates = selected.filter(s => { 5 const releaseDate = getModelReleaseDate(s.tags); 6 return releaseDate !== null; 7 }); 8 9 if (modelsWithDates.length === 0) { 10 return null; 11 } 12 13 // Plugin to draw labels on scatter plot 14 const labelPlugin = { 15 id: 'releaseDateLabels', 16 afterDatasetsDraw: (chart, args, options) => { 17 if (chart.config.type !== 'scatter') return; 18 const {ctx, chartArea} = chart; 19 if (!chartArea) return; 20 21 ctx.save(); 22 ctx.font = '11px sans-serif'; 23 ctx.fillStyle = colors.textColor; 24 ctx.textAlign = 'left'; 25 ctx.textBaseline = 'middle'; 26 27 chart.data.datasets[0].data.forEach((point, index) => { 28 const meta = chart.getDatasetMeta(0); 29 const element = meta.data[index]; 30 if (!element) return; 31 32 const x = element.x; 33 const y = element.y; 34 const label = modelsWithDates[index].name; 35 36 // Draw label with slight offset 37 ctx.fillText(label, x + 8, y); 38 }); 39 40 ctx.restore(); 41 } 42 }; 43 44 // Prepare scatter data 45 const scatterData = modelsWithDates.map(s => { 46 const releaseDateInt = getModelReleaseDate(s.tags); 47 const releaseDate = parseReleaseDateInt(releaseDateInt); 48 return { 49 x: releaseDate.getTime(), 50 y: s.resolved 51 }; 52 }); 53 54 // Calculate y-axis range with nice round numbers 55 const resolvedValues = modelsWithDates.map(s => s.resolved); 56 const minResolved = Math.min(...resolvedValues); 57 const maxResolved = Math.max(...resolvedValues); 58 59 // Round to nice values (multiples of 5) 60 const yMinRaw = Math.max(0, minResolved * 0.9); 61 const yMaxRaw = Math.min(100, maxResolved * 1.05); 62 const yMin = Math.floor(yMinRaw / 5) * 5; 63 const yMax = Math.ceil(yMaxRaw / 5) * 5; 64 65 // Calculate x-axis (date) range with padding 66 const dateValues = scatterData.map(d => d.x); 67 const minDate = Math.min(...dateValues); 68 const maxDate = Math.max(...dateValues); 69 const dateRange = maxDate - minDate; 70 const xMin = minDate - dateRange * 0.05; 71 const xMax = maxDate + dateRange * 0.20; // More padding on right for labels 72 73 return new Chart(ctx, { 74 type: 'scatter', 75 data: { 76 datasets: [{ 77 label: 'Models', 78 data: scatterData, 79 backgroundColor: colors.barBackground, 80 borderColor: colors.barBorder, 81 borderWidth: 2, 82 pointRadius: 6, 83 pointHoverRadius: 8 84 }] 85 }, 86 options: { 87 responsive: true, 88 maintainAspectRatio: false, 89 animation: false, 90 scales: { 91 y: { 92 min: yMin, 93 max: yMax, 94 title: { 95 display: true, 96 text: '% Resolved', 97 color: colors.textColor, 98 font: { size: 14 } 99 }, 100 ticks: { 101 stepSize: 5, 102 callback: (v) => v + '%', 103 color: colors.textColor, 104 font: { size: 12 } 105 }, 106 grid: { 107 color: colors.gridColor 108 } 109 }, 110 x: { 111 type: 'time', 112 min: xMin, 113 max: xMax, 114 time: { 115 unit: 'month', 116 displayFormats: { 117 month: 'MMM yyyy' 118 } 119 }, 120 title: { 121 display: true, 122 text: 'Model Release Date', 123 color: colors.textColor, 124 font: { size: 14 } 125 }, 126 ticks: { 127 color: colors.textColor, 128 font: { size: 12 }, 129 maxRotation: 45 130 }, 131 grid: { 132 color: colors.gridColor 133 } 134 } 135 }, 136 plugins: { 137 legend: { display: false }, 138 tooltip: { 139 callbacks: { 140 label: (ctx) => { 141 const model = modelsWithDates[ctx.dataIndex]; 142 const releaseDate = parseReleaseDateInt(getModelReleaseDate(model.tags)); 143 const formattedDate = formatReleaseDate(releaseDate); 144 return `${model.name}: ${formattedDate}, ${ctx.parsed.y.toFixed(2)}%`; 145 } 146 } 147 } 148 } 149 }, 150 plugins: [backgroundPlugin, labelPlugin] 151 }); 152}
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.