Home Reference Source

src/utilities.js

/**
 * @file Utilities for flag generation.
 */

import seedrandom from 'seedrandom';
import settings from './settings';
import tinycolor from 'tinycolor2';

/**
 * Utilities for flag generation.
 *
 * @module flag-generator/utilities
 */

/**
 * Generate the seed from a seed string.
 *
 * @function
 * @example
 * // Returns 0.8112494706388412
 * generateSeed('test');
 * @param {string} seedString - A string on which to run the prng function.
 */
export const generateSeed = (seedString) => {
  const seed = typeof seedString !== 'undefined' ? seedrandom(seedString, {state: true}) : seedrandom(Math.floor(Math.random() * 1e9).toString(), {state: true});

  // settings.seed = seed();
  return seed();
}
/**
 * Generates a seed multiplier converting the characters of the provided string to numbers name.
 *
 * @function
 * @example
 * // Returns 0.5363260631705106
 * generateSeedMultiplier('Border');
 * @param {string} str - A string value to turn into charcodes.
 * @returns {number} A multiplier to be used with seed multiplication based decision making.
 * @todo I've read somewhere that parseFloat is dangerous without radx, I should figure out if that's true.
 * @todo: handle the case where settings.seed may not be set.
 */
export const generateSeedMultiplier = (seed, str) => {
  // Make sure the string is a string.
  str = str.toString();
  // Add each character to an array.
  let multiplier = '';
  const strArray = str.toString().split('');
  // Loop over the array of characters, turn them into a number, and add them to a string.
  // I could probably do this more simply with array.map() or reduce() somehow.
  for (let i = 0; i < strArray.length; i++) {
      multiplier = multiplier + strArray[i].charCodeAt(0);
  }
  // Turn our multiplier string into an actual number.
  multiplier = parseFloat('.' + multiplier * seed);
  return multiplier;
}

/**
 * Modify a seed by multiplying it by a value.
 *
 * @function
 * @example
 * // Returns 0.05483563
 * modifySeed(0.2602354456965794, 0.2107154537);
 * @param {number} seed - The seed number on which generation depends.
 * @param {number} seedMultiplier - The multiplier used to alter the seed to generate values.
 * @returns {number} - The product of seed and seedMultiplier.
 */
// export const modifySeed = (seed, seedMultiplier) => seed * seedMultiplier
export const modifySeed = (seed, seedMultiplier) => {
  // console.log('seed to mod', seed);
  // console.log('multi', seedMultiplier);
  return seed * seedMultiplier
}


/**
 * Gets the last digit from a number.
 *
 * @function
 * @example
 * // Returns 7
 * getLastDigit(.2357);
 * @param {number} n - A number.
 * @returns {number} The last digit of n.
 */
export const getLastDigit = (n) => +(n.toString().substr(-1));


/**
 * Generate a random hex color based on the seed.
 *
 * @function
 * @example
 * // returns #2b32ad
 * randomHex(0.8112494706388412);
 * @param {number} seed - The prng generated seed value under 1.
 * @param {number} seedModifier - A number used to perform modifications to the seed.
 * @returns {string} The pseudo-randomly generated hexadecimal color value.
 */
export const randomHex = (seed, seedModifier) => {
    return '#'+((modifySeed(seed, seedModifier) % 1) * 0xFFFFFF << 0).toString(16).padStart(6, '0');
};

/**
 * Turn a hexadecimal color string into an rgb() object.
 *
 * @function
 * @example
 * // returns {r: 255, g: 255, b: 255}
 * hexToRgb('#ffffff');
 * @param {string} hex - A hexadecimal color string.
 * @returns {{r: number, b: number, g: number}} - An object containing RGB keys.
 */
export function hexToRgb(hex) {
    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;
}

/**
 * Convert a hexadecimal color string to an rgb() string.
 *
 * @function
 * @example
 * // Returns rgb(255, 255, 255)
 * convertHex('#ffffff');
 * @param {string} hex - A hexadecimal color string.
 * @returns {string} - An RGB color string.
 */
export const convertHex = (hex) => {
    const rgbObject = hexToRgb(hex);
    return 'rgb(' + rgbObject.r + ', ' + rgbObject.g + ', ' + rgbObject.b + ')';
}
/**
 * A color object.
 *
 * @typedef {object} ColorObject
 * @property {object} analogous - An object containing 6 hexadecimal color strings analogous to the originally generated color.
 * @property {string} color - A hexadecimal color value generated from the seed and a seedMultiplier.
 * @property {string} complement - A complement hexadecimal color value to the color string.
 * @property {object} monochromatic - An object containing 6 hexadecimal color strings describing the monochrome range of the original generated color.
 * @property {object} splitComplement - An object containing the originally generated color and two hexadecimal color string complements.
 * @property {object} tetrad - An object containing the originally generated color's tetrad.
 * @property {object} triad - An object containing the originally generated color's triad.
 */
/**
 * Generates a color object from a modified seed using the tinycolor2 library.
 *
 * @function
 * @example
 * // Generates a ColorObject with a primary color key of #575109;
 * const colorObject = generateColor(80857473, 0.06556305047688744);
 * @param {number} seedMultiplier - The multiplier used to alter the seed to generate values.
 * @param {number} seed - The seed number on which generation depends.
 * @returns {...ColorObject} A {@link module:flag-generator/utilities~ColorObject}.
 */
export const generateColor = (hex, seedMultiplier = 80857473, seed) => {
    let generated = randomHex(seed, seedMultiplier);
    if (/^#/.test(hex)) {
      generated = hex;
    }
    const color = {
        color: tinycolor(generated).toHexString(),
        complement: tinycolor(generated).complement().toHexString(),
        splitComplement: tinycolor(generated).splitcomplement().map((sc) => sc.toHexString()),
        triad: tinycolor(generated).triad().map((tr) => tr.toHexString()),
        tetrad: tinycolor(generated).tetrad().map((te) => te.toHexString()),
        analogous: tinycolor(generated).analogous().map((an) => an.toHexString()),
        monochromatic: tinycolor(generated).monochromatic().map((mo) => mo.toHexString()),
    }
    return color;
}

/**
 * Shuffles an array predictably using the Fisher Yates shuffle algorithm.
 *
 * @function
 * @example
 * // Returns [1, 2, 3, 5, 4]
 * const originalArray = [1, 2, 3, 4, 5]
 * const shuffledArray = pseudoShuffle(originalArray, 0.7243609520746538);
 * @param {Array} arr - The array to be shuffled.
 * @param {number} seed - The pseudorandom string used to predictabbly apply the algorithm.
 * @returns {Array} The arr parameter, but shuffled.
 */
export const pseudoShuffle = (arr, seed) => {
    let m = arr.length;
    let t, i

    while (m) {
        i = Math.floor(seed * m--)
        t = arr[m]
        arr[m] = arr[i]
        arr[i] = t
    }
    return arr;
};
/**
 * Takes an aspect ratio string, e.g.: 1:2, 2:3, 3:5, and returns an object with height (h) and width (w) keys.
 *
 * @function
 * @example
 * // Returns
 * // {
 * //     h: 3,
 * //     w: 5,
 * // }
 * const aspectObject = processAspectRatioString('3:5');
 * @param {string} aspect - A string describing a ratio.
 * @returns {object} An aspect ratio object containing height (h) and width (w) keys from the aspect ratio string.
 * @todo This function could be shortened significantly with a map callback.
 */
export const processAspectRatioString = (aspect) => {
  aspect = aspect.toString().split(':');
  let aspectObj = {};
  for (let i = 0; i < aspect.length; i++) {
    switch(i) {
      case 0:
        aspectObj.h = +(aspect[i]);
        break;
      case 1:
        aspectObj.w = +(aspect[i]);
        break;
      default:
        throw new Error('Could not process given aspect ratio string.');
      }
  }
  return aspectObj;
}

/**
 * Turn an aspect object into more realistic dimensions.
 *
 * @function
 * @example
 * // Returns
 * // {
 * //   h: 300,
 * //   w: 500,
 * // }
 * const dimensions = setDimensionsFromAspectObject({h: 3, w: 5});
 * @param {object} aspect - An aspect object.
 * @param {number} multiplier - A number to multiply the aspect keys by. Defaults to 100.
 * @returns {object} An object containing height (h) and width (w) keys representing dimensions.
 */
export const setDimensionsFromAspectObject = (aspect, multiplier = 100) => ({
  h: aspect.h * multiplier,
  w: aspect.w * multiplier
});

/**
 * Creates a canvas element and append it to the document body.
 *
 * @function
 * @example
 * // Creates and appends the canvas with an id of 'myCanvas'.
 * generateCanvas(document, 'myCanvas');
 * @param {object} document - A document object.
 * @param {string} id - The Id to give the created canvas element.
 * @param {object} dimensions - An object containing height(h) and width (w) keys.
 */
export const generateCanvas = (document, parentID, canvasID, dimensions) => {
  const parent = document.getElementById(parentID);
  const canvas = document.createElement('canvas');
  canvas.setAttribute('id', canvasID);
  canvas.setAttribute('style', 'border: 1px solid black;');
  canvas.setAttribute('width', dimensions.w);
  canvas.setAttribute('height', dimensions.h);
  parent.appendChild(canvas);
}
/**
 * Generates a number used for decision making based on the seed and a given multiplier.
 *
 * @function
 * @example
 * // Returns 1
 * generateCount(5, 0.8112494706388412, 0.6568774660735);
 * @param {number} limit - The maximum value of the count to be returned. Values above 9 are ignored.
 * @param {number} seedMultiplier - The multiplier used to alter the seed to generate values.
 * @param {number} seed - The seed number on which generation depends.
 * @returns {number} A single digit number between 0 and 9.
 */
export const generateCount = (limit, seedMultiplier, seed) => {
    const modifiedSeed = modifySeed(seed, seedMultiplier);
    let generated = getLastDigit(modifiedSeed);
    if (generated > limit || generated === 0) {
        generated = 1;
    }
    return generated;
}

export const findGreaterNumber = (a, b) => {
  let greater;
  greater = a >= b ? a : b;
  return greater;
}

// /**
//  *
//  * @returns {SVGSVGElement}
//  */
// const makeSVG = () => {
//     console.log('Make an SVG.');
//
//     let svg1 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
//     svg1.setAttribute("width", "400");
//     svg1.setAttribute("height", "400");
//     svg1.setAttribute("viewBox", "0 0 800 300");

//     let cir1 = document.createElementNS("http://www.w3.org/2000/svg", "circle");
//     cir1.setAttribute("cx", 50);
//     cir1.setAttribute("cy", 50);
//     cir1.setAttribute("r", 50);
//
//     svg1.appendChild(cir1);
//
//     return svg1;
//
// }
//
//
//