dynamically blending colors for the heatmap

fixes #1613
This commit is contained in:
Miodec 2023-03-08 15:44:40 +01:00
parent e1ab1b676c
commit 6a6d475e51
5 changed files with 106 additions and 48 deletions

View file

@ -15,6 +15,7 @@
"chartjs-adapter-date-fns": "2.0.0",
"chartjs-plugin-annotation": "1.4.0",
"chartjs-plugin-trendline": "1.0.2",
"color-blend": "4.0.0",
"crypto-browserify": "3.12.0",
"damerau-levenshtein": "1.0.8",
"date-fns": "2.28.0",
@ -5027,6 +5028,14 @@
"node": ">=0.10.0"
}
},
"node_modules/color-blend": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/color-blend/-/color-blend-4.0.0.tgz",
"integrity": "sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -21733,6 +21742,11 @@
"object-visit": "^1.0.0"
}
},
"color-blend": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/color-blend/-/color-blend-4.0.0.tgz",
"integrity": "sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw=="
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",

View file

@ -64,6 +64,7 @@
"chartjs-adapter-date-fns": "2.0.0",
"chartjs-plugin-annotation": "1.4.0",
"chartjs-plugin-trendline": "1.0.2",
"color-blend": "4.0.0",
"crypto-browserify": "3.12.0",
"damerau-levenshtein": "1.0.8",
"date-fns": "2.28.0",

View file

@ -504,22 +504,9 @@
place-content: center center;
}
.box:nth-child(1) {
background: var(--colorful-error-color);
border-radius: var(--roundness) 0 0 var(--roundness);
}
.box:nth-child(2) {
background: var(--colorful-error-color);
filter: opacity(0.6);
}
.box:nth-child(3) {
background: var(--sub-color);
}
.box:nth-child(4) {
background: var(--main-color);
filter: opacity(0.6);
}
.box:nth-child(5) {
background: var(--main-color);
border-radius: 0 var(--roundness) var(--roundness) 0;
}
}
@ -546,25 +533,8 @@
letter.incorrect.extra {
color: var(--error-extra-color);
}
&.unreached letter {
filter: opacity(0.2);
}
&.heatmap0 letter {
color: var(--colorful-error-color);
}
&.heatmap1 letter {
color: var(--colorful-error-color);
filter: opacity(0.6);
}
&.heatmap2 letter {
color: var(--sub-color);
}
&.heatmap3 letter {
color: var(--main-color);
filter: opacity(0.6);
}
&.heatmap4 letter {
color: var(--main-color);
&.heatmapInherit letter {
color: inherit;
}
}
&.rightToLeftTest {

View file

@ -1011,7 +1011,7 @@ export function toggleResultWords(): void {
}
}
export function applyBurstHeatmap(): void {
export async function applyBurstHeatmap(): Promise<void> {
if (Config.burstHeatmap) {
$("#resultWordsHistory .heatmapLegend").removeClass("hidden");
@ -1033,26 +1033,39 @@ export function applyBurstHeatmap(): void {
adatm.push(Math.abs(median - burst));
});
const step = Misc.mean(adatm);
const themeColors = await ThemeColors.getAll();
const colors = [
themeColors.colorfulError,
Misc.blendTwoHexColors(themeColors.colorfulError, themeColors.text),
themeColors.text,
Misc.blendTwoHexColors(themeColors.main, themeColors.text),
themeColors.main,
];
const unreachedColor = themeColors.sub;
const steps = [
{
val: 0,
class: "heatmap0",
colorId: 0,
},
{
val: median - step * 1.5,
class: "heatmap1",
colorId: 1,
},
{
val: median - step * 0.5,
class: "heatmap2",
colorId: 2,
},
{
val: median + step * 0.5,
class: "heatmap3",
colorId: 3,
},
{
val: median + step * 1.5,
class: "heatmap4",
colorId: 4,
},
];
@ -1074,26 +1087,29 @@ export function applyBurstHeatmap(): void {
});
$("#resultWordsHistory .words .word").each((_, word) => {
let cls = "";
const wordBurstAttr = $(word).attr("burst");
if (wordBurstAttr === undefined) {
cls = "unreached";
$(word).css("color", unreachedColor);
} else {
const wordBurstVal = parseInt(<string>wordBurstAttr);
steps.forEach((step) => {
if (wordBurstVal >= step.val) cls = step.class;
if (wordBurstVal >= step.val) {
$(word).addClass("heatmapInherit");
$(word).css("color", colors[step.colorId]);
}
});
}
$(word).addClass(cls);
});
$("#resultWordsHistory .heatmapLegend .boxes .box").each((index, box) => {
$(box).css("background", colors[index]);
});
} else {
$("#resultWordsHistory .heatmapLegend").addClass("hidden");
$("#resultWordsHistory .words .word").removeClass("heatmap0");
$("#resultWordsHistory .words .word").removeClass("heatmap1");
$("#resultWordsHistory .words .word").removeClass("heatmap2");
$("#resultWordsHistory .words .word").removeClass("heatmap3");
$("#resultWordsHistory .words .word").removeClass("heatmap4");
$("#resultWordsHistory .words .word").removeClass("unreached");
$("#resultWordsHistory .words .word").removeClass("heatmapInherit");
$("#resultWordsHistory .words .word").css("color", "");
$("#resultWordsHistory .heatmapLegend .boxes .box").css("color", "");
}
}

View file

@ -1,4 +1,5 @@
import * as Loader from "../elements/loader";
import { normal as normalBlend } from "color-blend";
async function fetchJson<T>(url: string): Promise<T> {
try {
@ -235,6 +236,62 @@ export async function getContributorsList(): Promise<string[]> {
}
}
export function blendTwoHexColors(color1: string, color2: string): string {
const rgb1 = hexToRgb(color1);
const rgb2 = hexToRgb(color2);
if (rgb1 && rgb2) {
const rgba1 = {
r: rgb1.r,
g: rgb1.g,
b: rgb1.b,
a: 1,
};
const rgba2 = {
r: rgb2.r,
g: rgb2.g,
b: rgb2.b,
a: 0.5,
};
const blended = normalBlend(rgba1, rgba2);
console.log(blended);
return rgbToHex(blended.r, blended.g, blended.b);
} else {
return "#000000";
}
}
function hexToRgb(hex: string):
| {
r: number;
g: number;
b: number;
}
| undefined {
if (hex.length != 4 && hex.length != 7 && !hex.startsWith("#")) {
return undefined;
}
let r: number;
let g: number;
let b: number;
if (hex.length == 4) {
r = ("0x" + hex[1] + hex[1]) as unknown as number;
g = ("0x" + hex[2] + hex[2]) as unknown as number;
b = ("0x" + hex[3] + hex[3]) as unknown as number;
} else if (hex.length == 7) {
r = ("0x" + hex[1] + hex[2]) as unknown as number;
g = ("0x" + hex[3] + hex[4]) as unknown as number;
b = ("0x" + hex[5] + hex[6]) as unknown as number;
} else {
return undefined;
}
return { r, g, b };
}
function rgbToHex(r: number, g: number, b: number): string {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function hexToHSL(hex: string): {
hue: number;
sat: number;