mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Merge 482cab50d4
into 3890e12c9e
This commit is contained in:
commit
e541006989
18 changed files with 270 additions and 63 deletions
|
@ -234,6 +234,10 @@ Spiritual Father <https://github.com/spiritualfather>
|
|||
Emmanuel Ferdman <https://github.com/emmanuel-ferdman>
|
||||
Sunong2008 <https://github.com/Sunrongguo2008>
|
||||
Marvin Kopf <marvinkopf@outlook.com>
|
||||
David Brenn <davidbrenn@t-online.de>
|
||||
Felix Kühne <flxkhn0602@gmail.com>
|
||||
Matthis Ehrhardt <matthis-ehrhardt@gmx.de>
|
||||
Billy Julian Lesmana <hunter140797@gmail.com>
|
||||
Kevin Nakamura <grinkers@grinkers.net>
|
||||
Bradley Szoke <bradleyszoke@gmail.com>
|
||||
jcznk <https://github.com/jcznk>
|
||||
|
|
|
@ -60,6 +60,7 @@ preferences-full-screen-only = Full screen only
|
|||
preferences-appearance = Appearance
|
||||
preferences-general = General
|
||||
preferences-style = Style
|
||||
preferences-color-blind = Color Blind Mode
|
||||
preferences-review = Review
|
||||
preferences-answer-keys = Answer keys
|
||||
preferences-distractions = Distractions
|
||||
|
|
|
@ -104,6 +104,23 @@
|
|||
<string>preferences_user_interface</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="styleLabel">
|
||||
<property name="text">
|
||||
<string>preferences_style</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>styleComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="styleComboBox">
|
||||
<property name="currentText">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="theme"/>
|
||||
</item>
|
||||
|
@ -123,23 +140,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="styleComboBox">
|
||||
<property name="currentText">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="styleLabel">
|
||||
<property name="text">
|
||||
<string>preferences_style</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>styleComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="themeLabel">
|
||||
<property name="text">
|
||||
|
@ -160,13 +160,26 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="resetWindowSizes">
|
||||
<property name="text">
|
||||
<string>preferences_reset_window_sizes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="color_blind">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>preferences_color_blind</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -371,11 +371,24 @@ class Preferences(QDialog):
|
|||
self.form.styleComboBox.setVisible(not is_win)
|
||||
qconnect(self.form.resetWindowSizes.clicked, self.on_reset_window_sizes)
|
||||
|
||||
self.form.color_blind.setChecked(self.mw.pm.color_blind())
|
||||
qconnect(
|
||||
self.form.color_blind.stateChanged, self.on_color_blind_checkbox_changed
|
||||
)
|
||||
|
||||
self.setup_language()
|
||||
self.setup_video_driver()
|
||||
|
||||
self.setupOptions()
|
||||
|
||||
def on_color_blind_checkbox_changed(self, state: int) -> None:
|
||||
if state == 2:
|
||||
# checkbox is checked
|
||||
self.mw.pm.set_color_blind(True)
|
||||
else:
|
||||
# checkbox is unchecked
|
||||
self.mw.pm.set_color_blind(False)
|
||||
|
||||
def update_global(self) -> None:
|
||||
restart_required = False
|
||||
|
||||
|
|
|
@ -536,6 +536,12 @@ create table if not exists profiles
|
|||
def setUiScale(self, scale: float) -> None:
|
||||
self.meta["uiScale"] = scale
|
||||
|
||||
def set_color_blind(self, value: bool) -> None:
|
||||
self.meta["color_blind"] = value
|
||||
|
||||
def color_blind(self) -> bool:
|
||||
return self.meta.get("color_blind", False)
|
||||
|
||||
def reduce_motion(self) -> bool:
|
||||
return self.meta.get("reduce_motion", True)
|
||||
|
||||
|
|
|
@ -136,10 +136,18 @@ class NewDeckStats(QDialog):
|
|||
_, query = cmd.split(":", 1)
|
||||
browser = aqt.dialogs.open("Browser", self.mw)
|
||||
browser.search_for(query)
|
||||
|
||||
return False
|
||||
|
||||
def refresh(self) -> None:
|
||||
def on_load_finished(success: bool) -> None:
|
||||
if success:
|
||||
is_color_blind = self.mw.pm.color_blind()
|
||||
js_code = f"window.colorBlindMode = {str(is_color_blind).lower()};"
|
||||
self.form.web.eval(js_code)
|
||||
# Disconnect after running once to avoid multiple triggers
|
||||
self.form.web.page().loadFinished.disconnect(on_load_finished)
|
||||
|
||||
self.form.web.page().loadFinished.connect(on_load_finished)
|
||||
self.form.web.load_sveltekit_page("graphs")
|
||||
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ Copyright: Ankitects Pty Ltd and contributors
|
|||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { colorBlindColors } from "./graph-helpers";
|
||||
import * as tr from "@generated/ftl";
|
||||
import { localizedNumber } from "@tslib/i18n";
|
||||
|
||||
import { type RevlogRange } from "./graph-helpers";
|
||||
import {
|
||||
calculateRetentionPercentageString,
|
||||
|
@ -13,6 +13,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
type PeriodTrueRetentionData,
|
||||
type RowData,
|
||||
} from "./true-retention";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
interface Props {
|
||||
revlogRange: RevlogRange;
|
||||
|
@ -22,6 +23,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
const { revlogRange, data }: Props = $props();
|
||||
|
||||
const rowData: RowData[] = $derived(getRowData(data, revlogRange));
|
||||
|
||||
// Default (non-colorblind) colors
|
||||
let youngColor = "#64c476";
|
||||
let matureColor = "#31a354";
|
||||
|
||||
onMount(() => {
|
||||
const isColorBlindMode = (window as any).colorBlindMode;
|
||||
if (isColorBlindMode) {
|
||||
youngColor = colorBlindColors.young;
|
||||
matureColor = colorBlindColors.mature;
|
||||
}
|
||||
// Set globally so scoped SCSS can use them
|
||||
document.documentElement.style.setProperty("--young-color", youngColor);
|
||||
document.documentElement.style.setProperty("--mature-color", matureColor);
|
||||
});
|
||||
</script>
|
||||
|
||||
<table>
|
||||
|
@ -84,11 +100,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
|
||||
.young {
|
||||
color: #64c476;
|
||||
color: var(--young-color);
|
||||
}
|
||||
|
||||
.mature {
|
||||
color: #31a354;
|
||||
color: var(--mature-color);
|
||||
}
|
||||
|
||||
.total {
|
||||
|
|
|
@ -3,6 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors
|
|||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { colorBlindColors } from "./graph-helpers";
|
||||
import * as tr from "@generated/ftl";
|
||||
import { type RevlogRange } from "./graph-helpers";
|
||||
import {
|
||||
|
@ -15,6 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
type Scope,
|
||||
} from "./true-retention";
|
||||
import { localizedNumber } from "@tslib/i18n";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
interface Props {
|
||||
revlogRange: RevlogRange;
|
||||
|
@ -25,6 +27,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
const { revlogRange, data, scope }: Props = $props();
|
||||
|
||||
const rowData: RowData[] = $derived(getRowData(data, revlogRange));
|
||||
|
||||
let passColor = "#3bc464";
|
||||
let failColor = "#c43b3b";
|
||||
|
||||
onMount(() => {
|
||||
const isColorBlindMode = (window as any).colorBlindMode;
|
||||
if (isColorBlindMode) {
|
||||
passColor = colorBlindColors.relearn;
|
||||
failColor = colorBlindColors.filtered;
|
||||
}
|
||||
// Apply them to document root so SCSS can see them
|
||||
document.documentElement.style.setProperty("--pass-color", passColor);
|
||||
document.documentElement.style.setProperty("--fail-color", failColor);
|
||||
});
|
||||
</script>
|
||||
|
||||
<table>
|
||||
|
@ -71,11 +87,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
|
||||
.pass {
|
||||
color: #3bc464;
|
||||
color: var(--pass-color);
|
||||
}
|
||||
|
||||
.fail {
|
||||
color: #c43b3b;
|
||||
color: var(--fail-color);
|
||||
}
|
||||
|
||||
.retention {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import type { GraphsResponse } from "@generated/anki/stats_pb";
|
||||
import * as tr from "@generated/ftl";
|
||||
import { dayLabel } from "@tslib/time";
|
||||
import type { Bin } from "d3";
|
||||
import { type Bin, interpolateViridis } from "d3";
|
||||
import { bin, interpolateBlues, min, scaleLinear, scaleSequential, sum } from "d3";
|
||||
|
||||
import type { SearchDispatch, TableDatum } from "./graph-helpers";
|
||||
|
@ -80,8 +80,18 @@ export function buildHistogram(
|
|||
return [null, []];
|
||||
}
|
||||
|
||||
const adjustedRange = scaleLinear().range([0.7, 0.3]);
|
||||
const colourScale = scaleSequential((n) => interpolateBlues(adjustedRange(n)!)).domain([xMax!, xMin!]);
|
||||
let adjustedRange;
|
||||
let colourScale;
|
||||
const isColourBlindMode = (window as any).colorBlindMode;
|
||||
|
||||
// Changing color based on mode
|
||||
if (isColourBlindMode) {
|
||||
adjustedRange = scaleLinear().range([0.3, 0.7]);
|
||||
colourScale = scaleSequential((n) => interpolateViridis(adjustedRange(n)!)).domain([xMax!, xMin!]);
|
||||
} else {
|
||||
adjustedRange = scaleLinear().range([0.7, 0.3]);
|
||||
colourScale = scaleSequential((n) => interpolateBlues(adjustedRange(n)!)).domain([xMax!, xMin!]);
|
||||
}
|
||||
|
||||
const totalInPeriod = sum(bins, accessor);
|
||||
const periodDays = Math.abs(xMin!);
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
axisBottom,
|
||||
axisLeft,
|
||||
interpolateRdYlGn,
|
||||
interpolateViridis,
|
||||
pointer,
|
||||
scaleBand,
|
||||
scaleLinear,
|
||||
|
@ -20,7 +21,7 @@ import {
|
|||
sum,
|
||||
} from "d3";
|
||||
|
||||
import type { GraphBounds } from "./graph-helpers";
|
||||
import { type GraphBounds } from "./graph-helpers";
|
||||
import { GraphRange } from "./graph-helpers";
|
||||
import { setDataAvailable } from "./graph-helpers";
|
||||
import { hideTooltip, showTooltip } from "./tooltip-utils.svelte";
|
||||
|
@ -162,7 +163,15 @@ export function renderButtons(
|
|||
.paddingOuter(1)
|
||||
.paddingInner(0.1);
|
||||
|
||||
const colour = scaleSequential(interpolateRdYlGn).domain([1, 4]);
|
||||
const isColourBlindMode = (window as any).colorBlindMode;
|
||||
let colour;
|
||||
|
||||
// Changing color based on mode
|
||||
if (isColourBlindMode) {
|
||||
colour = scaleSequential(interpolateViridis).domain([1, 4]);
|
||||
} else {
|
||||
colour = scaleSequential(interpolateRdYlGn).domain([1, 4]);
|
||||
}
|
||||
|
||||
// y scale
|
||||
const yTickFormat = (n: number): string => localizedNumber(n);
|
||||
|
|
|
@ -10,6 +10,7 @@ import type { CountableTimeInterval } from "d3";
|
|||
import { timeHour } from "d3";
|
||||
import {
|
||||
interpolateBlues,
|
||||
interpolateMagma,
|
||||
pointer,
|
||||
scaleLinear,
|
||||
scaleSequentialSqrt,
|
||||
|
@ -136,10 +137,21 @@ export function renderCalendar(
|
|||
}
|
||||
}
|
||||
const data = Array.from(dayMap.values());
|
||||
const cappedRange = scaleLinear().range([0.2, nightMode ? 0.8 : 1]);
|
||||
const blues = scaleSequentialSqrt()
|
||||
.domain([0, maxCount])
|
||||
.interpolator((n) => interpolateBlues(cappedRange(n)!));
|
||||
const cappedRange = scaleLinear().range([1, 0]);
|
||||
|
||||
const isColorBlindMode = (window as any).colorBlindMode;
|
||||
|
||||
let gradient;
|
||||
|
||||
if (isColorBlindMode) {
|
||||
gradient = scaleSequentialSqrt()
|
||||
.domain([0, maxCount])
|
||||
.interpolator((n) => interpolateMagma(cappedRange(n)!));
|
||||
} else {
|
||||
gradient = scaleSequentialSqrt()
|
||||
.domain([0, maxCount])
|
||||
.interpolator((n) => interpolateBlues(cappedRange(n)!));
|
||||
}
|
||||
|
||||
function tooltipText(d: DayDatum): string {
|
||||
const date = localizedDate(d.date, {
|
||||
|
@ -203,7 +215,7 @@ export function renderCalendar(
|
|||
})
|
||||
.transition()
|
||||
.duration(800)
|
||||
.attr("fill", (d: DayDatum) => (d.count === 0 ? emptyColour : blues(d.count)!));
|
||||
.attr("fill", (d: DayDatum) => (d.count === 0 ? emptyColour : gradient(d.count)!));
|
||||
}
|
||||
|
||||
function timeFunctionForDay(firstDayOfWeek: Weekday): CountableTimeInterval {
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
sum,
|
||||
} from "d3";
|
||||
|
||||
import type { GraphBounds } from "./graph-helpers";
|
||||
import { colorBlindColors, type GraphBounds } from "./graph-helpers";
|
||||
|
||||
type Count = [string, number, boolean, string];
|
||||
export interface GraphData {
|
||||
|
@ -31,15 +31,29 @@ export interface GraphData {
|
|||
totalCards: string;
|
||||
}
|
||||
|
||||
const barColours = [
|
||||
schemeBlues[5][2], /* new */
|
||||
schemeOranges[5][2], /* learn */
|
||||
schemeReds[5][2], /* relearn */
|
||||
schemeGreens[5][2], /* young */
|
||||
schemeGreens[5][3], /* mature */
|
||||
"#FFDC41", /* suspended */
|
||||
"grey", /* buried */
|
||||
];
|
||||
let barColours;
|
||||
|
||||
if ((window as any).colorBlindMode) {
|
||||
barColours = [
|
||||
colorBlindColors.new, /* new */
|
||||
colorBlindColors.learn, /* learn */
|
||||
colorBlindColors.relearn, /* relearn */
|
||||
colorBlindColors.young, /* young */
|
||||
colorBlindColors.mature, /* mature */
|
||||
colorBlindColors.suspended, /* suspended */
|
||||
colorBlindColors.buried, /* buried */
|
||||
];
|
||||
} else {
|
||||
barColours = [
|
||||
schemeBlues[5][2], /* new */
|
||||
schemeOranges[5][2], /* learn */
|
||||
schemeReds[5][2], /* relearn */
|
||||
schemeGreens[5][2], /* young */
|
||||
schemeGreens[5][3], /* mature */
|
||||
"#FFDC41", /* suspended */
|
||||
"#grey", /* buried */
|
||||
];
|
||||
}
|
||||
|
||||
function countCards(data: GraphsResponse, separateInactive: boolean): Count[] {
|
||||
const countData = separateInactive ? data.cardCounts!.excludingInactive! : data.cardCounts!.includingInactive!;
|
||||
|
|
|
@ -9,7 +9,7 @@ import type { GraphsResponse } from "@generated/anki/stats_pb";
|
|||
import * as tr from "@generated/ftl";
|
||||
import { localizedNumber } from "@tslib/i18n";
|
||||
import type { Bin, ScaleLinear } from "d3";
|
||||
import { bin, extent, interpolateRdYlGn, scaleLinear, scaleSequential, sum } from "d3";
|
||||
import { bin, extent, interpolateRdYlGn, interpolateViridis, scaleLinear, scaleSequential, sum } from "d3";
|
||||
|
||||
import type { SearchDispatch, TableDatum } from "./graph-helpers";
|
||||
import { getNumericMapBinValue, numericMap } from "./graph-helpers";
|
||||
|
@ -84,7 +84,13 @@ export function prepareData(
|
|||
.thresholds(ticks)(allEases.entries() as any);
|
||||
const total = sum(bins as any, getNumericMapBinValue);
|
||||
|
||||
const colourScale = scaleSequential(interpolateRdYlGn).domain([xMin, 300]);
|
||||
let colourScale;
|
||||
|
||||
if ((window as any).colorBlindMode) {
|
||||
colourScale = scaleSequential(interpolateViridis).domain([xMin, 300]);
|
||||
} else {
|
||||
colourScale = scaleSequential(interpolateRdYlGn).domain([xMin, 300]);
|
||||
}
|
||||
|
||||
function hoverText(bin: Bin<number, number>, _percent: number): string {
|
||||
const minPct = Math.floor(bin.x0!);
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as tr from "@generated/ftl";
|
|||
import { localizedNumber } from "@tslib/i18n";
|
||||
import { dayLabel } from "@tslib/time";
|
||||
import type { Bin } from "d3";
|
||||
import { bin, extent, interpolateGreens, scaleLinear, scaleSequential, sum } from "d3";
|
||||
import { bin, extent, interpolateGreens, interpolateViridis, scaleLinear, scaleSequential, sum } from "d3";
|
||||
|
||||
import type { SearchDispatch, TableDatum } from "./graph-helpers";
|
||||
import { getNumericMapBinValue, GraphRange, numericMap } from "./graph-helpers";
|
||||
|
@ -108,8 +108,20 @@ export function buildHistogram(
|
|||
}
|
||||
|
||||
const xTickFormat = (n: number): string => localizedNumber(n);
|
||||
const adjustedRange = scaleLinear().range([0.7, 0.3]);
|
||||
const colourScale = scaleSequential((n) => interpolateGreens(adjustedRange(n)!)).domain([xMin!, xMax!]);
|
||||
|
||||
let adjustedRange = scaleLinear().range([0.0, 1]);
|
||||
|
||||
const isColorBlindMode = (window as any).colorBlindMode;
|
||||
|
||||
let colourScale;
|
||||
|
||||
if (isColorBlindMode) {
|
||||
colourScale = scaleSequential((n) => interpolateViridis(adjustedRange(n)!)).domain([xMin!, xMax!]);
|
||||
adjustedRange = scaleLinear().range([0.0, 1]);
|
||||
} else {
|
||||
colourScale = scaleSequential((n) => interpolateGreens(adjustedRange(n)!)).domain([xMin!, xMax!]);
|
||||
adjustedRange = scaleLinear().range([0.7, 0.3]);
|
||||
}
|
||||
|
||||
const total = sum(bins as any, getNumericMapBinValue);
|
||||
|
||||
|
|
|
@ -105,3 +105,17 @@ export function numericMap<T>(obj: { [k: string]: T }): Map<number, T> {
|
|||
export function getNumericMapBinValue(d: Bin<Map<number, number>, number>): number {
|
||||
return sum(d, (d) => d[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorblind-friendly colors from https://davidmathlogic.com/colorblind/
|
||||
*/
|
||||
export const colorBlindColors = {
|
||||
new: "#88CCEE",
|
||||
learn: "#44AA99",
|
||||
relearn: "#117733",
|
||||
young: "#CC6677",
|
||||
mature: "#882255",
|
||||
suspended: "#DDCC77",
|
||||
buried: "#332288",
|
||||
filtered: "#AA4499",
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
axisRight,
|
||||
curveBasis,
|
||||
interpolateBlues,
|
||||
interpolateViridis,
|
||||
pointer,
|
||||
scaleBand,
|
||||
scaleLinear,
|
||||
|
@ -83,11 +84,23 @@ export function renderHours(
|
|||
.classed(oddTickClass, (d: any): boolean => d % 2 != 0)
|
||||
.attr("direction", "ltr");
|
||||
|
||||
const cappedRange = scaleLinear().range([0.1, 0.8]);
|
||||
const colour = scaleSequential((n) => interpolateBlues(cappedRange(n)!)).domain([
|
||||
0,
|
||||
yMax,
|
||||
]);
|
||||
let cappedRange;
|
||||
let colour;
|
||||
const isColorBlindMode = (window as any).colorBlindMode;
|
||||
|
||||
if (isColorBlindMode) {
|
||||
cappedRange = scaleLinear().range([0.0, 1]);
|
||||
colour = scaleSequential((n) => interpolateViridis(cappedRange(n)!)).domain([
|
||||
0,
|
||||
yMax,
|
||||
]);
|
||||
} else {
|
||||
cappedRange = scaleLinear().range([0.1, 0.8]);
|
||||
colour = scaleSequential((n) => interpolateBlues(cappedRange(n)!)).domain([
|
||||
0,
|
||||
yMax,
|
||||
]);
|
||||
}
|
||||
|
||||
// y scale
|
||||
const yTickFormat = (n: number): string => localizedNumber(n);
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as tr from "@generated/ftl";
|
|||
import { localizedNumber } from "@tslib/i18n";
|
||||
import { timeSpan } from "@tslib/time";
|
||||
import type { Bin } from "d3";
|
||||
import { bin, extent, interpolateBlues, quantile, scaleLinear, scaleSequential, sum } from "d3";
|
||||
import { bin, extent, interpolateBlues, interpolateViridis, quantile, scaleLinear, scaleSequential, sum } from "d3";
|
||||
|
||||
import type { SearchDispatch, TableDatum } from "./graph-helpers";
|
||||
import { numericMap } from "./graph-helpers";
|
||||
|
@ -147,8 +147,16 @@ export function prepareIntervalData(
|
|||
return [null, []];
|
||||
}
|
||||
|
||||
const adjustedRange = scaleLinear().range([0.7, 0.3]);
|
||||
const colourScale = scaleSequential((n) => interpolateBlues(adjustedRange(n)!)).domain([xMax!, xMin!]);
|
||||
let adjustedRange;
|
||||
let colourScale;
|
||||
|
||||
if ((window as any).colorBlindMode) {
|
||||
adjustedRange = scaleLinear().range([0.3, 0.7]);
|
||||
colourScale = scaleSequential((n) => interpolateViridis(adjustedRange(n)!)).domain([xMax!, xMin!]);
|
||||
} else {
|
||||
adjustedRange = scaleLinear().range([0.7, 0.3]);
|
||||
colourScale = scaleSequential((n) => interpolateBlues(adjustedRange(n)!)).domain([xMax!, xMin!]);
|
||||
}
|
||||
|
||||
function hoverText(
|
||||
bin: Bin<number, number>,
|
||||
|
|
|
@ -16,12 +16,15 @@ import {
|
|||
axisLeft,
|
||||
axisRight,
|
||||
bin,
|
||||
color,
|
||||
cumsum,
|
||||
curveBasis,
|
||||
hsl,
|
||||
interpolateGreens,
|
||||
interpolateOranges,
|
||||
interpolatePurples,
|
||||
interpolateReds,
|
||||
interpolateRgb,
|
||||
max,
|
||||
min,
|
||||
pointer,
|
||||
|
@ -31,7 +34,7 @@ import {
|
|||
sum,
|
||||
} from "d3";
|
||||
|
||||
import type { GraphBounds, TableDatum } from "./graph-helpers";
|
||||
import { colorBlindColors, type GraphBounds, type TableDatum } from "./graph-helpers";
|
||||
import { GraphRange, numericMap, setDataAvailable } from "./graph-helpers";
|
||||
import { hideTooltip, showTooltip } from "./tooltip-utils.svelte";
|
||||
|
||||
|
@ -188,18 +191,47 @@ export function renderReviews(
|
|||
x.domain() as any,
|
||||
);
|
||||
|
||||
const colorBlindMode = (window as any).colorBlindMode;
|
||||
|
||||
function makeColorBlindGradient(baseHex: string, satAdjust = 0.02, lightAdjust = 0.02) {
|
||||
const base = color(baseHex);
|
||||
if (!base) { throw new Error(`Invalid color: ${baseHex}`); }
|
||||
|
||||
const lighter = hsl(base);
|
||||
lighter.s = Math.min(1, lighter.s + satAdjust);
|
||||
lighter.l = Math.min(1, lighter.l + lightAdjust);
|
||||
|
||||
const darker = hsl(base);
|
||||
darker.s = Math.max(0, darker.s - satAdjust);
|
||||
darker.l = Math.max(0, darker.l - lightAdjust);
|
||||
|
||||
return scaleSequential(interpolateRgb(darker.toString(), lighter.toString()));
|
||||
}
|
||||
|
||||
const colorBlindScales = {
|
||||
mature: makeColorBlindGradient(colorBlindColors.mature),
|
||||
learn: makeColorBlindGradient(colorBlindColors.learn),
|
||||
relearn: makeColorBlindGradient(colorBlindColors.relearn),
|
||||
young: makeColorBlindGradient(colorBlindColors.young),
|
||||
suspended: makeColorBlindGradient(colorBlindColors.suspended),
|
||||
buried: makeColorBlindGradient(colorBlindColors.buried),
|
||||
filtered: makeColorBlindGradient(colorBlindColors.filtered),
|
||||
};
|
||||
|
||||
Object.values(colorBlindScales).forEach(scale => scale.domain(x.domain() as any));
|
||||
|
||||
function binColor(idx: BinIndex): ScaleSequential<string> {
|
||||
switch (idx) {
|
||||
case BinIndex.Mature:
|
||||
return darkerGreens;
|
||||
return colorBlindMode ? colorBlindScales.mature : darkerGreens;
|
||||
case BinIndex.Young:
|
||||
return lighterGreens;
|
||||
return colorBlindMode ? colorBlindScales.young : lighterGreens;
|
||||
case BinIndex.Learn:
|
||||
return oranges;
|
||||
return colorBlindMode ? colorBlindScales.learn : oranges;
|
||||
case BinIndex.Relearn:
|
||||
return reds;
|
||||
return colorBlindMode ? colorBlindScales.relearn : reds;
|
||||
case BinIndex.Filtered:
|
||||
return purples;
|
||||
return colorBlindMode ? colorBlindScales.filtered : purples;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue