Draw pie chart using spring boot, thymeleaf, js, highchart but can't - javascript

I'm new with js and spring, now i want to create a html dashboard and this page will have a small div with pie chart. But i can't create pie chart.
I try some tutorial in youtube but now i want to pass value to ajax or something like that to get the pie chart.
Here is my Code:
admin_homepage.html:
<div class="col-xl-4 col-lg-5">
<div class="card shadow mb-4">
<!-- Thay chart vào thẻ div này -->
<div class="card-body">
<div class="chart-pie pt-4 pb-2">
<div id="chartContainer" style="height: 370px; width: 100%;"></div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$.ajax({
/* for pie chart */
url: "admin_home",
success: function(result){
/* pie chart starts here */
var series = [];
var data = [];
for(var i = 0; i < result.length; i++){
var object = {};
object.name = result[i].catName.toUpperCase();
object.y = result[i].catCount;
data.push(object);
}
var seriesObject = {
name: 'Course By Category',
colorByPoint: true,
data: data
};
series.push(seriesObject);
drawPieChart(series);
/* pie chart ends here */
}
});
/* for pie chart */
function drawPieChart(series){
Highcharts.chart('chartContainer', {
chart: {
plotBackgroundColor: null,
plotBorderWidth: null,
plotShadow: false,
type: 'pie'
},
title: {
text: 'Browser market shares in January, 2018'
},
tooltip: {
formatter: function() {
return '<strong>'+this.key+': </strong>'+ this.y;
}
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: true,
format: '<b>{point.name}</b>: {point.y}'
}
}
},
series: series
});
}
</script>
My Controller
#GetMapping("/admin_home")
public String viewHomePage(){
// Get list of course and count
List<CountCourse> pieChart = dashBoardRepository.countCourseByCategory();
model.addAttribute("pieChart",pieChart);
return "Admin_Homepage";
}
All i want is pass value of catName, catCount to pie chart but i can't
Any one help me. Many thanks.

Because you are using a Thymeleaf template, you are not required to use $.ajax({...}) to retrieve the pie chart data. Instead you can provide the data directly to the Thymeleaf template.
(Alternatively, you can continue to use an Ajax call - in which case, The Thymeleaf template will be rendered to HTML - and then as a separate step, the Ajax call will fetch the pie chart data.)
The following assumes the first approach (no Ajax needed):
No Ajax Needed
I took your Thymeleaf template in the question and made some changes to the script:
I removed the Ajax call.
I added a Thymeleaf variable to hold the chart data.
Here is the updated script:
<script th:inline="javascript">
// this simply wraps the code in a function
// that waits for the DOM to be ready:
(function () {
// this is populated by Thymeleaf:
var pieChartData = /*[[${pieChartData}]]*/ [];
var series = [];
var data = [];
for (var i = 0; i < pieChartData.length; i++) {
var object = {};
object.name = pieChartData[i].catName.toUpperCase();
object.y = pieChartData[i].catCount;
data.push(object);
}
var seriesObject = {
name: 'Course By Category',
colorByPoint: true,
data: data
};
series.push(seriesObject);
drawPieChart(series);
// draw the pie chart:
function drawPieChart(series) {
Highcharts.chart('chartContainer', {
chart: {
plotBackgroundColor: null,
plotBorderWidth: null,
plotShadow: false,
type: 'pie'
},
title: {
text: 'Your Heading Goes Here'
},
tooltip: {
formatter: function () {
return '<strong>' + this.key + ': </strong>' + this.y;
}
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: true,
format: '<b>{point.name}</b>: {point.y}'
}
}
},
// use the series data defined earlier:
series: series
});
}
})();
</script>
The key points about this script are:
The script tag looks like this:
<script th:inline="javascript">
This tells Thymeleaf that the script will contain one or more Thymeleaf expressions.
In our case we have one expression - here it is:
var pieChartData = /*[[${pieChartData}]]*/ [];
This syntax will cause Thymeleaf to replace the pieChartData variable with the data structure provided by the Java controller.
Here is that piece from the controller:
List<CountCourse> pieChartData = dashBoardRepository.countCourseByCategory();
model.addAttribute("pieChartData", pieChartData);
return "admin_homepage";
This assumes you have a CountCourse object which contains String catName and int catCount.
Thymeleaf will take the List<CountCourse> pieChartData data and generate the following JavaScript for you (using my test data):
var pieChartData = [
{"catName":"Humanities","catCount":123},
{"catName":"Sciences","catCount":145},
{"catName":"Other","catCount":67}
];
After that, I use the same logic as you have in your Ajax success function to convert this raw data into HightCharts pie chart data.
The end result is the following HTML page:
With Ajax
If you want to use your Ajax approach instead of this, then you need to build a separate end point which will return the pie chart data directly to the Ajax handler in your JavaScript code.
When you take this approach, you no longer need to use the Thymeleaf attribute:
var pieChartData = /*[[${pieChartData}]]*/ []; // NO LONGER NEEDED
And you no longer need to pass this data to your model in the controller:
model.addAttribute("pieChartData", pieChartData); // NO LONGER NEEDED
Instead, you need to continue using your $.ajax code and you need to build a separate end-point which returns the pieChartData as JSON for that Ajax call:
$.ajax({
/* for pie chart */
url: "piechart_data_json", // some new URL for your JSON pie chart data
...
});
Given you are using Thymeleaf already, I think there is no need for this approach.
Update
Just to explain the following syntax a bit more:
var pieChartData = /*[[${pieChartData}]]*/ [];
It looks like an empty JavaScript array []. But in fact, there is more to it.
The Thymeleaf variable ${pieChartData} receives the data from the controller.
Because the variable is in a <script> tag, it's not sufficient just to use the standard Thymeleaf ${pieChartData} expression. You also have to surround that expression with [[ and ]]. This is because ${pieChartData} is actually valid JavaScript - for example, as used in string interpolation.
That gives us this:
var pieChartData = [[${pieChartData}]];
This is all you need. This will work.
The problem here is, it's not valid JavaScript, so your IDE may highlight it as having a syntax error.
To work around this, you can take one extra step. You can "hide" the expression in a JavaScript comment - and then provide a valid value (the empty array). This keeps the JavaScript syntax checker happy in your IDE.
Thymeleaf will locate the variable inside that comment and remove it - and also remove the placeholder [] value.
That is how Thymeleaf pushes the Java model data into the template in this case.

Related

How do I save a Ext.XTemplate into the DOM when using Rowexpander in EXT JS

I'm having a strange issue with the RowExpander plugin inside of my Ext.grid.Panel component. FYI this grid is backed by a Ext.data.BufferedStore component to handed the data via a REST Api
So I'm dynamically loading in values from an Ajax when the user clicks on the expand button, the row opens and makes an Ajax call using the expandbody event method.
That works great with no issues.
The problem is that when I scroll down the the page, the table rows that were open lose all thier data.
Here is my set up:
// Create Basic Ext Grid
var oDataGrid = Ext.create('Ext.grid.Panel', {
title: 'oData Entity Table',
store: oDataStore,
height: 450,
id: 'odataGrid',
loadMask: true,
plugins: [{
ptype: 'rowexpander',
enableCaching: false,
id: 'odataTableRowPlugin',
rowBodyTpl: new Ext.XTemplate('<div id="oData-Inner-Table-Row-{Id}" ><button class="btn btn-warning"><span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> Loading...</button></div>')
}],
viewConfig:{
listeners:{
expandbody: function(__rowNode, __record, __expandRow, __eOpts){
var _rowId = __record.get('Id');
var _targetId = 'oData-Inner-Table-Row-' + _rowId;
var _finalUrl = _that._URLROOT +'/odata/' + _that._ENTITYTYPE;
var _qb = new OData.QueryBuilder(_finalUrl);
var _type = OData.INT32;
var _operator = OData.EQUALS;
var _filter = 'Id';
_qb.addWhereFilter('id_'+_rowId, _type, _filter, _operator, _rowId);
var _query = _qb.generateQueryUrl();
// Include Expand NavProps
_query = _query + _that.createExpandedUrl();
_that.grabEntityObject(_query,_targetId);
}
}
},
renderTo: 'oData-grid'
});
So even though the row has been updated the information doesn't seem to be saved into the dom or something.
Any thoughts clues, suggestions, etc?

anychart not taking dynamically added data

I am using anychart to draw a chart in my page, My code is like this
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdn.anychart.com/js/7.12.0/anychart-bundle.min.js"></script>
<link rel="stylesheet" href="https://cdn.anychart.com/css/7.12.0/anychart-ui.min.css" />
<input id="chart-charitytomoney" value="[["Charity 4",10.00],["Charity 2",20.00],["Charity Donate",100.00],["Donate Your Humanity",5920.00],["Gift your Work",3155.00],["Celebrate Baby Shower",770.00],["Refer Friends",110.00],["Gift Your Friends",200.00],["Celebrate B\u0027day With Us",220.00],["Celebrate Weekend",50.00],["Piggy Bank",4100.00],["Give a Single Gift",4050.00]]">
<div id="chart-container" style="height:550px!important"></div>
<script type="text/javascript">
$(document).ready(function(){
anychart.onDocumentReady(function () {
var data = $("#chart-charitytomoney").val();
// create column chart
chart = anychart.column();
// turn on chart animation
chart.animation(true);
// set chart title text settings
chart.title('Charities by donation');
// create area series with passed data
alert(data);
var series = chart.column(data);
// set series tooltip settings
series.tooltip().titleFormatter(function () {
return this.x
});
series.tooltip().textFormatter(function () {
return '$' + parseInt(this.value).toLocaleString()
});
series.tooltip().position('top').anchor('bottom').offsetX(0).offsetY(5);
// set scale minimum
chart.yScale().minimum(0);
// set yAxis labels formatter
chart.yAxis().labels().textFormatter("${%Value}");
// tooltips position and interactivity settings
chart.tooltip().positionMode('point');
chart.interactivity().hoverMode('byX');
// axes titles
chart.xAxis().title('Product');
chart.yAxis().title('Revenue');
// set container id for the chart
chart.container('chart-container');
// initiate chart drawing
chart.draw();
});
});
</script>
Everything looks okay to me, But chart is not working.
but if I changed this line
var data = $("#chart-charitytomoney").val();
to
var data = [["Charity 4", 10.00], ["Charity 2", 20.00], ["Charity Donate", 100.00], ["Donate Your Humanity", 5920.00], ["Gift your Work", 3155.00], ["Celebrate Baby Shower", 770.00], ["Refer Friends", 110.00], ["Gift Your Friends", 200.00], ["Celebrate B\u0027day With Us", 220.00], ["Celebrate Weekend", 50.00], ["Piggy Bank", 4100.00], ["Give a Single Gift", 4050.00]]
Everything works. Can anyone point out what I am doing wrong here? And How I can overcome it?
It is a peculiar way to pass data but you can do that, just:
Option 1
You should use quotes in the input field:
<input id="chart-charitytomoney" value="[['Charity 4',10.00],['Charity 2',20.00],['Charity Donate',100.00],['Donate Your Humanity',5920.00],['Gift your Work',3155.00],['Celebrate Baby Shower',770.00],['Refer Friends',110.00],['Gift Your Friends',200.00],['Celebrate B\u0027day With Us',220.00],['Celebrate Weekend',50.00],['Piggy Bank',4100.00],['Give a Single Gift',4050.00]]">
And you need to eval() the result:
var data = eval($("#chart-charitytomoney").val());
Here is a sample: http://jsfiddle.net/yr35w6nu/8/
However, eval is no quite secure, if you want to store data in a string in a field like this consider using code like this:
Option 2
var data = JSON.parse($("#chart-charitytomoney").val().replace(/\'/g,'\"'));
shown in this sample: http://jsfiddle.net/yr35w6nu/9/
The same may be applied to your code with &quote;:
var data = JSON.parse($("#chart-charitytomoney").val().replace(/\"/g,'\"'));
Sample parsing quotes: http://jsfiddle.net/yr35w6nu/10/
Option 3
There is also a way to store CSV formatted string:
<input id="chart-charitytomoney" value="Charity 4,10.00;Charity 2,20.00;Charity Donate,100.00;Donate Your Humanity,5920.00;Gift your Work,3155.00;Celebrate Baby Shower,770.00\nRefer Friends,110.00;Gift Your Friends,200.00;Celebrate B\u0027day With Us,220.00;Celebrate Weekend,50.00\nPiggy Bank,4100.00\nGive a Single Gift,4050.00">
and then use it:
var data = anychart.data.set($("#chart-charitytomoney").val(),{rowsSeparator: ';'});
http://jsfiddle.net/yr35w6nu/13/

Google visualization table not allowing HTML

I am using the Google visualization tools to show a table, but all of my HTML is being shown as a string. Here is the code:
var data = new google.visualization.DataTable()
data.setTableProperty('allowHtml', true)
data.addColumn('string','Keyword')
data.addColumn('number','<img src="http://m8app.com/assets/google-icon-8556487cd6ff3508d7bf2c4f64a0e3ad.jpg">Rank')
data.addColumn('number','Rank Change')
data.addColumn('string','Page')
data.addColumn('string','Link')
var row = 1;
while(row < thing.length){
data.addRow([
thing[row][0],
parseInt(thing[row][1],10),
parseInt(thing[row][2],10),
thing[row][3],
"<a href='"+thing[row][4]+"' target='_blank'>Search</a>"]);
row++;
}
var table = new google.visualization.Table(document.getElementById('rankInner'));
table.draw(data, {
allowHtml:true,
showRowNumber: false,
page : 'enable',
pageSize:10,
sortColumn: 2,
sortAscending:false
});
I have set 'allowHtml' to true on both the DataTable and the Table, but the table still shows the full text of the HTML rather than rendering it as html. I'd be grateful for any recommendation to try, thank you.
I tried a sample like yours in the online visualization playground, and it seems good
The only line I see you got messed up is the below line, where the indentation is wrong [quotes, double quotes. Compare with the below working one]
"<a href='"+thing[row][4]+"' target='_blank'>Search</a>"]);
link : https://code.google.com/apis/ajax/playground/?type=visualization#table
Code I tried:
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable([
['Name', 'Height', 'Dance'],
['Kuttappan', 174, true],
['Raayappan', 523, false],
["<a href='abc.com' target='_blank'>Search</a>", 86, true]
]);
// Create and draw the visualization.
visualization = new google.visualization.Table(document.getElementById('table'));
visualization.draw(data, {allowHtml:true});
}
when you run it [click on RunCode in the tool], you see the search link, and not the HTML code.
The below code for image,
data.addColumn('number','<img src="http://m8app.com/assets/google-icon-8556487cd6ff3508d7bf2c4f64a0e3ad.jpg">Rank')
is indeed showing as image only, and not HTML

How to use function in Kendo Grid Column Template with AngularJS

I have a column in a Kendo grid that I want to perform some specific logic for when rendering, and am using Angular. I have the grid columns set up using the k-columns directive.
After looking at the documentation, it seemed simple: I could add the template option to my column, define the function to perform my logic, and pass the dataItem value in. What I have looks something like this:
k-columns='[{ field: "Name", title: "Name",
template: function (dataItem){
// Perform logic on value with dataItem.Name
// Return a string
}
}]'
However, running this causes a syntax error complaining about the character '{' that forms the opening of the block in my function.
I have seen several examples of defining a template function in this format. Is there something else that needs to be done for this to work? Am I doing something incorrectly? Is there another way of defining the template as a function and passing the column data to it? (I tried making a function on my $scope, which worked, except I couldn't figure out how to get data passed into the function.)
Thank you for your help.
It appears that defining a column template in this fashion isn't supported when using AngularJS and Kendo. This approach works for projects that do not use Angular (standard MVVM), but fails with its inclusion.
The workaround that a colleague of mine discovered is to build the template using ng-bind to specify a templating function on the $scope, all inside of a span:
template: "<span ng-bind=templateFunction(dataItem.Name)>#: data.Name# </span>"
This is the default column templating approach that is implemented by Telerik in their Kendo-Angular source code. I don't know yet if the data.Name value is required or not, but this works for us.
Warning: Don't have access to Kendo to test the code at the moment, but this should be very close
In your case, you are assigning a a string to the value of k-columns and that string contains the the word function and your curly brace {
You need to make sure the function gets executed ... something like this:
k-columns=[
{
field: "Name",
title: "Name",
template: (function (dataItem){
// Perform logic on value with dataItem.Name
// Return a string
}())
}
];
Note the difference:
We create an object -- a real honest-to-goodness object, and we use an IIFE to populate the template property.
Maybe, it will be useful for someone - this code works for me too:
columns: [
{
field: "processed",
title:"Processed",
width: "100px",
template: '<input type="checkbox" ng-model="dataItem.processed" />'
},
and you get the two-way binding with something like this:
<div class="col-md-2">
<label class="checkbox-inline">
<input type="checkbox" ng-model="vm.selectedInvoice.processed">
processed
</label>
</div>
This can be done via the columns.template parameter by supplying a callback function whose parameter is an object representing the row. If you give the row a field named name, this will be the property of the object you reference:
$("#grid").kendoGrid({
columns: [ {
field: "name",
title: "Name",
template: function(data) {
return data.name + "has my respect."
}
}],
dataSource: [ { name: "Jane Doe" }, { name: "John Doe" } ]
});
More information is available on Kendo's columns.template reference page.
After hours of searching. Here is the conclusion that worked:
access your grid data as {{dataItem.masterNoteId}} and your $scope data as simply the property name or function.
Example
template: '<i class="fa fa-edit"></i>',
I really hope this safes somebody life :)
just use like my example:
}, {
field: "TrackingNumber",
title: "#T("Admin.Orders.Shipments.TrackingNumber")",
//template: '<a class="k-link" href="#Url.Content("~/Admin/Shipment/ShipmentDetails/")#=Id#">#=kendo.htmlEncode(TrackingNumber)#</a>'
}, {
field: "ShippingMethodName",
title: "#T("Admin.Orders.Shipments.ShippingMethodName")",
template:function(dataItem) {
var template;
var ShippingMethodPluginName = dataItem.ShippingMethodPluginName;
var IsReferanceActive = dataItem.IsReferanceActive;
var ShippingMethodName = dataItem.ShippingMethodName;
var CargoReferanceNo = dataItem.CargoReferanceNo;
var ShipmentStatusId = dataItem.ShipmentStatusId;
if (ShipmentStatusId == 7) {
return "<div align='center'><label class='label-control'><b style='color:red'>Sipariş İptal Edildi<b></label></div>";
} else {
if (ShippingMethodPluginName == "Shipping.ArasCargo" || ShippingMethodPluginName == "Shipping.ArasCargoMP") {
template =
"<div align='center'><img src = '/content/images/aras-kargo-logo.png' width = '80' height = '40'/> <label class='label-control'><b>Delopi Aras Kargo Kodu<b></label>";
if (IsReferanceActive) {
template =
template +
"<label class='label-control'><b style='color:red; font-size:20px'>"+CargoReferanceNo+"<b></label></div>";
}
return template;
}

Dynamically creating kendo-grid columns in angular controller

I am trying to dynamically build the structure of a kendo-angular grid. My problem is that the grid options are not known when the k-options attribute is evaluated, so the grid is binding to ALL of the columns on the datasource.
Here is the HTML:
<div kendo-grid k-options="{{gridModel.options}}"
k-data-source="gridModel.myDataSource">
</div>
And here is the javascript in the controller:
// this is called after the api call has successfully returned with data
function getSucceeded(){
...
$scope.gridModel.options = function(){
// function that properly builds options object with columns, etc.
}
// this is just shown for example... the data is properly loading
$scope.gridModel.myDataSource.data(ds.data());
}
The data is properly loading, but because gridModel.options was evaluated in the HTML prior to being set by the success method, it is essentially ignored and all of the columns from the datasource are being rendered.
This works like a champ when gridModel.options is static.
How can I defer the evaluation of k-options and/or force a reevaluation after they've been set by the controller?
I was able to figure it out. I had to do four things:
Update my version of angularjs (I was on 1.08 which does not have the ng-if directive). I updated to 1.2.0rc3.
Wrap my kendo-grid div in an ng-if div
Invoke my function! I was just setting $scope.gridModel.options to a function - I needed to actually invoke the function so I'd be setting the variable to the value returned from the function.
I had to update my angular.module declaration to include ngRoute (based on it being separated into it's own module in 1.2.x).
Here's the updated HTML:
<div data-ng-if="contentAvailable">
<div kendo-grid k-options="{{gridModel.options}}"
k-data-source="gridModel.myDataSource">
</div>
</div>
And here's the updated controller (not shown: I set $scope.contentAvailable=false; at the beginning of the controller):
// this is called after the api call has successfully returned with data
function getSucceeded(){
...
$scope.gridModel.options = function(){
// function that dynamically builds options object with columns, etc.
}(); // <----- NEED to invoke function!!
// this is just shown for example... the data is properly loading
$scope.gridModel.myDataSource.data(ds.data());
$scope.contentAvailable=true; // trigger the ng-if
}
I actually moved the function into a config file so I'm not polluting the controller with too much configuration code. Very happy to have figured this out.
Here is a sample using 'Controller As' syntax, dynamic columns and paging.
var app = angular.module("app", ["kendo.directives"]);
function MyCtrl() {
var colsList = [{
name: "col1"
}, {
name: "col2"
}, {
name: "col3"
}, {
name: "col4"
}];
var gridCols = [];
var iteration = 1;
var vm = this;
vm.gridOptions = {
columns: gridCols,
dataSource: new kendo.data.DataSource({
pageSize: 10
}),
pageable: true
};
vm.buildGrid = function() {
var data = {};
vm.gridOptions.columns = [];
for (var x = 0; x < colsList.length; x++) {
if (iteration % 2 === 0 && x === colsList.length - 1) continue;
var col = {};
col.field = colsList[x].name;
col.title = colsList[x].name;
data[col.field] = "it " + iteration + " " + (1111 * (x + 1));
vm.gridOptions.columns.push(col);
}
// add one row to the table
vm.gridOptions.dataSource.add(data);
iteration++;
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.common.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.default.min.css" />
<script src="http://cdn.kendostatic.com/2015.1.318/js/kendo.all.min.js"></script>
<body ng-app="app">
<div ng-controller="MyCtrl as vm">
<button ng-click="vm.buildGrid()">Build Grid</button>
<div kendo-grid="grid" k-options="vm.gridOptions" k-rebind="vm.gridOptions"></div>
</div>
</body>
We can use the k-rebind directive for this. From the docs:
Widget Update upon Option Changes
You can update a widget from controller. Use the special k-rebind attribute to create a widget which automatically updates when some scope variable changes. This option will destroy the original widget and will recreate it using the changed options.
Apart from setting the array of columns in the GridOptions as we normally do, we have to hold a reference to it:
vm.gridOptions = { ... };
vm.gridColumns = [{...}, ... ,{...}];
vm.gridOptions.columns = vm.gridColumns;
and then pass that variable to the k-rebind directive:
<div kendo-grid="vm.grid" options="vm.gridOptions" k-rebind="vm.gridColumns">
</div>
And that's it when you are binding the grid to remote data (OData in my case). Now you can add or remove elements to/from the array of columns. The grid is going to query for the data again after it is recreated.
When binding the Grid to local data (local array of objects), we have to somehow postpone the binding of the data until the widget is recreated. What worked for me (maybe there is a cleaner solution to this) is to use the $timeout service:
vm.gridColumns.push({ ... });
vm.$timeout(function () {
vm.gridOptions.dataSource.data(vm.myArrayOfObjects);
}, 0);
This has been tested using AngularJS v1.5.0 and Kendo UI v2016.1.226.

Categories

Resources