Unwanted labels appear on RangeBar chart AxisX when adding data - c#

I have a Chart (RangeBarChart) with a ChartArea attached to it. The ChartArea has one Series attached to it. I also have a List<string>, this stringlist is filled with the values I want the Axislabels on my AxisX to have. In my example screenshot, this stringlist contains 6 strings.
All the 6 labels are named correct, but at the upper and lower limits of my AxisX, the labels are numbered as well, see screenshot (upper part, '-1' and the '6' labels are undesired).
Screenshot link: https://imgur.com/a/pwYF4yl
I add data to my chart with two lines of code in a foreach loop. I found that when I comment out these two lines, the extra numbers on my axis don't appear, but obviously this is a non-solution, as I'm also not showing any data. I also can't delete the labels manually in code because I have no pointIndices pointing to them. When looking at my series.Points[] collection in the debugger, all their X-values are between 0 and 5 (also in screenshot).
How can I get rid of these labels?
I recreated the unwanted behaviour in a quick test project, just add a Chart called 'chart' in the designer and copy this code in the code part of your main form and you can recreate the problem for yourself.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace XAxisLabelsNumbersTest
{
public partial class Form1 : Form
{
ChartArea ca;
Series serie;
List<string> xLabels = new List<string> {"Label1", "Label2", "Label3", "Label4", "Label5", "Label6"};
List<myObj> myObjList = new List<myObj>();
public class myObj
{
public Int32 begin { get; set; }
public Int32 end { get; set; }
public int xcord { get; set; }
public int pointIndex { get; set; }
public string label { get; set; }
public myObj(Int32 begin, Int32 end, string label)
{
this.begin = begin;
this.end = end;
this.label = label;
}
}
public Form1()
{
InitializeComponent();
// Setting some properties regarding chart behaviour
ca = chart.ChartAreas.Add("ca");
serie = chart.Series.Add("serie");
serie.ChartArea = ca.Name;
serie.ChartType = SeriesChartType.RangeBar;
serie.XValueType = ChartValueType.String;
serie.YValueType = ChartValueType.Int32;
serie["PixelPointWidth"] = "10";
//ca.AxisY.LabelStyle.Format = "HH:mm tt";
ca.AxisX.MajorGrid.Interval = 1;
ca.AxisY.Minimum = 0;
ca.AxisY.Maximum = 6;
Title title = new Title("Title");
chart.Titles.Add(title);
title.DockedToChartArea = ca.Name;
title.IsDockedInsideChartArea = false;
title.Font = new Font("Serif", 18);
ca.AxisX.LabelAutoFitStyle = LabelAutoFitStyles.None;
ca.AxisX.LabelStyle.Font = new Font("Trebuchet MS", ca.AxisX.LabelAutoFitMaxFontSize, FontStyle.Bold);
ca.AxisX.LabelStyle.Interval = 1;
// Create Labels from xLabels
for (int i = 0; i < xLabels.Count; i++)
{
int pi = serie.Points.AddXY(i, null, null);
serie.Points[pi].AxisLabel = xLabels[i];
}
// Fill myObjList with testdata
myObjList.Add(new myObj(0, 1, "Label1"));
myObjList.Add(new myObj(1, 2, "Label2"));
myObjList.Add(new myObj(2, 3, "Label3"));
myObjList.Add(new myObj(3, 4, "Label4"));
myObjList.Add(new myObj(4, 5, "Label5"));
myObjList.Add(new myObj(5, 6, "Label6"));
// Fill serie with data from myObjList
// Comment out this foreach block and the weird label numbering is gone...
foreach (myObj myObj in myObjList)
{
myObj.xcord = xLabels.FindIndex(Label => Label.Equals(myObj.label));
myObj.pointIndex = serie.Points.AddXY(myObj.xcord, myObj.begin, myObj.end);
}
}
}
}

You can hide both 'endlabels' by setting this axis property: Axis.LabelStyle.IsEndLabelVisible:
ca.Axis.LabelStyle.IsEndLabelVisible = false;

Related

How to have equally sized columns in winforms chart?

I am having difficulties with autoscaling with a c# WinForms chart. I thought by setting the Minimum and Maximum properties of ChartArea.AxisX it would take care of the scaling for me.
In the example below I show a column chart that tracks the number of accidents that occur in a workplace over a given year. Each month, an email will be sent out to the managers of the workplace showing them an updated chart with that month's entries added to it. When the first email is sent out, the columns are very large. Each month, as more workplace accidents occur, the columns of the chart begin to shrink.
In other words, if you make the fakeAccidentsData array have just one or two elements, then the columns are massive. If you have more elements in fakeAccidentsData then they are smaller. I want the columns in this chart to remain a fixed, narrow width so that as more elements are added the columns won't change in size. So there should be enough room to accomodate 365 columns (one for each day of the year). If 365 columns are too small to be seen then I am willing to change it to go by week instead (so I would need space for 52 columns in that case).
Can this be accomplished?
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;
namespace MinimalExample
{
class WorkplaceAccident
{
public DateTime Timestamp;
}
class ChartExample
{
private readonly Series _series;
private readonly ChartArea _chartArea;
private readonly Chart _chart;
public IEnumerable<WorkplaceAccident> Accidents
{
set
{
var accidentCount = new Dictionary<DateTime, int>();
foreach (var accident in value)
{
DateTime accidentDate = accident.Timestamp.Date;
if (accidentCount.ContainsKey(accidentDate))
{
++accidentCount[accidentDate];
}
else accidentCount.Add(accidentDate, 1);
}
foreach(KeyValuePair<DateTime, int> pair in accidentCount)
{
Console.WriteLine("[Debug info] Adding " + pair.Key + ", " + pair.Value);
_series.Points.AddXY(pair.Key, pair.Value);
}
}
}
public ChartExample(int year)
{
_series = new Series();
_series.ChartType = SeriesChartType.Column;
_chartArea = new ChartArea();
_chartArea.AxisY.Interval = 1;
_chartArea.AxisY.IsMarginVisible = false;
_chartArea.AxisY.Title = "Number of workplace accidents";
_chartArea.AxisX.MajorGrid.Enabled = false;
_chartArea.AxisX.Minimum = new DateTime(year, 1, 1).ToOADate();
_chartArea.AxisX.Maximum = new DateTime(year, 12, 31).ToOADate();
_chart = new Chart();
_chart.ChartAreas.Add(_chartArea);
_chart.Series.Add(_series);
_chart.Size = new Size(800, 400);
}
public void SaveAsPng(string filename)
{
_chart.SaveImage(filename, ChartImageFormat.Png);
}
public static void Main()
{
WorkplaceAccident[] fakeAccidentsData = new WorkplaceAccident[]
{
new WorkplaceAccident { Timestamp = DateTime.Now.AddDays(-1) },
new WorkplaceAccident { Timestamp = DateTime.Now.AddDays(-1) },
new WorkplaceAccident { Timestamp = DateTime.Now.AddDays(-5) },
new WorkplaceAccident { Timestamp = DateTime.Now.AddMonths(-1) }
};
var chart = new ChartExample(2020) { Accidents = fakeAccidentsData };
chart.SaveAsPng(#"C:\Users\Home\Documents\chart.png");
Console.Write("Press any key to terminate... "); Console.ReadKey();
}
}
}

Link combo box and parallel array

I need help linking or mapping a combo box to a parallel array in C#. I have a class project where I need to create a payroll system that displays the Net Pay after taxes.
I want to link parallel arrays that have all the employee information needed for payroll to the option selected in the combo box. I feel like I almost have it, but I don't know how to link the option selected from the combo box and the parallel arrays I have set up.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ZNSPayrollSystem
{
public partial class ZNSPayrollSystem : Form
{
public ZNSPayrollSystem()
{
InitializeComponent();
string[] arr = { "001 Peters", "002 Barnes", "003 Harris" };
cboEmp.DataSource = arr.ToArray();
}
private void btnCalc_Click(object sender, EventArgs e)
{
//parallel arrays
int[] empID = { 001, 002, 003 };
string[] empName = { "James Peters", "Sarah Barnes", "Candice Harris" };
double[] hrsWorked = { 40, 40, 40 };
double[] empWage = { 55.50, 65.50, 75.70 };
//declarations
double dblTaxRate = 8.2 / 100;
double dblNetPay;
double dblGrossPay;
double dblTaxWithheld;
dblGrossPay = hrsWorked[] * empWage[];
dblTaxWithheld = dblGrossPay * dblTaxRate;
dblNetPay = dblGrossPay - dblTaxWithheld;
txtGross.Text = dblGrossPay.ToString();
txtTax.Text = dblTaxWithheld.ToString();
txtNetPay.Text = dblNetPay.ToString();
}
}
}
Use the SelectedIndex property of the Combobox:
int i = cboEmp.SelectedIndex;
if (i != -1)
{
dblGrossPay = hrsWorked[i] * empWage[i];
}
i == -1 means nothing is selected. You may want to handle this separately to avoid getting any exceptions.

C# List<T> / List<class> get values

I write to my class different values and I would like to get the values of my class. The Debug outputs shows me:
Value: List.data Index 0
Value: List.data Index 1
How I get the real value of my class properties?
My code example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Diagnostics;
namespace List
{
class data
{
public string name { get; set; }
public Rectangle rect { get; set; }
}
class Program
{
static void Main(string[] args)
{
data dat1 = new data();
List<data> listdat = new List<data>();
dat1.name = "test1";
dat1.rect = new Rectangle(10, 10, 10, 10);
listdat.Add(dat1);
data dat2 = new data();
dat2.name = "test2";
dat2.rect = new Rectangle(20, 20, 20, 20);
listdat.Add(dat2);
data dat3 = new data();
dat3.name = "test3";
dat3.rect = new Rectangle(30, 30, 30, 30);
listdat.Add(dat3);
listdat.RemoveAt(1);
foreach (var item in listdat)
{
//This will yield the proper index that you are currently on
int index = listdat.IndexOf(item);
}
foreach (var item in listdat.Select((value, index) => new { Value = value, Index = index }))
{
//Get the value through item.Value;
var currentValue = item.Value;
//Get the index through item.Index;
int currentIndex = item.Index;
Debug.WriteLine("Value: {0} Index {1}", currentValue, currentIndex);
}
int i = 0;
}
}
}
I´m wondering why you use this weird Select-statement rather than a good old-style for-loop which also gives you the index:
for(int i = 0; i < listdat.Count; i++)
{
var currentValue = listdat[i].Name;
int currentIndex = item.Index;
Debug.WriteLine("Value: {0} Index {1}", currentValue, i);
}
You don´t even have to change your data-class-code, simply access the property (probably name in your case) of your current instance listdat[i] and you´re done.
Btw. the following code is useless as the variable index is reset on every loop but never read:
foreach (var item in listdat)
{
//This will yield the proper index that you are currently on
int index = listdat.IndexOf(item);
}
When you just put an object for printing out it will call the objext's ToString() method, which by default just returns the class' name.
If you want it to output something different you have to override.
You can for example add this to the data class:
public override string ToString()
{
return name;
}

C# Why can't I implicitly convert type 'string' to 'System.Collections.Generic.List<int>'?

I am trying to figure out how to solve the error as stated in the title, which occurs on the bold line within this snippet:
while (textIn.Peek() != -1)
{
string row = textIn.ReadLine();
string[] columns = row.Split('|');
StudentClass studentList = new StudentClass();
studentList.Name = columns[0];
**studentList.Scores = columns[1];**
students.Add(studentList);
}
The previous line of code loads the names just fine since it is not a List within the class I have created, but "Scores" is within a list, however. What modifications would I need to do? These values are supposed to be displayed within a textbox from a text file upon loading the application.
Here is the class in which "Scores" is in (I have highlighted it):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyNameSpace
{
//set the class to public
public class StudentClass
{
public StudentClass()
{
this.Scores = new List<int>();
}
public StudentClass (string Name, List<int> Scores)
{
this.Name = Name;
this.Scores = Scores;
}
public string Name
{ get;
set;
}
//initializes the scores
**public List<int> Scores
{ get;
set;
}**
public override string ToString()
{
string names = this.Name;
foreach (int myScore in Scores)
{ names += "|" + myScore.ToString();
}
return names;
}
public int GetScoreTotal()
{
int sum = 0;
foreach (int score in Scores)
{ sum += score;
}
return sum;
}
public int GetScoreCount()
{ return Scores.Count;
}
public void addScore(int Score)
{
Scores.Add(Score);
}
}
}
You can't just assign a string containing a sequence of numbers to a property of type List<int>.
You need to split the string into seperate numbers, then parse these substrings to get the integers they represent.
E.g.
var text = "1 2 3 4 5 6";
var numerals = text.Split(' ');
var numbers = numerals.Select(x => int.Parse(x)).ToList();
I.e. in your code replace:
studentList.Scores = columns[1];
with:
studentList.Scores = columns[1].Split(' ').Select(int.Parse).ToList();
(Or your own multi-line, more readable/debugable equivalent.)
You'll need to modify the parameter passed to Split() according to how the scores are formatted in your column.
You'll also need to add using System.Linq; at the top if you don't already have it.
As far as the question goes, how would the compiler ever know how to convert the string to a list, when there could be so many string representations of a list. If it was to do this it would be an incredibly slow operation.
Fix
To fix your code you could replace your loop with this.
while (textIn.Peek() != -1)
{
string row = textIn.ReadLine();
StudentClass studentList = new StudentClass();
int index = row.IndexOf("|");
//checking that there were some scores
if (index < 0) {
studentList.Name = row;
continue;
}
studentList.Name = row.Substring(0, index);
studentList.Scores = row.Substring(index + 1).Split('|').Select(int.Parse).ToList();
students.Add(studentList);
}
There are however a number of problems even with this fix.
For one if you were to add another list delimited by '|' it would become increasingly hard for you to parse using this kind of approach.
I suggest instead that you look at serializing your class(es) with something a little more powerful and generic like Json.Net.
Hope this helps.

Need a help on how to sort the list based on volume

Here,a list of boxes which has length,height,depth and volume has been created. Now, i need these boxes in a sorted order according to the volume. kindly provide me an idea how i can sort the boxes only considering the volume. I am using vs2008, linq is not working here. so is there any other solution.
using (StreamReader sr = new StreamReader("c:/containervalues.txt"))
while ((line = sr.ReadLine()) != null)
{
// create new instance of container for each line in file
Box box = new Box();
List<Box> listofboxes = new List<Box>();
string[] Parts = line.Split(' ');
// set non-static properties of container
box.bno = Parts[0];
box.length = Convert.ToDouble(Parts[1]);
box.height = Convert.ToDouble(Parts[2]);
box.depth = Convert.ToDouble(Parts[3]);
box.volume = Convert.ToDouble(Parts[4]);
// add container to list of containers
listofboxes.Add(box);
}
Try this:
You will need to use Linq.
listOfBoxes = listOfBoxes.OrderBy(x => x.volume).ToList();
List<Box> sortedList = listofboxes.OrderBy(x => x.volume);
.. or you can order by descending
listOfBoxes = listOfBoxes.OrderByDescending(p => p.volume).ToList();
you can use a delegate as an argument for the Sort method of the Generic List:
using System;
using System.Collections.Generic;
namespace sortVolumen
{
class Program
{
static void Main(string[] args)
{
List<box> BoxList = new List<box> {
new box { Width = 2, Height = 2, Depth = 2},
new box { Width = 3, Height = 3, Depth = 3},
new box { Width = 1, Height = 1, Depth = 1},
};
foreach (box myBox in BoxList)
{
Console.WriteLine(myBox.Volumen);
}
BoxList.Sort(delegate(box a, box b) { return a.Volumen < b.Volumen ? -1 : 1;});
Console.WriteLine("after comparing");
foreach (box myBox in BoxList)
{
Console.WriteLine(myBox.Volumen);
}
Console.ReadLine();
}
}
class box
{
public double Width { get; set; }
public double Height { get; set; }
public double Depth { get; set; }
public double Volumen {
get { return Width * Height * Depth; }
}
}
}
Also you can check these ways to achieve the same behaviour.

Categories