diff options
author | Miodec <[email protected]> | 2023-03-08 15:44:40 +0100 |
---|---|---|
committer | Miodec <[email protected]> | 2023-03-08 15:44:40 +0100 |
commit | 6a6d475e518ab009f11b1be7613ba01afebca653 (patch) | |
tree | 359548ace87d0dbb0dcd1fcdcda1975a323ada5d | |
parent | e1ab1b676c59512538c302fe347d7ea4e2ef2431 (diff) | |
download | monkeytype-6a6d475e518ab009f11b1be7613ba01afebca653.tar.gz monkeytype-6a6d475e518ab009f11b1be7613ba01afebca653.zip |
dynamically blending colors for the heatmap
fixes #1613
-rw-r--r-- | frontend/package-lock.json | 14 | ||||
-rw-r--r-- | frontend/package.json | 1 | ||||
-rw-r--r-- | frontend/src/styles/test.scss | 34 | ||||
-rw-r--r-- | frontend/src/ts/test/test-ui.ts | 48 | ||||
-rw-r--r-- | frontend/src/ts/utils/misc.ts | 57 |
5 files changed, 106 insertions, 48 deletions
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8a69c3cdc..032c15843 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index 81c4aa847..d965c63d3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index e86a9d43b..5936e82af 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -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 { diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 60a47d8a4..b955deddb 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -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", ""); } } diff --git a/frontend/src/ts/utils/misc.ts b/frontend/src/ts/utils/misc.ts index ad8569417..7b1e2ba00 100644 --- a/frontend/src/ts/utils/misc.ts +++ b/frontend/src/ts/utils/misc.ts @@ -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; |