/* The Axis module seeks to provide a simple facade to a lot of complex logic. * * Axes have many functions: * - Unit/Pixel conversions * - Configuring axis limits and boundaries * - Axis labels (XLabel, YLabel, Title, etc) * - Adding multiple axes * - Grid lines * - Tick marks * - Tick labels * */ using ScottPlot.Drawing; using ScottPlot.Ticks; using System; using System.Drawing; namespace ScottPlot.Renderable { /// /// An Axis stores dimensions (axis limits and pixel/unit conversion methods) and can render /// itself including axis label, tick marks, tick labels, and grid lines /// public class Axis : IRenderable//刻度标记 { /// /// Axis dimensions and methods for pixel/unit conversions /// public readonly AxisDimensions Dims = new AxisDimensions(); /// /// Plottables with this axis index will use pixel/unit conversions from this axis /// public int AxisIndex = 0; //public int ChannelPosition = 0; public bool IsVisible { get; set; } = true; private Edge _Edge; public Edge Edge { get => _Edge; set { _Edge = value; AxisLine.Edge = value; AxisLabel.Edge = value; AxisTicks.Edge = value; bool isVertical = (value == Edge.Left || value == Edge.Right); AxisTicks.TickCollection.Orientation = isVertical ? AxisOrientation.Vertical : AxisOrientation.Horizontal; Dims.IsInverted = isVertical; } } public bool IsHorizontal => Edge == Edge.Top || Edge == Edge.Bottom; public bool IsVertical => Edge == Edge.Left || Edge == Edge.Right; public double[] TickPositionsMajor { get; set; } // private renderable components private readonly AxisLabel AxisLabel = new AxisLabel(); private readonly AxisTicks AxisTicks = new AxisTicks(); private readonly AxisLine AxisLine = new AxisLine(); /// /// Define the size limits for this axis (in pixel units). /// public void SetSizeLimit(float? min = null, float? max = null, float? pad = null) { PixelSizeMinimum = min ?? PixelSizeMinimum; PixelSizeMaximum = max ?? PixelSizeMaximum; PixelSizePadding = pad ?? PixelSizePadding; } // private styling variables private float PixelSize; // how large this axis is private float PixelOffset; // distance from the data area private float PixelSizeMinimum = 5; private float PixelSizeMaximum = float.PositiveInfinity; private float PixelSizePadding = 3; /// /// Define how many pixels away from the data area this axis will be. /// TightenLayout() populates this value (based on other PixelSize values) to stack axes beside each other. /// public void SetOffset(float pixels) => PixelOffset = pixels; /// /// Define how large this axis is in pixels. /// RecalculateAxisSize() populates this value. /// public void SetSize(float pixels) => PixelSize = pixels; public float GetSize() => IsVisible ? PixelSize : 0; public override string ToString() => $"{Edge} axis from {Dims.Min} to {Dims.Max}"; /// /// Use the latest configuration (size, font settings, axis limits) to determine tick mark positions /// public void RecalculateTickPositions(PlotDimensions dims) => AxisTicks.TickCollection.Recalculate(dims, AxisTicks.TickLabelFont, Dims); /// /// Render all components of this axis onto the given Bitmap /// public void Render(PlotDimensions dims, Bitmap bmp, bool lowQuality = false) { if (IsVisible == false) return; //AxisLabel.PixelSizePadding = PixelSizePadding; AxisTicks.PixelOffset = PixelOffset;//标记的偏移 //AxisLabel.PixelOffset = PixelOffset; //AxisLabel.PixelSize = PixelSize;//标记的大小 AxisLine.PixelOffset = PixelOffset; using (var gfx = GDI.Graphics(bmp, dims, lowQuality, false)) { AxisTicks.Render(dims, bmp, Dims.AxisColor, TickPositionsMajor, lowQuality);//绘制网格线和刻度 //AxisLabel.Render(dims, bmp, lowQuality); AxisLine.Render(dims, bmp, lowQuality);//外框线 } } /// /// DateTime format assumes axis represents DateTime.ToOATime() units and displays tick labels accordingly. /// public void DateTimeFormat(bool enable) => AxisTicks.TickCollection.LabelFormat = enable ? ScottPlot.Ticks.TickLabelFormat.DateTime : ScottPlot.Ticks.TickLabelFormat.Numeric; /// /// Configure the label of this axis /// public string Label(string label = null, Color? color = null, float? size = null, bool? bold = null, string fontName = null) { AxisLabel.IsVisible = true; AxisLabel.ImageLabel = null; AxisLabel.Label = label ?? AxisLabel.Label; AxisLabel.Font.Color = color ?? AxisLabel.Font.Color; AxisLabel.Font.Size = size ?? AxisLabel.Font.Size; AxisLabel.Font.Bold = bold ?? AxisLabel.Font.Bold; AxisLabel.Font.Name = fontName ?? AxisLabel.Font.Name; return AxisLabel.Label; } /// /// Display a custom image as the axis label instead of text /// /// The image to display where the label should go /// pixels of padding between the inner image edge and the data area /// pixels of padding between the outer image edge and the figure edge public void ImageLabel(Bitmap img, float padInside = 5, float padOutside = 5) { IsVisible = true; AxisLabel.ImageLabel = img; AxisLabel.ImagePaddingToDataArea = padInside; AxisLabel.ImagePaddingToFigureEdge = padOutside; } /// /// Set color of every component of this axis (label, line, tick marks, and tick labels) /// public void Color(Color color) { Label(color: color); TickLabelStyle(color: color); AxisTicks.MajorTickColor = color; AxisTicks.MinorTickColor = color; AxisLine.Color = color; } /// /// Use a custom function to generate tick label strings /// public void TickLabelFormat(Func tickFormatter) { AxisTicks.TickCollection.ManualTickFormatter = tickFormatter; } /// /// Manually define the string format to use for translating tick positions to tick labels /// public void TickLabelFormat(string format, bool dateTimeFormat) { if (dateTimeFormat) { AxisTicks.TickCollection.dateTimeFormatString = format; DateTimeFormat(true); } else { AxisTicks.TickCollection.numericFormatString = format; DateTimeFormat(false); } } /// /// Customize string settings for the tick labels /// public void TickLabelNotation( bool? multiplier = null, bool? offset = null, bool? exponential = null, bool? invertSign = null, int? radix = null, string prefix = null) { AxisTicks.TickCollection.useMultiplierNotation = multiplier ?? AxisTicks.TickCollection.useMultiplierNotation; AxisTicks.TickCollection.useOffsetNotation = offset ?? AxisTicks.TickCollection.useOffsetNotation; AxisTicks.TickCollection.useExponentialNotation = exponential ?? AxisTicks.TickCollection.useExponentialNotation; AxisTicks.TickCollection.LabelUsingInvertedSign = invertSign ?? AxisTicks.TickCollection.LabelUsingInvertedSign; AxisTicks.TickCollection.radix = radix ?? AxisTicks.TickCollection.radix; AxisTicks.TickCollection.prefix = prefix ?? AxisTicks.TickCollection.prefix; } /// /// Define a manual spacing between major ticks (and major grid lines) /// public void ManualTickSpacing(double manualSpacing) { // TODO: cutt X and Y out of this AxisTicks.TickCollection.manualSpacingX = manualSpacing; AxisTicks.TickCollection.manualSpacingY = manualSpacing; } /// /// Define a manual spacing between major ticks (and major grid lines) for axes configured to display using DateTime format /// public void ManualTickSpacing(double manualSpacing, DateTimeUnit manualSpacingDateTimeUnit) { ManualTickSpacing(manualSpacing); AxisTicks.TickCollection.manualDateTimeSpacingUnitX = manualSpacingDateTimeUnit; } /// /// Manually define major tick (and grid) positions and labels /// public void ManualTickPositions(double[] positions, string[] labels) { AxisTicks.TickCollection.manualTickPositions = positions; AxisTicks.TickCollection.manualTickLabels = labels; } /// /// Ruler mode draws long tick marks and offsets tick labels for a ruler appearance /// public void RulerMode(bool enable) => AxisTicks.RulerMode = enable; /// /// Enable this to snap major ticks (and grid lines) to the nearest pixel to avoid anti-aliasing artifacts /// /// public void PixelSnap(bool enable) => AxisTicks.SnapPx = enable; /// /// Set style of the tick mark lines /// public void TickMarkColor(Color color) { AxisTicks.MajorTickColor = color; AxisTicks.MinorTickColor = color; } /// /// Set the culture to use for unit-to-string tick mark conversion /// public void SetCulture(System.Globalization.CultureInfo culture) => AxisTicks.TickCollection.Culture = culture; /// /// Manually define culture to use for unit-to-string tick mark conversion /// public void SetCulture( string shortDatePattern = null, string decimalSeparator = null, string numberGroupSeparator = null, int? decimalDigits = null, int? numberNegativePattern = null, int[] numberGroupSizes = null) => AxisTicks.TickCollection.SetCulture(shortDatePattern, decimalSeparator, numberGroupSeparator, decimalDigits, numberNegativePattern, numberGroupSizes); /// /// Customize styling of the tick labels /// public void TickLabelStyle( Color? color = null, string fontName = null, float? fontSize = null, bool? fontBold = null, float? rotation = null) { AxisTicks.TickLabelFont.Color = color ?? AxisTicks.TickLabelFont.Color; AxisTicks.TickLabelFont.Name = fontName ?? AxisTicks.TickLabelFont.Name; AxisTicks.TickLabelFont.Size = fontSize ?? AxisTicks.TickLabelFont.Size; AxisTicks.TickLabelFont.Bold = fontBold ?? AxisTicks.TickLabelFont.Bold; AxisTicks.TickLabelRotation = rotation ?? AxisTicks.TickLabelRotation; } /// /// Set visibility of all ticks /// public void Ticks(bool enable) { AxisTicks.MajorTickVisible = enable; AxisTicks.TickLabelVisible = enable; AxisTicks.MinorTickVisible = enable; } /// /// Set visibility of individual tick components /// public void Ticks(bool major, bool minor = true, bool majorLabels = true) { AxisTicks.MajorTickVisible = major; AxisTicks.TickLabelVisible = major && majorLabels; AxisTicks.MinorTickVisible = minor; } /// /// This value defines the packing density of tick labels. /// A density of 1.0 means labels fit tightly based on measured maximum label size. /// Higher densities place more ticks but tick labels may oberlap. /// public void TickDensity(double ratio = 1.0) { AxisTicks.TickCollection.TickDensity = (float)ratio; } /// /// Define the smallest distance between major ticks, grid lines, and tick labels in coordinate units. /// This only works for numeric tick systems (DateTime ticks are not supported). /// public void MinimumTickSpacing(double spacing) { AxisTicks.TickCollection.MinimumTickSpacing = spacing; } /// /// Sets whether minor ticks are evenly spaced or log-distributed between major tick positions /// public void MinorLogScale(bool enable) { AxisTicks.TickCollection.MinorTickDistribution =enable ? MinorTickDistribution.EvenAndLog : MinorTickDistribution.even; Dims.MinorTickDistribution = AxisTicks.TickCollection.MinorTickDistribution; } public MinorTickDistribution GetMinorLogScale() => AxisTicks.TickCollection.MinorTickDistribution; /// /// Configure the line drawn along the edge of the axis /// public void Line(bool? visible = null, Color? color = null, float? width = null) { AxisLine.IsVisible = visible ?? AxisLine.IsVisible; AxisLine.Color = color ?? AxisLine.Color; AxisLine.Width = width ?? AxisLine.Width; } /// /// Set the minimum size and padding of the axis /// public void Layout(float? padding = null, float? minimumSize = null, float? maximumSize = null) { PixelSizePadding = padding ?? PixelSizePadding; PixelSizeMinimum = minimumSize ?? PixelSizeMinimum; PixelSizeMaximum = maximumSize ?? PixelSizeMaximum; } /// /// Configure visibility and styling of the major grid /// public void MajorGrid( bool? enable = null, Color? color = null, float? lineWidth = null, LineStyle? lineStyle = null) { AxisTicks.MajorGridVisible = enable ?? AxisTicks.MajorGridVisible; AxisTicks.MajorGridColor = color ?? AxisTicks.MajorGridColor; AxisTicks.MajorGridWidth = lineWidth ?? AxisTicks.MajorGridWidth; AxisTicks.MajorGridStyle = lineStyle ?? AxisTicks.MajorGridStyle; } /// /// Configure visibility and styling of the minor grid /// public void MinorGrid( bool? enable = null, Color? color = null, float? lineWidth = null, LineStyle? lineStyle = null, bool? logScale = null) { AxisTicks.MinorGridVisible = enable ?? AxisTicks.MinorGridVisible; AxisTicks.MinorGridColor = color ?? AxisTicks.MinorGridColor; AxisTicks.MinorGridWidth = lineWidth ?? AxisTicks.MinorGridWidth; AxisTicks.MinorGridStyle = lineStyle ?? AxisTicks.MinorGridStyle; if (logScale.HasValue) AxisTicks.TickCollection.MinorTickDistribution = logScale.Value ? MinorTickDistribution.log : MinorTickDistribution.even; } public void MinorTick(Color? color = null) { AxisTicks.MinorTickColor = color ?? AxisTicks.MinorTickColor; } /// /// Disable all visibility and set size to 0px /// public void Hide() { AxisLine.IsVisible = false; AxisTicks.MajorTickVisible = false; AxisTicks.MinorTickVisible = false; PixelSizeMinimum = 0; PixelSizeMaximum = 0; PixelSizePadding = 0; } /// /// 小刻度开关 /// /// public void ConfigMinorTick(Boolean isShow) { AxisTicks.MinorTickVisible = isShow; } /// /// Set visibility for major tick grid lines /// public void Grid(bool enable) => AxisTicks.MajorGridVisible = enable; public double[] GetTickPositionsMajor() { return AxisTicks.TickCollection.tickPositionsMajor; } /// /// Set pixel size based on the latest axis label, tick marks, and tick label /// public void RecalculateAxisSize() { using (var tickFont = GDI.Font(AxisTicks.TickLabelFont)) using (var titleFont = GDI.Font(AxisLabel.Font)) { PixelSize = 0; if (AxisLabel.IsVisible) PixelSize += AxisLabel.Measure().Height; if (AxisTicks.TickLabelVisible) { // determine how many pixels the largest tick label occupies float maxHeight = AxisTicks.TickCollection.LargestLabelHeight; float maxWidth = AxisTicks.TickCollection.LargestLabelWidth * 1.2f; // calculate the width and height of the rotated label float largerEdgeLength = Math.Max(maxWidth, maxHeight); float shorterEdgeLength = Math.Min(maxWidth, maxHeight); float differenceInEdgeLengths = largerEdgeLength - shorterEdgeLength; double radians = AxisTicks.TickLabelRotation * Math.PI / 180; double fraction = IsHorizontal ? Math.Sin(radians) : Math.Cos(radians); double rotatedSize = shorterEdgeLength + differenceInEdgeLengths * fraction; // add the rotated label size to the size of this axis PixelSize += (float)rotatedSize; } if (AxisTicks.MajorTickVisible) PixelSize += AxisTicks.MajorTickLength; PixelSize = Math.Max(PixelSize, PixelSizeMinimum); PixelSize = Math.Min(PixelSize, PixelSizeMaximum); PixelSize += PixelSizePadding; } } } }