mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Apply gradient effect to forgetting curve (#3604)
* Add gradient color for forgetting curve * Add desiredRetention prop for CardInfo * update CONTRIBUTORS * Formatting * Tweak range of gradient * Tweak: salmon -> tomato * Get desired retention of the card from backend * Add a reference line for desired retention * Fix: Corrected the steel blue's height & Hide desired retention line when yMin is higher than desiredRetentionY * Add y axis title * Show desired retention in the tooltip * I18n: improve translation and vertical text display * Revert rotatation&writing-mode of vertical title * Tweak font-size of y axis title * Fix: delete old desired retention line when changing duration * Update ftl/core/card-stats.ftl --------- Co-authored-by: Damien Elmes <dae@users.noreply.github.com>
This commit is contained in:
parent
3b99ae4b91
commit
cee04bf20c
7 changed files with 70 additions and 4 deletions
|
@ -197,6 +197,7 @@ twwn <github.com/twwn>
|
|||
Cy Pokhrel <cy@cy7.sh>
|
||||
Park Hyunwoo <phu54321@naver.com>
|
||||
Tomas Fabrizio Orsi <torsi@fi.uba.ar>
|
||||
Dongjin Ouyang <1113117424@qq.com>
|
||||
Sawan Sunar <sawansunar24072002@gmail.com>
|
||||
hideo aoyama <https://github.com/boukendesho>
|
||||
Ross Brown <rbrownwsws@googlemail.com>
|
||||
|
|
|
@ -35,6 +35,8 @@ card-stats-fsrs-forgetting-curve-first-week = First Week
|
|||
card-stats-fsrs-forgetting-curve-first-month = First Month
|
||||
card-stats-fsrs-forgetting-curve-first-year = First Year
|
||||
card-stats-fsrs-forgetting-curve-all-time = All Time
|
||||
card-stats-fsrs-forgetting-curve-probability-of-recalling = Probability of Recall
|
||||
card-stats-fsrs-forgetting-curve-desired-retention = Desired Retention
|
||||
|
||||
## Window Titles
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ message CardStatsResponse {
|
|||
string custom_data = 20;
|
||||
string preset = 21;
|
||||
optional string original_deck = 22;
|
||||
optional float desired_retention = 23;
|
||||
}
|
||||
|
||||
message GraphsRequest {
|
||||
|
|
|
@ -80,6 +80,7 @@ impl Collection {
|
|||
} else {
|
||||
None
|
||||
},
|
||||
desired_retention: card.desired_retention,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
export let stats: CardStatsResponse | null = null;
|
||||
export let showRevlog: boolean = true;
|
||||
export let fsrsEnabled: boolean = stats?.memoryState != null;
|
||||
export let desiredRetention: number = stats?.desiredRetention ?? 0.9;
|
||||
</script>
|
||||
|
||||
<Container breakpoint="md" --gutter-inline="1rem" --gutter-block="0.5rem">
|
||||
|
@ -31,7 +32,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
{/if}
|
||||
{#if fsrsEnabled}
|
||||
<Row>
|
||||
<ForgettingCurve revlog={stats.revlog} />
|
||||
<ForgettingCurve revlog={stats.revlog} {desiredRetention} />
|
||||
</Row>
|
||||
{/if}
|
||||
{:else}
|
||||
|
|
|
@ -20,6 +20,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import HoverColumns from "../graphs/HoverColumns.svelte";
|
||||
|
||||
export let revlog: RevlogEntry[];
|
||||
export let desiredRetention: number;
|
||||
let svg = null as HTMLElement | SVGElement | null;
|
||||
const bounds = defaultGraphBounds();
|
||||
const title = tr.cardStatsFsrsForgettingCurveTitle();
|
||||
|
@ -35,7 +36,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
const timeRange = writable(defaultTimeRange);
|
||||
|
||||
$: renderForgettingCurve(filteredRevlog, $timeRange, svg as SVGElement, bounds);
|
||||
$: renderForgettingCurve(
|
||||
filteredRevlog,
|
||||
$timeRange,
|
||||
svg as SVGElement,
|
||||
bounds,
|
||||
desiredRetention,
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="forgetting-curve">
|
||||
|
|
|
@ -161,6 +161,7 @@ export function renderForgettingCurve(
|
|||
timeRange: TimeRange,
|
||||
svgElem: SVGElement,
|
||||
bounds: GraphBounds,
|
||||
desiredRetention: number,
|
||||
) {
|
||||
const svg = select(svgElem);
|
||||
const trans = svg.transition().duration(600) as any;
|
||||
|
@ -204,18 +205,63 @@ export function renderForgettingCurve(
|
|||
.call((selection) => selection.transition(trans).call(axisLeft(y).tickSizeOuter(0)))
|
||||
.attr("direction", "ltr");
|
||||
|
||||
svg.select(".y-ticks .y-axis-title").remove();
|
||||
svg.select(".y-ticks")
|
||||
.append("text")
|
||||
.attr("class", "y-axis-title")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", 0 - bounds.marginLeft)
|
||||
.attr("x", 0 - (bounds.height / 2))
|
||||
.attr("font-size", "1rem")
|
||||
.attr("dy", "1.1em")
|
||||
.attr("fill", "currentColor")
|
||||
.style("text-anchor", "middle")
|
||||
.text(`${tr.cardStatsFsrsForgettingCurveProbabilityOfRecalling()}(%)`);
|
||||
|
||||
const lineGenerator = line<DataPoint>()
|
||||
.x((d) => x(d.date))
|
||||
.y((d) => y(d.retrievability));
|
||||
|
||||
// gradient color
|
||||
const desiredRetentionY = desiredRetention * 100;
|
||||
svg.append("linearGradient")
|
||||
.attr("id", "line-gradient")
|
||||
.attr("gradientUnits", "userSpaceOnUse")
|
||||
.attr("x1", 0)
|
||||
.attr("y1", y(0))
|
||||
.attr("x2", 0)
|
||||
.attr("y2", y(100))
|
||||
.selectAll("stop")
|
||||
.data([
|
||||
{ offset: "0%", color: "tomato" },
|
||||
{ offset: `${desiredRetentionY}%`, color: "steelblue" },
|
||||
{ offset: "100%", color: "green" },
|
||||
])
|
||||
.enter().append("stop")
|
||||
.attr("offset", d => d.offset)
|
||||
.attr("stop-color", d => d.color);
|
||||
|
||||
svg.append("path")
|
||||
.datum(data)
|
||||
.attr("class", "forgetting-curve-line")
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "steelblue")
|
||||
.attr("stroke", "url(#line-gradient)")
|
||||
.attr("stroke-width", 1.5)
|
||||
.attr("d", lineGenerator);
|
||||
|
||||
svg.select(".desired-retention-line").remove();
|
||||
if (desiredRetentionY > yMin) {
|
||||
svg.append("line")
|
||||
.attr("class", "desired-retention-line")
|
||||
.attr("x1", bounds.marginLeft)
|
||||
.attr("x2", bounds.width - bounds.marginRight)
|
||||
.attr("y1", y(desiredRetentionY))
|
||||
.attr("y2", y(desiredRetentionY))
|
||||
.attr("stroke", "steelblue")
|
||||
.attr("stroke-dasharray", "4 4")
|
||||
.attr("stroke-width", 1.2);
|
||||
}
|
||||
|
||||
const focusLine = svg.append("line")
|
||||
.attr("class", "focus-line")
|
||||
.attr("y1", bounds.marginTop)
|
||||
|
@ -248,11 +294,18 @@ export function renderForgettingCurve(
|
|||
.attr("fill", "transparent")
|
||||
.on("mousemove", (event: MouseEvent, d: DataPoint) => {
|
||||
const [x1, y1] = pointer(event, document.body);
|
||||
const [_, y2] = pointer(event, svg.node());
|
||||
|
||||
const lineY = y(desiredRetentionY);
|
||||
focusLine.attr("x1", x(d.date) - 1).attr("x2", x(d.date) + 1).style(
|
||||
"opacity",
|
||||
1,
|
||||
);
|
||||
showTooltip(tooltipText(d), x1, y1);
|
||||
let text = tooltipText(d);
|
||||
if (y2 >= lineY - 10 && y2 <= lineY + 10) {
|
||||
text += `<br>${tr.cardStatsFsrsForgettingCurveDesiredRetention()}: ${desiredRetention.toFixed(2)}`;
|
||||
}
|
||||
showTooltip(text, x1, y1);
|
||||
})
|
||||
.on("mouseout", () => {
|
||||
focusLine.style("opacity", 0);
|
||||
|
|
Loading…
Reference in a new issue