|
@@ -0,0 +1,177 @@
|
|
|
+// Vector Math
|
|
|
+const vectorDot = (u, v) => u.map((x, i) => x * v[i]).reduce((x, y) => x + y);
|
|
|
+const vectorMag = (v) => Math.sqrt(vectorDot(v, v));
|
|
|
+
|
|
|
+// Angle Math
|
|
|
+const rad2deg = 180 / Math.PI;
|
|
|
+
|
|
|
+// Contrast
|
|
|
+const getContrastingTextColor = (hex) => {
|
|
|
+ const { r, g, b } = d3.color(hex);
|
|
|
+ return vectorDot([r, g, b], [0.3, 0.6, 0.1]) >= 128 ? "#222" : "#ddd";
|
|
|
+};
|
|
|
+
|
|
|
+// "Visual Importance"
|
|
|
+const calcImportance = (chroma, lightness, proportion) =>
|
|
|
+ chroma +
|
|
|
+ Math.tanh(100 * (chroma - 0.25)) + // penalty for being <25%
|
|
|
+ Math.tanh(100 * (chroma - 0.4)) + // penalty for being <40%
|
|
|
+ lightness +
|
|
|
+ Math.tanh(100 * (lightness - 0.5)) + // penalty for being <50%
|
|
|
+ proportion +
|
|
|
+ Math.tanh(100 * (proportion - 0.05)) + // penalty for being <5%
|
|
|
+ Math.tanh(100 * (proportion - 0.1)) + // penalty for being <15%
|
|
|
+ Math.tanh(100 * (proportion - 0.15)) + // penalty for being <15%
|
|
|
+ Math.tanh(100 * (proportion - 0.25)) + // penalty for being <25%
|
|
|
+ Math.tanh(100 * (proportion - 0.8)); // penalty for being <50%
|
|
|
+
|
|
|
+// Conversions
|
|
|
+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;
|
|
|
+
|
|
|
+// Pre-computation
|
|
|
+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 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,
|
|
|
+ inverseSize: 1 / size,
|
|
|
+ inertia,
|
|
|
+ mu,
|
|
|
+ nu,
|
|
|
+ muNuAngle,
|
|
|
+ proportion,
|
|
|
+ inverseProportion: 1 / 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
|
|
|
+ ),
|
|
|
+ buildClusterData(
|
|
|
+ ...values.slice(31, 39),
|
|
|
+ size,
|
|
|
+ jab2hue,
|
|
|
+ jab2lit,
|
|
|
+ jab2chroma,
|
|
|
+ jab2hex
|
|
|
+ ),
|
|
|
+ ].filter((c) => c.size !== 0),
|
|
|
+ },
|
|
|
+ rgb: {
|
|
|
+ total: buildClusterData(
|
|
|
+ size,
|
|
|
+ ...values.slice(39, 46),
|
|
|
+ size,
|
|
|
+ rgb2hue,
|
|
|
+ rgb2lit,
|
|
|
+ rgb2chroma,
|
|
|
+ rgb2hex
|
|
|
+ ),
|
|
|
+ clusters: [
|
|
|
+ buildClusterData(
|
|
|
+ ...values.slice(46, 54),
|
|
|
+ size,
|
|
|
+ rgb2hue,
|
|
|
+ rgb2lit,
|
|
|
+ rgb2chroma,
|
|
|
+ rgb2hex
|
|
|
+ ),
|
|
|
+ buildClusterData(
|
|
|
+ ...values.slice(54, 62),
|
|
|
+ size,
|
|
|
+ rgb2hue,
|
|
|
+ rgb2lit,
|
|
|
+ rgb2chroma,
|
|
|
+ rgb2hex
|
|
|
+ ),
|
|
|
+ buildClusterData(
|
|
|
+ ...values.slice(62, 70),
|
|
|
+ size,
|
|
|
+ rgb2hue,
|
|
|
+ rgb2lit,
|
|
|
+ rgb2chroma,
|
|
|
+ rgb2hex
|
|
|
+ ),
|
|
|
+ buildClusterData(
|
|
|
+ ...values.slice(70, 78),
|
|
|
+ size,
|
|
|
+ rgb2hue,
|
|
|
+ rgb2lit,
|
|
|
+ rgb2chroma,
|
|
|
+ rgb2hex
|
|
|
+ ),
|
|
|
+ ].filter((c) => c.size !== 0),
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const pokemonData = databaseV3.map((row) => buildPokemonData(row));
|