【发布时间】:2016-07-20 10:50:46
【问题描述】:
我有一张要在其上绘制热图的图表;我拥有的唯一数据是湿度和温度,它们代表图表中的一个点。
如何在c#中获取图表上矩形类型的热图?
我想要的类似于下图:
我真正想要的是图表中的一个矩形区域,该区域根据我从点列表中获得的点以不同颜色绘制,并形成图表中的彩色部分。
【问题讨论】:
-
问题是,你不知道你的问题是什么。你有代码吗?为什么它不起作用?请阅读faq 和How to Ask。
我有一张要在其上绘制热图的图表;我拥有的唯一数据是湿度和温度,它们代表图表中的一个点。
如何在c#中获取图表上矩形类型的热图?
我想要的类似于下图:
我真正想要的是图表中的一个矩形区域,该区域根据我从点列表中获得的点以不同颜色绘制,并形成图表中的彩色部分。
【问题讨论】:
您可以选择至少三种方法来创建带有彩色矩形的图表,这些矩形构成热图。
这是一个example
使用/滥用DataGridView。虽然我不建议这样做,但该帖子包含一个有用的功能,可以创建漂亮的颜色列表以在您的任务中使用。
然后有使用GDI+方法绘制图表的选项,即Graphics.FillRectangle。这一点都不难,但是一旦您想获得图表控件提供的那些不错的附加功能,例如缩放、轴、工具提示等,这些工作就会加起来......见下文!
让我们看看选项三:使用来自DataVisualization 命名空间的Chart 控件。
我们首先假设您已经创建了一个颜色列表:
List<Color> colorList = new List<Color>();
并且您已设法将数据投影到指向颜色列表的 int 索引的 2D 数组:
int[,] coloredData = null;
接下来你必须为你的Series S1 选择一个ChartType 真的只有我能想到的有帮助:
S1.ChartType = SeriesChartType.Point;
积分由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];
}
这需要一些解释:要设置不在磁盘路径上的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));
}
}
我们希望所有标记的大小至少大致正确;因此,每当该尺寸发生变化时,我们都会重新设置它:
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);
}
这并不关心像InnerPlotPosition 这样的细节,即实际要绘制的区域;所以这里有一些改进的空间..!
我们在设置图表时调用它,并且在调整大小时调用它:
private void chart1_Resize(object sender, EventArgs e)
{
setMarkerSize(chart1);
createMarkers(chart1, 100);
}
让我们使用一些廉价的测试数据来看看结果:
如您所见,调整大小可以正常工作..
这是设置我的示例的完整代码:
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];
}
}
关于限制的几点说明:
该点将始终位于任何网格线的顶部。如果您真的需要这些,则必须在 Paint 事件之一中将它们绘制在顶部。
显示的标签是指数据数组的整数索引。如果要显示原始数据,一种方法是将CustomLabels 添加到坐标区。请参阅here for an example!
这应该让您了解使用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;
}
生成的位图看起来很熟悉:
这很简单;但是将所有额外内容添加到填充保留的空间中并不容易..
【讨论】: