C# c语言中可缩放、可打印、可滚动的列车运行图#
我需要用C#构建一个图形化的列车时刻表可视化工具。实际上,我必须用C#重建。 图形必须可缩放、可滚动、可打印/导出为带有矢量图形元素的PDF 你能给我一些提示吗?我该怎么开始呢?我应该使用什么样的库 是否值得尝试使用OxyPlot这样的图形库?也许它不是最好的,因为特殊的轴和不规则的网格-我认为。你怎么看C# c语言中可缩放、可打印、可滚动的列车运行图#,c#,plot,charts,vector-graphics,graphing,C#,Plot,Charts,Vector Graphics,Graphing,我需要用C#构建一个图形化的列车时刻表可视化工具。实际上,我必须用C#重建。 图形必须可缩放、可滚动、可打印/导出为带有矢量图形元素的PDF 你能给我一些提示吗?我该怎么开始呢?我应该使用什么样的库 是否值得尝试使用OxyPlot这样的图形库?也许它不是最好的,因为特殊的轴和不规则的网格-我认为。你怎么看 无论您使用哪种图表工具,一旦需要特殊类型的显示,您总是需要添加一些额外的编码 下面是使用MSChart控件的示例 请注意,它对导出矢量格式有限制: 可输出到各种类型,包括3种类型;然而,只有一
无论您使用哪种图表工具,一旦需要特殊类型的显示,您总是需要添加一些额外的编码 下面是使用
MSChart
控件的示例
请注意,它对导出矢量格式有限制:
可输出到各种类型,包括3种类型;然而,只有一些应用程序可以实际使用这些。不确定您使用的PDF库
如果您不能使用Emf
格式,您可以通过将图表
控件设置得非常大,导出到Png
,然后将Dpi
分辨率设置得比保存后的默认屏幕分辨率大得多,从而获得很好的结果。。对于大多数pdf使用,将其设置为600
或1200dpi
现在让我们看一个例子:
请注意:
- 我在许多方面使我的生活变得更轻松。我只为一个方向编码,我没有反转公鸡,所以它只从下到上
- 我没有使用真实的数据,而是虚构出来的
- 我没有创建一个或多个类来保存站点数据;相反,我使用了一个非常简单的
元组
- 我没有创建一个
数据表来保存列车数据。相反,我把它们拼凑起来,并动态地添加到图表中
- 我没有测试,但缩放和滚动也应该可以
列表
:
// station name, distance, type: 0=terminal, 1=normal, 2=main station
List<Tuple<string, double, int>> TrainStops = null;
创建具有随机距离的桩号列表也非常简单。我做了第一个和最后一个终端,每五个做一个主站
public List<Tuple<string, double, int>> SetupTrainStops(int count)
{
var stops = new List<Tuple<string, double, int>>();
Random rnd = new Random(count);
for (int i = 0; i < count; i++)
{
string n = (char)(i+(byte)'A') + "-Street";
double d = 1 + rnd.Next(3) + rnd.Next(4) + rnd.Next(5) / 10d;
if (d < 3) d = 3; // a minimum distance so the label won't touch
int t = (i == 0 | i == count-1) ? 0 : rnd.Next(5)==0 ? 2 : 1;
var ts = new Tuple<string, double, int>(n, d, t);
stops.Add(ts);
}
return stops;
}
注意轴的ValueToPixelPosition
转换功能的使用
现在进入最后一部分:如何添加列车数据的系列
:
public void AddTrainStopSeries(Chart chart, DateTime start, int count, int speed)
{
Series s = chart.Series.Add(start.ToShortTimeString());
s.ChartType = SeriesChartType.Line;
s.Color = speed == 0 ? Color.Black : Color.Brown;
s.MarkerStyle = MarkerStyle.Circle;
s.MarkerSize = 4;
double totalDist = 0;
DateTime ct = start;
for (int i = 0; i < count; i++)
{
var ts = TrainStops[i];
ct = ct.AddMinutes(ts.Item2 * (speed == 0 ? 1 : 1.1d));
DataPoint dp = new DataPoint( ct.ToOADate(), totalDist );
totalDist += TrainStops[i].Item2;
s.Points.Add(dp);
}
}
当然,对于健壮的应用程序,您不会依赖于神奇的颜色;-)
相反,您可以将标记
对象添加到系列
,以保存各种列车信息
更新:正如您注意到绘制的网格线
覆盖了标记
。您可以在此处插入这段代码(**);它将在xxxPaint
事件结束时绘制标记
int w = chart1.Series[0].MarkerSize;
foreach(Series s in chart1.Series)
foreach(DataPoint dp in s.Points)
{
int x = (int) ax.ValueToPixelPosition(dp.XValue) - w / 2;
int y = (int) ay.ValueToPixelPosition(dp.YValues[0])- w / 2;
using (SolidBrush b = new SolidBrush(dp.Color))
e.ChartGraphics.Graphics.FillEllipse(b, x, y, w, w);
}
特写:
MSChart只能导出一些EMF矢量格式(和png),并且必须为垂直网格使用自定义标签和所有者绘制的网格。除此之外,我认为它可以完成这项工作……哇,亲爱的TaW,非常感谢您提供的详细解决方案和解释!这比我希望的要多得多。你为我最重要的问题提出了解决方案:不规则的Y轴和网格,它工作正常!;)同时,我在(PlotModel TrainSchedule)中发现了一个针对这种情况的尝试,但它也只包含一个常规的Y轴,没有站点名称。MSChart似乎足够了,而且有很好的文档记录。酷!再次感谢,我稍后会发布结果。这样,即使在预绘制或后绘制时,后绘制的网格线也会出现在标记()的前面。有没有办法把他们送到标记后面?我应该把它们移到背景图像还是有更简单的方法?啊,有趣!背景图像并不是一个好主意,毕竟图表需要是动态的。我已经添加了绘制标记的代码。。感谢更新!:)这个问题也出现在火车经过车站的情况下():在这种情况下,线路被网格线覆盖。那么我应该重新绘制(扩展标记代码)网格线附近图表的所有部分吗?嗯,不确定你指的是哪一部分。在y轴上稍微交叉的部分?第二张截图不是这里的样子。你在正确的位置添加代码了吗??
public void SetupTrainStopAxis(Chart chart)
{
Axis ay = chart.ChartAreas[0].AxisY;
ay.LabelStyle.Font = new Font("Consolas", 8f);
double totalDist = 0;
for (int i = 0; i < TrainStops.Count; i++)
{
CustomLabel cl = new CustomLabel();
cl.Text = TrainStops[i].Item1;
cl.FromPosition = totalDist - 0.1d;
cl.ToPosition = totalDist + 0.1d;
totalDist += TrainStops[i].Item2;
cl.ForeColor = TrainStops[i].Item3 == 1 ? Color.DimGray : Color.Black;
ay.CustomLabels.Add(cl);
}
ay.Minimum = 0;
ay.Maximum = totalDist;
ay.MajorGrid.Enabled = false;
ay.MajorTickMark.Enabled = false;
}
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
Axis ay = chart1.ChartAreas[0].AxisY;
Axis ax = chart1.ChartAreas[0].AxisX;
int x0 = (int) ax.ValueToPixelPosition(ax.Minimum);
int x1 = (int) ax.ValueToPixelPosition(ax.Maximum);
double totalDist = 0;
foreach (var ts in TrainStops)
{
int y = (int)ay.ValueToPixelPosition(totalDist);
totalDist += ts.Item2;
using (Pen p = new Pen(ts.Item3 == 1 ? Color.DarkGray : Color.Black,
ts.Item3 == 1 ? 0.5f : 1f))
e.ChartGraphics.Graphics.DrawLine(p, x0 + 1, y, x1, y);
}
// ** Insert marker drawing code (from update below) here !
}
public void AddTrainStopSeries(Chart chart, DateTime start, int count, int speed)
{
Series s = chart.Series.Add(start.ToShortTimeString());
s.ChartType = SeriesChartType.Line;
s.Color = speed == 0 ? Color.Black : Color.Brown;
s.MarkerStyle = MarkerStyle.Circle;
s.MarkerSize = 4;
double totalDist = 0;
DateTime ct = start;
for (int i = 0; i < count; i++)
{
var ts = TrainStops[i];
ct = ct.AddMinutes(ts.Item2 * (speed == 0 ? 1 : 1.1d));
DataPoint dp = new DataPoint( ct.ToOADate(), totalDist );
totalDist += TrainStops[i].Item2;
s.Points.Add(dp);
}
}
private void cbx_ShowOnlyFastTrains_CheckedChanged(object sender, EventArgs e)
{
foreach (Series s in chart1.Series)
s.Enabled = !cbx_ShowOnlyFastTrains.Checked || s.Color == Color.Brown;
}
int w = chart1.Series[0].MarkerSize;
foreach(Series s in chart1.Series)
foreach(DataPoint dp in s.Points)
{
int x = (int) ax.ValueToPixelPosition(dp.XValue) - w / 2;
int y = (int) ay.ValueToPixelPosition(dp.YValues[0])- w / 2;
using (SolidBrush b = new SolidBrush(dp.Color))
e.ChartGraphics.Graphics.FillEllipse(b, x, y, w, w);
}