// 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 `