I have a chart which has 9 different series to it, only two of which can ever be enabled at the same time.
My user has asked that when the first datapoint is added to the chart, it is labelled 'Start', and this enables the button that will then allow the user to mark 'End'.
During the development of the solution, this wasn't a problem as I had control over which of the series were enabled at any one time. Now the facility has been added for the user to change this in the 'Options' menu (series name and enabled state written to 'Settings' file, to then be read in next time the program begins.
In order to try and do this, I created a List<> 'enabledSeries' in my updateChart function that finds the enabled of the chart series and adds them to the list. This has been done fine, and adding datapoints to the chart works using this method. For some reason, however, Labels now do not appear at the start of each series.
The code for updateChart() and subfunctions is shown below:
public void updateChart(int minutesElapsed)
{
//int latestReading = 0; //local variable to hold va1ue from txtBP.Text
chartVitals.ChartAreas[0].AxisY2.Enabled = AxisEnabled.True;
chartVitals.Text = "Vitals Visual";
//Create correct coordinate using reading and time taken
//add newest (last) element in each list to respective chart series, along with value from timer
chartVitals.Series["Heart Rate"].Points.AddXY(minutesElapsed, HRlist.Last());
chartVitals.Series["Ventilation Rate"].Points.AddXY(minutesElapsed, VRlist.Last());
chartVitals.Series["Blood Pressure"].Points.AddXY(minutesElapsed, BPlist.Last());
chartVitals.Series["o2 Saturation"].Points.AddXY(minutesElapsed, BOlist.Last());
chartVitals.Series["ET Co2"].Points.AddXY(minutesElapsed, ETCo2list.Last());
chartVitals.Series["Vaporiser Setting"].Points.AddXY(minutesElapsed, VSlist.Last());
chartVitals.Series["FI Agent"].Points.AddXY(minutesElapsed, FIAlist.Last());
chartVitals.Series["ET Agent"].Points.AddXY(minutesElapsed, ETAlist.Last());
chartVitals.Series["Fresh Gas Flow"].Points.AddXY(minutesElapsed, FGFlist.Last());
//stores all enabled series (MAX 2)
List<string> enabledSeries = new List<string>();
//identify and isolate the enabled series in the chart
identifyEnabled(enabledSeries);
}
public void identifyEnabled(List<string> enabledSeries)
{
//takes name of chart series at current index
string seriesName = "";
//access all items in list
for (int index = 0; index < chartVitals.Series.Count; ++index)
{
//assign name to variable
seriesName = chartVitals.Series[index].Name;
//series with this name is enabled
if (chartVitals.Series[seriesName].Enabled)
{
//add name to list
enabledSeries.Add(seriesName);
}
}
formatEnabled(seriesName, enabledSeries);
}
public void formatEnabled(string seriesName, List<string> enabledSeries)
{
//color series in by index (0 - blue, 1 - red)
string blueSeries = enabledSeries.First();
string redSeries = enabledSeries[enabledSeries.IndexOf(blueSeries) + 1];
//access all elements in enabledSeries
for (int enabledIndex = 0; enabledIndex < enabledSeries.Count; ++enabledIndex)
{
//access all series in chartVitals
for (int seriesIndex = 0; seriesIndex < chartVitals.Series.Count; ++seriesIndex)
{
//when there is item in series
if (chartVitals.Series[seriesName].Points.Count > 0)
{
string start = "[Start]";
//set series type as line
chartVitals.Series[seriesName].ChartType = SeriesChartType.Line;
//apply label to first point of series
chartVitals.Series[seriesName].Points.First().Label = start;
//enable button to mark end
btnOpEnd.Enabled = true;
}
}
}
//apply colours to series
chartVitals.Series[blueSeries].Color = Color.Blue;
chartVitals.Series[redSeries].Color = Color.Red;
}
The code is a bit intricate, but it's commented and should all be there. If anyone can point out what might be causing the problem or a simpler way of doing things, I would really appreciate it!
Thanks,
Mark
Is this what you expected to see?
EDIT: I changed formatEnabled to use seriesIndex instead of seriesName, like below:
public void formatEnabled(string seriesName, List<string> enabledSeries)
{
//color series in by index (0 - blue, 1 - red)
string blueSeries = enabledSeries.First();
string redSeries = enabledSeries[enabledSeries.IndexOf(blueSeries) + 1];
//access all elements in enabledSeries
for (int enabledIndex = 0; enabledIndex < enabledSeries.Count; ++enabledIndex)
{
//access all series in chartVitals
for (int seriesIndex = 0; seriesIndex < chartVitals.Series.Count; ++seriesIndex)
{
//when there is item in series
if (chartVitals.Series[seriesIndex].Points.Count > 0)
{
string start = "[Start]";
//set series type as line
chartVitals.Series[seriesIndex].ChartType = SeriesChartType.Line;
//apply label to first point of series
chartVitals.Series[seriesIndex].Points.First().Label = start;
//enable button to mark end
//btnOpEnd.Enabled = true;
}
}
}
//apply colours to series
chartVitals.Series[blueSeries].Color = Color.Blue;
chartVitals.Series[redSeries].Color = Color.Red;
}
Related
I am trying to set the position of caret in richtextbox based on index position of a word. Even though I am able to change the caret position, the caret does not move to the correct location.
Here is my sample code:
private void Button_Click(object sender, RoutedEventArgs e)
{
RTB_Main.Document.Blocks.Clear();
for (int i = 0; i < 10; i++)
{
Paragraph para = new Paragraph(new Run(i + ""));
RTB_Main.Document.Blocks.Add(para);
}
TextRange richText = new TextRange(RTB_Main.Document.ContentStart, RTB_Main.Document.ContentEnd);
string searchText = tb_Search.Text; // 1 to 9
int position = Regex.Match(richText.Text, searchText).Index;
RTB_Main.CaretPosition = RTB_Main.Document.ContentStart;
RTB_Main.CaretPosition = RTB_Main.CaretPosition.GetPositionAtOffset(position);
RTB_Main.Focus();
}
What is wrong with this approach?
Also, Please let me know if there is a better way to set the caret position to an index?
The problem in my case was caused by new line characters \r\n. I just replaced these with another characters and it worked for me. Note that I am replacing them with not 2 characters but 4.
private void Button_Click(object sender, RoutedEventArgs e)
{
RTB_Main.Document.Blocks.Clear();
for (int i = 0; i < 10; i++)
{
Paragraph para = new Paragraph(new Run(i + ""));
RTB_Main.Document.Blocks.Add(para);
}
TextRange richText = new TextRange(RTB_Main.Document.ContentStart, RTB_Main.Document.ContentEnd);
string searchText = tb_Search.Text; // 1 to 9
string tmpStr = richText.Text.Replace("\r\n", "....");
int position = Regex.Match(tmpStr, searchText).Index;
RTB_Main.CaretPosition = RTB_Main.Document.ContentStart;
RTB_Main.CaretPosition = RTB_Main.CaretPosition.GetPositionAtOffset(position);
RTB_Main.Focus();
}
As Maciek noted, there are invisible formatting items that affects the count. My code adds a feedback loop because we are able to ask what the true caret position is. It feels hacky but I could not find anything better.
public static void SetCaretPositionOfRichTextBoxToCharIndex(
System.Windows.Controls.RichTextBox box, int charIndex)
{
// RichTextBox contains many formattings, and they, although invisible, count
// when setting CaretPosition. Calling GetPositionAtOffset with charIndex from
// DocumentStart can be less than the necessary CaretPosition. This code
// therefore has a feedback loop to see how much more offset is necessary.
box.CaretPosition = box.CaretPosition.DocumentStart;
int attemptedCharIndex = 0;
int fixerInc = 0;
while (attemptedCharIndex < charIndex)
{
box.CaretPosition = box.CaretPosition.GetPositionAtOffset(charIndex - attemptedCharIndex + fixerInc);
int temp = new TextRange(box.Document.ContentStart, box.CaretPosition).Text.Length;
if (attemptedCharIndex == temp)
{
fixerInc++;
}
else
{
fixerInc = 0
}
attemptedCharIndex = temp;
}
}
Currently, I am able to read data from multiple CSV file and plot line graph using windows form application. However, now I need to plot a line graph based on a CSV file's section name (3rd column of csv file).
Modified/New CSV file: (Added the Section Name column)
Values,Sector,Name
5.55,1024,red
5.37,1536,red
5.73,2048,blue
5.62,2560,.blue
5.12,3072,.yellow
...
Based on the Section Name column, my line graph need to be plotted accordingly in a Single line and different sections must be plotted with different colors, including the legends shown at the side of the graph must be shown based on the different section names.
1 csv file = 1 Series. But there are same section names in a csv file (csv file example shown above, e.g. red for the 1st 20lines). Same section names = same color. If I open 2 or more csv files = 2 Series. Each Series will have different colors due to different section names in the csv file.
I am quite new with programming, and would really appreciate someone could help me by editing from my code.
Thank you.
Updated code:
GraphDemo (Form):
List<Read> rrList = new List<Read>();
void openToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog ff = new OpenFileDialog();
Read rr;
ff.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); //"C:\\";
ff.Filter = "csv files (*.csv)|*.csv|All files (*.*)|*.*";
ff.Multiselect = true;
ff.FilterIndex = 1;
ff.RestoreDirectory = true;
if (ff.ShowDialog() == DialogResult.OK)
{
try
{
rrList.Clear();
foreach (String file in ff.FileNames) //if ((myStream = ff.OpenFile()) != null)
{
rr = new Read(file);
rrList.Add(rr);
}
//Populate the ComboBoxes
if (rrList.Count > 0)
{
string[] header = rrList[0].header; //header of first file
xBox.DataSource = header;
yBox.DataSource = header.Clone(); //without Clone the 2 comboboxes link together!
}
if (yBox.Items.Count > 1) yBox.SelectedIndex = 1; //select second item
}
catch (Exception err)
{
//Inform the user if we can't read the file
MessageBox.Show(err.Message);
}
}
}
private void button1_Click(object sender, EventArgs e)
{
Plot.Draw(rrList, xBox, yBox, chart);
}
class Read:
public class Read
{
public int nLines { get; private set; }
public int nColumns { get; private set; }
public string[] header { get; private set; }
public float[,] data { get; private set; }
public string fileName { get; set; }
public string[] section { get; private set; }
public Read(string file)
{
string[] pieces;
fileName = Path.GetFileName(file);
string[] lines = File.ReadAllLines(file); // read all lines
if (lines == null || lines.Length < 2) return; //no data in file
header = lines[0].Split(','); //first line is header
nLines = lines.Length - 1; //first line is header
nColumns = header.Length;
//read the numerical data and section name from the file
data = new float[nLines, nColumns - 1]; // *** 1 less than nColumns as last col is sectionName
section = new string[nLines]; // ***
for (int i = 0; i < nLines; i++)
{
pieces = lines[i + 1].Split(','); // first line is header
if (pieces.Length != nColumns) { MessageBox.Show("Invalid data at line " + (i + 2) + " of file " + fileName); return; }
for (int j = 0; j < nColumns - 1; j++)
{
float.TryParse(pieces[j], out data[i, j]); //data[i, j] = float.Parse(pieces[j]);
}
section[i] = pieces[nColumns - 1]; //last item is section
}
}
}
class Plot:
public class Plot
{
//public Plot() { } //no constructor required as we use a static class to be called
public static void Draw(List<Read> rrList, ComboBox xBox, ComboBox yBox, Chart chart) //***
{
int indX = xBox.SelectedIndex;
int indY = yBox.SelectedIndex;
chart.Series.Clear(); //ensure that the chart is empty
chart.Legends.Clear();
Legend myLegend = chart.Legends.Add("myLegend");
myLegend.Title = "myTitle";
//define a set of colors to be used for sections
Color[] colors = new Color[] { Color.Black, Color.Blue, Color.Red, Color.Green, Color.Magenta, Color.DarkCyan, Color.Chocolate, Color.DarkMagenta };
//use a Dictionary to keep iColor of each section
// key=sectionName, value=iColor (color index in our colors array)
var sectionColors = new Dictionary<string, int>();
int i = 0;
int iColor = -1, maxColor = -1;
foreach (Read rr in rrList)
{
float[,] data = rr.data;
int nLines = rr.nLines;
int nColumns = rr.nColumns;
string[] header = rr.header;
chart.Series.Add("Series" + i);
chart.Series[i].ChartType = SeriesChartType.Line;
//chart.Series[i].LegendText = rr.fileName;
chart.Series[i].IsVisibleInLegend = false; //hide default item from legend
chart.ChartAreas[0].AxisX.LabelStyle.Format = "{F2}";
chart.ChartAreas[0].AxisX.Title = header[indX];
chart.ChartAreas[0].AxisY.Title = header[indY];
for (int j = 0; j < nLines; j++)
{
int k = chart.Series[i].Points.AddXY(data[j, indX], data[j, indY]);
string curSection = rr.section[j];
if (sectionColors.ContainsKey(curSection))
{
iColor = sectionColors[curSection];
}
else
{
maxColor++;
iColor = maxColor; sectionColors[curSection] = iColor;
}
chart.Series[i].Points[k].Color = colors[iColor];
}
i++; //series#
} //end foreach rr
//fill custom legends based on sections/colors
foreach (var x in sectionColors)
{
string section = x.Key;
iColor = x.Value;
myLegend.CustomItems.Add(colors[iColor], section); //new LegendItem()
}
}
}
You can separate the data by the section column and use the section names as index into the Series collection instead of using i.
Best use the section name as the Series.Name. I suggest using a data class containing the two numbers and the string and collect them in a List<Dataclass>. Then create Series for the distinct sections. Then loop over them..
Here are a few code examples:
Define a class for your data:
public class Data3
{
public int N1 { get; set;}
public double N2 { get; set;}
public string S1 { get; set;}
public Data3(double n2, int n1, string s1)
{
N1 = n1; N2 = n2; S1 = s1;
}
}
Pick your own names! Optional but always recommended: Add a nice ToString() overload!
Declare a class level varible:
List<Data3> data = new List<Data3>();
During the read collect the data there:
data.Add(new Data3(Convert.ToDouble(pieces[1]), Convert.ToInt32(pieces[0]), pieces[2]));
To plot the chart first create the Series:
var sections= data.Select(x => x.S1).Distinct<string>();
foreach (string s in sections)
chart.Series.Add(new Series(s) { ChartType = SeriesChartType.Line });
Then plot the data; the series can be indexed by their Names:
foreach (var d in data) chart.Series[d.S1].Points.AddXY(d.N1, d.N2);
I left out the nitty gritty of integrating the code into your application; if you run into issues, do show the new code by editing your question!
A few notes:
When in doubt always create a class to hold your data
When in doubt always choose classes over structures
When in doubt always choose List<T> over arrays
Always try to break your code down to small chunks with helpful names.
Example: To read all the data in a csv file create a function to do so:
public void AppendCsvToDataList(string file, List<Data3> list)
{
if (File.Exists(file))
{
var lines = File.ReadAllLines(file);
for (int l = 1; l < lines.Length; l++)
{
var pieces = lines[l].Split(',');
list.Add(new Data3(Convert.ToInt32(pieces[1]),
Convert.ToDouble(pieces[0]), pieces[2]));
}
}
}
I have a dynamic chart control that's manipulated by the user during run time.
The user has the ability to add multiple series' to the chart.
I'm trying to implement a way for the user to filter each series by date.
MSDN provides:
// Filters all points where the X value is less than, or equal to, a specific date.
// The resulting data is stored in an output series, preserving the original data.
myDataManip.Filter(CompareMethod.LessOrEqual, DateTime.Parse("1/1/2001").ToOADate(), "MySeries", "ResultSeries", "X");
This would be fine if I had a single series and I knew the input series name.
The question is how would i implement this for a chart that has multiple series'
Here is a quick work around I did:
private void button2_Click(object sender, EventArgs e)
{
var dt = dateTimePicker.Value;
var tempSeries = new Series[chart1.Series.Count];
try
{
for (var i = 0; i < chart1.Series.Count; i++)
{
tempSeries[i] = new Series
{
Name = chart1.Series[i].Name,
IsVisibleInLegend = true,
IsXValueIndexed = true
};
for (var j = 0; j < chart1.Series[i].Points.Count; j++)
{
if (DateTime.Parse(chart1.Series[i].Points[j].AxisLabel) <= dt)
{
tempSeries[i].Points.Add(chart1.Series[i].Points[j]);
}
}
}
chart1.Series.Clear();
for (var i = 0; i < tempSeries.Count(); i++)
{
chart1.Series.Add(tempSeries[i]);
}
}
catch (Exception error)
{
MessageBox.Show(error.ToString());
}
}
Was trying out a simple FB API app to retrieve status.
So what i am intending to do is to perform a word check with my dictionary.
I have a database which stores emotive data on the feeling % and the genre of the feeling.
If the status contains the emotive word, i wish to perform a word analysis.
For instance: "I am feeling sad and angry"
So what i want it to display is like...
"Username"
was feeling
50% angry
and 25% sad.
*% is calculated by random function.
However, i think its impossible for me to keep creating labels. What if my status has > 5 emotions? Is it possible to create automatic labels which would display the output?
Below is my code:
private void EmotionAnalysis_Load(object sender, EventArgs e)
{
label1.Text = tpc.loadInfo(currentId)["target_name"].ToString();
//List<DataRow> result = dict.AngerPercent(fbStatus);
CalculateAndDisplayAnalysis("Angry", topPercentLabel, topFeelingLabel);
CalculateAndDisplayAnalysis("Caring", bottomPercentLabel, bottomFeelingLabel);
//var item = new ListViewItem(new[] { "", String.Format("{0}%", percent.ToString()), result[0]["Genre"].ToString() });
//listViewEmotion.Items.Add(item);
}
private void CalculateAndDisplayAnalysis(string genre, Label percentLabel, Label feelingLabel)
{
List<DataRow> result = dict.GenrePercent(fbStatus, genre);
var rnd = new Random();
int total = 0;
for (int i = 0; i < result.Count; i++)
{
total += rnd.Next(Convert.ToInt32(result[i]["Min_Percentage"]), Convert.ToInt32(result[i]["Max_Percentage"]));
}
if (result.Count != 0)
{
int percent = total / result.Count;
percentLabel.Text = String.Format("{0}%", percent.ToString());
feelingLabel.Text = result[0]["Genre"].ToString();
}
}
You can create as many labels as you want. you just need to set the position of the label and add it to the forms Controls enumeration:
Label lbl = new Label();
lbl.Text = "MyText";
lbl.Location = new Position(xPos, yPos);
this.Controls.Add(lbl);
You will have to keep track of the new position which is in this case determined by xPos and yPos
I have a ListView which I populate with the contents of an ImageList. When an Item is selected from the list, I check if the file still exists. If it doesn't, I want to remove it both from the Image List (which is private static) and from the ListView.
For some strange reason, which I can't figure out, after removing the selected image from the list, the image right after it disappears and the last image in the list appears twice.
For example, if the list held the following images: IMG1, IMG2, IMG3, IMG4, IMG5 and I remove IMG2 the new list will look like this: IMG1, IMG4, IMG5, IMG5.
Furthermore, if I select the second image from the list (which is now IMG4) and display it in some picture control, IMG3, which was supposed to be in that place will be displayed in the control.
Any ideas what's going on here?
EDIT:
Populating the List view:
private static ImageList stampsImages
if (stampsImages == null)
{
stampsImages = new ImageList();
stampsImages.ImageSize = new Size(125, 75);
}
DirectoryInfo di = new DirectoryInfo(Globals.Directory);
if (di.Exists)
{
FileInfo[] dFiles = di.GetFiles("*.png");
int stampListSize = stampsImages.Images.Count;
for (int i = 0; i < dFiles.Length; i++)
{
int idx = stampsImages.Images.IndexOfKey(dFiles[i].FullName);
if (idx < 0)
{
stampsImages.Images.Add(Bitmap.FromFile(dFiles[i].FullName));
stampsImages.Images[stampListSize].Tag = dFiles[i].FullName;
stampsImages.Images.SetKeyName(stampListSize, dFiles[i].FullName);
stampListSize++;
}
}
}
else di.Create();
for (int i = 0; i < stampsImages.Images.Count; i++)
{
ListViewItem stmp = new ListViewItem("", i);
lvwStamps.Items.Add(stmp);
}
lvwStamps.LargeImageList = stampsImages;
Checking if the file still exists:
private bool IsStampAvailable(int listIdx)
{
bool stampExists = true;
string stampFile = stampsImages.Images.Keys[listIdx];
if (!File.Exists(stampFile))
{
lvwStamps.Items.RemoveAt(listIdx);
stampsImages.Images.RemoveAt(listIdx);
stampExists = false;
}
return stampExists;
}
Every time you remove an image from the ImageList, you'll have to decrement the ImageIndex of each ListViewItem that points to an ImageIndex equal or higher than the ImageIndex of the deleted image. Usually a linear decrement is sufficient starting at the index of the Item after deleted Item (if ImageList and ListViewItems keep a 1:1 relation):
for (int i = lvItem.Index + 1; i < listView1.Items.Count; i++)
listView1.Items[i].ImageIndex--;
Maybe more important is to delete the items AFTER reindexing (after deleting a ListViewItem the ListView must repaint and indicies should be in the right order for this):
int iImageIndex = lvItem.ImageIndex;
int iIndex = lvItem.Index;
for (int i = iIndex + 1; i < listView1.Items.Count; i++) // correct the image indicies
listView1.Items[i].ImageIndex--;
lvItem.Remove(); // repaint
Image img = ImageList1.Images[iImageIndex];
ImageList1.Images.RemoveAt(iImageIndex);
img.Dispose();
The problem is that the ListView items probably remember the index of the image in the ImageList. If you remove an image from the ImageList, the ListView items will point to a wrong image.
Try to reference the images by key instead of by index.
This is a test I made
imageList1.Images.Add("img0", Properties.Resources.img0); // Use key as first argument.
imageList1.Images.Add("img1", Properties.Resources.img1);
imageList1.Images.Add("img2", Properties.Resources.img2);
imageList1.Images.Add("img3", Properties.Resources.img3);
imageList1.Images.Add("img4", Properties.Resources.img4);
imageList1.Images.Add("img5", Properties.Resources.img5);
for (int i = 0; i < 6; i++) {
var item = new ListViewItem(
"Image #" + i, // Text
"img" + i // <== Use key here, not index
);
listView1.Items.Add(item);
}
If I remove an entry with ...
listView1.Items[1].Remove();
imageList1.Images.RemoveAt(1);
... it works correctly.