const jab2hex = jab => d3.jab(...jab).formatHex(); const rgb2hex = rgb => d3.rgb(...rgb).formatHex(); const jab2hue = jab => d3.jch(d3.jab(...jab)).h || 0; const rgb2hue = rgb => d3.hsl(d3.rgb(...rgb)).h || 0; const jab2lit = ([j]) => j / 100; const rgb2lit = rgb => d3.hsl(d3.rgb(...rgb)).l || 0; const jab2chroma = jab => d3.jch(d3.jab(...jab)).C / 100; const rgb2chroma = rgb => d3.jch(d3.rgb(...rgb)).C / 100; const buildVectorData = (vector, toHue, toLightness, toChroma, toHex) => { const sqMag = vectorDot(vector, vector); const mag = Math.sqrt(sqMag); const unit = vector.map(c => c / mag); const hue = toHue(vector); const lightness = toLightness(vector); const chroma = toChroma(vector); const hex = toHex(vector); return { vector, sqMag, mag, unit, hue, lightness, chroma, hex }; }; const calcImportance = (chroma, lightness, proportion) => ( // scale the final result by 100 just for clarity 100 // ramp the proportion value such that // proportion > 85% => basically guaranteed to be most important, gets bonus // proportion < 15% => no way to be most important, goes negative // otherwise => depend approximately on sqrt(proportion) * ( Math.tanh(100 * (proportion - 0.85)) + Math.tanh(100 * (proportion - 0.15)) + Math.sqrt(proportion) ) // consider the chroma based on the sqrt of its part of chroma + lightness * Math.sqrt(chroma / (chroma + lightness)) // ramp the lightness value such that // lightness >> 50% => scale by 3 // lightness << 50% => scale by 1 // otherwise => slightly steep ramp * (2 + Math.tanh(10 * (lightness - 0.5))) ); const buildClusterData = (size, inertia, mu1, mu2, mu3, nu1, nu2, nu3, totalSize, toHue, toLightness, toChroma, toHex) => { const mu = buildVectorData([mu1, mu2, mu3], toHue, toLightness, toChroma, toHex); const nu = [nu1, nu2, nu3]; const muNuAngle = rad2deg * Math.acos(vectorDot(mu.unit, nu) / vectorMag(nu)); const proportion = size / totalSize; const importance = calcImportance(mu.chroma, mu.lightness, proportion); return { size, inertia, mu, nu, muNuAngle, proportion, importance, }; }; const buildPokemonData = ([name, size, ...values]) => ({ name, jab: { total: buildClusterData(size, ...values.slice(0, 7), size, jab2hue, jab2lit, jab2chroma, jab2hex), clusters: [ buildClusterData(...values.slice(7, 15), size, jab2hue, jab2lit, jab2chroma, jab2hex), buildClusterData(...values.slice(15, 23), size, jab2hue, jab2lit, jab2chroma, jab2hex), buildClusterData(...values.slice(23, 31), size, jab2hue, jab2lit, jab2chroma, jab2hex), ], }, rgb: { total: buildClusterData(size, ...values.slice(31, 38), size, rgb2hue, rgb2lit, rgb2chroma, rgb2hex), clusters: [ buildClusterData(...values.slice(38, 46), size, rgb2hue, rgb2lit, rgb2chroma, rgb2hex), buildClusterData(...values.slice(46, 54), size, rgb2hue, rgb2lit, rgb2chroma, rgb2hex), buildClusterData(...values.slice(54, 62), size, rgb2hue, rgb2lit, rgb2chroma, rgb2hex), ], }, }); const pokemonData = databaseV2.map(row => buildPokemonData(row));