Related
I am using PDFmake in my django project to generate pdf files. I want to download multiple files using one button clicked. That's why I planned to use jsZIP to download all the file in a zip format.
function downloadAllPayslip(){
var employeeIdLst = getSelectedPayslipEmployeeIds();
$.ajax({
type: 'GET',
url: '{% url "salary:ajax-download-all-payslip" %}',
data: {
'month': dateVar.getMonth()+1, // number
'year': dateVar.getFullYear(),
'employee_id_lst': JSON.stringify(employeeIdLst),
},
dataType: 'json',
success: function (data) {
var lastDay = new Date(dateVar.getFullYear(), dateVar.getMonth() + 1, 0);
var lastDayStr = lastDay.toString("MM/dd/yyyy");
var lastDayStrSplit = lastDayStr.split("/");
var monthName = months[parseInt(lastDayStrSplit[0]) - 1];
var monthNameS = monthName.slice(0, 3);
var year = lastDayStrSplit[2];
var day = lastDayStrSplit[1];
var zip = new JSZip();
var bufferPDFList = [];
var docDefinitionArr = [];
var docDefinitionBufferArr = [];
var content;
var dataPDF;
var nameArr = [];
for (var i = 0; i < data.length; i++) {
// Employee info
var dailyStartTime = moment(data[i].daily_start_time, 'HH:mm:ss').format('h:mm A');
var dailyExitTime = moment(data[i].daily_exit_time, 'HH:mm:ss').format('h:mm A');
var employeeId = data[i].e_id;
var employeeName = data[i].name;
nameArr.push(employeeName);
var employeeType = data[i].type;
var employeeDesignation = data[i].designation;
var employeeDepartment = data[i].department;
var employeeJoinDate = data[i].join_date;
var weekends = data[i].weekends;
var shift = dailyStartTime + ' - ' + dailyExitTime;
var breakDuration = data[i].break_duration;
var leaveBalanceDict = data[i].leave_balance_dict;
var leaveDataDict = []
for (var leaveType in leaveBalanceDict) {
var leaveDict = leaveBalanceDict[leaveType];
var total = leaveDict.total;
var taken = leaveDict.taken;
var accrued = leaveDict.accrued;
var remained = accrued - taken;
leaveDataDict.push({
'Leave Types': leaveType,
'Leave Entitled(days)': total,
'Leave Taken(days)': taken,
'Leave Accrued(days)': accrued,
'Leave Remained(days)': remained
})
}
// Salary details
if (data[i].is_overtime) {
var overtimeAddition = data[i].overtime_addition + ' (+' + formatHHMM(data[i].overtime_hour) + ')';
}
else {
var overtimeAddition = 'N/A';
}
var basicPay = data[i].basic_salary;
var overtimePay = overtimeAddition;
var lessWorkDeduction = data[i].less_work_deduction + "(" + formatHHMM(data[i].work_hour_deficit) + ")"
var unpaidLeaveDeduction = data[i].unpaid_leave_deduction + "(" + data[i].unpaid_leave_count + "days)"
var additionalPay = data[i].salary_addition;
var totalPay = data[i].total_salary;
var taxDeduction = data[i].tax_deduction+ "(" + data[i].percent_tax + "%)";
var netSalary = data[i].net_salary;
var comment = data[i].comment;
var companyName = '{{ company_name }}'
// Generate pdf
var docDefinition = {
footer: function(currentPage, pageCount) {
return {
columns: [
{
text: currentPage.toString() + ' of ' + pageCount, alignment: 'center'
}
]
};
},
pageSize: 'A4',
pageMargins: [40, 80, 40, 60],
content: [
{ text: companyName, style: 'header', alignment: 'center' },
{ lineHeight: 2, text: 'Pay Slip', fontSize: 15, alignment: 'center' },
{ lineHeight: 2, text: 'Pay Period: 01 ' + monthNameS + ' ' + year + ' - ' + day + ' ' + monthNameS + ' ' + year , fontSize: 12, alignment: 'center', bold: true },
{ text: 'Employee Information', style: 'subheader'},
{
columns: [
{
width: 120,
fontSize: 10,
text: 'Employee Name\n' +
'Employee ID\n' +
'Employee Type\n'+
'Designation'
},
{
width: 155,
fontSize: 10,
text: ': ' + employeeName + '\n' +
': ' + employeeId + '\n' +
': ' + employeeType + '\n' +
': ' + employeeDesignation + '\n'
},
{
width: 120,
fontSize: 10,
text: 'Department\n' +
'Join Date\n' +
'Weekends\n' +
'Shift'
},
{
width: 155,
fontSize: 10,
text: ': ' + employeeDepartment + '\n' +
': ' + employeeJoinDate + '\n' +
': ' + weekends + '\n' +
': ' + shift + '\n' +
' (Break: ' + breakDuration + 'min)'
}
]
},
{ text: 'Leave Information', style: 'subheader'},
{
columns: [
{ width: 20, text: '' },
table(leaveDataDict, ['Leave Types', 'Leave Entitled(days)', 'Leave Taken(days)', 'Leave Accrued(days)', 'Leave Remained(days)']),
]
},
{ text: 'Salary Information', style: 'subheader'},
{
style: 'table',
table: {
body: [
['Basic', 'Overtime', 'Additional', 'Unpaid Leave Deduction', 'Less Work Deduction', 'Total Pay', 'Tax Deduction', 'Net pay'],
[basicPay, overtimePay, additionalPay, unpaidLeaveDeduction, lessWorkDeduction, totalPay, taxDeduction, netSalary]
]
}
},
],
styles: {
header: {
fontSize: 18,
bold: true,
margin: [0, 0, 0, 10]
},
subheader: {
fontSize: 15,
bold: true,
margin: [0, 10, 0, 5]
},
tableHeader: {
bold: true,
fontSize: 13,
color: 'black'
},
table: {
fontSize: 9,
margin: [0, 5, 0, 15]
},
},
}
pdfFilename = 'Payslip-' + employeeName + '-' + monthNameS + year + '.pdf'
// pdfMake.createPdf(docDefinition).download('Payslip-' + employeeName + '-' + monthNameS + year, window);
pdfMake.createPdf(docDefinition).getBase64(function(data) {
zip.file(pdfFilename, data);
// var content = zip.generate();
// location.href="data:application/zip;base64,"+content;
});
}
console.log(zip);
var content = zip.generate();
location.href="data:application/zip;base64,"+content;
}
});
}
this is my code. I am looping over all the data to generate multiple pdf. Then passing the pdfs to jsZIP. but all the time zip file is returning empty.
Is this the right way to download multiple pdf in jsZIP?
I'm using ECharts in order to create dynamically some line charts.
They are created within div which are dynamically created in my js script based on the server response. The problem is that they are not shown.
I did some test and I noticed that if a div has height 0, the chart will not be shown. So I moved the charts initialization when the div is visible: also now I can't see anything.
var chartsIds = []; // div id list
var lineCharts = []; // charts array
var optionLineCharts = [];
this is the charts initialization function
function init_echart(id, index) {
if ($("#" + id).length) {
var echartLine = echarts.init(document.getElementById(id), theme);
echartLine.setOption(optionLineCharts[index], true);
lineCharts.push(echartLine);
}
}
which is called for each div id in this moment if ($("#block").is(":visible")).
The options are like this one:
var echartLineOptions = {
tooltip:
{
trigger: "item",
formatter: function(params) {
var date = new Date(params.value[0]);
data = date.getFullYear() +
"-" +
(date.getMonth() + 1) +
"-" +
date.getDate() +
" " +
date.getHours() +
":" +
date.getMinutes();
return data + "<br/>" + params.value[1];
}
},
legend:
{
data: []
},
toolbox:
{
show: true,
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
restore: { show: true },
saveAsImage: { show: true }
}
},
dataZoom: {
show: true,
start: 70
},
xAxis: [
{
type: "time",
splitNumber: 2
}
],
yAxis: [
{
type: "value"
}
],
series:
[]
};
where the series are populated (correctly) based on the server response. I can see with the debug all the data and the series.
I noticed that even if the biggest div is visible, the div created by the charts initialization function has height = 0. I can't find any solutions..any help is really appreciated!!
Here is an example of a graph I'm using: http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/stock/demo/basic-line/
Below it I have a table I need to update when the dates are changes (either with the draggable range at the bottom or manually inputed in input boxes not shown in this example).
Here's my dummied down code concerning the chart
const PageLineChart = props => {
let data = [];
const { title,
heading,
subtitle,
legend,
defaultRange,
xLabel,
yLabel
} = props.column;
var selected;
defaultRange
? selected = ['1m', '3m', '6m', 'ytd', '1y', 'all'].indexOf(defaultRange)
: selected = 3;
const series = props.column.values.map(field => {
return {
name: field,
data: [],
tooltip: {
valueDecimals: 2
}
};
});
props.data.map((row, index) => {
const timeStamp = moment(row[props.column.category]).unix() * 1000;
props.column.values.forEach((field, index) => {
series[index].data.push([timeStamp, Number(row[field])]);
});
});
data.reverse();
const chartConfig = {
rangeSelector: {
selected
},
credits: {
enabled: false
},
exporting: {
enabled: false
},
title: {
text: title
},
subtitle: {
text: subtitle
},
xAxis: {
title: {
text: xLabel || ''
}
},
yAxis: {
title: {
text: yLabel || ''
}
},
tooltip: {
formatter: function() {
var s = '<span style="font-size: 10px">' + moment(this.x).format('MMMM Do YYYY') + '</span><br/>';
for (var i = 0; i < this.points.length; i++) {
var prefix = '';
if (props.column.hasOwnProperty('format')) {
prefix = formatNumber(this.points[i].y, props.column.format);
} else {
prefix = formatNumber(this.points[i].y);
}
var myPoint = this.points[i];
s += '<br/><span style="color:' +
myPoint.series.color +
'">\u25CF</span> ' +
capitalize(myPoint.series.name) + ': ';
/* Need to check whether or not we are dealing with an
* area range plot and display a range if we are
*/
if (myPoint.point.low && myPoint.point.high) {
s += myPoint.point.low + ' - ' + myPoint.point.high;
} else {
s += prefix;
}
}
return s;
}
},
plotOptions: {
series: {
animation: true
}
},
shared: true,
series: series
};
return (
<div className="panel panel-default no-bg b-a-2 b-gray-dark">
<div className="panel-heading">
{heading}
</div>
<div className="panel-body">
<HighStock config={chartConfig} />
</div>
</div>
);
};
Preferably I would have liked a function that fires when changing dates and has access to the 'startDate' and 'endDate' but I can't seem to find anything in the docs.
Does anyone have any ideas please?
use setExtremes -
Fires when the minimum and maximum is set for the axis, either by calling the .setExtremes() method or by selecting an area in the chart. One parameter, event, is passed to the function. This contains common event information based on jQuery or MooTools depending on which library is used as the base for Highcharts.
Highcharts.stockChart('container', {
xAxis: {
events: {
setExtremes: function (e) {
$('#report').html('<b>Set extremes:</b> e.min: ' + Highcharts.dateFormat(null, e.min) +
' | e.max: ' + Highcharts.dateFormat(null, e.max) + ' | e.trigger: ' + e.trigger);
}
}
},
rangeSelector: {
selected: 1
},
series: [{
name: 'USD to EUR',
data: usdeur
}]
});
reference
Example demo
i am wondering if there is an option in ChartJS for how i can see extra information if i hover over a single point on a line chart. Currently my data looks like this:
function drawChart(dataSets) {
Chart.defaults.global.maintainAspectRatio = false;
Chart.defaults.global.responsive = false;
var data = {
labels: ["ID"]
, datasets: dataSets
};
var options = {
title: {
display: true
, text: 'Custom Chart Title'
}
, scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
, xAxes: [{
type: "linear"
, position: "bottom"
}]
}
, tooltips: {
callbacks: {
label: function (tooltipItem, data) {
alert("Entered tooltip callback.");
return i18n.t('chart.count') + ': ' + getCount(tooltipItem, data.datasets);
}
}
}
};
var myChart = Chart.Line(ctx, {
data: data
, options: options
});
}
function getCount(tooltipItem, datasets) {
return datasets[tooltipItem.datasetIndex].data.find(datum => {
return datum.x === tooltipItem.xLabel && datum.y === tooltipItem.yLabel;
}).count;
}
And this is how one data point looks like:
x:5
, y:5
, count:2
But the alert "Entered tooltip" never gets called, what am i doing wrong? Instead i get this error on my console (Google Chrome):
The way i added my custom tooltips.
Firstly i created a dataset in which data array i added additional info like "count"
x: 10,
y: 12,
count: 2
This count does not get shown in graph unless I manually get it in options hook under tooltips.
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
return i18n.t('chart.count') +': ' + getCount(tooltipItem, data.datasets);
}
}
}
Get count for given dataset that was not represented in chart
function getCount(tooltipItem, datasets) {
return datasets[tooltipItem.datasetIndex].data.find(datum => {
return datum.x === tooltipItem.xLabel && datum.y === tooltipItem.yLabel;
}).count;
}
And the result:
Edit: adding an example of my dataset
return [{
label: 'Example dataset',
backgroundColor: '#98cc99',
borderColor: '#98cc99',
pointHoverRadius: 3,
data: [ {x:1,y:2,count:3}, {x:2,y3,count:4}, ...]
}];
This looks like it:
label: sensorID,
data: [0,1,2,3],
backgroundColor: [
'rgba(' + rndColor() + ',' + rndColor() + ',' + rndColor() + ',0.2)'
],
borderColor: [
'rgba(' + rndColor() + ',' + rndColor() + ',' + rndColor() + ',0.2)'
],
options: {
tooltips: {
enabled: true
custom: function(tooltip) {
// code here to customize a tooltip
}
}
}
http://www.chartjs.org/docs/#chart-configuration-tooltip-configuration
http://www.chartjs.org/docs/#advanced-usage-external-tooltips
See sample/line-customTooltips.html for examples on how to get
started.
I have worked with chart.js 1.0 and had my doughnut chart tooltips displaying percentages based on data divided by dataset, but I'm unable to replicate this with chart 2.0.
I have searched high and low and have not found a working solution. I know that it will go under options but everything I've tried has made the pie dysfunctional at best.
<html>
<head>
<title>Doughnut Chart</title>
<script src="../dist/Chart.bundle.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<style>
canvas {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
</style>
</head>
<body>
<div id="canvas-holder" style="width:75%">
<canvas id="chart-area" />
</div>
<script>
var randomScalingFactor = function() {
return Math.round(Math.random() * 100);
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var randomColor = function(opacity) {
return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
};
var config = {
type: 'doughnut',
data: {
datasets: [{
data: [
486.5,
501.5,
139.3,
162,
263.7,
],
backgroundColor: [
"#F7464A",
"#46BFBD",
"#FDB45C",
"#949FB1",
"#4D5360",
],
label: 'Expenditures'
}],
labels: [
"Hospitals: $486.5 billion",
"Physicians & Professional Services: $501.5 billion",
"Long Term Care: $139.3 billion",
"Prescription Drugs: $162 billion",
"Other Expenditures: $263.7 billion"
]
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
title: {
display: false,
text: 'Chart.js Doughnut Chart'
},
animation: {
animateScale: true,
animateRotate: true
}
}
};
window.onload = function() {
var ctx = document.getElementById("chart-area").getContext("2d");
window.myDoughnut = new Chart(ctx, config);{
}
};
</script>
</body>
</html>
Update: The below answer shows a percentage based on total data but #William Surya Permana has an excellent answer that updates based on the shown data https://stackoverflow.com/a/49717859/2737978
In options you can pass in a tooltips object (more can be read at the chartjs docs)
A field of tooltips, to get the result you want, is a callbacks object with a label field. label will be a function that takes in the tooltip item which you have hovered over and the data which makes up your graph. Just return a string, that you want to go in the tooltip, from this function.
Here is an example of what this can look like
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
//get the concerned dataset
var dataset = data.datasets[tooltipItem.datasetIndex];
//calculate the total of this data set
var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) {
return previousValue + currentValue;
});
//get the current items value
var currentValue = dataset.data[tooltipItem.index];
//calculate the precentage based on the total and current item, also this does a rough rounding to give a whole number
var percentage = Math.floor(((currentValue/total) * 100)+0.5);
return percentage + "%";
}
}
}
and a full example with the data you provided
fiddle
var randomScalingFactor = function() {
return Math.round(Math.random() * 100);
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var randomColor = function(opacity) {
return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
};
var config = {
type: 'doughnut',
data: {
datasets: [{
data: [
486.5,
501.5,
139.3,
162,
263.7,
],
backgroundColor: [
"#F7464A",
"#46BFBD",
"#FDB45C",
"#949FB1",
"#4D5360",
],
label: 'Expenditures'
}],
labels: [
"Hospitals: $486.5 billion",
"Physicians & Professional Services: $501.5 billion",
"Long Term Care: $139.3 billion",
"Prescription Drugs: $162 billion",
"Other Expenditures: $263.7 billion"
]
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
title: {
display: false,
text: 'Chart.js Doughnut Chart'
},
animation: {
animateScale: true,
animateRotate: true
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) {
return previousValue + currentValue;
});
var currentValue = dataset.data[tooltipItem.index];
var percentage = Math.floor(((currentValue/total) * 100)+0.5);
return percentage + "%";
}
}
}
}
};
var ctx = document.getElementById("chart-area").getContext("2d");
window.myDoughnut = new Chart(ctx, config); {
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.bundle.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="canvas-holder" style="width:75%">
<canvas id="chart-area" />
</div>
For those who want to display dynamic percentages based on what currently displayed on the chart (not based on total data), you can try this code:
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var meta = dataset._meta[Object.keys(dataset._meta)[0]];
var total = meta.total;
var currentValue = dataset.data[tooltipItem.index];
var percentage = parseFloat((currentValue/total*100).toFixed(1));
return currentValue + ' (' + percentage + '%)';
},
title: function(tooltipItem, data) {
return data.labels[tooltipItem[0].index];
}
}
},
In 3.5 it will be:
options: {
plugins: {
tooltip: {
callbacks: {
label: function(context){
var data = context.dataset.data,
label = context.label,
currentValue = context.raw,
total = 0;
for( var i = 0; i < data.length; i++ ){
total += data[i];
}
var percentage = parseFloat((currentValue/total*100).toFixed(1));
return label + ": " +currentValue + ' (' + percentage + '%)';
}
}
}
}
}
but better, dynamic version:
options: {
plugins: {
tooltip: {
callbacks: {
label: function(context){
var label = context.label,
currentValue = context.raw,
total = context.chart._metasets[context.datasetIndex].total;
var percentage = parseFloat((currentValue/total*100).toFixed(1));
return label + ": " +currentValue + ' (' + percentage + '%)';
}
}
}
}
}
I came across this question because I needed to show percentage on stacked bar charts. The percentage I needed was per stacked columns. I accomplished this by modifying Willian Surya's answer like this:
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var index = tooltipItem.index;
var currentValue = data.datasets[tooltipItem.datasetIndex].data[index];
var total = 0;
data.datasets.forEach(function(el){
total = total + el.data[index];
});
var percentage = parseFloat((currentValue/total*100).toFixed(1));
return currentValue + ' (' + percentage + '%)';
},
title: function(tooltipItem, data) {
return data.datasets[tooltipItem[0].datasetIndex].label;
}
}
}
This is the final result:
The usage has changed in 3.x and higher versions, so I will attach a method for this.
const data: ChartData = {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [
{
data: excerciseData,
backgroundColor: [
"rgba(255, 99, 132, 0.5)",
"rgba(54, 162, 235, 0.5)",
"rgba(255, 206, 86, 0.5)",
"rgba(75, 192, 192, 0.5)",
"rgba(153, 102, 255, 0.5)",
"rgba(255, 159, 64, 0.5)"
]
}
]
};
...
callbacks: {
label: tooltipItem => {
let total = 0;
data.datasets[0].data.forEach(num => {
total += num as number;
});
const currentValue = data.datasets[0].data[tooltipItem.dataIndex] as number;
const percentage = ((currentValue * 100) / total).toFixed(1) + "%";
return `${currentValue}(${percentage})`;
},
title: tooltipItems => {
return tooltipItems[0].label;
}
}
Simply use this:
const options = {
responsive: true,
plugins: {
tooltip: {
callbacks: {
label: (Item) => '%' + (Item.formattedValue) + ' | ' + Item.label
}
}
},
};