I'm trying to create a group of chart series, add data to the series and then show a few of the series by making them visible. The data changes and a user can select which series to view. I think this method will be better than clearing all series with chart.Series.Clear(); and then recreating the series in the same method.
For example a list of cars in a carpool with random mileages and then select which cars to show.
The code below doesn't work (I've commented where). The series aren't public and I think they need to be added to a public collection like a SeriesCollection but I'm not sure how.
Thanks for any help.
// create new chart series and add to a chartarea
ChartArea TestChartArea = new ChartArea();
public void CreateChartSeries()
{
List<string> lstCars = new List<string> { "Mazda", "Tesla", "Honda", "Jaguar", "Ford", "Toyota" };
foreach (string Car in lstCars)
{
// car series created correctly?
var Srs = new Series(Car);
Srs.ChartArea = TestChart.Name;
Srs.YAxisType = AxisType.Primary;
Srs.Color = Color.Red;
Srs.ChartType = SeriesChartType.Line;
TestChart.Series.Add(Srs);
}
}
// add data to chart series
public void SeriesData()
{
List<string> lstCars = new List<string> { "Mazda", "Tesla", "Honda", "Jaguar", "Ford", "Toyota" };
int[] Xseries = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] Milage = new int[10];
Random Random = new Random();
foreach (string Car in lstCars)
{
for (int M = 0; M < 10; M++)
Milage[M] = Random.Next(150, 15000);
// not sure how to call and add data to each series
Srs.Points.DataBindXY(Xseries, Milage);
}
}
// plot series - some visible
public void PlotCreatedSeries()
{
// not sure how to refer to each series
Mazda.Enabled = true;
Tesla.Enabled = false;
Honda.Enabled = true;
Jaguar.Enabled = false;
Ford.Enabled = true;
Toyota.Enabled = false;
}
The name 'Srs' you use to create the Series is only in scope i.e. usable within the loop. At the end of the loop you do add the newly created Series to your Chart:
TestChart.Series.Add(Srs);
The Series property is a public SeriesCollection. This is a bit confusing, as the singular type name and the plural property name are the same in this case, as oppose to, say, Legend(s) or ChartArea(s)..
From now on you can access it either by its index..
Series s = TestChart.Series[0] // the series you have added first
..or, more readable and more stable, by its Name property:
Series s = TestChart.Series["Mazda"] // the same series
TestChart.Series["Mazda"].Enabled = true;
Note that 'name' is also a tricky word:
When you declare a variable you give it a 'name'. Series s = new Series();
But many objects also have a property called Name: s.Name = "Volvo";
The former must be unique but the latter is just a string; do keep it unique as well, but the system will not guard you.
The former can never change, but, as you have seen, can go out of scope; the latter is just a string and you can change it.
Note that the variable itself doesn't go out of scop as long as it is still referenced somewhere, here as an element of the SeriesiesCollection Series..
Whether you want to add DataPoints bound or directly is up to you.
For the former there are many binding options.
For the latter you can use the Chart.Points methods..:
Add(DataPoint)
AddY(YValue)
AddXY(XValues, YValue(s))
Note that sometimes, especially with live charts it makes sense to insert a DataPoint using one of the InsertXXX methods!
Do look it up in MSDN! The middle version makes only sense if the x-values are either non-numeric or have to no real meaning, like, say, names.. - Note that adding meaningful x-values as numbers (or DateTimes) is crucial to use them for further goals, like tooltips, zoom or display ranges etc..
Failing to do so is probably the most common mistake newbies make. The Chart looks ok, but the data inside are broken, read lost.
Related
I have a list that is constantly being updated throughout my program. I would like to be able to compare the initial count and final count of my list after every update. The following is just a sample code (the original code is too lengthy) but it sufficiently captures the problem.
class Bot
{
public int ID { get; set; }
}
public class Program
{
public void Main()
{
List<Bot> InitialList = new List<Bot>();
List<Bot> FinalList = new List<Bot>();
for (int i = 0; i < 12345; i++)
{
Bot b = new Bot() {ID = i};
InitialList.Add(b);
}
FinalList = InitialList;
for (int i = 0; i < 12345; i++)
{
Bot b = new Bot() {ID = i};
FinalList.Add(b);
}
Console.Write($"Initial list has {InitialList.Count} bots");
Console.Write($"Final list has {FinalList.Count} bots");
}
}
Output:
Initial list has 24690 bots
Final list has 24690 bots
Expected for both lists to have 12345 bots.
What is correct way to copy the initial list so new set is not simply added to original?
To do what you seem to want to do, you want to copy the list rather than assign a new reference to the same list. So instead of
FinalList = InitialList;
Use
FinalList.AddRange(InitialList);
Basically what you had was two variables both referring to the same list. This way you have two different lists, one with the initial values and one with new values.
That said, you could also just store the count if that's all you want to do.
int initialCount = InitialList.Count;
FinalList = InitialList;
Although there's now no longer a reason to copy from one to the other if you already have the data you need.
I get the feeling you actually want to do more than what's stated in the question though, so the correct approach may change depending on what you actually want to do.
Suppose that I have a Color class
class Color
{
int id;
string name;
int? predecessorId;
Color(_id, _name, _predecessorId)
{
id = _id;
name = _name;
predecessorId = _predecessorId;
}
}
The purpose of the predecessor ID is that I can have a collection of colors and have them sorted arbitrarily. (I did not design this data structure.)
Suppose I have the following seven colors:
var colors = new []
{
new Color(0, "red", null),
new Color(1, "orange", 0),
new Color(2, "yellow", 1),
new Color(3, "green", 2),
new Color(4, "blue", 3),
new Color(5, "indigo", 4),
new Color(6, "violet", 5)
}
Now, suppose that I receive data from an external source. The data looks like the above, but the colors don't necessarily come in the array in the order that the predecessorId field. However, I will always assume that the predecessorId values always form an unbroken linked list.
Given an array of seven of these color objects, how do I sort them so that the first one is the one with no predecessor, the second one has the first one as its predecessor, and so on.
I am well aware that there are any number of ways to skin this cat, but I am using C# .net and I would like to use the built-in framework classes as much as possible to avoid writing more code that must be maintained.
Pick the first (and hopefully unique) color by finding the one with predecessorId == null. Then find the next by indexing a dictionary of predecessorId to color until no more matches are found:
var current = colors.Single(c => c._predecessorId == null);
var sorted = new List<Color> { current };
var colorsByPredecessorId = colors.Where(c => c._predecessorId.HasValue)
.ToDictionary(c => c._predecessorId.Value, c => c);
while (colorsByPredecessorId.ContainsKey(current._id))
{
current = colorsByPredecessorId[current._id];
sorted.Add(current);
}
return sorted;
I posted the same thing as ryanyuyu but deleted it as he was a few seconds faster, but seeing as it seems to do what you want and he hasn't submitted it as answer yet, I will:
var orderedColors = colors.OrderBy(x => x.predecessorId);
Which provides the output like so:
So I'm not sure what you mean in your comment
solution here would only work if we could guarantee that the predecessorIds are always in numeric order. If I changed the predecessor IDs to reverse the order, it wouldn't work anymore
It doesn't matter what order the source data is in. That's the point of OrderBy. It puts the collection in the order you dictate. Unless you can clarify on the requirements, this should suffice.
I am trying to sort a collection of objects in C# by a custom property.
(For context, I am working with the Twitter API using the Twitterizer library, sorting Direct Messages into conversation view)
Say a custom class has a property named label, where label is a string that is assigned when the class constructor.
I have a Collection (or a List, it doesn't matter) of said classes, and I want to sort them all into separate Lists (or Collections) based on the value of label, and group them together.
At the moment I've been doing this by using a foreach loop and checking the values that way - a horrible waste of CPU time and awful programming, I know. I'm ashamed of it.
Basically I know that all of the data I have is there given to me, and I also know that it should be really easy to sort. It's easy enough for a human to do it with bits of paper, but I just don't know how to do it in C#.
Does anyone have the solution to this? If you need more information and/or context just ask.
Have you tried Linq's OrderBy?
var mySortedList = myCollection.OrderBy(x => x.PropertyName).ToList();
This is still going to loop through the values to sort - there's no way around that. This will at least clean up your code.
You say sorting but it sounds like you're trying to divide up a list of things based on a common value. For that you want GroupBy.
You'll also want ToDictionary to switch from an IGrouping as you'll presumably be wanting key based lookup.
I assume that the elements within each of the output sets will need to be sorted, so check out OrderBy. Since you'll undoubtedly be accessing each list multiple times you'll want to collapse it to a list or an array (you mentioned list) so I used ToList
//Make some test data
var labels = new[] {"A", "B", "C", "D"};
var rawMessages = new List<Message>();
for (var i = 0; i < 15; ++i)
{
rawMessages.Add(new Message
{
Label = labels[i % labels.Length],
Text = "Hi" + i,
Timestamp = DateTime.Now.AddMinutes(i * Math.Pow(-1, i))
});
}
//Group the data up by label
var groupedMessages = rawMessages.GroupBy(message => message.Label);
//Convert to a dictionary for by-label lookup (this gives us a Dictionary<string, List<Message>>)
var messageLookup = groupedMessages.ToDictionary(
//Make the dictionary key the label of the conversation (set of messages)
grouping => grouping.Key,
//Sort the messages in each conversation by their timestamps and convert to a list
messages => messages.OrderBy(message => message.Timestamp).ToList());
//Use the data...
var messagesInConversationA = messageLookup["A"];
var messagesInConversationB = messageLookup["B"];
var messagesInConversationC = messageLookup["C"];
var messagesInConversationD = messageLookup["D"];
It sounds to me like mlorbetske was correct in his interpretation of your question. It sounds like you want to do grouping rather than sorting. I just went at the answer a bit differently
var originalList = new[] { new { Name = "Andy", Label = "Junk" }, new { Name = "Frank", Label = "Junk" }, new { Name = "Lisa", Label = "Trash" } }.ToList();
var myLists = new Dictionary<string, List<Object>>();
originalList.ForEach(x =>
{
if (!myLists.ContainsKey(x.Label))
myLists.Add(x.Label,new List<object>());
myLists[x.Label].Add(x);
});
Here is my problem in English:
I've got several WidgetContainer objects.
Each WidgetContainer will have at least one Widget.
Each WidgetContainer wants to display one of its Widgets n amount of times per day.
Widgets could be displayed on 'x' number of Venues.
A Widget is displayed for exactly t seconds before the next scheduled WidgetContainer's Widget takes its place.
If the entire day's is not filled up then nothing should be displayed during those times (ads should be evenly dispersed throughout the day t seconds at a time)
And here are the objects represented by pseudo code:
var WidgetContainers = [
{
DailyImpressionsRequired: 52, // should be split between Venues
Widgets: ["one", "two"],
Venues: ["here", "there"]
},
{
DailyImpressionsRequired: 20,
Widgets: ["foo"],
Venues: ["here", "there", "everywhere"]
},
{
DailyImpressionsRequired: 78,
Widgets: ["bar", "bat", "heyhey!"],
Venues: ["up", "down", "allAround"]
}
];
var SecondsInADay = 86400;
var DisplayInterval = 30; // seconds
var TotalNumverOrVenues = /*eh, some calulations...*/;
var AvailableSlots = /*eh, some calulations...*/;
var SlotsNeeded = /*eh, some calulations...*/;
I need to find an efficient way of calculating an evenly distributed schedule for these objects. These "objects" are linq-to-sql objects so some linq suggestions would be nice
My idea right now is to flatten the WidgetContainers to their Widgets; dividing their DailyImpressions by the number of Widgets.
I could figure it out easily if there weren't multiple and differing Venues to take into account.
I have a feeling I just need to see someone else's perspective on the problem since I've been staring at is so long.
So, any help that could possibly point me in the right direction or provide some perspective on the problem, even if it is obvious, would be greatly appreciated!
Based on that lot, if I've understood, this should give you correct answers:
static void Main(string[] args)
{
List<WidgetContainer> data = new List<WidgetContainer>();
data.Add(new WidgetContainer {
Widgets = new List<String> {"one","two"},
Venues = new List<String>{"here","there"},
DailyImpressionsRequired=52});
data.Add(new WidgetContainer {
Widgets = new List<String> {"foo"},
Venues = new List<String>{"here","there","everywhere"},
DailyImpressionsRequired=20});
data.Add(new WidgetContainer {
Widgets = new List<String> {"bar","bat", "heyhey!"},
Venues = new List<String>{"up","down", "allAround"},
DailyImpressionsRequired=78});
var SecondsInADay = 86400;
var DisplayInterval = 30; // seconds
var TotalNumverOfVenues = data.SelectMany(x=> x.Venues).Distinct().Count();
var AvailableSlots = SecondsInADay * data.SelectMany(x=> x.Venues).Distinct().Count() / DisplayInterval ; //assuming you didn't already have the count as a variable - will re-evaluate so don't use this for real!
//var AvailableSlots = SecondsInADay * TotalNumverOfVenues / DisplayInterval ; //the better way - avoids recalculating count
var SlotsNeeded = data.Sum(x => x.DailyImpressionsRequired);
}
We are using the dotNETCHARTING to portray our charts, they accepts Series for their SeriesCollection. This is a new chart I'm working on, all the previous ones have a 1:1 relation between value shown and value extracted. Now I have a list of values to show as a list of values 12:12.
I currently have my 2 lists of data showing (Actual vs Budgetted over the past 12 months) - but in a single Series, where they should be 2 Series. I have the data sorted and listed as needed, well almost listed right.
Restrictions: .NET 3.5 (VS2008), dotNETCHARTING.
It will be a very sad solution if I had to create 12 SQLs for each month and 12 for the budgetted. From what I see that is not necessary, as soon as I find a way to seperate each list into seperate Series.
Each Module has a List<ModuleValue>, I have tried with a Dictionary<int, List<ModuleValue>> so that each series of values (12months) could have a seperate List.
I have tried the For each list of Values, add each value in the list to a Series, repeat until out of List of values. (Foreach in a Foreach)
My question is: Can anyone give me some pointers to a possible solution. Graph below is per say correct, if there weren't lined up one after the other, but started and ended at the same timeframe (month). Eg budget for Jan compares to actual for Jan. I'm not asking about the dotNETCHARTING module, they have plenty of help. I'm asking for this mid-between and how it feeds the data to the module.
Main logic body:
protected override void CreateChildControls()
{
base.CreateChildControls();
//_chart.Type = ChartType.Combo;
_chart.DefaultSeries.Type = SeriesType.Line;
// Up for change - between here
IList listSeries = new List();
listSeries.Add(GetSeries(_module)); // This line should be listSeries = GetMultipleSeries(_module); or to that effect.
foreach (var series in listSeries)
{
_chart.SeriesCollection.Add(series);
}
// Up for change - and here
// This shows the title above the chart:
_chart.Title = _module.Title;
// This shows the title below the chart:
//_chart.XAxis.Label = new Label(_module.Title);
_chart.TitleBox.Line.Color = Charter.BackgroundColor;
base.SetAreaStyles();
base.SetLinkUrl(_module.LinkUrl);
}
This logic is the old logic, should remain as is - because all the other charts rely on it.
Can be used as a point of reference. Consider this logic locked.
protected Series GetSeries(FrontModule module)
{
Series series = new Series(module.Title);
foreach (var value in module.Values)
{
string sFieldTitle = value.Text;
Element element = new Element(sFieldTitle, value.Value);
element.Color = Charter.GetColor(value.ColorIndex);
series.Elements.Add(element);
string sToolTip = string.Format
("{0}: {1:N0}"
, value.Tooltip
, value.Value);
element.ToolTip = sToolTip;
if (!string.IsNullOrEmpty(value.LinkUrl))
{
element.URL = Page.ResolveUrl(value.LinkUrl);
}
ChartTooltip += string.Concat(sToolTip, ", ");
}
ChartTooltip += "\n";
return series;
}
This is the new Logic and should be changed to reflect the desired logic. Consider this as free as can be.
protected List GetMultipleSeries(FrontModule module)
{
List listSeries = new List();
Series series = new Series(module.Title);
foreach (var keyPair in module.DictionaryValues)
{
string sFieldTitle = keyPair.Value.Text;
Element element = new Element(sFieldTitle, keyPair.Value.Value);
element.Color = Charter.GetColor(keyPair.Value.ColorIndex);
series.Elements.Add(element);
string sToolTip = string.Format
("{0}: {1:N0}"
, keyPair.Value.Tooltip
, keyPair.Value.Value);
element.ToolTip = sToolTip;
if (!string.IsNullOrEmpty(keyPair.Value.LinkUrl))
{
element.URL = Page.ResolveUrl(keyPair.Value.LinkUrl);
}
ChartTooltip += string.Concat(sToolTip, ", ");
}
listSeries.Add(series);
ChartTooltip += "\n";
return listSeries;
}
This is how it shouldn't be, listing data in a sequal line. Though it shows it has all the required data.
I'd appreciate anything you could add. Thank you.
You need two Series objects:
protected List GetMultipleSeries(FrontModule module)
{
List listSeries = new List();
Series seriesActual = new Series(module.Title);
Series seriesBudgetted = new Series(module.Title);
foreach (var keyPair in module.DictionaryValues)
{
string sFieldTitle = keyPair.Value.Text;
Element element = new Element(sFieldTitle, keyPair.Value.Value);
element.Color = Charter.GetColor(keyPair.Value.ColorIndex);
// Is is actual or budgetted
if (keyPair.Value.IsActual)
seriesActual.Elements.Add(element);
else
seriesBudgetted.Elements.Add(element);
string sToolTip = string.Format
("{0}: {1:N0}"
, keyPair.Value.Tooltip
, keyPair.Value.Value);
element.ToolTip = sToolTip;
if (!string.IsNullOrEmpty(keyPair.Value.LinkUrl))
{
element.URL = Page.ResolveUrl(keyPair.Value.LinkUrl);
}
ChartTooltip += string.Concat(sToolTip, ", ");
}
listSeries.Add(seriesActual);
listSeries.Add(seriesBudgetted);
ChartTooltip += "\n";
return listSeries;
}
I'm assuming you have some way of testing whether the points are actual or budgetted for the if statement.