您要设置图表区域的Interval。如果您会阅读 C#,请查看下面的代码。
Area.AxisY.Interval = p_axisYInterval;
Area.AxisX.Interval = p_axisXInterval;
这是一个演示应用
在 c# 中创建一个新的 WinForms 项目。添加一个名为 chart1(默认名称)的图表控件和一个名为 button1(也是默认名称)的按钮。双击按钮为它连接点击事件。
然后复制粘贴下面的整个代码块并运行它。单击该按钮可将数据添加到图表并进行单步调试/调试以查看发生了什么/在何处发生。
我刚刚从我已经拥有的项目中提取了 99% 的代码,所以它有点乱,但它明白了重点。
编辑
更新了我的示例以执行 XY 系列而不是固定的 X 值,并硬编码了一些内容以查看其行为方式。
查看下面显示的此代码块的方法,并将 bool flag 更改为 true 或 false 并运行它以查看行为的变化。如果Maximum 和Minimum 对称 或者如果0 恰好是Interval 命中的值之一(即。如果 Min = -10.0, Max = 5.0 和 Interval = 1.0,那么它将显示 -10, -9, -8... 0, 1, 2, ... 5)。
我对这些值进行了硬编码,但您当然希望以编程方式获取它们。然而,这是我留给你的练习。享受吧!
// Make X-Interval Clean numbers
if (flag)
{
// Play with these number, the hard part I suppose is calculating them programmatically.
m_chart.ChartAreas[p_chartArea].AxisX.Minimum = -2;
m_chart.ChartAreas[p_chartArea].AxisX.Interval = 0.5;
m_chart.ChartAreas[p_chartArea].AxisX.Maximum = 1;
}
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using Common.Extensions;
using Common.FluentValidation;
using InspectionStation.Classes.Adapters;
using InspectionStation.Classes.Components;
using InspectionStation.Interfaces.IComponents;
using System.Linq;
using System.Text;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public IScanResultsDisplay Single_Point_Lasers_Display { get; private set; }
public Form1()
{
InitializeComponent();
Single_Point_Lasers_Display = new ChartControl_To_IScanResultsDisplay_Adapter(this.chart1, Color.Silver);
}
int Count = 0;
private void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("Generate_new_graph_data");
try
{
var foo = new double[720];
var bar = new double[720];
for (int i = 0; i < foo.Length; i++)
{
foo [i] = (i - 460) / 567.5432;
bar[i] = Math.Sin((i + Count++ * Count++) / (float)Count++ / i);
}
Single_Point_Lasers_Display
.AddSeries("foo", 0, ScanKey.Inside_Scan, foo.ToList(), bar.ToList());
}
catch (Exception ex)
{
var sb = new StringBuilder();
sb.AppendFormat("Exception: \"{0}\"", ex.GetType().FullName).AppendLine();
sb.AppendFormat("Message: \"{0}\"", ex.Message).AppendLine();
sb.AppendFormat("TargetSite: \"{0}\"", ex.TargetSite).AppendLine();
sb.AppendFormat("Source: \"{0}\"", ex.Source).AppendLine();
sb.AppendFormat("Stack_Trace: \"{0}\"", ex.StackTrace);
Console.WriteLine(sb.ToString());
}
}
}
}
namespace InspectionStation.Classes.Components
{
public static class ScanKey
{
public const string Outside_Scan = "Outside Scan";
public const string Inside_Scan = "Inside Scan";
}
}
namespace InspectionStation.Interfaces.IComponents
{
public interface IScanResultsDisplay
{
// Properties
List<Color> Pallete { get; set; }
// Methodsvoid
void AddSeries(string p_seriesName, int p_groupID, string p_chartArea, List<double> p_seriesXData, List<double> p_seriesYData, double p_stripLineValue = default(double));
void Clear();
}
}
namespace InspectionStation.Classes.Adapters
{
public class ChartControl_To_IScanResultsDisplay_Adapter : IScanResultsDisplay
{
// http://en.wikipedia.org/wiki/Adapter_pattern
// Fields
private Chart m_chart;
private Dictionary<int, Tuple<List<Series>, List<HorizontalLineAnnotation>>> m_displayedSeries;
// Properties
public List<Color> Pallete { get; set; }
// Constructor
public ChartControl_To_IScanResultsDisplay_Adapter(Chart p_chart, Color p_lineColour, double p_axisYInterval = 0, double p_axisXInterval = 0)
{
m_chart = p_chart;
var h = m_chart.Handle;
m_displayedSeries = new Dictionary<int, Tuple<List<Series>, List<HorizontalLineAnnotation>>>();
Pallete = new List<Color>() { Color.Black };
m_chart.Series.Clear();
m_chart.ChartAreas.Clear();
m_chart.Legends.Clear();
m_chart.ChartAreas.Add(ScanKey.Outside_Scan);
m_chart.ChartAreas.Add(ScanKey.Inside_Scan);
m_chart.ChartAreas[ScanKey.Outside_Scan].Position.X = 0;
m_chart.ChartAreas[ScanKey.Outside_Scan].Position.Y = 0;
m_chart.ChartAreas[ScanKey.Outside_Scan].Position.Width = 100;
m_chart.ChartAreas[ScanKey.Outside_Scan].Position.Height = 50;
m_chart.ChartAreas[ScanKey.Inside_Scan].Position.X = 0;
m_chart.ChartAreas[ScanKey.Inside_Scan].Position.Y = 50;
m_chart.ChartAreas[ScanKey.Inside_Scan].Position.Width = 100;
m_chart.ChartAreas[ScanKey.Inside_Scan].Position.Height = 50;
m_chart.ChartAreas[ScanKey.Outside_Scan].AxisX.LabelStyle.Enabled = false;
m_chart.ChartAreas[ScanKey.Inside_Scan].AlignWithChartArea = ScanKey.Outside_Scan;
foreach (var Area in m_chart.ChartAreas)
{
var AreaTitle = new Title(Area.Name, Docking.Top);
AreaTitle.DockedToChartArea = Area.Name;
m_chart.Titles.Add(AreaTitle);
Area.AxisY.Interval = p_axisYInterval;
Area.AxisX.Interval = p_axisXInterval;
foreach (var Axes in Area.Axes)
{
Axes.LabelAutoFitMaxFontSize = 5;
Axes.LabelAutoFitMinFontSize = 5;
Axes.IsLabelAutoFit = false;
AreaTitle.Font = Axes.LabelStyle.Font;
AreaTitle.ForeColor = p_lineColour;
Axes.LineColor = p_lineColour;
Axes.MinorGrid.LineColor = p_lineColour;
Axes.MajorGrid.LineColor = p_lineColour;
}
}
HookEvents();
}
public void HookEvents()
{
m_chart
.MouseClick += Chart_MouseClick;
}
// Event Handlers
//[jwdebug("Hardcoded values for zoomming chart control.")]
void Chart_MouseClick(object sender, MouseEventArgs e)
{
var XPos = (e.X * 100) / m_chart.Width;
var YPos = (e.Y * 100) / m_chart.Height;
//Log
// .FormattedLine(MessageScope.Integration, "Chart_MouseClick. e.Button = {0}, [X{1}, Y{2}]. m_chart.Height = {3} ", e.Button, XPos, YPos, m_chart.Height);
//foreach (var Area in m_chart.ChartAreas)
//{
// Log
// .FormattedLine(MessageScope.Integration, "Chart_MouseClick. Area.Name = {0}, [X{1}, Y{2}]. Area.Position.Height = {3} ", Area.Name, Area.Position.X, Area.Position.Y, Area.Position.Height);
//}
foreach (var Area in m_chart.ChartAreas)
{
if (e.Button == MouseButtons.Left)
{
if (XPos < 33)
{
Area.AxisX.Minimum = 0;
Area.AxisX.Maximum = 240;
}
else if (XPos > 66)
{
Area.AxisX.Minimum = 240;
Area.AxisX.Maximum = 480;
}
else
{
Area.AxisX.Minimum = 480;
Area.AxisX.Maximum = 720;
}
}
else
{
Area.AxisX.Minimum = 0;
Area.AxisX.Maximum = 720;
}
}
}
// Methods
bool flag = true;
public void AddSeries(string p_seriesName, int p_groupID, string p_chartArea, List<double> p_seriesXData, List<double> p_seriesYData, double p_limitLine = default(double))
{
Series SeriesData;
HorizontalLineAnnotation Limit;
// Get Series
if (p_seriesXData != null && p_seriesYData != null)
{
SeriesData = new Series(p_seriesName);
SeriesData.ChartArea = p_chartArea;
SeriesData.ChartType = SeriesChartType.FastLine;
SeriesData.Points.DataBindXY(p_seriesXData, p_seriesYData);
// Make X-Interval Clean numbers
if (flag)
{
// Play with these number, the hard part I suppose is calculating them programmatically.
m_chart.ChartAreas[p_chartArea].AxisX.Minimum = -2;
m_chart.ChartAreas[p_chartArea].AxisX.Interval = 0.5;
m_chart.ChartAreas[p_chartArea].AxisX.Maximum = 1;
}
}
else
SeriesData = new Series();
// Create Horizontal Line
Limit = new HorizontalLineAnnotation();
Limit.LineDashStyle = ChartDashStyle.Dash;
Limit.LineColor = Color.Magenta;
Limit.IsInfinitive = true;
Limit.ClipToChartArea = p_chartArea;
Limit.Y = p_limitLine;
m_chart
.SafeInvoke(() =>
{
// Drop off old series by group ID, except for group 0
if (p_groupID != 0)
{
// Initialize a new Group if one does not exist
if (!m_displayedSeries.ContainsKey(p_groupID))
m_displayedSeries[p_groupID] =
new Tuple<List<Series>, List<HorizontalLineAnnotation>>(
new List<Series>(),
new List<HorizontalLineAnnotation>());
// Allow as many Series to be Displayed at a time as there are Colours in the Pallete list
var Count = m_displayedSeries[p_groupID].Item1.Count;
if (Count >= Pallete.Count)
{
// Items to Remove
Series
Oldest_Series = m_displayedSeries[p_groupID].Item1[Count - Pallete.Count];
HorizontalLineAnnotation
Oldest_Limit = m_displayedSeries[p_groupID].Item2[Count - Pallete.Count];
// Remove oldest Series and Limits from Chart by Object Reference
m_chart.Series.Remove(Oldest_Series);
m_chart.Annotations.Remove(Oldest_Limit);
// Prune Obsolete References
m_displayedSeries[p_groupID].Item1.RemoveAt(0);
m_displayedSeries[p_groupID].Item2.RemoveAt(0);
}
// Add new Object References to Dictionary
m_displayedSeries[p_groupID].Item1.Add(SeriesData);
m_displayedSeries[p_groupID].Item2.Add(Limit);
// Add new Stripline to Graph
Limit.AxisY = m_chart.ChartAreas[p_chartArea].AxisY;
if (p_limitLine != default(double))
m_chart.Annotations.Add(Limit);
}
// Add new series to Graph
m_chart.Series[p_seriesName] = SeriesData;
if (p_groupID == 0)
m_chart.Series[p_seriesName].Color = Color.Black;
else
{
for (int i = 0; i < m_chart.Series.Count; i++)
m_chart.Series[i].Color = Pallete[Pallete.Count.Span(0, i)];
}
foreach (var Area in m_chart.ChartAreas)
Area.RecalculateAxesScale();
}, true);
}
public void Clear()
{
m_chart
.SafeInvoke(() =>
{
m_chart.Series.Clear();
m_chart.Annotations.Clear();
m_displayedSeries.Clear();
});
}
}
}
namespace Common.Extensions
{
public static partial class ExtensionMethods
{
/// <summary>
/// Execute a method on the control's owning thread.
/// </summary>
/// <param name="p_control">The control that is being updated.</param>
/// <param name="p_action">The method that updates uiElement.</param>
/// <param name="p_synchronous">True to force synchronous execution of
/// updater. False to allow asynchronous execution if the call is marshalled
/// from a non-GUI thread. If the method is called on the GUI thread,
/// execution is always synchronous.</param>
/// http://stackoverflow.com/q/714666
public static void SafeInvoke(this Control p_control, Action p_action, bool p_synchronous = false)
{
p_control
.CannotBeNull("p_control");
if (p_control.InvokeRequired)
{
if (p_synchronous)
p_control.Invoke((Action)delegate { SafeInvoke(p_control, p_action, p_synchronous); });
else
p_control.BeginInvoke((Action)delegate { SafeInvoke(p_control, p_action, p_synchronous); });
}
else
{
if (!p_control.IsHandleCreated)
{
// The user is responsible for ensuring that the control has a valid handle
throw
new
InvalidOperationException("SafeInvoke on \"" + p_control.Name + "\" failed because the control had no handle.");
// jwdebug
// Only manually create handles when knowingly on the GUI thread such as a Form's Constructor
// Add the line below to generate a handle http://stackoverflow.com/a/3289692/1718702
// var h = this.Handle;
}
if (p_control.IsDisposed)
throw
new
ObjectDisposedException("Control is already disposed.");
p_action.Invoke();
}
}
}
}
namespace Common.Extensions
{
public static partial class ExtensionMethods
{
/// <summary>
/// Gets the index for an array relative to an anchor point, seamlessly crossing array boundaries in either direction.
/// Returns calculated index value of an element within a collection as if the collection was a ring of contiguous elements (Ring Buffer).
/// </summary>
/// <param name="p_rollover">Index value after which the iterator should return back to zero.</param>
/// <param name="p_anchor">A fixed or variable position to offset the iteration from.</param>
/// <param name="p_offset">A fixed or variable position to offset from the anchor.</param>
/// <returns>calculated index value of an element within a collection as if the collection was a ring of contiguous elements (Ring Buffer).</returns>
public static int Span(this int p_rollover, int p_anchor, int p_offset)
{
// Prevent absolute value of `n` from being larger than count
int n = (p_anchor + p_offset) % p_rollover;
// If `n` is negative, then result is n less than rollover
if (n < 0)
n = n + p_rollover;
return n;
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
/// </summary>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentNullException"></exception>
public static void CannotBeNull(this object p_parameter, string p_name)
{
if (p_parameter == null)
throw
new
ArgumentNullException(
string.Format("Parameter \"{0}\" cannot be null.",
p_name), default(Exception));
}
}
}