public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { if (IsVisible == false || (string.IsNullOrWhiteSpace(Label) && ImageLabel == null)) { return; } using var gfx = GDI.Graphics(bmp, lowQuality); (float x, float y) = GetAxisCenter(dims); if (ImageLabel is null) { RenderTextLabel(gfx, x, y); } else { RenderImageLabel(gfx, x, y); } }
private void RenderBarHorizontal(PlotDimensions dims, Graphics gfx, double position, double value, double valueError, double yOffset) { // bar body float centerPx = dims.GetPixelY(position); double edge2 = position + BarWidth / 2; double value1 = Math.Min(ValueBase, value) + yOffset; double value2 = Math.Max(ValueBase, value) + yOffset; double valueSpan = value2 - value1; var rect = new RectangleF( x: dims.GetPixelX(value1), y: dims.GetPixelY(edge2), height: (float)(BarWidth * dims.PxPerUnitY), width: (float)(valueSpan * dims.PxPerUnitX)); RenderBarFromRect(rect, value < 0, gfx); // errorbar double error1 = value > 0 ? value2 - Math.Abs(valueError) : value1 - Math.Abs(valueError); double error2 = value > 0 ? value2 + Math.Abs(valueError) : value1 + Math.Abs(valueError); float capPx1 = dims.GetPixelY(position - ErrorCapSize * BarWidth / 2); float capPx2 = dims.GetPixelY(position + ErrorCapSize * BarWidth / 2); float errorPx2 = dims.GetPixelX(error2); float errorPx1 = dims.GetPixelX(error1); if (ErrorLineWidth > 0 && valueError > 0) { using var errorPen = new Pen(ErrorColor, ErrorLineWidth); gfx.DrawLine(errorPen, errorPx1, centerPx, errorPx2, centerPx); gfx.DrawLine(errorPen, errorPx1, capPx2, errorPx1, capPx1); gfx.DrawLine(errorPen, errorPx2, capPx2, errorPx2, capPx1); } if (ShowValuesAboveBars) { using (var valueTextFont = GDI.Font(Font)) using (var valueTextBrush = GDI.Brush(Font.Color)) using (var sf = new StringFormat() { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Near }) gfx.DrawString(ValueFormatter(value), valueTextFont, valueTextBrush, rect.X + rect.Width, centerPx, sf); } }
public Tick[] GetVisibleMinorTicks(PlotDimensions dims) { double high, low; if (Orientation == AxisOrientation.Vertical) { low = dims.YMin - dims.UnitsPerPxY; // add an extra pixel to capture the edge tick high = dims.YMax + dims.UnitsPerPxY; // add an extra pixel to capture the edge tick } else { low = dims.XMin - dims.UnitsPerPxX; // add an extra pixel to capture the edge tick high = dims.XMax + dims.UnitsPerPxX; // add an extra pixel to capture the edge tick } return(GetMinorTicks() .Where(t => t.Position >= low && t.Position <= high) .ToArray()); }
private (float x, float y) GetLocationPx(PlotDimensions dims, float width, float height) { float leftX = dims.DataOffsetX + Padding; float rightX = dims.DataOffsetX + dims.DataWidth - Padding - width; float centerX = dims.DataOffsetX + dims.DataWidth / 2 - width / 2; float topY = dims.DataOffsetY + Padding; float bottomY = dims.DataOffsetY + dims.DataHeight - Padding - height; float centerY = dims.DataOffsetY + dims.DataHeight / 2 - height / 2; switch (Location) { case Alignment.UpperLeft: return(leftX, topY); case Alignment.UpperCenter: return(centerX, topY); case Alignment.UpperRight: return(rightX, topY); case Alignment.MiddleRight: return(rightX, centerY); case Alignment.LowerRight: return(rightX, bottomY); case Alignment.LowerCenter: return(centerX, bottomY); case Alignment.LowerLeft: return(leftX, bottomY); case Alignment.MiddleLeft: return(leftX, centerY); case Alignment.MiddleCenter: return(centerX, centerY); default: throw new NotImplementedException(); } }
public new void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { base.Render(dims, bmp, lowQuality); if (isHighlighted is null || isHighlighted.Length == 0) { return; } using (var gfx = GDI.Graphics(bmp, dims, lowQuality)) { var highlightedIndexes = Enumerable.Range(0, isHighlighted.Length).Where(x => isHighlighted[x]); foreach (int i in highlightedIndexes) { PointF pt = new PointF(dims.GetPixelX(xs[i]), dims.GetPixelY(ys[i])); MarkerTools.DrawMarker(gfx, pt, highlightedShape, highlightedMarkerSize, highlightedColor); } } }
public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { PointF defaultPoint = new PointF(dims.GetPixelX(x), dims.GetPixelY(y)); PointF textLocationPoint = (rotation == 0) ? TextLocation(defaultPoint) : defaultPoint; using (Graphics gfx = GDI.Graphics(bmp, dims, lowQuality)) using (var framePen = new Pen(frameColor, frameSize * 2)) { gfx.TranslateTransform((int)textLocationPoint.X, (int)textLocationPoint.Y); gfx.RotateTransform((float)rotation); if (frameSize > 0) { gfx.DrawRectangle(framePen, new Rectangle(0, 0, image.Width - 1, image.Height - 1)); } gfx.DrawImage(image, new PointF(0, 0)); gfx.ResetTransform(); } }
public void Recalculate(PlotDimensions dims, Drawing.Font tickFont) { if (manualTickPositions is null) { // first pass uses forced density with manual label sizes to consistently approximate labels if (LabelFormat == TickLabelFormat.DateTime) { RecalculatePositionsAutomaticDatetime(dims, 20, 24, (int)(10 * TickDensity)); } else { RecalculatePositionsAutomaticNumeric(dims, 15, 12, (int)(10 * TickDensity)); } // second pass calculates density using measured labels produced by the first pass (LargestLabelWidth, LargestLabelHeight) = MaxLabelSize(tickFont); if (LabelFormat == TickLabelFormat.DateTime) { RecalculatePositionsAutomaticDatetime(dims, LargestLabelWidth, LargestLabelHeight, null); } else { RecalculatePositionsAutomaticNumeric(dims, LargestLabelWidth, LargestLabelHeight, null); } } else { double min = Orientation == AxisOrientation.Vertical ? dims.YMin : dims.XMin; double max = Orientation == AxisOrientation.Vertical ? dims.YMax : dims.XMax; var visibleIndexes = Enumerable.Range(0, manualTickPositions.Count()) .Where(i => manualTickPositions[i] >= min) .Where(i => manualTickPositions[i] <= max); tickPositionsMajor = visibleIndexes.Select(x => manualTickPositions[x]).ToArray(); tickPositionsMinor = null; tickLabels = visibleIndexes.Select(x => manualTickLabels[x]).ToArray(); CornerLabel = null; (LargestLabelWidth, LargestLabelHeight) = MaxLabelSize(tickFont); } }
public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { using (var gfx = GDI.Graphics(bmp, dims, lowQuality)) using (var pen = GDI.Pen(color, lineWidth, lineStyle, true)) { if (IsHorizontal) { float pixelX1 = dims.GetPixelX(dims.XMin); float pixelX2 = dims.GetPixelX(dims.XMax); float pixelY = dims.GetPixelY(position); gfx.DrawLine(pen, pixelX1, pixelY, pixelX2, pixelY); } else { float pixelX = dims.GetPixelX(position); float pixelY1 = dims.GetPixelY(dims.YMin); float pixelY2 = dims.GetPixelY(dims.YMax); gfx.DrawLine(pen, pixelX, pixelY1, pixelX, pixelY2); } } }
public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { using (var gfx = GDI.Graphics(bmp, dims, lowQuality)) using (var font = GDI.Font(FontName, FontSize, FontBold)) using (var fontBrush = new SolidBrush(FontColor)) using (var linePen = new Pen(LineColor, LineWidth)) using (var sfNorth = new StringFormat() { LineAlignment = StringAlignment.Near, Alignment = StringAlignment.Center }) using (var sfWest = new StringFormat() { LineAlignment = StringAlignment.Center, Alignment = StringAlignment.Near }) { // determine where the corner of the scalebar will be float widthPx = (float)(Width * dims.PxPerUnitX); float heightPx = (float)(Height * dims.PxPerUnitY); PointF cornerPoint = new PointF(dims.GetPixelX(dims.XMax) - Padding, dims.GetPixelY(dims.YMin) - Padding); // move the corner point away from the edge to accommodate label size var xLabelSize = GDI.MeasureString(gfx, HorizontalLabel, font); var yLabelSize = GDI.MeasureString(gfx, VerticalLabel, font); cornerPoint.X -= yLabelSize.Width * 1.2f; cornerPoint.Y -= yLabelSize.Height; // determine all other points relative to the corner point PointF horizPoint = new PointF(cornerPoint.X - widthPx, cornerPoint.Y); PointF vertPoint = new PointF(cornerPoint.X, cornerPoint.Y - heightPx); PointF horizMidPoint = new PointF((cornerPoint.X + horizPoint.X) / 2, cornerPoint.Y); PointF vertMidPoint = new PointF(cornerPoint.X, (cornerPoint.Y + vertPoint.Y) / 2); // draw the scalebar gfx.DrawLines(linePen, new PointF[] { horizPoint, cornerPoint, vertPoint }); gfx.DrawString(HorizontalLabel, font, fontBrush, horizMidPoint.X, cornerPoint.Y, sfNorth); gfx.DrawString(VerticalLabel, font, fontBrush, cornerPoint.X, vertMidPoint.Y, sfWest); } }
private void RenderGridLines(PlotDimensions dims, Graphics gfx, double[] positions, LineStyle gridLineStyle, Color gridLineColor, float gridLineWidth) { if (positions is null || positions.Length == 0 || gridLineStyle == LineStyle.None) { return; } if (IsVertical) { float x = (Edge == Edge.Left) ? dims.DataOffsetX : dims.DataOffsetX + dims.DataWidth; float x2 = (Edge == Edge.Left) ? dims.DataOffsetX + dims.DataWidth : dims.DataOffsetX; var ys = positions.Select(i => dims.GetPixelY(i)); if (gridLineStyle != LineStyle.None) { using (var pen = GDI.Pen(gridLineColor, gridLineWidth, gridLineStyle)) foreach (float y in ys) { gfx.DrawLine(pen, x, y, x2, y); } } } if (IsHorizontal) { float y = (Edge == Edge.Top) ? dims.DataOffsetY : dims.DataOffsetY + dims.DataHeight; float y2 = (Edge == Edge.Top) ? dims.DataOffsetY + dims.DataHeight : dims.DataOffsetY; var xs = positions.Select(i => dims.GetPixelX(i)); if (gridLineStyle != LineStyle.None) { using (var pen = GDI.Pen(gridLineColor, gridLineWidth, gridLineStyle)) foreach (float x in xs) { gfx.DrawLine(pen, x, y, x, y2); } } } }
public static void Scatter(PlotDimensions dims, Bitmap bmp, bool lowQuality, Population pop, Random rand, double popLeft, double popWidth, Color fillColor, Color edgeColor, byte alpha, Position position) { // adjust edges to accomodate special positions if (position == Position.Hide) { return; } if (position == Position.Left || position == Position.Right) { popWidth /= 2; } if (position == Position.Right) { popLeft += popWidth; } // contract edges slightly to encourage padding between elements double edgePaddingFrac = 0.2; popLeft += popWidth * edgePaddingFrac; popWidth -= (popWidth * edgePaddingFrac) * 2; float radius = 5; using (Graphics gfx = GDI.Graphics(bmp, dims, lowQuality)) using (Pen penEdge = GDI.Pen(Color.FromArgb(alpha, edgeColor))) using (Brush brushFill = GDI.Brush(Color.FromArgb(alpha, fillColor))) { foreach (double value in pop.values) { double yPx = dims.GetPixelY(value); double xPx = dims.GetPixelX(popLeft + rand.NextDouble() * popWidth); gfx.FillEllipse(brushFill, (float)(xPx - radius), (float)(yPx - radius), radius * 2, radius * 2); gfx.DrawEllipse(penEdge, (float)(xPx - radius), (float)(yPx - radius), radius * 2, radius * 2); } } }
public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { if (string.IsNullOrWhiteSpace(text)) { return; // no render needed } using (Graphics gfx = GDI.Graphics(bmp, dims, lowQuality)) using (var font = GDI.Font(FontName, FontSize, FontBold)) { float pixelX = dims.GetPixelX(x); float pixelY = dims.GetPixelY(y); SizeF stringSize = GDI.MeasureString(gfx, text, font); RectangleF stringRect = new RectangleF(0, 0, stringSize.Width, stringSize.Height); if (rotation == 0) { (pixelX, pixelY) = ApplyAlignmentOffset(pixelX, pixelY, stringSize.Width, stringSize.Height); } gfx.TranslateTransform(pixelX, pixelY); gfx.RotateTransform((float)rotation); if (frame) { using (var frameBrush = new SolidBrush(frameColor)) { gfx.FillRectangle(frameBrush, stringRect); } } using (var fontBrush = new SolidBrush(FontColor)) { gfx.DrawString(text, font, fontBrush, new PointF(0, 0)); } gfx.ResetTransform(); } }
private void RenderAxis(PlotDimensions dims, Bitmap bmp, bool lowQuality) { using (Graphics gfx = GDI.Graphics(bmp, dims, lowQuality)) using (Pen pen = GDI.Pen(Color.Black)) using (Brush brush = GDI.Brush(Color.Black)) using (var axisFont = GDI.Font(null, 12)) using (StringFormat right_centre = new StringFormat() { Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Center }) using (StringFormat centre_top = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Near }) { double offset = -2; double minScale = Math.Min(dims.PxPerUnitX, dims.PxPerUnitY); gfx.DrawString($"{AxisOffsets[0]:f3}", axisFont, brush, dims.GetPixelX(0), dims.GetPixelY(offset), centre_top); gfx.DrawString($"{AxisOffsets[0] + AxisMultipliers[0]:f3}", axisFont, brush, new PointF((float)((Width * minScale) + dims.GetPixelX(0)), dims.GetPixelY(offset)), centre_top); gfx.DrawString($"{AxisOffsets[1]:f3}", axisFont, brush, dims.GetPixelX(offset), dims.GetPixelY(0), right_centre); gfx.DrawString($"{AxisOffsets[1] + AxisMultipliers[1]:f3}", axisFont, brush, new PointF(dims.GetPixelX(offset), dims.GetPixelY(0) - (float)(Height * minScale)), right_centre); } }
private RectangleF GetPixelRect(PlotDimensions dims, Bar bar) { if (bar.IsVertical) { float left = dims.GetPixelX(bar.Position - bar.Thickness / 2); float right = dims.GetPixelX(bar.Position + bar.Thickness / 2); float bottom = dims.GetPixelY(bar.ValueBase); float top = dims.GetPixelY(bar.Value); float width = right - left; float height = bottom - top; return(new RectangleF(left, top, width, height)); } else { float left = dims.GetPixelX(bar.ValueBase); float right = dims.GetPixelX(bar.Value); float top = dims.GetPixelY(bar.Position + bar.Thickness / 2); float bottom = dims.GetPixelY(bar.Position - bar.Thickness / 2); float width = right - left; float height = bottom - top; return(new RectangleF(left, top, width, height)); } }
// TODO: the negative coordiante thing is silly. Use alignment fields to control this behavior. public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { if (!IsVisible) { return; } using var gfx = GDI.Graphics(bmp, lowQuality); using var font = GDI.Font(Font); using var fontBrush = new SolidBrush(Font.Color); using var shadowBrush = new SolidBrush(ShadowColor); using var backgroundBrush = new SolidBrush(BackgroundColor); using var borderPen = new Pen(BorderColor, BorderWidth); SizeF size = GDI.MeasureString(gfx, Label, font); double x = (X >= 0) ? X : dims.DataWidth + X - size.Width; double y = (Y >= 0) ? Y : dims.DataHeight + Y - size.Height; PointF location = new PointF((float)x + dims.DataOffsetX, (float)y + dims.DataOffsetY); if (Background && Shadow) { gfx.FillRectangle(shadowBrush, location.X + 5, location.Y + 5, size.Width, size.Height); } if (Background) { gfx.FillRectangle(backgroundBrush, location.X, location.Y, size.Width, size.Height); } if (Border) { gfx.DrawRectangle(borderPen, location.X, location.Y, size.Width, size.Height); } gfx.DrawString(Label, font, fontBrush, location); }
public void Recalculate(PlotDimensions dims, Drawing.Font tickFont) { if (manualTickPositions is null) { // first pass uses forced density with manual label sizes to consistently approximate labels if (dateFormat) { RecalculatePositionsAutomaticDatetime(dims, 20, 24, 10); } else { RecalculatePositionsAutomaticNumeric(dims, 15, 12, 10); } // second pass calculates density using measured labels produced by the first pass (maxLabelWidth, maxLabelHeight) = MaxLabelSize(tickFont); if (dateFormat) { RecalculatePositionsAutomaticDatetime(dims, maxLabelWidth, maxLabelHeight, null); } else { RecalculatePositionsAutomaticNumeric(dims, maxLabelWidth, maxLabelHeight, null); } } else { tickPositionsMajor = manualTickPositions; double min = verticalAxis ? dims.YMin : dims.XMin; double max = verticalAxis ? dims.YMax : dims.XMax; tickPositionsMajor = manualTickPositions.Where(x => x >= min && x <= max).ToArray(); tickPositionsMinor = null; tickLabels = manualTickLabels; cornerLabel = null; (maxLabelWidth, maxLabelHeight) = MaxLabelSize(tickFont); } }
public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { if (IsVisible == false) { return; } using (var gfx = GDI.Graphics(bmp, dims, lowQuality, false)) using (var pen = GDI.Pen(Color, Width)) { float left = dims.DataOffsetX; float right = dims.DataOffsetX + dims.DataWidth; float top = dims.DataOffsetY; float bottom = dims.DataOffsetY + dims.DataHeight; if (Edge == Edge.Bottom) { gfx.DrawLine(pen, left, bottom + PixelOffset, right, bottom + PixelOffset); } else if (Edge == Edge.Left) { gfx.DrawLine(pen, left - PixelOffset, bottom, left - PixelOffset, top); } else if (Edge == Edge.Right) { gfx.DrawLine(pen, right + PixelOffset, bottom, right + PixelOffset, top); } else if (Edge == Edge.Top) { gfx.DrawLine(pen, left, top - PixelOffset, right, top - PixelOffset); } else { throw new NotImplementedException(); } } }
/// <summary> /// Return the point and rotation representing the center of the base of this axis /// </summary> private (float x, float y) GetAxisCenter(PlotDimensions dims) { float x = Edge switch { Edge.Left => dims.DataOffsetX - PixelOffset - PixelSize, Edge.Right => dims.DataOffsetX + dims.DataWidth + PixelOffset + PixelSize, Edge.Bottom => dims.DataOffsetX + dims.DataWidth / 2, Edge.Top => dims.DataOffsetX + dims.DataWidth / 2, _ => throw new NotImplementedException() }; float y = Edge switch { Edge.Left => dims.DataOffsetY + dims.DataHeight / 2, Edge.Right => dims.DataOffsetY + dims.DataHeight / 2, Edge.Bottom => dims.DataOffsetY + dims.DataHeight + PixelOffset + PixelSize, Edge.Top => dims.DataOffsetY - PixelOffset - PixelSize, _ => throw new NotImplementedException() }; return(x, y); } } }
public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { Random rand = new Random(0); double groupWidth = .8; var popWidth = groupWidth / seriesCount; for (int seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) { for (int groupIndex = 0; groupIndex < groupCount; groupIndex++) { var series = popMultiSeries.multiSeries[seriesIndex]; var population = series.populations[groupIndex]; var groupLeft = groupIndex - groupWidth / 2; var popLeft = groupLeft + popWidth * seriesIndex; Position scatterPos, boxPos; switch (displayItems) { case DisplayItems.BoxAndScatter: boxPos = Position.Left; scatterPos = Position.Right; break; case DisplayItems.BoxOnly: boxPos = Position.Center; scatterPos = Position.Hide; break; case DisplayItems.ScatterAndBox: boxPos = Position.Right; scatterPos = Position.Left; break; case DisplayItems.ScatterOnly: boxPos = Position.Hide; scatterPos = Position.Center; break; default: throw new NotImplementedException(); } Scatter(dims, bmp, lowQuality, population, rand, popLeft, popWidth, series.color, scatterOutlineColor, 128, scatterPos); if (displayDistributionCurve) { Distribution(dims, bmp, lowQuality, population, rand, popLeft, popWidth, distributionCurveColor, scatterPos, distributionCurveLineStyle); } switch (boxStyle) { case BoxStyle.BarMeanStdErr: Bar(dims, bmp, lowQuality, population, rand, popLeft, popWidth, series.color, boxPos, useStdErr: true); break; case BoxStyle.BarMeanStDev: Bar(dims, bmp, lowQuality, population, rand, popLeft, popWidth, series.color, boxPos, useStdErr: false); break; case BoxStyle.BoxMeanStdevStderr: Box(dims, bmp, lowQuality, population, rand, popLeft, popWidth, series.color, boxPos, BoxFormat.StdevStderrMean); break; case BoxStyle.BoxMedianQuartileOutlier: Box(dims, bmp, lowQuality, population, rand, popLeft, popWidth, series.color, boxPos, BoxFormat.OutlierQuartileMedian); break; case BoxStyle.MeanAndStderr: MeanAndError(dims, bmp, lowQuality, population, rand, popLeft, popWidth, series.color, boxPos, useStdErr: true); break; case BoxStyle.MeanAndStdev: MeanAndError(dims, bmp, lowQuality, population, rand, popLeft, popWidth, series.color, boxPos, useStdErr: false); break; default: throw new NotImplementedException(); } } } }
public new void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) => throw new NotImplementedException();
private void RenderTickLabels(PlotDimensions dims, Graphics gfx) { if (TickCollection.tickLabels is null || TickCollection.tickLabels.Length == 0 || MajorLabelEnable == false) { return; } using (var font = GDI.Font(MajorLabelFont)) using (var brush = GDI.Brush(Color)) using (var sf = GDI.StringFormat()) { if (Edge == Edge.Bottom) { if (Rotation == 0) { sf.Alignment = RulerMode ? StringAlignment.Near : StringAlignment.Center; sf.LineAlignment = StringAlignment.Near; for (int i = 0; i < TickCollection.tickPositionsMajor.Length; i++) { gfx.DrawString(TickCollection.tickLabels[i], font, brush, format: sf, x: dims.GetPixelX(TickCollection.tickPositionsMajor[i]), y: dims.DataOffsetY + dims.DataHeight + PixelOffset + MajorTickLength); } sf.Alignment = StringAlignment.Far; gfx.DrawString(TickCollection.cornerLabel, font, brush, format: sf, x: dims.DataOffsetX + dims.DataWidth, y: dims.DataOffsetY + dims.DataHeight + MajorTickLength + TickCollection.maxLabelHeight); } else { for (int i = 0; i < TickCollection.tickPositionsMajor.Length; i++) { float x = dims.GetPixelX(TickCollection.tickPositionsMajor[i]); float y = dims.DataOffsetY + dims.DataHeight + MajorTickLength + 3; gfx.TranslateTransform(x, y); gfx.RotateTransform(-Rotation); sf.Alignment = StringAlignment.Far; sf.LineAlignment = StringAlignment.Center; gfx.DrawString(TickCollection.tickLabels[i], font, brush, 0, 0, sf); gfx.ResetTransform(); } } } else if (Edge == Edge.Top) { sf.Alignment = RulerMode ? StringAlignment.Near : StringAlignment.Center; sf.LineAlignment = StringAlignment.Far; for (int i = 0; i < TickCollection.tickPositionsMajor.Length; i++) { gfx.DrawString(TickCollection.tickLabels[i], font, brush, format: sf, x: dims.GetPixelX(TickCollection.tickPositionsMajor[i]), y: dims.DataOffsetY - PixelOffset - MajorTickLength); } } else if (Edge == Edge.Left) { sf.LineAlignment = RulerMode ? StringAlignment.Far : StringAlignment.Center; sf.Alignment = StringAlignment.Far; for (int i = 0; i < TickCollection.tickPositionsMajor.Length; i++) { gfx.DrawString(TickCollection.tickLabels[i], font, brush, format: sf, x: dims.DataOffsetX - PixelOffset - MajorTickLength, y: dims.GetPixelY(TickCollection.tickPositionsMajor[i])); } sf.LineAlignment = StringAlignment.Far; sf.Alignment = StringAlignment.Near; gfx.DrawString(TickCollection.cornerLabel, font, brush, dims.DataOffsetX, dims.DataOffsetY, sf); } else if (Edge == Edge.Right) { sf.LineAlignment = RulerMode ? StringAlignment.Far : StringAlignment.Center; sf.Alignment = StringAlignment.Near; for (int i = 0; i < TickCollection.tickPositionsMajor.Length; i++) { gfx.DrawString(TickCollection.tickLabels[i], font, brush, format: sf, x: dims.DataOffsetX + PixelOffset + MajorTickLength + dims.DataWidth, y: dims.GetPixelY(TickCollection.tickPositionsMajor[i])); } } else { throw new NotImplementedException(); } } }
private void RecalculatePositionsAutomaticNumeric(PlotDimensions dims, float labelWidth, float labelHeight, int?forcedTickCount) { double low, high, tickSpacing; int maxTickCount; if (Orientation == AxisOrientation.Vertical) { low = dims.YMin - dims.UnitsPerPxY; // add an extra pixel to capture the edge tick high = dims.YMax + dims.UnitsPerPxY; // add an extra pixel to capture the edge tick maxTickCount = (int)(dims.DataHeight / labelHeight * TickDensity); maxTickCount = forcedTickCount ?? maxTickCount; tickSpacing = (manualSpacingY != 0) ? manualSpacingY : GetIdealTickSpacing(low, high, maxTickCount, radix); tickSpacing = Math.Max(tickSpacing, MinimumTickSpacing); } else { low = dims.XMin - dims.UnitsPerPxX; // add an extra pixel to capture the edge tick high = dims.XMax + dims.UnitsPerPxX; // add an extra pixel to capture the edge tick maxTickCount = (int)(dims.DataWidth / labelWidth * TickDensity); maxTickCount = forcedTickCount ?? maxTickCount; tickSpacing = (manualSpacingX != 0) ? manualSpacingX : GetIdealTickSpacing(low, high, maxTickCount, radix); tickSpacing = Math.Max(tickSpacing, MinimumTickSpacing); } // now that tick spacing is known, populate the list of ticks and labels double firstTickOffset = low % tickSpacing; int tickCount = (int)((high - low) / tickSpacing) + 2; tickCount = tickCount > 1000 ? 1000 : tickCount; tickCount = tickCount < 1 ? 1 : tickCount; tickPositionsMajor = Enumerable.Range(0, tickCount) .Select(x => low - firstTickOffset + tickSpacing * x) .Where(x => low <= x && x <= high) .ToArray(); if (LabelFormat == TickLabelFormat.DateTime) { tickLabels = GetDateLabels(tickPositionsMajor, Culture); tickPositionsMinor = null; } else { (tickLabels, CornerLabel) = GetPrettyTickLabels( tickPositionsMajor, useMultiplierNotation, useOffsetNotation, useExponentialNotation, invertSign: LabelUsingInvertedSign, culture: Culture ); if (MinorTickDistribution == MinorTickDistribution.log) { tickPositionsMinor = MinorFromMajorLog(tickPositionsMajor, low, high); } else { tickPositionsMinor = MinorFromMajor(tickPositionsMajor, 5, low, high); } } }
public Tick[] GetVisibleTicks(PlotDimensions dims) { return(GetVisibleMajorTicks(dims).Concat(GetVisibleMinorTicks(dims)).ToArray()); }
public static void RenderTickLabels(PlotDimensions dims, Graphics gfx, TickCollection tc, Drawing.Font tickFont, Edge edge, float rotation, bool rulerMode, float PixelOffset, float MajorTickLength, float MinorTickLength) { if (tc.tickLabels is null || tc.tickLabels.Length == 0) { return; } using var font = GDI.Font(tickFont); using var brush = GDI.Brush(tickFont.Color); using var sf = GDI.StringFormat(); Tick[] visibleMajorTicks = tc.GetVisibleMajorTicks(dims); switch (edge) { case Edge.Bottom: for (int i = 0; i < visibleMajorTicks.Length; i++) { float x = dims.GetPixelX(visibleMajorTicks[i].Position); float y = dims.DataOffsetY + dims.DataHeight + MajorTickLength; gfx.TranslateTransform(x, y); gfx.RotateTransform(-rotation); sf.Alignment = rotation == 0 ? StringAlignment.Center : StringAlignment.Far; if (rulerMode) { sf.Alignment = StringAlignment.Near; } sf.LineAlignment = rotation == 0 ? StringAlignment.Near : StringAlignment.Center; gfx.DrawString(visibleMajorTicks[i].Label, font, brush, 0, 0, sf); GDI.ResetTransformPreservingScale(gfx, dims); } break; case Edge.Top: for (int i = 0; i < visibleMajorTicks.Length; i++) { float x = dims.GetPixelX(visibleMajorTicks[i].Position); float y = dims.DataOffsetY - MajorTickLength; gfx.TranslateTransform(x, y); gfx.RotateTransform(-rotation); sf.Alignment = rotation == 0 ? StringAlignment.Center : StringAlignment.Near; if (rulerMode) { sf.Alignment = StringAlignment.Near; } sf.LineAlignment = rotation == 0 ? StringAlignment.Far : StringAlignment.Center; gfx.DrawString(visibleMajorTicks[i].Label, font, brush, 0, 0, sf); GDI.ResetTransformPreservingScale(gfx, dims); } break; case Edge.Left: for (int i = 0; i < visibleMajorTicks.Length; i++) { float x = dims.DataOffsetX - PixelOffset - MajorTickLength; float y = dims.GetPixelY(visibleMajorTicks[i].Position); gfx.TranslateTransform(x, y); gfx.RotateTransform(-rotation); sf.Alignment = StringAlignment.Far; sf.LineAlignment = rulerMode ? StringAlignment.Far : StringAlignment.Center; if (rotation == 90) { sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Far; } gfx.DrawString(visibleMajorTicks[i].Label, font, brush, 0, 0, sf); GDI.ResetTransformPreservingScale(gfx, dims); } break; case Edge.Right: for (int i = 0; i < visibleMajorTicks.Length; i++) { float x = dims.DataOffsetX + PixelOffset + MajorTickLength + dims.DataWidth; float y = dims.GetPixelY(visibleMajorTicks[i].Position); gfx.TranslateTransform(x, y); gfx.RotateTransform(-rotation); sf.Alignment = StringAlignment.Near; sf.LineAlignment = rulerMode ? StringAlignment.Far : StringAlignment.Center; if (rotation == 90) { sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Near; } gfx.DrawString(visibleMajorTicks[i].Label, font, brush, 0, 0, sf); GDI.ResetTransformPreservingScale(gfx, dims); } break; default: throw new NotImplementedException($"unsupported edge type {edge}"); } if (!string.IsNullOrWhiteSpace(tc.CornerLabel)) { switch (edge) { case Edge.Bottom: sf.Alignment = StringAlignment.Far; sf.LineAlignment = StringAlignment.Near; gfx.DrawString(s: "\n" + tc.CornerLabel, x: dims.DataOffsetX + dims.DataWidth, y: dims.DataOffsetY + dims.DataHeight + MajorTickLength, font: font, brush: brush, format: sf); break; case Edge.Left: sf.Alignment = StringAlignment.Near; sf.LineAlignment = StringAlignment.Far; gfx.DrawString(s: "\n" + tc.CornerLabel, x: dims.DataOffsetX, y: dims.DataOffsetY, font: font, brush: brush, format: sf); break; case Edge.Top: throw new NotImplementedException("multiplier and offset notation is not supported for right and top axes"); case Edge.Right: throw new NotImplementedException("multiplier and offset notation is not supported for right and top axes"); default: throw new NotImplementedException($"unsupported edge type {edge}"); } } }
/// <summary> /// Render the gauge onto an existing Bitmap /// </summary> /// <param name="gfx">active graphics object</param> /// <param name="dims">plot dimensions (used to determine pixel scaling)</param> /// <param name="centerPixel">pixel location on the bitmap to center the gauge on</param> /// <param name="radius">distance from the center (pixel units) to render the gauge</param> public void Render(Graphics gfx, PlotDimensions dims, PointF centerPixel, float radius) { RenderBackground(gfx, centerPixel, radius); RenderGaugeForeground(gfx, centerPixel, radius); RenderGaugeLabels(gfx, dims, centerPixel, radius); }
public static void Bar(PlotDimensions dims, Bitmap bmp, bool lowQuality, Population pop, Random rand, double popLeft, double popWidth, Color color, Position position, bool useStdErr = false) { // adjust edges to accomodate special positions if (position == Position.Hide) { return; } if (position == Position.Left || position == Position.Right) { popWidth /= 2; } if (position == Position.Right) { popLeft += popWidth; } // determine the center point and calculate bounds double centerX = popLeft + popWidth / 2; double xPx = dims.GetPixelX(centerX); double yPxTop = dims.GetPixelY(pop.mean); double yPxBase = dims.GetPixelY(0); double errorMaxPx, errorMinPx; if (useStdErr) { errorMaxPx = dims.GetPixelY(pop.mean + pop.stdErr); errorMinPx = dims.GetPixelY(pop.mean - pop.stdErr); } else { errorMaxPx = dims.GetPixelY(pop.mean + pop.stDev); errorMinPx = dims.GetPixelY(pop.mean - pop.stDev); } // make cap width a fraction of available space double capWidthFrac = .38; double capWidth = popWidth * capWidthFrac; double capPx1 = dims.GetPixelX(centerX - capWidth / 2); double capPx2 = dims.GetPixelX(centerX + capWidth / 2); // contract edges slightly to encourage padding between elements double edgePaddingFrac = 0.2; popLeft += popWidth * edgePaddingFrac; popWidth -= (popWidth * edgePaddingFrac) * 2; double leftPx = dims.GetPixelX(popLeft); double rightPx = dims.GetPixelX(popLeft + popWidth); RectangleF rect = new RectangleF((float)leftPx, (float)yPxTop, (float)(rightPx - leftPx), (float)(yPxBase - yPxTop)); using (Graphics gfx = GDI.Graphics(bmp, dims, lowQuality)) using (Pen pen = GDI.Pen(Color.Black)) using (Brush brush = GDI.Brush(color)) { gfx.FillRectangle(brush, rect.X, rect.Y, rect.Width, rect.Height); gfx.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height); gfx.DrawLine(pen, (float)xPx, (float)errorMinPx, (float)xPx, (float)errorMaxPx); gfx.DrawLine(pen, (float)capPx1, (float)errorMinPx, (float)capPx2, (float)errorMinPx); gfx.DrawLine(pen, (float)capPx1, (float)errorMaxPx, (float)capPx2, (float)errorMaxPx); } }
public static void Box(PlotDimensions dims, Bitmap bmp, bool lowQuality, Population pop, Random rand, double popLeft, double popWidth, Color color, Position position, BoxFormat boxFormat, HorizontalAlignment errorAlignment = HorizontalAlignment.Right) { // adjust edges to accomodate special positions if (position == Position.Hide) { return; } if (position == Position.Left || position == Position.Right) { popWidth /= 2; } if (position == Position.Right) { popLeft += popWidth; } double errorMaxPx, errorMinPx; double yPxTop, yPxBase; double yPx; if (boxFormat == BoxFormat.StdevStderrMean) { errorMaxPx = dims.GetPixelY(pop.mean + pop.stDev); errorMinPx = dims.GetPixelY(pop.mean - pop.stDev); yPxTop = dims.GetPixelY(pop.mean + pop.stdErr); yPxBase = dims.GetPixelY(pop.mean - pop.stdErr); yPx = dims.GetPixelY(pop.mean); } else if (boxFormat == BoxFormat.OutlierQuartileMedian) { errorMaxPx = dims.GetPixelY(pop.maxNonOutlier); errorMinPx = dims.GetPixelY(pop.minNonOutlier); yPxTop = dims.GetPixelY(pop.Q3); yPxBase = dims.GetPixelY(pop.Q1); yPx = dims.GetPixelY(pop.median); } else { throw new NotImplementedException(); } // make cap width a fraction of available space double capWidthFrac = .38; double capWidth = popWidth * capWidthFrac; // contract edges slightly to encourage padding between elements double edgePaddingFrac = 0.2; popLeft += popWidth * edgePaddingFrac; popWidth -= (popWidth * edgePaddingFrac) * 2; double leftPx = dims.GetPixelX(popLeft); double rightPx = dims.GetPixelX(popLeft + popWidth); RectangleF rect = new RectangleF( x: (float)leftPx, y: (float)yPxTop, width: (float)(rightPx - leftPx), height: (float)(yPxBase - yPxTop)); // determine location of errorbars and caps double capPx1, capPx2, errorPxX; switch (errorAlignment) { case HorizontalAlignment.Center: double centerX = popLeft + popWidth / 2; errorPxX = dims.GetPixelX(centerX); capPx1 = dims.GetPixelX(centerX - capWidth / 2); capPx2 = dims.GetPixelX(centerX + capWidth / 2); break; case HorizontalAlignment.Right: errorPxX = dims.GetPixelX(popLeft + popWidth); capPx1 = dims.GetPixelX(popLeft + popWidth - capWidth / 2); capPx2 = dims.GetPixelX(popLeft + popWidth); break; case HorizontalAlignment.Left: errorPxX = dims.GetPixelX(popLeft); capPx1 = dims.GetPixelX(popLeft); capPx2 = dims.GetPixelX(popLeft + capWidth / 2); break; default: throw new NotImplementedException(); } using (Graphics gfx = GDI.Graphics(bmp, dims, lowQuality)) using (Pen pen = GDI.Pen(Color.Black)) using (Brush brush = GDI.Brush(color)) { // draw the box gfx.FillRectangle(brush, rect.X, rect.Y, rect.Width, rect.Height); gfx.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height); // draw the line in the center gfx.DrawLine(pen, rect.X, (float)yPx, rect.X + rect.Width, (float)yPx); // draw errorbars and caps gfx.DrawLine(pen, (float)errorPxX, (float)errorMinPx, (float)errorPxX, rect.Y + rect.Height); gfx.DrawLine(pen, (float)errorPxX, (float)errorMaxPx, (float)errorPxX, rect.Y); gfx.DrawLine(pen, (float)capPx1, (float)errorMinPx, (float)capPx2, (float)errorMinPx); gfx.DrawLine(pen, (float)capPx1, (float)errorMaxPx, (float)capPx2, (float)errorMaxPx); } }
public static void RenderTickLabels(PlotDimensions dims, Graphics gfx, TickCollection tc, Drawing.Font tickFont, Edge edge, float rotation, bool rulerMode, float PixelOffset, float MajorTickLength, float MinorTickLength) { if (tc.tickLabels is null || tc.tickLabels.Length == 0) { return; } using (var font = GDI.Font(tickFont)) using (var brush = GDI.Brush(tickFont.Color)) using (var sf = GDI.StringFormat()) { // TODO: Refactor to improve rotated tick label rendering: // 1) rotation should always be assumed // 2) a separate function should translate/rotate/render/reset // 3) all edges should support rotation if (edge == Edge.Bottom) { if (rotation == 0) { sf.Alignment = rulerMode ? StringAlignment.Near : StringAlignment.Center; sf.LineAlignment = StringAlignment.Near; for (int i = 0; i < tc.tickPositionsMajor.Length; i++) { gfx.DrawString(tc.tickLabels[i], font, brush, format: sf, x: dims.GetPixelX(tc.tickPositionsMajor[i]), y: dims.DataOffsetY + dims.DataHeight + PixelOffset + MajorTickLength); } sf.Alignment = StringAlignment.Far; gfx.DrawString(tc.cornerLabel, font, brush, format: sf, x: dims.DataOffsetX + dims.DataWidth, y: dims.DataOffsetY + dims.DataHeight + MajorTickLength + tc.maxLabelHeight); } else { for (int i = 0; i < tc.tickPositionsMajor.Length; i++) { float x = dims.GetPixelX(tc.tickPositionsMajor[i]); float y = dims.DataOffsetY + dims.DataHeight + MajorTickLength + 3; gfx.TranslateTransform(x, y); gfx.RotateTransform(-rotation); sf.Alignment = StringAlignment.Far; sf.LineAlignment = StringAlignment.Center; gfx.DrawString(tc.tickLabels[i], font, brush, 0, 0, sf); gfx.ResetTransform(); } } } else if (edge == Edge.Top) { sf.Alignment = rulerMode ? StringAlignment.Near : StringAlignment.Center; sf.LineAlignment = StringAlignment.Far; for (int i = 0; i < tc.tickPositionsMajor.Length; i++) { gfx.DrawString(tc.tickLabels[i], font, brush, format: sf, x: dims.GetPixelX(tc.tickPositionsMajor[i]), y: dims.DataOffsetY - PixelOffset - MajorTickLength); } } else if (edge == Edge.Left) { if (rotation == 0) { sf.LineAlignment = rulerMode ? StringAlignment.Far : StringAlignment.Center; sf.Alignment = StringAlignment.Far; for (int i = 0; i < tc.tickPositionsMajor.Length; i++) { gfx.DrawString(tc.tickLabels[i], font, brush, format: sf, x: dims.DataOffsetX - PixelOffset - MajorTickLength, y: dims.GetPixelY(tc.tickPositionsMajor[i])); } sf.LineAlignment = StringAlignment.Far; sf.Alignment = StringAlignment.Near; gfx.DrawString(tc.cornerLabel, font, brush, dims.DataOffsetX, dims.DataOffsetY, sf); } else { for (int i = 0; i < tc.tickPositionsMajor.Length; i++) { float x = dims.DataOffsetX - PixelOffset - MajorTickLength; float y = dims.GetPixelY(tc.tickPositionsMajor[i]); gfx.TranslateTransform(x, y); gfx.RotateTransform(-rotation); sf.Alignment = StringAlignment.Far; sf.LineAlignment = StringAlignment.Center; gfx.DrawString(tc.tickLabels[i], font, brush, 0, 0, sf); gfx.ResetTransform(); } } } else if (edge == Edge.Right) { sf.LineAlignment = rulerMode ? StringAlignment.Far : StringAlignment.Center; sf.Alignment = StringAlignment.Near; for (int i = 0; i < tc.tickPositionsMajor.Length; i++) { gfx.DrawString(tc.tickLabels[i], font, brush, format: sf, x: dims.DataOffsetX + PixelOffset + MajorTickLength + dims.DataWidth, y: dims.GetPixelY(tc.tickPositionsMajor[i])); } } else { throw new NotImplementedException(); } } }
/// <summary> /// Render an evenly-spaced 2D vector field. /// </summary> public void Render(PlotDimensions dims, Graphics gfx, double[] xs, double[] ys, Statistics.Vector2[,] vectors, Color[] colors) { (float tipScale, float headAngle) = GetTipDimensions(); // precalculate angles for fancy arrows using Pen pen = Drawing.GDI.Pen(Color.Black); if (!ScaledArrowheads) { pen.CustomEndCap = new System.Drawing.Drawing2D.AdjustableArrowCap(NonScaledArrowheadWidth, NonScaledArrowheadLength); } for (int i = 0; i < xs.Length; i++) { for (int j = 0; j < ys.Length; j++) { Statistics.Vector2 v = vectors[i, j]; float tailX, tailY, endX, endY; switch (Anchor) { case ArrowAnchor.Base: tailX = dims.GetPixelX(xs[i]); tailY = dims.GetPixelY(ys[j]); endX = dims.GetPixelX(xs[i] + v.X); endY = dims.GetPixelY(ys[j] + v.Y); break; case ArrowAnchor.Center: tailX = dims.GetPixelX(xs[i] - v.X / 2); tailY = dims.GetPixelY(ys[j] - v.Y / 2); endX = dims.GetPixelX(xs[i] + v.X / 2); endY = dims.GetPixelY(ys[j] + v.Y / 2); break; case ArrowAnchor.Tip: tailX = dims.GetPixelX(xs[i] - v.X); tailY = dims.GetPixelY(ys[j] - v.Y); endX = dims.GetPixelX(xs[i]); endY = dims.GetPixelY(ys[j]); break; default: throw new NotImplementedException("unsupported anchor type"); } pen.Color = colors[i * ys.Length + j]; if (ScaledArrowheads) { DrawFancyArrow(gfx, pen, tailX, tailY, endX, endY, headAngle, tipScale); } else { gfx.DrawLine(pen, tailX, tailY, endX, endY); } if (MarkerShape != MarkerShape.none && MarkerSize > 0) { PointF markerPoint = new PointF(dims.GetPixelX(xs[i]), dims.GetPixelY(ys[j])); MarkerTools.DrawMarker(gfx, markerPoint, MarkerShape, MarkerSize, pen.Color); } } } }
private void RenderCandles(PlotDimensions dims, Bitmap bmp, bool lowQuality) { double fractionalTickWidth = .7; using Graphics gfx = GDI.Graphics(bmp, dims, lowQuality); using Pen pen = new Pen(Color.Magenta); using SolidBrush brush = new SolidBrush(Color.Magenta); for (int i = 0; i < OHLCs.Count; i++) { var ohlc = OHLCs[i]; bool closedHigher = ohlc.Close >= ohlc.Open; double highestOpenClose = Math.Max(ohlc.Open, ohlc.Close); double lowestOpenClose = Math.Min(ohlc.Open, ohlc.Close); var ohlcTime = Sequential ? i : ohlc.DateTime.ToOADate(); var ohlcSpan = Sequential ? 1 : ohlc.TimeSpan.TotalDays; float pixelX = dims.GetPixelX(ohlcTime); float boxWidth = (float)(ohlcSpan * dims.PxPerUnitX / 2 * fractionalTickWidth); Color priceChangeColor = closedHigher ? ColorUp : ColorDown; pen.Color = WickColor ?? priceChangeColor; pen.Width = (boxWidth >= 2) ? 2 : 1; // draw the wick below the box PointF wickLowBot = new PointF(pixelX, dims.GetPixelY(ohlc.Low)); PointF wickLowTop = new PointF(pixelX, dims.GetPixelY(lowestOpenClose)); gfx.DrawLine(pen, wickLowBot, wickLowTop); // draw the wick above the box PointF wickHighBot = new PointF(pixelX, dims.GetPixelY(highestOpenClose)); PointF wickHighTop = new PointF(pixelX, dims.GetPixelY(ohlc.High)); gfx.DrawLine(pen, wickHighBot, wickHighTop); // draw the candle body PointF boxLowerLeft = new PointF(pixelX, dims.GetPixelY(lowestOpenClose)); PointF boxUpperRight = new PointF(pixelX, dims.GetPixelY(highestOpenClose)); if (ohlc.Open == ohlc.Close) { // draw OHLC (non-filled) candle gfx.DrawLine(pen, boxLowerLeft.X - boxWidth, boxLowerLeft.Y, boxLowerLeft.X + boxWidth, boxLowerLeft.Y); } else { // draw a filled candle brush.Color = priceChangeColor; gfx.FillRectangle( brush: brush, x: boxLowerLeft.X - boxWidth, y: boxUpperRight.Y, width: boxWidth * 2, height: boxLowerLeft.Y - boxUpperRight.Y); if (WickColor != null) { gfx.DrawRectangle( pen: pen, x: boxLowerLeft.X - boxWidth, y: boxUpperRight.Y, width: boxWidth * 2, height: boxLowerLeft.Y - boxUpperRight.Y); } } } }