chart control - string on x axis - c#

How do I show string values on the x axis of a chart control ? the datapoints (x & y) are both double. I have an IEnumerable containing a list of objects with 2 properties. The name of a report and an integer value indicating how many times it was run. so the integer is on the y axis and the report name is on the x axis. But I only seem to have double options for the value types. So when I create my datapoint, Im geting an error
DataPoint p1 = new DataPoint();
p1.XValue = log.ReportName; ---> this is invalid
p1.YValues = new Double[] { log.ReportTotal };
how can this be done ?
Ive tried this
reportTotal.XValueType = ChartValueType.String;
and when I plot my datapoints, Ive done this
int i = 1;
foreach (var log in this._reportLogs)
{
DataPoint p1 = new DataPoint();
p1.XValue = (double)i;
p1.YValues = new Double[] { log.ReportFrequency };
i++;
reportTotal.Points.Add(p1);
}
but now all im getting along the x axis are the number of the i varaible, how on earth do I just get the content of the text property ?
this is how I made it work
Dictionary<string, int> ReportLogs = new Dictionary<string, int>();
foreach (var log in this._reportLogs)
ReportLogs.Add(log.ReportName, log.ReportFrequency);
foreach (KeyValuePair<string, int> log in ReportLogs)
reportTotal.Points.AddXY(log.Key, log.Value);

The labels along the axis are created from the x-values and their number is determined either automatically as they fit or by setting the Interval property of the x-axis.
So their placement will not necessarily coincide with the actual DataPoints.
And you can format them but only within the normal formatting rules, ie you can't get creative with expressions to convert a number to a custom string, which seems to be just what you want: show a report name according to its number.
Other examples could be show a city from its zip code or a person name acording to emp.ID..
To gain full control you may need to use CustomLabels. They are a little bit tricky but quite useful. See several of these posts for numerous examples!
Also note: If you change the x-value type to string (which is possible : yourseries.XValueType = ChartValueType.String) these strings would show directly but you would loose the actual x-values! Usually not recommended (but possibly fine in your case)!
So if you want to can do it; however you can't prepare the DataPoint like you did. Instead you need to use the Points.AddXY method.
This will convert the strings to double (resulting in zeroes) and copy the string into the labels..:
reportTotal.Points.AddXY(log.ReportName, new Double[] { log.ReportTotal });
Update
Another way would be to set the AxisLabel of each DataPoint. For all of these to show up you may need to set the yourxaxis.Interval = 1.
So in your loop simply add
p1.AxisLabel = log.ReportName;

Related

How to align indexed Series in a Chart

When setting a Series to be indexed by setting the Series.IsXValueIndexed to true the chart requires all Series to be aligned:
If you are displaying multiple series and at least one series uses
indexed X-values, then all series must be aligned — that is, have the
same number of data points—and the corresponding points must have the
same X-values.
How can you add the necessary Emtpy DataPoints to the slots they are missing in, in any of the Series?
This routine first collects all values in all Series in a collection of doubles.
Then it loops over all Series and over all values and inserts the missing empty DataPoints:
void AlignSeries(Chart chart)
{
var allValues = chart.Series.SelectMany(s => s.Points)
.Select(x=>x.XValue).Distinct().ToList();
foreach (Series series in chart.Series)
{
int px = 0; //insertion index
foreach(double d in allValues )
{
var p = series.Points.FirstOrDefault(x=> x.XValue == d);
if (p == null) // this value is missing
{
DataPoint dp = new DataPoint(d, double.NaN);
dp.IsEmpty = true;
series.Points.Insert(px, dp);
}
px++;
}
}
}
Note that the code assumes ..
that your x-values are correctly set, i.e. they were added as numbers or DateTimes. If you added them as strings they all are 0 and indexing makes no sense.
that the DataPoints were added in ascending order. This is not always the case, especially when plotting LineCharts. However indexing these makes no sense either.
Also note that you can set several options of how to treat Empty DataPoints in a Series by setting properties in the Series.EmptyPointStyle, which is derived from DataPointCustomProperties.
So you could set their Color like this:
someSeries.EmptyPointStyle.Color = Color.Red;

How to filter or drop a value based on the previous one using Deedle in C#?

I am dealing with data from sensors. Sometimes these sensors have blackouts and brownouts, in consequence I can have the following kind of Time Series in a Frame, let's call it "myData":
[7.438984; 0,000002; 7.512345; 0.000000; 7.634912; 0.005123; 7.845627...]
Because I need only 3 decimals precision, I rounded the data from the frame:
var myRoundedData = myData.ColumnApply((Series<DateTime, double> numbers) => numbers.Select(kvp => Math.Round(kvp.Value, 3)));
I get the columns from the frame and filtered the Zeros "0.000":
var myFilteredTimeSeries = from kvp in myTimeSeries where kvp.Value != 0.000 select kvp;
So, my Time Series is partially filtered:
[7.439; 7.512; 7.635; 0.006; 7.846...]
However, the value "0.006" is not valid!
How could I implement an elegant filtering syntax based on the previous value, something like a "percent limit" in the rate of change:
if (0.006 / 7.635) * 100 < 0.1 then ---> drop / delete(0.006)
If you want to look just at the previous/next value, then you can shift the series by one and zip it with the original. This will give you a series of pairs (a value together with the previous/next value):
var r = actual.ZipInner(actual.Shift(1));
If you want to look at more elements around the specified one, then you'll need one of the windowing functions provided by Deedle:
Floating windows and chunking
The simplest example would be to use WindowInto to get a value together with 4 values before it:
var res = values.WindowInto(5, win =>
// 'win' is a series with the values - do something clever here!
);
One of the keys is to stay focused in methods that involve the value and its "neighbourhood", just like #tomaspetricek pointed before (Thanks!).
My goal was to find a "free-of-noise" time stamp or keys to build a Frame and perform an AddColumn operation, which is by nature a JoinKind.Left operation.
To solve the problem I used the Pairwise() method to get focused on "Item1" (current value), and "Item2" (next value) as follows:
double filterSensibility = 5.0 // % percentage
var myBooleanFilteredTimeSeries = myTimeSeries.Pairwise().Select(kvp => (kvp.Value.Item2 / kvp.Value.Item1) * 100 < filterSensibility);
Here I could write the relation I wanted! (see question) Then based on the Time Series (example) posted before I got:
myBooleanFilteredTimeSeries = [FALSE; FALSE; FALSE, TRUE; FALSE...]
TRUE means that this value is noisy! So I get only the FALSE boolean values with:
var myDateKeysModel = from kvp in myBooleanFilteredTimeSeries where kvp.Value == false select kvp;
I created a frame from this last Time Series:
myCleanDateTimeKeysFrame = Frame.FromRecords(myDateKeysModel);
Finally, I add the original (noisy) Time Series to the previously created Frame:
myCleanDateTimeKeysFrame.AddColumn("Column Title", myOrginalTimeSeries);
...et voilà!

passing a result(double type variable) from a method as an element of an array

Background -
I am new to programming in C# and am trying to code a custom indicator. On a trading platform that uses C#. A histogram with
positive Y axis = reqAskArraySum
negative Y axis = reqBidArraySum
The inbuilt compiler shows no errors. However the desired results do not seem to show up.
I know/it is possible there are some platform specific initialization problems i.e. code that I have not entered yet correctly.
Question -
The question here is with regards to the code posted below , which is a part of the whole code.
I would like to know whether the posted code satisfies the below objectives.
The objective here is to get a 'number' using a method.
Then only accept selected 'numbers' into an array. The selection/filtration is done by an 'If' statement
Any help and pointers would be highly appreciated.
int i=0;
double iAskSize = GetLevel2Size(ASK,0); //GetLevel2Size(ASK,0) is the method that helps me to retrieve the ASK side 'numbers'//
double iBidSize = GetLevel2Size(BID,0); //GetLevel2Size(BID,0) is the method that helps me to retrieve the BID side 'numbers' //
if(iAskSize>=AskTH_value) //the number should be >= user defined AskTH_value, this is the filtration of the Ask Side 'numbers'//
I am trying to get the filtered iAskSize 'numbers' into the array
reqAskSize, I believe there is a problem here. However I am not sure
{
double[] reqAskSize = new double[1000];
double reqAskArraySum = 0;
for(i=0;i<reqAskSize.Length;i++)
{
reqAskArraySum = reqAskArraySum + reqAskSize[i];
}
SetValue(0,reqAskArraySum);
}
if(iBidSize>=BidTH_value) **//the number should be >= user defined BidTH_value,this is the filtration of the Bid Side 'numbers'//**
I am trying to get the filtered iBidSize 'numbers' into the array
reqBidSize, I believe there is a problem here. However I am not sure
{
double[] reqBidSize = new double[1000];
double reqBidArraySum = 0;
for(i=0;i<reqBidSize.Length;i++)
{
reqBidArraySum = reqBidArraySum + reqBidSize[i];
}
SetValue(1,-reqBidArraySum);
}
If you are trying to add selected numbers into an array, once a number has been selected (through a conditional like if/else), the number should be set in the array.
For example
int[] array = new int[6];
for(int val = 0; val < 6; val++)
{
if(array[val] > 0)
array[val] = 6;
}
Also to obtain a numeral, a return statement makes clear if anything is got.
You should try and see where a return statement may belong within your code.

Extract x,y values from deldir object using RDotNet

Background
I am using RDotNet to run an R script that performs a voronoi tessellation using the deldir package. After
R:tiles = tile.list(voro) I wish to extract R:tiles[[i]][c("x","y")] for the each tile i into a C#:List<Tuple<double,double>>.
Issue 1
I can extract the R:tiles object into C#-world using var tiles = engine.Evaluate("tiles").AsVector().ToList(); but I am struggling to understand how to use RDotNet to extract the x, y values for each tile from this point:
I don't know how to iterate over this object to extract the x, y values that I desire.
Issue 2
Alternatively, I attempted to create a new simpler object in R, i.e. values and attempt to extract a string and parse values from that. So far I have only created this object for one of the points:
R: e.g.
values <- tiles[[1]][c("x","y")]
C#: e.g.
var xvalues = engine.Evaluate("values[\"x\"]").AsCharacter();
var yvalues = engine.Evaluate("values[\"y\"]").AsCharacter();
// Some boring code that parses the strings, casts to double and populates the Tuple
However I can only extract one string at a time and have to split the string to obtain the values I'm after. This does not seem like the way I should be doing things.
Question
How can extract the x,y coordinates for every tile from R:tiles[[i]][c("x","y")] into a C#:List<Tuple<double,double>>?
I think you are after something like the following if I got what you seek correctly. The full code I tested is committed to a git repo I've just set up for SO queries. I've tested against the NuGet package for 1.5.5; note to later readers that subsequent versions of R.NET may let you use other idioms.
var res = new List<List<Tuple<double, double>>>();
// w is the result of tile.list as per the sample in ?tile.list
var n = engine.Evaluate("length(w)").AsInteger()[0];
for (int i = 1; i <= n; i++)
{
var x = engine.Evaluate("w[[" + i + "]]$x").AsNumeric().ToArray();
var y = engine.Evaluate("w[[" + i + "]]$y").AsNumeric().ToArray();
var t = x.Zip(y, (first, second) => Tuple.Create(first, second)).ToList();
res.Add(t);
}

How to add string array into chartcontrol series

I have these arrays:
string[] Line1= data[3].ToString().Split(' ');
string[] Line2= data[4].ToString().Split(' ');
The string array contain only integer values. Data are like -20 -30 -12 0 10 20 30 and so on.
Now want to add these values which is in lineNeg1 to Devexpress Chart Control Series without loop.
As right now things are working but due to loop, system gets too slow. Code sample is here under:
for (int i = 0; i < Line1.Length; i++)
{
int y = int.Parse(Line1[i]);
SeriesPoint pt = new SeriesPoint(i, y);
chartControl1.Series[0].Points.Add(pt);
}
Is there any way that i can do something like: Add string array to series without using loop
maybe like: series[0].addrange[Line1] <- Maybe this kind of something option is available
I know the state is wrong, still just want to give an idea of what i am looking for.
You could use Linq:
int[] ints = Line1.Select(x => int.Parse(x)).ToArray();
It's still a for-loop, but now it's hidden! The compiler needs to convert the strings into ints one by one as they're fundamentally different things and stored quite differently. Strings are objects whereas integers are native types. It's not like Javascript or PHP where strings and integers get converted on the fly unfortunately. So this doesn't help you much, it's just semantic sugar.
Now, as far as adding the series goes, maybe the problem is that the chart redraws every time a point is added. Have you tried your code like this:
chartControl1.SuspendLayout();
for (int i = 0; i < Line1.Length; i++)
{
int y = int.Parse(Line1[i]);
SeriesPoint pt = new SeriesPoint(i, y);
chartControl1.Series[0].Points.Add(pt);
}
chartControl1.ResumeLayout();

Categories