mirror of
https://github.com/ankitects/anki.git
synced 2026-01-05 18:13:56 -05:00
Merge 98f12bebed into 8f2144534b
This commit is contained in:
commit
715fbb92c5
5 changed files with 69 additions and 13 deletions
|
|
@ -513,9 +513,8 @@ deck-config-save-options-to-preset-confirm = Overwrite the options in your curre
|
|||
# to show the total number of cards that can be recalled or retrieved on a
|
||||
# specific date.
|
||||
deck-config-fsrs-simulator-radio-memorized = Memorized
|
||||
deck-config-fsrs-simulator-radio-ratio = Time / Memorized Ratio
|
||||
# $time here is pre-formatted e.g. "10 Seconds"
|
||||
deck-config-fsrs-simulator-ratio-tooltip = { $time } per memorized card
|
||||
deck-config-fsrs-simulator-radio-ratio2 = Memorized / Time Ratio
|
||||
deck-config-fsrs-simulator-ratio-tooltip2 = { $time } memorized cards per hour
|
||||
|
||||
## Messages related to the FSRS scheduler’s health check. The health check determines whether the correlation between FSRS predictions and your memory is good or bad. It can be optionally triggered as part of the "Optimize" function.
|
||||
|
||||
|
|
@ -536,6 +535,9 @@ deck-config-fsrs-good-fit = Health Check:
|
|||
|
||||
## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future.
|
||||
|
||||
deck-config-fsrs-simulator-radio-ratio = Time / Memorized Ratio
|
||||
# $time here is pre-formatted e.g. "10 Seconds"
|
||||
deck-config-fsrs-simulator-ratio-tooltip = { $time } per memorized card
|
||||
deck-config-unable-to-determine-desired-retention =
|
||||
Unable to determine a minimum recommended retention.
|
||||
deck-config-predicted-minimum-recommended-retention = Minimum recommended retention: { $num }
|
||||
|
|
|
|||
|
|
@ -420,8 +420,9 @@ message SimulateFsrsReviewResponse {
|
|||
|
||||
message SimulateFsrsWorkloadResponse {
|
||||
map<uint32, float> cost = 1;
|
||||
map<uint32, float> memorized = 2;
|
||||
map<uint32, uint32> review_count = 3;
|
||||
float reviewless_end_memorized = 2;
|
||||
map<uint32, float> memorized = 3;
|
||||
map<uint32, uint32> review_count = 4;
|
||||
}
|
||||
|
||||
message ComputeOptimalRetentionResponse {
|
||||
|
|
|
|||
|
|
@ -300,7 +300,11 @@ impl Collection {
|
|||
))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>>>()?;
|
||||
let reviewless_end_memorized = cards.iter().fold(0., |p, c| {
|
||||
p + c.retention_on(&req.params, req.days_to_simulate as f32)
|
||||
});
|
||||
Ok(SimulateFsrsWorkloadResponse {
|
||||
reviewless_end_memorized,
|
||||
memorized: dr_workload.iter().map(|(k, v)| (*k, v.0)).collect(),
|
||||
cost: dr_workload.iter().map(|(k, v)| (*k, v.1)).collect(),
|
||||
review_count: dr_workload.iter().map(|(k, v)| (*k, v.2)).collect(),
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
x: parseInt(dr),
|
||||
timeCost: resp!.cost[dr],
|
||||
memorized: v,
|
||||
reviewless_end_memorized: resp!.reviewlessEndMemorized,
|
||||
count: resp!.reviewCount[dr],
|
||||
label: simulationNumber,
|
||||
learnSpan: simulateFsrsRequest.daysToSimulate,
|
||||
|
|
@ -570,7 +571,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
value={SimulateWorkloadSubgraph.ratio}
|
||||
bind:group={simulateWorkloadSubgraph}
|
||||
/>
|
||||
{tr.deckConfigFsrsSimulatorRadioRatio()}
|
||||
{tr.deckConfigFsrsSimulatorRadioRatio2()}
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -8,12 +8,16 @@ import {
|
|||
bisector,
|
||||
line,
|
||||
max,
|
||||
min,
|
||||
pointer,
|
||||
rollup,
|
||||
type ScaleLinear,
|
||||
scaleLinear,
|
||||
type ScaleTime,
|
||||
scaleTime,
|
||||
schemeCategory10,
|
||||
select,
|
||||
type Selection,
|
||||
} from "d3";
|
||||
|
||||
import * as tr from "@generated/ftl";
|
||||
|
|
@ -33,6 +37,7 @@ export interface Point {
|
|||
|
||||
export type WorkloadPoint = Point & {
|
||||
learnSpan: number;
|
||||
reviewless_end_memorized: number;
|
||||
};
|
||||
|
||||
export enum SimulateSubgraph {
|
||||
|
|
@ -62,14 +67,17 @@ export function renderWorkloadChart(
|
|||
.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
|
||||
|
||||
const subgraph_data = ({
|
||||
[SimulateWorkloadSubgraph.ratio]: data.map(d => ({ ...d, y: d.timeCost / d.memorized })),
|
||||
[SimulateWorkloadSubgraph.ratio]: data.map(d => ({
|
||||
...d,
|
||||
y: (60 * 60 * (d.memorized - d.reviewless_end_memorized)) / d.timeCost,
|
||||
})),
|
||||
[SimulateWorkloadSubgraph.time]: data.map(d => ({ ...d, y: d.timeCost / d.learnSpan })),
|
||||
[SimulateWorkloadSubgraph.count]: data.map(d => ({ ...d, y: d.count / d.learnSpan })),
|
||||
[SimulateWorkloadSubgraph.memorized]: data.map(d => ({ ...d, y: d.memorized })),
|
||||
})[subgraph];
|
||||
|
||||
const yTickFormat = (n: number): string => {
|
||||
return subgraph == SimulateWorkloadSubgraph.time || subgraph == SimulateWorkloadSubgraph.ratio
|
||||
return subgraph == SimulateWorkloadSubgraph.time
|
||||
? timeSpan(n, true)
|
||||
: n.toString();
|
||||
};
|
||||
|
|
@ -83,7 +91,7 @@ export function renderWorkloadChart(
|
|||
|
||||
const formatY: (value: number) => string = ({
|
||||
[SimulateWorkloadSubgraph.ratio]: (value: number) =>
|
||||
tr.deckConfigFsrsSimulatorRatioTooltip({ time: timeSpan(value) }),
|
||||
tr.deckConfigFsrsSimulatorRatioTooltip2({ time: value.toFixed(2) }),
|
||||
[SimulateWorkloadSubgraph.time]: (value: number) =>
|
||||
tr.statisticsMinutesPerDay({ count: parseFloat((value / 60).toPrecision(2)) }),
|
||||
[SimulateWorkloadSubgraph.count]: (value: number) => tr.statisticsReviewsPerDay({ count: Math.round(value) }),
|
||||
|
|
@ -95,6 +103,19 @@ export function renderWorkloadChart(
|
|||
return `${tr.deckConfigDesiredRetention()}: ${xTickFormat(dr)}<br>`;
|
||||
}
|
||||
|
||||
select(svgElem)
|
||||
.enter()
|
||||
.datum(subgraph_data[subgraph_data.length - 1])
|
||||
.append("line")
|
||||
.attr("x1", bounds.marginLeft)
|
||||
.attr("x2", bounds.width - bounds.marginRight)
|
||||
.attr("y1", bounds.marginTop)
|
||||
.attr("y2", bounds.marginTop)
|
||||
.attr("stroke", "black")
|
||||
.attr("stroke-width", 1);
|
||||
|
||||
const startMemorized = subgraph_data[0].reviewless_end_memorized;
|
||||
|
||||
return _renderSimulationChart(
|
||||
svgElem,
|
||||
bounds,
|
||||
|
|
@ -105,6 +126,20 @@ export function renderWorkloadChart(
|
|||
(_e: MouseEvent, _d: number) => undefined,
|
||||
yTickFormat,
|
||||
xTickFormat,
|
||||
(svg, x, y) => {
|
||||
svg
|
||||
.selectAll("line")
|
||||
.data(subgraph == SimulateWorkloadSubgraph.memorized ? [startMemorized] : [])
|
||||
.enter()
|
||||
.attr("x1", x(xMin))
|
||||
.attr("x2", x(xMax))
|
||||
.attr("y1", d => y(d))
|
||||
.attr("y2", d => y(d))
|
||||
.attr("stroke", "black")
|
||||
.attr("stroke-dasharray", "5,5")
|
||||
.attr("stroke-width", 1);
|
||||
},
|
||||
subgraph == SimulateWorkloadSubgraph.memorized ? startMemorized : 0,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -185,16 +220,25 @@ export function renderSimulationChart(
|
|||
);
|
||||
}
|
||||
|
||||
function _renderSimulationChart<T extends { x: any; y: any; label: number }>(
|
||||
function _renderSimulationChart<
|
||||
X extends ScaleLinear<number, number> | ScaleTime<number, number>,
|
||||
T extends { x: any; y: any; label: number },
|
||||
>(
|
||||
svgElem: SVGElement,
|
||||
bounds: GraphBounds,
|
||||
subgraph_data: T[],
|
||||
x: any,
|
||||
x: X,
|
||||
formatY: (n: T["y"]) => string,
|
||||
formatX: (n: T["x"]) => string,
|
||||
legendMouseMove: (e: MouseEvent, d: number) => void,
|
||||
yTickFormat?: (n: number) => string,
|
||||
xTickFormat?: (n: number) => string,
|
||||
renderExtra?: (
|
||||
svg: Selection<SVGElement, unknown, null, undefined>,
|
||||
x: X,
|
||||
y: ScaleLinear<number, number, never>,
|
||||
) => void,
|
||||
y_min = Infinity,
|
||||
): TableDatum[] {
|
||||
const svg = select(svgElem);
|
||||
svg.selectAll(".lines").remove();
|
||||
|
|
@ -215,9 +259,11 @@ function _renderSimulationChart<T extends { x: any; y: any; label: number }>(
|
|||
// y scale
|
||||
|
||||
const yMax = max(subgraph_data, d => d.y)!;
|
||||
let yMin = min(subgraph_data, d => d.y)!;
|
||||
yMin = min([yMin, y_min])!;
|
||||
const y = scaleLinear()
|
||||
.range([bounds.height - bounds.marginBottom, bounds.marginTop])
|
||||
.domain([0, yMax])
|
||||
.domain([yMin, yMax])
|
||||
.nice();
|
||||
svg.select<SVGGElement>(".y-ticks")
|
||||
.call((selection) =>
|
||||
|
|
@ -242,7 +288,7 @@ function _renderSimulationChart<T extends { x: any; y: any; label: number }>(
|
|||
.attr("fill", "currentColor");
|
||||
|
||||
// x lines
|
||||
const points = subgraph_data.map((d) => [x(d.x), y(d.y), d.label]);
|
||||
const points = subgraph_data.map((d) => [x(d.x)!, y(d.y)!, d.label]);
|
||||
const groups = rollup(points, v => Object.assign(v, { z: v[0][2] }), d => d[2]);
|
||||
|
||||
const color = schemeCategory10;
|
||||
|
|
@ -364,6 +410,8 @@ function _renderSimulationChart<T extends { x: any; y: any; label: number }>(
|
|||
|
||||
setDataAvailable(svg, true);
|
||||
|
||||
renderExtra?.(svg, x, y);
|
||||
|
||||
const tableData: TableDatum[] = [];
|
||||
|
||||
return tableData;
|
||||
|
|
|
|||
Loading…
Reference in a new issue