C# MS Chart Control - two questions - c#

So I've made this range bar chart with the MS Chart Control. I have two questions:
How can I implement an event handler for when the user double clicks a series? I can't see one anywhere.
For some reason the scrollbar on my X Axis (which, amusingly, the Chart Control seems to think is the Y Axis...) seems to be partially transparent for some reason...can anyone shed any light as to why that might be?
Here's my code so far, bastardised from a PDF I found somewhere on the net (yeah, I know, it's messy, needs tidying up):
private void PopulateGantt()
{
foreach (Job jobThis in lstJobs)
{
if ((jobThis.HireFrom != null) && (jobThis.HireTo != null))
{
string xlabel = string.Empty;
double xordinal = 0;
double yplot1 = 0;
double yplot2 = 0;
yplot1 = (double)((DateTime)jobThis.HireFrom).ToOADate();
yplot2 = (double)((DateTime)jobThis.HireTo).ToOADate()+1;
// Use a different series for each datapoint
Series seriesInstance = new Series();
// For Gantt charts, we want a RangeBar graph type
seriesInstance.ChartType = SeriesChartType.RangeBar;
// Have a start and end date so plotting 2 points on the y-axis
seriesInstance.YValuesPerPoint = 2;
// We want to draw datapoint side by side (night is day?)
seriesInstance.CustomProperties = "DrawSideBySide=false";
// Add the datapoint to the series, specifying tooltiptext, colorand label
xordinal = lstJobs.IndexOf(jobThis); //(double)itemIndex;
seriesInstance.Points.AddXY(xordinal, yplot1, yplot2);
//seriesInstance.Points[0].Color = resourceColor;
seriesInstance.Points[0].AxisLabel = xlabel;
seriesInstance.Label = jobThis.Number + jobThis.Type + " - " + jobThis.ClientCompanyName;
seriesInstance.Points[0].ToolTip = jobThis.Number + jobThis.Type +
"\r\n\r\n" + jobThis.ClientCompanyName +
"\r\n\r\n" + jobThis.BriefDescription;
seriesList.Add(seriesInstance);
}
chtHC.Series.Clear();
foreach (Series plotSeries in seriesList)
{
chtHC.Series.Add(plotSeries);
}
// Force x-axis to show each task or resource
chtHC.ChartAreas[0].AxisX.Interval = 1;
// Set y-axis to show each day of the month
chtHC.ChartAreas[0].AxisY.Interval = 1;
// Set x-axis to show in reversed order so dates are displayed leftto-right
//chtHC.ChartAreas[0].AxisY.IsReversed = true;
//chtHC.ChartAreas[0].AxisX
// Set other y-axis properties
chtHC.ChartAreas[0].AxisY.IsStartedFromZero = false;
chtHC.ChartAreas[0].AxisY.IsMarginVisible = false;
chtHC.ChartAreas[0].AxisY.IntervalType = DateTimeIntervalType.Days;
// Set the y-axis labels
DateTime? datFirst = null;// = new DateTime();
DateTime? datLast = null; //= new DateTime();
//datFirst = (DateTime)lstJobs[0].HireFrom;
foreach (Job jobFirst in lstJobs)
{
if (jobFirst.HireFrom != null)
{
if (datFirst == null)
{
datFirst = (DateTime)jobFirst.HireFrom;
}
else
{
if (jobFirst.HireFrom < datFirst)
{
datFirst = (DateTime)jobFirst.HireFrom;
}
}
}
if (jobFirst.HireTo != null)
{
if (datLast == null)
{
datLast = (DateTime)jobFirst.HireTo;
}
else
{
if (jobFirst.HireTo > datLast)
{
datLast = (DateTime)jobFirst.HireTo;
}
}
}
}
if ((datFirst != null))
{
//datLast = ((DateTime)datFirst).AddDays(21);
chtHC.ChartAreas[0].AxisY.Minimum = ((DateTime)datFirst).AddDays(-1).ToOADate();
chtHC.ChartAreas[0].AxisY.Maximum = ((DateTime)datLast).AddDays(+1).ToOADate();
}
chtHC.ChartAreas[0].CursorY.AutoScroll = true;
chtHC.ChartAreas[0].AxisY.ScaleView.Zoomable = true;
chtHC.ChartAreas[0].AxisY.ScaleView.SizeType = DateTimeIntervalType.Days;
//chtHC.ChartAreas[0].AxisY.LabelStyle.Format = "MMM dd ddd";
//chtHC.ChartAreas[0].AxisY.LabelStyle.Format = "ddd MMM dd";
chtHC.ChartAreas[0].AxisY.LabelStyle.Format = "ddd dd/MM/yyyy";
//chtHC.ChartAreas[0].AxisX.// Force redraw of chart
chtHC.ChartAreas[0].AxisY.ScaleView.Zoom(chtHC.ChartAreas[0].AxisY.Minimum, chtHC.ChartAreas[0].AxisY.Minimum+21);
chtHC.ChartAreas[0].AxisY.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chtHC.ChartAreas[0].AxisY.ScaleView.SmallScrollSize = 1;
chtHC.Update();
}
}

There's no specific event able to handle datapoint clicks, but you can use MouseClick event plus HitTest method, e.g.:
void chart1_MouseClick(object sender, MouseEventArgs e)
{
var pos = e.Location;
var results = chart1.HitTest(pos.X, pos.Y,false, ChartElementType.DataPoint);
foreach (var result in results)
{
if (result.ChartElementType == ChartElementType.DataPoint)
{
// use result.Series etc...
}
}
}

the chart has also a double click event
private void chart_MouseDoubleClick(object sender, MouseEventArgs e)

Related

How to display tooltips with various data in MS charts

I am developing a candle chart in C #.
I've been working on creating candle charts using data from the current datagridview.
Additionally, when I place the cursor over the candle point of the chart, I want to show the information (open, close, high, low) of the datagridview. (See image)
Currently developed source.
DataTable table_ChartData = new DataTable();
table_ChartData.Columns.Add("Id");
table_ChartData.Columns.Add("Open");
table_ChartData.Columns.Add("Close");
table_ChartData.Columns.Add("High");
table_ChartData.Columns.Add("Low");
table_ChartData.Columns.Add("Day");
dataGridView1.DataSource = table_ChartData;
chart1.ChartAreas["ChartArea1"].AxisX.MajorGrid.LineWidth = 1;
chart1.ChartAreas["ChartArea1"].AxisY.MajorGrid.LineWidth = 1;
chart1.ChartAreas["ChartArea1"].AxisY.Maximum = max;
chart1.ChartAreas["ChartArea1"].AxisY.Minimum = min;
chart1.Series["Daily"].XValueMember = "Day";
chart1.Series["Daily"].YValueMembers = "High,Low,Open,Close";
chart1.Series["Daily"].XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Date;
chart1.Series["Daily"].CustomProperties = "PriceDownColor=Blue,PriceUpColor=Red";
chart1.Series["Daily"]["OpenCloseStyle"] = "Triangle";
chart1.Series["Daily"]["ShowOpenClose"] = "Both";
chart1.DataSource = table_ChartData;
chart1.DataBind();
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
Point mousePoint = new Point(e.X, e.Y);
chart1.ChartAreas[0].CursorX.SetCursorPixelPosition(mousePoint, true);
chart1.ChartAreas[0].CursorY.SetCursorPixelPosition(mousePoint, true);`
var pos = e.Location;
if (prevPosition.HasValue && pos == prevPosition.Value)
return;
tooltip.RemoveAll();
prevPosition = pos;
var results = chart1.HitTest(pos.X, pos.Y, false, ChartElementType.DataPoint); // set ChartElementType.PlottingArea for full area, not only DataPoints
foreach (var result in results)
{
if (result.ChartElementType == ChartElementType.DataPoint) // set ChartElementType.PlottingArea for full area, not only DataPoints
{
var yVal = result.ChartArea.AxisY.PixelPositionToValue(pos.Y);
tooltip.Show(((int)yVal).ToString(), chart1, pos.X, pos.Y - 15);
}
}
}
Thank you for your help. Thank you :)
You are creating ToolTips in the MouseMove; this is one way but the easiest approach is to let the Chart do the work by setting the DataPoint.Tooltip property right when you create the DataPoints..:
DataPoint dp = new DataPoint (..);
dp.ToolTip = "x=" + dp.XValue + "\n high=" + dp.YValues[0]+ "\n low=" + dp.YValues[1] + ..;
yourSeries.Points.Add(dp);
.. or, if the Points are DataBound either add ToolTips right after you bind or, maybe include them in the binding itself.
Note that only some of the various data binding methods will let you bind 'extended chart properties' like tooltips. Points.DataBind is mentioned explicitly. This means that you need a prepared field for the tooltips in your datasource as I know of no way to write a concatenating expression in the otherField string..
If you have your data in a DataTable with the fields below you can use syntax like this for the binding:
var enumerableTable = (dt as System.ComponentModel.IListSource).GetList();
yourSeries.Points.DataBind(enumerableTable, "x-col",
"highField, lowField..", "Tooltip=tooltipField");
If you want to do it in the MouseMove you can easily get a reference to the DataPoint you are over, if any, and work with all its values like above..:
DataPoint dp = null;
if (results.PointIndex >= 0 && results.ChartElementType == ChartElementType.DataPoint)
{
dp = results.Series.Points[hitt.PointIndex];
string tText = "x=" + dp.XValue + "\n high=" +
dp.YValues[0]+ "\n low=" + dp.YValues[1] + ..;
..
}
Note that the HitTest result is one object with several properties. No need to loop over it!

C# DataGridView

I have been tasked with creating a somewhat hierarchical datagridview for my company. I heavily modified one from Syed Shanu that is posted here https://www.codeproject.com/Articles/848637/Nested-DataGridView-in-windows-forms-csharp. I'm almost done (data loads properly, etc.), however I cannot for the life of me figure out how to get the detail grid to move when I scroll. It's a drawn on rectangle and I'm looking for a way to somehow bind it to the master grid so it scrolls up and down with the regular grid. Any help would be appreciated. Here is the code that draws the rectangle:
private void masterDGVs_CellContentClick_Event(object sender, DataGridViewCellEventArgs e)
{
DataGridViewImageColumn cols = (DataGridViewImageColumn)MasterDGVs.Columns[0];
MasterDGVs.Rows[e.RowIndex].Cells[0].Value = Image.FromFile(#"expand.png");
if (e.ColumnIndex == gridColumnIndex)
{
if (ImageName == #"expand.png")
{
DetailDGVs.Visible = true;
ImageName = #"toggle.png";
MasterDGVs.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = Image.FromFile(ImageName);
String FilterExpression = MasterDGVs.Rows[e.RowIndex].Cells[FilterColumnName].Value.ToString();
MasterDGVs.Controls.Add(DetailDGVs);
Rectangle DGVRectangle = MasterDGVs.GetCellDisplayRectangle(1, e.RowIndex, true);
DetailDGVs.Size = new Size(MasterDGVs.Width - 48, DetailDGVs.PreferredSize.Height - 16);
DetailDGVs.Location = new Point(DGVRectangle.X, DGVRectangle.Y + 20);
DataView detailView = new DataView(DetailGridDT);
detailView.RowFilter = FilterColumnName + " = '" + FilterExpression + "'";
foreach (DataGridViewRow row in DetailDGVs.Rows)
{
if (row.Cells[5].Value.ToString() == "Error")
{
row.Cells[5].Style.ForeColor = Color.DarkRed;
}
else if (row.Cells[5].Value.ToString() == "Processed and Complete")
{
row.Cells[5].Style.ForeColor = Color.Green;
}
else
{
row.Cells[5].Style.ForeColor = Color.Yellow;
}
}
}
else
{
ImageName = #"expand.png";
MasterDGVs.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = Image.FromFile(ImageName);
DetailDGVs.Visible = false;
}
}
else
{
DetailDGVs.Visible = false;
}
}
I have sort of working by adding:
MasterDGVs.MouseWheel += new MouseEventHandler(DetailDGV_Scroll);
DetailDGVs.MouseWheel += new MouseEventHandler(MasterDGV_Scroll);
and
private void DetailDGV_Scroll(object sender, MouseEventArgs e)
{
int scale = e.Delta * SystemInformation.MouseWheelScrollLines / 5;
DetailDGVs.Top = DetailDGVs.Top + scale;
}
private void MasterDGV_Scroll(object sender, MouseEventArgs e)
{
int scale = e.Delta * SystemInformation.MouseWheelScrollDelta / 5;
MasterDGVs.Top = MasterDGVs.Top - scale;
}

Reloading Form in C#

I am trying to create a calendar using c# that contains a tab for each month of the year with buttons representing the days located on the tabs (see attached picture). The user can input the desired year into a text box and click a button to submit their request (not on attached picture). Currently, I have the calendar working but I cannot figure out how to redraw the calendar when different years are submitted.
I have tried to follow this example https://stackoverflow.com/a/33104430 but I don’t understand when Form_Load() should be called. I have also tried this.refresh() at various places with no avail.
Any help would be much appreciated.
public Form1()
{
InitializeComponent();
call_on_load();
}
private void call_on_load()
{
pages = tabControl.TabPages;
year = Convert.ToInt16(textBoxYear.Text);
dt = new DateTime(year, 1, 1);
day = -1;
foreach (TabPage page in pages) //declare a page object and cycle through each tab page
{
if (!initialMonth)
{
mth++; //inc month if not first time. Originally set.
}
initialMonth = false;
if (mth > 12) //make a 1 year calendar
break;
//ftime = true;
Console.WriteLine("********************************The date is:" + dt.ToString());
x = ((((int)dt.DayOfWeek) * 75) + 10); //reset x coordinate
y = 20;
for (int rows = 1; rows <= 7; rows++) // # of rows in a month
{ //Some months have 6 rows. Use 7 to ensure the below break statement
if (!ftime)
{
if (dt.Day == 1) //at the top of another month
{
ftime = true;
break;
}
}
ftime = false;
y += 75; //move y coordinate
for (int col = 1; col <= 7; col++) //make 7 columns
{
Button b = new Button();
b.Name = dt.ToString("MMMM") + "_" + Convert.ToString(dt.Day) + "_" + dt.ToString("yyyy"); //store the date in the button name to parse
b.Click += (s, e) => //https://stackoverflow.com/questions/6187944/how-can-i-create-dynamic-button-click-event-on-dynamic-button
{
secondForm = new Form2();
String[] date = b.Name.Split('_');
secondForm.setDate(date[0], Convert.ToInt16(date[1]), Convert.ToInt16(date[2]));
secondForm.Show();
};
b.Size = new Size(50, 50);
b.Left = x;
b.Top = y;
page.Controls.Add(b); //add button to current tab page
// btnInt++;
b.Text = Convert.ToString(dt.Day);
getDate();
Console.WriteLine("The date is:" + dt.ToString());
dt = dt.AddDays(1);
if (dt.Day == 1)
break;
x += 75;
if (x > 460) //if x coordinate is at the end of the line
{
x = 10;
break;
}
}
}
}
}
private void btnSubmitF1_Click(object sender, EventArgs e)
{
year = Convert.ToInt16(textBoxYear.Text);
//this.Refresh(); //does not redraw
call_on_load(); //keeps original layout, does not redraw on button click
//Form_Load(btnSubmitF1,e); //probably not calling this method correctly. Is this method needed?
//this.Refresh(); //does not redraw
}
private void Form_Load(object sender, EventArgs e)
{
call_on_load();
}
I don't think the issue is to do with refreshing the page. I think you're simply not resetting the mth variable and then the if (mth > 12) is always being hit. However, you haven't shown enough code for us to tell for sure.
Also, your code doesn't seem to be very well structured. There's lots of stuff going on that could cause you grief.
To help out I re-wrote the code for you in a way that I think would help.
Try this:
private void call_on_load()
{
var year = Convert.ToInt16(textBoxYear.Text);
var dt = new DateTime(year, 1, 1);
var months =
Enumerable
.Range(0, dt.AddYears(1).Subtract(dt).Days)
.Select(d => dt.AddDays(d))
.GroupBy(x => x.Month);
foreach (var month in months)
{
var tab = tabControl.TabPages[month.Key - 1];
tab.Controls.Clear();
var firstDayOfWeek = (int)month.First().DayOfWeek;
foreach (var date in month)
{
var position = firstDayOfWeek + date.Day - 1;
var button = new Button()
{
Size = new Size(50, 50),
Left = (position % 7) * 75 + 10,
Top = (position / 7) * 75 + 20,
Text = date.ToShortDateString(),
};
button.Click += (s, e) =>
{
var secondForm = new Form2();
secondForm.setDate(date);
secondForm.Show();
};
tab.Controls.Add(button);
}
}
}
I tested this and it seemed to work just fine.
//you just missing a postback maybe, try this.
private void Form_Load(object sender, EventArgs e)
{
if(!IsPostBack){
call_on_load();
}
}
edited
private void btnSubmitF1_Click(object sender, EventArgs e)
{
year = Convert.ToInt16(textBoxYear.Text);
//this.Refresh(); //does not redraw
call_on_load(); //keeps original layout,
//does not redraw on button click
//Form_Load(btnSubmitF1,e); //probably not calling
//this method correctly. Is this method needed?
//this.Refresh(); //does not redraw
this.ParentForm.Refresh();
}

Unable to Plot Serial Data on Y2 Axis Zedgraph

I am making a GUI that reads in serial data, and I want to have the option to choose to plot the data on either the left or right Y axis. I can plot the data on the left y axis, but I am unable to plot it on the Left axis. What is wrong with my code?
Here is the relevant Code:
GraphPane myPane2;
PointPairList inst3time = new PointPairList();
myPane2 = zedGraphControl2.GraphPane;
myPane2.Title = "Data vs Time Plots";
myPane2.XAxis.Title = "Elapsed Minutes";
private void UpdateData3(string line)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new UpdateDataDelegate(UpdateData3), new object[] { line });
}
else
{
if (chk_DISPLAY_3.Checked == true)
{
timer3.Interval = (30000);
timer3.Start();
OZ1lastdatatime = DateTime.Now;
count++;
if (count > 7)
{
count = 0;
TextBox_3.Text = "";
TextBox_3.AppendText(line);
}
else
{
TextBox_3.AppendText(line);
}
}
if (chk_SAVE_FILE_3.Checked == true)
{
StoreData3.Write(line);
StoreData3.Flush();
}
if (chk_PLOT_3.Checked == true)
{
string[] blahArray = line.Split(new char[] { ',' });
//string blaharray = Convert.ToDouble(blahArray[2]).ToString("F4");
int column_data = Convert.ToInt32(textBox5.Text);
double inst3 = Convert.ToDouble(blahArray[column_data]);
//TextBox_3.Text = Convert.ToString(oz1);
TimeSpan span = DateTime.UtcNow - startDateTimeOfProgram;
double elapsedMinutes = span.TotalMinutes;
inst3time.Add(elapsedMinutes,inst3);
if (cbo_POSITION_3.Text == "LEFT")
{
myPane2.YAxis.Title = cbo_Instrument_3.Text;
zedGraphControl2.AxisChange();
zedGraphControl2.GraphPane.AddCurve("",inst3time, Color.Blue, SymbolType.Circle);
zedGraphControl2.Refresh();
}
if (cbo_POSITION_3.Text == "RIGHT")
{
myPane2.Y2Axis.Title = cbo_Instrument_3.Text;
zedGraphControl2.AxisChange();
LineItem myCurve = zedGraphControl2.GraphPane.AddCurve("",inst3time, Color.Blue, SymbolType.Circle);
myCurve.IsY2Axis = true;
zedGraphControl2.Refresh();
}
}
}
}
I was able to figure it out: I needed to make sure that the Y2 Axis was visible:
myPane2.Y2Axis.IsVisible = true;
myPane2.Y2Axis.Title = cbo_Instrument_3.Text;
zedGraphControl2.AxisChange();
LineItem myCurve = myPane2.AddCurve("",inst3time, Color.Blue, SymbolType.Circle);
myCurve.IsY2Axis = true;
zedGraphControl2.AxisChange();
zedGraphControl2.Refresh();

View #VAL label on Pie Chart and NOT inside the Series when slice selected = true

I have a pie chart I created using mschart and I, for now, have hard-coded the data populated. When selected a slice, I want a label to appear displaying the value - it does this now BUT aswell as displaying on the chart (correct), it displays in the series (incorrect).
Pseudo Code
//Diplay chart and data
//Highlight over slices and get #PERCENT (also when selected slice)
//Select slice
//Expand slice
//Display #VAL on slice & NOT in Series
//Re-select slice
//Minimise slice
I have two methods - MouseDown and MouseMove: (The #PERCENT tooltip works fine)
private void PieChart_MouseDown(object sender, MouseEventArgs e)
{
HitTestResult result = PieChart.HitTest(e.X, e.Y);
// Exit event if no item was clicked on
if (result.PointIndex < 0)
{
return;
}
// Check if data point is already exploded
bool exploded = (PieChart.Series[0].Points[result.PointIndex].CustomProperties == "Exploded=true");
// Remove all exploded attributes
foreach (DataPoint p in PieChart.Series[0].Points)
{
p.CustomProperties = "PieLabelStyle = Disabled, Exploded = False";
p.Label = "";
p.ToolTip = "";
}
// If data point is already exploded get out
if (exploded)
{
return;
}
// If data point is selected or if legend item is selected
if (result.ChartElementType == ChartElementType.DataPoint || result.ChartElementType == ChartElementType.LegendItem)
{
DataPoint point = PieChart.Series[0].Points[result.PointIndex];
point.Label = "Value: #VAL";
point.LabelBackColor = Color.Bisque;
point.LabelBorderColor = Color.Black;
point.Font = new Font("Calibri Light", 8);
point.CustomProperties = "PieLabelStyle = Inside, Exploded = True";
}
}
private void PieChart_MouseMove(object sender, MouseEventArgs e)
{
HitTestResult result = PieChart.HitTest(e.X, e.Y);
foreach (DataPoint point in PieChart.Series[0].Points) // ALL DATA POINTS - SECTIONS OF CHART
{
point.BackSecondaryColor = Color.Black;
point.BackHatchStyle = ChartHatchStyle.None;
point.BorderWidth = 1;
}
// If a Data Point or a Legend item is selected
if (result.ChartElementType == ChartElementType.DataPoint || result.ChartElementType == ChartElementType.LegendItem)
{
this.Cursor = Cursors.Hand;
DataPoint point = PieChart.Series[0].Points[result.PointIndex];
point.ToolTip = "Percentage: #PERCENT";
point.BackSecondaryColor = Color.White;
point.BackHatchStyle = ChartHatchStyle.Percent25;
point.BorderWidth = 2;
}
else
{
this.Cursor = Cursors.Default;
}
}
What it does now:-
What I want it to do:-
Notice series7 is now visible and NOT the value - CORRECT!
Attempted solutions:
PieChart.Series[0].Points.Add(new DataPoint(1, 1) { LegendText = "aaa" });
point.Label = "Value: #VAL";
point.LegendText = "Series#INDEX";
Simply use DataPoint's LegendText property when populating chart's data:
PieChart.Series[0].Points.Add(new DataPoint(1, 1) { LegendText = "aaa" });
PieChart.Series[0].Points.Add(new DataPoint(1, 2) { LegendText = "bbb" });
PieChart.Series[0].Points.Add(new DataPoint(1, 3) { LegendText = "ccc" });
Alternative way is to set LegendText each time you set Label:
point.Label = "Value: #VAL";
point.LegendText = "Series#INDEX"; // Or whatever you want

Categories