I am trying to figure out how to my start the X axis in my line chart at the first date in stored in an object. I have been trying for quite some time now, and I am not sure what I am doing wrong. Any help would be appreciated.
It simply says that I can't convert from DateTime to Double, which makes sense, but I have no clue how to get around this.
I have indented the part of the code I am trying to make work. Thanks in advance!
private void loadChart(List<Temperatur> templist)
{
// clear charts
this.chart1.Series.Clear();
this.chart2.Series.Clear();
// add Data to charts
Series series = this.chart1.Series.Add((templist.ElementAt(0).Date).ToString());
Series series2 = this.chart2.Series.Add((templist.ElementAt(0).Date).ToString());
// defines type of chart
series.ChartType = SeriesChartType.Line;
series2.ChartType = SeriesChartType.Line;
// sets line thickness
series.BorderWidth = 3;
series2.BorderWidth = 3;
// sets chart limits and intervals
chart1.ChartAreas[0].AxisY.Maximum = 42;
chart1.ChartAreas[0].AxisY.Minimum = 35;
chart1.ChartAreas[0].AxisY.Interval = 0.5;
//sets X axis labels
chart1.ChartAreas[0].AxisX.IntervalType = DateTimeIntervalType.Days;
chart1.ChartAreas[0].AxisX.Minimum = Convert.ToDouble(templist.ElementAt(0).Date);
// removes legend from chart
chart1.Legends[0].Enabled = false;
chart2.Legends[0].Enabled = false;
foreach (Temperatur tempObj in templist)
{
// Add point.
series.Points.Add(Convert.ToDouble(tempObj.Temp));
}
foreach (Temperatur tempObj in templist)
{
// Add point.
series2.Points.Add(Convert.ToInt32(tempObj.Puls.ToString()));
}
}
You can add DataPoints in several formats.
You want them to have a X-Value of type DateTime and an Y-Value of type double, so use a call like this:
series.Points.AddXY(tempObj.Date, tempObj.Temp);
If you want to start the display at a Minimum you need to use the key of the Points collection, if the X-Values are dates it will be a date, if they are numbers (doubles by default) you use a number.
If you never have set the X-Values they will start at 0 by default (which will be shown as 1899 when cast to a DateTime..)
Doing so is a little tricky, as the Minimum only accepts doubles; that's because even when your type is set to DateTime and even though you have added DateTime X-Values, internally they still are stored as double.
To set the Minimum etc after you have added the DataPoints with their X-Values as DateTimes you can use :
int yourStartIndex = 1;
chart1.ChartAreas[0].AxisX.Minimum = series.Points.ElementAt(yourStartIndex).XValue;
To control the displayed X-Axis label set its Format; here is an example:
chart1.ChartAreas[0].AxisX.LabelStyle.Format = "dd.MM.\\'yy hh\\h";
Note the added 'h' and the added apostrophe resulting in something like: 21.03'15 12h
Related
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 called DrawSideBySide.
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 a NamedImage to the chart's Images 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 or Legends or even more ChartAreas; instead you should use the height of the ChartArea, or even more precise, the height of the InnerPlotPosition. 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 the AxisViewChanged 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..)
I have a Winforms chart in which I have temperature readings arriving and displaying every second. I like the way the chart works automatically handling the display of the values, but I want to change one simple thing.
I want to increase the minimum displayed y axis range, so it displays a range of 20. At the moment it only displays around 5. I have tried a few things:
//(when new data arrives...)
//Does not work, I think because by default, Size is always NaN?
if (chart1.ChartAreas[0].AxisY.ScaleView.Size < 20)
{
chart1.ChartAreas[0].AxisY.ScaleView.Size = 20;
}
None of these work either:
chart1.ChartAreas[0].AxisY.ScaleView.SmallScrollMinSize = 20;
chart1.ChartAreas[0].AxisY.ScaleView.SmallScrollSize = 20;
chart1.ChartAreas[0].AxisY.ScaleView.MinSize = 20;
chart1.ChartAreas[0].AxisY.Minimum //doesn't seem to have any effect
chart1.ChartAreas[0].AxisY.Maximum //doesn't seem to have any effect
I'm sure I've missed something simple. I hope I have anyway.
The 'minimum display range' is not something built-in in the MSChart control.
But you can easily fake it:
Add a dummy Series which contains only two points to make sure the display range will not go below the range of their y-values..:
int rangeMin = -10;
int rangeMax = 20;
sDummy = chart.Series.Add("dummy");
sDummy.Color = Color.Transparent;
sDummy.IsVisibleInLegend = false;
sDummy.ChartType = SeriesChartType.Point;
sDummy.Points.AddXY(0, rangeMin + 1);
sDummy.Points.AddXY(0, rangeMax - 1);
Style your y-axis as you like:
Axis ay = chart.ChartAreas[0].AxisY;
ay.MajorGrid.Interval = 5;
And add one or more data Series:
sData = chart.Series.Add("data");
sData.LegendText = "Temperature";
sData.ChartType = SeriesChartType.Line;
Now as you add data points with a larger range of values the y-axis will grow its display range to accommodate them. And if you remove the larger points it will shrink back, but not below the range needed for the dummy series..:
Note that since the Chart automatically adds some slack I reduce the range on both sides by 1; with other Intervals etc other numbers are needed..
The code to remove the larger values, btw:
var toRemove = sData.Points.Cast<DataPoint>()
.Where(x => x.YValues[0] >= rangeMax).ToList();
foreach (var dp in toRemove) sData.Points.Remove(dp);
I am new to C# winform. As title, my problem is how to make the points more compact in fastpoint chart. To make it clear, how to proportionally reduce the distance of the blue points like the red line segments shown in the image; that is, make the X axis more compact:
I have searched and found a lot of information about control the interval of X or Y axis labels but which is not the situation here.
You can control the range of data values for each Axis by setting their Minimum and Maximum values.
The syntax is:
someChart.ChartAreas[CAIndexOrName].AxisX.Minimum = someDoubleValue;
Let's prepare a chart to show one day:
Axis ax = chart.ChartAreas[0].AxisX; // a short reference
ax.IntervalType = DateTimeIntervalType.Hours;
ax.Interval = 1;
To set the properties to some DateTime values you need to convert them to doubles; for this conversion there are two built-in functions: DateTime.ToOADate and, to reverse DateTime.FromOADate
This makes the x-axis display 24 hours:
DateTime dt = DateTime.Today;
ax.Minimum = dt.ToOADate();
ax.Maximum = (dt.AddHours(24)).ToOADate();
ax.LabelStyle.Format = "H:mm"; // optional
You also may want to control the data type of the series values:
Series s = chart.Series[0];
s.XValueType = ChartValueType.DateTime; // or some other type, maybe Time
Note you you can also control both the Interval of the Labels and TickMarks and Gridlines on each axis but also set an Offset to start them a little earlier or later..
I am trying to control the value labels for my zedgraph's x-axis. Before, the labels would "fly around" and not really stay put on the axis. They might move left or right on the axis and pop in and out of existence based on the data. Like in the picture below
I first tried to draw the labels myself when I finally found good documentation for the zedgraph library. There I found the [AXIS].Scale options of MajorStep MinorStep and BaseTic. Which if set correctly should cause the labels to stay in place and just change value as the data is added.
The issue I am running into though is that my x-axis scale is in XDate units. Which means I cannot do the simple math I was hoping to. So I have since figured a way that I think I can find the values I need using TimeSpan and DateTime. Below curMaxX and curMinX are XDate values of the current minimum and maximum x-axis values (curMinX should basically be DateTime.Now because this data is realtime)
// Setup
TimeSpan scaleDist = curMaxX.DateTime.Subtract(curMinX.DateTime);
TimeSpan major = new TimeSpan(scaleDist.Ticks / 5);
TimeSpan minor = new TimeSpan(major.Ticks / 5);
TimeSpan baseT = new TimeSpan(curMinX.DateTime.Ticks + major.Ticks);
// Setting the values
myPane.XAxis.Scale.MajorStep = new XDate(new DateTime(major.Ticks));
myPane.XAxis.Scale.MinorStep = new XDate(new DateTime(minor.Ticks));
myPane.XAxis.Scale.BaseTic = new XDate(new DateTime(baseT.Ticks));
// Print statement of the values
SDist: 00:00:10
Major: 00:00:02
Minor: 00:00:00.4000000
BaseT: 735382.07:32:34
BaseTAsDateTime: 5/30/2014 7:32:34 AM
CurMinX: 5/30/2014 7:32:32 AM
Though setting the values as such still does not achieve what I want. With this code my x-axis comes out looking like so
It is somewhat closer to what I am looking for but still off. Only a single major tic is shown, and no minor tics. I am not sure what other ways there might be to specify the values.
For the major and minor step documentation it says
For Date axes, this value is defined in units of Major/MinorUnit.
Which my MajorUnit is minutes and the MinorUnit is seconds, but I pass them a XDate value which specifies both minutes and seconds. Also, setting the values as fractions of a whole (so if I want MajorStep to be 2 seconds I'd set it as (2/60)), causes nothing to show up.
Any ideas/suggestions?
Well, I determined that it was a waste of time to try and use the default zedgraph tics, steps, etc. So I made my own method to do so based on the data currently on the graph.
After I posted this I realized trying to figure out the tics was a lost cause so I looked for other ways to do it. I was going to add text beside each point showing its values, but it became very cluttered, then I realized I can just draw the text at the bottom of the graph which is what I wanted all along. I also added a little label on the right showing the current value.
A little bit o code
private void setPointLabels()
{
// All hail the almighty pane.
GraphPane myPane = theGraph.GraphPane;
// Give us some room to draw labels
myPane.Margin.Right = 50;
myPane.Margin.Bottom = 20;
// Dont show the default stuff
myPane.XAxis.Scale.IsVisible = false;
myPane.XAxis.MajorTic.IsAllTics = false;
myPane.XAxis.MinorTic.IsAllTics = false;
// Remove the old labels
myPane.GraphObjList.Clear();
// Get the curve showing our data
LineItem myCurve = (LineItem)myPane.CurveList[0];
// Show a voltage value on the far right
PointPair pt = myCurve.Points[myCurve.Points.Count - 1];
TextObj text = new TextObj(" " + pt.Y.ToString("f2"), pt.X, pt.Y, CoordType.AxisXYScale, AlignH.Left, AlignV.Center);
text.ZOrder = ZOrder.A_InFront;
text.FontSpec.Border.IsVisible = false;
text.FontSpec.Fill.IsVisible = false;
myPane.GraphObjList.Add(text);
// Determine a hardcoded yOffset for the labels
double yOffset = -1.2;
// Determine if we need to fix the center label
int fixVal = 1;
if (xScaleSec == 10)
fixVal = 0;
// Loop over each point in the curve
for (int i = 0; i < myCurve.Points.Count; i++)
{
if (i == 0 ||
i == (myCurve.Points.Count/4) ||
i == ((myCurve.Points.Count/2)-fixVal) ||
i == ((3*myCurve.Points.Count)/4) ||
i == myCurve.Points.Count-1)
{
PointPair aPt = myCurve.Points[i];
// Add a text object just below the x-axis showing the point's x-value
XDate xVal = new XDate(aPt.X);
TextObj label = new TextObj(xVal.ToString("hh:mm.ss"), aPt.X, myPane.YAxis.Scale.Min + yOffset, CoordType.AxisXYScale, AlignH.Center, AlignV.Center);
label.ZOrder = ZOrder.A_InFront;
label.FontSpec.Fill.IsVisible = false;
label.FontSpec.Border.IsVisible = false;
myPane.GraphObjList.Add(label);
// Add a line object on the x-axis representing a tic mark
LineObj aTic = new LineObj(aPt.X, myPane.YAxis.Scale.Min - (yOffset / 4), aPt.X, myPane.YAxis.Scale.Min + (yOffset / 4));
myPane.GraphObjList.Add(aTic);
}
}
}
I am using C# Excel API to generate some reports. However, Excel leaves gaps between the first axis point and minimum value in data set, and between last axis point and maximum value in data set. My data set is sorted by datetime. How do I force excel to set the lower and upper bounds of axes precisely to minimum and maximum values in my data set so that I don't see any gaps? I can do that in excel by manually setting min/max axis points in excel chart.
But is there a way to have excel do this automatically, or otherwise set min/max points from my C# application using the data set?
Example (marked gaps)
Hope it makes sense.
thanks
[Edited] OK, I did some playing, and I've figured out how to set the vertical and horizontal axis range. This is working with Excel 2010.
Here, I clear all charts on the page and create a new one (_resultsSheet is an Excel.Worksheet):
var resultCharts = (ChartObjects)_resultsSheet.ChartObjects();
foreach (ChartObject ch in resultCharts)
{
ch.Delete();
}
ChartObject resultChart = resultCharts.Add(150, 40, 300, 200);
_resultChartPage = resultChart.Chart;
Now set up the source - I've just used a predefined range of fixed values. You could scan your source to find the actual min and max values:
_resultChartRange = _resultsSheet.get_Range("J5", "K15");
_resultChartPage.SetSourceData(_resultChartRange);
_resultChartPage.ChartType = Excel.XlChartType.xlXYScatterLines;
_resultChartPage.HasLegend = false;
Now for the vertical axis setup:
Axis vertAxis = (Axis)resultChart.Chart.Axes(XlAxisType.xlValue, XlAxisGroup.xlPrimary);
vertAxis.HasMajorGridlines = true; // change this to whatever you wish
vertAxis.HasTitle = true;
vertAxis.AxisTitle.Text = "up the side";
vertAxis.MaximumScaleIsAuto = false;
vertAxis.MaximumScale = 500; // you can pick this based on your input
vertAxis.MinimumScaleIsAuto = false;
vertAxis.MinimumScale = 5;
now for the other axis. Note here I've used fixed times. To convert a time to an axis scale, just use the 24 hour time in decimal, divided by 24. Eg. 9:30pm is 21:30 which is 21.5 hours. Don't forget the (double) cast just in case you use to ints.
Axis horizAxis = resultChart.Chart.Axes(XlAxisType.xlCategory, XlAxisGroup.xlPrimary);
horizAxis.MaximumScaleIsAuto = false;
horizAxis.MaximumScale = (double)21.5 / 24; // 9:30 pm
horizAxis.MinimumScaleIsAuto = false;
horizAxis.MinimumScale = (double)13 / 24; // 1:00 pm
horizAxis.HasTitle = true;
horizAxis.AxisTitle.Text = "across the bottom";
and for those who "like to watch":
_resultsSheet.Activate();
_workBook.Application.Visible = true;