Related
I'm using Django Rest to aggregate some data for my front end to render (through Chart.js)
Say I want to have a chart with the months (Jan-Dec) on the x-axis and total_cost values on the y-axis. How can I make sure to always spit out all 12 months and provide "0" for the y-axis if no value for that month is found?
E.g, this serialized data from Django rest is missing values for November and December, but I still need those labels for Chart JS to properly render
[
{
"month": "2020-01-01",
"total_cost": "199.00"
},
{
"month": "2020-02-01",
"total_cost": "222.00"
},
{
"month": "2020-03-01",
"total_cost": "399.00"
},
{
"month": "2020-04-01",
"total_cost": "414.00"
},
{
"month": "2020-05-01",
"total_cost": "555.00"
},
{
"month": "2020-06-01",
"total_cost": "615.00"
},
{
"month": "2020-07-01",
"total_cost": "700.00"
},
{
"month": "2020-08-01",
"total_cost": "913.00"
},
{
"month": "2020-09-01",
"total_cost": "552.00"
},
{
"month": "2020-10-01",
"total_cost": "1000.00"
}
]
Perhaps this is a matter for the frontend rather than the backend?
If it helps, my django query looks like this:
queryset = Expense.object.annotate(month=TruncMonth('expense_date'))
.values('month')
.annotate(total_cost=Sum('cost'))
.values('month', 'total_cost')
.order_by('month')
As you correctly guessed, this is best handled in the the frontend rather than in the backend.
You can define your x-axis as a time cartesian axis that accepts the data of your dataset as individual points through objects containing x and y properties each. Given the base data present in an array named baseData, such data can be created through Array.map() as follows:
baseData.map(o => ({ x: o.month, y: Number(o.total_cost) }))
To make sure, all 12 months are included in the chart, you'll further have to define ticks.min and ticks.max options on the x-axis.
ticks: {
min: '2020-01',
max: '2020-12'
}
Please take a look at the following runnable code and see how it works.
Note that Chart.js internally uses Moment.js for the functionality of the time axis. Therefore you should use the bundled version of Chart.js that includes Moment.js in a single file.
const baseData = [
{ "month": "2020-01-01", "total_cost": "199.00" },
{ "month": "2020-02-01", "total_cost": "222.00" },
{ "month": "2020-03-01", "total_cost": "399.00" },
{ "month": "2020-04-01", "total_cost": "414.00" },
{ "month": "2020-05-01", "total_cost": "555.00" },
{ "month": "2020-06-01", "total_cost": "615.00" },
{ "month": "2020-07-01", "total_cost": "700.00" },
{ "month": "2020-08-01", "total_cost": "913.00" },
{ "month": "2020-09-01", "total_cost": "552.00" },
{ "month": "2020-10-01", "total_cost": "1000.00" }
];
new Chart(document.getElementById('myChart'), {
type: 'line',
data: {
datasets: [{
label: 'My Dataset',
data: baseData.map(o => ({ x: o.month, y: Number(o.total_cost) })),
fill: false,
backgroundColor: 'green',
borderColor: 'green'
}]
},
options: {
responsive: true,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'month',
tooltipFormat: 'MMM YYYY'
},
ticks: {
min: '2020-01',
max: '2020-12'
}
}],
yAxes: [{
ticks: {
beginAtZero: true,
stepSize: 200
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="myChart" height="90"></canvas>
I'm trying to display total monthly sales and daily stock level. This way you could easily see that you didn't have any sales a particular month because you had low stock. Monthly sales is a bar chart that should be in the center of each month (in between the ticks).
In order to get it close to the middle my data is using the 15th of each month as the date to center it. I would want to know if there is a better way to achieve this?
JSFiddle to play around with: https://jsfiddle.net/8Lydhpqc/3/
const dailyStock = [
{ x: "2017-08-02", y: 1 },
{ x: "2017-08-25", y: 3 },
{ x: "2017-09-10", y: 7 },
{ x: "2017-09-28", y: 0 },
{ x: "2017-10-02", y: 3 },
{ x: "2017-10-24", y: 2 },
{ x: "2017-11-01", y: 1 },
{ x: "2017-11-30", y: 0 },
];
//using the 15th of each month to center it
const monthlyTotal = [
{ x: "2017-08-15", y: 1 },
{ x: "2017-09-15", y: 10 },
{ x: "2017-10-15", y: 5 },
{ x: "2017-11-15", y: 5 },
];
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx, {
type: "bar",
data: {
labels: ["2017-08", "2017-09", "2017-10", "2017-11"],
datasets: [
{
label: "sales",
data: data,
backgroundColor: "green",
borderColor: "black",
borderWidth: 1,
order: 2,
},
{
label: "stock",
type: "line",
data: dailyStock,
backgroundColor: "orange",
borderColor: "orange",
fill: false,
order: 1,
},
],
},
options: {
scales: {
xAxes: [
{
type: "time",
time: {
unit: "month",
displayFormats: {
month: "MMM",
},
},
distribution: "linear",
},
],
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
},
});
Welcome to Stackoverflow!
It seems that there is a way better than using the 15th of the month.
You need to add another axis for the bar that is a category type axis. Also its pretty critical that you have "offset: true" on that axis as well. Otherwise it will not center.
In the code below I named that category "bar" and the existing one "line"
I also created a fiddle: https://jsfiddle.net/jyf8ax3e/
var myChart = new Chart(ctx, {
type: "bar",
data: {
labels: ["2017-08", "2017-09", "2017-10", "2017-11"],
datasets: [
{
barPercentage: .7,
xAxisID: "bar",
label: "sales",
data: monthlyTotal,
backgroundColor: "green",
borderColor: "black",
borderWidth: 1,
width: 55,
order: 2,
},
{
label: "stock",
type: "line",
data: dailyStock,
backgroundColor: "orange",
borderColor: "orange",
fill: false,
order: 1,
},
],
},
options: {
scales: {
xAxes: [
{
id: "line",
type: "time",
time: {
unit: "month",
displayFormats: {
month: "MMM",
},
},
distribution: "linear",
},
{
id: "bar",
offset: true,
type: "category",
distribution: "series",
}
],
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
},
},
});
Following is the kendo grid where the nested property has to be accessed:
function detailInit(e) {
$("<div/>").appendTo(e.detailCell).kendoGrid({
dataSource: {
transport: {
read: constants.AppUrls.GetUrls().WebApi + "gprScoreprofiel/GetProfielen/" + e.data.id
},
serverPaging: false,
serverSorting: false,
serverFiltering: false,
sortable: { mode: 'single' },
pageSize: 5,
schema: {
type: "json",
data: "items",
model: {
fields: {
isBerekend: { from: "isBerekend", type: "boolean" },
GPR: { from: "gprGebouwVersieId", type: "string" },
En: { from: "en", type: "number" },
Mi: { from: "mi", type: "number" },
Gz: { from: "gz", type: "number" },
Gb: { from: "gb", type: "number" },
Tk: { from: "tk", type: "number" },
Dpg: { from: "dpg", type: "number" },
VariantNaam: { from: "variant.naam", type: "string" }
}
}
}
},
dataBound: function (e) {
},
reorderable: true,
resizable: true,
sortable: true,
columnMenu: false,
scrollable: true, //http://www.telerik.com/forums/max-height-on-grid
pageable: {
refresh: true,
pageSizes: ["All", 10, 20, 50, 100, 200],
buttonCount: 5,
messages: {
display:
"{0} - {1} van {2} adressen", //{0} is the index of the first record on the page, {1} - index of the last record on the page, {2} is the total amount of records
empty: "Geen adressen",
page: "Page",
of: "of {0}", //{0} is total amount of pages
itemsPerPage: "adressen per pagina",
first: "Ga naar de eerste pagina",
previous: "Ga naar de vorige pagihna",
next: "Ga naar de volgende pagina",
last: "Ga naar de laatste pagina",
refresh: "Herlaad"
}
},
columns:
[
{ field: "isBerekend", title: "Berekend", template: "#= isBerekend ? 'Ja' : 'Nee' #" },
{ field: "GPR", title: "GPR versie" },
{ field: "En", title: "Energie" },
{ field: "Mi", title: "Milieu" },
{ field: "Gz", title: "Gezondheid" },
{ field: "Gb", title: "Gebruik" },
{ field: "Tk", title: "Toekomst" },
{ field: "Dpg", title: "Energie en Milieu" },
{ field: "VariantNaam", title: "Variant Naam" }
]
}).data("kendoGrid");
}
On accessing the api we get the following data:
{
"resultaatgegevens": null,
"items": [
{
"isBerekend": true,
"gprGebouwBerekeningId": 37391,
"gprGebouwVersieId": 10,
"gebruikersdoelId": 1,
"gebruikersdoel": "Celfunctie",
"gebruikstypeId": 9,
"gebruikstype": "Twee onder een kap woning",
"bouwjaarKlasseId": 6,
"gemiddeld": 7.0,
"en": 8.4,
"en1": 8.9,
"en2": 6.6,
"en3": 0.0,
"mi": 6.0,
"mi1": 6.6,
"mi2": 6.2,
"mi3": 5.9,
"gz": 6.9,
"gz1": 5.9,
"gz2": 6.9,
"gz3": 7.5,
"gz4": 7.0,
"gb": 7.5,
"gb1": 6.2,
"gb2": 6.7,
"gb3": 10.0,
"gb4": 6.7,
"tk": 6.2,
"tk1": 6.3,
"tk2": 5.6,
"tk3": 6.6,
"dpg": 0.0,
"dpg1": 0.0,
"bouwjaarKlasse": null,
"gprGebouwVersie": null,
"gprScoreprofielByVastgoedItems": null,
"variant": {
"id": 10,
"naam": "VariantId10",
"omschrijving": "VariantId10",
"gprScoreprofielen": null
}
},
{
"isBerekend": false,
"gprGebouwBerekeningId": 0,
"gprGebouwVersieId": 11,
"gebruikersdoelId": 28,
"gebruikersdoel": "Woonfunctie",
"gebruikstypeId": 3,
"gebruikstype": "portiekwoning 1965 1974",
"bouwjaarKlasseId": 3,
"gemiddeld": 5.2,
"en": 5.9,
"en1": 6.0,
"en2": 5.6,
"en3": null,
"mi": 5.0,
"mi1": 5.4,
"mi2": 4.0,
"mi3": 4.8,
"gz": 4.4,
"gz1": 4.5,
"gz2": 4.2,
"gz3": 4.8,
"gz4": 4.2,
"gb": 5.1,
"gb1": 6.0,
"gb2": 6.0,
"gb3": 3.0,
"gb4": 5.4,
"tk": 5.6,
"tk1": 6.0,
"tk2": 6.0,
"tk3": 4.8,
"dpg": null,
"dpg1": null,
"bouwjaarKlasse": null,
"gprGebouwVersie": null,
"gprScoreprofielByVastgoedItems": null,
"variant": null
}
],
"page": 1,
"pageSize": 1000,
"total": 0,
"hasMore": false
}
The grid doesn't work due to variant.naam
From the docs:
A field configuration cannot contain nested fields' configurations.
So, you have two options:
Change your API to return the nested properties in the same level as the items, e.g.: variantnaam;
Or do it in the Javascript before the widget renders it in schema.parse, something like:
schema: {
data: "items",
parse: function(data) {
if (data && data.items) {
var key = "variant";
data.items.forEach((i) => {
if (i[key]) {
Object.keys(i[key]).forEach((k) => {
i[key + k] = i[key][k];
});
}
});
}
return data;
}
}
Demo
I'm currently trying to build a chart showing number of downloads of a product per date.
A sample of the current code is as follows:
var downloads = [
{ value: 48, date: new Date("2013/11/01") },
{ value: 50, date: new Date("2013/11/02") },
{ value: 55, date: new Date("2013/11/03") },
{ value: 35, date: new Date("2013/11/04") }
];
$("#chart").kendoChart({
dataSource: {
data: downloads
},
series: [{
type: "line",
aggregate: "avg",
field: "value",
categoryField: "date"
}],
categoryAxis: {
baseUnit: "days",
min: new Date("2013/10/31"),
max: new Date("2013/11/10"),
labels: {
dateFormats: {
days: "dd/MM"
}
}
}
});
It works fine if I have to display data for one product only. How would I proceed to display download data for another product, i.e. adding another series to the chart?
Right! I figured it out myself. Here it is:
$("#chart").kendoChart({
seriesDefaults: {
tooltip: {
visible: true,
},
type:"line",
aggregate:"avg",
field:"value",
categoryField:"date"
},
series: [{
name: "Product 1",
data: [{ value: 48, date: new Date("2013/11/01") }, { value: 50, date: new Date("2013/11/02") }]
},
{
name: "Product 2",
data: [{ value: 55, date: new Date("2013/11/03") }, { value: 35, date: new Date("2013/11/04") }]
}],
categoryAxis: {
baseUnit: "days",
min: new Date("2013/10/31"),
max: new Date("2013/11/10"),
labels: {
dateFormats: {
days: "dd/MM"
}
}
}
});
need help about adding a new recort in a kendo grid.
I have a grid with a foreign key field. The grid is populated by a json data with subobject, returned from a webmethod. it looks like this:
[
{
"classe_iva": {
"id_classe_iva": 5,
"desc_classe_iva": "Esente",
"note_classe_iva": null
},
"id_iva": 37,
"desc_iva": "bbb",
"codice_iva": "bbb",
"imposta": 2,
"indetr": 2,
"id_classe_iva": 5,
"note": "dddddfsf",
"predefinito": false,
"id_company": 4
},
{
"classe_iva": {
"id_classe_iva": 6,
"desc_classe_iva": "Escluso",
"note_classe_iva": null
},
"id_iva": 52,
"desc_iva": "o",
"codice_iva": "jj",
"imposta": 1,
"indetr": 1,
"id_classe_iva": 6,
"note": "l",
"predefinito": false,
"id_company": 4
}
]
and this is the schema.model used in the kendo datasource:
model = {
id: "id_iva",
fields: {
id_iva: { type: "string", editable: false },
desc_iva: { type: "string" },
codice_iva: { type: "string" },
imposta: { type: "number" },
indetr: { type: "number" },
id_classe_iva: {type: "string"},
note: { type: "string" },
predefinito: { type: "boolean" },
id_company: { type: "number" }
}
}
.. and below is shown the grid columns format:
toolbar = [{
name: "create",
text: "Aggiungi nuova aliquota IVA"
}];
columns = [
{ field: "desc_iva", title: "Descrizione", width: 45 },
{ field: "codice_iva", title: "Codice", width: 45 },
{ field: "imposta", title: "Imposta", width: 45 },
{ field: "indetr", title: "Indetr", width: 45 },
{ field: "classe_iva.desc_classe_iva", title: "Classe IVA", width: 200, editor: categoryDropDownEditor, template: "#= classe_iva ? classe_iva.desc_classe_iva : 1 #", defaultValue: { id_classe_iva: 1, desc_classe_iva: "Acq. Intra-UE" } },
{ field: "note", title: "Note", width: 45 },
{
command: [{
name: "destroy",
text: "Elimina",
confirmation: "Sei sicuro di voler eliminare questa voce?"
} ,
{
name: "edit",
text: {
edit: "Modifica",
update: "Aggiorna",
cancel: "Cancella"
}
}
]
}
];
Theese settings works fine when i edit a row, and the grid shows the right content into the combobox field.
The problem is when i click on the "add new record" button, because when it tries to add a new row, it doesn't find the subobjects field "classe_iva".
if i change the column.field into this
{ field: "id_classe_iva", title: "Classe IVA", width: 200, editor: categoryDropDownEditor, template: "#= id_classe_iva ? id_classe_iva : 1 #", defaultValue: { id_classe_iva: 1, desc_classe_iva: "Acq. Intra-UE" } },
{ field: "note", title: "Note", width: 45 }
the add button works fine, but when the grid is loaded, the column id_classe_iva doesn't shows me the classe_iva.desc_classe_iva field...
how can i fix the issue??^?
thanks in advance.
to fix this issue, i used a workaround:
the error was thrown because, in the declaration of the field.template there where a not declared variable (classe_iva):
{ field: "id_classe_iva", title: "Classe IVA", width: 200, editor: categoryDropDownEditor, template: "#= classe_iva ? classe_iva.desc_classe_iva : 1 #", defaultValue: { id_classe_iva: 1, desc_classe_iva: "Acq. Intra-UE" } },
then, i declared a global variable
var classe_iva;
in this way, when i add a new record, the code doesn't throw any errors, and by the ternary if, the default value is set.
hope it will help someone.