UNPKG

gradient-color

Version:

Gradient color generator that lives in 21st

215 lines (178 loc) 5.14 kB
import Color from 'color' import { NO_EDN_FRAC, MIN_ARRAY_LENGTH, FRAC_SUM_ERROR, COLOR_NUMBER_ERROR, } from './errors' /** * Generate n colors with given color stops * * @param {Array} colorArray * @param {Number} n number of colors that need to generate * @returns {Array} array of generated colors in rgb mode */ function gradient(colorArray, n) { const isFullOption = checkParam(colorArray, n) // init 2 arrays for algorithm let colorList = [] let fracList = [] // result array for storing data let resultArray = [] // simple array of color string if (!isFullOption) { const frac = 1 / (colorArray.length - 1) colorArray.forEach((colorString, index) => { if (index !== (colorArray.length - 1)) { colorList.push(Color(colorString)) fracList.push(frac) } else { colorList.push(Color(colorString)) } }) } else { colorArray.forEach((obj, index) => { if (index !== (colorArray.length - 1)) { colorList.push(Color(obj.color)) fracList.push(obj.frac) } else { if (obj.color) { // the last item could be like { color: #ffffff } colorList.push(Color(obj.color)) } else { // and it could also be like '#ffffff' colorList.push(Color(obj)) } } }) } const assignList = assignNumbers(fracList, n) resultArray = createGradient(colorList, assignList) // convert colors to string version resultArray = resultArray.map(c => c.rgb().toString()) return resultArray } /** * Explainations: * o -> stop color for gradient * * -> generated color * * o * * * | o * * * * | o * * o -> generated color list in char version * 4 5 4 -> assigned number of colors need to be generated * * The last section, the end color should be considered in the generated colors * * @returns {Array} array of colors in Color(pkg) format, need toString() call */ function createGradient(colorList, assignList) { let result = [] assignList.forEach((num, index) => { const isLastElement = index === assignList.length - 1 const list = [] // get end point color const start = colorList[index] const end = colorList[index + 1] // if last element, end color should be in the list, // so the num = num - 1 if (isLastElement) { num = num - 1 } const deltaR = (end.red() - start.red()) / num const deltaG = (end.green() - start.green()) / num const deltaB = (end.blue() - start.blue()) / num // generate num colors for (let i = 0; i < num; i++) { const R = start.red() + i * deltaR const G = start.green() + i * deltaG const B = start.blue() + i * deltaB list.push(Color.rgb(R, G, B)) } // if last element, end this list with the last color if (isLastElement) { list.push(end) } result = result.concat(list) }) return result } /** * Calculate and optimize the number of each color period * * Sometimes frac * N might be a fraction * So we use this algorithm: * * 1. Split the number into 2 parts, each part fits in an array: * [2, 4, 1, 5] -> int array * [0.2, 0.5, 0.9, 0.3] -> decimal array * * The left number should be: * left = N - sum(intArray) * * 2. Sort the decimal array from large to small, assign left to * the corresponding element in intArray one by one * until left === 0 * * 3. There goes your final array! * * @returns {Array} array of optimized color numbers */ function assignNumbers(fracList, n) { const intArray = [] const decimalArray = [] // assign int part fracList.forEach((frac, index) => { const real = frac * n const intPart = Math.floor(real) const decimalPart = real - intPart intArray.push(intPart) decimalArray.push({ value: decimalPart, index: index }) }) // how many left ? const left = n - intArray.reduce((a, b) => a + b, 0) // sort O -> o decimalArray.sort((a, b) => b.value - a.value) // assign the left number regard to the decimal part's value // until nothing left for (let i = 0; i < left; i++) { const targetIndex = decimalArray[i].index intArray[targetIndex] = intArray[targetIndex] + 1 } return intArray } /** * Check param format and throw some errors */ function checkParam(array, n) { // Seriously? Anyone this dumb? if (array.length < 2) { throw MIN_ARRAY_LENGTH } // Read the documentation OMG! Of course no frac at the end! if (array[array.length - 1].frac) { throw NO_EDN_FRAC } // You need to see a doctor, like, right now if (n <= array.length) { throw COLOR_NUMBER_ERROR } // if full option mode, sum should be 1 if (typeof array[0] !== 'string') { const fracSum = array .slice(0, array.length - 1) .reduce((a, b) => a + b.frac, 0) if (fracSum < 0.99) { throw FRAC_SUM_ERROR } } let result if (typeof array[0] === 'string') { result = false } else { result = true } return result } export default gradient