I am drawing a chart which I populate with the data I obtain from different procedures. I want to make two buttons to zoom in and out. I saw that I can use different functions from AxisX.ScaleView and I am playing a bit with those. I am almost there but I have a problem at the moment of drawing the chart:If you see the image 1, this is the chart after executing the different procedures and drawing it for the first time. When I do a zoom in and a zoom out, the last bars (Week 22 from image 2) are cut in half and doesn't go to its original size.
Does anyone have any idea how can I manipulate the start and end position for the Axis X in order to make the zoom? Does anyone know how to get the initian values of start and end of the Chart Area? I place the code of my function to make the zoom of the chart:
private void setSize(int zoom)
{
int blockSize = (Convert.ToInt32(tbZoom.Text) + zoom) / 100;
// set view range to [0,max]
chartReport.ChartAreas[0].AxisX.Minimum = 0;
chartReport.ChartAreas[0].AxisX.Maximum = chartReport.Series[0].Points.Count;
// enable autoscroll
chartReport.ChartAreas[0].CursorX.AutoScroll = true;
chartReport.ChartAreas[0].CursorX.IsUserSelectionEnabled = true;
// let's zoom to [0,blockSize] (e.g. [0,100])
chartReport.ChartAreas[0].AxisX.ScaleView.Zoomable = true;
chartReport.ChartAreas[0].AxisX.ScaleView.SizeType = DateTimeIntervalType.Number;
int actualHeight = chartReport.Height;
int actualWidth = chartReport.Width;
int position = 0;
int size = blockSize;
chartReport.ChartAreas[0].AxisX.ScaleView.Zoom(position, size);
// disable zoom-reset button (only scrollbar's arrows are available)
chartReport.ChartAreas[0].AxisX.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
// set scrollbar small change to blockSize (e.g. 100)
chartReport.ChartAreas[0].AxisX.ScaleView.SmallScrollSize = blockSize;
tbZoom.Text = (blockSize * 100).ToString();
}
Your first line is setting the maximum of the axis wrong: chartReport.ChartAreas[0].AxisX.Maximum = chartReport.Series[0].Points.Count; sets it to 22, when it really should be 23 (based on the first image).
If your data will always look like this, simply add 1:
chartReport.ChartAreas[0].AxisX.Maximum = chartReport.Series[0].Points.Count + 1;
Unfortunately, using the automatic min/max values won't give you the actual values until the chart is actually drawn. If your chart has few DataPoints this isn't a problem, as you can just call chartReport.Refresh(); or something similar and then get the values from the axes. But, if you have a lot of points, the Refresh() will take a long time which is undesirable. In my extensive use of the charts, I wound up setting the axis ranges myself so I have full control, rather than using the automatic min/max values.
Related
I have an application that have 2 charts as follows:
Is it possible to programatically show the tooltip of the second chart when hovering on the first chart - via chart 1's DataHover event?
EDIT
The info on the second chart is for Relative Strength Index. It is usually presented at the bottom of the chart. For this reason I made it a separate chart.
Although not an answer to my question, here is what I did. A now deleted comment asked why there are two charts. When I did it like that initially, the charts were over each other.
So I simply went back to that, and applied a scaling factor. 20% of the chart are is fine for the RSI, and as RSI is always calculated out of 100, I set the axis max value to 500% as follows:
chartMain.AxisY.Add(new Axis
{
MaxValue = 500,
MinValue = 0,
IsMerged = true,
Position = AxisPosition.RightTop,
ShowLabels = false,
Sections = new SectionsCollection
{
new AxisSection
{
SectionWidth = m_TradeManager.Settings.RSIThreshold,
Fill = new System.Windows.Media.SolidColorBrush
{
Color = System.Windows.Media.Color.FromRgb(254,132,132),
Opacity = .4
}
}
}
});
The end result is this:
I use the C# Chart in WinForms to plot a variety of variables in real time using the "line" chart type. That works well for analog values, but it's less than ideal for on/off flags.
I'd like to plot multiple flags as horizontal bars that are filled when the value is '1" and clear when the value is '0'.
Before I start coding a solution from scratch, do you have any suggestion on how I could take advantage of any features of the "chart" object to implement this more effectively?
EDIT: I am playing with the Area type, and it seems to be promising.
EDIT 2: That didn't work, because the area in the Area type always starts at the bottom of the chart, hiding the other rows. I am now trying the Range Column type
There are several ways to tackle this.: StackedBars, AreaChart, Annotations but I think by far the simplest is using a LineChartType.
The first issue is: How to create the gaps? The simplest way is to draw them as lines but with Color.Transparent. So instead of using the flag value as our y-value we use it to set the color..
So we could use a function like this:
void AddFlagLine(Chart chart, int series, int flag, int x)
{
Series s = chart.Series[series];
int px = s.Points.AddXY(x, series);
s.Points[px].Color = s.Color;
if (px > 0) s.Points[px - 1].Color = flag == 1 ? s.Color : Color.Transparent;
}
It takes the index of your Series and uses the flag to determine the color; note that the color of a line segment is controlled by the color of the end point.
So if you want to have the line going out from the new point to have its flag color, you need to set it when adding the next one..
This is simple enough and for lines as thick as 1-10 it works fine. But if you want larger widths things get a bit ugly..:
The rounded caps start to get bigger and bigger until they actually touch, flling the gaps more or less.
Unfortunately there seems to be no way to controls the caps-style of the lines. There are many CustomAttributes including DashStyles but not this one. So we have to resort to owner-drawing. This is rather simple for line charts. Here is an example:
The xxxPaint event looks like this:
private void chart_PostPaint(object sender, ChartPaintEventArgs e)
{
Graphics g = e.ChartGraphics.Graphics;
Axis ax = chart.ChartAreas[0].AxisX;
Axis ay = chart.ChartAreas[0].AxisY;
for (int si = 0; si < chart.Series.Count; si++ )
{
Series s = chart.Series[si];
for (int pi = 1; pi < s.Points.Count - 1; pi++)
{
DataPoint dp = s.Points[pi];
int y = (int) ay.ValueToPixelPosition(dp.YValues[0]+1); ///*1*
int x0 = (int)ax.ValueToPixelPosition(ax.Minimum);
int x1 = (int)ax.ValueToPixelPosition(s.Points[pi-1].XValue); ///*2*
int x2 = (int)ax.ValueToPixelPosition(dp.XValue);
x1 = Math.Max(x1, x0);
x2 = Math.Max(x2, x0);
using (Pen pen = new Pen(dp.Color, 40) ///*3*
{ StartCap = System.Drawing.Drawing2D.LineCap.Flat,
EndCap = System.Drawing.Drawing2D.LineCap.Flat })
{
g.DrawLine(pen, x1, y, x2, y);
}
}
}
A few notes:
1 : I have decided to move the the series up by one; this is up to you just as using or turning off the y-axis labels or replacing them by custom labels..
2 : Here we use the previous point's x-position!
3 : Note that instead of hard coding a width of 40 pixels you really should decide on a calculated width. This is an example that almost fills up the area:
int width = (int)( ( ay.ValueToPixelPosition(ay.Minimum) -
ay.ValueToPixelPosition(ay.Maximum)) / (chart7.Series.Count + 2));
You can twist is to fill more or less by adding less or more than 2.
I have turned all BorderWidths to 0 so only the drawn lines show.
I got it:
It turned out to actually be pretty easy; I used the Range Column type.
A) Set-up (done once):
plotChart.Series[chanNo].ChartType = SeriesChartType.RangeColumn;
plotChart.Series[chanNo].CustomProperties = "PointWidth=" + noOfFlags;
PointWidth is required to set the relative width of each rectangle so that it fills the entire width of one data point (if too small, there are gaps in the horizontal bar; if too large, there is overlap). noOfFlags is the number of flags shown (in the example shown above, noOfFlags = 4). (By the way the MSDN documentation is wrong: PointWidth is not limited to 2.)
B) Plotting (done for each new data point):
baseLine--;
int barHeight = flagHigh ? 1 : 0;
plotChart.Series[chanNo].Points.AddXY(pointX, baseLine, baseLine + barHeight);
flagHigh is a bool that is equal to the flag being monitored.
baseLine is decremented for each trace. In the example above, baseLine starts at 4, and is decremented down to 0.
Note that for each data point, RangeColumn requires 2 "Y" values: one for the bottom of the rectangle, one for the top; in the code, I set the bottom Y to the bottom of the row that I use for that particular flag, and the top to 1 above the bottom, to give me a height of 1.
Using the charting control from System.Windows.Forms.DataVisualization.Charting.Chart, I am making a scatter plot.
How can I constrain it so that the scale of the X axis is the same as the scale of the Y axis?
Simply setting the control itself to be square is insufficient, because it has internal margins for drawing and labeling the axes which are not equal.
I could pick a specific size and tweak it to be square, but it needs to be both square and resizable.
I've searched high and low in the documentation and in the property browser, but I can't find anything or think of any ways to do it in the resize event.
This is a good question but unfortunately there is no simple solution like locking the two Axes or setting one value..
Let's start by looking at the relevant players:
The Chart control has an inner Size called ClientSize, which is the Chart.Size minus the borders. Both sizes are measured in pixels.
Inside there may be one or more ChartAreas. Each has a Position which is of type ElementPosition.
Inside each ChartArea the is an area which is used for the actual drawing of the points; it is called InnerPlotPosition.
The InnerPlotPosition property defines the rectangle within a chart
area element that is used for plotting data; it excludes tick marks,
axis labels, and so forth.
The coordinates used for this property (0,0 to 100,100) are related to
the ChartArea object, and not to the entire Chart.
The InnerPlotPosition property can be used to align multiple chart
areas. However, if one chart area has tick marks and axis labels and
another one does not, their axis lines cannot be aligned.
Both ChartArea.Position and ChartArea.InnerPlotPosition contain not just the location but also the size of the areas; all values are in percent of the outer area, ie ChartArea.InnerPlotPosition is relative to the ChartArea.Position and ChartArea.Position is relative to the Chart.ClientSize. All percentages go from 0-100.
So the ChartArea includes Labels and Legends as well as Axes and TickMarks..
What we want is to find a way to make the InnerPlotArea square, i.e. have the same width and height in pixels. The percentages won't do!
Let's start with a few simple calculations; if these are the data we have..:
// we'll work with one ChartArea only..:
ChartArea ca = chart1.ChartAreas[0];
ElementPosition cap = ca.Position;
ElementPosition ipp = ca.InnerPlotPosition;
.. then these are the pixel sizes of the two areas:
// chartarea pixel size:
Size CaSize = new Size( (int)( cap.Width * chart1.ClientSize.Width / 100f),
(int)( cap.Height * chart1.ClientSize.Height / 100f));
// InnerPlotArea pixel size:
Size IppSize = new Size((int)(ipp.Width * CaSize.Width / 100f),
(int)(ipp.Height * CaSize.Height / 100f));
Ideally we would like the InnerPlotArea to be square; since can't very well let the smaller side grow (or else the chart would overdraw,) we need to shrink the larger one. So the new pixel size of the InnerPlotArea is
int ippNewSide = Math.Min(IppSize.Width, IppSize.Height);
What next? Since the Chart.Size has just been set, we don't want to mess with it. Nor should we mess with the ChartArea: It still needs space to hold the Legend etc..
So we change the size of the InnerPlotArea..:
First create a class level variable to store the original values of the InnerPlotPosition :
ElementPosition ipp0 = null;
We will need it to keep the original percentages, i.e. the margins in order to use them when calculating the new ones. When we adapt the chart the then current ones will already have been changed/distorted..
Then we create a function to make the InnerPlotArea square, which wraps it all up:
void makeSquare(Chart chart)
{
ChartArea ca = chart.ChartAreas[0];
// store the original value:
if (ipp0 == null) ipp0 = ca.InnerPlotPosition;
// get the current chart area :
ElementPosition cap = ca.Position;
// get both area sizes in pixels:
Size CaSize = new Size( (int)( cap.Width * chart1.ClientSize.Width / 100f),
(int)( cap.Height * chart1.ClientSize.Height / 100f));
Size IppSize = new Size((int)(ipp0.Width * CaSize.Width / 100f),
(int)(ipp0.Height * CaSize.Height / 100f));
// we need to use the smaller side:
int ippNewSide = Math.Min(IppSize.Width, IppSize.Height);
// calculate the scaling factors
float px = ipp0.Width / IppSize.Width * ippNewSide;
float py = ipp0.Height / IppSize.Height * ippNewSide;
// use one or the other:
if (IppSize.Width < IppSize.Height)
ca.InnerPlotPosition = new ElementPosition(ipp0.X, ipp0.Y, ipp0.Width, py);
else
ca.InnerPlotPosition = new ElementPosition(ipp0.X, ipp0.Y, px, ipp0.Height);
}
You would call the function after or during resizing.
private void chart1_Resize(object sender, EventArgs e)
{
makeSquare(chart1);
}
Here the function is at work:
The original size:
Squeezed a little:
And made square again:
Note how the green ChartArea reserves enough space for the Labels and the Legend and how the automatic scaling for the axes still works.. But the X-Axis labels now don't fit in one row. Also note how the ChartArea.BackColor actually is the color of the InnerPlotArea only!
Note that you may have to refresh the variable ipp0 to reflect the changed percentages, after making modification to the ChartArea layout like enlarging or moving or removing Legends or changing the size or angle of Labels etc..
Of course you can modify the function to pass in any other ratio to keep instead of keeping the plot area a square..
I'm trying to use the chart control on a windows form and have it working, plotting some real time data, however before the data arrives nothing is displayed. I would like to show an empty graph with an X Y of 10 30 but still have the graph auto range if values go above this.
I cannot find a property to show the "blank" graph it this possible and if so how?
thanks
You can hide all data of a Series by making its line color Transparent. If you also set its LegendText to be " " all you can see are the Axis ticks. you can control them by adding a few Points and by setting the Minimum and Maximum values:
// short reference for our dummy:
Series S0 = chart1.Series[0];
// a simple type
S0.ChartType = SeriesChartType.Line;
// set 10 point with x-values going from 0-100 and y-values going from 1-10:
for (int i = 0; i < 100; i +=10) S0.Points.AddXY(i , i / 10);
// or add only a few, e.g. the first and last points:
//S0.Points.AddXY(100, 10);
//S0.Points.AddXY(0, 10);
// hide the line:
S0.Color = Color.Transparent;
// hide the legend text (it will still take up a little space, though)
S0.LegendText = " ";
// limit the axis to the target values
chart1.ChartAreas[0].AxisX.Maximum = 100;
chart1.ChartAreas[0].AxisX.Minimum = 0;
The result looks like an empty chart:
So, I am trying to plot the graph and I have 3 fastlines in the Tchart, It seems like the graph are correct but there is something I need to set with labels of axes, What I get is this,
[my graph with x labels as 0 0 0 0 1 1 1 1 ]
http://s8.postimage.org/t7tappekl/image.png
[I want something like this]
http://s7.postimage.org/amltkb917/untitled.png
and what I want to get is something like this , the actual x labels as these . X labels are seconds.
I have already tried setting the valueformat for series and chart lablels. It did not work,
How should I do this? and also I am trying to zoom and scroll up the y axes to set the focus like the image 2. the y graph always starts from 0 , but initially is there any way to set the focus on starting point i.e.81
Thank you so much!
You can set desired increment for the bottom axis labels using the so called Increment property, for example:
tChart1.Axes.Bottom.Increment = 0.1;
For changing axes range you can use SetMinMax or Minimum and Maximum properties:
tChart1.Axes.Left.SetMinMax(50, 100);
or
tChart1.Axes.Left.AutomaticMinimum = false;
tChart1.Axes.Left.Minimum = 50;
tChart1.Axes.Left.AutomaticMaximum = false;
tChart1.Axes.Left.Maximum = 100;
Finally, you can change the chart view perspective changing some of the following properties:
tChart1.Aspect.View3D = true;
tChart1.Aspect.Orthogonal = false;
tChart1.Aspect.Chart3DPercent = 50;
tChart1.Aspect.Elevation = 0;
tChart1.Aspect.Rotation = 345;
tChart1.Aspect.Perspective = 50;
BTW, you'll find more information about axis settings in tutorial 4. Tutorials can be found a TeeChart's program group.