Chartjs.Blazor Why IS Time Line Chart not rendering? - c#

Trying to get a Line chart to render using ChartJS.Blazor but I'm just getting an unhandled exception which I can't seem to catch in any form. The documentation isn't great and I get no hints on what's going wrong. Has anyone got experience with this?
NOTE: I cannot see the console as this is a .NET Maui Blazor App. Nothing appears in my output window in VS2022 either.
Here is my code:
LengthTimeOverTime.Razor:
#page "/lengthtimeovertimechart"
#using Application.Services.UIData;
#using ChartJs.Blazor.Common.Axes;
#using ChartJs.Blazor.Common.Time;
#using ChartJs.Blazor.LineChart;
#using ChartJs.Blazor;
#using Application.DTOs;
#inject IUIDataService uiDataService;
<div class="w-50">
<h3>Length Time Over Time</h3>
<Chart Config="#_config" />
</div>
#code {
private LineConfig _config;
protected override void OnInitialized()
{
var data = uiDataService.GetLengthTimeOverTimeData().Select(record => new TimePoint(record.Time, record.LengthTime)).ToArray();
_config = new()
{
Options = new()
{
Title = new()
{
Text = "Length Time Over Time"
},
Responsive = true,
MaintainAspectRatio = true,
Scales = new()
{
YAxes = new List<CartesianAxis>()
{
new LinearCartesianAxis
{
ScaleLabel = new()
{
Display = true,
LabelString = "Length Time",
},
//Ticks = new()
//{
// Display = true
//}
}
},
XAxes = new List<CartesianAxis>()
{
new TimeAxis
{
ScaleLabel = new()
{
Display = true,
LabelString = "Time"
},
//Ticks = new()
//{
// Display = true
//},
//Time = new TimeOptions()
//{
// TooltipFormat = "hh:mm:ss"
//},
}
}
}
},
};
var lineDataSet = new LineDataset<TimePoint>(data)
{
BackgroundColor = "rgb(124,252,0)",
Label = "Any",
};
_config.Data.Datasets.Add(lineDataSet);
}
}
My DTO: LengthTimeOverTimeDTO.cs
namespace Application.DTOs
{
public class LengthTimeOverTimeDTO
{
public DateTime Time { get; set; }
public double LengthTime { get; set; }
}
}
I have another scatter graph which follows almost the exact same pattern which renders absolutely fine. I did wonder if it was because there are two graphs, and so I added Id's for both, but that didn't resolve it either.
Am I doing something stupid?

Related

Is it possible to replace a part of a string with a dynamic 2 way binding?

Goal
Provided a list of labels (and a moustache or other style string template), I want to dynamically create some input boxes, and then replace wherever the parameter exists in the template with a 2 way binding of what is in the input box. I am able to create the input boxes, but no idea how I can substitute the values for them into the template (or whether this is even possible in Blazor).
Example
public class TemplateService
{
private List<NameValuePairs> _labels = new List<NameValuePairs>
{
new NameValuePairs(){Name="Name", Value="John"},
new NameValuePairs(){Name="Age", Value="27"},
new NameValuePairs(){Name="Height", Value=""},
new NameValuePairs(){Name="Hair Colour", Value=""},
};
public Task<Template> GetTemplateByIdAsync(int id)
{
Template t = new Template() { Labels = _labels, Text = #"Hi, my name is {{Name}}. I am {{Age}} years old" };
return Task.FromResult(t);
}
}
#inject TemplateService TemplateService
<h3>Text Templates</h3>
#if (template == null)
{
<p><em>Loading...</em></p>
}
else
{
#foreach (var label in template.Labels)
{
<label>
#label.Name :
<input #bind-value="label.Value" #bind-value:event="oninput" />
</label>
}
<h4>Model</h4>
#foreach (var l in template.Labels)
{
<p>#l.Value</p>
}
<h4>Interpolated</h4>
//HERE I WANT TO DISPLAY A RESULT OF template.text WHEREBY {{Name}} WOULD BE SUBSTITUTED WITH JOHN IN THIS CASE, AND WOULD CHANGE AS THE INPUTBOX FOR NAME IS TYPED IN
}
#code {
private Template template;
protected override async Task OnInitializedAsync()
{
template = await TemplateService.GetTemplateByIdAsync(1);
}
}
It is the Interpolated bit in the view I am stuck with. I have control over the service so thought about replacing {{Age}} with a function like #FindValueForName("Age"), but not sure if this could be inserted as executable code instead of a literal string. Any thoughts of how this could be achieved in any way?
If you can re-structure a bit, you could try this
public class Template
{
public List<NameValuePairs> Labels { get; set; }
public Func<string> Text { get; set; }
}
public class TemplateService
{
private List<NameValuePairs> _labels = new List<NameValuePairs>
{
new NameValuePairs(){Name="Name", Value="John"},
new NameValuePairs(){Name="Age", Value="27"},
new NameValuePairs(){Name="Height", Value=""},
new NameValuePairs(){Name="Hair Colour", Value=""},
};
public string GetListValue(string Name) => _labels.Find(lbl=>lbl.Name==Name).Value;
public Task<Template> GetTemplateByIdAsync(int id)
{
Template t = new Template()
{
Labels = _labels,
Text = () => $#"Hi, my name is {GetListValue("Name")}. I am {GetListValue("Age")} years old" };
return Task.FromResult(t);
}
}
...
<h4>#template.text()</h4>

How to get a Sitecore item from a fake site

In a unittest I am using Sitecore.FakeDb.
I have extended the sample to add the fakeSite with a rootPath, which gets set.
If I try to retrieve the rootItem with Context.Site.GetItem(rootPath) it returns null.
[Test]
public void FakeSite()
{
// create a fake site context
var fakeSite = new Sitecore.FakeDb.Sites.FakeSiteContext(
new Sitecore.Collections.StringDictionary
{
{ "name", "website" }, { "database", "web" }, { "rootPath", "/sitecore/content/NL" }
});
// switch the context site
using (new Sitecore.Sites.SiteContextSwitcher(fakeSite))
{
var rootItem = Context.Site.Database.GetItem(Context.Site.RootPath); // returns null
Assert.IsNotNull(rootItem);
Assert.AreEqual("website", Sitecore.Context.Site.Name);
Assert.AreEqual("master", Sitecore.Context.Site.Database.Name);
}
}
What am I missing?
You need to add fake item to your fake db first.
See sample code from github here:
public void HowToCreateSimpleItem()
{
using (Sitecore.FakeDb.Db db = new Sitecore.FakeDb.Db
{
new Sitecore.FakeDb.DbItem("Home") { { "Title", "Welcome!" } }
})
{
Sitecore.Data.Items.Item home = db.GetItem("/sitecore/content/home");
Xunit.Assert.Equal("Welcome!", home["Title"]);
}
}
public void HowToCreateHierarchyOfItems()
{
using (Sitecore.FakeDb.Db db = new Sitecore.FakeDb.Db
{
new Sitecore.FakeDb.DbItem("Articles")
{
new Sitecore.FakeDb.DbItem("Getting Started"),
new Sitecore.FakeDb.DbItem("Troubleshooting")
}
})
{
Sitecore.Data.Items.Item articles =
db.GetItem("/sitecore/content/Articles");
Xunit.Assert.NotNull(articles.Children["Getting Started"]);
Xunit.Assert.NotNull(articles.Children["Troubleshooting"]);
}
}
https://github.com/sergeyshushlyapin/Sitecore.FakeDb/wiki/Creating-a-Simple-Item
https://github.com/sergeyshushlyapin/Sitecore.FakeDb/wiki/Creating-a-Hierarchy-of-Items
As #Marek said, I didn't create an item, just set the rootPath to which item it should point.
This is the working test.
[Test]
public void FakeSite()
{
using (Db db = new Db("web")
{
new DbItem("NL") { { "Title", "NL Site" } }
})
{
Item siteItem = db.GetItem("/sitecore/content/NL");
// create a fake site context
var fakeSite = new Sitecore.FakeDb.Sites.FakeSiteContext(
new Sitecore.Collections.StringDictionary
{
{ "name", "website" }, { "database", "web" }, { "rootPath", "/sitecore/content/NL" }
});
// switch the context site
using (new Sitecore.Sites.SiteContextSwitcher(fakeSite))
{
Assert.AreEqual("website", Sitecore.Context.Site.Name);
Assert.AreEqual("web", Sitecore.Context.Site.Database.Name);
var rootItem = Context.Site.Database.GetItem(Context.Site.RootPath);
Assert.IsNotNull(rootItem);
}
}
}
Although I realize that Site means the CM/CD site. Not the MultiSite I was looking for.

Xamarin Forms "...DisplayAlert does not exist in the current context."

I'm working with Xamarin, still new to it, but I'm having a problem that I get the feeling I shouldn't be. Here's my problem:
using System;
using Xamarin.Forms;
namespace DataBinding_Lists
{
public class App
{
public static Page GetMainPage ()
{
var listView = new ListView { RowHeight = 40 };
listView.ItemsSource = new Person []
{
new Person { FirstName = "Abe", LastName = "Lincoln" },
new Person { FirstName = "Groucho", LastName = "Marks" },
new Person { FirstName = "Carl", LastName = "Marks" },
};
listView.ItemTemplate = new DataTemplate(typeof(TextCell));
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "FirstName");
listView.ItemSelected += async (sender, e) => {
await DisplayAlert ("Tapped!", e.SelectedItem + " was tapped.", "OK", "");
};
return new ContentPage {
Content = new StackLayout
{
Padding = new Thickness (5,20,5,5),
Spacing = 10,
Children = { listView }
}
};
}
}
}
Obviously, I've got a class on another page called "Person." This class has two properties: "FirstName" and "LastName." When I try and put all of this together like so in Xamarin, I get the error saying: "The name 'DisplayAlert' does not exist in the current context."
or just try to use :
await App.Current.MainPage.DisplayAlert("Alert", "your message", "OK");
There are two ways to solve this and I lean toward the second one. Close to what Edward L. has said.
DisplayAlert is a method on a Xamarin.Forms Page... and you are inside of a static method that is returning that page, so you have no reference to it.
So you could do this:
using System;
using Xamarin.Forms;
namespace DataBinding_Lists
{
public class App
{
private static Page page;
public static Page GetMainPage ()
{
var listView = new ListView { RowHeight = 40 };
listView.ItemsSource = new Person []
{
new Person { FirstName = "Abe", LastName = "Lincoln" },
new Person { FirstName = "Groucho", LastName = "Marks" },
new Person { FirstName = "Carl", LastName = "Marks" },
};
listView.ItemTemplate = new DataTemplate(typeof(TextCell));
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "FirstName");
listView.ItemSelected += async (sender, e) => {
await page.DisplayAlert ("Tapped!", e.SelectedItem + " was tapped.", "OK", "");
};
page = new ContentPage {
Content = new StackLayout
{
Padding = new Thickness (5,20,5,5),
Spacing = 10,
Children = { listView }
}
};
return page;
}
}
}
What you should really do is create a new class that is your page.
Your App.cs turns into this:
using System;
using Xamarin.Forms;
namespace DataBinding_Lists
{
public class App
{
public static Page GetMainPage ()
{
return new PeoplePage();
}
}
}
Then you create a new class that inherits from Page:
using System;
using Xamarin.Forms;
namespace DataBinding_Lists
{
public class PeoplePage : Page
{
public PeoplePage()
{
var listView = new ListView { RowHeight = 40 };
listView.ItemsSource = new Person []
{
new Person { FirstName = "Abe", LastName = "Lincoln" },
new Person { FirstName = "Groucho", LastName = "Marks" },
new Person { FirstName = "Carl", LastName = "Marks" },
};
listView.ItemTemplate = new DataTemplate(typeof(TextCell));
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "FirstName");
listView.ItemSelected += async (sender, e) => {
await DisplayAlert ("Tapped!", e.SelectedItem + " was tapped.", "OK", "");
};
Content = new ContentPage {
Content = new StackLayout
{
Padding = new Thickness (5,20,5,5),
Spacing = 10,
Children = { listView }
}
};
}
}
}
DisplayAlert() is a method of the Page class.
In order for your class to be able to use it, it needs to either instantiate a Page object and invoke it using that object or directly inherit from it.
Since you stated that your Person class is actually also a Page class, you could also invoke it using one of your Person objects i.e. personObj.DisplayAlert(...)
Perhaps something similar to the following:
var personObj = new Person();
personObj.DisplayAlert ("Tapped!", e.SelectedItem + " was tapped.", "OK", "");
This of course, assumes that your Person class declaration goes something like the following:
public class Person : Page
{
...
}
Obviously, the exact implementation will depend on how you structure your code. I am just going by what I can see in your question and assuming a few things.

Updating Monotouch.Dialog RootElement

I have been following the code from the monotouch.dialog task sample app (http://docs.xamarin.com/ios/Guides/User_Interface/MonoTouch.Dialog/Elements_API_Walkthrough).
Something I dont seem able to work out, is when the user clicks the + button a new row to the table is added. The user touches this and navigates to another screen where they can enter information. Now, when they navigate back to the root view, I want the list of rootElements to be updated so the entered name is used instead of the default name 'connection'
How would I go about updating the text for each of the RootElements based upon what has been entered?
I hope that all makes sense!
-- code below.
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
RootElement _rootElement = new RootElement ("To Do List"){ new Section()};
DialogViewController _rootVC = new DialogViewController (_rootElement);
// This adds an 'add' button to the root view controller, and handles by delegate,the push to a screen where you can add a new vCenter connection profile.
UIBarButtonItem _addButton = new UIBarButtonItem (UIBarButtonSystemItem.Add);
_rootVC.NavigationItem.RightBarButtonItem = _addButton;
_addButton.Clicked += (sender, e) => {
var connectionProfile = new connectionProfile{};
// See, on the line below, I add a default RootElement with the text New Connection.
var connectionProfileElements = new RootElement ("New Connection") {
new Section () {
// When they enter the name on the next line of code, I want to use this to update the New Connection text on the root screen.
new EntryElement ("Name", "Enter Connection Name", connectionProfile._connectionName),
new EntryElement ("VC Address", "Enter VC Address", connectionProfile._address),
new EntryElement ("VC Port", "Enter VC Port", connectionProfile._port),
new EntryElement ("Username", "Enter Username", connectionProfile._userID),
new EntryElement ("Password", "Enter Password", connectionProfile._password,true)}
};
_rootElement [0].Add (connectionProfileElements);
};
UINavigationController _nav = new UINavigationController (_rootVC);
window.RootViewController = _nav;
window.MakeKeyAndVisible ();
return true;
}
}
And :
public class connectionProfile
{
public connectionProfile ()
{
}
public string _connectionName { get; set; }
public string _address { get; set; }
public string _port { get; set; }
public string _userID {get; set; }
public string _password { get; set; }
}
Did you try this.ReloadData(); like this ?
public NameOfYourDialogViewController() : base (UITableViewStyle.Grouped, null)
{
Root = new RootElement ("test") {
new Section ("First Section"){
new StringElement ("Hello", () => {
new UIAlertView ("Hola", "Thanks for tapping!", null, "Continue").Show ();
}),
new EntryElement ("Name", "Enter your name", String.Empty)
},
new Section ("Second Section"){
},
};
}
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Do your stuff
}
public override void ViewWillAppear (bool animated)
{
base.ViewWillAppear (animated);
this.ReloadData();
}
You can find other topic about that here.
EDIT
You forgot to say that this is located in your AppDelegate, which is not made for what you are doing.
At first, create a DialogViewController: Project > Add new file > MonoTouch > DialogViewController.
And in the different method I mentioned earlier, put the ReloadData() method.
These methods (ViewDidLoad, WillAppear and so on) are override methods from UIView.
AppDelegate exists to get data before your is launched, store static data for your app, and so on. It's completly different usage.
And for your AppDelegate, you should have this for example:
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
_window = new UIWindow (UIScreen.MainScreen.Bounds);
_controller = new NameOfYourDialogViewController();
_navigationController = new UINavigationController (_controller);
UIImageView splash = new UIImageView(_window.Bounds);
splash.Image = UIImage.FromFile("Default.png");
_window.AddSubview(splash);
_window.AddSubview(_navigationController.View);
_window.BringSubviewToFront(splash);
_window.MakeKeyAndVisible ();
// This is used to create a fadding effect in your splashscreen
UIView.Animate(1,
delegate { splash.Alpha = 0f; },
delegate {
_window.RootViewController = _navigationController;
splash.RemoveFromSuperview();
});
return true;
}

How to cast from a list with objects into another list.

I have an Interface [BindControls] which takes data from GUI and store it into a list „ieis”.
After that, Into another class, which sends this data through WebServices, I want to take this data from „ieis” and put it into required by WS Class fields (bottom is a snippet of code)
This is the interface:
void BindControls(ValidationFrameBindModel<A.B> model)
{
model.Bind(this.mtbxTax, (obj, value) =>
{
var taxa = TConvertor.Convert<double>((string)value, -1);
if (taxa > 0)
{
var ieis = new List<X>();
var iei = new X
{
service = new ServiceInfo
{
id = Constants.SERVICE_TAX
},
amount = tax,
currency = new CurrencyInfo
{
id = Constants.DEFAULT_CURRENCY_ID
}
};
ieis.Add(iei);
}
},"Tax");
}
This is the intermediate property:
//**********
class A
{
public B BasicInfo
{
get;
set;
}
class B
{
public X Tax
{
get;
set;
}
}
}
//***********
This is the class which sends through WS:
void WebServiceExecute(SomeType someParam)
{
//into ‚iai’ i store the data which comes from interface
var iai = base.Params.FetchOrDefault<A>( INFO, null);
var convertedObj = new IWEI();
//...
var lx = new List<X>();
//1st WAY: I tried to put all data from ‚Tax’into my local list ‚lx’
//lx.Add(iai.BasicInfo.Tax); - this way is not working
//2nd WAY: I tried to put data separately into ‚lx’
var iei = new X
{
service = new ServiceInfo
{
id = iai.BasicInfo.Tax.service.id
},
amount = iai.BasicInfo.Tax.amount,
currency = new CurrencyInfo
{
id = iai.BasicInfo.Tax.currency.id
}
};
lx.Add(iei);
// but also is not working
Can you help me please to suggest how to implement a way that will fine do the work (take data from ‚ieis’ and put her into ‚lx’).
Thank you so much
As noted in my comment, it looks like iai.BasicInfo.Tax is null, once you find out why that is null your original Add() (#1) will work.

Categories

Resources