while using header option with XLSX.utils.json_to_sheet , headers not overriding - js-xlsx

I'm trying to change header titles by passing an array of titles to options but it does not override the headers. Instead it inserts new headers before the original data. I am passing the same numbers of header titles.
Here is my code:
const ws: XLSX.WorkSheet = XLSX.utils.json_to_sheet(
json,
{header: headerColumns}
);
const wb: XLSX.WorkBook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Transactions');
const excelBuffer: any = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
this.saveAsExcelFile(excelBuffer, excelFileName);
And output looks like below:

The basic job of the "header" option is not to override, rather just shift the starting option of the columns.
i.e. any value passed in the header option will be treated as the first column, provided the value should match with existing keys you have in the data.
XLSX.utils.json_to_sheet([{A:1,B:2}, {B:2,C:3}], {header:['C']});
Here column "C" will be the first column in the excel.
For more look out for detailed description here: https://docs.sheetjs.com/#sheetjs-js-xlsx

This is how I have achieved similar behavior:
const XLSX = require('xlsx');
const wb = XLSX.utils.book_new();
const Heading = [
['Sr No', 'User Name', 'Department', 'Bank', 'Country', 'Region', 'Amount']
];
// creating sheet and adding data from 2nd row of column A.
// leaving first row to add Heading
const ws = XLSX.utils.json_to_sheet(data, { origin: 'A2', skipHeader: true });
// adding heading to the first row of the created sheet.
// sheet already have contents from above statement.
XLSX.utils.sheet_add_aoa(ws, Heading, { origin: 'A1' });
// appending sheet with a name
XLSX.utils.book_append_sheet(wb, ws, 'Records');
const fileContent = XLSX.write(wb, { bookType: 'xlsx', type: 'buffer' });

Very traditional approach but working, please see complete code below:
const worksheet: XLSX.WorkSheet = XLSX.utils.json_to_sheet(
this.releaseDateWiseCountList
);
worksheet.A1.v = "Pick Release Date";
worksheet.B1.v = "Task Type";
worksheet.C1.v = "First Shift";
worksheet.D1.v = "Second Shift";
worksheet.E1.v = "Total";
worksheet.F1.v = "Grand Total";
worksheet.G1.v = "Pick %";
const workbook: XLSX.WorkBook = {
Sheets: { 'data': worksheet }, SheetNames: ['data']
};
const excelBuffer: any = XLSX.write(
workbook, { bookType: 'xlsx', type: 'array' }
);
const data: Blob = new Blob([buffer], {type: EXCEL_TYPE});
FileSaver.saveAs(data, 'Result_export_' + new Date().getTime() + EXCEL_EXTENSION);

Related

Data from a Google Sheet "form" to a Google sheet spreadsheet

I have a Google sheet that is in a “Form” format. I need to program a button that once the sender completes the form, will send the data to another sheet in a spreadsheet format and erase the data from the “form” making it ready for another form entry.
enable goole sheets api service, and adapt this script (names of source and destination sheets, and cellsA1Notation of data in source sheet).
function onOpen() {
SpreadsheetApp.getUi().createMenu('⇩ M E N U ⇩')
.addItem('👉 Validate Unit Registration (FORM)', 'copyDataRegistrationForm')
.addItem('👉 Reset Unit Registration (FORM)', 'initRegistrationForm')
.addToUi();
}
function copyDataRegistrationForm() {
const ssId = SpreadsheetApp.getActiveSpreadsheet().getId();
const srcSheet = "Unit Registration (FORM)";
const dstSheet = "Master Data";
const rngA1Notation = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dstSheet).getRange(1, 1, 1, 74).getValues().flat()
const src = rngA1Notation.map(e => `'${srcSheet}'!${e}`);
const values = Sheets.Spreadsheets.Values.batchGet(ssId, { ranges: src })
var data = []
values.valueRanges.forEach(e => data.push(e.values ? e.values.flat().toString() : ""))
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dstSheet).appendRow(data)
}
function initRegistrationForm() {
const srcSheet = "Unit Registration (FORM)";
const dstSheet = "Master Data";
const rngA1Notation = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dstSheet).getRange(1, 1, 1, 74).getValues().flat()
var rng = rngA1Notation.filter(r => SpreadsheetApp.getActiveSpreadsheet().getRange(r).getFormula() == '')
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(srcSheet).getRangeList(rng).clearContent()
}

Populating a Doc with Google Sheets

I don't know why the text shows up in the Google Doc for First Name and Last Name columns but no text for the notes column. I'm not sure why, I'd also like for row 9, 10, and 11 to populate text depending on which row is selected.
function onOpen() {
const ui = SpreadsheetApp.getUi();
const menu = ui.createMenu('AutoFill Docs');
menu.addItem('Create New Docs', 'createNewGoogleDocs');
menu.addToUi();
}
function createNewGoogleDocs() {
const googleDocTemplate = DriveApp.getFileById('1ybOPZ-Y3wIZMyZpCW3HUojb72nDF8D9ruipdphpWvBE');
const destinationFolder = DriveApp.getFolderById('1jtR9DjOY7wvu2I9NQvZtrWBkZtDlK2c0');
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Enrollments");
const rows = sheet.getDataRange().getDisplayValues();
rows.forEach(function(row, index) {
if (index === 0) return;
if (row[14]) return;
const copy = googleDocTemplate.makeCopy(`${row[1]}, ${row[2]} ABC Bartending and Casino
School`, destinationFolder);
const doc = DocumentApp.openById(copy.getId());
const body = doc.getBody();
const friendlyDate = new Date(row[1]).toLocaleDateString();
body.replaceText('{{Student ID}}', row[0]);
body.replaceText('{{Date}}', friendlyDate);
body.replaceText('{{First Name}}', row[3]);
body.replaceText('{{Last Name}}', row[4]);
body.replaceText('{{Phone}}', row[5]);
body.replaceText('{{Amount Paid}}', row[6]);
body.replaceText('{{Balance}}', row[7]);
body.replaceText('{{Start}}', friendlyDate);
body.replaceText('{{am}}', row[9]);
body.replaceText('{{pm}}', row[10]);
body.replaceText('{{we}}', row[11]);
body.replaceText('{{Email}}', row[12]);
body.replaceText('{{Notes}}', row[13]);
doc.saveAndClose();
const url = doc.getUrl();
sheet.getRange(index + 1, 15).setValue(url);
})
}

Merging topojson using topomerge messes up winding order

I'm trying to create a custom world map where countries are merged into regions instead of having individual countries. Unfortunately for some reason something seems to get messed up with the winding order along the process.
As base data I'm using the natural earth 10m_admin_0_countries shape files available here. As criteria for merging countries I have a lookup map that looks like this:
const countryGroups = {
"EUR": ["ALA", "AUT", "BEL"...],
"AFR": ["AGO", "BDI", "BEN"...],
...
}
To merge the shapes I'm using topojson-client. Since I want to have a higher level of control than the CLI commands offer, I wrote a script. It goes through the lookup map and picks out all the topojson features that belong to a group and merges them into one shape and places the resulting merged features into a geojson frame:
const topojsonClient = require("topojson-client");
const topojsonServer = require("topojson-server");
const worldTopo = topojsonServer.topology({
countries: JSON.parse(fs.readFileSync("./world.geojson", "utf-8")),
});
const geoJson = {
type: "FeatureCollection",
features: Object.entries(countryGroups).map(([region, ids]) => {
const relevantCountries = worldTopo.objects.countries.geometries.filter(
(country, i) =>
ids.indexOf(country.properties.ISO_A3) >= 0
);
return {
type: "Feature",
properties: { region, countries: ids },
geometry: topojsonClient.merge(worldTopo, relevantCountries),
};
}),
};
So far everything works well (allegedly). When I try to visualise the map using github gist (or any other visualisation tool like vega lite) the shapes seem to be all messed up. I'm suspecting that I'm doing something wrong during the merging of the features but I can't figure out what it is.
When I try to do the same using the CLI it seems to work fine. But since I need more control over the merging, using just the CLI is not really an option.
The last feature, called "World", should contain all remaining countries, but instead, it contains all countries, period. You can see this in the following showcase.
var w = 900,
h = 300;
var projection = d3.geoMercator().translate([w / 2, h / 2]).scale(100);
var path = d3.geoPath().projection(projection);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select('svg')
.attr('width', w)
.attr('height', h);
var url = "https://gist.githubusercontent.com/Flave/832ebba5726aeca3518b1356d9d726cb/raw/5957dca433cbf50fe4dea0c3fa94bb4f91c754b7/world-regions-wrong.topojson";
d3.json(url)
.then(data => {
var geojson = topojson.feature(data, data.objects.regions);
geojson.features.forEach(f => {
console.log(f.properties.region, f.properties.countries);
});
svg.selectAll('path')
// Reverse because it's the last feature that is the problem
.data(geojson.features.reverse())
.enter()
.append('path')
.attr('d', path)
.attr('fill', d => color(d.properties.region))
.attr('stroke', d => color(d.properties.region))
.on('mouseenter', function() {
d3.select(this).style('fill-opacity', 1);
})
.on('mouseleave', function() {
d3.select(this).style('fill-opacity', null);
});
});
path {
fill-opacity: 0.3;
stroke-width: 2px;
stroke-opacity: 0.4;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.js"></script>
<script src="https://d3js.org/topojson.v3.js"></script>
<svg></svg>
To fix this, I'd make sure to always remove all assigned countries from the list. From your data, I can't see where "World" is defined, and if it contains all countries on earth, or if it's a wildcard assignment.
In any case, you should be able to fix it by removing all matches from worldTopo:
const topojsonClient = require("topojson-client");
const topojsonServer = require("topojson-server");
const worldTopo = topojsonServer.topology({
countries: JSON.parse(fs.readFileSync("./world.geojson", "utf-8")),
});
const geoJson = {
type: "FeatureCollection",
features: Object.entries(countryGroups).map(([region, ids]) => {
const relevantCountries = worldTopo.objects.countries.geometries.filter(
(country, i) =>
ids.indexOf(country.properties.ISO_A3) >= 0
);
relevantCountries.forEach(c => {
const index = worldTopo.indexOf(c);
if (index === -1) throw Error(`Expected to find country ${c.properties.ISO_A3} in worldTopo`);
worldTopo.splice(index, 1);
});
return {
type: "Feature",
properties: { region, countries: ids },
geometry: topojsonClient.merge(worldTopo, relevantCountries),
};
}),
};

Map implementation with duplicate keys in Dart

I want to have a map with duplicate keys. Is there such a map in Dart or a utility library that has this functionality?
I'm using the following get a count of items:
myList.forEach(
(element) {
if (!myMap.containsKey(element)) {
myMap[element] = 1;
} else {
myMap[element] += 1;
}
},
);
then convert keys/values to lists: Need to switch key/values...
final keys = myMap.keys.toList();
final itemSpit = keys.map((e) => e.toString().split('§º')).toList();
final values = myMap.values.toList();
put it in a map
final map = Map();
for (var i = 0; i < values.length; i++) {
map[values[i]] = itemSpit[i];
}
Obviously the keys are overridden in the for loop.
then
iterate over map (Put keys/values in flutter widgets)
final cells = map.entries
.map((e) => ........
THe first method increases the value count if there's a duplicate value. So I have this. ... {breadwhitelarge: 3, cornyellowsmall:5 ..etc..}
I then have to split the strings and have output like this
5 bread white large
3 corn yellow small
Instead of defining a map which allows duplicated keys you can instead create a Map<K,List<V>> like this example:
void main() {
final map = <String, List<int>>{};
addValueToMap(map, 'Test 1', 1);
addValueToMap(map, 'Test 1', 2);
addValueToMap(map, 'Test 2', 3);
addValueToMap(map, 'Test 1', 4);
addValueToMap(map, 'Test 2', 5);
addValueToMap(map, 'Test 3', 6);
print(map); // {Test 1: [1, 2, 4], Test 2: [3, 5], Test 3: [6]}
}
void addValueToMap<K, V>(Map<K, List<V>> map, K key, V value) =>
map.update(key, (list) => list..add(value), ifAbsent: () => [value]);
You can then ask for a given key and get a list of all values connected to this key.
package:quiver provides a MultiMap class with List-based and Set-based implementations.
Could you just create the Flutter widgets directly from the first map?
var widgets = [for (var e in myMap.entries) MyWidget(
count: e.value,
strings: [... e.key.split("§º")])];
Building the intermediate map seems to be what is causing the trouble.
this is an example map has duplicate keys
withDuplicateKey() {
List<dynamic> demoList = [
{1},
{2},
{3},
{1}
];
var toRemove = {};
demoList.forEach((e) {
toRemove.putIfAbsent(e, () => e);
});
print(toRemove.keys.toList());
}
output is ( printed list of key )
[{1}, {2}, {3}, {1}]

Google Calendar Orderby when using two linq queries

I am using google charts to display a stacked column chart. I am using entity framework and linq queries to gather my data from the db.
The problems I am having is:
that it will not order the chart. I have ordered the chart but the x-axis remains un-ordered. Can this be done through the linq query or could I do it in the script?
Currently it only displays x-axis values for data that I have. Example is on the x-axis I have month number but it only displays marks for data I have eg. 1,4,5,6. Is there a way to include from 1-12 although there is no data for that particular month number?
Code:
#region Total Hours Per Month sick
var querythpshols = (from r in db.HolidayRequestForms
where (r.StartDate) >= dateAndTime
group r by r.MonthOfHoliday into g
select new { Value = g.Key, Count = g.Sum(h => h.HoursTaken)});
var resultthpshols = querythpshols.ToList();
var datachartthpshols = new object[resultthpshols.Count];
int G = 0;
foreach (var i in resultthpshols)
{
datachartthpshols[G] = new object[] { i.Value.ToString(), i.Count };
G++;
}
string datathpshols = JsonConvert.SerializeObject(datachartthpshols, Formatting.None);
ViewBag.datajthpshols = new HtmlString(datathpshols);
#endregion
#region Total Hours Per Month
var querythpshols1 = (from r in db.HolidayRequestForms
where (r.StartDate) <= dateAndTime
group r by r.MonthOfHoliday into g
select new { Value = g.Key, Count1 = g.Sum(r => r.HoursTaken) })
;
var resultthpshols1 = querythpshols1.ToList();
var datachartthpshols1 = new object[resultthpshols1.Count];
int P = 0;
foreach (var i in resultthpshols1)
{
datachartthpshols1[P] = new object[] { i.Value.ToString(), i.Count1 };
P++;
}
string datathpshols1 = JsonConvert.SerializeObject(datachartthpshols1, Formatting.None);
ViewBag.datajthpshols1 = new HtmlString(datathpshols1);
#endregion
Script:
#*TOTAL HOURS PER MONTH CHART*#
<scipt>
<script>
var datathpshols = '#ViewBag.datajthpshols';
var datassthpshols = JSON.parse(datathpshols);
var datathpshols1 = '#ViewBag.datajthpshols1';
var datassthpshols1 = JSON.parse(datathpshols1);
</script>
<script type="text/javascript">
// Load the Visualization API and the corechart package.
google.charts.load('current', { 'packages': ['corechart'] });
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(drawChartA);
// Callback that creates and populates a data table,
// instantiates the pie chart, passes in the data and
// draws it.
function drawChartA() {
// Create the data table.
var data1 = new google.visualization.DataTable();
data1.addColumn('string', 'Value');
data1.addColumn('number', 'Holiday Hours Booked');
data1.addRows(datassthpshols);
var data2 = new google.visualization.DataTable();
data2.addColumn('string', 'Value');
data2.addColumn('number', 'Holiday Hours Taken');
data2.addRows(datassthpshols1);
var joinedData = google.visualization.data.join(data1, data2, 'full', [[0, 0]], [1], [1]);
// Set chart options
var options = {
'title': 'Holiday Hours Taken Per Month',
'width': 600,
'height': 350,
'hAxis': { title: 'Month Number' },
'vAxis': { title: 'Holiday Hours Taken' },
'is3D': true,
'isStacked': true,
'legend': 'right'
};
// Instantiate and draw our chart, passing in some options.
var chart = new google.visualization.ColumnChart(document.getElementById('chartTHPShols_div'));
chart.draw(joinedData, options);
}
</script>
1) Use data table method --> sort -- to order the x-axis.
joinedData.sort([{column: 0}]);
2) strings produce a discrete axis, and will only display the data available. numbers produce a continuous axis, and provide much more flexibility for the axis ticks. probably the most simplest solution would be to use a data view to convert the x-axis to numbers. (use the data view to draw the chart)
var joinedData = google.visualization.data.join(data1, data2, 'full', [[0, 0]], [1], [1]);
var dataView = new google.visualization.DataView(joinedData);
dataView.setColumns([{
calc: function (dt, row) {
return parseFloat(dt.getValue(row, 0));
},
label: joinedData.getColumnLabel(0),
type: 'number'
}, 1, 2]);
chart.draw(dataView, options);

Resources