Apex Charts tooltip formatter: how to convert number to string - charts

I am trying to show y value of a line graph in tooltips (apexcharts). The original data is set in string format in series {'8:50 AM', '11:00 AM', '9:02 AM'...}, but they appear as '8', '11', '9'... in tooltips, rounded up in number format. I do not understand why the string data is converted and rounded by itself. I used 'toString' to reveal the full text ('8:50 AM', '11:00 AM', '9:02 AM'...) in the tooltip formatter, but it did not work. Any suggestion?
var spark1 = {
chart: {
id: 'sparkline1',
type: 'line',
height: 200,
sparkline: {
enabled: true
},
group: 'sparklines'
},
series: [{
name: 'wake-up time',
data: [{x: '05/06/2014', y:'8:50 AM'}, {x: '05/07/2014', y:'11:00 AM'}, {x: '05/08/2014', y:'9:02 AM'}, {x: '05/09/2014', y:'10:47 AM'}]
}],
tooltip: {
x: {
show: false
},
y: {
formatter: function(value){
return value.toString();
}
}
}
}
}

the chart expects a number to be provided for y.
if a string is provided, it will try to parse it to a number using parseFloat, or something similar.
this is why the numbers are rounded.
parseFloat('8:50 AM') = 8
so if you have something like '1:00 PM', you will not get the desired line.
instead, convert the values to a true date, then extract the time value.
we can do this be creating a new date with both the date and time.
then create another date with just the date portion and subtract it from the previous.
we'll save the chart data in a variable.
var chartData = [{x: '05/06/2014', y: '8:50 AM'}, {x: '05/07/2014', y: '11:00 AM'}, {x: '05/08/2014', y: '9:02 AM'}, {x: '05/09/2014', y: '10:47 AM'}];
then map it for the chart.
data: chartData.map(function (row) {
// create full date time
var rowDate = new Date(row.x + ' ' + row.y);
// subtract date only from full date time
var rowTime = rowDate.getTime() - (new Date(row.x)).getTime();
// return new data point
return {x: row.x, y: rowTime};
})
as for the tooltip, we can use the series argument to pull the index from the original data...
formatter: function(value, series) {
// use series argument to pull original string from chart data
return chartData[series.dataPointIndex].y;
}
see following working snippet...
$(document).ready(function() {
var chartData = [{x: '05/06/2014', y: '8:50 AM'}, {x: '05/07/2014', y: '11:00 AM'}, {x: '05/08/2014', y: '9:02 AM'}, {x: '05/09/2014', y: '10:47 AM'}];
var spark1 = {
chart: {
id: 'sparkline1',
type: 'line',
height: 200,
sparkline: {
enabled: true
},
group: 'sparklines'
},
series: [{
name: 'wake-up time',
data: chartData.map(function (row) {
// create full date time
var rowDate = new Date(row.x + ' ' + row.y);
// subtract date only from full date time
var rowTime = rowDate.getTime() - (new Date(row.x)).getTime();
// return new data point
return {x: row.x, y: rowTime};
})
}],
tooltip: {
x: {
show: false
},
y: {
formatter: function(value, series) {
// use series argument to pull original string from chart data
return chartData[series.dataPointIndex].y;
}
}
}
};
var chart = new ApexCharts(
document.getElementById('sparkline1'),
spark1
);
chart.render();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
<div id="sparkline1"></div>

You can also customise the title and the value with the below format
tooltip: {
y: {
formatter: function (val) {
return "Count: " + val
},
title: {
formatter: function (seriesName) {
return seriesName + "<br/>-------------------<br/>"
},
},
},
},

Related

How can I display time and date in separate lines along xaxis in QML chartview line series

I am able to display epoch value to Date and Time strings along x-axis in QML Chartview Lineseries. But, I want to split the time and date into separate lines as shown in the attachment.
Can someone please help me on this.
Below is the sample code
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
property var lineSeries
property var x1Val : [1649736833, 1649740465, 1649744065, 1649747665, 1649751265, 1649754865, 1649758465, 1649762065, 1649765665, 1649769265]
property var y1Val : [0,1,2,3,4,5,6,7,8,9]
Component.onCompleted: {
for(var i = 0; i < 2; i++) {
lineSeries = chartView.createSeries(ChartView.SeriesTypeLine, "strend" + i)
lineSeries.axisX = axisx2
lineSeries.axisY = axisy2
for(var iLoop = 0; iLoop < x1Val.length; iLoop++) {
lineSeries.append(x1Val[iLoop] * 1000 + (i * 10000000), y1Val[iLoop])
}
}
}
ChartView {
x: 0
y: 0
width: 640
height: 480
id: chartView
DateTimeAxis {
id: axisx2
min: new Date(1649736833000)
max: new Date(1649779265000)
//format: "yyyy/MM/dd hh:mm"
format: "hh:mm yyyy/MM/dd"
}
ValueAxis {
id: axisy2
min: 0
max: 10
}
}
}
Expected Output
use the format like below. This aligns the time to center of the date.
format: "     hh:mmyyyy/MM/dd"
//where &#160 - spacing character in HTML
Thank you all for your help

Making a list of dates from last month

I want to make a list of last months dates in cell A5:A35.
I am currently just using formulas but it is going to list all the days regardless of count in the month. So when I make the sheet for November, it's going to have December 1st on the list. I don't want that.
I tried scripting to get the current month and searching the range for that month but it's not working and seems convoluted. There must be a cleaner way.
I just want to programmatically list the days in the prior month.
I have this
Code function won't work on mobile
function assignDates() {
const cell = sheet.getRange('A5:A35');
cell.setFormula(=EOMONTH(TODAY(),-2)+D5
d5 is a hidden column with 1,2,3... etc. It's a really cheap way to do it.
It will list 31 days regardless of the length of the month.
Now to deal with this, I tried to make a script to get the current month and then delete entries that contain that but it does not work.
//check to see if dates fall within month
function dateCheck(sheet){
var sheet = SpreadsheetApp.getActive().getSheetByName('test');
//get current month
var month = Utilities.formatDate(new Date(), "GMT-5", "MMMM")
// Delete days that fall out of range
var dayRange = sheet.getRange('A5:A36').getDisplayValues();
dayRange.forEach((date) => { if (date.toString().includes(month))
{ sheet.getRangeList(dayRange).clearContent() } })
}
=SEQUENCE(DAYS(EOMONTH(TODAY(),)+1,EOMONTH(TODAY(),-1)+1),1,EOMONTH(TODAY(),-1)+1)
DAYS to calculate number of days in this month
EOMONTH to get end date of last month and this month
SEQUENCE to create sequence of dates.
You need to change TODAY() to a static date string, if you don't want the sequence to change every month.
Get Last Month and This Month Calendar on a Spreadsheet
Code:
function getCalendar() {
const ss = SpreadsheetApp.getActive()
const sh = ss.getActiveSheet();
sh.clear();
let oA = [];
oA.push(monthlyCalendar(new Date().getMonth() - 1, null, true));//helper function
oA.push(monthlyCalendar(new Date().getMonth(), null, true));//helper function
//oA.push(monthlyCalendar(new Date().getMonth() + 1, null, true));
oA.forEach((obj, i) => {
if (i == 0) {
sh.getRange(1, 1, 2, obj.cA[0].length).setFontWeight('bold');
sh.getRange(1, 1, obj.cA.length, obj.cA[0].length).setValues(obj.cA);
} else {
let sr = sh.getLastRow() + 2;
sh.getRange(sr, 1, 2, obj.cA[0].length).setFontWeight('bold');
sh.getRange(sr, 1, obj.cA.length, obj.cA[0].length).setValues(obj.cA);
if (obj.roff && obj.coff) {
sh.getRange(sr, 1).offset(obj.roff, obj.coff).setFontWeight('bold').setFontColor('red');//sets the current date to bold and red
}
}
});
}
Helper Function:
function monthlyCalendar(m, wsd, ret) {
var m = m || new Date().getMonth();
var wsd = wsd || 1;//defaults to Monday
var ret = ret || false;
const td = new Date();
const [cy,cm,cd] = [td.getFullYear(),td.getMonth(),td.getDate()];
const dA = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const oA = [...Array.from(Array(7).keys(), idx => dA[(idx + wsd) % 7])]
let dObj = {};
let midx = {};
let rObj = {cA:null,roff:null,coff:null};
oA.forEach(function (e, i) { dObj[e] = i; });
const mA = [...Array.from(new Array(12).keys(), x => Utilities.formatDate(new Date(2021, x, 15), Session.getScriptTimeZone(), "MMM"))];
mA.forEach((e, i) => { midx[i] = i; })
let cA = [];
let bA = [];
let wA = [null, null, null, null, null, null, null];
const ss = SpreadsheetApp.getActive();
const sh = ss.getActiveSheet();
sh.clear();
const year = new Date().getFullYear();
let i = midx[m % 12];
let month = new Date(year, i, 1).getMonth();
let dates = new Date(year, i + 1, 0).getDate();
cA.push([mA[month], dates, '', '', '', '', '']);
bA.push(['#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff']);
cA.push(oA)
//bA.push(['#ffff00', '#ffff00', '#ffff00', '#ffff00', '#ffff00', '#ffff00', '#ffff00']);
let d = [];
let ddd = [];
for (let j = 0; j < dates; j++) {
let day = new Date(year, i, j + 1).getDay();
let date = new Date(year, i, j + 1).getDate();
if (day < wA.length) {
wA[dObj[dA[day]]] = date;
}
if(cy == year && cm == month && cd == date) {
rObj.roff = cA.length;
rObj.coff = dObj[dA[day]];
}
if (dA[day] == oA[wA.length - 1] || date == dates) {
cA.push(wA);
//bA.push(['#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff']);
wA = ['', '', '', '', '', '', ''];
}
}
if (!ret) {
rObj.cA = cA;
sh.getRange(1, 1, rObj.cA.length, rObj.cA[0].length).setValues(cA);
if (rObj.roff && rObj.coff) {
sh.getRange(1, 1).offset(rObj.roff, rObj.coff).setFontWeight('bold').setFontColor('red');
}
} else {
rObj.cA = cA;
return rObj;
}
}
Demo:
Nice challenge!
Enter the following function on A5:
=arrayformula(if(eomonth(today(),-2)+row(A5:A35)+1-row()>eomonth(today(),-1),"",eomonth(today(),-2)+row(A5:A35)+1-row()))
It will retrieve all dates from the 1st until the last date of the last month (related to today()). until 31th, 30th, 28th, or 29th dynamically!
Cheers!
If you need a script here you go:
function myFunction() {
var today = new Date();
var year = today.getFullYear();
var month = today.getMonth(); // 0 -> January, 1 -> February, etc...
// get the number of days of the previous month
// 'zero day' of a month is the last day of a previous month
var len = new Date(year, month, 0).getDate();
// make an array with dates (strings, actually)
var options = { year: 'numeric', month: 'numeric', day: 'numeric' };
var dates = new Array(len).fill('').map((_, day) =>
[new Date(year, month-1, day+1).toLocaleString('en-US', options)])
// put the array on the sheet
SpreadsheetApp.getActiveSheet().getRange(5,1,len,1).setValues(dates);
}
Dates is a tricky thing always.

How to find maximum time from array?

I have an array. I want to find the maximum time from array.
I tried the follwing code.
var maxtm = Math.max.apply(null, this.my_arr.map(function (e) {
return e['time'];
}));
this.my_arr = [{
date: '21-jun-2019',
time: '21:22:00'
}, {
date: '21-june-2019',
time: '11:33:23',
}, {
date: '21-june-2019',
time: '12:12:00'
}]
I expect the output '21:22:00', but the actual output is NaN.
Try convert to int parseInt(time.split(':').join(''), 10)
Example
var maxtm = Math.max.apply(null, this.my_arr.map(function (e) { return parseInt(e['time'].split(':').join(''), 10);}));
After you need convert max back to time

MongoDB String to Date?

Following is the JSON of the data:
{
projectName: String,
projectDescription: String,
users: [
],
projectManager: [
],
teamLead: [
],
createdBy: String,
superAdmin: String,
createdAt: Date,
task: [
{
userID: String,
name: String,
taskName: String,
nohrs: Number,
description: String,
comments: String,
date: String,
from: String,
to: String,
lastUpdatedDate: String
}
]
}
The array of task contains many tasks for each project. And each task has a date in string format. I need to convert each string date to ISO date format.
How can we achieve this?
I have tried this :
db.projects.find({}).forEach(function(task){
task.date = new Date(task.date);
db.collection.save(task);})
but this is adding a column date and not editing the date in task array?
You were really close to the solution. The correct code snippet would be:
db.projects.find({}).forEach(function(project){
project.task.forEach(function(task){
task.date = new Date(task.date);
});
db.projects.save(project);
})
You needed one more loop in your code :)
Well guys this worked
db.projects.find({}).forEach(function(project){
project.task.forEach(function(task){
// printjson(task)
task.date = new Date(task.date);
print(task.date)
db.projects.save(project);
})})
db.projects.find({},function(err,data){
//this find query will return the array of documents through which we need to iterate
for(var i =0; i<data.length;i++){
//then we want convert the date string into Date we need to iterate task array with in each document.
for(var j = 0;j<data[i].task.length;j++){
data[i].task[j].date = new Date(data[i].task[j].date);
}
}
//now save the new data
data.save();
})

MapReduce sub-document

I am trying to graph email activity that I am recording in a Mongo db. Whenever I send out an email I create a record, then, when there is activity on the email (open, click, mark as spam) I update the document by adding to it's history.
Here is a sample document:
{
"_id" : new BinData(3, "wbbS0lRI0ESx5DyStKq9pA=="),
"MemberId" : null,
"NewsletterId" : 4,
"NewsletterTypeId" : null,
"Contents" : "[message goes here]",
"History" : [{
"EmailActionType" : "spam",
"DateAdded" : new Date("Sat, 10 Dec 2011 04:17:26 GMT -08:00")
}, {
"EmailActionType" : "processed",
"DateAdded" : new Date("Sun, 11 Dec 2011 04:17:26 GMT -08:00")
}, {
"EmailActionType" : "deffered",
"DateAdded" : new Date("Mon, 12 Dec 2011 04:17:26 GMT -08:00")
}],
"DateAdded" : new Date("Mon, 01 Jan 0001 00:00:00 GMT -08:00")
}
What I would like to do is query the database for a specific history date range. The end result should be a list with an item for each day where there is an activity and a total for each activity type:
date: "20111210", spam: 1, processed: 0, deffered: 0
date: "20111211", spam: 0, processed: 1, deffered: 0
date: "20111212", spam: 0, processed: 0, deffered: 1
Here is what I currently have:
db.runCommand({ mapreduce: Email,
map : function Map() {
var key = this.NewsletterId;
emit(
key,
{ "history" : this.History }
);
}
reduce : function Reduce(key, history) {
var from = new Date (2011, 1, 1, 0, 0, 0, 0);
var to = new Date (2013, 05, 15, 23, 59, 59, 0);
// \/ determine # days in the date range \/
var ONE_DAY = 1000 * 60 * 60 * 24; // The number of milliseconds in one day
var from_ms = from.getTime(); // Convert both date1 to milliseconds
var to_ms = to.getTime(); // Convert both date1 to milliseconds
var difference_ms = Math.abs(from_ms - to_ms); // Calculate the difference in milliseconds
var numDays = Math.round(difference_ms/ONE_DAY); // Convert back to days and return
// /\ determine # days between the two days /\
var results = new Array(numDays); //array where we will store the results. We will have an entry for each day in the date range.
//initialize array that will contain our results for each type of emailActivity
for(var i=0; i < numDays; i++){
results[i] = {
numSpam: 0,
numProcessed: 0,
numDeffered: 0
}
}
//traverse the history records and count each type of event
for (var i = 0; i < history.length; i++){
var to_ms2 = history[i].DateAdded.getTime(); // Convert both date1 to milliseconds
var difference_ms2 = Math.abs(from_ms - to_ms2); // Calculate the difference in milliseconds
var resultsIndex = Math.round(difference_ms2/ONE_DAY); //determine which row in the results array this date corresponds to
switch(history[i].EmailActionType)
{
case 'spam':
results[resultsIndex].numSpam = ++results[resultsIndex].numSpam;
break;
case 'processed':
results[resultsIndex].numProcessed = ++results[resultsIndex].numProcessed;
break;
case 'deffered':
results[resultsIndex].numDeffered = ++results[resultsIndex].numDeffered;
break;
}
}
return results;
}
finalize : function Finalize(key, reduced) {
return {
"numSpam": reduced.numSpam,
"numProcessed": reduced.numProcessed,
"numDeffered": reduced.numDeffered,
};
}
out : { inline : 1 }
});
When I run it, I don't get anything, but I'm also not getting any errors, so not really sure where to look.
Your problem is definitely in your Map / Reduce functions. There is a disconnect between your emit and your expected output.
Your expected output:
date: "20111210", spam: 1, processed: 0, deffered: 0
Map / Reduce always outputs in terms of a key and a value. So your output would look like this:
_id: "20111220", value: { spam: 1, processed: 0, deferred: 0 }
Here is the basic premise. Your emit needs to output data of the correct format. So if you emit(key, value), then you should have:
var key='20111220'
var value={spam:1, processed:0, deferred:0}
In your case, you are emitting several times per document as you loop through History. This is normal.
The reduce function is only run if there are multiple values for the same key. So if you have this:
_id: "20111220", value: { spam: 1, processed: 0, deferred: 0 }
_id: "20111220", value: { spam: 1, processed: 2, deferred: 0 }
Then reduce will pull those together and give you this:
_id: "20111220", value: { spam: **2**, processed: **2**, deferred: 0 }
Here is a quick stab at the answer:
map = function() {
for(var i in this.History) {
var key = get_date(this.History[i].DateAdded);
var value = {spam: 0, processed: 0, deffered: 0};
if(this.History[i].EmailActionType == "Spam") { value.spam++; }
else if(....)
...
emit(key, value);
}
}
reduce = function(key, values) {
// values is an array of these things {spam: 0, processed: 0, deffered: 0}
var returnValue = { spam: 1, processed: 0, deffered: 0 };
for(var i in values) {
returnValue.spam += values[i].spam;
returnValue.processed += values[i].processed;
returnValue.deffered += values[i].deffered;
}
return returnValue;
}
Just remember that the structure of emit has to match the structure of your final values.