Title: Buttons Do Not Resize Proportionally in Custom Panel
Hi everyone,
I’ve created a custom panel that displays services as clickable boxes (with buttons and animations) using ECharts in Grafana. However, when I resize the panel (make it smaller or larger), the buttons do not scale proportionally with the panel. Their size stays fixed, which breaks the layout.
I’m looking for a way to make the buttons and text responsive to the panel size (or any workaround).
Can someone guide me or point me in the right direction?
ECharts Code (Volkovlabs Plugin)
function wrapText(text, maxLen = 16) {
const cleanedText = text?.toString().replace(/Servisi/gi, '').trim() || '';
const words = cleanedText.split(' ');
let lines = [], current = '';
for (const word of words) {
if ((current + word).length <= maxLen) {
current += word + ' ';
} else {
lines.push(current.trim());
current = word + ' ';
}
}
if (current) lines.push(current.trim());
return lines.join('\n');
}
function getOption() {
const data = context.panel.data.series?.[0];
if (!data) {
return { title: { text: '❌ Veri yok', left: 'center', top: 'middle' } };
}
const serviceField = data.fields.find(f => f.name.toLowerCase().includes('servis'));
const severityField = data.fields.find(f => f.name.toLowerCase().includes('severity') || f.name.toLowerCase().includes('value'));
const tooltipField = data.fields.find(f => f.name.toLowerCase().includes('tooltip'));
if (!serviceField || !severityField || !tooltipField) {
return { title: { text: '❌ Alanlar eksik', left: 'center', top: 'middle' } };
}
const services = serviceField.values.toArray();
const severities = severityField.values.toArray();
const tooltips = tooltipField.values.toArray();
const boxWidth = 140;
const boxHeight = 70;
const boxSpacing = 3;
const itemsPerRow = 13;
const minFontSize = 10;
const maxFontSize = 14;
const servisLinkleri = {
"Ankes": "https://kokpitizl.vakifbank.intra/d/ee6h5un4vi9kwd/ankes-servisi?orgId=1",
"Banka Kartları Yönetimi": "https://kokpitizl.vakifbank.intra/d/ce6iams4c2ayod/banka-kartlari-yonetimi-servisi?orgId=1",
"Banka Sigortacılığı": "https://kokpitizl.vakifbank.intra/d/ce6kzvbpcnvnke/banka-sigortaciligi-servisi?orgId=1",
"Bireysel Krediler": "https://kokpitizl.vakifbank.intra/d/ce6l7r2py81kwe/breysel-krediler-servisi?orgId=1",
"Çek-Senet": "https://kokpitizl.vakifbank.intra/d/fe6lg38zr1w5ca/cek-senet-servisi?orgId=1",
"Dış İşlemler": "https://kokpitizl.vakifbank.intra/d/ee6lk9oqm9hq8c/dis-islemler-servisi?orgId=1",
"Fiyatlama Yönetimi": "https://kokpitizl.vakifbank.intra/d/ce6lnwxh9of7ka/fiyatlama-yonetimi-servisi?orgId=1",
"Hazine İşlemleri": "https://kokpitizl.vakifbank.intra/d/ae6lr66brz94wb/hazine-islemleri-servisi?orgId=1",
"Kredi Kartları Yönetimi": "https://kokpitizl.vakifbank.intra/d/ee6mfr3j1bls0f/kredi-kartlari-yonetimi-servisi?orgId=1",
"Kredili Mevduat": "https://kokpitizl.vakifbank.intra/d/fe6mi7w5pfwn4f/kredili-mevduat-servisi?orgId=1",
"Kur ve Döviz Yönetimi": "https://kokpitizl.vakifbank.intra/d/fe6mk2elgh0cgb/kur-ve-doviz-yonetimi-servisi?orgId=1",
"Kurum Ödemeleri": "https://kokpitizl.vakifbank.intra/d/ce6mnb5a6uk8we/kurum-odemeleri-servisi?orgId=1",
"Kurum Tahsilatları": "https://kokpitizl.vakifbank.intra/d/fe6mp83hwxfcwe/kurum-tahsilatlari-servisi?orgId=1",
"Mevduat": "https://kokpitizl.vakifbank.intra/d/fe6mp83hwxfcwe/kurum-tahsilatlari-servisi?orgId=1",
"Para Transferleri": "https://kokpitizl.vakifbank.intra/d/fe6mp83hwxfcwe/kurum-tahsilatlari-servisi?orgId=1",
"Ticari Krediler": "https://kokpitizl.vakifbank.intra/d/fe6nzym5dm48wd/ticari-krediler-servisi?orgId=1",
"Yatırım İşlemleri": "https://kokpitizl.vakifbank.intra/d/fe6o8v6n9zshse/yatirim-islemleri-servisi?orgId=1"
};
const graphic = [];
services.forEach((name, idx) => {
const severity = parseInt(severities[idx] ?? 0);
const tooltipText = tooltips[idx] || name;
let color = '#D9D9D9';
let needsAnimation = false;
if (severity === 1) {
color = '#ffc000';
needsAnimation = true;
} else if (severity === 2) {
color = '#ff0000';
needsAnimation = true;
}
const cleanedName = name.replace(/Servisi/gi, '').trim();
const row = Math.floor(idx / itemsPerRow);
const col = idx % itemsPerRow;
const x = col * (boxWidth + boxSpacing);
const y = row * (boxHeight + boxSpacing);
const wrapped = wrapText(name);
const lines = wrapped.split('\n');
const calculatedFontSize = Math.min(maxFontSize, Math.max(minFontSize, Math.floor(boxWidth / 8)));
const lineHeight = calculatedFontSize + 2;
const totalTextHeight = lines.length * lineHeight;
const textY = y + (boxHeight - totalTextHeight) / 2 + lineHeight / 2;
const rectObj = {
type: 'rect',
name: cleanedName,
shape: { x, y, width: boxWidth, height: boxHeight },
style: {
fill: color,
stroke: '#000',
lineWidth: 1,
cursor: servisLinkleri[cleanedName] ? 'pointer' : 'default',
shadowBlur: 8,
shadowColor: color
},
tooltip: {
show: true,
formatter: () => {
if (severity === 1) {
return `<div style="font-size:15px; font-weight:bold; color:#ff9900;">⚠️ ${tooltipText}</div>`;
} else if (severity === 2) {
return `<div style="font-size:16px; font-weight:bold; color:#ff3333; text-shadow: 1px 1px #000;">🔥 ${tooltipText}</div>`;
} else {
return tooltipText;
}
}
},
onclick: servisLinkleri[cleanedName] ? () => window.open(servisLinkleri[cleanedName], '_blank') : undefined
};
if (needsAnimation) {
rectObj.keyframeAnimation = {
duration: 1000,
loop: true,
keyframes: [
{ percent: 0, style: { opacity: 1 } },
{ percent: 0.5, style: { opacity: 0.2 } },
{ percent: 1, style: { opacity: 1 } }
]
};
}
graphic.push(rectObj);
lines.forEach((line, i) => {
graphic.push({
type: 'text',
style: {
text: line,
x: x + boxWidth / 2,
y: textY + i * lineHeight,
textAlign: 'center',
textVerticalAlign: 'middle',
fill: '#000',
font: `bold ${calculatedFontSize}px Segoe UI, sans-serif`,
lineHeight: lineHeight
}
});
});
});
return {
backgroundColor: '#111',
tooltip: {
show: true,
confine: true,
backgroundColor: '#000',
borderColor: '#666',
borderWidth: 1,
padding: 10,
textStyle: {
fontFamily: 'Segoe UI',
fontSize: 13,
color: '#00ffcc'
}
},
graphic
};
}
return getOption();