mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12: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>
|
Emmanuel Ferdman <https://github.com/emmanuel-ferdman>
|
||||||
Sunong2008 <https://github.com/Sunrongguo2008>
|
Sunong2008 <https://github.com/Sunrongguo2008>
|
||||||
Marvin Kopf <marvinkopf@outlook.com>
|
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>
|
Kevin Nakamura <grinkers@grinkers.net>
|
||||||
Bradley Szoke <bradleyszoke@gmail.com>
|
Bradley Szoke <bradleyszoke@gmail.com>
|
||||||
jcznk <https://github.com/jcznk>
|
jcznk <https://github.com/jcznk>
|
||||||
|
|
|
@ -60,6 +60,7 @@ preferences-full-screen-only = Full screen only
|
||||||
preferences-appearance = Appearance
|
preferences-appearance = Appearance
|
||||||
preferences-general = General
|
preferences-general = General
|
||||||
preferences-style = Style
|
preferences-style = Style
|
||||||
|
preferences-color-blind = Color Blind Mode
|
||||||
preferences-review = Review
|
preferences-review = Review
|
||||||
preferences-answer-keys = Answer keys
|
preferences-answer-keys = Answer keys
|
||||||
preferences-distractions = Distractions
|
preferences-distractions = Distractions
|
||||||
|
|
|
@ -104,6 +104,23 @@
|
||||||
<string>preferences_user_interface</string>
|
<string>preferences_user_interface</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<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">
|
<item row="0" column="1">
|
||||||
<widget class="QComboBox" name="theme"/>
|
<widget class="QComboBox" name="theme"/>
|
||||||
</item>
|
</item>
|
||||||
|
@ -123,23 +140,6 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="themeLabel">
|
<widget class="QLabel" name="themeLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -160,13 +160,26 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0" colspan="2">
|
<item row="4" column="0" colspan="2">
|
||||||
<widget class="QPushButton" name="resetWindowSizes">
|
<widget class="QPushButton" name="resetWindowSizes">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>preferences_reset_window_sizes</string>
|
<string>preferences_reset_window_sizes</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -371,11 +371,24 @@ class Preferences(QDialog):
|
||||||
self.form.styleComboBox.setVisible(not is_win)
|
self.form.styleComboBox.setVisible(not is_win)
|
||||||
qconnect(self.form.resetWindowSizes.clicked, self.on_reset_window_sizes)
|
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_language()
|
||||||
self.setup_video_driver()
|
self.setup_video_driver()
|
||||||
|
|
||||||
self.setupOptions()
|
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:
|
def update_global(self) -> None:
|
||||||
restart_required = False
|
restart_required = False
|
||||||
|
|
||||||
|
|
|
@ -536,6 +536,12 @@ create table if not exists profiles
|
||||||
def setUiScale(self, scale: float) -> None:
|
def setUiScale(self, scale: float) -> None:
|
||||||
self.meta["uiScale"] = scale
|
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:
|
def reduce_motion(self) -> bool:
|
||||||
return self.meta.get("reduce_motion", True)
|
return self.meta.get("reduce_motion", True)
|
||||||
|
|
||||||
|
|
|
@ -136,10 +136,18 @@ class NewDeckStats(QDialog):
|
||||||
_, query = cmd.split(":", 1)
|
_, query = cmd.split(":", 1)
|
||||||
browser = aqt.dialogs.open("Browser", self.mw)
|
browser = aqt.dialogs.open("Browser", self.mw)
|
||||||
browser.search_for(query)
|
browser.search_for(query)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def refresh(self) -> None:
|
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")
|
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
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { colorBlindColors } from "./graph-helpers";
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
import { localizedNumber } from "@tslib/i18n";
|
import { localizedNumber } from "@tslib/i18n";
|
||||||
|
|
||||||
import { type RevlogRange } from "./graph-helpers";
|
import { type RevlogRange } from "./graph-helpers";
|
||||||
import {
|
import {
|
||||||
calculateRetentionPercentageString,
|
calculateRetentionPercentageString,
|
||||||
|
@ -13,6 +13,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
type PeriodTrueRetentionData,
|
type PeriodTrueRetentionData,
|
||||||
type RowData,
|
type RowData,
|
||||||
} from "./true-retention";
|
} from "./true-retention";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
revlogRange: RevlogRange;
|
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 { revlogRange, data }: Props = $props();
|
||||||
|
|
||||||
const rowData: RowData[] = $derived(getRowData(data, revlogRange));
|
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>
|
</script>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
@ -84,11 +100,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
.young {
|
.young {
|
||||||
color: #64c476;
|
color: var(--young-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mature {
|
.mature {
|
||||||
color: #31a354;
|
color: var(--mature-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total {
|
.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
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { colorBlindColors } from "./graph-helpers";
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
import { type RevlogRange } from "./graph-helpers";
|
import { type RevlogRange } from "./graph-helpers";
|
||||||
import {
|
import {
|
||||||
|
@ -15,6 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
type Scope,
|
type Scope,
|
||||||
} from "./true-retention";
|
} from "./true-retention";
|
||||||
import { localizedNumber } from "@tslib/i18n";
|
import { localizedNumber } from "@tslib/i18n";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
revlogRange: RevlogRange;
|
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 { revlogRange, data, scope }: Props = $props();
|
||||||
|
|
||||||
const rowData: RowData[] = $derived(getRowData(data, revlogRange));
|
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>
|
</script>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
@ -71,11 +87,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
.pass {
|
.pass {
|
||||||
color: #3bc464;
|
color: var(--pass-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fail {
|
.fail {
|
||||||
color: #c43b3b;
|
color: var(--fail-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.retention {
|
.retention {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import type { GraphsResponse } from "@generated/anki/stats_pb";
|
import type { GraphsResponse } from "@generated/anki/stats_pb";
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
import { dayLabel } from "@tslib/time";
|
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 { bin, interpolateBlues, min, scaleLinear, scaleSequential, sum } from "d3";
|
||||||
|
|
||||||
import type { SearchDispatch, TableDatum } from "./graph-helpers";
|
import type { SearchDispatch, TableDatum } from "./graph-helpers";
|
||||||
|
@ -80,8 +80,18 @@ export function buildHistogram(
|
||||||
return [null, []];
|
return [null, []];
|
||||||
}
|
}
|
||||||
|
|
||||||
const adjustedRange = scaleLinear().range([0.7, 0.3]);
|
let adjustedRange;
|
||||||
const colourScale = scaleSequential((n) => interpolateBlues(adjustedRange(n)!)).domain([xMax!, xMin!]);
|
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 totalInPeriod = sum(bins, accessor);
|
||||||
const periodDays = Math.abs(xMin!);
|
const periodDays = Math.abs(xMin!);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
axisBottom,
|
axisBottom,
|
||||||
axisLeft,
|
axisLeft,
|
||||||
interpolateRdYlGn,
|
interpolateRdYlGn,
|
||||||
|
interpolateViridis,
|
||||||
pointer,
|
pointer,
|
||||||
scaleBand,
|
scaleBand,
|
||||||
scaleLinear,
|
scaleLinear,
|
||||||
|
@ -20,7 +21,7 @@ import {
|
||||||
sum,
|
sum,
|
||||||
} from "d3";
|
} from "d3";
|
||||||
|
|
||||||
import type { GraphBounds } from "./graph-helpers";
|
import { type GraphBounds } from "./graph-helpers";
|
||||||
import { GraphRange } from "./graph-helpers";
|
import { GraphRange } from "./graph-helpers";
|
||||||
import { setDataAvailable } from "./graph-helpers";
|
import { setDataAvailable } from "./graph-helpers";
|
||||||
import { hideTooltip, showTooltip } from "./tooltip-utils.svelte";
|
import { hideTooltip, showTooltip } from "./tooltip-utils.svelte";
|
||||||
|
@ -162,7 +163,15 @@ export function renderButtons(
|
||||||
.paddingOuter(1)
|
.paddingOuter(1)
|
||||||
.paddingInner(0.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
|
// y scale
|
||||||
const yTickFormat = (n: number): string => localizedNumber(n);
|
const yTickFormat = (n: number): string => localizedNumber(n);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type { CountableTimeInterval } from "d3";
|
||||||
import { timeHour } from "d3";
|
import { timeHour } from "d3";
|
||||||
import {
|
import {
|
||||||
interpolateBlues,
|
interpolateBlues,
|
||||||
|
interpolateMagma,
|
||||||
pointer,
|
pointer,
|
||||||
scaleLinear,
|
scaleLinear,
|
||||||
scaleSequentialSqrt,
|
scaleSequentialSqrt,
|
||||||
|
@ -136,10 +137,21 @@ export function renderCalendar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const data = Array.from(dayMap.values());
|
const data = Array.from(dayMap.values());
|
||||||
const cappedRange = scaleLinear().range([0.2, nightMode ? 0.8 : 1]);
|
const cappedRange = scaleLinear().range([1, 0]);
|
||||||
const blues = scaleSequentialSqrt()
|
|
||||||
|
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])
|
.domain([0, maxCount])
|
||||||
.interpolator((n) => interpolateBlues(cappedRange(n)!));
|
.interpolator((n) => interpolateBlues(cappedRange(n)!));
|
||||||
|
}
|
||||||
|
|
||||||
function tooltipText(d: DayDatum): string {
|
function tooltipText(d: DayDatum): string {
|
||||||
const date = localizedDate(d.date, {
|
const date = localizedDate(d.date, {
|
||||||
|
@ -203,7 +215,7 @@ export function renderCalendar(
|
||||||
})
|
})
|
||||||
.transition()
|
.transition()
|
||||||
.duration(800)
|
.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 {
|
function timeFunctionForDay(firstDayOfWeek: Weekday): CountableTimeInterval {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
sum,
|
sum,
|
||||||
} from "d3";
|
} from "d3";
|
||||||
|
|
||||||
import type { GraphBounds } from "./graph-helpers";
|
import { colorBlindColors, type GraphBounds } from "./graph-helpers";
|
||||||
|
|
||||||
type Count = [string, number, boolean, string];
|
type Count = [string, number, boolean, string];
|
||||||
export interface GraphData {
|
export interface GraphData {
|
||||||
|
@ -31,15 +31,29 @@ export interface GraphData {
|
||||||
totalCards: string;
|
totalCards: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const barColours = [
|
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 */
|
schemeBlues[5][2], /* new */
|
||||||
schemeOranges[5][2], /* learn */
|
schemeOranges[5][2], /* learn */
|
||||||
schemeReds[5][2], /* relearn */
|
schemeReds[5][2], /* relearn */
|
||||||
schemeGreens[5][2], /* young */
|
schemeGreens[5][2], /* young */
|
||||||
schemeGreens[5][3], /* mature */
|
schemeGreens[5][3], /* mature */
|
||||||
"#FFDC41", /* suspended */
|
"#FFDC41", /* suspended */
|
||||||
"grey", /* buried */
|
"#grey", /* buried */
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function countCards(data: GraphsResponse, separateInactive: boolean): Count[] {
|
function countCards(data: GraphsResponse, separateInactive: boolean): Count[] {
|
||||||
const countData = separateInactive ? data.cardCounts!.excludingInactive! : data.cardCounts!.includingInactive!;
|
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 * as tr from "@generated/ftl";
|
||||||
import { localizedNumber } from "@tslib/i18n";
|
import { localizedNumber } from "@tslib/i18n";
|
||||||
import type { Bin, ScaleLinear } from "d3";
|
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 type { SearchDispatch, TableDatum } from "./graph-helpers";
|
||||||
import { getNumericMapBinValue, numericMap } from "./graph-helpers";
|
import { getNumericMapBinValue, numericMap } from "./graph-helpers";
|
||||||
|
@ -84,7 +84,13 @@ export function prepareData(
|
||||||
.thresholds(ticks)(allEases.entries() as any);
|
.thresholds(ticks)(allEases.entries() as any);
|
||||||
const total = sum(bins as any, getNumericMapBinValue);
|
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 {
|
function hoverText(bin: Bin<number, number>, _percent: number): string {
|
||||||
const minPct = Math.floor(bin.x0!);
|
const minPct = Math.floor(bin.x0!);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import * as tr from "@generated/ftl";
|
||||||
import { localizedNumber } from "@tslib/i18n";
|
import { localizedNumber } from "@tslib/i18n";
|
||||||
import { dayLabel } from "@tslib/time";
|
import { dayLabel } from "@tslib/time";
|
||||||
import type { Bin } from "d3";
|
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 type { SearchDispatch, TableDatum } from "./graph-helpers";
|
||||||
import { getNumericMapBinValue, GraphRange, numericMap } 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 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);
|
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 {
|
export function getNumericMapBinValue(d: Bin<Map<number, number>, number>): number {
|
||||||
return sum(d, (d) => d[1]);
|
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,
|
axisRight,
|
||||||
curveBasis,
|
curveBasis,
|
||||||
interpolateBlues,
|
interpolateBlues,
|
||||||
|
interpolateViridis,
|
||||||
pointer,
|
pointer,
|
||||||
scaleBand,
|
scaleBand,
|
||||||
scaleLinear,
|
scaleLinear,
|
||||||
|
@ -83,11 +84,23 @@ export function renderHours(
|
||||||
.classed(oddTickClass, (d: any): boolean => d % 2 != 0)
|
.classed(oddTickClass, (d: any): boolean => d % 2 != 0)
|
||||||
.attr("direction", "ltr");
|
.attr("direction", "ltr");
|
||||||
|
|
||||||
const cappedRange = scaleLinear().range([0.1, 0.8]);
|
let cappedRange;
|
||||||
const colour = scaleSequential((n) => interpolateBlues(cappedRange(n)!)).domain([
|
let colour;
|
||||||
|
const isColorBlindMode = (window as any).colorBlindMode;
|
||||||
|
|
||||||
|
if (isColorBlindMode) {
|
||||||
|
cappedRange = scaleLinear().range([0.0, 1]);
|
||||||
|
colour = scaleSequential((n) => interpolateViridis(cappedRange(n)!)).domain([
|
||||||
0,
|
0,
|
||||||
yMax,
|
yMax,
|
||||||
]);
|
]);
|
||||||
|
} else {
|
||||||
|
cappedRange = scaleLinear().range([0.1, 0.8]);
|
||||||
|
colour = scaleSequential((n) => interpolateBlues(cappedRange(n)!)).domain([
|
||||||
|
0,
|
||||||
|
yMax,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
// y scale
|
// y scale
|
||||||
const yTickFormat = (n: number): string => localizedNumber(n);
|
const yTickFormat = (n: number): string => localizedNumber(n);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import * as tr from "@generated/ftl";
|
||||||
import { localizedNumber } from "@tslib/i18n";
|
import { localizedNumber } from "@tslib/i18n";
|
||||||
import { timeSpan } from "@tslib/time";
|
import { timeSpan } from "@tslib/time";
|
||||||
import type { Bin } from "d3";
|
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 type { SearchDispatch, TableDatum } from "./graph-helpers";
|
||||||
import { numericMap } from "./graph-helpers";
|
import { numericMap } from "./graph-helpers";
|
||||||
|
@ -147,8 +147,16 @@ export function prepareIntervalData(
|
||||||
return [null, []];
|
return [null, []];
|
||||||
}
|
}
|
||||||
|
|
||||||
const adjustedRange = scaleLinear().range([0.7, 0.3]);
|
let adjustedRange;
|
||||||
const colourScale = scaleSequential((n) => interpolateBlues(adjustedRange(n)!)).domain([xMax!, xMin!]);
|
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(
|
function hoverText(
|
||||||
bin: Bin<number, number>,
|
bin: Bin<number, number>,
|
||||||
|
|
|
@ -16,12 +16,15 @@ import {
|
||||||
axisLeft,
|
axisLeft,
|
||||||
axisRight,
|
axisRight,
|
||||||
bin,
|
bin,
|
||||||
|
color,
|
||||||
cumsum,
|
cumsum,
|
||||||
curveBasis,
|
curveBasis,
|
||||||
|
hsl,
|
||||||
interpolateGreens,
|
interpolateGreens,
|
||||||
interpolateOranges,
|
interpolateOranges,
|
||||||
interpolatePurples,
|
interpolatePurples,
|
||||||
interpolateReds,
|
interpolateReds,
|
||||||
|
interpolateRgb,
|
||||||
max,
|
max,
|
||||||
min,
|
min,
|
||||||
pointer,
|
pointer,
|
||||||
|
@ -31,7 +34,7 @@ import {
|
||||||
sum,
|
sum,
|
||||||
} from "d3";
|
} 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 { GraphRange, numericMap, setDataAvailable } from "./graph-helpers";
|
||||||
import { hideTooltip, showTooltip } from "./tooltip-utils.svelte";
|
import { hideTooltip, showTooltip } from "./tooltip-utils.svelte";
|
||||||
|
|
||||||
|
@ -188,18 +191,47 @@ export function renderReviews(
|
||||||
x.domain() as any,
|
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> {
|
function binColor(idx: BinIndex): ScaleSequential<string> {
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
case BinIndex.Mature:
|
case BinIndex.Mature:
|
||||||
return darkerGreens;
|
return colorBlindMode ? colorBlindScales.mature : darkerGreens;
|
||||||
case BinIndex.Young:
|
case BinIndex.Young:
|
||||||
return lighterGreens;
|
return colorBlindMode ? colorBlindScales.young : lighterGreens;
|
||||||
case BinIndex.Learn:
|
case BinIndex.Learn:
|
||||||
return oranges;
|
return colorBlindMode ? colorBlindScales.learn : oranges;
|
||||||
case BinIndex.Relearn:
|
case BinIndex.Relearn:
|
||||||
return reds;
|
return colorBlindMode ? colorBlindScales.relearn : reds;
|
||||||
case BinIndex.Filtered:
|
case BinIndex.Filtered:
|
||||||
return purples;
|
return colorBlindMode ? colorBlindScales.filtered : purples;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue