How to show a message if chart data is empty? - c#

I have WinForm with chart and database.Chart get data from database. If no data the chart isn't visible. I would like to show a message in the chart place.For example: "No data yet." Can I do it?
if (chart1["Series1"].Points.Count == 0)
{
???
}

..show a message in the chart..Can I do it?
Sure. There are in fact many ways, from setting the chart's Title to using the Paint event and DrawString or creating a TextAnnotation etc..
The two latter options are easy to center and both will keep the position even when the chart is resized.
Example 1 - A TextAnnotation:
TextAnnotation ta = new TextAnnotation();
Set it up like this:
ta.Text = "No Data Yet";
ta.X = 45; // % of the..
ta.Y = 45; // chart size
ta.Font = new Font("Consolas", 20f);
ta.Visible = false; // first we hide it
chart1.Annotations.Add(ta);
Show whenever the data are changed:
ta.Visible = (chart1.Series[seriesNameOrIndex].Points.Count == 0)
Example 2 - Drawing the messag in the Paint event:
private void chart1_Paint(object sender, PaintEventArgs e)
{
if (chart1.Series[seriesNameOrIndex].Points.Count == 0)
{
using (Font font = new Font("Consolas", 20f))
using (StringFormat fmt = new StringFormat()
{ Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center })
e.Graphics.DrawString("No data yet",
font, Brushes.Black, chart1.ClientRectangle, fmt);
}
}
This should keep itself updated as adding or removing DataPoints will trigger the Paint event.
Btw: The recommended way to test to a collection to contain any data is using the Linq Any() function:
(!chart1.Series[seriesNameOrIndex].Points.Any())
It is both as fast as possible and clear in its intent.

Related

Extra rectangle gets drawn after label

I want to include the colored panel below into my form:
For this I have created custom panel which will change Border color based on Radio Button selection. My panel code is
InfoPanel.cs
class InfoPanel : Panel
{
private Color colorBorder = Color.Transparent;
public InfoPanel()
: base()
{
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawRectangle(
new Pen(
new SolidBrush(colorBorder), 2),
e.ClipRectangle);
e.Graphics.DrawLine(new Pen(new SolidBrush(colorBorder), 0), 50, 0, 50, 50); //drawing a line to split the child & parent info panel
}
public Color BorderColor
{
get
{
return colorBorder;
}
set
{
colorBorder = value;
}
}
}
In my form,
1. created one parent Info Panel
2. created one child panel with Picture box
3. One label in parent info panel to show the information
Now for the parent panel I am changing the colors [back, border] & text based on user selection & for child panel I am not changing border color but updating back color based on user selection.
Below is the code for changing the panel colors, image, text update:
private void rbIPAddress_CheckedChanged(object sender, EventArgs e)
{
if (rbIPAddress.Checked)
{
ParentInfoPanel.BackColor = System.Drawing.ColorTranslator.FromHtml("#FFFFEE");
ParentInfoPanel.BorderColor = System.Drawing.ColorTranslator.FromHtml("#DADA85");
ChildInfoPanel.BackColor = System.Drawing.ColorTranslator.FromHtml("#F6F6D8");
InfoPanelPictureBox.Image = Template.InfoPanelInfoImage;
Infolabel.Text = "IP Address is already configured. You can switch to Forward Lookup Zone by choosing other configuration. *IP Address \ncan be either LB IP Address.";
txtBoxIPAddress.Enabled = true;
textBoxPort.Enabled = true;
}
else
{
Infolabel.Text = "";
txtBoxIPAddress.Text = "";
txtBoxIPAddress.Enabled = false;
textBoxPort.Enabled = false;
}
}
private void rbForwardLookupZone_CheckedChanged(object sender, EventArgs e)
{
if (rbForwardLookupZone.Checked)
{
ParentInfoPanel.BackColor = System.Drawing.ColorTranslator.FromHtml("#FFFFEE");
ParentInfoPanel.BorderColor = System.Drawing.ColorTranslator.FromHtml("#DADA85");
ChildInfoPanel.BackColor = System.Drawing.ColorTranslator.FromHtml("#F6F6D8");
InfoPanelPictureBox.Image = Template.InfoPanelInfoImage;
Infolabel.Text = "Forward Lookup Zone is already configured. You can switch to IP Address by choosing other configuration and \nchanging port number will affect Firewall rules.";
textBoxControlPlane.Enabled = true;
if (string.IsNullOrEmpty(textBoxControlPlane.Text))
{
textBoxControlPlane.Text = Constants.DefaultControlPlaneDomain;
}
}
else
{
Infolabel.Text = "";
textBoxControlPlane.Text = "";
textBoxControlPlane.Enabled = false;
}
}
Note: used next line character to display label text in multiple line
Output: Everything is ok but in the end of label text I am getting another rectangle box. I'm wondering why is showing like this? Am I doing wrong? Please help me on this.
The issue is that you're using e.ClipRectangle. It informs you which portion of the control needs to be redrawn. This is sometimes only a small part of the control rather than the whole thing (in your case the area of the extra rectangle). Always draw the control's full rectangle instead.
Also, you must dispose of both the Pen and SolidBrush. Failing to do so causes memory leaks. Utilize the using statement.
using(SolidBrush brush = new SolidBrush(colorBorder))
using(Pen pen = new Pen(brush, 2))
{
e.Graphics.DrawRectangle(pen, new Rectangle(0, 0, this.ClientSize.Width - 1, this.ClientSize.Height - 1));
e.Graphics.DrawLine(pen, 50, 0, 50, 50);
}

How to add data table with legend keys to a MS Chart in C#?

There are 2 lists called listversion & MIN_list. Using values of these list I have created a line chart. Everything is work fine. But I am wondering whether it is possible to add a data table with legend keys in to the chart like MS Excel.
chart.Series.Clear();
chart.ChartAreas[0].AxisX.Title = "Version";
chart.ChartAreas[0].AxisX.TitleFont = new System.Drawing.Font("Arial", 12, FontStyle.Regular);
chart.ChartAreas[0].AxisY.Title = "Time";
chart.ChartAreas[0].AxisY.TitleFont = new System.Drawing.Font("Arial", 12, FontStyle.Regular);
Series MIN = chart.Series.Add("Minimum");
MIN.Points.DataBindXY(listVersion, MIN_list[j]);
MIN.ChartType = SeriesChartType.Line;
MIN.Color = Color.Red;
MIN.BorderWidth = 3;
I am looking forward to something like this
If it is possible How can I do it ?
Thank you.
Yes you can do that:
Here are the steps I took:
First we disable the original Legend as it can't be manipulated the way we need to..:
chart1.Legends[0].Enabled = false;
Now we create a new one and a shortcut reference to it:
chart1.Legends.Add(new Legend("customLegend"));
Legend L = chart1.Legends[1];
Next we do some positioning:
L.DockedToChartArea = chart1.ChartAreas[0].Name; // the ca it refers to
L.Docking = Docking.Bottom;
L.IsDockedInsideChartArea = false;
L.Alignment = StringAlignment.Center;
Now we want to fill in one line for headers and one line per series.
I use a common function for both and pass in a flag to indicate whether the headers (x-values) or the cell data (y-values) should be filled in. Here is how I call the function:
addValuesToLegend(L, chart1.Series[0], false);
foreach (Series s in chart1.Series) addValuesToLegend(L, s, true);
Note that for this to work we need a few preparations in our Series:
We need to set the Series.Colors explicitly or else we can't refer to them.
I have added a format string to the Tag of each series; but maybe you find a better solution that avoids hard-coding the format for the header..
So here is the function that does all the filling and some styling:
void addValuesToLegend(Legend L, Series S, bool addYValues)
{
// create a new row for the legend
LegendItem newItem = new LegendItem();
// if the series has a markerstyle we show it:
newItem.MarkerStyle = S.MarkerStyle ;
newItem.MarkerColor = S.Color;
newItem.MarkerSize *= 2; // bump up the size
if (S.MarkerStyle == MarkerStyle.None)
{
// no markerstyle so we just show a colored rectangle
// you could add code to show a line for other chart types..
newItem.ImageStyle = LegendImageStyle.Rectangle;
newItem.BorderColor = Color.Transparent;
newItem.Color = S.Color;
}
else newItem.ImageStyle = LegendImageStyle.Marker;
// the rowheader shows the marker or the series color
newItem.Cells.Add(LegendCellType.SeriesSymbol, "", ContentAlignment.MiddleCenter);
// add series name
newItem.Cells.Add(LegendCellType.Text, addYValues ? S.Name : "",
ContentAlignment.MiddleLeft);
// combine the 1st two cells:
newItem.Cells[1].CellSpan = 2;
// we hide the first cell of the header row
if (!addYValues)
{
newItem.ImageStyle = LegendImageStyle.Line;
newItem.Color = Color.Transparent;
newItem.Cells[0].Tag = "*"; // we mark the 1st cell for not painting it
}
// now we loop over the points:
foreach (DataPoint dp in S.Points)
{
// we format the y-value
string t = dp.YValues[0].ToString(S.Tag.ToString());
// or maybe the x-value. it is a datatime so we need to convert it!
// note the escaping to work around my european locale!
if (!addYValues) t = DateTime.FromOADate(dp.XValue).ToString("M\\/d\\/yyyy");
newItem.Cells.Add(LegendCellType.Text, t, ContentAlignment.MiddleCenter);
}
// we can create some white space around the data:
foreach (var cell in newItem.Cells) cell.Margins = new Margins(25, 20, 25, 20);
// finally add the row of cells:
L.CustomItems.Add(newItem);
}
To draw the borders around the cells of our legend table we need to code the PrePaint event:
private void chart1_PrePaint(object sender, ChartPaintEventArgs e)
{
LegendCell cell = e.ChartElement as LegendCell;
if (cell != null && cell.Tag == null)
{
RectangleF r = e.ChartGraphics.GetAbsoluteRectangle(e.Position.ToRectangleF());
e.ChartGraphics.Graphics.DrawRectangle(Pens.DimGray,Rectangle.Round(r));
// Let's hide the left border when there is a cell span!
if (cell.CellSpan != 1)
e.ChartGraphics.Graphics.DrawLine(Pens.White,
r.Left, r.Top+1, r.Left, r.Bottom-1);
}
}
You can add more styling although I'm not sure if you can match the example perfectly..

Dynamic Chart Shows no data MSCHART, only white chart appears

Here is the Problem.
I need to create multiple charts(number is not known previously). So, I am generating dynamic Chart. The problem is, chart appears but shows no data. It is just a blank white space.
private void Form1_Load(object sender, EventArgs e)
{
Chart demo = new Chart();
demo.Location = new Point(0, 0);
demo.Size=new Size(this.Width,this.Height);
demo.Series.Add("check");
DataPoint dp1 = new DataPoint(1, 1);
DataPoint dp2 = new DataPoint(2, 2);
DataPoint dp3 = new DataPoint(3, 3);
demo.Series["check"].Points.Add(dp1);
demo.Series["check"].Points.Add(dp2);
demo.Series["check"].Points.Add(dp3);
this.Controls.Add(demo);
demo.BringToFront();
demo.Visible = true;
}
Output is just a white chart with nothing on it.
You need to create a chart area. Just add this line in your code.
demo.ChartAreas.Add("newchartarea");

Ensuring text wraps in a dataGridView column

I have dataGridView with a particular column. When I write long text in dataGridView it shows me a shortened version, with ellipses, because the column isn't wide enough to display the entire string.
| textdsadasda... |
What do I must to do if I want to dataGridView show this text in next line, or wrap the text?
| textdsadasda |
| dasdasa | (continuation of line above)
How can this be done?
There is no need to reinvent the wheel by repainting the cell.
Instead simply:
Set AutoSizeRowsMode property to AllCells. This allows row height to
grow with any wrapped text.
Set DataGridView.DefaultCellStyle.WrapMode
to DataGridViewTriState.True to wrap text in the cells.
Most importantly set DataGridView.AutoSizeColumnsMode to
DataGridViewAutoSizeColumnsMode.None so that the columns don't resize themselves
(so they remain at the user specified width).
After that the text should wrap to the next line if there is not enough space in the column.
Try setting
.AutoSizeMode to .DisplayedCells.
Set the AutoSizeRowsMode to AllCells.
DataGridView.DefaultCellStyle.WrapMode to DataGridViewTriState.True
You can try setting the DataGridView.DefaultCellStyle.WrapMode to DataGridViewTriState.True
May be handling cell painting event can help you
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.Value == null)
return;
var s = e.Graphics.MeasureString(e.Value.ToString(), dataGridView1.Font);
if (s.Width > dataGridView1.Columns[e.ColumnIndex].Width)
{
using (
Brush gridBrush = new SolidBrush(this.dataGridView1.GridColor),
backColorBrush = new SolidBrush(e.CellStyle.BackColor))
{
e.Graphics.FillRectangle(backColorBrush, e.CellBounds);
e.Graphics.DrawString(e.Value.ToString(), dataGridView1.Font, Brushes.Black, e.CellBounds,StringFormat.GenericDefault);
dataGridView1.Rows[e.RowIndex].Height = (int)(s.Height * Math.Ceiling( s.Width / dataGridView1.Columns[e.ColumnIndex].Width)) ;
e.Handled = true;
}
}
}
I've found #DeveloperX answer really useful, but with a couple of hiccups:
It causes some rows to flicker, if there is more than one cell that needs wrapping
Some cells have the last line missing or truncated (this happens if there are long words which can't be wrapped inside the text)
And it also caused missing cell borders (but this depends on grid/cell border settings).
I did a rework of #DeveloperX code to solve this issues, and came up with the following code:
private int _rowMaxHeight = 0;
private int _rowDefaultHeight = 0;
private void dataGridView1_CellPainting(object sender,
DataGridViewCellPaintingEventArgs e)
{
if (e.Value == null || e.RowIndex < 0)
{
// The WordWrap code is ony executed if requested the cell has a value,
// and if this is not the heading row.
return;
}
if (e.ColumnIndex == 0)
{
// Resetting row max height on each row's first cell
_rowMaxHeight = 0;
if (_rowDefaultHeight == 0)
{
/* The default DataGridView row height is saved when the first cell
* inside the first row is populated the first time. This is later
* used as the minimum row height, to avoid
* smaller-than-default rows. */
_rowDefaultHeight = dataGridView1.Rows[e.RowIndex].Height;
}
}
// Word wrap code
var sOriginal = e.Graphics.MeasureString(e.Value.ToString(),
dataGridView1.Font);
var sWrapped = e.Graphics.MeasureString(e.Value.ToString(),
dataGridView1.Font,
// Is is MeasureString that determines the height given the width, so
// that it properly takes the actual wrapping into account
dataGridView1.Columns[e.ColumnIndex].Width);
if (sOriginal.Width != dataGridView1.Columns[e.ColumnIndex].Width)
{
using (Brush gridBrush = new SolidBrush(this.dataGridView1.GridColor),
backColorBrush = new SolidBrush(e.CellStyle.BackColor),
fontBrush = new SolidBrush(e.CellStyle.ForeColor))
{
e.Graphics.FillRectangle(backColorBrush, e.CellBounds);
// The DrawLine calls restore the missing borders: which borders
// miss and how to paint them depends on border style settings
e.Graphics.DrawLine(new Pen(gridBrush, 1),
new Point(e.CellBounds.X - 1,
e.CellBounds.Y + e.CellBounds.Height - 1),
new Point(e.CellBounds.X + e.CellBounds.Width - 1,
e.CellBounds.Y + e.CellBounds.Height - 1));
e.Graphics.DrawLine(new Pen(gridBrush, 1),
new Point(e.CellBounds.X + e.CellBounds.Width - 1,
e.CellBounds.Y - 1),
new Point(e.CellBounds.X + e.CellBounds.Width - 1,
e.CellBounds.Y + e.CellBounds.Height - 1));
//Updating the maximum cell height for wrapped text inside the row:
// it will later be set to the row height to avoid the flickering
// that would occur by setting the height multiple times.
_rowMaxHeight = (Math.Ceiling(sWrapped.Height) > _rowMaxHeight)
? (int)Math.Ceiling(sWrapped.Height) : _rowMaxHeight;
// The text is generated inside the row.
e.Graphics.DrawString(e.Value.ToString(), dataGridView1.Font,
fontBrush, e.CellBounds, StringFormat.GenericDefault);
e.Handled = true;
}
}
if (e.ColumnIndex == dataGridView1.ColumnCount -1
&& _rowMaxHeight > 0
&& _rowMaxHeight != dataGridView1.Rows[e.RowIndex].Height)
{
// Setting the height only in the last cell, when the full row has been
// painted, helps to avoid flickering when more than one row
// needs the wrap.
dataGridView1.Rows[e.RowIndex].Height =
(_rowMaxHeight > _rowDefaultHeight)
? _rowMaxHeight : _rowDefaultHeight;
}
}
Note that there is one problem still unresolved with this code: the text is not vertically centered anymore inside the cells!
Does setting this value help in achieving the display as you want
dataGridView1.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.DisplayedCells;
in addition to setting the WrapMode = DataGridViewTriState.True;
I found DeveloperX's answer to be very good. But I found I needed to tweak it a little. Firstly, I needed to ensure that the column in question was not in AutoSizeMode:
if (dgv.Columns[e.ColumnIndex].AutoSizeMode != DataGridViewAutoSizeColumnMode.None)
throw new InvalidOperationException(String.Format("dgv {0} AutoSizeMode <> 'None'", dgv.Columns[e.ColumnIndex].Name));
I also found that using
var s = e.Graphics.MeasureString(e.Value.ToString(), dataGridView1.Font);
returns a string length, which can't be used for comparison to ColumnWidth, which is given in pixels. So, using How can I convert a string length to a pixel unit?, I modified the above line of code to be
var s = e.Graphics.MeasureString(e.Value.ToString(), new Font("Segoe UI", 11, FontStyle.Regular, GraphicsUnit.Pixel));
I also found that doing a direct comparison of widths was insufficient to determine when to prevent clipping - there were some edge cases that weren't being caught. So, I replaced
if (s.Width > dataGridView1.Columns[e.ColumnIndex].Width)
with a ratio comparison (cutoff value determined by experiment) :
if (e.Value.ToString().Length / (double)dataGridView1.Columns[e.ColumnIndex].Width >= .189)
Finally, the cell in the selected row was not hightlighted, so I added the following:
SolidBrush backColorBrush;
if (dataGridView1.SelectedRows[0].Index == e.RowIndex)
backColorBrush = new SolidBrush(e.CellStyle.SelectionBackColor);
else
backColorBrush = new SolidBrush(e.CellStyle.BackColor);
Final code:
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.Value == null || e.RowIndex == -1)
return;
if (dataGridView1.Columns[e.ColumnIndex].AutoSizeMode != DataGridViewAutoSizeColumnMode.None)
throw new InvalidOperationException(Format("dataGridView1 {0} AutoSizeMode <> 'None'", dataGridView1.Columns[e.ColumnIndex].Name));
var s = e.Graphics.MeasureString(e.Value.ToString(), new Font("Segoe UI", 11, FontStyle.Regular, GraphicsUnit.Pixel));
if (e.Value.ToString().Length / (double)dataGridView1.Columns[e.ColumnIndex].Width >= .189)
{
SolidBrush backColorBrush;
if (dataGridView1.SelectedRows[0].Index == e.RowIndex)
backColorBrush = new SolidBrush(e.CellStyle.SelectionBackColor);
else
backColorBrush = new SolidBrush(e.CellStyle.BackColor);
using (backColorBrush)
{
e.Graphics.FillRectangle(backColorBrush, e.CellBounds);
e.Graphics.DrawString(e.Value.ToString(), dataGridView1.Font, Brushes.Black, e.CellBounds, StringFormat.GenericDefault);
dataGridView1.Rows[e.RowIndex].Height = System.Convert.ToInt32((s.Height * Math.Ceiling(s.Width / (double)dataGridView1.Columns[e.ColumnIndex].Width)));
e.Handled = true;
}
}
}
To do wrapping add this after binding
DataGrid1.ItemStyle.Wrap = true;
I agree with the answer that discussed simply setting the WordWrap on the cell and would add to it this scenario.
I was needing to change the colors and font styles on the fly based on the data in each cell. Initially I thought that I was stuck figuring out how to make the DrawString work with wrapping inside CellPainting event due to needing different text colors.
However, in the end I just set the Cell.Style properties inside the CellPainting event and then exited the event without setting the e.Handled = true. This way the grid's paint event used the styles I set for each cell and the text wrapped properly.
For example:
datagrid1[e.ColumnIndex, e.RowIndex].Style.BackColor = Color.Green;

Printing chart in c#

I am able to print a chart from my c# project using:
chart1.Printing.PrintDocument.DocumentName = "Graph of data";
But is it possible to add a title to this? I was hoping the document name would achieve this, but apparently not!
You can print whatever you want directly to the page and then invoke the chart PrintPaint(). Note that if you don't switch the PageUnit to Pixels that the chart scaling gets confused.
void PrintChart(object sender, PrintPageEventArgs ev)
{
using (var f = new System.Drawing.Font("Arial", 10))
{
var size = ev.Graphics.MeasureString(Text, f);
ev.Graphics.DrawString("Whatever text you want", f, Brushes.Black, ev.PageBounds.X + (ev.PageBounds.Width - size.Width) / 2, ev.PageBounds.Y);
}
//Note, the chart printing code wants to print in pixels.
Rectangle marginBounds = ev.MarginBounds;
if (ev.Graphics.PageUnit != GraphicsUnit.Pixel)
{
ev.Graphics.PageUnit = GraphicsUnit.Pixel;
marginBounds.X = (int)(marginBounds.X * (ev.Graphics.DpiX / 100f));
marginBounds.Y = (int)(marginBounds.Y * (ev.Graphics.DpiY / 100f));
marginBounds.Width = (int)(marginBounds.Width * (ev.Graphics.DpiX / 100f));
marginBounds.Height = (int)(marginBounds.Height * (ev.Graphics.DpiY / 100f));
}
chart1.Printing.PrintPaint(ev.Graphics, marginBounds);
}
This menu handler opens a PrintDialog(). If you don't want a dialog you can just call pd.Print().
private void printToolStripMenuItem_Click(object sender, EventArgs e)
{
var pd = new System.Drawing.Printing.PrintDocument();
pd.PrintPage += new PrintPageEventHandler(PrintChart);
PrintDialog pdi = new PrintDialog();
pdi.Document = pd;
if (pdi.ShowDialog() == DialogResult.OK)
pdi.Document.Print();
}
Here is a workaround solution to your problem, if you place the ChartingControl inside a Panel control on the Windows Form. You can then print the panel, inside the panel you can add the document heading as a label and whatever other stuff you want to add.
Firstly from the toolbox add a PrintDocument control and call it MyPrintDocument
Then add a Panel control and put your chart inside it.
Make sure you have imported the System.Drawing namespace, then you can print the panel like this.
Bitmap MyChartPanel = new Bitmap(panel1.Width, panel1.Height);
panel1.DrawToBitmap(MyChartPanel, new Rectangle(0, 0, panel1.Width, panel1.Height));
PrintDialog MyPrintDialog = new PrintDialog();
if (MyPrintDialog.ShowDialog() == DialogResult.OK)
{
System.Drawing.Printing.PrinterSettings values;
values = MyPrintDialog.PrinterSettings;
MyPrintDialog.Document = MyPrintDocument;
MyPrintDocument.PrintController = new System.Drawing.Printing.StandardPrintController();
MyPrintDocument.Print();
}
MyPrintDocument.Dispose();
This code converts the panel into a Bitmap and then prints that Bitmap.
You could condense this into a function like:
public void PrintPanel(Panel MyPanel)
{
// Add code from above in here, changing panel1 to MyPanel...
}

Categories

Resources