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();