Drawing an GraphObj to Zedgraph control - c#

I have a zedgraph control where I got at the the basic line chart of sine and cosine wave (from tutorial)
I am trying to add a BoxObj to the curve by clicking on it (code below) and I see that the BoxObj is added to GraphObjList, but nothing is actually drawn. Can it be the location that I give the object?
//This works
void zedGraphControl1_Click(object sender, EventArgs e)
{
Point p = (e as MouseEventArgs).Location;
CurveItem nearestCurve;
int index;
this.zedGraphControl1.GraphPane.FindNearestPoint(new PointF(p.X, p.Y), out nearestCurve, out index);
//Check for null when no curve clicked
if (nearestCurve == null)
return;
BoxObj box = new BoxObj(nearestCurve[index].X, nearestCurve[index].Y, 1, 0.1, Color.Black,Color.Red);
box.IsVisible = true;
box.Location.CoordinateFrame = CoordType.AxisXYScale;
box.ZOrder = ZOrder.A_InFront;
zedGraphControl1.GraphPane.GraphObjList.Add(box);
zedGraphControl1.Invalidate();
}
Here is the whole graph creation
public void CreateGraph(zedGraph ZedGraphControl)
{
// Lets generate sine and cosine wave
double[] x = new double[100];
double[] y = new double[100];
double[] z = new double[100];
for (int i = 0; i < x.Length; i++)
{
x[i] = i;
y[i] = Math.Sin(0.3 * x[i]);
z[i] = Math.Cos(0.3 * x[i]);
}
// This is to remove all plots
zedGraph.GraphPane.CurveList.Clear();
// GraphPane object holds one or more Curve objects (or plots)
GraphPane myPane = zedGraph.GraphPane;
// PointPairList holds the data for plotting, X and Y arrays
PointPairList spl1 = new PointPairList(x, y);
PointPairList spl2 = new PointPairList(x, z);
// Add cruves to myPane object
LineItem myCurve1 = myPane.AddCurve("Sine Wave", spl1, Color.Blue, SymbolType.None);
LineItem myCurve2 = myPane.AddCurve("Cosine Wave", spl2, Color.Red, SymbolType.None);
myCurve1.Line.Width = 3.0F;
myCurve2.Line.Width = 3.0F;
myPane.Title.Text = "My First Plot";
// I add all three functions just to be sure it refeshes the plot.
zedGraph.AxisChange();
zedGraph.Invalidate();
zedGraph.Refresh();
}
Environment:
MS Visual Studio 2010 and .NET Framework 4.0 on Windows XP

You're right, the location is wrong. The click-Event gives you the display-coordinates, but the BoxObj Constructor needs either units of your axis-scales or fraction of the chart-rect, depending on the CoordType of your BoxObj.
So you have to decide, which CoordType is more handy to you, convert the event-location to this type and also assign the CoordType to your BoxObj, for instance:
box.Location.CoordinateFrame = CoordType.XScaleYChartFraction;
EDIT: For testing you could try the following, and the box should be in the middle of your chart:
BoxObj box = new BoxObj(0.5, 0.5, 40, 40, Color.Black,Color.Red);
box.Location.CoordinateFrame = CoordType.ChartFraction;

Related

Setting fixed major grid marks independent of data range

This is my first project in c# and I'm trying to create plots from data.
I'm struggling with drawing minor and major grid lines and labels on a logarithmic scale.
I've set the scale to logarithmic, set the base to 10 and both major and minor intervals to 1, and it works great, however, the interval starts with the minimum value on scale, so for example if data starts at 30M (I'm dealing with frequencies) the next major tick is at 300M and 3G, which is not as it should be.
Is there a way to set major grid to 1, 10, 100 etc, independent of what data is displayed? i've tried changing intervals, base and offset but have not achieved much.
area.AxisX.IsLogarithmic = true;
area.AxisX.LogarithmBase = 10;
area.AxisX.Interval = 1;
//area.AxisX.IntervalOffset = 10000;
area.AxisX.IntervalAutoMode = IntervalAutoMode.FixedCount;
area.AxisX.MajorGrid.Enabled = true;
area.AxisX.MajorTickMark.Enabled = true;
area.AxisX.MinorGrid.Enabled = true;
area.AxisX.MinorGrid.Interval = 1;
area.AxisX.MinorTickMark.Enabled = true;
area.AxisX.MinorTickMark.Interval = 1;
area.AxisX.Minimum = minMaxXY[0]; // in this example 30 M
area.AxisX.Maximum = minMaxXY[1]; // in this example 1 G
here's the link to the current grid
https://ibb.co/3WkxLfc
Thank you for your time and answers!
Thanks to TaW replay I managed to get my program working.
Here is my solution using customLabels location to draw the grid lines.
private void Chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (e.Chart.ChartAreas.Count > 0) // I don't yet truly understand when this event occurs,
// so I got plenty of null references.
{
Graphics g = e.ChartGraphics.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
Color minorGridColor = Color.Gainsboro;
ChartArea area = e.Chart.ChartAreas[0];
double aymin = area.AxisY.Minimum;
double aymax = area.AxisY.Maximum;
int y0 = (int)area.AxisY.ValueToPixelPosition(aymin);
int y1 = (int)area.AxisY.ValueToPixelPosition(aymax);
foreach (var label in chart1.ChartAreas[0].AxisX.CustomLabels)
{
double xposition = area.AxisX.ValueToPixelPosition(Math.Pow(10, label.FromPosition + 0.1));
if (xposition > area.AxisX.ValueToPixelPosition(minMaxXY[0]) && xposition < area.AxisX.ValueToPixelPosition(minMaxXY[1]))
//this prevents drawing of lines outside of the chart area
{
int x = (int)xposition;
using (Pen dashed_pen = new Pen(Color.FromArgb(10, 0, 0, 0), 1))
{
dashed_pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;
g.DrawLine(dashed_pen, x, y0, x, y1);
}
}
}
}
}
I also found the CustomLabel.GridTicks Property, but for some reason it did not work.

Refresh the canvas only for certain brushes

I'm trying to graph some circles and lines etc but I only want some lines to refresh on the canvas and the others not to, is there any way around this?
For example the mypen, mypen2 and mypen3, I want them to refresh on canvas but the graphics "g" a little further down I don't want to refresh, I want all the instances to show. How do I do this? Here is my code
private void drawlines()
{
canvas.Refresh();
int j = Int32.Parse(ivalue.Text);
float position1 = canvas.Width / 2;
float position2 = canvas.Height / 2;
float XX = (float)(Math.Round(position1 + Math.Sin(DegreeToRadian(j)) * 100));
float XY = (float)(Math.Round(position2 - Math.Cos(DegreeToRadian(j)) * 100));
float X2 = (position1 + XX);
float XY2 = XY;
System.Drawing.Pen myPen;
System.Drawing.Pen myPen2;
System.Drawing.Pen myPen3;
System.Drawing.Pen myPen4;
myPen = new System.Drawing.Pen(System.Drawing.Color.Red);
myPen2 = new System.Drawing.Pen(System.Drawing.Color.Blue);
myPen3 = new System.Drawing.Pen(System.Drawing.Color.Black);
myPen4 = new System.Drawing.Pen(System.Drawing.Color.Green);
System.Drawing.Graphics formGraphics = canvas.CreateGraphics();
formGraphics.DrawRectangle(myPen,XX, XY,3,3);
formGraphics.DrawRectangle(myPen2, canvas.Width / 2, XY, 3, 3);
formGraphics.DrawRectangle(myPen3, position1, position2, 3, 3);
formGraphics.DrawRectangle(myPen4, position1, XY2, 3, 3);
label1.Text = Convert.ToString(XY);
label1.Refresh();
listBox1.Items.Clear();
listBox1.Items.Add("XX=[" + XX + "] XY=[" + XY + "]");
}
private void Go_Click(object sender, EventArgs e)
{
for (int i = 0; i <= 360; i = i + 1)
{
drawlines();
int linearm = (canvas.Width / 2) - i;
ivalue.Text = Convert.ToString(i);
ivalue.Refresh();
int testx = Int32.Parse(label1.Text);
Graphics g;
g = canvas.CreateGraphics();
Pen p;
Rectangle r;
p = new Pen(Brushes.Green);
r = new Rectangle(linearm,testx, 1, 1);
g.DrawRectangle(p, r);
System.Threading.Thread.Sleep(15);
}
}
I assume you are using winforms? If so you need to change your code to work like this:
To be persistant everything need to be drawin in the Paint event and using its e.Graphics object. (This is the Golden Rule! Corollary: Never use System.Drawing.Graphics formGraphics = canvas.CreateGraphics();)
Everything you want to be drawn must be stored in Lists of classes, sufficient to hold all info you need.
If you were to draw only Rectangles in only one pen a List<Rectangle> would be enough, but for other shapes and pens you will want to create a class to hold those data.
Now you can:
Draw them all in the Paint event, iterating the List<your DrawItemClass>
Remove or set inactive those items in the List you don't want to be drawn any longer..

Axis text isn't printed and coordinates aren't plotted

The following code will only display the standard graph, but will not print the axis text nor plot any of the coordinates.
public void JapaneseCandleStick()
{
//GraphPane myPane = base.GraphPane;
GraphPane myPane = new GraphPane();
myPane.Title.Text = "Japanese Candlestick Chart Demo";
myPane.XAxis.Title.Text = "Trading Date";
myPane.YAxis.Title.Text = "Share Price, $US";
StockPointList spl = new StockPointList();
Random rand = new Random();
// First day is jan 1st
XDate xDate = new XDate(2006, 1, 1);
double open = 50.0;
for (int i = 0; i < 50; i++)
{
double x = xDate.XLDate;
double close = open + rand.NextDouble() * 10.0 - 5.0;
double hi = Math.Max(open, close) + rand.NextDouble() * 5.0;
double low = Math.Min(open, close) - rand.NextDouble() * 5.0;
StockPt pt = new StockPt(x, hi, low, open, close, 100000);
spl.Add(pt);
open = close;
// Advance one day
xDate.AddDays(1.0);
// but skip the weekends
if (XDate.XLDateToDayOfWeek(xDate.XLDate) == 6)
xDate.AddDays(2.0);
}
JapaneseCandleStickItem myCurve = myPane.AddJapaneseCandleStick("trades", spl);
myCurve.Stick.IsAutoSize = true;
myCurve.Stick.Color = Color.Blue;
// Use DateAsOrdinal to skip weekend gaps
myPane.XAxis.Type = AxisType.DateAsOrdinal;
// pretty it up a little
myPane.Chart.Fill = new Fill(Color.White, Color.LightGoldenrodYellow, 45.0f);
myPane.Fill = new Fill(Color.White, Color.FromArgb(220, 220, 255), 45.0f);
zedGraphControl1.AxisChange();
//base.ZedGraphControl.AxisChange();
}
What is wrong with the above and why cannot I see any text or see the plots? It all compiles without errors, hence the referencing and the ZedGraph implementation/referencing seems to be in order.
when you create an instance of Graphpane it must be referenced to zedGraphControl1, use the following line of code :
GraphPane myPane = zedGraphControl1.GraphPane;
& here's the output:

ILSurface plot parameters

I'm stucked on plotting a surface in ILSurface.
The scenario is the following:
Plot an irregular grid:
float[] x = new float[sizeX]; // filled with timestamps
float[] y = new float[sizeY]; // filled with values
float[,] z = new float[sizeX, sizeY]; // filled with values mapped by [x,y]
ILInArray<float> inX = ILMath.array(x);
ILInArray<float> inY = ILMath.array(y);
ILInArray<float> inZ = ILMath.meshgrid(inX * inY, inX, inY);
// how do i fill the inZ with z[,]?
ILRetArray<float> outMesh = ILMath.meshgrid(inX, inY, inZ, null, null);
plotCube.Add(new ILSurface(outMesh, null, null, null, null));
// plotCube already attached to the scene, and the scene with the ILPanel
ilPanel1.Refresh();
Want to map this in a array so it can be plot on a ILSurface.
I've tried out some ILMath.meshgrid to fill the ILInArray<double> ZXYArray with no success.
Hope i've been clear. Any help welcome.
Thanks.
Here comes a simple example how to plot a 3D surface with ILNumerics and provide custom X and Y ranges. It expects the regular setup of a Windows.Forms application with ILNumerics:
private void ilPanel1_Load(object sender, EventArgs e) {
// define X and Y range
ILArray<float> X = ILMath.vec<float>(-10.0, 0.1, 10.0);
ILArray<float> Y = ILMath.vec<float>(-6.0, 0.1, 6.0);
// compute X and Y coordinates for every grid point
ILArray<float> YMat = 1; // provide YMat as output to meshgrid
ILArray<float> XMat = ILMath.meshgrid(X, Y, YMat); // only need mesh for 2D function here
// preallocate data array for ILSurface: X by Y by 3
// Note the order: 3 matrix slices of X by Y each, for Z,X,Y coordinates of every grid point
ILArray<float> A = ILMath.zeros<float>(Y.Length, X.Length, 3);
// fill in Z values (replace this with your own function / data!!)
A[":;:;0"] = ILMath.sin(XMat) * ILMath.sin(YMat) * ILMath.exp(-ILMath.abs(XMat * YMat) / 5);
A[":;:;1"] = XMat; // X coordinates for every grid point
A[":;:;2"] = YMat; // Y coordinates for every grid point
// setup the scene + plot cube + surface
ilPanel1.Scene = new ILScene() {
new ILPlotCube(twoDMode: false) {
new ILSurface(A) {
UseLighting = true,
Children = { new ILColorbar() }
}
}
};
}
It produces the following result:
Here is the same example as interactive Web Component.
Note the order the grid point coordinates are defined. See the documentation here: http://ilnumerics.net/surface-plots.html

Draw date on X axis and time on Y axis using ZedGraph and C#

In ZedGraph, how do I draw a time (like 00:00, 02:00, 04:00, etc.) on the Y axis and date (like 12-Apr-11, 13-Apr-11, 14-Apr-11, etc.) on the X axis?
The bar settings has been set to BarType.Stack.
Sample code will be very helpful.
Here is a sample that I constructed. I was not sure what sort of data you would plot along the Y Axis using a time format except for something like an accrued amount of time (such as number of hours employees worked).
ZedGraph uses an XDate format for time along the axes, which are doubles converted from datetimes. However in a stacked bar, I am not sure if ZedGraph can aggregate the times properly (I couldn't get it to work). Thus, in my example I used a Linear type for the Y Axis and changed the format so that it displays as hours and minutes.
Note that the min and max of both axes' scales have been set. This is especially important along the X axis, as the auto setting gets it wrong. Some of the other settings I specify clean up the minor tic marks, etc.
Here's an example showing a stacked bar graph for number of hours worked by three employees during each day:
const int NumberOfBars = 5;
GraphPane myPane = zedGraphControl1.GraphPane;
myPane.Title.Text = "Employee Hours";
myPane.BarSettings.Type = BarType.Stack;
myPane.BarSettings.ClusterScaleWidth = 1D;
// X AXIS SETTINGS
myPane.XAxis.Title.Text = "Date";
myPane.XAxis.Type = AxisType.Date;
myPane.XAxis.Scale.Format = "dd-MMM-yy";
myPane.XAxis.Scale.MajorUnit = DateUnit.Day;
myPane.XAxis.Scale.MajorStep = 1;
myPane.XAxis.Scale.Min = new XDate(DateTime.Now.AddDays(-NumberOfBars));
myPane.XAxis.Scale.Max = new XDate(DateTime.Now);
myPane.XAxis.MajorTic.IsBetweenLabels = true;
myPane.XAxis.MinorTic.Size = 0;
myPane.XAxis.MajorTic.IsInside = false;
myPane.XAxis.MajorTic.IsOutside = true;
// Y AXIS SETTINGS
myPane.YAxis.Title.Text = "Hours Worked";
myPane.YAxis.Type = AxisType.Linear;
myPane.YAxis.Scale.Format = #"00:\0\0";
myPane.YAxis.Scale.Min = 0;
myPane.YAxis.Scale.Max = 24;
myPane.YAxis.Scale.MajorStep = 1;
myPane.YAxis.MinorTic.Size = 0;
// Construct some sample data
Random r = new Random();
List<double> DatesX = new List<double>();
double[] JohnHours = new double[NumberOfBars];
double[] JoanHours = new double[NumberOfBars];
double[] JaneHours = new double[NumberOfBars];
for (int i = 0; i < NumberOfBars; i++)
{
DatesX.Add(new XDate(DateTime.Today.AddDays(-i)));
JohnHours[i] = r.Next(1, 9);
JoanHours[i] = r.Next(1, 9);
JaneHours[i] = r.Next(1, 9);
}
myPane.AddBar("John", DatesX.ToArray(), JohnHours, Color.Red);
myPane.AddBar("Joan", DatesX.ToArray(), JoanHours, Color.Blue);
myPane.AddBar("Jane", DatesX.ToArray(), JaneHours, Color.Green);

Categories