I'm implementing a little app with Xamarin Forms for a web page, the thing is that in this web is a linear chart with multiple entries and if the user clicks on a point of the line shows info about that point, as you can see in the picture:
Web Line Chart
After some work, I could create a more or less similar line chart using the OxyPlot.Xamarin.Forms plugin with multiple entries which shows the points
My App Line Chart
This is my code:
OnPropertyChanged("GraphModel");
var model = new PlotModel
{
LegendPlacement = LegendPlacement.Outside,
LegendPosition = LegendPosition.BottomCenter,
LegendOrientation = LegendOrientation.Horizontal,
LegendBorderThickness = 0
};
model.PlotType = PlotType.XY;
model.InvalidatePlot(false);
Dictionary<string, List<Prices>> values = HistoricData[Selected.ProductId];
int colorIndex = 0;
List<string> x_names = new List<string>();
foreach (var item in values.Keys)
{
if (item.ToUpper() == Selected.ProductName) { SelectedIndex = colorIndex; }
var lineSeries = new LineSeries()
{
Title = item,
MarkerType = MarkerType.Circle,
};
lineSeries.MarkerResolution = 3;
lineSeries.MarkerFill = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
lineSeries.MarkerStroke = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
lineSeries.MarkerSize = 3;
var points = new List<DataPoint>();
lineSeries.Color = OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
foreach (var price in values[item])
{
points.Add(new DataPoint(price.Week+price.Year, price.Price));
}
if (ButtonsVisibility.Count == 0)
{
lineSeries.IsVisible = (Selected.ProductName == item.ToUpper()) ? true : false;
}
else
{
lineSeries.IsVisible = ButtonsVisibility[colorIndex];
}
lineSeries.ItemsSource = points;
lineSeries.MarkerType = OxyPlot.MarkerType.Circle;
model.Series.Add(lineSeries);
colorIndex++;
}
NumButtons = colorIndex;
LinearAxis yaxis = new LinearAxis();
yaxis.Position = AxisPosition.Left;
yaxis.MajorGridlineStyle = LineStyle.Dot;
model.Axes.Add(yaxis);
LineChart = model;
OnPropertyChanged("GraphModel");
return LineChart;
My doubt is which property I should work with and show at least the value of a concrete point, I have seen the property OnTouchStarted but is only for all the LineSeries and not for a single point. I read in some articles that OxyPlot.Xamarin.Forms include a tracker. I added this line in my code:
lineSeries.TrackerFormatString = "X={2},\nY={4}";
Is supposed to show the x and y values on click but doesn't show anything, any suggestion?
Should show something like that: Tracker info on point
From the following example: Tracker Example
Updated Code
public PlotModel GetLineChart()
{
OnPropertyChanged("GraphModel");
var model = new PlotModel
{
LegendPlacement = LegendPlacement.Outside,
LegendPosition = LegendPosition.BottomCenter,
LegendOrientation = LegendOrientation.Horizontal,
LegendBorderThickness = 0
};
model.PlotType = PlotType.XY;
model.InvalidatePlot(false);
Dictionary<string, List<Prices>> values = HistoricData[Selected.ProductId];
int colorIndex = 0;
List<string> x_names = new List<string>();
foreach (var item in values.Keys)
{
if (item.ToUpper() == Selected.ProductName) { SelectedIndex = colorIndex; }
var lineSeries = new LineSeries()
{
Title = item,
MarkerType = MarkerType.Circle,
CanTrackerInterpolatePoints = false
};
lineSeries.MarkerResolution = 3;
lineSeries.MarkerFill = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
lineSeries.MarkerStroke = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
lineSeries.MarkerSize = 3;
var points = new List<DataPoint>();
lineSeries.Color = OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
foreach (var price in values[item])
{
points.Add(new DataPoint(price.Week+price.Year, price.Price));
}
if (ButtonsVisibility.Count == 0)
{
lineSeries.IsVisible = (Selected.ProductName == item.ToUpper()) ? true : false;
}
else
{
lineSeries.IsVisible = ButtonsVisibility[colorIndex];
}
lineSeries.ItemsSource = points;
lineSeries.MarkerType = OxyPlot.MarkerType.Circle;
lineSeries.TrackerFormatString = "X={2},\nY={4}";
lineSeries.TextColor = OxyPlot.OxyColor.Parse(SubCategoriesViewModel.AvailableColors[colorIndex]);
model.Series.Add(lineSeries);
colorIndex++;
}
NumButtons = colorIndex;
LinearAxis yaxis = new LinearAxis();
yaxis.Position = AxisPosition.Left;
//yaxis.StringFormat = "X={2},\nY={4}";
yaxis.MajorGridlineStyle = LineStyle.Dot;
model.Axes.Add(yaxis);
LineChart = model;
OnPropertyChanged("GraphModel");
return LineChart;
}
}
protected async override void OnAppearing()
{
await _viewModel.LinearViewModel.GetSubCategoryHistoricWeekPrices(App.ViewModel.LoginViewModel.SesionToken, FROM_DATE, TO_DATE);
Plot.Model = _viewModel.LinearViewModel.GetLineChart();
PlotController controller = new PlotController();
controller.UnbindAll();
controller.BindTouchDown(PlotCommands.PointsOnlyTrackTouch);
Plot.Controller = controller;
AddButtons();
}
Xaml Declaration for plot view:
<oxy:PlotView
Grid.Row="2"
Grid.RowSpan="2"
Grid.ColumnSpan="4"
x:Name="Plot" />
Your problem lies with following line.
lineSeries.TrackerKey = "X={2},\nY={4}";
When you use series.TrackerKey, you are specifying that you are using a CustomTracker, which in this case you are not. Custom trackers would be useful if you need to use different trackers for each series in the model.
In you case, you should remove that line and use only TrackerFormatString.
lineSeries.TrackerFormatString = "X={2},\nY={4}";
This would show the tooltip using the format string parameters, where {2} signifies X Value and {4} signifies Y. For your information, following are place holders.
{0} = Title of Series
{1} = Title of X-Axis
{2} = X Value
{3} = Title of Y-Axis
{4} = Y Value
If you need to include additional/custom information in your tool, your Data Point needs to be include that information. This where IDataPointProvider interface becomes handy. You could create a Custom DataPoint by implementing the interface and then you could include the same information in your tooltip as well.
Update Based On Comments
Additionally, to include "Touch", you can specify TouchDown in the PlotController. You can do so by defining the PlotController in your viewModel as following.
public PlotController CustomPlotController{get;set;}
You can define the property as follows.
CustomPlotController = new PlotController();
CustomPlotController.UnbindAll();
CustomPlotController.BindTouchDown(PlotCommands.PointsOnlyTrackTouch);
And in your Xaml
<oxy:Plot Controller="{Binding CustomPlotController}"......
Related
enter image description here
public async Task<ResultModel> AddPnrListByParams(AddPnrModel model)
{
AddPnrModel addPnrModels = new AddPnrModel();
{
addPnrModels.OrgDestFrom = model.OrgDestFrom;
addPnrModels.OrgDestTo = model.OrgDestTo;
addPnrModels.AirLineName = model.AirLineName;
addPnrModels.FlightNo = model.FlightNo;
addPnrModels.DepartureDate = model.DepartureDate;
addPnrModels.DepartureTime = model.DepartureTime;
addPnrModels.ArrivalTime = model.ArrivalTime;
addPnrModels.DepartureTerminal = model.DepartureTerminal;
addPnrModels.ArrivalTerminal = model.ArrivalTerminal;
addPnrModels.FareBasis = model.FareBasis;
addPnrModels.FareDet = model.FareDet;
addPnrModels.BagInfo = model.BagInfo;
addPnrModels.ClassType = model.ClassType;
addPnrModels.NextDayArrival = model.NextDayArrival;
addPnrModels.FareRule = model.FareRule;
}
_db.FlightSearchResults.Add(addPnrModels);
_db.SaveChanges();
return null;
}
I want to pass my ADDPNRMODEL to FLIGHTSEARCHMODEL and FLIGHTSEARCHMODEL is created I persistence folder I don't know how to solve this issue basically I want to create Post API to submit data in database
I'm using ElarsticSearch 7.7 & NEST 7.7 and on my web page, I'm getting 9 search result documents per page. Even I'm showing the first 9 results on the page, I need to return some property values from all the results for side filtration on the web page.
Eg: if I'm searching "LapTop", my page will show 9 results on the first page. Also, I need to show all the "Manufactures" from all the search results. Not only manufacturers in the first-page result. Then customers can filter by manufacture not only display on the first page.
I have tried GlobalAggregation but it returns categories and manufactures only items in selected page.
public SearchResult Search(SearchType searchType, string searchQuery, int storeId, int pageNumber = 1, int pageSize = 12, IList<SearchFilter> requestFilter = null, decimal? priceFrom = 0, decimal? priceTo = 100000000, string sortBy = null, int totalCount = 0)
{
var queryContainer = new QueryContainer();
var sorts = new List<ISort>();
sorts.Add(new FieldSort { Field = "_score", Order = SortOrder.Descending });
switch (sortBy)
{
case "z-a":
sorts.Add(new FieldSort { Field = Field<ElasticIndexGroupProduct>(p => p.SortValue), Order = SortOrder.Descending });
break;
case "a-z":
sorts.Add(new FieldSort { Field = Field<ElasticIndexGroupProduct>(p => p.SortValue), Order = SortOrder.Ascending });
break;
}
var aggrigations = new AggregationDictionary
{
{"average_per_child", new
AverageAggregation("average_per_child",Field<ElasticIndexGroupProduct>(d => d.Price))},
{"max_per_child", new MaxAggregation("max_per_child",Field<ElasticIndexGroupProduct>(d => d.Price))},
{"min_per_child", new MinAggregation("min_per_child", Field<ElasticIndexGroupProduct>(d => d.Price))},
{
"globle_filter_aggrigation", new GlobalAggregation("globle_filter_aggrigation")
{
Aggregations =new AggregationDictionary
{
{"category_flow", new TermsAggregation("category_flow"){Field = Field<ElasticIndexGroupProduct>(p => p.CategoryFlow)} },
{"manufacturers", new TermsAggregation("manufacturers"){Field = Field<ElasticIndexGroupProduct>(p => p.Manufacturer)} }
}
}
}
};
var searchRequest = new SearchRequest<ElasticIndexGroupProduct>()
{
Profile = true,
From = (pageNumber - 1) * pageSize,
Size = pageSize,
Version = true,
Sort = sorts,
//Scroll = Time.MinusOne,
Aggregations = aggrigations
};
var multiMatch = new QueryStringQuery
{
Query = searchQuery,
Fields = GetSearchFields(searchType),
Boost = 1.1,
Name = "named_query",
DefaultOperator = Operator.Or,
Analyzer = "standard",
QuoteAnalyzer = "keyword",
AllowLeadingWildcard = true,
MaximumDeterminizedStates = 2,
Escape = true,
FuzzyPrefixLength = 2,
FuzzyMaxExpansions = 3,
FuzzyRewrite = MultiTermQueryRewrite.ConstantScore,
Rewrite = MultiTermQueryRewrite.ConstantScore,
Fuzziness = Fuzziness.Auto,
TieBreaker = 1,
AnalyzeWildcard = true,
MinimumShouldMatch = 2,
QuoteFieldSuffix = "'",
Lenient = true,
AutoGenerateSynonymsPhraseQuery = false
};
searchRequest.Query = new BoolQuery
{
Must = new QueryContainer[] { multiMatch },
Filter = new QueryContainer[] { queryContainer }
};
var searchResponse = _client.Search<ElasticIndexGroupProduct>(searchRequest);
var categoryFlowsGlobe = new List<string>();
var allAggregations = searchResponse.Aggregations.Global("globle_filter_aggrigation");
var categories = allAggregations.Terms("category_flow");
foreach (var aggItem in categories.Buckets)
{
if (!categoryFlowsGlobe.Any(x => x == aggItem.Key))
{
categoryFlowsGlobe.Add(aggItem.Key);
}
}
}
This is the exact use case for Post filter - to run a search request that returns hits and aggregations, then to apply filtering to the hits after aggregations have been calculated.
For Manufacturers, these can be retrieved with a terms aggregation in the search request - you can adjust the size on the aggregation if you need to return all manufacturers, otherwise you might decide to return only the top x.
I have used linq to store data, below is the code:
var result = (dynamic)null;
var serviceData = (dynamic)null;
var userData = (dynamic)null;
/****Linq 1*****/
serviceData= dtPasscode.AsEnumerable().Select(m => new
{
ACCOUNT_ID = intAccountId,
SUB_ACC_ID = m.Field<string>("ACCOUNT_ID_ALIAS")
});
/**Linq 2**/
userData = DisplyCustomerDetails(Convert.ToInt64(strSubAccountID));
result = serviceData.Concat(userData);
And another linq through function:
/**Function**/
System.Data.EnumerableRowCollection DisplyCustomerDetails(Int64 intAccountId)
{
var result = (dynamic)null;
/** Data Display if no service avaiable **/
IAccount_BL objAccount_BL = new Account_BL();
Customer objCustomer = new Customer();
DataTable dtCustomer = null;
int intErrorCount = 0;
objCustomer.Account_Id = Convert.ToInt64(intAccountId);
dtCustomer = objAccount_BL.GetCustomerDetails(objCustomer, ref intErrorCount);
objAccount_BL = null;
objCustomer = null;
if (intErrorCount == 0)
{
if (dtCustomer != null)
{
if (dtCustomer.Rows.Count > 0)
{
result = dtCustomer.AsEnumerable().Select(m => new
{
ACCOUNT_ID = intAccountId,
SUB_ACC_ID = m.Field<string>("ACCOUNT_ID_ALIAS")
});
}
}
}
return result;
}
I wanted to join both the result of Linq1 & Linq2, I tired Concat & Union, getting below error
'System.Data.EnumerableRowCollection<<>f__AnonymousTypea>' does not contain a definition for 'Concat'
To Concat both enumerables must of the same class; you cannot use anonymous classes. Define a class that has the two fields and change the code to Select them.
Also, don't use ... = (dynamic) null; just assign the variable directly
var serviceData= dtPasscode ...
var userData = DisplyCustomerDetails ...
var result = serviceData.Concat(userData);
I am using the DynamicControlsPlaceholder by Denis Bauer to save the viewstate of dynamic controls after postback.
I used DynamicControlsPlaceholder before in an earlier part of my project and it worked flawlessly.
However, today I have run into difficulty. I have created a page where there are a number of text labels, slider bars and textboxes (defined by how many elements there are on a database) as shown below. The slider bars are JuiceUI slider controls and the text boxes are normal ASP.NET textboxes.
After postback the text labels (literal controls) and pie chart disappear, the textboxes reduce in size (text inside remains) and the sliderbars are reset to the lowest value without the ability to move the slider (the sliders cannot move at all).
I am quite new to ASP.NET and I am completely stumped as to why this is happening. Do you think it is a problem with the dynamic control placeholder, JuiceUI slider or my code (see below)?
{
SqlCeCommand cmdb = new SqlCeCommand();
cmdb.CommandText = "SELECT CriteriaName,CriteriaDesc FROM tblCriteria WHERE (DecisionID = #DID)";
cmdb.Parameters.AddWithValue("#DID", DID.Text.Trim());
cmdb.Connection = sqlConnection1;
reader = cmdb.ExecuteReader();
string[] criterianames = new string[critno];
string[] criteriadescs = new string[critno];
int i = 0;
while (reader.Read())
{
criterianames[i] = reader["CriteriaName"].ToString().Trim();
criteriadescs[i] = reader["CriteriaDesc"].ToString().Trim();
i++;
}
reader.Close();
Cont2.Controls.Add(new LiteralControl("<h3>Thank you for contributing to the following decision.<h4>Decision Goal: " + dgoal + "</h4><br><br><center>"));
Series weights = new Series();
weights.ChartType = SeriesChartType.Pie;
double[] yBar = new double[critno];
string[] xBar = new string[critno];
xBar = criterianames;
for (i = 0; i < critno; i++)
{
yBar[i] = 1;
}
ChartArea ca = new ChartArea();
ca.Position = new ElementPosition(0, 0, 100, 100);
ca.InnerPlotPosition = new ElementPosition(0, 0, 100, 100);
ca.BackColor = System.Drawing.Color.Transparent;
Chart piechart = new Chart();
piechart.RenderType = RenderType.ImageTag;
piechart.ChartAreas.Add(ca);
piechart.BackColor = System.Drawing.Color.Transparent;
piechart.Palette= ChartColorPalette.BrightPastel;
piechart.BorderColor = System.Drawing.Color.Black;
piechart.BorderSkin.PageColor = System.Drawing.Color.Transparent;
piechart.BorderSkin.BackColor = System.Drawing.Color.Transparent;
piechart.Width = 800;
piechart.Series.Add(weights);
piechart.ImageStorageMode = ImageStorageMode.UseImageLocation;
piechart.ImageLocation = "~/TempImages/ChartPic_#SEQ(300,3)";
piechart.Series[0].Points.DataBindXY(xBar, yBar);
piechart.DataBind();
Cont2.Controls.Add(piechart);
Cont2.Controls.Add(new LiteralControl("</center><h3>Please provide a weighting for each criterion.</h3><p>Please provide a weighting for each criterion along with a description of why you made this choice. </p>"));
for (i = 0; i < critno; i++)
{
Cont3.Controls.Add(new LiteralControl("<h3>" + criterianames[i] + "</h3><p><strong>Description: </strong>" + criteriadescs[i] + "</p><center>"));
Juice.Slider weightslider = new Juice.Slider();
weightslider.ID = "w" + i.ToString();
weightslider.Min = 1;
weightslider.Value = 50;
weightslider.Max = 100;
weightslider.AutoPostBack = true;
Cont3.Controls.Add(weightslider);
weightslider.ValueChanged += (o, a) =>
{
ClientScript.RegisterStartupScript(this.GetType(), "myalert", "alert('" + weightslider.Value.ToString() + "');", true);
};
TextBox wdesc = new TextBox();
wdesc.ID = "wd" + Convert.ToString(i);
wdesc.Rows = 3;
wdesc.Width = 900;
wdesc.TextMode = TextBoxMode.MultiLine;
Cont3.Controls.Add(wdesc);
Cont3.Controls.Add(new LiteralControl("</center>"));
}
Cont3.Controls.Add(new LiteralControl("<p align='right'>"));
Button continue1 = new Button();
continue1.Text = "Continue";
Cont3.Controls.Add(continue1);
Cont3.Controls.Add(new LiteralControl("</p>"));
// Database Disconnect
sqlConnection1.Close();
}
Many thanks for any help you can provide,
Kind regards,
Richard
You could eliminate or confirm the problem exists with Juice UI by creating a page containing nothing more than a Juice UI slider, one of these dynamic placeholders and a label. That'd be the first stop.
If you do run into problems with Juice UI, you can use it's cousin Brew
I am currently using a UILabel to display multiple lines of text. The line break most is set to NSLineBreakByWordWrapping so the the label automatically inserts line breaks. How can I detect where those line breaks are? Basically, I want to return a string with each \n break included.
Here is my work around for detecting line breaks inside UILabel
- (int)getLengthForString:(NSString *)str fromFrame:(CGRect)frame withFont:(UIFont *)font
{
int length = 1;
int lastSpace = 1;
NSString *cutText = [str substringToIndex:length];
CGSize textSize = [cutText sizeWithFont:font constrainedToSize:CGSizeMake(frame.size.width, frame.size.height + 500)];
while (textSize.height <= frame.size.height)
{
NSRange range = NSMakeRange (length, 1);
if ([[str substringWithRange:range] isEqualToString:#" "])
{
lastSpace = length;
}
length++;
cutText = [str substringToIndex:length];
textSize = [cutText sizeWithFont:font constrainedToSize:CGSizeMake(frame.size.width, frame.size.height + 500)];
}
return lastSpace;
}
-(NSString*) getLinebreaksWithString:(NSString*)labelText forWidth:(CGFloat)lblWidth forPoint:(CGPoint)point
{
//Create Label
UILabel *label = [[UILabel alloc] init];
label.text = labelText;
label.numberOfLines = 0;
label.lineBreakMode = NSLineBreakByWordWrapping;
//Set frame according to string
CGSize size = [label.text sizeWithFont:label.font
constrainedToSize:CGSizeMake(lblWidth, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap];
[label setFrame:CGRectMake(point.x , point.y , size.width , size.height)];
//Add Label in current view
[self.view addSubview:label];
//Count the total number of lines for the Label which has NSLineBreakByWordWrapping line break mode
int numLines = (int)(label.frame.size.height/label.font.leading);
//Getting and dumping lines from text set on the Label
NSMutableArray *allLines = [[NSMutableArray alloc] init];
BOOL shouldLoop = YES;
while (shouldLoop)
{
//Getting length of the string from rect with font
int length = [self getLengthForString:labelText fromFrame:CGRectMake(point.x, point.y, size.width, size.height/numLines) withFont:label.font] + 1;
[allLines addObject:[labelText substringToIndex:length]];
labelText = [labelText substringFromIndex:length];
if(labelText.length<length)
shouldLoop = NO;
}
[allLines addObject:labelText];
//NSLog(#"\n\n%#\n",allLines);
return [allLines componentsJoinedByString:#"\n"];
}
How to use
NSString *labelText = #"We are what our thoughts have made us; so take care about what you think. Words are secondary. Thoughts live; they travel far.";
NSString *stringWithLineBreakChar = [self getLinebreaksWithString:labelText forWidth:200 forPoint:CGPointMake(15, 15)];
NSLog(#"\n\n%#",stringWithLineBreakChar);
You just need to set parameters required by getLinebreaksWithString and you will get a string with each "\n" break included.
OutPut
Here is my solution to this problem.
I iterate through all whitespace characters in the provided string, set substrings (from beginning to current whitespace) to my label and check the difference in the height. When the height changes I insert a new line character (simulates a line break).
func wordWrapFormattedStringFor(string: String) -> String {
// Save label state
let labelHidden = label.isHidden
let labelText = label.text
// Set text to current label and size it to fit the content
label.isHidden = true
label.text = string
label.sizeToFit()
label.layoutIfNeeded()
// Prepare array with indexes of all whitespace characters
let whitespaceIndexes: [String.Index] = {
let whitespacesSet = CharacterSet.whitespacesAndNewlines
var indexes: [String.Index] = []
string.unicodeScalars.enumerated().forEach { i, unicodeScalar in
if whitespacesSet.contains(unicodeScalar) {
let index = string.index(string.startIndex, offsetBy: i)
indexes.append(index)
}
}
// We want also the index after the last character so that when we make substrings later we include also the last word
// This index can only be used for substring to this index (not from or including, since it points to \0)
let index = string.index(string.startIndex, offsetBy: string.characters.count)
indexes.append(index)
return indexes
}()
var reformattedString = ""
let boundingSize = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let attributes = [NSFontAttributeName: font]
var runningHeight: CGFloat?
var previousIndex: String.Index?
whitespaceIndexes.forEach { index in
let string = string.substring(to: index)
let stringHeight = string.boundingRect(with: boundingSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil).height
var newString: String = {
if let previousIndex = previousIndex {
return string.substring(from: previousIndex)
} else {
return string
}
}()
if runningHeight == nil {
runningHeight = stringHeight
}
if runningHeight! < stringHeight {
// Check that we can create a new index with offset of 1 and that newString does not contain only a whitespace (for example if there are multiple consecutive whitespaces)
if newString.characters.count > 1 {
let splitIndex = newString.index(newString.startIndex, offsetBy: 1)
// Insert a new line between the whitespace and rest of the string
newString.insert("\n", at: splitIndex)
}
}
reformattedString += newString
runningHeight = stringHeight
previousIndex = index
}
// Restore label
label.text = labelText
label.isHidden = labelHidden
return reformattedString
}
Image - Comparison of output string with label text.
This solution worked really well in my case, hope it helps.