// skrypt_plugins.js (function () { const verticalLinePlugin = { id: 'verticalLinePlugin', afterDraw(chart, args, pluginOptions) { const opts = pluginOptions || chart?.options?.plugins?.verticalLinePlugin || {}; const lineWidth = Number.isFinite(opts.lineWidth) ? opts.lineWidth : 2; const lineColor = opts.lineColor || 'rgba(139, 139, 139, 0.9)'; const xScale = chart.scales?.x; const area = chart.chartArea; if (!xScale || !area) return; let x = null; const active = chart.getActiveElements?.(); if (active && active.length) { x = xScale.getPixelForValue(active[0].index); } else if (Number.isFinite(opts.xIndex)) { x = xScale.getPixelForValue(opts.xIndex); } else if (opts.xValue != null) { x = xScale.getPixelForValue(opts.xValue); } if (x == null) return; const { top, bottom } = area; const { ctx } = chart; ctx.save(); ctx.beginPath(); ctx.lineWidth = lineWidth; ctx.strokeStyle = lineColor; ctx.moveTo(x, top); ctx.lineTo(x, bottom); ctx.stroke(); ctx.restore(); } }; const horizontalLinePlugin = { id: 'horizontalLine', beforeDraw(chart) { const yScale = chart.scales.y; const yValue = 100; if (!yScale) return; const y = yScale.getPixelForValue(yValue); const ctx = chart.ctx; ctx.save(); ctx.beginPath(); ctx.moveTo(chart.chartArea.left, y); ctx.lineTo(chart.chartArea.right, y); ctx.lineWidth = 1; ctx.strokeStyle = 'rgba(173, 173, 173, 1)'; ctx.stroke(); ctx.restore(); } }; const yearLabelPlugin = { id: 'yearLabelPlugin', afterDraw(chart) { const { ctx, chartArea, scales } = chart; const xScale = scales.x; const labels = chart.data.labels; const { yearYmovie = 0, yearYLine = 45 } = chart.options.plugins.yearLabelPlugin ?? {}; ctx.save(); ctx.font = '14px Fira Sans'; ctx.fillStyle = '#000'; ctx.textAlign = 'center'; const styczenIndexes = []; const tickWidth = (chartArea.right - chartArea.left) / labels.length; // === DŁUGIE KRESKI (pomiędzy 12 -> 01) === for (let i = 0; i < labels.length; i++) { const m = labels[i]?.month?.toString().replace(/\s/g, ''); if (m === '01') { styczenIndexes.push(i); // pomijamy pierwszy raz if (styczenIndexes.length === 1) continue; // kreska między grudniem a styczniem const x = xScale.getPixelForValue(i) - tickWidth / 1.5; const y = chartArea.bottom + 1; ctx.beginPath(); ctx.moveTo(x, y + yearYmovie); ctx.lineTo(x, y + yearYLine + 5); ctx.lineWidth = 1; ctx.strokeStyle = 'rgba(0,0,0,0.2)'; ctx.stroke(); } } // === PODPISY LAT === // tu NIE przesuwamy roku o pół ticka, bo mają być idealnie nad swoimi styczeniami for (let i = 0; i < styczenIndexes.length - 1; i++) { const idx1 = styczenIndexes[i]; const idx2 = styczenIndexes[i + 1]; const x1 = xScale.getPixelForValue(idx1); const x2 = xScale.getPixelForValue(idx2); const xMiddle = (x1 + x2) / 2; const y = chartArea.bottom + yearYLine; ctx.fillText(labels[idx1].year, xMiddle, y); } // ostatni rok do prawej krawędzi if (styczenIndexes.length > 0) { const lastIndex = styczenIndexes.at(-1); const x1 = xScale.getPixelForValue(lastIndex); const x2 = chartArea.right; const xMiddle = (x1 + x2) / 2; const y = chartArea.bottom + yearYLine; ctx.fillText(labels[lastIndex].year, xMiddle, y); } ctx.restore(); } }; const yearLabelPlugin2 = { id: 'yearLabelPlugin2', afterDraw(chart) { const { ctx, chartArea, scales } = chart; const xScale = scales.x; const labels = chart.data.labels; const { yearYmovie = 0, yearYLine = 45 } = chart.options.plugins.yearLabelPlugin2 ?? {}; ctx.save(); ctx.font = '14px Fira Sans'; ctx.fillStyle = '#000'; ctx.textAlign = 'center'; const styczenIndexes = []; // Zbieramy wszystkie stycznie (początki lat) for (let i = 0; i < labels.length; i++) { const m = labels[i]?.month?.toString().replace(/\s/g, '').replace('–', '-'); if (m === '01-03') styczenIndexes.push(i); } // Rysujemy kreski tylko pomiędzy rocznikami for (let k = 1; k < styczenIndexes.length; k++) { const i = styczenIndexes[k]; const tickWidth = (chartArea.right - chartArea.left) / labels.length; const x = xScale.getPixelForValue(i) - tickWidth / 2; const y = chartArea.bottom + 1; ctx.beginPath(); ctx.moveTo(x, y + yearYmovie); ctx.lineTo(x, y + yearYLine); ctx.lineWidth = 1; ctx.strokeStyle = 'rgba(0,0,0,0.2)'; ctx.stroke(); } // === RYSOWANIE OPISÓW LAT === const years = []; // Każde 4 miesiące = 1 kwartał → wykrywanie roku for (let i = 0; i < labels.length; i += 4) { const label = labels[i]; if (typeof label === 'object' && label.year) { const x = (xScale.getPixelForValue(i) + xScale.getPixelForValue(Math.min(i + 3, labels.length - 1))) / 2; years.push({ year: label.year, x }); } } // Ostatni rok – dodaj, jeśli nie został wykryty const lastLabel = labels[labels.length - 1]; const lastYear = lastLabel?.year; const lastAlready = years.some(y => y.year === lastYear); if (lastYear && !lastAlready) { const firstIndex = labels.findIndex(l => l?.year === lastYear); const lastIndex = labels.length - 1; const x = (xScale.getPixelForValue(firstIndex) + xScale.getPixelForValue(lastIndex)) / 2; years.push({ year: lastYear, x }); } // Rysowanie podpisów lat years.forEach(({ year, x }) => { const y = chartArea.bottom + 40; ctx.fillText(year, x, y); }); ctx.restore(); } }; const shadingOnHoverPlugin = { id: "shadingOnHover", afterDraw(chart) { const activeElements = chart.getActiveElements?.() || []; if (!activeElements.length) return; const ctx = chart.ctx; const chartArea = chart.chartArea; const { padding = 1, padding2 = 0 } = chart.options.plugins.shadingOnHoverPlugin ?? {}; ctx.save(); ctx.fillStyle = "rgba(218, 216, 216, 0.3)"; for (const active of activeElements) { const el = active.element; if (!el) continue; // pomiń elementy linii (line charts) if (el.height === undefined) continue; // ✔ słupek — rysujemy cień const barHeight = el.height; const centerY = el.y; const topY = centerY - barHeight / 2 - padding + padding2; const height = barHeight + padding * 2; ctx.fillRect( chartArea.left, topY, chartArea.right - chartArea.left, height ); } ctx.restore(); } }; const shadingOnHoverPlugin2 = { id: 'shadingOnHover2', afterDraw(chart) { const ctx = chart.ctx; const chartArea = chart.chartArea; const activeElements = chart.getActiveElements(); if (!activeElements.length) return; const { padding = 0 } = chart.options.plugins.shadingOnHoverPlugin2 ?? {}; const xPositions = activeElements.map(item => { const element = item.element; return { x: element.x, width: element.width ?? element.options.radius * 2 ?? 10 }; }); const uniqueXPositions = Array.from( new Map(xPositions.map(p => [p.x, p])).values() ); ctx.save(); ctx.fillStyle = 'rgba(218, 216, 216, 0.45)'; uniqueXPositions.forEach(pos => { ctx.fillRect( pos.x - pos.width / 2 - padding, chartArea.top, pos.width + padding * 2, chartArea.bottom - chartArea.top ); }); ctx.restore(); } }; const valueLabelPlugin = { id: 'valueLabel', afterDatasetsDraw(chart) { const { ctx } = chart; chart.data.datasets.forEach((dataset, i) => { const meta = chart.getDatasetMeta(i); meta.data.forEach((bar, index) => { const value = dataset.data[index]; const formattedValue = value?.toLocaleString ? value.toLocaleString('pl-PL', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) : String(value); ctx.save(); ctx.fillStyle = '#000'; ctx.font = '14px Fira Sans'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(`${formattedValue}%`, bar.x + 15, bar.y); ctx.restore(); }); }); } }; const percentLabelPlugin = { id: 'percentLabelPlugin', afterDraw(chart) { const { ctx, scales } = chart; const xAxis = scales.x; const yAxis = scales.y; const xPos = xAxis.right; const yZero = yAxis.bottom + 30 ctx.save(); ctx.font = '14px Fira Sans'; ctx.fillStyle = '#000'; ctx.textAlign = 'right'; ctx.textBaseline = 'middle'; ctx.fillText('%', xPos + 6, yZero); ctx.restore(); } }; const percent2LabelPlugin = { id: 'percent2LabelPlugin', afterDraw(chart) { const { ctx, chartArea, scales } = chart; const { left, width } = chartArea; const yAxis = scales.y; const xPos = left + width * 0.001; const yPos = yAxis.getPixelForValue(yAxis.max) - 11; ctx.save(); ctx.font = '14px Fira Sans'; ctx.fillStyle = '#000'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText('%', xPos, yPos); ctx.restore(); } }; const zlLabelPlugin = { id: 'zlLabelPlugin', afterDraw(chart) { const { ctx, chartArea, scales } = chart; const { left, right, width } = chartArea; const yAxis = scales.y; const xPos = left + width * 0.001; const yPos = yAxis.getPixelForValue(yAxis.max) - 12; ctx.save(); ctx.font = '14px Fira Sans'; ctx.fillStyle = '#000'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText('zł za 1 dt', xPos, yPos); ctx.restore(); } }; const kgLLabelPlugin = { id: 'kgLLabelPlugin', afterDraw(chart) { const { ctx, scales } = chart; const xAxis = scales.x; const yAxis = scales.y; const xPos = xAxis.right; const yMax = yAxis.getPixelForValue(yAxis.max); ctx.save(); ctx.font = '14px Fira Sans'; ctx.fillStyle = '#000'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText('zł za 1 kg', xPos - 770, yMax - 12); ctx.fillText('zł za 1 l', xPos - 45, yMax - 12); ctx.restore(); } }; const removeZeroLinePlugin = { id: 'removeZeroLine', beforeDraw(chart) { const ctx = chart.ctx; const chartArea = chart.chartArea; const xScale = chart.scales.x; if (!xScale) return; const xZero = xScale.getPixelForValue(0); ctx.save(); ctx.clearRect(xZero - 1, chartArea.top, 2, chartArea.bottom - chartArea.top); ctx.restore(); } }; window.verticalLinePlugin = verticalLinePlugin; window.horizontalLinePlugin = horizontalLinePlugin; window.yearLabelPlugin = yearLabelPlugin; window.yearLabelPlugin2 = yearLabelPlugin2; window.shadingOnHoverPlugin = shadingOnHoverPlugin; window.shadingOnHoverPlugin2 = shadingOnHoverPlugin2; window.valueLabelPlugin = valueLabelPlugin; window.percentLabelPlugin = percentLabelPlugin; window.percent2LabelPlugin = percent2LabelPlugin; window.zlLabelPlugin = zlLabelPlugin; window.kgLLabelPlugin = kgLLabelPlugin; window.removeZeroLinePlugin = removeZeroLinePlugin; // --- custom-tooltip (przerobione) --- const font_size = "18px"; function customTooltip(context, fractionDigits) { let tooltipEl = document.getElementById('chartjs-tooltip'); if (!tooltipEl) { tooltipEl = document.createElement('div'); tooltipEl.id = 'chartjs-tooltip'; tooltipEl.style.position = 'absolute'; tooltipEl.style.pointerEvents = 'none'; tooltipEl.style.zIndex = '1000'; tooltipEl.style.transition = 'all .1s ease'; document.body.appendChild(tooltipEl); } const tooltipModel = context.tooltip; if (tooltipModel.opacity === 0) { tooltipEl.style.opacity = 0; tooltipEl.style.display = 'none'; return; } tooltipEl.style.display = 'block'; const chartContainer = context.chart.canvas.closest('div'); const canvasId = context.chart.canvas.id; function formatTooltipTitle(name) { if (!name) return ''; name = String(name).trim(); if (/^Powiat\b/u.test(name)) { return name.replace(/^Powiat\b/u, 'powiat
'); } if (/^[Mm]\.\s*/u.test(name)) { const rest = name.replace(/^[Mm]\.\s*/u, 'M. '); return 'powiat
' + rest; } const parts = name.split(' '); if (parts.length > 0 && parts[0].length > 0) { parts[0] = parts[0].charAt(0).toLowerCase() + parts[0].slice(1); return parts.join(' '); } return name; } const bodyLines = tooltipModel.dataPoints.map((dp, i) => { if (dp.raw == null) return null; const value = dp.raw.toLocaleString('pl-PL', { useGrouping: false, minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits }); const dataset = context.chart.data.datasets[dp.datasetIndex] || {}; const labelColors = tooltipModel.labelColors || []; const pointStyle = dataset.pointStyle ?? null; const isLineStyle = (pointStyle === 'line'); const hideKreska = ((chartContainer && chartContainer.id === 'charts-Wynagrodzenie2')); let kreskaHtml = ''; if (!hideKreska) { const meta = context.chart.getDatasetMeta(dp.datasetIndex); const style = meta?.dataset?.options || {}; const lc = labelColors[i] || {}; const kolor = lc.borderColor || lc.backgroundColor || dataset.borderColor || dataset.backgroundColor || '#000'; const borderW = Array.isArray(style.borderWidth) ? style.borderWidth[0] : style.borderWidth || 11; const kreskaGrubosc = Math.max(3, Math.round(borderW)); const kreskaDlugosc = 18; kreskaHtml = ` `; } let kreska = ''; let divAlign = 'text-align: center;'; if (chartContainer && (chartContainer.id === 'charts-Rynek_Pracy3' || chartContainer.id === 'charts-Ludnosc2')) { kreska = ''; divAlign = 'text-align: center;'; } else { kreska = kreskaHtml; divAlign = 'text-align: left;'; } return `
${kreska} ${value}
`; }).filter(Boolean).join(''); const tooltipItem = tooltipModel.dataPoints?.[0]; const index = tooltipItem?.dataIndex; const chart = tooltipItem?.chart; let label = chart?.data?.labels?.[index] ?? ''; let title = ''; if (canvasId === 'charts-Trzoda' || canvasId === 'charts-Bydla') { const years = [2021, 2022, 2023, 2024, 2025]; const yearIndex = Math.floor(index / 2); const year = years[yearIndex] ?? ''; title = `${label} ${year}`.trim(); } else if (typeof label === 'object') { const year = label.year ?? ''; const month = label.month ?? ''; title = `${month} ${year}`.trim(); } else { title = String(label); } title = formatTooltipTitle(title); let title_on = `
${title}
`; if ( (chartContainer && chartContainer.id === 'charts-Wynagrodzenie2') || (canvasId === 'chartLeft' || canvasId === 'chartRight') ) { title_on = ''; } tooltipEl.innerHTML = `
${title_on} ${bodyLines}
`; const position = context.chart.canvas.getBoundingClientRect(); tooltipEl.style.opacity = 1; tooltipEl.style.left = position.left + window.pageXOffset + tooltipModel.caretX + 15 + 'px'; tooltipEl.style.top = position.top + window.pageYOffset + tooltipModel.caretY - 5 + 'px'; } window.customTooltip = customTooltip; function announce(message) { const el = document.getElementById("sr-alert"); el.textContent = message; setTimeout(() => (el.textContent = ""), 100); } window.addEventListener("load", () => { announce("Strona zawiera wykresy oraz interaktywną mapę z legendą."); }); })();