const TopluyoCHART = function(options) { let that = {} that.container = options.container; that.options = options that.options.padding = options.padding==undefined ? 10 : options.padding let canvas = document.createElement("canvas"); that.canvas = canvas that.canvas.style.width = "100%"; that.canvas.style.height = "100%"; that.canvas.style.display = "block"; that.canvas.style.zIndex = "1" that.container.appendChild(that.canvas); let ctx = canvas.getContext("2d"); that.ctx = ctx let hintCanvas = document.createElement("canvas"); that.hintCanvas = hintCanvas that.hintCanvas.style.width = "100%"; that.hintCanvas.style.height = "100%"; that.hintCanvas.style.display = "block"; that.hintCanvas.style.zIndex = "2" that.hintCanvas.style.position = "absolute"; that.hintCanvas.style.left = "0" that.hintCanvas.style.top = "0" that.hintCanvas.style.cursor = "none" that.container.appendChild(that.hintCanvas); let hintCtx = hintCanvas.getContext("2d"); that.hintCtx = hintCtx that.hintCanvas.onpointermove = function(event){ that.legend( event.pageX,event.pageY ) } that.hintCanvas.onpointerleave = function(){ that.hintCanvas.style.opacity="0" } that.hintCanvas.onpointerenter = function(){ that.hintCanvas.style.opacity="1" } that.hintCanvas.oncontextmenu = function(e){ e.preventDefault(); return false; } that.sort = function(){ let key = that.options.graf[0].property //that.options.data = that.options.data.sort((a,b)=>a[key]>b[key]?1:-1) that.options.min = {} that.options.max = {} that.options.graf.forEach((e,k)=>{ that.options.max[k] = -Infinity that.options.min[k] = +Infinity e.property.split(",").forEach(i=>{ let min = that.options.data.map(a=>a[i]).reduce((a, b) => Math.min(a, b)); let max = that.options.data.map(a=>a[i]).reduce((a, b) => Math.max(a, b)); that.options.min[k] = Math.min(min,that.options.min[k]) that.options.max[k] = Math.max(max,that.options.max[k]) }) if(e.min != undefined) that.options.min[k] = e.min if(e.max != undefined) that.options.max[k] = e.max }) } that.legend = function(x,y){ hintCtx.clearRect(0,0,that.width,that.height) if(x==undefined) return hintCtx.strokeStyle = "#8888"; hintCtx.beginPath(); hintCtx.setLineDash([10, 8]); hintCtx.lineDashOffset = -x % 18 - 4; hintCtx.moveTo(0, y); hintCtx.lineTo(that.width, y); hintCtx.stroke(); hintCtx.closePath(); hintCtx.beginPath(); hintCtx.setLineDash([10, 8]); hintCtx.lineDashOffset = -y % 18 - 4; hintCtx.moveTo(x, 0); hintCtx.lineTo(x, that.height); /* hintCtx.moveTo(x, y); if(y>that.height/2){ hintCtx.lineTo(x, that.height); }else{ hintCtx.lineTo(x, 0); } */ hintCtx.stroke(); hintCtx.closePath(); hintCtx.beginPath(); let positions = 0; let oldWidth = 0 let i = 0; let sx = that.indexFromX(x) for(var part=1; part < that.options.graf.length; part++){ let colors = that.options.graf[part].colors.split(",") for(let key=0; key < that.options.graf[part].property.split(',').length; key++){ let keyName = that.options.graf[part].property.split(',')[key] let val = that.options.data[sx][keyName] if(val>1000_000){ val = parseInt(val/1000_000)+"M" }else if(val>1000){ val = parseInt(val/1000)+"K" } if(val==0){ //continue } let metrics = hintCtx.measureText(val.toString().padStart(4)); let height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; let width = metrics.width i = i + height + 12 //badge(hintCtx,x,y+(y>that.height/2 ? -i: i),val,colors[key] ) let name = that.options.graf[part].name.split('/')[key] val = name + ":" + val.toString().padStart(4) metrics = hintCtx.measureText(val); height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; width = metrics.width positions = positions + width/2 + 14 + oldWidth/2 badge(hintCtx,that.width/2>x? -positions:positions,that.height/2>y?-20:20,val,colors[key] ) oldWidth = width } let vali = ilerp(y,that.height-that.options.padding,that.options.padding) let valy = parseInt(lerp(vali,that.options.min[part],that.options.max[part])) let metrics = hintCtx.measureText(val.toString().padStart(4)); let height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; let width = metrics.width if(valy>1000_000){ valy = parseInt(valy/1000_000)+"M" }else if(valy>1000){ valy = parseInt(valy/1000)+"K" } //on near badge(hintCtx,x+(x>that.width/2?-1:1)*45*(part-.2),y,valy,colors[colors.length-1],30) } let time = that.options.data[sx]["time"] let date = new Date(time*1000) time = date.getUTCFullYear()+"-"+date.getUTCMonth().toString().padStart(2,"0")+"-"+date.getUTCDate().toString().padStart(2,"0")+" "+date.getUTCHours().toString().padStart(2,"0")+":"+date.getUTCMinutes().toString().padStart(2,"0") time = date.getFullYear()+"-"+date.getMonth().toString().padStart(2,"0")+"-"+date.getDate().toString().padStart(2,"0")+" "+date.getHours().toString().padStart(2,"0")+":"+date.getMinutes().toString().padStart(2,"0") metrics = hintCtx.measureText(time); height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; width = metrics.width positions = positions + width/2 + 14 + oldWidth/2 badge(hintCtx,that.width/2>x ? -positions : positions,that.height/2>y?-20:20,time,"#888") // On Near //badge(hintCtx,x,y+(ythat.width/2?-1:1)*45*(part+.8),y,time,"#888") hintCtx.closePath(); } function lerp(val, start, end) { return start + (end - start) * val; } function ilerp(value, start, end) { return (value - start) / (end - start); } function badge(ctx,x,y,text,color,_width=null){ if(x<0){ x = that.width + x } if(y<0){ y = that.height + y } ctx.font = "bold 12px monospace"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; let metrics = ctx.measureText(text); let height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; let width = metrics.width if(_width!=null){ width = _width } ctx.fillStyle=color roundRect(ctx, x - width / 2 - 5,y - height / 2 - 5, width+10, height+10,10) ctx.fill(); ctx.fillStyle="#FFF" ctx.fillText(text,x , y) } that.indexFromX = function(x) { let s = (that.width - that.options.padding * 2) / (that.options.data.length ); let index = parseInt((x - that.options.padding) / s); index = Math.max(0, Math.min(index, that.options.data.length - 1)); return index; }; function roundRect(ctx, x, y, width, height, radius) { radius = Math.min(radius, width / 2, height / 2); ctx.beginPath(); ctx.moveTo(x, y); ctx.arcTo(x + width, y, x + width, y + height, radius); ctx.arcTo(x + width, y + height, x, y + height, radius); ctx.arcTo(x, y + height, x, y, radius); ctx.arcTo(x, y, x + width, y, radius); ctx.closePath(); } that.x = 0; //@ Drawing that.draw = function (part=2,index=0,color="#0008",type="line") { let key = that.options.graf[part].property.split(",")[index]; let h = that.options.max[part] - that.options.min[part]; let s = (that.width - that.options.padding*2) / that.options.data.length ctx.beginPath(); ctx.strokeStyle = color; // line color ctx.fillStyle = color + "2", ctx.lineWidth = 3; // line width in pixels ctx.lineCap = "round"; // "butt", "round", "square" ctx.lineJoin = "round"; // "miter", "round", "bevel" ctx.miterLimit = 5; if(type=="line"){ let s = (that.width - that.options.padding*2) / (that.options.data.length-1) for (let i = 0; i < that.options.data.length; i++) { let y = that.height - that.options.padding - (that.height - that.options.padding*2 ) * (that.options.data[i][key] - that.options.min[part]) / h; if(i==0){ ctx.moveTo(that.options.padding+s*i, y); }else{ ctx.lineTo(that.options.padding+s*i, y); } } ctx.stroke(); } if (type == "area") { ctx.beginPath(); let s = (that.width - that.options.padding*2) / (that.options.data.length-1) const points = that.options.data.map((item, i) => ({ x: that.options.padding + s * i, y: that.height - that.options.padding - (that.height - that.options.padding*2) * (item[key] - that.options.min[part]) / h })); ctx.moveTo(points[0].x, points[0].y); for (let i = 0; i < points.length - 1; i++) { const xc = (points[i].x + points[i + 1].x) / 2; const yc = (points[i].y + points[i + 1].y) / 2; ctx.quadraticCurveTo(points[i].x,points[i].y,xc,yc); } ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y); // Alanı kapat ctx.lineTo(points[points.length - 1].x, that.height - that.options.padding); ctx.lineTo(points[0].x, that.height - that.options.padding); ctx.closePath(); const gradient = ctx.createLinearGradient(0, 0, 0, that.height); gradient.addColorStop(0, color+"2"); gradient.addColorStop(.5, color+"2"); gradient.addColorStop(1, "#0000"); ctx.fillStyle = gradient; ctx.fill(); // Çizgiyi tekrar çiz ctx.beginPath(); ctx.moveTo(points[0].x, points[0].y); for (let i = 0; i < points.length - 1; i++) { const xc = (points[i].x + points[i + 1].x) / 2; const yc = (points[i].y + points[i + 1].y) / 2; ctx.quadraticCurveTo(points[i].x,points[i].y,xc,yc); } ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y); ctx.stroke(); } if (type == "bar") { const barWidth = (that.width - that.options.padding*2) / that.options.data.length; const radius = barWidth * 0.2; ctx.fillStyle = color for (let i = 0; i < that.options.data.length; i++) { if (that.options.data[i][key] > that.options.min[part]) { const x = that.options.padding + s * i; const y = that.height - that.options.padding - (that.height - that.options.padding*2) * (that.options.data[i][key] - that.options.min[part]) / h; const height = (that.height - that.options.padding) - y; roundRect(ctx, x, y, barWidth, height, radius); ctx.fill(); } } } }; that.render = function(){ that.sort() ctx.clearRect(0,0,that.width,that.height) for(let part=1; part < that.options.graf.length; part++){ let colors = that.options.graf[part].colors.split(",") for(let key=0; key < that.options.graf[part].property.split(',').length; key++){ that.draw(part,key,colors[key],that.options.graf[part].type) } } that.legend() } that.resize = () => { const rect = that.container.getBoundingClientRect(); const dpr = window.devicePixelRatio || 1; // Gerçek pixel çözünürlüğü that.hintCanvas.width = that.canvas.width = rect.width * dpr; that.hintCanvas.height = that.canvas.height = rect.height * dpr; // Görsel boyut that.hintCanvas.style.width = that.canvas.style.width = rect.width + "px"; that.hintCanvas.style.height = that.canvas.style.height = rect.height + "px"; // Koordinat sistemini CSS px'e göre ayarla that.ctx.setTransform(dpr, 0, 0, dpr, 0, 0); that.hintCtx.setTransform(dpr, 0, 0, dpr, 0, 0); that.width = rect.width; that.height = rect.height; that.render() }; that.observer = new ResizeObserver(() => { that.resize(); }); that.observer.observe(that.container); that.sort(); that.resize(); that.render(); return that; }; TopluyoCHART.AVERAGE = "AVERAGE"; TopluyoCHART.TOTAL = "TOTAL"; TopluyoCHART.MIN = "MIN"; TopluyoCHART.MAX = "MAX"; TopluyoCHART.FIRST = "FIRST"; TopluyoCHART.LAST = "LAST"; TopluyoCHART.Sample = function ( data, options = { time: "TIME", value: TopluyoCHART.AVERAGE, request: TopluyoCHART.TOTAL, rated: TopluyoCHART.TOTAL, socket: TopluyoCHART.AVERAGE, blocked: TopluyoCHART.TOTAL }, unit = 10 ) { if (!data.length) return []; unit = Math.max(1, unit); const result = []; for (let i = 0; i < data.length; i += unit) { const chunk = data.slice(i, i + unit); const group = { time: chunk[0].time, __count: chunk.length }; for (const row of chunk) { for (const field in options) { if (field === "time") continue; const mode = options[field]; const value = row[field] ?? 0; switch (mode) { case TopluyoCHART.TOTAL: group[field] = (group[field] || 0) + value; break; case TopluyoCHART.AVERAGE: group[field] = (group[field] || 0) + value; break; case TopluyoCHART.MIN: group[field] = group[field] === undefined ? value : Math.min(group[field], value); break; case TopluyoCHART.MAX: group[field] = group[field] === undefined ? value : Math.max(group[field], value); break; case TopluyoCHART.FIRST: if (group[field] === undefined) { group[field] = value; } break; case TopluyoCHART.LAST: group[field] = value; break; } } } for (const field in options) { if (field === "time") continue; if (options[field] === TopluyoCHART.AVERAGE) { group[field] /= group.__count; } } delete group.__count; result.push(group); } return result; }; TopluyoCHART.FromMinute10 = function(data) { if (!data?.length) return []; let startIndex = 0; let minTime = Infinity; for (let i=0; i