I have written a following code in my .NET Controller action which fetches specific time zone. After that I Group by the data based on that time zone like following:
[HttpPost]
[ActionName("FetchTimeZone")]
public ActionResult FetchTimeZone(string timeZone)
{
using (var ctx = new myContext())
{
var loggedUser = ctx.Users.FirstOrDefault(x => x.Email == User.Identity.Name);
var userStores = ctx.UserStores.Where(x => x.UserId == loggedUser.UserId).SelectMany(x => x.StoreItems.SelectMany(y => y.StoreItemTransactions)).ToList();
var hourlyData = userStores
.GroupBy(x => TimeZoneInfo.ConvertTime(x.TransactionDate.Value, TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time")).Hour)
.Select(pr => new HourlyGraph { Hour = pr.Key, Sales = pr.Sum(x => x.QuantitySoldTransaction) })
.ToList();
hourlyData.AddRange(Enumerable.Range(0, 24)
.Except(hourlyData.Select(m => m.Hour))
.Select(i => new HourlyGraph() { Hour = i, Sales = 0 }));
ViewBag.HourlyGraph = hourlyData.OrderBy(x => x.Hour).ToList();
return Json("OK");
}
}
All here is good, so we move on to the View part with jQuery and data representation.. Initially when the page is loaded I fill graph with CEST time zone and it represents the data correctly. So I made an on click event which basically should redraw the morris graph based on the time zone that was sent.
By default I set in code Tokyo Standard Time just to get different results to verify whether this works or not. So initially I fill the graph like this:
var hourlyGraph = Morris.Bar({
element: 'graph_bar',
data: [
#foreach (var item in ViewBag.HourlyGraph)
{
#:{device: '#item.Hour.ToString("D2"):00', geekbench:#item.Sales },
}
],
xkey: 'device',
ykeys: ['geekbench'],
labels: ['Sold'],
barRatio: 0.4,
barColors: ['#0A4D70', '#34495E', '#ACADAC', '#3498DB'],
xLabelAngle: 35,
hideHover: 'auto',
resize: true
});
The onclick event looks like this:
$('.convertTimeZone').click(function(event){
event.preventDefault();
$.post("/GeoTargeting/FetchTimeZone",{timeZone: "timeZone"}).done(function(data){
hourlyGraph.redraw();
$(window).trigger('resize');
});
});
This is the part that doesn't works:
hourlyGraph.redraw();
$(window).trigger('resize');
I need to somehow now just redraw the graph after the post is done and display it on my View... But I'm not sure what am I doing wrong here... Can someone help me out ?
Your method is returning json (and the only thing in the response is "OK" - it does not contain any data that could be used to update your chart). There is no point setting ViewBag properties unless you return a view (and the view is using those values).
Your method should either
Return a partial view containing the html for a new graph based on
the values of hourlyData and in the success callbacl of your
$.post() function, update the DOM by replacing the existing graph,
or
Return json containing the data for updating the graph, for example
var data = hourlyData.Select(x => new { device = x.Hour.ToString(...), geekbench = x.Sales }); return Json(data); so
that in the success callback, you can the update the data in the graph (e.g. using hourlyGraph.setData(data);)
Related
I have a Django Framework project (Python) and I want to add a real-time chart to my webpage. For that I am using Plotly. In my script in javascript I am generating random numbers and plotting them in the chart. Now, I want to plot specific data (coming from POST requests thanks to Django Rest Framework) so that the user can check that real-time data, together with old data (for example, from yesterday). The thing is that javascript is running in the client side, that means that my chart starts when the user reload the page, so there is no old data to load, he can just check real time one. If he reload the site, the previous data will disappear too. Any idea of how to achieve this?
My script is:
<script>
function rand() {
return Math.random();
}
var time = new Date();
var data = [{
x: [time],
y: [rand()],
mode: 'line',
line: {color: '#80CAF6'}
}]
Plotly.plot('chart', data);
var cnt = 0;
var interval = setInterval(function() {
var time = new Date();
var update = {
x: [[time]],
y: [[rand()]]
}
Plotly.extendTraces('chart', update, [0])
if(cnt > 100) {
Plotly.relayout('chart',{
xaxis: {
range: [cnt-100,cnt]
}
});
}
}, 1000);
</script>```
I get data from my Firebase database every ten seconds. I want to plot a graph with highcharts.js, but when I access the web page, I get all the data prior to the moment the page is loaded. At this point, my web page freezes and crashes after a few minutes while the data is being recovered. (I also have a second piece of code that retrieves the new data each time it is added to the database but the web page crashes before).
I think it's my plotvalue function that is crashing the page. When I suppress plotValue(...) I can read my data in a few seconds even if there's 15k+. Indeed, it may be because i'm redrawing the chart each time I receive a data instead of waiting to retrieve it all.
I would like to know how to draw the chart once all of my data is retrieved from my database.
I thank you in advance for your answers.
Here is the code:
setInterval(test, 10000);
function test() {
if (it === 0) {
console.log("it=", it);
dbRef.orderByKey().once('value', snapshot => { //"value" to have all existing data
var jsonData = snapshot.toJSON();
//console.log(jsonData); // Test purpose
var coco = Object.values(jsonData); //Extract the numerical values of the object
// Save values on variables
//console.log(Défaut_communication_automate); //Test purpose
coco.forEach(elt => { //Choose in coco for each element
var Puissance_batterie = elt.Puissance_batterie; //Choose all the values
of the element named "Puissance_batterie"
var Puissance_AC_Onduleurs = elt.Puissance_AC_Onduleurs;
var Tension_réseau = elt.Tension_réseau;
var timestamp = elt.timestamp;
//console.log(Puissance_batterie);
plotValues(chartALM, timestamp, Puissance_batterie, 0);
plotValues(chartALM, timestamp, Puissance_AC_Onduleurs, 1);
plotValues(chartALM, timestamp, Tension_réseau, 2);
})
++it;
console.log("it=", it);
})
}
//function to plot values on charts
function plotValues(chart, timestamp, value, series) {
var x = epochToJsDate(timestamp).getTime();
var y = Number(value);
chart.series[series].addPoint([x, y], false, false);
chart.redraw();
}
// Create the charts when the web page loads
window.addEventListener('load', onload);
function onload(event) {
chartALM = createALMChart();
barALM = createALMBar();
}
// Create Chart
function createALMChart() {
var chart = new Highcharts.Chart({
chart: {
renderTo: 'chart-ALM',
type: 'spline'
}
//The following is only about the visual aspect
}
I have a simple issue relating to function sequence that I'm struggling to solve on my own. Essentially, I'm rendering a bar chart based on parameters selected by the user. This works perfectly on the first go, but when the user updates their parameter selection and hits "render" again, the chart doesn't update. It's only on the second button click that the chart renders with updated data.
I can see in the console that the "generate chart" function IS receiving the updated data, so I know the issue is that the chart is rendering before the new data is loaded. I understand that the common solution to this type of problem would be using JS Promises or callbacks, but I can't figure out how to execute it, and would really appreciate some help.
Here's the code structure. I have to make two API calls to get the data.
const ajaxCallOne = () => {
// Make API call for first data set (data1). On success, invoke the next API call, passing data1.
success: (data1) => {
ajaxCallTwo(data1);
}
}
const ajaxCallTwo = (data1) => {
// Make second API call for another set of data ('data2'). On success, invoke a function to visualize all the data in a bar chart, passing it both data1 and data2.
success: (data2) => {
populateBarChart(data1, data2)
}
}
const populateBarChart = (data1, data2) => {
// Function to create a bar chart and populate it with data1 and data2 from the API calls
// The data retrieved from the API calls is based on parameters selected by the user. When the user selects new parameters and hits the "view results" button again, we kick off another round of API calls, which pass the new data to this populateBarChart function.
// I am using ApexCharts.js library to render the bar chart. The syntax for actually rendering the chart looks like this:
let chart = new ApexCharts(
document.querySelector("#audience-reach-chart-container"),
options
);
chart.render();
}
At first I thought I needed to destroy / empty out the chart at the beginning of the function, but since the function creates a new instance of the chart every time it's invoked, there's nothing to target and destroy.
Also, when I console.log the data being received by the populateBarChart function, I can see the updated data is being received. So the issue must be that the chart is rendering based on the old data, and I need to control the sequence of events?
I can't figure out how to go about this within the code design. Any insights would be greatly appreciated.
You're absolutely right that this can be solved using callbacks, promises and even async/await.
However, this problem statement may have a nuance e.g. if you're using <select> dropdowns, the event listener registered on them could be slightly incorrect. OR, if the ApexCharts library's behavior is to withhold rendering for some reason, then it'll be hard to say so without going through the documentation. Hence, an example on codesandbox or jsfiddle can help us.
Let's look at one possible solution - Ideally, when using promises, we should do something like this:
let container = document.querySelector("#audience-reach-chart-container");
const ajaxCallOne = () => {
// Make API call for first data set (data1).
// No need to invoke the next API call. Just ensure it returns a promise object
}
const ajaxCallTwo = () => {
// Make second API call for another set of data ('data2').
// Sit still! No need to do anything here as long as this function returns a promise
}
// Run this when the user hits 'render' button
Promise.all([ajaxCallOne, ajaxCallTwo]).then((values) => {
console.log(values);
// values[0] holds data1
// values[1] holds data2
let options = doSomethingOnThese(values[0], values[1]);
// Populate the BarChart here
let chart = new ApexCharts(container, options);
chart.render();
});
I've created a sample app(https://codesandbox.io/s/unruffled-cookies-tbcerp?file=/src/index.js) to mimic your usecase here:
// https://codesandbox.io/s/unruffled-cookies-tbcerp?file=/src/index.js
(async function () {
var options = {
chart: {
height: 350,
type: "bar"
},
dataLabels: {
enabled: false
},
series: [],
title: {
text: "Ajax Example"
},
noData: {
text: "Loading..."
}
};
var chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
try {
const ajaxCallOne = await fetch(
"https://my-json-server.typicode.com/apexcharts/apexcharts.js/yearly"
);
const ajaxCallTwo = await fetch(
"https://my-json-server.typicode.com/apexcharts/apexcharts.js/yearly2"
);
let data1 = await ajaxCallOne.json();
let data2 = await ajaxCallTwo.json();
console.log(data1, data2);
chart.updateSeries([
{
name: "Sales",
data: data1
},
{
name: "Sales2",
data: data2
}
]);
} catch (e) {
console.log(e);
}
})();
I am creating a frontend to a patient booking system with Vue.js, which is simply a dynamic web form. The user first selects a type of treatment, then the practitioner they want to see, and finally the appointment time. All data is obtained via RESTful API calls using axios.
The available options in each form field are filtered using the previous choice. For example, the user will only be presented with the available times of their selected practitioner, and the practitioner can only be selected from the group of practitioners who can perform the chosen treatment.
Filtering the practitioners based on the selected treatment works just fine.
However, filtering the appointments based on the selected practitioner does not work -- it's out of sync: the appointments are loaded for the previously selected practitioner. I have checked the backend, which is fine, and the API calls are in-sync (i.e. the person_id matches the id of the newly selected practitioner).
What is causing this problem and how do I fix it?
Here is the Vue.js code that performs this filtering:
var app = new Vue({
el: '#app',
data: {
appointments: [],
practitionerId: 0,
practitioners: [],
treatmentId: 0,
treatments: [],
},
mounted: function () {
axios.get('/api/treatments')
.then(response => this.treatments = response.data);
},
watch: {
// filter available practitioners by the selected treatment
treatmentId: function () {
// get the allowed role ids for the selected treatment
var allowedRoleIds = '';
const allowedRoles = this.treatments[this.treatmentId - 1]['allowed_roles'];
for (var i = 0; i < allowedRoles.length; i++) {
allowedRoleIds += allowedRoles[i]['id'];
if (i + 1 < allowedRoles.length) {
allowedRoleIds += ',';
}
}
// load the practitioners using the allowed role ids
axios.get('/api/people?role_ids=' + allowedRoleIds)
.then(response => this.practitioners = response.data);
},
// filter the available appointments by the selected practitioner
practitionerId: function () {
axios.get('/api/appointments?person_id=' + this.practitionerId)
// ERROR!!! This is out of sync.
.then(response => this.appointments = response.data);
}
}
});
The problem can be resolved by adding a watcher to the appointments variable.
All I needed to do was add the following code within watch: { ... }:
appointments: function () {
// now it works -- even without any function body
}
This seems really odd to me. I should not need to create a watcher for a variable in order to have that variable updated in the function body of another watcher.
I have either missed something in the Vue.js documentation about watchers or this is a bug. If someone can shed some light on this in the comments that would be great!
You need to refresh practitionerId after fetching people from RESTful API.
For example, in treatmentId watch:
axios.get('/api/people?role_ids=' + allowedRoleIds).then(response => {
this.practitioners = response.data;
// refresh practitionerId whenever fetch new people
const selectedPractitionerId = this.practitionerId;
this.practitionerId = 0;
// if selected practitioner exists in new people
practitioners.forEach(p => {
if (p.id == selectedPractitionerId) {
this.practitionerId = p.id;
}
}) // you can omit this search if you force the user to select new practitioner whenever they change treatment
});
I am working on a project where the client wants to be able to have a "Control" on the page where the user can start typing and a data grid will filter with each keystroke.
The filter should use the starts with operator, and removing all of the characters inside of the input ("control") will reset the Grid to its original unfiltered state.
My Controller, I don't want to modify it or add additional parameters:
public JsonResult GetFoo([DataSourceRequest]DataSourceRequest request, bool active = true)
{
List<Foo> model = FooContext.Foo.GetFoo(active);
model = model.OrderBy(m => m.Name).ToList();
return Json(model.ToDataSourceResult(request),JsonRequestBehavior.AllowGet);
}
This is my current Gird:
#(Html.Kendo().Grid<foo>()
.Name("fooTable")
.PrefixUrlParameters(Settings.Grid.PrefixUrlParameters)
.Columns(columns =>
{
columns
.Bound("fooName")
.ClientTemplate("<a href='#= Id #'>#= fooName #</a>");
columns
.Bound("StateName")
.Title("State").Width(120);
columns.Bound("PreviousYearsHomes")
.Title("Previous Years Homes")
.Width(120);
columns.Bound("CurrentYearsHomes")
.Title("Current Years Homes")
.Width(120);
.Sortable()
.Resizable(q => q.Columns(true))
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read.Action("GetBuilders", "Builders", new { Area = "Administrator", active = true }))
)
)
The Filter should filter the 'fooName' column.
I would recommend specifying the .Data(string handler) method available on the data source, for example
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read
.Action("GetBuilders", "Builders", new { Area = "Administrator", active = true })
.Data("getDataParams")
)
)
This allows you to specify a javascript function that returns a JSON object defining additional parameters to append to the ajax request.
You can use something like this:
var getDataParams = function (e) {
var result = {
name: $('#fooNameInput').val()
}
return result;
};
And to trigger a refresh of the grid (from a key up event or similar):
$("#fooTable").data("kendoGrid").dataSource.read();
Some docs to assist:
Kendo Forums working example
MVC Fluent docs
I really didn't want to answer my own question, but for anyone trying this for themselves this is what I did to get the results I was looking for.
Added an input with an Id of fooNameInput
<script type="text/javascript">
$(function () {
$('#fooNameInput').on("keyup change", function () {
var Value = $(this).val();
if (Value.length) {
FilterGridByName(Value, 'Name');
}
else {
$('#fooTable').data("kendoGrid").dataSource.filter([]);
}
});
function FilterGridByName(Value, Field) {
if (Field != "") {
if (Value != "") {
$('#fooTable').data("kendoGrid").dataSource.filter({ field: Field, operator: "startswith", value: Value })
}
else {
$('#fooTable').data("kendoGrid").dataSource.filter([]);
}
}
}
});
</script>
This is working as I wanted it to work but if there is a better way please let me know in the comments and I will update this answer/remove it.
This is a another option that I feel is important to include -
Another Option Provided by : https://stackoverflow.com/users/2293059/stevechapman
I would recommend specifying the .Data(string handler) method available on the data source, for example
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read
.Action("GetBuilders", "Builders", new { Area = "Administrator", active = true })
.Data("getDataParams")
)
)
This allows you to specify a javascript function that returns a JSON object defining additional parameters to append to the ajax request.
You can use something like this:
var getDataParams = function (e) {
var result = {
name: $('#fooNameInput').val()
}
return result;
};
And to trigger a refresh of the grid (from a key up event or similar):
$("#fooTable").data("kendoGrid").dataSource.read();
Some docs to assist:
Kendo Forums working example
MVC Fluent docs