I have a lot of charts i am graphing and for some i need to give each X value a custom color and have that item in the legend as well. The following code achieves this but on the legend there is always the first default value which is now pointless. How would i remove/hide/clear that original default legend item? Just for reference the chartColors array is just an array of colors to cycle through.
mainChart.Legends.Add(new Legend("legend"));
mainChart.Legends["legend"].Docking = Docking.Bottom;
foreach (Series ser in mainChart.Series)
{
int i = 0;
foreach (DataPoint point in ser.Points)
{
point.Color = chartColors[i % chartColors.Count()];
mainChart.Legends["legend"].CustomItems.Add(point.Color,point.AxisLabel);
i++;
}
}
According to http://msdn.microsoft.com/en-us/library/vstudio/dd456659(v=vs.100).aspx
"Legend items in this collection are always attached to the end of other legend items in the legend. "
so to maybe clarify my question, how do i remove the "other legend items"
edit: I found this answer but it is for winforms and im not sure how to call the function they add
WinForms.Charting suppress autogenerating legend
Okay figured it out posting in case someone else needs to do it, i used the awnser from the posted question as a bases but added the points before the method was called and then only removed the non custom ones
protected void chartarea1_CustomizeLegend(object sender,System.Web.UI.DataVisualization.Charting.CustomizeLegendEventArgs e)
{
int customItems = ((Chart)sender).Legends[0].CustomItems.Count();
if (customItems>0)
{
int numberOfAutoItems = e.LegendItems.Count()-customItems;
for (int i = 0; i < numberOfAutoItems; i++)
{
e.LegendItems.RemoveAt(0);
}
}
}
for all series at your Chart do next:
series .IsVisibleInLegend = false;
mainChart.Series[0].IsVisibleInLegend = false;
Related
after searching for a proper solution for the given problem, I could not accept the solutions in the forum and also the solutions provided by Microsoft are not what I was looking for.
Here is an example of the problem:
(Link to image) MSChart pie diagram with overlapping labels
I tried to set the labels outside of the diagram, but first, the connection lines are kind of ugly and second, the outside labels use too much space.
Also the SmartLabels didn't solve the problem.
Edit:
Does anybody know how to set the labels radius? It seems like the center of each label is a fixed radius?!
Did anybody figure out how to fix the overlapping labels?
Prepare your data!
You have to set points in the Series, so it is easy to create a list at first, enter your data to that list, sort the list in descending or ascending order of the value you want to display and than resort that list to place the smallest value between the two biggest value.
For example:
100
70
50
30
10
5
(old order)
100
5
70
10
50
30
(new order)
Resulting diagram:
(Link to image) MSChart pie diagram without overlapping labels
Lets call every slice of the pie a category, with a value and a tooltip text.
So you need a class like this:
private class CatValue
{
public string CategoryName;
public decimal Value;
public string TooltipText;
}
Now put your data in the List object, for example like this:
List<CatValue> listOfPieSlices = new List<CatValue>();
// ....
// Add your data to the list object
// ....
// Sort the list in ascending order
listOfPieSlices.Sort((x, y) => x.Value.CompareTo(y.Value));
// Sort the list in descending order
// listOfPieSlices.Sort((x, y) => -x.Value.CompareTo(y.Value));
//
// Now sort the "listOfPieSlices" with the specialSort methode:
listOfPieSlices = specialSort<CatValue>(listOfPieSlices);
// Now create the Series of DataPoints
Series sortedPoints = new Series("PCDSsorted");
for (int i = 0; i < listOfPieSlices.Count; i++)
{
sortedPoints.Points.AddY(Math.Abs(listOfPieSlices[i].Value));
sortedPoints.Points[i].Label = listOfPieSlices[i].CategoryName + ": "+listOfPieSlices[i].Value.ToString();
sortedPoints.Points[i].Font = new Font("Microsoft Sans Serif", 12);
sortedPoints.Points[i].LabelToolTip = listOfPieSlices[i].Tooltip;
sortedPoints.Points[i].ToolTip = listOfPieSlices[i].Tooltip;
}
private List<T> specialSort<T>(List<T> objects)
{
if (objects == null) return null;
int count = objects.Count;
List<T> sortedList = new List<T>();
for (int i = 0; i <= count - 1; i += 2)
{
if(i == count - 1)
sortedList.Add(objects[i / 2]);
else
{
sortedList.Add(objects[i / 2]);
sortedList.Add(objects[count - 1 - (i / 2)]);
}
}
return sortedList;
}
Hope this works for someone out there as well as it did for me.
Happy coding
Is there any way to get the index of the first item currently showing, when the list is scrollable?
I'm making a CharMap with some extensions and just found that ListView can't contain 64k items (see code below)
for (var i = char.MinValue; i < char.MaxValue; i++)
{
var c = Convert.ToChar(i);
if (!char.IsControl(c))
lv1.Items.Add(""+c);
}
so decided to load chars when scroll is at some appropriate points (ie first/last 15%) but ListView doesn't give absolute position of the scrollbar.
It does feel a little hackish, but maybe it will do the job:
int getFirstVisibleItem(ListView lv)
{
ListViewHitTestInfo HI;
for (int i = 0; i < Math.Min(lv.ClientSize.Width, lv.ClientSize.Height); i += 3)
{
HI = lv.HitTest(i, i);
if (HI.Item != null) return HI.Item.Index;
}
return -1;
}
This does not directly help with your scrolling issue but should find the first visible Item as you have asked. If your Items have extremely weird (ie non-square) shapes you may want to change the travesal code a little..
For your requirement implement ListView with custom scrollbar. So you have more control over your scroll position. You decide when to what action based on scroll position. This might be helpful Code
This question might be a bit open ended but I am looking for advice on how to tackle the issue of merging and splitting Grid cells dynamically.
So currently I generate a Grid of X rows and Y columns and each of these cells is then populated with a StackPanel. When clicked a StackPanel has a border added to it and an array holding bool values representing each Grid cell is modified to show that it has been clicked. The grid holds no other controls. This is all done programmatically in c# rather than in the xaml.
What I want to be able to do is to merge and split selected cells in the grid if they are next to each other by hitting a button. I have found no way of easily doing this and I am wondering if anyone can recommend a good approach for this issue.
I am not tied to my current approach and any alternate method suggestions are appreciated.
I just posted one example of how to do this on Github.
The basic idea is to initialize the grid with the maximum number of rows and columns and then create and delete cells while modifying their positions and spans as necessary to fill the grid.
Here are the most relevant snippets:
Initialization: (MainWindow.xaml.cs)
public MainWindow()
{
InitializeComponent();
var numRows = grid.RowDefinitions.Count;
var numCols = grid.ColumnDefinitions.Count;
for (int i = 0; i < numRows; i++)
{
for (int j = 0; j < numCols; j++)
{
var item = new DynamicGridItem(j, i);
item.Merge += HandleMerge;
item.Split += HandleSplit;
grid.Children.Add(item);
Grid.SetRow(item, i);
Grid.SetColumn(item, j);
}
}
}
Here you can see that my cell items are a custom UserControl class with a constructor that takes two integers as X and Y grid coordinates. These coordinates can then be used to simplify grid row and column assignment. They aren't strictly necessary in my example, but could be helpful if storing/loading grid items and working with them in other code modules.
Merge Event:
My example uses a single Merge event with a boolean event argument to indicate whether the merge is to the left or to the right. The actual event handler just figures out whether to offset left or right and passes that information as well as the source item to the HandleMergeOffset method below.
private void HandleMergeOffset(DynamicGridItem item, int offset)
{
var otherItem = FindItemByXOffset(item, offset);
if (otherItem == null)
{
return; // Nothing to do
}
otherItem.Merge -= HandleMerge;
otherItem.Split -= HandleSplit;
Grid.SetColumnSpan(item, Grid.GetColumnSpan(item) + Grid.GetColumnSpan(otherItem));
grid.Children.Remove(otherItem);
if (offset < 0) // Reposition item if merging left
{
Grid.SetColumn(item, otherItem.X);
item.X = otherItem.X;
}
}
You could probably write a better FindItem method than I did. The basic idea is to find the neighbor to remove, expand the current item to include both original column spans, unhook events from the removed item, and of course remove it from the grid.
Split Event:
Since we remove an item on Merge, we create a new item on Split. I decided not to bother with a SplitLeft or SplitRight designation. This code will always create a minimum cell at the left-most index of the original cell and push the remainder one column right.
private void HandleSplit(object sender, EventArgs e)
{
var item = (DynamicGridItem)sender;
var itemColSpan = Grid.GetColumnSpan(item);
if (itemColSpan < 2)
{
return; // Nothing to do
}
var newItem = new DynamicGridItem(item.X, item.Y);
newItem.Merge += HandleMerge;
newItem.Split += HandleSplit;
grid.Children.Add(newItem);
Grid.SetColumn(newItem, newItem.X);
Grid.SetRow(newItem, newItem.Y);
Grid.SetColumn(item, item.X + 1);
Grid.SetColumnSpan(item, itemColSpan - 1);
item.X += 1;
}
Note that this makes no attempt to handle splitting a single cell into half cells (so that we never mutate the original grid or deal with sub-grids). If you need arbitrary dynamic grid behavior, you'll want to look elsewhere.
I have a method in a windows form application that tries to remove 2 text boxes from a panel.
In the method, I loop through all the controls in the panel. There are always supposed to be 2 panels removed and added together, but when removing, it randomly removes 1 or 2 containers when I press the button.
Here is the code to remove the textboxes:
private void removeRows()
{
string descName = "Desc" + (textBoxCounter - 1).ToString();
string costName = "Cost" + (textBoxCounter - 1).ToString();
if (textBoxCounter >= 0)
{
foreach (Control c in costItems.Controls)
{
if (c.Name == descName)
{
// Remove the control from the panel and dispose of it
panel.Controls.Remove(c);
c.Dispose();
}
if(c.Name == costName)
{
// Remove the control from the panel and dispose of it
panel.Controls.Remove(c);
c.Dispose();
}
}
// Decrement the counter
// This happens only once since two controls need to be removed
if (textBoxCounter == 0)
textBoxCounter = 0;
else
textBoxCounter--;
}
else
MessageBox.Show("There are no more rows to remove", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
testlabel1.Text = textBoxCounter.ToString();
testlabel2.Text = panel.Controls.Count.ToString();
}
Here is the code to add a button:
private void addRows(string desc, string cost)
{
if (textBoxCounter >= maxExpenses)
{
MessageBox.Show("Maximum number of expenses entered", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
TextBox Desc = new TextBox();
TextBox Cost = new TextBox();
// Give the text boxes names
Desc.Name = "Desc" + textBoxCounter.ToString();
Cost.Name = "Cost" + textBoxCounter.ToString();
// Format the text boxes
Desc.Width = panel.Width / 2;
Cost.Width = panel.Width / 4;
// Add the items to the costItems panel
panel.Controls.Add(expenseDesc);
panel.Controls.Add(expenseCost);
// Add the items to the expenses dictionary
panel.Add(Desc, Cost);
// Increment the text box counter variable
textBoxCounter++;
testlabel1.Text = textBoxCounter.ToString();
testlabel2.Text = costItems.Controls.Count.ToString();
}
}
Some info to know.
There will always be 2 textboxes added and removed, they relate to each other.
The textBoxCounter is initialized to 0, so the first two boxe names will be "Desc0" and "Cost0".
When I press the button to remove rows the first time, one text box is removed, and then if i press it again it might remove 2, it might only remove 1.
I tried debugging and I noticed that the foreach loop that iterates over all the controls in the panel seems to loop one time short of the full number of controls.
Any help with my code would be great.
Your problem is caused by the foreach, modifying the collection in foreach may cause some unexpected behavior. You just want to remove the TextBoxes with names being known beforehand, so why not using the method ControlCollection.RemoveByKey?
If you want to remove the last added textBoxes (Desc... and Cost...) do this:
panel.Controls.RemoveByKey(descName);
panel.Controls.RemoveByKey(costName);
If you want to remove all the added textBoxes (suppose you have other kinds of TextBoxes, otherwise we can use a little LINQ to remove all the textboxes easily):
for(int i = 0; i < textBoxCounter; i++){
panel.Controls.RemoveByKey("Desc" + i);
panel.Controls.RemoveByKey("Cost" + i);
}
Your code has two problems: you are disposing something you cannot dispose and you are iterating through a collection (which you are modifying) in the wrong way. You can delete all the Controls by doing:
panel.Controls.Clear();
Or iterating backwards by relying on the indices:
for (int i = panel.Controls.Count - 1; i >= 0; i--)
{
panel.Controls.RemoveAt(i);
}
Regarding the Dispose, you can use it if you wish but don't need to use Remove:
for (int i = panel.Controls.Count - 1; i >= 0; i--)
{
panel.Controls[i].Dispose();
}
PS: I asked something identical to this and got -6. One of the reasons for maintaining this question was precisely being helpful to others (I saw the code you are using to delete controls in internet and I knew that quite a few people were using it). Pretty ironical, indeed.
I would like to know how many rows are actually displayed by a WPF DataGrid.
I tried looping over DataGridRow and checking IsVisible, but it seems that rows report IsVisible = true even when they are not in the DataGrid viewport.
How can I count the number of visible rows correctly?
I've asked this question also on MSDN forum and got a good answer:
private bool IsUserVisible(FrameworkElement element, FrameworkElement container) {
if (!element.IsVisible)
return false;
Rect bounds = element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}
I had the same problem with rows showing as Visible = true even when they weren't.
Trying to come up with a solution, I posted this question: Visible rows in DataGrid is off by 1 (counted using ContainerFromItem).
Here's what worked for me:
uint VisibleRows = 0;
var TicketGrid = (DataGrid) MyWindow.FindName("TicketGrid");
foreach(var Item in TicketGrid.Items) {
var Row = (DataGridRow) TicketGrid.ItemContainerGenerator.ContainerFromItem(Item);
if(Row != null) {
if(Row.TransformToVisual(TicketGrid).Transform(new Point(0, 0)).Y + Row.ActualHeight >= TicketGrid.ActualHeight) {
break;
}
VisibleRows++;
}
}
For further guidance, there are some /* comments */ in my answer on the linked question, as well as a thread of user comments on the question itself that led to the answer.
a simple hack come to mind,
loop over all rows and check if item has a container?
dataGrid.GetContainerFromItem(dataGrid.Items[row]);
hope this helps
If you need it for another xaml element just add a reference via "ElementName" and the property via "Items.Count" to your content property(in this case "Text"). You might also use a converter to parse the value.
<TextBlock Text="{Binding ElementName=ComponentDataGrid, Path=Items.Count, Converter={StaticResource IntToStringConverter}}"/>