aboutsummaryrefslogtreecommitdiff
path: root/src/util.ts
diff options
context:
space:
mode:
authorTerry Truong <terry06890@gmail.com>2022-06-24 19:19:12 +1000
committerTerry Truong <terry06890@gmail.com>2022-06-24 19:19:12 +1000
commit45bca977157084e11aaa2554ca3a2a403cedcfdb (patch)
treec99abbb4c7c478cd3341dfc48853245e27e7ce65 /src/util.ts
parentbce4ab3facf63f70a0dd3cefa1f8c82453dba2d4 (diff)
Move utility functions into util.ts
Diffstat (limited to 'src/util.ts')
-rw-r--r--src/util.ts135
1 files changed, 135 insertions, 0 deletions
diff --git a/src/util.ts b/src/util.ts
new file mode 100644
index 0000000..139e668
--- /dev/null
+++ b/src/util.ts
@@ -0,0 +1,135 @@
+/*
+ * General utility functions
+ */
+
+export type Breakpoint = 'sm' | 'md' | 'lg'; // These represent screen sizes
+export function getBreakpoint(): Breakpoint {
+ let w = window.innerWidth;
+ if (w < 768){
+ return 'sm';
+ } else if (w < 1024){
+ return 'md';
+ } else {
+ return 'lg';
+ }
+}
+
+// Returns [0 ... len]
+export function range(len: number): number[] {
+ return [...Array(len).keys()];
+}
+// Returns sum of array values
+export function arraySum(array: number[]): number {
+ return array.reduce((x,y) => x+y);
+}
+// Returns an array of increasing evenly-spaced numbers from 'start' to 'end' with size 'size'
+export function linspace(start: number, end: number, size: number): number[] {
+ let step = (end - start) / (size - 1);
+ let ar = [];
+ for (let i = 0; i < size; i++){
+ ar.push(start + step * i);
+ }
+ return ar;
+}
+// Returns array copy with vals clipped to within [min,max], redistributing to compensate
+// Returns null on failure
+export function limitVals(arr: number[], min: number, max: number): number[] | null {
+ let vals = [...arr];
+ let clipped = new Array(vals.length).fill(false);
+ let owedChg = 0; // Stores total change made after clipping values
+ while (true){
+ // Clip values
+ for (let i = 0; i < vals.length; i++){
+ if (clipped[i]){
+ continue;
+ }
+ if (vals[i] < min){
+ owedChg += vals[i] - min;
+ vals[i] = min;
+ clipped[i] = true;
+ } else if (vals[i] > max){
+ owedChg += vals[i] - max;
+ vals[i] = max;
+ clipped[i] = true;
+ }
+ }
+ if (Math.abs(owedChg) < Number.EPSILON){
+ return vals;
+ }
+ // Compensate for changes made
+ let indicesToUpdate = (owedChg > 0) ?
+ range(vals.length).filter(idx => vals[idx] < max) :
+ range(vals.length).filter(idx => vals[idx] > min);
+ if (indicesToUpdate.length == 0){
+ return null;
+ }
+ for (let i of indicesToUpdate){
+ vals[i] += owedChg / indicesToUpdate.length;
+ }
+ owedChg = 0;
+ }
+}
+// Usable to iterate through possible int arrays with ascending values in the range 0 to maxLen-1, starting with [0]
+ // eg: With maxLen 3, updates [0] to [0,1], then to [0,2], then [0,1,2]
+// Returns false when there is no next array
+export function updateAscSeq(seq: number[], maxLen: number): boolean {
+ // Try increasing last element, then preceding elements, then extending the array
+ let i = seq.length - 1;
+ while (true){
+ if (i > 0 && seq[i] < (maxLen - 1) - (seq.length - 1 - i)){
+ seq[i]++;
+ return true;
+ } else if (i > 0){
+ i--;
+ } else {
+ if (seq.length < maxLen){
+ seq.push(0);
+ seq.splice(0, seq.length, ...range(seq.length));
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+}
+// Given a non-empty array of non-negative weights, returns an array index chosen with weighted pseudorandomness
+// Returns null if array contains all zeros
+export function randWeightedChoice(weights: number[]): number | null {
+ let thresholds = Array(weights.length);
+ let sum = 0;
+ for (let i = 0; i < weights.length; i++){
+ sum += weights[i];
+ thresholds[i] = sum;
+ }
+ let rand = Math.random();
+ for (let i = 0; i < weights.length; i++){
+ if (rand <= thresholds[i] / sum){
+ return i;
+ }
+ }
+ return null;
+}
+// Returns a string with words first-letter capitalised
+export function capitalizeWords(str: string){
+ str = str.replace(/\b\w/g, x => x.toUpperCase()); // '\b' matches word boundary, '\w' is like [a-zA-Z0-9_]
+ str = str.replace(/(\w)'S/, '$1\'s'); // Avoid cases like "traveler's tree" -> "Traveler'S Tree"
+ return str;
+}
+// Dynamically obtains scroll bar width
+// From stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript
+export function getScrollBarWidth(){
+ // Create hidden outer div
+ let outer = document.createElement('div');
+ outer.style.visibility = 'hidden';
+ outer.style.overflow = 'scroll';
+ document.body.appendChild(outer);
+ // Create inner div
+ let inner = document.createElement('div');
+ outer.appendChild(inner);
+ // Get width difference
+ let scrollBarWidth = outer.offsetWidth - inner.offsetWidth;
+ // Remove temporary divs
+ outer.parentNode!.removeChild(outer);
+ //
+ return scrollBarWidth;
+}