//绘制MARK点 internal static void Draw(Graphics g, Channels chnls, ChannelMark[] mrks, AixsVisibleArea ava, ChannelPictureArea cpa, Font mrkFont) { int k = 0; //扫描mrks中所有的点 for (int i = 0; i < mrks.Length; i++) { //第i个MARK点,可见,则绘制它 //并且根据不同的MarkType,绘制动作会有差异 if (mrks[i].showText) { if (mrks[i].markType == MarkType.mrkNormal) DrawMark(k, (i + 1), g, chnls, mrks[i], ava, cpa, mrkFont); else if (mrks[i].markType == MarkType.mrkPeak) DrawPeak(k, (i + 1), g, chnls, mrks[i], ava, cpa, mrkFont); else continue; //else if (mrks[i].markType == MarkType.mrkValley) // ; //递增可见MARK点的个数 k++; } else continue; } }
//使用channel中的所有数据 //依X坐标,从小到大,绘制成图形曲线 internal static void Draw(Channel chnl, Graphics g, AixsVisibleArea ava, ChannelPictureArea cpa, Boolean sampling) { PointF[] p; PointF[] src; ChannelNode chNode; ChannelCurveStyle ccStyle = chnl.ccStyle; //当前段数据与上一段数据的衔接点 //当(i > 0), 将被更新(页面坐标) PointF preTail = new PointF(float.MinValue, float.MinValue); //建立画笔 Pen pn = new Pen(ccStyle.lineColor); pn.Width = ccStyle.lineWidth; //上一次有效数据段在chnl中的索引 int k = -1; //重置channel Nodes chnl.ClearNodes(); //一次将channel中,以i为索引的一段数据 //依X坐标,从小到大,绘制成图形曲线 for (int i = 0; i <= chnl.MaxIndex; i++) { src = chnl.DataOf(i); if (src == null) continue; //假设src按X递增顺序形式 if (src[0].X > src[src.Length - 1].X) continue; //不启动X轴滚动,则从src中采样数据(世界坐标) if (sampling) p = DataSampling.SamplingFrom(src, ava.xBegin, ava.xEnd, cpa.N0); //启用X轴滚动时,不采样,从src(世界坐标)复制数据 else { p = new PointF[src.Length]; for (int j = 0; j < src.Length; j++) p[j] = src[j]; } if (p == null) continue; //查找p中,具有最大、最小Y值的点 //并且设置当前数据段的最小,最大值(世界坐标) chNode = MinMaxOf(p); chnl.SetHeadNode(2, i, chNode.head); chnl.SetTailNode(2, i, chNode.tail); //对p做世界变换,页面坐标 //并且设置当前数据段绘图数据的头部点、尾部点(页面坐标) DrawingChannel.WorldTransform(p, chnl.UnitOffset, ava, cpa); chnl.SetHeadNode(0, i, p[0]); chnl.SetTailNode(0, i, p[p.Length - 1]); //计算当前数据段,与其前一数据段的连接点 //索引(i - 1)无效时,TailNodeOf返回 p(-1, -1) preTail = chnl.TailNodeOf(0, k); //绘制当前段与前一段的连接线 if ((preTail.X > float.MinValue) && (preTail != p[0])) g.DrawLine(pn, preTail, p[0]); //绘制当前数据段曲线 if (p.Length >= 2) g.DrawLines(pn, p); else g.DrawLine(pn, p[0], p[0]); //绘制曲线图标 if (ccStyle.showIcon) DrawingCurveIcon.Draw(p, g, ccStyle); //记录前一次有效数据段在chnl中的索引 k = i; } }
//将p进行世界变换, //va坐标轴当前可见区域 //cpa绘制曲线区域 internal static void WorldTransform(PointF[] p, float yUnitOffset, AixsVisibleArea ava, ChannelPictureArea cpa) { int yValue; double dx, dy; yValue = cpa.rec.Y + cpa.rec.Height; dx = ava.xEnd - ava.xBegin; dy = ava.yEnd - ava.yBegin; for (int i = 0; i < p.Length; i++) { //使用yUnitOffset修正p在Y方向上的值 p[i].Y = p[i].Y + yUnitOffset; //执行世界变换计算 p[i].X = (float)(cpa.rec.X + (p[i].X - ava.xBegin) * cpa.rec.Width / dx); p[i].Y = (float)(yValue - (p[i].Y - ava.yBegin) * cpa.rec.Height / dy); } }
//在mrk.sequence[]中指定的那些channel中,搜索MARK点 //把找到的点,合并成一个文本,作为该MARK点文本 //绘制MARK点文本和MARK图形 private static void DrawMark( int mrkCount, int mrkOrder, Graphics g, Channels chnls, ChannelMark mrk, AixsVisibleArea ava, ChannelPictureArea cpa, Font mrkFont) { int i, j; SizeF szf; Channel chnl; string label = ""; bool bExists = false; PointF srcPoint = new PointF(float.MinValue, float.MinValue); //用于绘制MARK线时,定位坐标 PointF gphPoint = srcPoint; //计算MARK点的世界坐标,即mrk.value,由外部传入 MarkInfo[] mi = new MarkInfo[mrk.sequence.Length]; //在指定的channel中构造MARK文本 for (i = 0; i < mrk.sequence.Length; i++) { mi[i].iChannel = -1; mi[i].iOrder = mrkOrder; j = mrk.sequence[i]; chnl = chnls.ChannelOf(j); if (chnl == null) continue; srcPoint = Search2(chnl, ava, cpa, mrk.searchType, mrk.value); if (srcPoint.X > float.MinValue) { if (!bExists) { bExists = true; gphPoint = srcPoint; } } mi[i].fPoint = srcPoint; mi[i].iChannel = (j + 1); } //若找到MARK点 if ((mrk.showMarkShape) && (gphPoint.X > float.MinValue)) { //使用找的MARK,绘制其文本 if (GenMarkText != null) { label = GenMarkText(mi); szf = g.MeasureString(label, mrkFont); g.DrawString(label, mrkFont, new LinearGradientBrush(new RectangleF(0, 0, szf.Width, szf.Height), mrk.markColor, mrk.markColor, 0.0), new Point(cpa.rec.X, (int)(cpa.rec.Y + mrkCount * szf.Height + MARKMARGIN))); } //使用找的MARK,构造形状 Point[] p; RectangleF recf; //使用找到点的X坐标值来计算MARK点页面坐标在X轴方向的值,不使用mrk.value float x = (int)(cpa.rec.X + (gphPoint.X - ava.xBegin) * cpa.rec.Width / (ava.xEnd - ava.xBegin)); if (mrk.markShape == MarkShape.markPloygon) { p = new Point[7]; recf = MakeMarkPolygon(p, (int)x, cpa); } else { p = new Point[3]; int y = (int)(cpa.rec.Y + cpa.rec.Height - (gphPoint.Y - ava.yBegin) * cpa.rec.Height / (ava.yEnd - ava.yBegin)); recf = MakeMarkTriangle(p, new Point((int)x, y), cpa); } //绘制MARK形状 g.DrawPolygon(new Pen(mrk.markColor), p); //绘制MARK形状内的文本 szf = g.MeasureString(mrkOrder.ToString(), mrkFont); g.DrawString(mrkOrder.ToString(), mrkFont, new LinearGradientBrush(recf, mrk.markColor, mrk.markColor, 0.0), new PointF(recf.X + (recf.Width - szf.Width) / 2, recf.Y + (recf.Height - szf.Height) / 2) ); } }
//在chnl的源数据中,搜索MARK点 private static PointF Search2(Channel chnl, AixsVisibleArea ava, ChannelPictureArea cpa, MarkSearchType schType, float value) { int i, k; float Vcurr, Vmin; PointF pMin = new PointF(float.MinValue, float.MinValue); //获取chnl拥有的第一段数据和最后一段数据 PointF[] src1 = chnl.DataOf(0); PointF[] src2 = chnl.DataOf(chnl.MaxIndex); //chnl中包含的源数据为空 if ((src1 == null) || (src2 == null)) return pMin; //若value在chnl数据之外 if ((value < src1[0].X) || (value > src2[src2.Length - 1].X)) return pMin; //若value在坐标轴之外 if ((value < ava.xBegin) || (value > ava.xEnd)) return pMin; //查找与value最接近的段 PointF[] src; k = -1; Vmin = float.MaxValue; for (i = 0; i <= chnl.MaxIndex; i++) { src = chnl.DataOf(i); if (src == null) continue; Vcurr = Math.Min(Math.Abs(value - src[0].X), Math.Abs(src[src.Length - 1].X - value)); if (Vcurr < Vmin) { k = i; Vmin = Vcurr; } } //找不到源数据所在的数据段 if (k < 0) return pMin; //若MarkSearchType.schValue,则在chnl.DataOf(k)中搜索 //否则将对chnl.DataOf(k)进行采样,再在采样返回的点集中进行搜索 src = chnl.DataOf(k); if (schType == MarkSearchType.schPoint) src = DataSampling.SamplingFrom(src, ava.xBegin, ava.xEnd, cpa.N0); if (src == null) return pMin; //在src中,查找一个点,其X坐标与value相差最小 k = -1; Vmin = float.MaxValue; for (i = 0; i < src.Length; i++) { Vcurr = Math.Abs(src[i].X - value); if (Vcurr < Vmin) { Vmin = Vcurr; k = i; } } //使用yUnitOffset修正src的Y方向的值 src[k].Y = src[k].Y + chnl.UnitOffset; return src[k]; }
//以三角形图形,显示MARK点 private static RectangleF MakeMarkTriangle(Point[] p, Point m, ChannelPictureArea cpa) { p[0] = m; p[1].X = p[0].X - 8; p[1].Y = p[0].Y - 14; if (p[1].X <= cpa.rec.X) { p[1].X = p[0].X; p[1].Y = p[0].Y - 16; p[2].X = p[0].X + 14; p[2].Y = p[0].Y - 8; return new RectangleF(p[0].X, p[0].Y - 14, 10, 11); } else if ((p[1].X + 15) >= (cpa.rec.X + cpa.rec.Width)) { p[1].X = p[0].X - 14; p[1].Y = p[0].Y - 8; p[2].X = p[0].X; p[2].Y = p[0].Y - 16; return new RectangleF(p[0].X - 10, p[0].Y - 14, 10, 11); } else { p[1].X = p[0].X - 8; p[1].Y = p[0].Y - 14; p[2].X = p[0].X + 8; p[2].Y = p[0].Y - 14; return new RectangleF(p[0].X - 5, p[0].Y - 14, 10, 11); } }
//以竖直线和多边形构成的图形,显示MARK点 private static RectangleF MakeMarkPolygon(Point[] p, int x, ChannelPictureArea cpa) { bool b = false; p[0].X = x; p[0].Y = cpa.rec.Y; p[1].X = p[0].X; p[1].Y = p[0].Y + cpa.rec.Height - 22; p[2].X = p[1].X - 8; p[2].Y = p[1].Y + 6; if (p[2].X <= cpa.rec.X) { p[2].X = p[1].X; p[2].Y = p[1].Y + 6; p[3].X = p[2].X; p[3].Y = p[2].Y + 16; p[4].X = p[3].X + 16; p[4].Y = p[3].Y; } else if ((p[2].X + 16) >= (cpa.rec.X + cpa.rec.Width)) { p[2].X = p[1].X; p[2].Y = p[1].Y + 6; p[3].X = p[2].X; p[3].Y = p[2].Y + 16; p[4].X = p[3].X - 16; p[4].Y = p[3].Y; b = true; } else { p[3].X = p[2].X; p[3].Y = p[2].Y + 16; p[4].X = p[3].X + 16; p[4].Y = p[3].Y; } p[5].X = p[4].X; p[5].Y = p[4].Y - 16; p[6].X = p[1].X; p[6].Y = p[1].Y; if (b) return new RectangleF(p[5].X, p[5].Y, 16, 16); else return new RectangleF(p[2].X, p[2].Y, 16, 16); }
//在mrk.sequence[]中指定的那些channel中,搜索Y值最大的点 //绘制PEAK点文本和PEAK图形 private static void DrawPeak( int mrkCount, int mrkOrder, Graphics g, Channels chnls, ChannelMark mrk, AixsVisibleArea ava, ChannelPictureArea cpa, Font mrkFont) { SizeF szf; Channel chnl; string label = ""; PointF p2 = new PointF(float.MinValue, float.MinValue); PointF srcPoint = p2; //在mrk.sequence指定的channel搜索PEAK点 int k = -1; for (int i = 0; i < mrk.sequence.Length; i++) { chnl = chnls.ChannelOf(mrk.sequence[i]); if (chnl != null) { p2 = SearchPeak(chnl); if (p2.Y > srcPoint.Y) { k = i; srcPoint = p2; } } } //找到PEAK点 if ((mrk.showMarkShape) && (srcPoint.X > float.MinValue)) { //对srcPoint的X坐标执行世界变换 int x = (int)(cpa.rec.X + (srcPoint.X - ava.xBegin) * cpa.rec.Width / (ava.xEnd - ava.xBegin)); MarkInfo[] mi = new MarkInfo[1]; mi[0].fPoint = srcPoint; mi[0].iChannel = (k + 1); mi[0].iOrder = mrkOrder; //构造并绘制PEAK文本 if (GenPeakText != null) { label = GenPeakText(mi); szf = g.MeasureString(label, mrkFont); g.DrawString(label, mrkFont, new LinearGradientBrush(new RectangleF(0, 0, szf.Width, szf.Height), mrk.markColor, mrk.markColor, 0.0), new Point(cpa.rec.X, (int)(cpa.rec.Y + mrkCount * szf.Height + MARKMARGIN))); } Point[] p; RectangleF recf; p = new Point[7]; recf = MakeMarkPolygon(p, x, cpa); //绘制PEAK形状 g.DrawPolygon(new Pen(mrk.markColor), p); //绘制PEAK形状内的文本 szf = g.MeasureString(mrkOrder.ToString(), mrkFont); g.DrawString(mrkOrder.ToString(), mrkFont, new LinearGradientBrush(recf, mrk.markColor, mrk.markColor, 0.0), new PointF(recf.X + (recf.Width - szf.Width) / 2, recf.Y + (recf.Height - szf.Height) / 2) ); } }
//语句依据次序执行,建立控件 internal PlotEx(XY2dPlotEx plot) { //保存控件的winform对象 this.plot = plot; //默认,启用采样,仅绘制坐标轴可见范围内的数据 Sampling = true; //设置控件内边距,默认10像素 innerMargin = 10; GridBackColor = Color.SkyBlue; AixsLineColor = Color.Black; //构造控件的标题对象 tt = new plotTitle(); //构造控件的X轴对象 xa = new XAixs(); //构造控件的Y轴对象 ya = new YAixs(); //构造控件的当前可见区域对象 aixvblArea = new AixsVisibleArea(xa.start, xa.stop, ya.start, ya.stop); //构造控件的绘图区域对象 chnlPicArea = new ChannelPictureArea(); //构造控件的边框控制对象 pltBorder = new PlotBorder(); //使用上述部件,绘制控件 RedrawInNewBitmap(); //构造数据对象,用于绘制曲线 chnls = new Channels(); //构造支持的MARK对象 marks = new ChannelMark[MARKCAPCITY]; for (int i = 0; i < marks.Length; i++) marks[i] = new ChannelMark(chnls.Length); }