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