/* 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;
}
}
}
}