Skip to content

Instantly share code, notes, and snippets.

@cddouglass
Created September 24, 2023 03:21
Show Gist options
  • Save cddouglass/98febc5535bf5d941ce2f3bc5662a467 to your computer and use it in GitHub Desktop.
Save cddouglass/98febc5535bf5d941ce2f3bc5662a467 to your computer and use it in GitHub Desktop.
let settings = input.config({
title: 'Create bar chart in Markdown',
description: 'This script will create a bar chart from your data and output it in Markdown',
items: [
input.config.table('table', {
label: 'Table'
}),
input.config.view('view', {
parentTable: 'table',
label: 'View'
}),
input.config.field('label', {
parentTable: 'table',
label: 'Label field',
}),
input.config.field('value', {
parentTable: 'table',
label: 'Value field',
}),
],
});
let barChart = function(title, bars, options) {
options = {
...{
'character': '█',
'totalNumberOfBlocks': 30,
'emptyBlock': '   ',
'sortOnValue': false, // ASC or DESC
'displayPercentages': true
},
...options || {}
};
let sumOfAllValues = bars.reduce((a, b) => a + b.value, 0);
let sort = function(bars, direction) {
if (direction === 'ASC') {
bars.sort((a, b) => (a.value > b.value) ? 1 : -1);
} else if (direction === 'DESC') {
bars.sort((a, b) => (a.value > b.value) ? -1 : 1);
}
return bars;
};
let formatValue = function(value) {
if (options.displayPercentages) {
value = Math.round(value / sumOfAllValues * 100) + '%';
}
return value;
};
sort(bars, options.sortOnValue)
return markdownTable([
['', title, ''],
...bars.map((bar) => {
let fullNumberOfblocks = Math.ceil(options.totalNumberOfBlocks * (bar.value / sumOfAllValues));
let emptyNumberOfBlocks = Math.floor(options.totalNumberOfBlocks - fullNumberOfblocks);
return ['**' + bar.label + '** |', options.character.repeat(fullNumberOfblocks) + options.emptyBlock.repeat(emptyNumberOfBlocks), ' | **' + formatValue(bar.value) + '**'];
})
], {
align: ['r', 'l', 'l']
});
};
async function generateChart() {
let result = await settings.view.selectRecordsAsync();
let bars = [];
result.records.forEach(function(record) {
bars.push({
'label': record.getCellValueAsString(settings.label.id),
'value': record.getCellValue(settings.value.id),
});
});
output.markdown(barChart('test', bars));
}
await generateChart();
/**
* Code to generate markdown table from array.
* Grabbed from https://github.com/wooorm/markdown-table/blob/main/index.js
*/
function markdownTable(table, options) {
const settings = options || {}
const align = (settings.align || []).concat()
const stringLength = settings.stringLength || defaultStringLength
/** @type {number[]} Character codes as symbols for alignment per column. */
const alignments = []
let rowIndex = -1
/** @type {string[][]} Cells per row. */
const cellMatrix = []
/** @type {number[][]} Sizes of each cell per row. */
const sizeMatrix = []
/** @type {number[]} */
const longestCellByColumn = []
let mostCellsPerRow = 0
/** @type {number} */
let columnIndex
/** @type {string[]} Cells of current row */
let row
/** @type {number[]} Sizes of current row */
let sizes
/** @type {number} Sizes of current cell */
let size
/** @type {string} Current cell */
let cell
/** @type {string[]} Chunks of current line. */
let line
/** @type {string} */
let before
/** @type {string} */
let after
/** @type {number} */
let code
// This is a superfluous loop if we don’t align delimiters, but otherwise we’d
// do superfluous work when aligning, so optimize for aligning.
while (++rowIndex < table.length) {
columnIndex = -1
row = []
sizes = []
if (table[rowIndex].length > mostCellsPerRow) {
mostCellsPerRow = table[rowIndex].length
}
while (++columnIndex < table[rowIndex].length) {
cell = serialize(table[rowIndex][columnIndex])
if (settings.alignDelimiters !== false) {
size = stringLength(cell)
sizes[columnIndex] = size
if (
longestCellByColumn[columnIndex] === undefined ||
size > longestCellByColumn[columnIndex]
) {
longestCellByColumn[columnIndex] = size
}
}
row.push(cell)
}
cellMatrix[rowIndex] = row
sizeMatrix[rowIndex] = sizes
}
// Figure out which alignments to use.
columnIndex = -1
if (typeof align === 'object' && 'length' in align) {
while (++columnIndex < mostCellsPerRow) {
alignments[columnIndex] = toAlignment(align[columnIndex])
}
} else {
code = toAlignment(align)
while (++columnIndex < mostCellsPerRow) {
alignments[columnIndex] = code
}
}
// Inject the alignment row.
columnIndex = -1
row = []
sizes = []
while (++columnIndex < mostCellsPerRow) {
code = alignments[columnIndex]
before = ''
after = ''
if (code === 99 /* `c` */ ) {
before = ':'
after = ':'
} else if (code === 108 /* `l` */ ) {
before = ':'
} else if (code === 114 /* `r` */ ) {
after = ':'
}
// There *must* be at least one hyphen-minus in each alignment cell.
size =
settings.alignDelimiters === false ?
1 :
Math.max(
1,
longestCellByColumn[columnIndex] - before.length - after.length
)
cell = before + '-'.repeat(size) + after
if (settings.alignDelimiters !== false) {
size = before.length + size + after.length
if (size > longestCellByColumn[columnIndex]) {
longestCellByColumn[columnIndex] = size
}
sizes[columnIndex] = size
}
row[columnIndex] = cell
}
// Inject the alignment row.
cellMatrix.splice(1, 0, row)
sizeMatrix.splice(1, 0, sizes)
rowIndex = -1
/** @type {string[]} */
const lines = []
while (++rowIndex < cellMatrix.length) {
row = cellMatrix[rowIndex]
sizes = sizeMatrix[rowIndex]
columnIndex = -1
line = []
while (++columnIndex < mostCellsPerRow) {
cell = row[columnIndex] || ''
before = ''
after = ''
if (settings.alignDelimiters !== false) {
size = longestCellByColumn[columnIndex] - (sizes[columnIndex] || 0)
code = alignments[columnIndex]
if (code === 114 /* `r` */ ) {
before = ' '.repeat(size)
} else if (code === 99 /* `c` */ ) {
if (size % 2) {
before = ' '.repeat(size / 2 + 0.5)
after = ' '.repeat(size / 2 - 0.5)
} else {
before = ' '.repeat(size / 2)
after = before
}
} else {
after = ' '.repeat(size)
}
}
if (settings.delimiterStart !== false && !columnIndex) {
line.push('|')
}
if (
settings.padding !== false &&
// Don’t add the opening space if we’re not aligning and the cell is
// empty: there will be a closing space.
!(settings.alignDelimiters === false && cell === '') &&
(settings.delimiterStart !== false || columnIndex)
) {
line.push(' ')
}
if (settings.alignDelimiters !== false) {
line.push(before)
}
line.push(cell)
if (settings.alignDelimiters !== false) {
line.push(after)
}
if (settings.padding !== false) {
line.push(' ')
}
if (
settings.delimiterEnd !== false ||
columnIndex !== mostCellsPerRow - 1
) {
line.push('|')
}
}
lines.push(
settings.delimiterEnd === false ?
line.join('').replace(/ +$/, '') :
line.join('')
)
}
return lines.join('\n')
}
/**
* @param {string|null|undefined} [value]
* @returns {string}
*/
function serialize(value) {
return value === null || value === undefined ? '' : String(value)
}
/**
* @param {string} value
* @returns {number}
*/
function defaultStringLength(value) {
return value.length
}
/**
* @param {string|null|undefined} value
* @returns {number}
*/
function toAlignment(value) {
const code = typeof value === 'string' ? value.charCodeAt(0) : 0
return code === 67 /* `C` */ || code === 99 /* `c` */ ?
99 /* `c` */ :
code === 76 /* `L` */ || code === 108 /* `l` */ ?
108 /* `l` */ :
code === 82 /* `R` */ || code === 114 /* `r` */ ?
114 /* `r` */ :
0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment