I have a LineSeries chart. By series.IsSelectionEnabled = true; when I move the mouse over the points, I can select that node. But how can I do it when the mouse is not exactly over the point but when it's near it (above or under)? Thanks.
PS:
One more thing. How can I change the color of the column when the mouse is over it so the user can tell which one of the columns he/she is going to select.
I have created the example of the chart with the single LineSeries. You can click anywhere at the plot and the nearest point will be selected.
XAML (Change the ItemsSource property and other properties to yours):
<Charting:Chart MouseLeftButtonDown="Chart_MouseLeftButtonDown">
<Charting:Chart.Series>
<Charting:LineSeries IsSelectionEnabled="True" ItemsSource="..." ... />
</Charting:Chart.Series>
</Charting:Chart>
Code-behind:
private void Chart_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var chart = sender as Chart;
//In my example the line series is the first item of the chart series
var line = (LineSeries)chart.Series[0];
//Find the nearest point on the LineSeries
var newPoint = e.GetPosition(line);
var selectIndex = this.FindNearestPointIndex(line.Points, newPoint);
if (selectIndex != null)
{
//Select a real item from the items source
var source = line.ItemsSource as IList;
line.SelectedItem = source[selectIndex.Value];
}
}
private int? FindNearestPointIndex(PointCollection points, Point newPoint)
{
if (points == null || !points.Any())
return null;
//c^2 = a^2+b^2
Func<Point, Point, double> getLength = (p1, p2) => Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
//Create the collection of points with more information
var items = points.Select((p,i) => new { Point = p, Length = getLength(p, newPoint), Index = i });
var minLength = items.Min(item => item.Length);
//Uncomment if it is necessary to have some kind of sensitive area
//if (minLength > 50)
// return null;
//The index of the point with min distance to the new point
return items.First(item => item.Length == minLength).Index;
}
As I said this chart will select the nearest point even if you click at a great distance away from any chart point. If it isn't intended behavior, you can uncomment these lines and set any number in pixels:
//Uncomment if it is necessary to have some kind of sensitive area
if (minLength > 50)
return null;
I have written comments, but if something isn't clear you can ask and I will explain.
Related
In winforms I need to retrieve the position of each column in my DataGridView, to do this I use this function:
Rectangle rec = dgv.GetColumnDisplayRectangle(getColumnIndexByName(entry.Key), true);
and give me the rec of the column, but if I have a table with no records, the function get a rec with 0 value...
How can I solve this??
here is what I have if the grid has rows:
in the blue panel I've got some textbox that I use as filter of the value of the column below
Now if the grid has some rows, it works perfectly, I get right the column rectangle, I obtain the left and width and I'm able to place the filters above the header.. but if I set a wrong filter and the query retrieve 0 records, I don't know where place the textboxes
Edit: the code of getColumnIndexByName:
protected int getColumnIndexByName(string name)
{
foreach (DataGridViewColumn col in dgv.Columns)
{
if (col.Name.ToLower().Trim() == name.ToLower().Trim()) return col.Index;
}
return -1;
}
thanks
GetColumnDisplayRectangle returns Rectangle.Empty if the column is not Displayed.
You may also want to take a look at source code for GetColumnDisplayRectanglePrivate method which is used by DataGridView to calculate rectangle of the given column.
The method contains following code:
if (!this.Columns[columnIndex].Displayed)
{
return Rectangle.Empty;
}
Workaround
If you are looking for a workaround, you can use the following code which gets a list of rectangles without any flicker:
this.SuspendLayout();
var width = this.dataGridView1.Width;
this.dataGridView1.Width = 8388607;
var rectangles = this.dataGridView1.Columns.Cast<DataGridViewColumn>()
.Select(x => dataGridView1.GetColumnDisplayRectangle(x.Index, false))
.ToList();
this.dataGridView1.Width = width;
this.ResumeLayout();
I am developing windows form using c# and using datagridview object. I am almost done but I have a problem with displaying item value to a specific column(PTS GAIN COLUMN) that I selected in a comboboxcell all inside datagridview. Data is selected from database(coded). The column(PTS GAIN COLUMN) where I want to display the selected item value has no entry in the database. It is empty. I want that every time I select a item from a comboboxcell per row is that it will display the value to a specific column(PTS GAIN COLUMN) and compute the total dynamically/real-time(I want to show the result in label.text)
Also the combobox cell has items YES,NO,NA(this has no datatable, I just added the items by coding combobox.items.add("yes/no/na"). Yes item will get value depending on the column PTS AVAIL and display on column PTS GAIN. If I select no, 0 will display in PTS GAIN column, and if NA, both PTS AVAIL and PTS GAIN will have 0. Again I want to if possible to compute the total real-time.
Any help with this matter is much appreciated. I am meeting a dead line so please, anyone! Have a great day! Btw, I will post screenshot of the program, and if you want to see a particular block of code for reference just comment.
You will need to hook up with 2 events to accomplish this but for the most part it is pretty easy.
private void MyInitializeComponent()
{
dg.CurrentCellDirtyStateChanged += Dg_CurrentCellDirtyStateChanged;
dg.CellValueChanged += Dg_CellValueChanged;
this.CalculateTotals();
}
private void Dg_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (dg.Columns[e.ColumnIndex].Name == "Choices")
{
switch (dg.Rows[e.RowIndex].Cells["Choices"].Value.ToString())
{
case ("Yes"):
{
dg.Rows[e.RowIndex].Cells["PointsGained"].Value =
dg.Rows[dg.CurrentCell.RowIndex].Cells["PointsAvailable"].Value;
break;
}
case ("No"):
{
dg.Rows[e.RowIndex].Cells["PointsGained"].Value = 0;
break;
}
case ("NA"):
{
dg.Rows[e.RowIndex].Cells["PointsGained"].Value = "NA";
break;
}
}
this.CalculateTotals();
}
}
private void Dg_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if ((dg.Columns[dg.CurrentCell.ColumnIndex].Name == "Choices") &&
(dg.IsCurrentCellDirty))
{
dg.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
private void CalculateTotals()
{
var totalPointsGained = dg.Rows.Cast<DataGridViewRow>()
.Where(a => a.Cells["PointsGained"].Value?.ToString() != "NA")
.Sum(a => Convert.ToInt32(a.Cells["PointsGained"].Value));
var totalPointsAvailable = dg.Rows.Cast<DataGridViewRow>()
.Where(a => a.Cells["PointsAvailable"].Value?.ToString() != "NA")
.Sum(a => Convert.ToInt32(a.Cells["PointsAvailable"].Value));
lblTotalPointsGained.Text = "Total Points Gained: " + totalPointsGained;
lblTotalAvailable.Text = "Total Points Available: " + totalPointsAvailable;
}
You can put the code I have in MyInitializeComponent() wherever you initialize your other objects on the form.
i have some PictureBoxes in my program with different colors and I want to count, how much boxes there are for one color. So I created a function to count it:
private void cmdCount(object sender, EventArgs e)
{
int counterWhite, counterRed, counterGreen, counterYellow, counterBlue, counterOrange = 0;
if (alreadyAdded == false)
{
lstBox.Items.Add(picA1);
lstBox.Items.Add(picA2);
lstBox.Items.Add(picA3);
//...
alreadyAdded = true;
}
//Log
String value = Convert.ToString(lstBox.Items.Count);
lblLog.Text = "Objects in array: " + value;
for(int i = 0; i < lstBox.Items.Count; i++)
{
if(lstBox.Items[i].BackColor == Color.White)
{
counterWhite += 1;
}
else if...
}
}
I know, that my for-loop will not work that way, but it's the basic idea how I want to do it.
I have put all my PictureBoxes into my list and in the for-loop I want to count them. First it should play as long as the list is long, then every time it goes to the next box and should check the color of it and then add a one to the seperate counters. The problem is that I get errors every time and I have no idea how to read out the values of the BackColors in my list for each item.
Thank you for maybe helping me out :)
You're getting an error because the ListBox.Items collection is an ObjectCollection... it has to be, since it allows you store any object you want in it.
You'll have to cast the object back to a PictureBox before accessing properties on it:
if (((PictureBox)lstBox.Items[i]).BackColor == Color.White)
{
counterWhite += 1;
}
Or you could switch to a foreach loop and cast them all at once (using LINQ):
foreach (var pBox in new lstBox.Items.Cast<PictureBox>())
{
if (pBox.BackColor == Color.White)
{
counterWhite += 1;
}
...
}
Don't use a ListBox control as temporary storage for referencing your PictureBox controls though. You could create a List<PictureBox> to store references in, and then you won't have to cast when you iterate through the collection.
Or better yet (and the one I'd choose), just query the controls on your Form and count the number of controls of type "PictureBox" that have the BackColor you're looking for (using LINQ again).
var counterWhite = Controls.OfType<PictureBox>()
.Count(p => p.BackColor == Color.White);
var counterGreen = Controls.OfType<PictureBox>()
.Count(p => p.BackColor == Color.Green);
I am trying to plot multiple curves in different color on my graph. I am currently using one plotter (not sure if that will work, and that is why I am posting a thread here), and here is my code:
if (_dataXChA != null && _dataXChA.Length > 1)
{
EnumerableDataSource<double> xChA = new EnumerableDataSource<double>(_dataXChA);
xChA.SetXMapping(xVal => xVal);
if (_dataYChA != null && _dataYChA.Length == _dataXChA.Length)
{
EnumerableDataSource<double> yChA = new EnumerableDataSource<double>(_dataYChA);
yChA.SetYMapping(yVal => yVal);
CompositeDataSource dsChA = new CompositeDataSource(xChA, yChA);
((LineGraph)plotter.Children.ElementAt(startIndex)).DataSource = dsChA;
plotter.FitToView();
}
}
if (_dataXChB != null && _dataXChB.Length > 1)
{
EnumerableDataSource<double> xChB = new EnumerableDataSource<double>(_dataXChB);
xChB.SetXMapping(xVal => xVal);
if (_dataYChB != null && _dataYChB.Length == _dataXChB.Length)
{
EnumerableDataSource<double> yChB = new EnumerableDataSource<double>(_dataYChB);
yChB.SetYMapping(yVal => yVal);
CompositeDataSource dsChB = new CompositeDataSource(xChB, yChB);
((LineGraph)plotter.Children.ElementAt(startIndex)).DataSource = dsChB;
//LineGraph lgChA = plotter.AddLineGraph(dsChB, _dataBrushColorChB, 1, "Data");
plotter.FitToView();
}
}
The first curve should be in green, and the second curve should be in red. plotter is CharterPlotter But when I look at the graph,I only got one curve. Then I looked at the data, it seems the curve displays the data from second data source, but the color of the curve is green.
The constructor assigns the color like this:
LineGraph lgChA = plotter.AddLineGraph(dsChA, _dataBrushColorChA, 1, "Data");
LineGraph lgChB = plotter.AddLineGraph(dsChB, _dataBrushColorChB, 1, "Data");
where,
_dataBrushColorChA = Colors.Green;
_dataBrushColorChB = Colors.Red;
Basically, I only update the data points each time when event occurs, because I have tried AddLineGraph(), but it turned out to be very slow,
so I only update the data points.
So, anyone give me any pointers? How can I handle this multiple data source situation?
It looks like you are setting the data source for the same plotter child at startIndex for both channels:
((LineGraph)plotter.Children.ElementAt(startIndex)).DataSource = dsChA;
...
((LineGraph)plotter.Children.ElementAt(startIndex)).DataSource = dsChB;
The second assignment would cause the DataSource to be overridden by dsChB, which would make it only display one line.
Maybe the index should be different for A and B?
Hi I would like to know how can i do to set the label for X axis and y axis?
Righ now, i have a chart with the values, and I format the tooltip, but i can't realize how to set the label for X an Y axis.
Another thing is, Is posible to execute zooming in a chart series, I mean, if i have the x axis in years, i would like to change it to months, or semesters and new points need to appear in the line? if this is posible, is too dificult to do it?
I haven't able to set the label of the y axis (I don't think its possible) but you could set it on the legend using the Title property. On the x axis it depends on the binding set on your DataPointSeries'IndependentValueBinding.
Lets say on this sample I have created a class object that will represent every record/datapoint.
public class ChartInfo
{
public string Label { get; set; }
public double Value { get; set; }
}
Then I have this code:
List<ChartInfo> list = new List<ChartInfo>();
ChartInfo item = new ChartInfo();
item.Label = "Individual";
item.Vale = 27;
list.Add(item);
item = new ChartInfo();
item.Label = "Corporate";
item.Vale = 108;
list.Add(item);
DataPointSeries series = new ColumnSeries();
series.Title = "Quantity";
series.DependentValueBinding = new Binding("Value");
series.IndependentValueBinding = new Binding("Label");
series.ItemsSource = list;
series.SelectionChanged += new SelectionChangedEventHandler(series_SelectionChanged);
this.chartingToolkitControl.Series.Add(series);
It will give me this result.
alt text http://www.freeimagehosting.net/uploads/78e2598620.jpg
For the zooming - I think the right term is drill-down. You could use the SelectionChanged event (see the code above). What you should do is requery your datasource and clear the graph's series and add a new one based on your query result.
private void series_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//The sender here is of type DataPointSeries wherein you could get the SelectedItem (in our case ChartInfo) and from there you could do the requery.
}