// 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 styczniami
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 zlLPlugin = {
id: "zlLPlugin",
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ł", 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, chartArea, scales } = chart;
const { left, right, width } = chartArea;
const yAxis = scales.y;
const xPos = left + width * 0.001;
const xPos2 = right - 4;
const yPos = yAxis.getPixelForValue(yAxis.max) - 12;
ctx.save();
ctx.font = "14px Fira Sans";
ctx.fillStyle = "#000";
ctx.textBaseline = "middle";
ctx.textAlign = "left";
ctx.fillText("zł za 1 kg", xPos, yPos);
ctx.textAlign = "right";
ctx.fillText("zł za 1 l", xPos2, yPos);
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.zlLPlugin = zlLPlugin;
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" ||
chartContainer.id === "charts-Mieszkanie3" ||
chartContainer.id === "charts-Podmioty3")
) {
kreska = "";
divAlign = "text-align: center;";
} else {
kreska = kreskaHtml;
divAlign = "text-align: left;";
}
return `