C# c语言中可缩放、可打印、可滚动的列车运行图#

C# c语言中可缩放、可打印、可滚动的列车运行图#,c#,plot,charts,vector-graphics,graphing,C#,Plot,Charts,Vector Graphics,Graphing,我需要用C#构建一个图形化的列车时刻表可视化工具。实际上,我必须用C#重建。 图形必须可缩放、可滚动、可打印/导出为带有矢量图形元素的PDF 你能给我一些提示吗?我该怎么开始呢?我应该使用什么样的库 是否值得尝试使用OxyPlot这样的图形库?也许它不是最好的,因为特殊的轴和不规则的网格-我认为。你怎么看 无论您使用哪种图表工具,一旦需要特殊类型的显示,您总是需要添加一些额外的编码 下面是使用MSChart控件的示例 请注意,它对导出矢量格式有限制: 可输出到各种类型,包括3种类型;然而,只有一

我需要用C#构建一个图形化的列车时刻表可视化工具。实际上,我必须用C#重建。 图形必须可缩放、可滚动、可打印/导出为带有矢量图形元素的PDF

你能给我一些提示吗?我该怎么开始呢?我应该使用什么样的库

是否值得尝试使用OxyPlot这样的图形库?也许它不是最好的,因为特殊的轴和不规则的网格-我认为。你怎么看


无论您使用哪种图表工具,一旦需要特殊类型的显示,您总是需要添加一些额外的编码

下面是使用
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);
}