结合BarChart和PointChart [英] Combine BarChart and PointChart
问题描述
我遇到了一个小问题",我想创建一个如下所示的图表:
所以基本上 系列1 =普通条形图.如果在最大时间"(系列2)系列2之前结束,则将其颜色为绿色.
尽管如此,我仍在为此而苦苦挣扎...
我的代码:
chart_TimeChart.Series.Clear();
string series_timeneeded = "Time Needed";
chart_TimeChart.Series.Add(series_timeneeded);
chart_TimeChart.Series[series_timeneeded]["PixelPointWidth"] = "5";
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Size = 10;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.IsPositionedInside = true;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Enabled = true;
chart_TimeChart.Series[series_timeneeded].BorderWidth = 2;
chart_TimeChart.Series[series_timeneeded].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_timeneeded].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[series_timeneeded].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
chart_TimeChart.Series[series_timeneeded].Points.AddXY("item"+ " " + (i + 1).ToString(), DateTime.Now.Add(Timespans[i]));
}
chart_TimeChart.Series.Add(series_FinishTime);
chart_TimeChart.Series[series_FinishTime].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_FinishTime].BorderWidth = 0;
chart_TimeChart.Series[series_FinishTime].MarkerSize = 15;
chart_TimeChart.Series[series_FinishTime].MarkerStyle = MarkerStyle.Square;
chart_TimeChart.Series[series_FinishTime].MarkerColor = Color.Black;
chart_TimeChart.Series[series_FinishTime].YValueType = ChartValueType.DateTime;
chart_TimeChart.Series[series_FinishTime].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
DateTime YPosition = GetFinishTime(i);
chart_TimeChart.Series[series_FinishTime].Points.AddXY("item"+ " " +(i+1).ToString(), YPosition);
}
但这仅在第一个序列的顶部显示第二个序列,但第一个序列不再可见.没有显示系列2的制造商,但是条形图是(尽管我将borderwidth设置为0).以我的观点/想法,我只需要使系列2的条"不可见,而只需显示系列2的标记点即可.
有什么想法吗?
更新:
string seriesname = Name+ i.ToString();
chart_TimeChart.Series.Add(seriesname);
chart_TimeChart.Series[seriesname].SetCustomProperty("DrawSideBySide", "false");
chart_TimeChart.Series[seriesname].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].ChartType = SeriesChartType.StackedBar; //Y and X are exchanged
chart_TimeChart.Series[seriesname].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[seriesname].XValueType = ChartValueType.String;
DateTime TimeNeeded = DateTime.Now.Add(List_AllLiniengroupsTimespans[k][i]);
DateTime TimeMax = GetFinishTime(k, i);
TimeSpan TimeDifference = TimeNeeded - TimeMax;
if (TimeNeeded > TimeMax) //All good
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //Time till finish
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //time left
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Red;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax > TimeNeeded) //wont make it in time
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //time till still okay
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //Time that is too much
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Green;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax == TimeNeeded) //fits exactly
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded);
chart_TimeChart.Series[seriesname].Points[0].Color = Color.DarkOrange;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
}
代码将显示为:
但是我希望它看起来像这样:
!看到下面的更新!
如果您确实要创建StackedBar图表,则该图表有两个问题:
- 如果要堆叠数据点,则需要具有有意义的x值;没有他们,怎么会知道彼此堆叠的东西?
您添加了字符串,看起来不错,但根本行不通.这是因为DataPoint.XValue
字段是double
,并且在其中添加string
时将其设置为0
!您的字符串已复制到Label
,但否则丢失.
因此,您需要提出用于x值的合适的数字值.
- 您还需要对要堆叠的系列进行分组.为此,有一个称为
StackedGroupName
的特殊属性,用于将应堆叠的序列分组.
您可以在此处使用它:
yourSeries1.SetCustomProperty("StackedGroupName", "Group1");
有关完整示例,请参见 DrawSideBySide
.
默认情况下设置为Auto
,其作用类似于True
.通常这很好,因为我们不想让条形图彼此相邻,从而有效地隐藏了全部或部分重叠点.
但是在这里,我们确实希望它们共享相同的y位置,因此我们需要将至少一个Series
的属性设置为false
;其他(在Auto
上)..
您可以这样做:
aSeries["DrawSideBySide"] = "false";
或类似这样:
aSeries.SetCustomProperty("DrawSideBySide", "false");
- 接下来,我们隐藏重叠的
Series
;这很简单:
aSeries.Color = Color.Transparent;
- 最后一个问题是显示行标记.没有这样的
NamedImage
到图表的Images
集合.
听起来比实际要复杂得多;但是MarkerImage
不会缩放,因此无论何时调整图表大小或添加/删除点时,我们都需要创建合适的大小.我暂时将忽略这种复杂性.
int pointCount = 10;
Bitmap bmp = new Bitmap(2, chart.ClientSize.Height / pointCount - 5);
using (Graphics g = Graphics.FromImage(bmp)) g.Clear(Color.Black);
NamedImage marker = new NamedImage("marker", bmp);
chart.Images.Clear(); // quick & dirty
chart.Images.Add(marker);
这是结果:
一些注意事项:
-
我建议对重复引用的所有图表元素使用变量,而不要一直使用索引引用.更少的代码,更易于阅读,更容易维护,并且可能会有更好的性能.
-
由于您的代码要求可见数据点为红色或绿色,因此
Legend
将无法很好地表示.请参阅此处绘制多色的图例项.. -
我使用了图表高度;确实不建议这样做,因为可能存在
Titles
或Legends
甚至更多的ChartAreas
;相反,您应该使用ChartArea
的高度,或者甚至更精确地使用InnerPlotPosition
的高度.您需要将其从百分比转换为像素.不太难,请参见下文或参见在这里 或此处了解更多例子! -
标记应从
Resize
事件(可能从AxisViewChanged
事件)改编.将其放在一个不错的函数中以进行调用(例如void setMarkerImage(Chart chart, Series s, string name, int width, Color c)
)总是一个好主意. -
如果需要反复调整标记图像的大小,则可能需要编写更好的代码来清除旧的代码;这应该包括处置以前使用过的
Bitmap
.
这里是一个例子:
var oldni = chart.Images.FindByName("marker");
if (oldni != null)
{
oldni.Image.Dispose();
chart.Images.Remove(oldni);
oldni.Dispose();
}
- 在某些情况下,需要轻推图表以更新其某些属性;
RecalculateAxesScale
就是这样一个微调.
计算合适的标记高度的示例:
ChartArea ca = chart.ChartAreas[0];
ca.RecalculateAxesScale();
float cah = ca.Position.Height;
float iph = ca.InnerPlotPosition.Height;
float h = chart3.ClientSize.Height * cah / 100f * iph / 100f;
int mh = (int)(h / s.Points.Count);
- 最后的注释:原始答案强调了使用有意义的x值的重要性.字符串是无用的!这对于堆叠钢筋很重要.但是现在同等重要,当我们希望钢筋位于相同的垂直位置时!将x值作为字符串添加会再次导致胡说八道.
(因为我们有Bars
,所以x值沿垂直轴,反之亦然.)
i got a Little "Problem", i want to create a Chart looking like this:
So basically Series 1 = Normal bar Chart. Color green if it Ends before the "time max" (series2) Series 2 = just a DataPoint / Marker on top of series 1 items.
I am struggling with this though...
my Code:
chart_TimeChart.Series.Clear();
string series_timeneeded = "Time Needed";
chart_TimeChart.Series.Add(series_timeneeded);
chart_TimeChart.Series[series_timeneeded]["PixelPointWidth"] = "5";
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Size = 10;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.IsPositionedInside = true;
chart_TimeChart.ChartAreas[0].AxisY.ScrollBar.Enabled = true;
chart_TimeChart.Series[series_timeneeded].BorderWidth = 2;
chart_TimeChart.Series[series_timeneeded].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_timeneeded].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[series_timeneeded].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
chart_TimeChart.Series[series_timeneeded].Points.AddXY("item"+ " " + (i + 1).ToString(), DateTime.Now.Add(Timespans[i]));
}
chart_TimeChart.Series.Add(series_FinishTime);
chart_TimeChart.Series[series_FinishTime].ChartType = SeriesChartType.StackedBar;
chart_TimeChart.Series[series_FinishTime].BorderWidth = 0;
chart_TimeChart.Series[series_FinishTime].MarkerSize = 15;
chart_TimeChart.Series[series_FinishTime].MarkerStyle = MarkerStyle.Square;
chart_TimeChart.Series[series_FinishTime].MarkerColor = Color.Black;
chart_TimeChart.Series[series_FinishTime].YValueType = ChartValueType.DateTime;
chart_TimeChart.Series[series_FinishTime].XValueType = ChartValueType.String;
for (int i = 0; i < MaxNumber; i++)
{
DateTime YPosition = GetFinishTime(i);
chart_TimeChart.Series[series_FinishTime].Points.AddXY("item"+ " " +(i+1).ToString(), YPosition);
}
but this only Displays the 2nd series on top of the first one but the first one isnt visible anymore. The Maker of series 2 isnt shown but instead the bar is (eventhough i made borderwidth to 0). In my opinion/thinking i just have to make the "bar" of series 2 invisible and just Show the marker Points for series 2.
Any ideas?
Update:
string seriesname = Name+ i.ToString();
chart_TimeChart.Series.Add(seriesname);
chart_TimeChart.Series[seriesname].SetCustomProperty("DrawSideBySide", "false");
chart_TimeChart.Series[seriesname].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].ChartType = SeriesChartType.StackedBar; //Y and X are exchanged
chart_TimeChart.Series[seriesname].YValueType = ChartValueType.Time;
chart_TimeChart.ChartAreas[0].AxisY.LabelStyle.Format = "HH:mm:ss";
chart_TimeChart.Series[seriesname].XValueType = ChartValueType.String;
DateTime TimeNeeded = DateTime.Now.Add(List_AllLiniengroupsTimespans[k][i]);
DateTime TimeMax = GetFinishTime(k, i);
TimeSpan TimeDifference = TimeNeeded - TimeMax;
if (TimeNeeded > TimeMax) //All good
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //Time till finish
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //time left
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Red;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax > TimeNeeded) //wont make it in time
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded); //time till still okay
chart_TimeChart.Series[seriesname].Points[0].Color = Color.Blue;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded.Add(TimeDifference)); //Time that is too much
chart_TimeChart.Series[seriesname].Points[1].Color = Color.Green;
chart_TimeChart.Series[seriesname].Points[1].SetCustomProperty("StackedGroupName", seriesname);
}
else if (TimeMax == TimeNeeded) //fits exactly
{
chart_TimeChart.Series[seriesname].Points.AddXY(seriesname, TimeNeeded);
chart_TimeChart.Series[seriesname].Points[0].Color = Color.DarkOrange;
chart_TimeChart.Series[seriesname].Points[0].SetCustomProperty("StackedGroupName", seriesname);
}
the Code will be displayed as:
but i want it to look like this:
!! See the update below !!
If you really want to create a StackedBar chart, your chart has two issues:
- If you want to stack datapoints they need to have meaningful x-values; without them how can it know what to stack on each other?
You add strings, which look fine but simply don't work. That is because the DataPoint.XValue
field is double
and when you add string
into it it is set to 0
!! Your string is copied to the Label
but otherwise lost.
So you need to come up with a suitable numeric value you use for the x-values..
- And you also need to group the series you want to stack. For this there is a special property called
StackedGroupName
which serves to group those series that shall be stacked.
Here is how you can use it:
yourSeries1.SetCustomProperty("StackedGroupName", "Group1");
For a full example see this post !
It also shows one way of setting the Labels with string values of your choice..
This is the way to go for real StackedBar
charts. Your workaround may or may not work. You could try to make the colors transparent or equal to the chart's backcolor; but it won't be more than a hack, imo.
Update
I guess I have misread the question. From what I see you do not really want to create a stacked chart.
Instead you struggle with these issues:
- displaying bars at the same y-spot
- making some bars invisible
- displaying a vertical line as a marker
Let's tackle each:
- Some column types including all
Bars
,Columns
and then some have a little known special property calledDrawSideBySide
.
By default is is set to Auto
, which will work like True
. This is usually fine as we don't want bars to sit upon each other, effectively hiding all or part of the overlaid points.
But here we do want them to share the same y-position, so we need to set the property to false
for at least one Series
; the others (on Auto
) will follow..:
You can do it either like this:
aSeries["DrawSideBySide"] = "false";
or like this:
aSeries.SetCustomProperty("DrawSideBySide", "false");
- Next we hide the overlaid
Series
; this is simple:
aSeries.Color = Color.Transparent;
- The last issue is displaying a line marker. There is no such
MarkerStyle
, so we need to use a custom style. For this we need to create a suitable bitmap and add it as aNamedImage
to the chart'sImages
collection.
This sounds more complicated than it is; however the MarkerImage
will not be scaled, so we need to created suitable sizes whenever we resize the Chart or add/remove points. I will ignore this complication for now..
int pointCount = 10;
Bitmap bmp = new Bitmap(2, chart.ClientSize.Height / pointCount - 5);
using (Graphics g = Graphics.FromImage(bmp)) g.Clear(Color.Black);
NamedImage marker = new NamedImage("marker", bmp);
chart.Images.Clear(); // quick & dirty
chart.Images.Add(marker);
Here is the result:
A few notes:
I would recommend to use variables for all chart elements you refer to repeatedly instead of using indexed references all the time. Less code, easier to read, a lot easier to maintain, and probably better performance.
Since your code called for the visible datapoints to be either red or green the
Legend
will not show a good representation. See here for an example of drawing a multi-colored legend item..I used the chart height; this is not really recommended as there may be
Titles
orLegends
or even moreChartAreas
; instead you should use the height of theChartArea
, or even more precise, the height of theInnerPlotPosition
. You would need to convert those from percentages to pixels. Not too hard, see below or see here or here for more examples!The markers should be adapted from the
Resize
and probably from theAxisViewChanged
events. Putting it in a nice function to call (e.g.void setMarkerImage(Chart chart, Series s, string name, int width, Color c)
) is always a good idea.If you need to adapt the size of the marker image repeatedly, you may want to write better code for clearing the old one; this should include disposing of the
Bitmap
that was used before..
Here is an example:
var oldni = chart.Images.FindByName("marker");
if (oldni != null)
{
oldni.Image.Dispose();
chart.Images.Remove(oldni);
oldni.Dispose();
}
- In some situations one needs to nudge the Chart to update some of its properties;
RecalculateAxesScale
is one such nudge.
Example for calculating a suitable marker height:
ChartArea ca = chart.ChartAreas[0];
ca.RecalculateAxesScale();
float cah = ca.Position.Height;
float iph = ca.InnerPlotPosition.Height;
float h = chart3.ClientSize.Height * cah / 100f * iph / 100f;
int mh = (int)(h / s.Points.Count);
- Final note: The original answer stressed the importance of using meaningful x-values. Strings are useless! This was important for stacking bars; but it is equally important now, when we want bars to sit at the same vertical positions! Adding the x-values as strings is again resulting in nonsense..
(Since we have Bars
the x-values go along the vertical axis and vice versa..)
这篇关于结合BarChart和PointChart的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!