绘制2D热图

Lit*_*der 4 c# charts heatmap

我有一张图表,我想要绘制热图; 我所拥有的唯一数据是湿度和温度,它代表图表中的一个点.

如何在c#中获取图表上的矩形热图?

我想要的是类似于下图:

在此输入图像描述

我真正想要的是图表中的一个矩形区域,它根据我从点列表中得到的点以不同的颜色绘制,并在图表中形成彩色部分.

TaW*_*TaW 9

您可以选择至少三种方法来创建具有组成热图的彩色矩形的图表.

这是一个 使用/滥用a的例子DataGridView.虽然我不建议这样做,但帖子包含一个有用的功能,可以创建在任务中使用的漂亮的颜色列表.

然后可以选择使用GDI +方法绘制图表,即Graphics.FillRectangle.这一点并不难,但是一旦你想获得Chart控件提供的那些漂亮的附加功能,比如缩放,轴,工具提示等,工作就会增加..见下文!

那么让我们看一下选项三:使用命名空间中的Chart控件DataVisualization.

我们首先假设您已经创建了一个颜色列表:

List<Color> colorList = new List<Color>();
Run Code Online (Sandbox Code Playgroud)

并且您已设法将数据投影到指向颜色列表的2D索引数组:

int[,] coloredData = null;
Run Code Online (Sandbox Code Playgroud)

接下来你必须选择一个ChartTypeSeries S1真的只有一个我能想到的会有所帮助:

S1.ChartType = SeriesChartType.Point;
Run Code Online (Sandbox Code Playgroud)

点数显示为Markers.我们希望它DataPoints不会真正显示为标准MarkerTypes之一.

Square如果我们想要显示正方形,那就没关系; 但对于矩形,它将无法正常工作:即使我们让它们重叠,仍然会在边界处有不同大小的点,因为它们没有完全重叠.

因此,我们通过将每个点设置为合适大小和颜色的位图来使用自定义标记.MarkerImage

这是一个循环,它添加DataPoints到我们的Series并设置每个有一个MarkerImage:

for (int x = 1; x < coloredData.GetLength(0); x++)
    for (int y = 1; y < coloredData.GetLength(1); y++)
    {
        int pt = S1.Points.AddXY(x, y);
        S1.Points[pt].MarkerImage = "NI" +  coloredData[x,y];

    }
Run Code Online (Sandbox Code Playgroud)

这需要一些解释:要设置MarkerImage不在磁盘上的路径,它必须驻留在Chart's Images集合中.这意味着需要是类型NamedImage.任何图像都可以,但它必须添加一个唯一的名称字符串来识别它NamedImagesCollection.我选择的名字是'NI1','NI2'..

显然我们需要创建所有这些图像; 这是一个功能:

void createMarkers(Chart chart, int count)
{
    // rough calculation:
    int sw = chart.ClientSize.Width / coloredData.GetLength(0);
    int sh = chart.ClientSize.Height / coloredData.GetLength(1);

    // clean up previous images:
    foreach(NamedImage ni in chart1.Images) ni.Dispose();
    chart.Images.Clear();

    // now create count images:
    for (int i = 0; i < count; i++)
    {
        Bitmap bmp = new Bitmap(sw, sh);
        using (Graphics G = Graphics.FromImage(bmp))
            G.Clear(colorList[i]);
        chart.Images.Add(new NamedImage("NI" + i, bmp));
    }
}
Run Code Online (Sandbox Code Playgroud)

我们希望所有标记至少具有大致合适的尺寸; 所以每当这个尺寸改变时,我们再次设置它:

void setMarkerSize(Chart chart)
{
    int sx = chart1.ClientSize.Width / coloredData.GetLength(0);
    int sy = chart1.ClientSize.Height / coloredData.GetLength(1);
    chart1.Series["S1"].MarkerSize = (int)Math.Max(sx, sy);
}
Run Code Online (Sandbox Code Playgroud)

这并不关心细节InnerPlotPosition,即要绘制的实际区域; 所以这里有一些细化的空间..!

我们在设置图表时调用它,但也在调整大小时调用:

private void chart1_Resize(object sender, EventArgs e)
{
    setMarkerSize(chart1);
    createMarkers(chart1, 100);
}
Run Code Online (Sandbox Code Playgroud)

让我们看一下使用一些便宜的testdata的结果:

在此输入图像描述在此输入图像描述

你可以看到调整大小工作正常..

以下是设置我的示例的完整代码:

private void button6_Click(object sender, EventArgs e)
{
    List<Color> stopColors = new List<Color>()
    { Color.Blue, Color.Cyan, Color.YellowGreen, Color.Orange, Color.Red };
    colorList = interpolateColors(stopColors, 100);

    coloredData = getCData(32, 24);
    // basic setup..
    chart1.ChartAreas.Clear();
    ChartArea CA = chart1.ChartAreas.Add("CA");
    chart1.Series.Clear();
    Series S1 = chart1.Series.Add("S1");
    chart1.Legends.Clear();
    // we choose a charttype that lets us add points freely:
    S1.ChartType = SeriesChartType.Point;

    Size sz = chart1.ClientSize;

    // we need to make the markers large enough to fill the area completely:
    setMarkerSize(chart1);
    createMarkers(chart1, 100);

    // now we fill in the datapoints
    for (int x = 1; x < coloredData.GetLength(0); x++)
        for (int y = 1; y < coloredData.GetLength(1); y++)
        {
            int pt = S1.Points.AddXY(x, y);
            //  S1.Points[pt].Color = coloredData[x, y];

            S1.Points[pt].MarkerImage = "NI" +  coloredData[x,y];
        }
}
Run Code Online (Sandbox Code Playgroud)

关于限制的一些注意事项:

  • 该点始终位于任何网格线的顶部.如果你真的需要那些,你将不得不在其中一个Paint事件中将它们绘制在顶部.

  • 所示的标签指的是数据数组的整数索引.如果要显示原始数据,可以选择添加CustomLabels到轴中.有关示例,请参见此处!

这应该让你知道你可以用Chart控件做什么; 在这里完成你的困惑是如何使用相同的颜色和数据在GDI +中绘制这些矩形:

Bitmap getChartImg(float[,] data, Size sz, Padding pad) 
{
    Bitmap bmp = new Bitmap(sz.Width , sz.Height);
    using (Graphics G = Graphics.FromImage(bmp))
    {
        float w = 1f * (sz.Width - pad.Left - pad.Right) / coloredData.GetLength(0);
        float h = 1f * (sz.Height - pad.Top - pad.Bottom) / coloredData.GetLength(1);
        for (int x = 0; x < coloredData.GetLength(0); x++)
            for (int y = 0; y < coloredData.GetLength(1); y++)
            {
                using (SolidBrush brush = new SolidBrush(colorList[coloredData[x,y]]))
                    G.FillRectangle(brush, pad.Left + x * w, y * h - pad.Bottom, w, h);
            }

    }
    return bmp;
}
Run Code Online (Sandbox Code Playgroud)

生成的Bitmap看起来很熟悉:

在此输入图像描述

那很简单; 但是将所有额外内容添加到填充所保留的空间中并不是那么容易.