I've got a 2d array of objects that I am passing into my seriesSet for creating a column graph, one column is a percentage, which is what I want to see plotted on the graph where the other I only want to show in the tooltip.
Can any of you highcharts geniuses think of a way I can do this? e.g. it looks something like this
{ 100, 20 }
{ 100, 20 }
{ 80 , 16 }
{ 80 , 16 }
{ 40 , 8 }
{ 40 , 8 }
{ 20 , 4 }
...
I know now how to refer to each in the SetToolTip Formatter using 'this.x/this.y/this.point.x' which is progress since I was stuck for a quite a while trying to do that. But now I only want the first set (100, 80, 40, 20) to be used to draw the graph where the second set should only be used for the tooltip.
Coming up on 2 weeks puzzling over this work item so any help is appreciated
edit:
to clarify, the graph draws as I want it to when I pass in a 1D object array consisting of the percentages only, then breaks when I include the second set (the counts)
so I am setting my seriesData like so:
List<DotNet.Highcharts.Options.Series> seriesSet = new List<DotNet.Highcharts.Options.Series>();
seriesSet.Add(new DotNet.Highcharts.Options.Series
{
Type = chartType,
Name = "ExampleArray",
Data = new DotNet.Highcharts.Helpers.Data(ExampleArray),
Color = tarColor,
PlotOptionsColumn = new DotNet.Highcharts.Options.PlotOptionsColumn
{
PointPadding = -0.1
},
});
where example array is composed of the list of numbers above.
I'm not sure I understood correctly but if what you want is get a collection of all the first items in this collection and then another one for the second then:
// Remove any item that doesn't have at least these two wanted values
items = items.Where(item => item.Count >= 1);
// Select the sub-collection you need
var percentages = items.Select(item => item[0]);
var tooltips = items.Select(item => item[1]);
From looking around a bit (like here) it seems you should pass a Object[] to the Data so replace ExampleArray with:
items.Select(item => (object)item[0]);
seriesSet.Add(new DotNet.Highcharts.Options.Series
{
Type = chartType,
//Name = "Targeted_" + Convert.ToString(tarCount.Count()),
Name = "Targeted",
Data = nnew DotNet.Highcharts.Helpers.Data((object[])targeted.Cast<object[]>()
.Select(item => (object)item[0])),
Color = tarColor,
PlotOptionsColumn = new DotNet.Highcharts.Options.PlotOptionsColumn
{
PointPadding = -0.1
},
});
Related
I am looking for guidence, and as I tried to convey with my title, I have an issue where I receive data that sometimes look like this for example :
entry[0] = "SHAPE", "X", "Y"
entry[1] = "Circle", "2", "3"
and sometimes may look like this:
entry[0] = "X", "Y", "SHAPE"
entry[1] = "2", "3", "Circle"
As you can see, they are ordered based on the first row values, which I will call "headerValues" below.
I am now trying to map my variables (for example "shape") so it's placed where the entry actually correlates to the shape value. I want to do this so I dont end up with a X number in my "Shape" variable due to a different input order then I planned for.
I am also well aware that I may want to remove the first row before I add them into my shapes, but that is an issue I want to try and figure out on my own in order to learn. I am only here due to the fact that I have been stuck on this problem for a while now, and therefore really appriciate any help I can get from a more seasoned programmer than me.
Below you will find the code:
var csvRows = csvData.Split(';');
var headerValues = csvRows[0].Split(',');
List<Shapes> shapes = new List<Shapes>();
if (csvRows.Count() > 0)
foreach (var row in csvRows)
{
var csvColumn = row.Split(',').Select(csvData => csvData.Replace(" ", "")).Where(csvData => !string.IsNullOrEmpty(csvData)).Distinct().ToList();
if (csvColumn.Count() == 5)
{
shapes.Add(new()
{
shape = csvColumn[0], //want to have same index palcement as where headervalue contains = "Shape"
});
}
else
{
Console.WriteLine(row + " does not have 5 inputs and cannot be added!");
}
}
Thank you in advance!
You can determine your column(s) by using linq:
var colShape = headerValues.ToList().FindIndex(e => e.Equals("SHAPE"));
and then use that to set the the property in the object:
shapes.Add(new()
{
shape = csvColumn[colShape], //want to have same index palcement as where headervalue contains = "Shape"
});
In the long run you would be better off using a csv parsing library.
Since your data is in the CSV format, you don't need to reinvent the wheel, just use a helper library like CsvHelper
using var reader = new StringReader(csvData);
using var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture);
var shapes = csvReader.GetRecords<Shapes>().ToList();
You may need to annotate the Shapes.shape field or property if it has different casing from the data, use the NameAttribute provided by CsvHelper
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.
I am pulling form values from a loosely bound razor form. The reason I am not using strongly bound model is that the payment value fields and categories are dynamic.
The form collection array reaching the controller is as below:
Payment {"1":"120","4":"23","6":"12","8":"120","9":"100"}
I need to split the array like (When PayCatId =1, Pay =120) (When PayCatId =4, Pay=23) etc..
string [] pa =collection["Payment"].Split(char.Parse(","));
string [] pc =collection.AllKeys["Payment"].Split(char.Parse(","));
Then I am trying to save to database using the logic below;
for (var i = 0; i < pa.Length; i++)
{
payment.Pay = Convert.ToDecimal(pa[i]);
payment.PayCatId = Convert.ToInt32(pa[i]); (added:how do i get this value from pair?)
payment.PayDate = DateTime.Now;
db.Payments.Add(payment);
db.SaveChanges();
}
Removed the Error bit as I have been enlightened that that approach is not applicable
I also want to know if this is the right and reliable approach to achieve this objective.
Just loop through two steps at a time instead of one
for (var i = 0; i < pa.Length; i+=2)
{
payment.Pay = Convert.ToDecimal(pa[i]);
payment.PayCatId = Convert.ToInt32(pa[i+1]);
payment.PayDate = DateTime.Now;
db.Payments.Add(payment);
db.SaveChanges();
}
You can use a combination of Split, Select and ToDictionary to do this, see the code:
var srt = "\"1\":\"120\",\"4\":\"23\",\"6\":\"12\",\"8\":\"120\",\"9\":\"100\"";
srt.Split(',')
.Select(x => x.Split(':'))
.ToDictionary(x => int.Parse(x[0].Replace("\"","")), x => int.Parse(x[1].Replace("\"","")))
/*
Output:
Dictionary<int, int>(5)
{
{ 1, 120 },
{ 4, 23 },
{ 6, 12 },
{ 8, 120 },
{ 9, 100 }
}
*/
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);
}