/* * This file contains code related to Axes including: * - Unit/Pixel conversions * - Configuring axis limits and boundaries * - Axis labels (XLabel, YLabel, Title, etc) * - Adding multiple axes * - Grid lines * - Tick marks * - Tick labels */ using System; using System.Drawing; using System.Linq; namespace ScottPlot { public partial class Plot { #region shortcuts: primary axes /// /// Axis on the bottom edge of the plot /// public Renderable.Axis XAxis => settings.XAxis; /// /// Axis on the top edge of the plot /// public Renderable.Axis XAxis2 => settings.XAxis2; /// /// Axis on the left edge of the plot /// public Renderable.Axis YAxis => settings.YAxis; /// /// Axis on the right edge of the plot /// public Renderable.Axis YAxis2 => settings.YAxis2; #endregion #region shortcuts: axis label, tick, and grid /// /// Set the label for the vertical axis to the right of the plot (XAxis) /// /// new text /// /// /// /// public void XLabel(string label, Color? color = null, float? size = null, bool? bold = null, string fontName = null) { XAxis.Label(label, color, size, bold, fontName); } /// /// Set the label for the vertical axis to the right of the plot (YAxis2) /// /// new text /// /// /// /// public void YLabel(string label, Color? color = null, float? size = null, bool? bold = null, string fontName = null) { YAxis.Label(label, color, size, bold, fontName); } /// /// Set the label for the horizontal axis above the plot (XAxis2) /// /// new text /// controls font weight public void Title(string label, bool bold = true) => XAxis2.Label(label, bold: bold); /// /// Configure color and visibility of the frame that outlines the data area. /// Note that the axis lines of all 4 primary axes touch each other, /// giving the appearance of a rectangle framing the data area. /// This method allows the user to customize these lines as a group or individually. /// /// visibility of the frames for the 4 primary axes /// color for the 4 primary axis lines /// visibility of the left axis (YAxis) line /// visibility of the right axis (YAxis2) line /// visibility of the bottom axis (XAxis) line /// visibility of the top axis (XAxis2) line public void Frame(bool? visible = null, Color? color = null, bool? left = null, bool? right = null, bool? bottom = null, bool? top = null) { var primaryAxes = new Renderable.Axis[] { XAxis, XAxis2, YAxis, YAxis2 }; foreach (var axis in primaryAxes) axis.Line(visible, color); YAxis.Line(visible: left); YAxis2.Line(visible: right); XAxis.Line(visible: bottom); XAxis2.Line(visible: top); } /// /// Give the plot a frameless appearance by setting the size of all axes to zero. /// This causes the data area to go right up to the edge of the plot. /// public void Frameless() { foreach (var axis in settings.Axes) axis.Hide(); } /// /// 小刻度开关 /// /// public void MinorTickVisible(Boolean isShow) { foreach (var axis in settings.Axes) axis.ConfigMinorTick(isShow); } /// /// 十字线开关 /// /// public void GridCrosslineVisible(Boolean isShow) { foreach (var axis in settings.Axes) axis.ConfigGridCrossline(isShow); } /// /// Customize basic options for the primary X and Y axes. /// Call XAxis.Grid() and YAxis.Grid() to further customize grid settings. /// /// sets visibility of X and Y grid lines /// sets color of of X and Y grid lines /// defines the style for X and Y grid lines public void Grid(bool? enable = null, Color? color = null, LineStyle? lineStyle = null) { if (enable.HasValue) { XAxis.Grid(enable.Value); YAxis.Grid(enable.Value); } XAxis.MajorGrid(color: color, lineStyle: lineStyle); YAxis.MajorGrid(color: color, lineStyle: lineStyle); XAxis.MinorGrid(color: color, lineStyle: lineStyle); YAxis.MinorGrid(color: color, lineStyle: lineStyle); XAxis.MinorTick(color); YAxis.MinorTick(color); } /// /// Set padding around the data area by defining the minimum size and padding for all axes /// /// YAxis size (in pixels) that defines the area to the left of the plot /// YAxis2 size (in pixels) that defines the area to the right of the plot /// XAxis size (in pixels) that defines the area to the bottom of the plot /// XAxis2 size (in pixels) that defines the area to the top of the plot /// Customize the default padding between axes and the edge of the plot public void Layout(float? left = null, float? right = null, float? bottom = null, float? top = null, float? padding = 5) { YAxis.Layout(padding, left); YAxis2.Layout(padding, right); XAxis.Layout(padding, bottom); XAxis2.Layout(padding, top); } /// /// Adjust this axis layout based on the layout of a source plot /// /// plot to use for layout reference /// if true, horizontal layout will be matched /// if true, vertical layout will be matched public void MatchLayout(Plot sourcePlot, bool horizontal = true, bool vertical = true) { if (!sourcePlot.GetSettings(showWarning: false).AllAxesHaveBeenSet) sourcePlot.AxisAuto(); if (!settings.AllAxesHaveBeenSet) AxisAuto(); var sourceSettings = sourcePlot.GetSettings(false); if (horizontal) { YAxis.SetSize(sourceSettings.YAxis.GetSize()); YAxis2.SetSize(sourceSettings.YAxis2.GetSize()); } if (vertical) { XAxis.SetSize(sourceSettings.XAxis.GetSize()); XAxis2.SetSize(sourceSettings.XAxis2.GetSize()); } } /// /// Manually define X axis tick labels using consecutive integer positions (0, 1, 2, etc.) /// /// new tick labels for the X axis public void XTicks(string[] labels) => XTicks(DataGen.Consecutive(labels.Length), labels); /// /// Manually define X axis tick positions and labels /// /// positions on the X axis /// new tick labels for the X axis public void XTicks(double[] positions = null, string[] labels = null) => XAxis.ManualTickPositions(positions, labels); /// /// Manually define Y axis tick labels using consecutive integer positions (0, 1, 2, etc.) /// /// new tick labels for the Y axis public void YTicks(string[] labels) => YTicks(DataGen.Consecutive(labels.Length), labels); /// /// Manually define Y axis tick positions and labels /// /// positions on the Y axis /// new tick labels for the Y axis public void YTicks(double[] positions = null, string[] labels = null) => YAxis.ManualTickPositions(positions, labels); /// /// Set the culture to use for number-to-string converstion for tick labels of all axes. /// /// standard culture public void SetCulture(System.Globalization.CultureInfo culture) { foreach (var axis in settings.Axes) axis.SetCulture(culture); } /// /// Set the culture to use for number-to-string converstion for tick labels of all axes. /// This overload allows you to manually define every format string, /// allowing extensive customization of number and date formatting. /// /// /// Separates the decimal digits /// Separates large numbers ito groups of digits for readability /// Number of digits after the numberDecimalSeparator /// Appearance of negative numbers /// Sizes of decimal groups which are separated by the numberGroupSeparator public void SetCulture(string shortDatePattern = null, string decimalSeparator = null, string numberGroupSeparator = null, int? decimalDigits = null, int? numberNegativePattern = null, int[] numberGroupSizes = null) { foreach (var axis in settings.Axes) axis.SetCulture(shortDatePattern, decimalSeparator, numberGroupSeparator, decimalDigits, numberNegativePattern, numberGroupSizes); } #endregion #region Axis creation /// /// Create and return an additional axis /// /// Edge of the plot the new axis will belong to /// Only plottables with the same axis index will use this axis /// defualt label to use for the axis /// defualt color to use for the axis /// The axis that was just created and added to the plot. You can further customize it by interacting with it. public Renderable.Axis AddAxis(Renderable.Edge edge, int axisIndex, string title = null, Color? color = null) { if (axisIndex <= 1) throw new ArgumentException("The default axes already occupy indexes 0 and 1. Additional axes require higher indexes."); Renderable.Axis axis; if (edge == Renderable.Edge.Left) axis = new Renderable.AdditionalLeftAxis(axisIndex, title); else if (edge == Renderable.Edge.Right) axis = new Renderable.AdditionalRightAxis(axisIndex, title); else if (edge == Renderable.Edge.Bottom) axis = new Renderable.AdditionalBottomAxis(axisIndex, title); else if (edge == Renderable.Edge.Top) axis = new Renderable.AdditionalTopAxis(axisIndex, title); else throw new NotImplementedException("unsupported edge"); if (color.HasValue) axis.Color(color.Value); settings.Axes.Add(axis); return axis; } #endregion #region coordinate/pixel conversions /// /// Return the coordinate (in coordinate space) for the given pixel /// /// horizontal pixel location /// vertical pixel location /// point in coordinate space public (double x, double y) GetCoordinate(float xPixel, float yPixel) => (settings.XAxis.Dims.GetUnit(xPixel), settings.YAxis.Dims.GetUnit(yPixel)); /// /// Return the X position (in coordinate space) for the given pixel column /// /// horizontal pixel location /// horizontal position in coordinate space public double GetCoordinateX(float xPixel) => settings.XAxis.Dims.GetUnit(xPixel); /// /// Return the Y position (in coordinate space) for the given pixel row /// /// vertical pixel location /// vertical position in coordinate space public double GetCoordinateY(float yPixel) => settings.YAxis.Dims.GetUnit(yPixel); /// /// Return the pixel for the given point in coordinate space /// /// horizontal coordinate /// vertical coordinate /// pixel location public (float xPixel, float yPixel) GetPixel(double x, double y) => (settings.XAxis.Dims.GetPixel(x), settings.YAxis.Dims.GetPixel(y)); /// /// Return the horizontal pixel location given position in coordinate space /// /// horizontal coordinate /// horizontal pixel position public float GetPixelX(double x) => settings.XAxis.Dims.GetPixel(x); /// /// Return the vertical pixel location given position in coordinate space /// /// vertical coordinate /// vertical pixel position public float GetPixelY(double y) => settings.YAxis.Dims.GetPixel(y); /// /// xMin 左边界 /// xMax 右边界 /// yMin 上边界 /// yMax 下边界 /// /// public (double xLeft, double xRight, double yTop, double yBottom) GetZoomUnitArea() => settings.ZoomUnitArea; #endregion #region axis limits: get and set /// /// Returns the current limits for a given pair of axes. /// /// which axis index to reference /// which axis index to reference /// current limits public AxisLimits GetAxisLimits(int xAxisIndex = 0, int yAxisIndex = 0) { (double xMin, double xMax) = settings.GetXAxis(xAxisIndex).Dims.RationalLimits(); (double yMin, double yMax) = settings.GetYAxis(yAxisIndex).Dims.RationalLimits(); return new AxisLimits(xMin, xMax, yMin, yMax); } /// /// Set limits for the a given pair of axes /// /// lower limit of the horizontal axis /// upper limit of the horizontal axis /// lower limit of the vertical axis /// upper limit of the vertical axis /// index of the axis the horizontal limits apply to /// index of the axis the vertical limits apply to public void SetAxisLimits( double? xMin = null, double? xMax = null, double? yMin = null, double? yMax = null, int xAxisIndex = 0, int yAxisIndex = 0) { bool notAllAxesDefined = xMin is null || xMax is null || yMin is null || yMax is null; if (notAllAxesDefined) settings.AxisAutoUnsetAxes(); settings.AxisSet(xMin, xMax, yMin, yMax, xAxisIndex, yAxisIndex); } /// /// Set limits for the primary X axis /// /// lower limit of the horizontal axis /// upper limit of the horizontal axis public void SetAxisLimitsX(double xMin, double xMax) => SetAxisLimits(xMin, xMax, null, null); /// /// Set limits for the primary Y axis /// /// lower limit of the vertical axis /// upper limit of the vertical axis public void SetAxisLimitsY(double yMin, double yMax) => SetAxisLimits(null, null, yMin, yMax); /// /// Set limits for a pair of axes /// /// new limits /// index of the axis the horizontal limits apply to /// index of the axis the vertical limits apply to public void SetAxisLimits(AxisLimits limits, int xAxisIndex = 0, int yAxisIndex = 0) => settings.AxisSet(limits, xAxisIndex, yAxisIndex); /// /// Set limits of the view for the primary axes. /// View limits define the boundaries of axis limits. /// You cannot zoom, pan, or set axis limits beyond view limits. /// /// lower limit of the horizontal axis /// upper limit of the horizontal axis /// lower limit of the vertical axis /// upper limit of the vertical axis public void SetViewLimits( double xMin = double.NegativeInfinity, double xMax = double.PositiveInfinity, double yMin = double.NegativeInfinity, double yMax = double.PositiveInfinity) { settings.XAxis.Dims.SetBounds(xMin, xMax); settings.YAxis.Dims.SetBounds(yMin, yMax); } #endregion #region axis limits: fit to plottable data /// /// Automatically adjust axis limits to fit the data /// /// amount of space to the left and right of the data (as a fraction of its width) /// amount of space above and below the data (as a fraction of its height) public void AxisAuto(double horizontalMargin = .05, double verticalMargin = .1) => settings.AxisAutoAll(horizontalMargin, verticalMargin); /// /// Automatically adjust axis limits to fit the data /// /// amount of space to the left and right of the data (as a fraction of its width) public void AxisAutoX(double margin = .05) { if (settings.Plottables.Count == 0) { SetAxisLimits(yMin: -10, yMax: 10); return; } AxisLimits originalLimits = GetAxisLimits(); AxisAuto(horizontalMargin: margin); SetAxisLimits(yMin: originalLimits.YMin, yMax: originalLimits.YMax); } /// /// Automatically adjust axis limits to fit the data (with a little extra margin) /// /// amount of space above and below the data (as a fraction of its height) public void AxisAutoY(double margin = .1) { if (settings.Plottables.Count == 0) { SetAxisLimits(xMin: -10, xMax: 10); return; } AxisLimits originalLimits = GetAxisLimits(); AxisAuto(horizontalMargin: margin); SetAxisLimits(xMin: originalLimits.XMin, xMax: originalLimits.XMax); } #endregion #region axis limits: scaling /// /// Adjust axis limits to achieve a certain pixel scale (units per pixel) /// /// zoom so 1 pixel equals this many horizontal units in coordinate space /// zoom so 1 pixel equals this many vertical units in coordinate space public void AxisScale(double? unitsPerPixelX = null, double? unitsPerPixelY = null) { if (unitsPerPixelX != null) { double spanX = unitsPerPixelX.Value * settings.DataWidth; SetAxisLimits(xMin: settings.XAxis.Dims.Center - spanX / 2, xMax: settings.XAxis.Dims.Center + spanX / 2); } if (unitsPerPixelY != null) { double spanY = unitsPerPixelY.Value * settings.DataHeight; SetAxisLimits(xMin: settings.YAxis.Dims.Center - spanY / 2, xMax: settings.YAxis.Dims.Center + spanY / 2); } } /// /// Lock X and Y axis scales (units per pixel) together to protect symmetry of circles and squares /// /// if true, scales are locked such that zooming one zooms the other /// defines behavior for how to adjust axis limits to achieve equal scales public void AxisScaleLock(bool enable, EqualScaleMode scaleMode = EqualScaleMode.PreserveY) { settings.AxisAutoUnsetAxes(); settings.EqualScaleMode = enable ? scaleMode : EqualScaleMode.Disabled; settings.LayoutAuto(); settings.EnforceEqualAxisScales(); } #endregion #region axis limits: pan and zoom /// /// Zoom in or out. The amount of zoom is defined as a fraction of the current axis span. /// /// horizontal zoom (>1 means zoom in) /// vertical zoom (>1 means zoom in) /// if defined, zoom will be centered at this point /// if defined, zoom will be centered at this point /// index of the axis to zoom /// index of the axis to zoom public void AxisZoom( double xFrac = 1, double yFrac = 1, double? zoomToX = null, double? zoomToY = null, int xAxisIndex = 0, int yAxisIndex = 0) { var xAxis = settings.GetXAxis(xAxisIndex); var yAxis = settings.GetYAxis(yAxisIndex); if (xAxis.Dims.HasBeenSet == false || yAxis.Dims.HasBeenSet == false) settings.AxisAutoAll(); xAxis.Dims.Zoom(xFrac, zoomToX ?? xAxis.Dims.Center); yAxis.Dims.Zoom(yFrac, zoomToY ?? yAxis.Dims.Center); } /// /// Pan the primary X and Y axis without affecting zoom /// /// horizontal distance to pan (in coordinate units) /// vertical distance to pan (in coordinate units) public void AxisPan(double dx = 0, double dy = 0) { if (!settings.AllAxesHaveBeenSet) settings.AxisAutoAll(); settings.XAxis.Dims.Pan(dx); settings.XAxis.Dims.Pan(dy); } #endregion #region obsolete [Obsolete("Use SetAxisLimits() and GetAxisLimits()", true)] public AxisLimits AxisLimits(int xAxisIndex = 0, int yAxisIndex = 0) => throw new NotImplementedException(); [Obsolete("use GetCoordinateX()", true)] public double CoordinateFromPixelX(float pixelX) => throw new NotImplementedException(); [Obsolete("use GetCoordinateY()", true)] public double CoordinateFromPixelY(float pixelY) => throw new NotImplementedException(); [Obsolete("use GetCoordinateX()", true)] public double CoordinateFromPixelX(double pixelX) => throw new NotImplementedException(); [Obsolete("use GetCoordinateY()", true)] public double CoordinateFromPixelY(double pixelY) => throw new NotImplementedException(); [Obsolete("use GetCoordinate(), GetCoordinateX() or GetCoordinateY()", true)] public System.Drawing.PointF CoordinateFromPixel(int pixelX, int pixelY) => throw new NotImplementedException(); [Obsolete("use GetCoordinate(), GetCoordinateX() or GetCoordinateY()", true)] public System.Drawing.PointF CoordinateFromPixel(float pixelX, float pixelY) => throw new NotImplementedException(); [Obsolete("use GetCoordinate(), GetCoordinateX() or GetCoordinateY()", true)] public System.Drawing.PointF CoordinateFromPixel(double pixelX, double pixelY) => throw new NotImplementedException(); [Obsolete("use GetCoordinate(), GetCoordinateX() or GetCoordinateY()", true)] public System.Drawing.PointF CoordinateFromPixel(System.Drawing.Point pixel) => throw new NotImplementedException(); [Obsolete("use GetCoordinate(), GetCoordinateX() or GetCoordinateY()", true)] public System.Drawing.PointF CoordinateFromPixel(System.Drawing.PointF pixel) => throw new NotImplementedException(); [Obsolete("use GetPixel, GetPixelX(), or GetPixelY()", true)] public System.Drawing.PointF CoordinateToPixel(System.Drawing.PointF location) => throw new NotImplementedException(); [Obsolete("use GetPixel, GetPixelX(), or GetPixelY()", true)] public System.Drawing.PointF CoordinateToPixel(double locationX, double locationY) => throw new NotImplementedException(); [Obsolete("use GetPixelX()", true)] public float CoordinateToPixelX(double locationX) => throw new NotImplementedException(); [Obsolete("use GetPixelY()", true)] public float CoordinateToPixelY(double locationY) => throw new NotImplementedException(); [Obsolete("use GetAxisLimits() and SetAxisLimits()", true)] public AxisLimits Axis(double? x1 = null, double? x2 = null, double? y1 = null, double? y2 = null) => throw new NotImplementedException(); [Obsolete("use GetAxisLimits() and SetAxisLimits()", true)] public void Axis(double[] axisLimits, int xAxisIndex = 0, int yAxisIndex = 0) => throw new NotImplementedException(); [Obsolete("use GetAxisLimits() and SetAxisLimits()", true)] public double[] Axis(double? x1 = null, double? x2 = null, double? y1 = null, double? y2 = null, double? _ = null) => null; [Obsolete("use GetAxisLimits() and SetAxisLimits()", true)] public double[] Axis(double[] axisLimits) => null; [Obsolete("use GetAxisLimits() and SetAxisLimits()", true)] public void MatchAxis(Plot sourcePlot, bool horizontal = true, bool vertical = true) => throw new NotImplementedException(); [Obsolete("use GetAxisLimits() and SetAxisLimits()", true)] public void Axis(AxisLimits limits, int xAxisIndex = 0, int yAxisIndex = 0) => throw new NotImplementedException(); [Obsolete("use AxisScaleLock()", true)] public bool EqualAxis; [Obsolete("Use AxisAuto()", true)] public double[] AutoAxis() => null; [Obsolete("Use AxisAuto()", true)] public double[] AutoScale() => null; [Obsolete("Individual axes (e.g., XAxis and YAxis) have their own tick configuration methods", true)] public void Ticks( bool? displayTicksX = null, bool? displayTicksY = null, bool? displayTicksXminor = null, bool? displayTicksYminor = null, bool? displayTickLabelsX = null, bool? displayTickLabelsY = null, Color? color = null, bool? useMultiplierNotation = null, bool? useOffsetNotation = null, bool? useExponentialNotation = null, bool? dateTimeX = null, bool? dateTimeY = null, bool? rulerModeX = null, bool? rulerModeY = null, bool? invertSignX = null, bool? invertSignY = null, string fontName = null, float? fontSize = null, float? xTickRotation = null, bool? logScaleX = null, bool? logScaleY = null, string numericFormatStringX = null, string numericFormatStringY = null, bool? snapToNearestPixel = null, int? baseX = null, int? baseY = null, string prefixX = null, string prefixY = null, string dateTimeFormatStringX = null, string dateTimeFormatStringY = null ) => throw new NotImplementedException(); #endregion public void SetTickLabelColor(Color color) { foreach (var axis in settings.Axes) { axis.TickLabelStyle(color:color); } } /// /// 设置垂直标记参数 /// /// /// /// /// /// /// /// public void ResetChannelParameter(Double position, Double scale, String prefix, String scaleUnit, Int32 interval = 1000,Boolean isUnitPrefix = true, Int32 tickDecimalPlaces = 4) { YAxis.Dims.Update(position, scale, prefix, scaleUnit, interval, isUnitPrefix, tickDecimalPlaces); } /// /// 设置水平标记参数 /// /// /// /// /// /// /// /// public void ResetTimebaseParameter(Double position, Double scale, String prefix, String scaleUnit,/* Color color,*/ Int32 interval = 1000, Boolean isUnitPrefix = true, Int32 tickDecimalPlaces = 4) { XAxis.Dims.Update(position, scale, prefix, scaleUnit, interval, isUnitPrefix, tickDecimalPlaces); } } }