图表控件Flot改造
不得不说,flot算是一个精巧的js绘制图表的插件。原型类似上图。下面列出一些API。 网上有很多介绍其API的文章,有兴趣可以去cnblogs.com上去搜搜。目前,这个只是实现了,点线,区域图形,以及饼图,柱状图的绘制。一般情况下做一点简单的应用时可以的。但是,现在需要将其改造成另一个样子。 背景区域绘制 可以描点,并且可以根据不同点的数据,将点绘制为不同的颜色。 根据数据序列的不同,以及数据序列的个数,绘制多张不同的控制图。传入几个数据序列绘制几张图。 数据序列只是一个数组,横坐标为时间,纵坐标为值。纵坐标只显示UCL,CL,lCL三个数值 多张控制图时,只有最后一张控制图显示时间横轴。 只有第一张图需要根据数据点值的不同,设置点的颜色。 ToolTips需要用数组传入,不使用其拼字符串的形式。 小批换批时,根据传入的换批时间绘制换批提示线。 改造后效果为:
绘制背景区域。 首先,影藏其网格线。需要将其源代码中drawGrid()方法的,绘制网格线部分注释掉。第1680到1764行。 它原有的绘制背景的方法,我们不适用,并且重写其方法。增加方法function drawBackground(options){}参数是innit时传入的options。 方法代码: var top=ticks[2]; var center=ticks[1]; var bottom=ticks[0]; var percent=plotHeight/(max-min);
//第一个空白区域高度为00到max-top var area1=(max-top)*percent; ctx.fillStyle = "#F8F8FF"; ctx.fillRect(0, 0, plotWidth, area1);
ctx.strokeStyle = "#696969"; ctx.lineWidth = 0.5;//0, area3, plotWidth, smalltoparea ctx.moveTo(0, area1); ctx.lineTo(plotWidth, area1); //第二个区域为:area1到area2加上,(top-center)/3 var smalltoparea=((top-center)/3)*percent; ctx.fillStyle = "rgb(250, 188, 186)"; ctx.fillRect(0, area1, plotWidth, smalltoparea); //第三个区域 var area2=2*smalltoparea; ctx.fillStyle = "rgb(251, 254, 188)"; ctx.fillRect(0, area2, plotWidth, smalltoparea); // 第四个区域 var area3=3*smalltoparea; ctx.fillStyle = "rgb(209, 254, 214)"; ctx.fillRect(0, area3, plotWidth, smalltoparea); //CL下面每一个小区域高度 var smallbottomarea=((center-bottom)/3)*percent; var area4=4*smalltoparea;
ctx.strokeStyle = "#696969"; ctx.lineWidth = 0.5;//0, area3, plotWidth, smalltoparea ctx.moveTo(0, area4); ctx.lineTo(plotWidth, area4);
ctx.fillStyle = "rgb(209, 254, 214)"; ctx.fillRect(0, area4, plotWidth, smallbottomarea);
var area5=area4+smallbottomarea; ctx.fillStyle = "rgb(251, 254, 188)"; ctx.fillRect(0, area5, plotWidth, smallbottomarea);
var area6=area5+smallbottomarea; ctx.fillStyle = "rgb(250, 188, 186)"; ctx.fillRect(0, area6, plotWidth, smallbottomarea);
var area7=area6+smallbottomarea; ctx.fillStyle = "#F8F8FF"; ctx.fillRect(0, area7, plotWidth, smallbottomarea); ctx.strokeStyle = "#696969"; ctx.lineWidth = 0.5;//0, area3, plotWidth, smalltoparea ctx.moveTo(0, area7); ctx.lineTo(plotWidth, area7); ctx.stroke(); ctx.restore();
}
填充区域时,其小坐标原点在绘图区域左上角。需要根据比例算出需要在小坐标中绘制背景的高度。背景分为八个区,从坐标(0,0)点高度依次叠加。在draw()方法中调用drawbackground(options)方法。// if (!options.grid.backgroundImage) ctx.clearRect(0, 0, canvasWidth, canvasHeight); // ctx.clearRect(0, 0, canvasWidth, canvasHeight);
var grid = options.grid;
// draw background, if any //if (grid.show && grid.backgroundColor) drawBackground(options); 绘制表格,绘制背景,绘制数据序列都在draw()方法中调用。 二、描点,根据不同的数据将点绘制为不同的颜色。后台根据不同的判异规格,将数据点序列判异后,返回一个与数据点一一对应的数组。数组中保存数据点是否被判异。不判异的点为0,判异的点位1。所有为0的点都使用正常颜色,为1的点使用红色。源代码从2124行开始。 function drawSeriesPoints(series) { function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol,ispoint) { var points = datapoints.points, ps = datapoints.pointsize; //复制 产生新的ColorIndex数组 var ColorArry=[]; if (series.colorIndex!=null&&series.colorIndex.length>0) { for (var i = 0; i < series.colorIndex.length; i++) { ColorArry.push(series.colorIndex[i]); ColorArry.push(series.colorIndex[i]); //由于每个points都包含两个坐标轴的数据,先x轴,后y轴。一一对应的数据也需要成对出现 }
}
for (var i = 0; i < points.length; i += ps) { var x = points[i], y = points[i + 1]; // if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) // continue; if (x == null || x < axisx.min || x > axisx.max )//超出x轴的数据不绘图 continue; //超出最大值的点,在绘制时将其值设为最大值,不会改变原有数据 if (y < axisy.min ) { y= axisy.min ; } if (y > axisy.max) { y=axisy.max; }
ctx.beginPath(); x = axisx.p2c(x); y = axisy.p2c(y) + offset; if (symbol == "circle") ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); else symbol(ctx, x, y, radius, shadow); ctx.closePath(); if (ColorArry[i]==1) { ctx.fillStyle = "#FF0000"; ctx.strokeStyle="#FF0000"; ctx.fill();
} else { if (fillStyle) { ctx.fillStyle = series.color; ctx.fill(); } }
ctx.stroke();
if (ispoint) { ctx.strokeStyle= series.color; }
} }
ctx.save(); ctx.translate(plotOffset.left, plotOffset.top);
var lw = series.points.lineWidth, sw = series.shadowSize, radius = series.points.radius, symbol = series.points.symbol; if (lw > 0 && sw > 0) { // draw shadow in two steps var w = sw / 2; ctx.lineWidth = w; ctx.strokeStyle = "rgba(0,0,0,0.1)"; plotPoints(series.datapoints, radius, null, w + w/2, true, series.xaxis, series.yaxis, symbol,false);
ctx.strokeStyle = "rgba(0,0,0,0.2)"; plotPoints(series.datapoints, radius, null, w/2, true, series.xaxis, series.yaxis, symbol,false); }
ctx.lineWidth = lw; ctx.strokeStyle = series.color; plotPoints(series.datapoints, radius, getFillStyle(series.points, series.color), 0, false, series.xaxis, series.yaxis, symbol,true); ctx.restore(); }
我们改变了最大值点的绘制,原本如果数据点超出最大值时,是不绘制点的。此时我们将超出最大值的点的值设为最大值。那么,还需要更改其亮点之间的连接线。 方法为:(源代码1867行开始)
function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize, prevx = null, prevy = null;
ctx.beginPath(); for (var i = ps; i < points.length; i += ps) { var x1 = points[i - ps], y1 = points[i - ps + 1], x2 = points[i], y2 = points[i + 1];
if (x1 == null || x2 == null) continue;
// clip with ymin if (y1 <= y2 && y1 < axisy.min) { if (y2 < axisy.min) y2=axisy.min; // continue; // line segment is outside // compute new intersection point // x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.min; } else if (y2 <= y1 && y2 < axisy.min) { if (y1 < axisy.min) y1=axisy.min; // continue; // x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.min; }
// clip with ymax if (y1 >= y2 && y1 > axisy.max) { if (y2 > axisy.max) y2=axisy.max; // continue; // x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.max; } else if (y2 >= y1 && y2 > axisy.max) { if (y1 > axisy.max) y1=axisy.max; // continue; // x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.max; }
// clip with xmin if (x1 <= x2 && x1 < axisx.min) { if (x2 < axisx.min) continue; y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.min; } else if (x2 <= x1 && x2 < axisx.min) { if (x1 < axisx.min) continue; y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.min; }
// clip with xmax if (x1 >= x2 && x1 > axisx.max) { if (x2 > axisx.max) continue; y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.max; } else if (x2 >= x1 && x2 > axisx.max) { if (x1 > axisx.max) continue; y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.max; }
if (x1 != prevx || y1 != prevy) ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
prevx = x2; prevy = y2; ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); } ctx.stroke(); }
由于先前绘制时,超出最大值的数据不绘制,所以当数据移到修改后的点时也不会显示ToolTip。 源码2439行 function findNearbyItem(mouseX, mouseY, seriesFilter) 方法中,需要将超出最大值的数据点设为最大值。 2470行增加几行代码 if (y < axisy.min ) { y= axisy.min ; } if (y > axisy.max) { y=axisy.max; }
然后直接获取数据位置,取其y值 function showTooltip(x, y, contents) {
var ev = document.onmousemove; var mousePos = mousePosition(ev); y = mousePos.y; $('<div id="tooltip">' + contents + '</div>').css({ position: 'absolute', display: 'none', top: y + 5, left: x + 5, border: '1px solid #fdd', padding: '2px', 'background-color': '#fee', opacity: 0.80 }).appendTo("body").fadeIn(200); }
获取鼠标位置: function mousePosition(ev){ var x, y; var ev = ev || window.event; return { x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, y:ev.clientY + document.body.scrollTop - document.body.clientTop }; }
三、根据数据序列个数不同,绘制不同张数的控制图。目前Spc控制图最多有三张,至少有1长。所以将绘制方法包装一下即可。 所有的数据序列,时间,自定义的tooltips等都已参数传入。并计算其最大最小值。最没有技术的就是这个部分…. function DrawChart(chartcount, spec1, spec2, spec3, data1, reddotArry, data2, data3, timeArry, batchChangeTime, Tips1, Tips2, Tips3, placeholder1, placeholder2, placeholder3) { var d1 = []; var d2 = []; var d3 = []; var changeTime = []; var maxspec1 = 0; var minspec1 = 0; var specMaxLength if (spec1 != null && spec1.length > 0) { maxspec1 = spec1[2] + (spec1[2] - spec1[1]) / 3; minspec1 = spec1[0] -(spec1[1] - spec1[0]) / 3; } var maxspec2 = 0; var minspec2 = 0; if (spec2 != null && spec2.length > 0) { maxspec2 = spec2[2] + (spec2[2] - spec2[1]) / 3; minspec2 = spec2[0] - (spec2[1] - spec2[0]) / 3; } var maxspec3 = 0; var minspec3 = 0; if (spec3 != null && spec3.length > 0) { maxspec3 = spec3[2] + (spec3[2] - spec3[1]) / 3; minspec3= spec3[0] - (spec3[1] - spec3[0]) / 3; }
if (timeArry != null && timeArry.length > 0) { for (var i = 0; i < data1.length; i++) { d1.push([GetTimeStep(new Date(timeArry[i])), data1[i]]); } for (var i = 0; i < data2.length; i++) { d2.push([GetTimeStep(new Date(timeArry[i])), data2[i]]); } for (var i = 0; i < data3.length; i++) { d3.push([GetTimeStep(new Date(timeArry[i])), data3[i]]); } }
if (batchChangeTime != null && batchChangeTime.length>0) { for (var i = 0; i < batchChangeTime.length; i++) { changeTime.push(GetTimeStep(new Date(batchChangeTime[i]))); } }
if (chartcount == 1 || (placeholder2 == "" && placeholder3 == "")) { DrawLastChart(spec3, maxspec3, minspec3, d3, changeTime, Tips3, reddotArry, placeholder3, timeArry); $(placeholder1).hide(); $(placeholder2).hide(); } else if (chartcount == 2 || placeholder3 == "") { plot3 = DrawLastChart(spec3, maxspec3, minspec3, d3, changeTime, Tips3, null, placeholder3); DrawFirstChart(spec1, maxspec1, minspec1, d1, changeTime, Tips1, reddotArry, placeholder1,plot3);
$(placeholder2).hide(); } else if (chartcount == 3) { plot2= DrawCenterChart(spec2, maxspec2, minspec2, d2, changeTime, Tips2, placeholder2); plot3= DrawLastChart(spec3, maxspec3, minspec3, d3, changeTime, Tips3, null, placeholder3); DrawFirstChart(spec1, maxspec1, minspec1, d1, changeTime, Tips1, reddotArry, placeholder1,plot2,plot3);
} else { placeholder1.hide(); placeholder2.hide(); placeholder3.hide(); }
}
然后调用绘制控制图的方法即可。第一张图:需要根据数据点的不同标红。第二张:不需要横坐标。但是数据要与第一张图一一对应。第三张图:需要横坐标,而且第三张图,有时需要当第一张图使用。所以需要三种绘图方法。DrawFirstChart();DrawCenterChart();DrawLastChart(); 具体代码在后面贴出。 三张图数据点的一一对应。 三张图的绘制时间是一致的。即同一时间会在三张图中绘制三个点。此时,如果不做处理,绘制出来的数据是有偏移的——与时间不能对应。有两种方式,可以设置坐标轴的小数点位置,但是会出现以下情况: 0.0000或者,23.1220与3.1200的位置依旧有偏移。 固定左边坐标轴位置:擦,还要改源码 877行 function measureTickLabels(axis)方法中修改923行 if (labels.length > 0) { dummyDiv = makeDummyDiv(labels, ""); if (w == null) w=40; // w = dummyDiv.children().width(); if (h == null) h = dummyDiv.find("div.tickLabel").height(); dummyDiv.remove(); } 将w设置为固定宽度。同时修改背景绘制区域的起始位置,否则坐标轴会被绘制在背景区域中。 修改228行 checkload()方法 function checkload() { if(options.grid.backgroundImage) if ( bgimage.complete ) { ctx.save(); ctx.restore();
var pp = ctx.createPattern(bgimage, 'repeat'); ctx.fillStyle = pp; ctx.fillRect(45, 0, canvasWidth-47, canvasHeight); draw(); } else setTimeout( checkload, 100 ) ; }
四:y轴可以直接设置yaxis: { ticks: spec ,min:minspec,max:maxspec}, 但是x轴的时间需要先转换为UTC再放到数组中。2012/4/12 12:12:12格式转换为UTC代码为 function GetTimeStep(time) { day = time.getHours();
time = time.setHours(8 + day);
time = new Date(time);
time = time.getTime();
return time;
} 五、只有最后一张显示时间横轴, xaxis: { mode: "time", timeformat: "%h:%M", minTickSize: [1, "minute"], show: false }, 把不需要x轴的控制图show:FALSE即可。
7,切换批次时,根据传入的换批时间绘制换批提示线。增加方法
function drawBatchChangeLine(series){ ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); var axisx=series.xaxis; var axisy=series.yaxis; var ticks=axisy.ticks; var max=axisy.max; var min=axisy.min; var changeTime=series.BatchChangeTime; for (var i = 0; i <changeTime. length; i++) { ctx.moveTo(axisx.p2c(changeTime[i]) , 0); ctx.lineTo(axisx.p2c(changeTime[i]) , plotHeight ); ctx.stroke();
}
//ctx.stroke(); ctx.restore(); } 该方法在drawSeries()时被调用
function drawSeries(series) { if (series.lines.show) drawSeriesLines(series); if (series.bars.show) drawSeriesBars(series); if (series.points.show) drawSeriesPoints(series); drawBatchChangeLine(series); }
到此,改造基本结束
图中黑线为换批线。红色的竖线为校准线,会随鼠标移动而移动。
|