I've tried without luck do make such graph in WinForms with Chart control.Graph with uneven intervals on y axis My first idea was to use pointed Chart with images for empty and not empty marker. The lines are displayed, but unfortunatelly there is a big space between values 4 - 19 and 20 - 33. Is there a way to avoid the space when using standard WinForms chart Control?
My second idea was to use a DatagridView. I could add not more that something aboit 700 columns. I would need almost 2000 columns. Thus the DataGridView isn't best choice for me.
Thank for any hint how to achieve it.
Edit 1:
As sugested by TnTinMn I've tried ScaleBreakStyle. This seems to remove some spaces from the graph. At the moment my graph looks like this:
This was achieved by the Code:
var chartArea = chart1.ChartAreas[series.ChartArea];
chartArea.AxisX.LabelStyle.IsStaggered = false;
chartArea.AxisX.Minimum = 0;
chartArea.AxisX.Maximum = (int)numericUpDown1.Value;
chartArea.AxisX.IsReversed = true;
chartArea.AxisX.Interval = 1;
chartArea.AxisX.ScaleView.SizeType = DateTimeIntervalType.Number;
chartArea.AxisY.ScaleBreakStyle.Enabled = true;
chartArea.AxisY.ScaleBreakStyle.BreakLineStyle = BreakLineStyle.None;
chartArea.AxisY.ScaleBreakStyle.Spacing = 0;
chartArea.AxisY.ScaleBreakStyle.CollapsibleSpaceThreshold = 50;
chartArea.AxisY.Interval = 1;
If I try to put the y labels on the left side, it doesn't work
chartArea.AxisY.Enabled = AxisEnabled.False;
chartArea.AxisY2.Enabled = AxisEnabled.True;
Adding folowing code doesn't solve the problem:
chartArea.AxisY2.ScaleBreakStyle.Enabled = true;
chartArea.AxisY2.ScaleBreakStyle.BreakLineStyle = BreakLineStyle.None;
chartArea.AxisY2.ScaleBreakStyle.Spacing = 0;
chartArea.AxisY2.ScaleBreakStyle.CollapsibleSpaceThreshold = 50;
chartArea.AxisY2.Interval = 1;
How can I remove 26 and 28, and leave only 1 and 27 on the chart? How can I put 1 and 27 on the bottom and leave a "big" space above them?
Edit 2:
After some tryies I've achieved what I wanted(I still have to adjust the marker images, but it looks very promising).
This is what I have at the moment:
As TnTiMn said, it is a little bit tricky. I had to map 1, 2, 3... to my real values and then to assign custom labels. I didn't used ScaleBreakStyle. Thanks anyway for your help.
My code:
chart1.ChartAreas[0].AxisX.IsReversed = true;
chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY2.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.Enabled = AxisEnabled.False;
chart1.ChartAreas[0].AxisY2.Enabled = AxisEnabled.True;
chart1.ChartAreas[0].AxisY2.ScaleView.SizeType = DateTimeIntervalType.Number;
chart1.ChartAreas[0].AxisY2.MajorTickMark.Enabled = false;
chart1.Series[0].MarkerImage ="NotEmptyPoint.png";
chart1.Series[0].EmptyPointStyle.MarkerImage = "EmptyPoint.png";
chart1.Series[1].MarkerImage = "NotEmptyPoint.png";
chart1.Series[1].EmptyPointStyle.MarkerImage = "EmptyPoint.png";
chart1.Series[2].MarkerImage = "NotEmptyPoint.png";
chart1.Series[2].EmptyPointStyle.MarkerImage = "EmptyPoint.png";
chart1.ChartAreas[0].AxisY.Interval = 1;
chart1.ChartAreas[0].AxisY2.Interval = 1;
chart1.ChartAreas[0].AxisY2.Minimum = 0;
chart1.ChartAreas[0].AxisY2.Maximum = 10;
chart1.ChartAreas[0].AxisY.Minimum = 0;
chart1.ChartAreas[0].AxisY.Maximum = 10;
int number = 0;
string mappedNumber;
for (int i = 1; i < 15; i++)
{
number = 1;
mappedNumber = "1";
chart1.Series["Series1"].Points.AddXY(i, number);
chart1.ChartAreas[0].AxisY2.CustomLabels.Add(number - 0.5, number + 0.5, mappedNumber);
if(i % 3 == 0)
{
chart1.Series["Series1"].Points[chart1.Series["Series1"].Points.Count - 1].IsEmpty = true;
}
}
for (int i = 1; i < 15; i++)
{
number = 2;
mappedNumber = "7";
chart1.Series["Series2"].Points.AddXY(i, number);
chart1.ChartAreas[0].AxisY2.CustomLabels.Add(number - 0.5, number + 0.5, mappedNumber);
if (i % 4 == 0)
{
chart1.Series["Series2"].Points[chart1.Series["Series2"].Points.Count - 1].IsEmpty = true;
}
}
for (int i = 1; i < 15; i++)
{
number = 3;
mappedNumber = "20";
chart1.Series["Series3"].Points.AddXY(i, number);
chart1.ChartAreas[0].AxisY2.CustomLabels.Add(number - 0.5, number + 0.5, mappedNumber);
if (i % 5 == 0)
{
chart1.Series["Series3"].Points[chart1.Series["Series3"].Points.Count - 1].IsEmpty = true;
}
}
Related
I am building a "Stacked Column" chart via MS-Chart using C# in MVC. My chart has 3 series. I am trying to get the data value labels of each series to display underneath the X-axis instead of on each column.
I have been searching the net in hoping to find some pointer to this similar lay-out but have found none for 2 days.
Can someone please give me some pointer on how to accomplish this same position of data value labels arrangement?
Here is the simplest solution. It isn't nicely styled but takes only a few lines:
var months = new[] { "Jan", "Feb", "Mar", "Apr", "May",
"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
for (int i = 0; i < pointCount; i++)
{
double sum = 0;
string label = "";
for (int j = seriesCount - 1; j >= 0; j--)
{
sum += chart.Series[j].Points[i].YValues[0];
label += "\n" + +chart.Series[j].Points[i].YValues[0] ;
}
chart.Series[0].Points[i].AxisLabel = months[i] + "\n" + sum + label;
}
This adds a label string to each DataPoint of the 1st Series. Note that only one such labels can be shown per point; labels from later Series will be ignored.
Use suitable counters and whatever you want for the month part of the label.
For nicer looks, like bold sums or colored backgrounds you will need to do a lot more work.
Note that the numbers in the labels and the series are reversely stacked so the inner loop goes backward.
This will work for any number of series.
Here is a variation using the PostPaint event:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (chart1.Series[0].Points.Count <= 0) return;
Graphics g = e.ChartGraphics.Graphics;
ChartArea ca = chart1.ChartAreas[0];
Rectangle rip = Rectangle.Round(InnerPlotPositionClientRectangle(chart1, ca));
StringFormat fmt = new StringFormat()
{ Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
int cols = chart1.Series[0].Points.Count;
int sCount = chart1.Series.Count;
int w = rip.Width / (cols + 1); // there is ca. 1/2 column gap to the sides
for (int i = 0; i < cols; i++)
{
List<string> s = (List<string>)(chart1.Series[0].Points[i].Tag);
for (int j = 0; j < s.Count; j++)
{
// change magic numbers with your font!
Rectangle r = new Rectangle(rip.Left + i * w + w / 2,
rip.Bottom + 5 + 25 * j, w, 25);
// 1st row: header, 2nd row sum, rest moved up by and reversed
using (SolidBrush brush = new SolidBrush(j == 0 ? Color.Transparent
: j == 1 ? Color.Gray : chart1.Series[sCount + 1 - j].Color))
g.FillRectangle(brush, r);
g.DrawRectangle(Pens.White, r);
g.DrawString(s[j], ca.AxisX.LabelStyle.Font, Brushes.White, r, fmt);
}
}
}
It uses the same routine to collect the label strings, but instead of setting the AxisLabels it adds them to the Tags of the DataPoints:
string l = months[i] + "\n" + sum + label;
chart.Series[0].Points[i].Tag = l.Split('\n').ToList();
The Chart styling almost takes the largest part of the code:
chart.BackColor = Color.DarkSlateGray;
ChartArea ca = chart.ChartAreas[0];
chart.Legends[0].Alignment = StringAlignment.Center;
chart.Legends[0].Docking = Docking.Top;
chart.Legends[0].BackColor = chart.BackColor;
chart.Legends[0].ForeColor = Color.White;
Legend L = chart.Legends[0];
L.CustomItems.Add(Color.Silver, "Sum");
ca.BackColor = Color.LightSteelBlue;
ca.Position = new ElementPosition(2, 8, 93, 70); // make room
ca.Area3DStyle.Enable3D = true;
ca.Area3DStyle.PointDepth = 25;
ca.Area3DStyle.WallWidth = 0;
ca.AxisX.MajorGrid.Enabled = false;
ca.AxisY.MajorGrid.LineColor = Color.White;
ca.AxisY.LineColor = Color.White;
ca.AxisY.LabelStyle.ForeColor = Color.White;
ca.AxisY.MajorTickMark.LineColor = Color.White;
ca.AxisX.LabelStyle.Enabled = false;
ca.AxisX.LineColor = Color.White;
ca.AxisX.MajorTickMark.Enabled = false;
After creating the Series with theit Colors you need to apply them, so they can be accessed in code:
chart1.ApplyPaletteColors();
The nice rounded columns of a Series s are created by a CustomProperty
s.SetCustomProperty("DrawingStyle", "Cylinder");
Some more details:
chart.Series[1].Color = Color.Crimson;
chart.Series[0].LegendText = "Hobbits";
..
Update: You need to include two functions InnerPlotPositionClientRectangle and ChartAreaClientRectangle from some of my other posts, like here or here!
To make this work in ASP you hook up the event in the PageLoad:
chart1.PostPaint += new EventHandler<ChartPaintEventArgs>(chart1_PostPaint);
Suppose the next array:
int a[] = new int[15];
Each value is a counter of some days in a specific state in a period in a database.
Example: Period 1/1/2000 - 1/3/2000 (3 days, not 3 months): Number of days in state XXXXX.
What I want to do is check if the objects count is correct compared to the objects count on a website. The search itself takes some seconds at best if the website is not loaded.
I had made a very simple test project which compares the values of a with some fixed values on another array and I randomly chose some values to be different, in fact 7 out of 15 were different.
The current algorithm implemented is binary search. The output of this piece of code is correct, but the number of searches that would occur on the real application is 144 for the data provided, which is not efficient at all. Is there any other algorithm that I could use to minimize the number of searches (or summary calculations in this example)?
IMPORTANT NOTE: The periods can be as large as Sep 1, 2010 - Today, so for the moment searching each day independently is not an option.
Ask me for explanations if needed.
a = new int[15];
b = new int[15];
searchCount = 0;
// Fill a and b with some test values
a[0] = 12;
a[1] = 13;
a[2] = 26;
a[3] = 30;
a[4] = 6;
a[5] = 3;
a[6] = 1;
a[7] = 2;
a[8] = 8;
a[9] = 12;
a[10] = 19;
a[11] = 21;
a[12] = 56;
a[13] = 100;
a[14] = 80;
b[0] = 11;
b[1] = 9;
b[2] = 26;
b[3] = 30;
b[4] = 8;
b[5] = 3;
b[6] = 1;
b[7] = 5;
b[8] = 8;
b[9] = 13;
b[10] = 19;
b[11] = 21;
b[12] = 55;
b[13] = 99;
b[14] = 80;
// Filled.
void BinarySearch(int start, int end)
{
if (AreSumsEqual(start, end))
{
Debug.WriteLine("Values from positions" + start + " to " + end + " are ok");
}
else if (start == end)
{
Debug.WriteLine("Value at position " + start + " is not ok");
}
else
{
int mid = Middle(start, end);
BinarySearch(start, mid - 1);
BinarySearch(mid, end);
}
}
int Middle(int start, int end)
{
return (int)Math.Ceiling((start + end) / 2.0);
}
bool AreSumsEqual(int start, int end)
{
bool areEqual = false;
int sumA = 0;
int sumB = 0;
for (int i = start; i <= end; i++)
{
sumA += a[i];
sumB += b[i];
searchCount += 2; // Each sum calculated is the same as one
// website search. This takes the most time in real application, so
// repeat it as few times as possible.
}
return areEqual = (sumA == sumB);
}
You can't use binary search here, since you need to check every [start, end] combindation. Also, if you search in both directions with binary search, it is not binary search anyway.
I'd suggest the following solution:
// Remove this, if you want all matches
bool found = false;
for (int start = 0; start < a.count; start++)
{
// Maybe you need end = start + 1, not sure
for (int end = start; end < a.count; end++)
{
if (AreSumsEqual(start, end)
{
// Found! Let's break to avoid useless iterations,
// if we only want one match.
found = true;
break;
}
}
if (found)
{
break;
}
}
This runs in O([n(n - 1)] / 2) (if I'm not mistaken) which is O(n²) in the worst case. Since you have to check all all [start, end] combinations, you can't solve this with a smaller order of magnitude.
EDIT: This is provided I understood your question.
var SearchResults = a.Select((value, index) => a[index] == b[index]);
for (int i=0;i<a.Length;i++)
{
Debug.WriteLine("Values in position {0} are {1}", i, SearchResults.ToList()[i]);
}
As i said in the comments, you just need a simple comparison between each position on two arrays.
Here is the code I'm using
It's supposed to create labels for each module name entered by a user and all the assessment names for that module under it. Should work with any number of modules and assessments. The problem is that it only shows assessments for the last module displayed.
dat.Modules and dat.Assessments are arraylists, each of them holds 4 elements with info about a module or an assessments, that is why i divide the count by 4.
private void testingButton_Click(object sender, EventArgs e)
{
int pos1 = 50;
int pos2 = 150;
int modLength = dat.Modules.Count;
modLength = modLength / 4;
int assessLength = 0;
int arrayData = 0;
int displayCount = 0;
for (int i = 0; i < modLength; i++)
{
this.moduleLabels.Add(new Label());
System.Drawing.Point pLabel1 = new System.Drawing.Point(50, pos1);
(moduleLabels[i] as Label).Location = pLabel1;
(moduleLabels[i] as Label).Size = new System.Drawing.Size(100, 13);
(moduleLabels[i] as Label).Text = dat.Modules[arrayData].ToString();
tabPage5.Controls.Add((moduleLabels[i] as Label));
String asd = dat.Modules[arrayData + 2].ToString();
Console.WriteLine(asd);
assessLength = int.Parse(asd);
pos2 = pos1 + 25;
for (int y = 0; y < assessLength; y++)
{
this.assessLabels.Add(new Label());
System.Drawing.Point pLabel2 = new System.Drawing.Point(70, pos2);
(assessLabels[y] as Label).Location = pLabel2;
(assessLabels[y] as Label).Size = new System.Drawing.Size(250, 13);
(assessLabels[y] as Label).Text = dat.Assessments[displayCount + 1].ToString() + " weights " + dat.Assessments[displayCount+2].ToString() +"%, Enter your mark:";
textboxComputer.Add(new TextBox());
System.Drawing.Point pText1 = new System.Drawing.Point(400, pos2);
(textboxComputer[y] as TextBox).Location = pText1;
(textboxComputer[y] as TextBox).Size = new System.Drawing.Size(20, 20);
tabPage5.Controls.Add(assessLabels[y] as Label);
tabPage5.Controls.Add(textboxComputer[y] as TextBox);
pos2 = pos2 + 25;
displayCount = displayCount + 4;
}
pos1 = pos2+25;
arrayData = arrayData + 4;
}
}
this is an example of what it displays
http://dc540.4shared.com/download/YI8IENYI/tsid20120501-211723-cbc785f9/asd.jpg
The first two modules should have their assessments listed. The first one doesn't display any. For Java it only displays the last one, out of 3 total for that module. And for the last Module "Another Module" it displays all assessments.
For each increment of i, y starts at 0. You then add new labels to assessLabels, but attempt to access the one you added by using assessLabels[y] which would usually yield the labels created for the previous value of i. This causes labels created for the first module to be reused by the next, and so forth.
A quick solution is not to use assessLabels[y] but assessLabels[assessLabels.Count - 1].
A better solution is to create a local variable for the labels, set their properties, and then add them to the list:
for (int y = 0; y < assessLength; y++)
{
Label assessLabel = new Label();
assessLabel.Location = ...;
// etc.
tabPage5.Controls.Add(assessLabel);
assessLabels.Add(assessLabel);
}
This would also remove the need to continuously cast the ArrayList members and unneeded access to the list.
PS. If assessLabels only contains objects of type Labels, consider using a List<Label> instead.
Somewhere in internet I found this code
private void PopulateChart()
{
int elements = 500;
Random r = new Random();
List<double> xValues = new List<double>();
double currentX = 0;
for (int i = 0; i < elements; i++)
{
xValues.Add(currentX);
currentX = currentX + r.Next(1, 2000);
}
List<double> yValues = new List<double>();
for (int i = 0; i < elements; i++)
{
yValues.Add(r.Next(0, 50));
}
// remove all previous series
chart1.Series.Clear();
var series = chart1.Series.Add("MySeries");
series.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Stock;
//series.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Auto;
DateTime baseDate = DateTime.Today;
for (int i = 0; i < xValues.Count; i++)
{
var xDate = baseDate.AddSeconds(xValues[i]);
var yValue = yValues[i];
series.Points.AddXY(xDate, yValue);
}
// show an X label every itme interval (values in minute 60 = 1 hour)
chart1.ChartAreas[series.ChartArea].AxisX.Interval = 100.0;
chart1.ChartAreas[0].AxisX.IntervalType = System.Windows.Forms.DataVisualization.Charting.DateTimeIntervalType.Minutes;
// label format
chart1.ChartAreas[0].AxisX.LabelStyle.Format = "HH:mm:ss";
}
This displays random data in chart with grouping of data with some time interval.
Now I want to put a horizontal scrollbar (x-axis). I tried using code used in this post
Adding a scroll bar to MS Chart control C#
but I couldnot apply it with full functionality.
Can anyone help me in this problem?
You have enable the X axis for zooming.
chart1.ChartAreas["ChartArea1"].CursorX.IsUserEnabled = true;
chart1.ChartAreas["ChartArea1"].CursorX.IsUserSelectionEnabled = true;
chart1.ChartAreas["ChartArea1"].AxisX.ScaleView.Zoomable = true;
chart1.ChartAreas["ChartArea1"].AxisX.ScrollBar.IsPositionedInside = true;
I generate an excel spread-cheat with c# and I want to freeze the first column.
This is the code that I use :
public static void SaveToExcel(object[,] data)
{
Excel = Microsoft.VisualBasic.Interaction.CreateObject("Excel.Application", String.Empty);
Excel.ScreenUpdating = false;
dynamic workbook = Excel.workbooks;
workbook.Add();
dynamic worksheet = Excel.ActiveSheet;
const int left = 1;
const int top = 1;
int height = data.GetLength(0);
int width = data.GetLength(1);
int bottom = top + height - 1;
int right = left + width - 1;
if (height == 0 || width == 0)
return;
dynamic rg = worksheet.Range[worksheet.Cells[top, left], worksheet.Cells[bottom, right]];
rg.Value = data;
// Set borders
for (var i = 1; i <= 4; i++)
rg.Borders[i].LineStyle = 1;
// Set header view
dynamic rgHeader = worksheet.Range[worksheet.Cells[top, left], worksheet.Cells[top, right]];
rgHeader.Font.Bold = true;
rgHeader.Interior.Color = 189 * (int)Math.Pow(16, 4) + 129 * (int)Math.Pow(16, 2) + 78;
rg.EntireColumn.AutoFit();
// Show excel app
Excel.ScreenUpdating = true;
Excel.Visible = true;
}
Could You Please Help me???
The right solution is to add the following code :
worksheet.Activate();
worksheet.Application.ActiveWindow.SplitColumn = 1;
worksheet.Application.ActiveWindow.FreezePanes = true;
You need to set
yuorRange.Application.ActiveWindow.FreezePanes = true;
Have a look at
Window.FreezePanes Property
Window.SplitColumn Property
Window.SplitRow Property