charts/shared/table.js (282 lines of code) (raw):
import { contains, numberFormat, commas } from './toolbelt';
import ColorScale from "./colorscale"
var currentSort = null
var lastSorted = null
var reversed = null
export function propComparator(prop) {
//console.log(prop)
var c, d;
currentSort = (currentSort !== prop) ? prop : null;
return function Comparator(a, b) {
c = (typeof a[prop].sort === "string" && !cannotBeConvertedToNumber(a[prop].sort)) ? parseFloat(a[prop].sort.replace(/[,|$]/g, '')) : a[prop].sort;
d = (typeof b[prop].sort === "string" && !cannotBeConvertedToNumber(b[prop].sort)) ? parseFloat(b[prop].sort.replace(/[,|$]/g, '')) : b[prop].sort;
if (("" + c).substring(0, 5) == "<svg>") {
if (c < d) return (currentSort !== prop) ? 1 : -1;
if (c > d) return (currentSort !== prop) ? -1 : 1;
return 0;
} else {
if (c < d) return (currentSort !== prop) ? -1 : 1;
if (c > d) return (currentSort !== prop) ? 1 : -1;
return 0;
}
}
}
function cannotBeConvertedToNumber(inputString) {
const cleanedString = inputString.replace(/[,|$]/g, '');
const num = Number(cleanedString);
return isNaN(num);
}
export function getMax(array) {
return d3.max(array)
}
export function addCustomCSS(headerRows, rows, enableScroll, enableShowMore) {
var css = "",
head = document.head || document.getElementsByTagName('head')[0],
style = document.createElement('style'),
css = "@media (max-width: 30em) {";
headerRows.map((name, index) => {
css += `.collapse-true table tbody tr td:nth-child(${index + 1}) span.header-prefix::before { content: '${name}: '; font-weight: 600; }`;
});
css += "}";
css += `th {
position: sticky!Important;
top: 0;
}`
if (enableScroll && !enableShowMore && rows > 10) {
css += `table {
text-align: left;
border-collapse: collapse;
position: relative;
}`
css += `#yacht__table__wrapper {
height: 480px;
overflow: auto;
touch-action: auto;
}`
}
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
}
const reorderValues = (data, order) => {
return data.map(row => {
return order.map(key => row[key]);
});
};
export async function colourize(headings, userKey, data, columnOrder) {
const desiredOrder = columnOrder.sort((a, b) => a.index - b.index).map(col => col.column);
const pantone = swatches(data, userKey)
const highlighted = userKey.map(item => item.key)
const formating = userKey.map(item => {
if (item.format) {
console.log(item.format)
if (item.format.includes(",")) {
return item.format.split(",")
} else {
return [ item.format.trim() ]
}
} else {
return []
}
})
const hasDate = formating.map(item => contains(item, 'date'))
const graphics = userKey.map(item => {
return (item.graphics) ? item.graphics : undefined ;
})
const outta = userKey.map(item => {
return (item.outta) ? item.outta : undefined ;
})
const colourizer = (value, index) => (!contains(headings[index], highlighted)) ? false : pantone.find(item => item.name === headings[index]).profile.get(value) ;
//const values = data.map((row) => Object.values(row))
const values = reorderValues(data, desiredOrder);
const getFormat = (index) => {
return (highlighted.indexOf(headings[index]) > -1) ? formating[highlighted.indexOf(headings[index])] : [""]
}
const checkDate = (value, index) => {
return (hasDate[index-1]) ? Math.floor(new Date(value).getTime() / 1000) : value
}
const getGraphics = (index) => {
return (highlighted.indexOf(headings[index]) > -1) ? graphics[highlighted.indexOf(headings[index])] : null
}
const getOutta = (index) => {
return (highlighted.indexOf(headings[index]) > -1) ? outta[highlighted.indexOf(headings[index])] : null
}
return await values.map((row, i) => {
return row.map((value, index) => { return { value : value, sort : checkDate(value, index), format: getFormat(index), color : colourizer(value, index), contrast : setContrast(colourizer(value, index)), graphics: getGraphics(index), outta : getOutta(index) }})
})
}
function hexToRgb(hex) {
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function setContrast(colour) {
if (colour) {
if (colour.indexOf('#') === 0) {
colour = colour.slice(1);
return invertColor(colour)
} else {
let rgb = (colour.includes("#")) ? hexToRgb(colour.trim()) : colour
if (colour.includes('rgb(')) {
let shade = getRGB(colour)
rgb = { r: shade[0], g: shade[1], b: shade[1] }
}
return (rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114) > 186
? '#000000'
: '#FFFFFF';
}
}
return 'black'
}
function invertColor(hex, bw=true) {
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
if (hex.length !== 6) {
throw new Error('Invalid HEX color.');
}
let r = parseInt(hex.slice(0, 2), 16),
g = parseInt(hex.slice(2, 4), 16),
b = parseInt(hex.slice(4, 6), 16);
if (bw) {
return (r * 0.299 + g * 0.587 + b * 0.114) > 186
? '#000000'
: '#FFFFFF';
}
r = (255 - r).toString(16);
g = (255 - g).toString(16);
b = (255 - b).toString(16);
return "#" + padZero(r) + padZero(g) + padZero(b);
}
function getRGB(str){
let match = str.match(/rgba?\((\d{1,3}), ?(\d{1,3}), ?(\d{1,3})\)?(?:, ?(\d(?:\.\d?))\))?/);
return match ? [
+match[1],
+match[2],
+match[3]
] : [];
}
function swatches(data, userKey) {
const swatches = userKey.map((name) => {
var extent = d3.extent(data.map((item) => item[name.key]));
var swatch = {}
swatch.name = name.key
let domain = (name.values && name.values.includes(",")) ? name.values.split(',') :
name.values != "" ? [ name.values ] : [];
domain = (domain[0]=="") ? extent : domain
let colours = (name.colours && name.colours.includes(",")) ? name.colours.split(',') : ['red'];
name.colours != "" ? [ name.colours ] : ['red'];
swatch.profile = new ColorScale({
type: name.scale,
domain: domain,
colors: (colours.length>0) ? colours : ['grey']
})
return swatch
})
return swatches
}
export function createTable(table, data, enableSort) {
let thead = table.createTHead()
let row = thead.insertRow()
for (let key of data) {
let th = document.createElement("th")
th.classList.add("column-header")
let span = document.createElement("SPAN")
let heading = (key.includes('unknown title')) ? '' : key
let text = document.createTextNode(heading)
th.appendChild(span)
span.appendChild(text)
if (enableSort==="TRUE") {
let div = document.createElement("DIV")
div.classList.add("toggle-wrapper")
th.appendChild(div)
}
row.appendChild(th)
}
let body = table.appendChild(document.createElement('tbody'))
body.classList.add("table-body")
}
export function matchArray(array, value) {
return (array.toString().toLowerCase().includes(value.toLowerCase())) ? true : false ;
}
export function styleHeaders(e) {
const headers = document.querySelectorAll(`#yacht__table .column-header`);
const tableEl = document.querySelector(`#yacht__table`);
for (var h = 0; h < headers.length; h++) {
headers[h].className = "column-header";
}
if (lastSorted === e.target && reversed === false) {
e.target.className = "column-header sorted-reversed";
reversed = true;
} else {
e.target.className = "column-header sorted";
reversed = false;
}
if (!hasClass(tableEl, "table-sorted")) {
tableEl.className = "table-sorted";
}
lastSorted = e.target;
}
function hasClass(el, cls) {
if (!el.className) {
return false;
} else {
var newElementClass = ' ' + el.className + ' ';
var newClassName = ' ' + cls + ' ';
return newElementClass.indexOf(newClassName) !== -1;
}
}
export function styleCheck() {
return (this.color && this.format[0] == "shading") ? `style="background-color:${this.color};color:${this.contrast};font-weight:bold;"` :
(!isNaN(this.value)) ? `style=""` : '' ;
}
function removeZero(value) {
return (value==0) ? '' : value
}
export function formatedNumber() {
var value = ""
if (this.format!=undefined && this.value != null) {
let arr = this.format.map(item => item.trim())
/*
Formatting options are
$, nozero, numberFormat, commas, bar, date, textColor, shading, rating
*/
let val = this.value
value += (contains(arr,'$')) ? '$' : '' ;
// value += (contains(arr,'commas')) ? commas(val) : val ;
value += (contains(arr,'numberFormat')) ? numberFormat(val) : (contains(arr,'commas')) ? commas(val) : val ;
value = (contains(arr,'nozero')) ? removeZero(val) : value ;
if (contains(arr,'bar') && this.graphics.max) {
let percentage = 100 / this.graphics.max * this.value
let position = (percentage < 30) ? percentage + 5 : 5 ;
let background = (this.color) ? this.color : this.graphics.colour.get(value)
let contrast = (percentage < 30) ? 'black' : 'white'
value = `<div class="table-bar-chart"><div class="table-bar" style="background-color: ${background}; margin-left: 0%; width: ${percentage}%;"></div><div class="table-bar-label" style="left:${position}%;color:${contrast}">${value}</div></div>`
}
if (contains(arr,'textColor')) {
value = `<span style="color:${ this.color };"><strong>${value}<strong></span>`
}
if (contains(arr,'date')) {
let date = new Date(value);
var options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
value = date.toLocaleDateString("en-AU", options)
}
if (contains(arr,'rating')) {
let outOf = (this.outta) ? this.outta : 10
value = `<span style="font-weight:bold;">${value}</span>/${outOf}`
}
} else {
value = (this.value != null) ? this.value : ""
}
return value
}