Broken line chart in Microsoft Chart Control - c#

Is it possible to create a broken line chart with the Microsoft chart control?
Similar to this:
Preferably using the same series.

Yes, it's possible. You can use the DataPoint.IsEmpty property to indicate blank points.
Sample code:
Series series = new Series("sample") { ChartType = SeriesChartType.Line, BorderWidth = 2, MarkerSize = 5, MarkerStyle = MarkerStyle.Square };
series.Points.Add(new DataPoint(0, 1));
series.Points.Add(new DataPoint(1, 1));
series.Points.Add(new DataPoint(1.5, double.NaN) { IsEmpty = true });
series.Points.Add(new DataPoint(2, 1));
series.Points.Add(new DataPoint(3, 2));
chart.Series.Add(series);

Related

Misalignment of two chart areas

I was trying to overlap two chart areas. They would share the same x values, but Y would have different values and scales.
Here is outcome of my code:
As you can see red series is not in alignment with green series.I was searching this site for answers, but couldn't find one that worked. Could someone explain me why they don't align?
Code:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace TestGraph
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
#region Data
// Creating first series
Series s1 = new Series();
s1.Name = "Values";
s1.ChartType = SeriesChartType.Column;
s1.XValueType = ChartValueType.DateTime;
s1.Color = Color.Green;
s1.BorderWidth = 2;
// Hard Coding test values
DataPoint[] values =
{
new DataPoint(new DateTime(2017, 8, 1).ToOADate(), 10),
new DataPoint(new DateTime(2017, 8, 2).ToOADate(), 11),
new DataPoint(new DateTime(2017, 8, 3).ToOADate(), 12),
new DataPoint(new DateTime(2017, 8, 4).ToOADate(), 13),
};
// Adding vales to s1
foreach (DataPoint p in values)
{
s1.Points.Add(p);
}
// Creating second series
Series s2 = new Series();
s2.Name = "Values 2";
s2.ChartType = SeriesChartType.Column;
s2.XValueType = ChartValueType.DateTime;
s2.Color = Color.Red;
s2.BorderWidth = 2;
// Hard Coding test values
DataPoint[] values2 =
{
new DataPoint(new DateTime(2017, 8, 1).ToOADate(), 0.1),
new DataPoint(new DateTime(2017, 8, 2).ToOADate(), -0.2),
new DataPoint(new DateTime(2017, 8, 3).ToOADate(), -0.7),
new DataPoint(new DateTime(2017, 8, 4).ToOADate(), 13),
};
// Adding vales to s2
foreach (DataPoint p in values2)
{
s2.Points.Add(p);
}
#endregion
#region Charts
// Initializing chart
Chart mainChart = new Chart();
ChartArea area = new ChartArea();
ChartArea area2 = new ChartArea();
Controls.Add(mainChart);
mainChart.Dock = DockStyle.Fill;
// Adding areas to mainChart
mainChart.ChartAreas.Add(area);
mainChart.ChartAreas.Add(area2);
// Adding series to areas
s1.ChartArea = area.Name;
s2.ChartArea = area2.Name;
mainChart.Series.Add(s1);
mainChart.Series.Add(s2);
// Aligning areas
// Overlapping area2 with area
area2.AlignmentStyle = AreaAlignmentStyles.All;
area2.AlignmentOrientation = AreaAlignmentOrientations.All;
area2.AlignWithChartArea = area.Name;
// Scale actualization
area2.RecalculateAxesScale();
area.RecalculateAxesScale();
// Defining Y scale
area2.AxisY.Maximum = 2;
area2.AxisY.Minimum = -2;
area2.BackColor = Color.Transparent;
// Disabling unnecessary graphics
area2.BackGradientStyle = GradientStyle.None;
area2.AxisX.IsMarginVisible = false;
area2.AxisX.LabelStyle.Enabled = false;
area2.AxisY.LabelStyle.Enabled = false;
area2.AxisX.Enabled = AxisEnabled.False;
area2.AxisY.Enabled = AxisEnabled.False;
// Resizing chart back to 100%
area.Position = new ElementPosition(0, 0, 100, 100);
#endregion
}
}
}
One of your chart has AxisX.IsMarginVisible set to true, the other to false, hence the mismatch.
However, if you are trying to plot overlapping series, why are you not adding them to the same chart area instead of going through all this trouble?

C#, DataVisualization.Charting: assign custom labels to values on axes

In C# I am using the DataVisualization.Charting library for plotting.
In a simple line graph, I would like to show some custom text on the x-axis on positions x=0, 1, 2, 3.
Something like this (in matplotlib, though):
This is the documentation for Axis class, but I'm not sure what I should look for.
Use the AxisLabel property of DataPoint. Description of AxisLabel property:
Gets or sets the text of the X-axis label for the data point, series
or an empty point. This property is only used if a custom label has
not been specified for the relevant Axis object.
So your code can look like this:
DataPoint dp1 = new DataPoint(1, 1);
dp1.AxisLabel = "Frogs";
DataPoint dp2 = new DataPoint(2, 4);
dp2.AxisLabel = "Hogs";
DataPoint dp3 = new DataPoint(3, 9);
dp3.AxisLabel = "Bogs";
DataPoint dp4 = new DataPoint(4, 6);
dp4.AxisLabel = "Slogs";
chart1.Series[0].Points.Add(dp1);
chart1.Series[0].Points.Add(dp2);
chart1.Series[0].Points.Add(dp3);
chart1.Series[0].Points.Add(dp4);
Or you can enforce 1=Frogs, 2=Hogs, 3=Bogs and 4=Slogs with following code:
chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
chart1.Series[0].MarkerStyle = MarkerStyle.Circle;
chart1.Series[0].MarkerBorderColor = System.Drawing.Color.Black;
chart1.Series[0].MarkerColor = System.Drawing.Color.Red;
chart1.Series[0].Points.AddXY(1, 1);
chart1.Series[0].Points.AddXY(2, 4);
chart1.Series[0].Points.AddXY(3, 9);
chart1.Series[0].Points.AddXY(4, 6);
foreach (DataPoint dp in chart1.Series[0].Points)
{
switch ((int)dp.XValue)
{
case 1: dp.AxisLabel = "Frogs"; break;
case 2: dp.AxisLabel = "Hogs"; break;
case 3: dp.AxisLabel = "Bogs"; break;
case 4: dp.AxisLabel = "Slogs"; break;
}
}
To achieve really the same as on the picture - axes on both sides - you would need to use the following trick.
It works for X axis only, for Y axis you can add custom labels as described here.
You can also use labels of datapoints which appear inside the chart using IsValueShownAsLabel.
Just add your points with those labels as the x values:
chart1.Series[0].Points.AddXY("Frogs", 1);
chart1.Series[0].Points.AddXY("Hogs", 4);
// etc
Or, you could databind the points using two arrays:
string[] xvalues = new [] {"Frogs", "Hogs", "Bogs", "Slogs"};
int[] yvalues = new [] {1, 4, 9, 6};
chart1.Series[0].Points.DataBindXY(xvalues, yvalues);
You need to set the "Format" property of the LabelStyle on your axis.
See: http://msdn.microsoft.com/en-us/library/system.web.ui.datavisualization.charting.labelstyle(v=vs.110).aspx
There's a few reasonable tutorials on using the charting library out there if you search for them. Here's one: http://weblogs.asp.net/dwahlin/getting-started-with-the-asp-net-3-5-chart-control

MS Chart Control: Drawing and Labeling Line Series Across the Chart Area

I have questions about the MS ASP.NET chart control.
How can a line series be set over a bar series so that it extends to the y-axis of a chart?
Is it possible to place the name of the line series, i.e. "Goal", to the right of the chart as a replacement to including this series in the legend?
As you can see in the screenshot below, I have a line series presenting on top of a bar series that doesn't extend to the y-axis of the chart.
The code is as follows:
var data1 = new Dictionary<string, float>
{
{ "W1", 80},
{ "W2", 60},
{ "W3", 40},
{ "W4", 20},
{ "W5", 10}
};
var data2 = new Dictionary<string, float>
{
{ "W1", 10},
{ "W2", 10},
{ "W3", 0},
{ "W4", 10},
{ "W5", 10}
};
var data3 = new Dictionary<string, float>
{
{ "W1", 10},
{ "W2", 30},
{ "W3", 50},
{ "W4", 70},
{ "W5", 80}
};
var data4 = new Dictionary<string, float>
{
{ "W1", 50},
{ "W2", 50},
{ "W3", 50},
{ "W4", 50},
{ "W5", 50}
};
var chart = new Chart();
chart.Height = Unit.Pixel(300);
chart.Width = Unit.Pixel(450);
chart.Legends.Add("Legend").Alignment = StringAlignment.Center;
chart.Palette = ChartColorPalette.None;
chart.PaletteCustomColors = new Color[] { Color.FromArgb(191, 214, 151), Color.FromArgb(249, 255, 149), Color.FromArgb(191, 79, 75), Color.Green };
var area = new ChartArea();
area.AxisX.MajorGrid.LineColor = Color.Transparent;
chart.ChartAreas.Add(area);
var series1 = new Series("Done");
foreach (var item in data1)
{
series1.Points.AddXY(item.Key, item.Value);
}
series1.MarkerBorderWidth = 1;
var series2 = new Series("In Progress");
foreach (var item in data2)
{
series2.Points.AddXY(item.Key, item.Value);
}
var series3 = new Series("Needs Review");
foreach (var item in data3)
{
series3.Points.AddXY(item.Key, item.Value);
}
var series4 = new Series("Goal");
foreach (var item in data4)
{
series4.Points.AddXY(item.Key, item.Value);
}
series4.ChartType = SeriesChartType.Line;
series4.BorderWidth = 2;
series1.ChartType = series2.ChartType = series3.ChartType = SeriesChartType.StackedColumn;
series1.Font = series2.Font = series3.Font = series4.Font = new Font("Verdana", 8.25f, FontStyle.Regular);
chart.Series.Add(series1);
chart.Series.Add(series2);
chart.Series.Add(series3);
chart.Series.Add(series4);
Thanks for the help.
UPDATE:
As I continue to search for an appropriate solution, I implemented an additional chart for just the "Goal" line series with the intentions of:
Setting the color of certain properties of this new chart to Transparent and
Laying this chart on top of the existing chart
This approach provided the correct presentation by displaying the "Goal" line series over the bar series and allowing the "Goal" line series to extend to the y-axis. But it disabled the tooltips and the click actions of the bar series on the existing chart. Due to this deficient user experience, this approach isn't a suitable solution.
The search for a solution continues...
For question 1:
Another way is to use the PostPaint event, where you can draw anything you wish, anywhere on the chart... but still you lose the tooltips and such interactive features with that line.
For your question 2:
You can exclude the unwanted legend entry from the legend item collection; you can customize legend like it is done this example:
yourChart.CustomizeLegend += new EventHandler<CustomizeLegendEventArgs> (CustomizeLegendEventHandler);
//...
static void CustomizeLegendEventHandler(object sender, CustomizeLegendEventArgs e)
{
int anotherIndex = 3;
if (sender != null && sender is Chart)
{
if (e != null && e.LegendItems != null && e.LegendItems.Count > 0)
{
Chart ch = ((Chart)sender);
if (...) //your logic here
{
//example: you can move a legend item from one index to another here:
LegendItem item = e.LegendItems[0];
e.LegendItems.RemoveAt(0);
e.LegendItems.Insert(anotherIndex, item);
}
}
}
}
And, you can have a secondary Y axis with only one label, one point, etc. or: you can use the PostPaint event handler to draw anything you wish.
You can download a great installable sample pack from MS: here. They show many code examples which you can study. I found out these things from those samples and from using this new free reflector: ILSpy.
Hope this helps.

MS Chart C# - Why the Chart don't show the first Monthname?

I have a Ms Chart on a simple Form and the follow Testcode:
ChartArea myAreachart2 = new ChartArea();
myAreachart2.AxisX.IntervalType = DateTimeIntervalType.Months;
myAreachart2.AxisX.Minimum = new DateTime(2011, 1, 1).ToOADate();
myAreachart2.AxisX.Maximum = new DateTime(2011, 12, 31).ToOADate();
myAreachart2.AxisX.IsLabelAutoFit = false;
myAreachart2.AxisX.LabelStyle.IsEndLabelVisible = false;
myAreachart2.AxisX.LabelStyle.Format = "MMMM";
chart2.ChartAreas.Add(myAreachart2);
chart2.Series.Add("Default");
chart2.Series[0].XValueType = ChartValueType.DateTime
chart2.Series[0].BorderWidth = 4;
chart2.Series[0].Color = Color.Black;
chart2.Series[0].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
chart2.Series[0].Points.AddXY(new DateTime(2011, 1, 1), 100);
chart2.Series[0].Points.AddXY(new DateTime(2011, 2, 1), 200);
chart2.Series[0].Points.AddXY(new DateTime(2011, 3, 1), 300);
chart2.Series[0].Points.AddXY(new DateTime(2011, 4, 1), 400);
chart2.Series[0].Points.AddXY(new DateTime(2011, 5, 1), 500);
The result is a Chart with the Values and on the x-Axis are the monthnames as Labels. But not on january. The monthname will not be displayed. (There ist nothing.)
Please help me ? :-) I seach a lot of websites an examples, but i can't find any solution.
Thank you very much.
Might be because you set:
myAreachart2.AxisX.LabelStyle.IsEndLabelVisible = false;
From MSDN:
LabelStyle.IsEndLabelVisible Property Gets or sets a flag that
determines whether the labels are shown at axis ends.

How to add grab handle in Splitter of SplitContainer

There used to be 3 dots in the splitter bar of a SplitContainer. Just like there are three lines in question details text box on StackOverflow that shows it can be grabbed. How can I do this with the splitter bar of a SplitContainer in .NET?
Not that I have anything against Alex's answer, but I thought I'd share this solution as it looks a bit nicer to me (on an XP machine anyway?).
private void SplitContainer_Paint(object sender, PaintEventArgs e)
{
var control = sender as SplitContainer;
//paint the three dots'
Point[] points = new Point[3];
var w = control.Width;
var h = control.Height;
var d = control.SplitterDistance;
var sW = control.SplitterWidth;
//calculate the position of the points'
if (control.Orientation == Orientation.Horizontal)
{
points[0] = new Point((w / 2), d + (sW / 2));
points[1] = new Point(points[0].X - 10, points[0].Y);
points[2] = new Point(points[0].X + 10, points[0].Y);
}
else
{
points[0] = new Point(d + (sW / 2), (h / 2));
points[1] = new Point(points[0].X, points[0].Y - 10);
points[2] = new Point(points[0].X, points[0].Y + 10);
}
foreach (Point p in points)
{
p.Offset(-2, -2);
e.Graphics.FillEllipse(SystemBrushes.ControlDark,
new Rectangle(p, new Size(3, 3)));
p.Offset(1, 1);
e.Graphics.FillEllipse(SystemBrushes.ControlLight,
new Rectangle(p, new Size(3, 3)));
}
}
Hope this pleases someone? Haa!
That isn't implemented. If you'd like that feature, it's best you derive the SplitContainer and override the OnPaint method.
Update 1
Here's some code to do what you requested. It is in VB.NET and the dot placement can do with some tweaking. Overall, the code works as expected.
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Drawing
Public Class SplitContainerEx
Inherits SplitContainer
''' <summary>Determines the thickness of the splitter.</summary>
<DefaultValue(GetType(Integer), "5"), Description("Determines the thickness of the splitter.")> _
Public Overridable Shadows Property SplitterWidth() As Integer
Get
Return MyBase.SplitterWidth
End Get
Set(ByVal value As Integer)
If value < 5 Then value = 5
MyBase.SplitterWidth = value
End Set
End Property
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
'paint the three dots
Dim points(2) As Point
Dim pointRect = Rectangle.Empty
'calculate the position of the points
If Orientation = Windows.Forms.Orientation.Horizontal Then
points(0) = New Point((MyBase.Width \ 2), SplitterDistance + (SplitterWidth \ 2))
points(1) = New Point(points(0).X - 10, points(0).Y)
points(2) = New Point(points(2).X + 10, points(0).Y)
pointRect = New Rectangle(points(1).X - 2, points(1).Y - 2, 25, 5)
Else
points(0) = New Point(SplitterDistance + (SplitterWidth \ 2), (MyBase.Height \ 2))
points(1) = New Point(points(0).X, points(0).Y - 10)
points(2) = New Point(points(0).X, points(0).Y + 10)
pointRect = New Rectangle(points(1).X - 2, points(1).Y - 2, 5, 25)
End If
e.Graphics.FillRectangle(Brushes.Gray, pointRect)
For Each p In points
p.Offset(-1, -1)
e.Graphics.FillEllipse(Brushes.Black, New Rectangle(p, New Size(3, 3)))
Next
End Sub
End Class
Update 2
I'm putting up the C# equivalent because you tagged your question so.
If vb makes you sick, learn to head over to Convert VB.NET to C# - Developer Fusion and do the VB to C# conversion.
using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
public class SplitContainerEx : SplitContainer
{
/// <summary>Determines the thickness of the splitter.</summary>
[DefaultValue(typeof(int), "5"), Description("Determines the thickness of the splitter.")]
public virtual new int SplitterWidth {
get { return base.SplitterWidth; }
set {
if (value < 5)
value = 5;
base.SplitterWidth = value;
}
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
//paint the three dots
Point[] points = new Point[3];
Rectangle pointRect = Rectangle.Empty;
//calculate the position of the points
if (Orientation == System.Windows.Forms.Orientation.Horizontal) {
points[0] = new Point((int)(base.Width / 2), SplitterDistance + (int)(SplitterWidth / 2));
points[1] = new Point(points[0].X - 10, points[0].Y);
points[2] = new Point(points[2].X + 10, points[0].Y);
pointRect = new Rectangle(points[1].X - 2, points[1].Y - 2, 25, 5);
} else {
points[0] = new Point(SplitterDistance + (int)(SplitterWidth / 2), (int)(base.Height / 2));
points[1] = new Point(points[0].X, points[0].Y - 10);
points[2] = new Point(points[0].X, points[0].Y + 10);
pointRect = new Rectangle(points[1].X - 2, points[1].Y - 2, 5, 25);
}
e.Graphics.FillRectangle(Brushes.Gray, pointRect);
foreach (Point p in points) {
p.Offset(-1, -1);
e.Graphics.FillEllipse(Brushes.Black, new Rectangle(p, new Size(3, 3)));
}
}
}
The closest you can come without painting it yourself is to change the BorderStyle to Fixed3D. That will give you a sort of "bar" in between the two panels.
If you don't like the sunken look on the two panels themselves, you can sort of "hide" the outer borders by putting your splitpanel inside of another panel and setting its Location to a negative value (e.g. -n,-n) and its size to its parent panel size + 2*n. Then I'd set the Anchor to Top | Left | Bottom | Right so that it stays that way as you resize the parent panel.
It's kind of a kludge, but something I've certainly considered doing as I hate that there's no indication of where the "grab point" is.
I liked shousper's and Alex's answers, but they seemed a little 'complex' for my taste; there seemed to be more code that I would have thought necessary.
Shaun's answer also works (I also found that one in MSDN), but in the application I'm working on, the 'grab handles' became quite distracting, since they almost fill the splitter, and we have quite a few of them.
So I came up with this, which is in-between. No arrays, no new rectangles. Would take very little additional work for the '3D' effect from the 'accepted' answer, but the 3 plain dots worked for me:
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
base.OnPaint(e);
Point centerPoint = new Point(SplitterRectangle.Left - 1 + SplitterRectangle.Width / 2, SplitterRectangle.Top - 1 + SplitterRectangle.Height / 2);
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y, 3, 3);
if (Orientation == System.Windows.Forms.Orientation.Horizontal) {
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X - 10, centerPoint.Y, 3, 3);
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X + 10, centerPoint.Y, 3, 3);
} else {
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y - 10, 3, 3);
e.Graphics.FillEllipse(SystemBrushes.ControlText, centerPoint.X, centerPoint.Y + 10, 3, 3);
}
}
I didn't really want to do all the grunt work of drawing a grab handle as I was writing an in-house admin tool and that was overkill. As per this article on MSDN you can sub-class the SplitContainer, override the OnPaint(PaintEventArgs) method and include the following line:
ControlPaint.DrawGrabHandle(e.Graphics, SplitterRectangle, true, Enabled);
This will draw a basic dividing line between the panes.
What I prefer is to just add a paint handler. This means that you don't need to derive a new class and the static function is easily reusable if you put it into a file that can be shared by various projects - just remember to use "add as link" so that if you edit the file, it will be changed for all projects that include it. The main fault with this is it doesn't automatically handle changing the color of the control.
...
mySplitContainer.Paint += CustomPaint.PaintSplitterWithHandle;
...
public static class CustomPaint {
public static void PaintSplitterWithHandle(object sender, PaintEventArgs p) {
SplitContainer splitter = sender as SplitContainer;
if (splitter == null) return;
if (splitter.Orientation == Orientation.Horizontal)
p.Graphics.DrawLine(Pens.DarkGray,
0, splitter.SplitterDistance + (splitter.SplitterWidth / 2),
splitter.Width, splitter.SplitterDistance + (splitter.SplitterWidth / 2));
else
p.Graphics.DrawLine(Pens.DarkGray,
splitter.SplitterDistance + (splitter.SplitterWidth / 2), 0,
splitter.SplitterDistance + (splitter.SplitterWidth / 2), splitter.Height);
}
}

Categories

Resources