mirror of
https://github.com/ankitects/anki.git
synced 2025-09-26 17:56:38 -04:00
extract some simulator logic
This commit is contained in:
parent
005b4b7147
commit
000cffc6d6
2 changed files with 97 additions and 71 deletions
|
@ -471,19 +471,19 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
|
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
|
||||||
disabled={computing}
|
disabled={computing}
|
||||||
on:click={clearSimulation}
|
on:click={clearSimulation}
|
||||||
>
|
>
|
||||||
{tr.deckConfigClearLastSimulate()}
|
{tr.deckConfigClearLastSimulate()}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
disabled={computing}
|
disabled={computing}
|
||||||
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
|
class="btn {computing ? 'btn-warning' : 'btn-primary'}"
|
||||||
on:click={workloadGraph}
|
on:click={workloadGraph}
|
||||||
>
|
>
|
||||||
Display Dr Workloads
|
Display Dr Workloads
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -37,44 +37,23 @@ export enum SimulateSubgraph {
|
||||||
memorized,
|
memorized,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SimulateWorkloadSubgraph {
|
||||||
|
ratio,
|
||||||
|
time,
|
||||||
|
memorized,
|
||||||
|
}
|
||||||
|
|
||||||
export function renderSimulationChart(
|
export function renderSimulationChart(
|
||||||
svgElem: SVGElement,
|
svgElem: SVGElement,
|
||||||
bounds: GraphBounds,
|
bounds: GraphBounds,
|
||||||
data: Point[],
|
data: Point[],
|
||||||
subgraph: SimulateSubgraph,
|
subgraph: SimulateSubgraph,
|
||||||
): TableDatum[] {
|
): TableDatum[] {
|
||||||
const svg = select(svgElem);
|
|
||||||
svg.selectAll(".lines").remove();
|
|
||||||
svg.selectAll(".hover-columns").remove();
|
|
||||||
svg.selectAll(".focus-line").remove();
|
|
||||||
svg.selectAll(".legend").remove();
|
|
||||||
if (data.length == 0) {
|
|
||||||
setDataAvailable(svg, false);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const trans = svg.transition().duration(600) as any;
|
|
||||||
|
|
||||||
// Prepare data
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const convertedData = data.map(d => ({
|
const convertedData = data.map(d => ({
|
||||||
...d,
|
...d,
|
||||||
date: new Date(today.getTime() + d.x * 24 * 60 * 60 * 1000),
|
x: new Date(today.getTime() + d.x * 24 * 60 * 60 * 1000),
|
||||||
}));
|
}));
|
||||||
const xMin = today;
|
|
||||||
const xMax = max(convertedData, d => d.date);
|
|
||||||
|
|
||||||
const x = scaleTime()
|
|
||||||
.domain([xMin, xMax!])
|
|
||||||
.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
|
|
||||||
|
|
||||||
svg.select<SVGGElement>(".x-ticks")
|
|
||||||
.call((selection) => selection.transition(trans).call(axisBottom(x).ticks(7).tickSizeOuter(0)))
|
|
||||||
.attr("direction", "ltr");
|
|
||||||
// y scale
|
|
||||||
|
|
||||||
const yTickFormat = (n: number): string => {
|
|
||||||
return subgraph == SimulateSubgraph.time ? timeSpan(n, true) : n.toString();
|
|
||||||
};
|
|
||||||
|
|
||||||
const subgraph_data = ({
|
const subgraph_data = ({
|
||||||
[SimulateSubgraph.count]: convertedData.map(d => ({ ...d, y: d.count })),
|
[SimulateSubgraph.count]: convertedData.map(d => ({ ...d, y: d.count })),
|
||||||
|
@ -82,6 +61,83 @@ export function renderSimulationChart(
|
||||||
[SimulateSubgraph.memorized]: convertedData.map(d => ({ ...d, y: d.memorized })),
|
[SimulateSubgraph.memorized]: convertedData.map(d => ({ ...d, y: d.memorized })),
|
||||||
})[subgraph];
|
})[subgraph];
|
||||||
|
|
||||||
|
const xMin = today;
|
||||||
|
const xMax = max(subgraph_data, d => d.x);
|
||||||
|
|
||||||
|
const x = scaleTime()
|
||||||
|
.domain([xMin, xMax!])
|
||||||
|
.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
|
||||||
|
|
||||||
|
const yTickFormat = (n: number): string => {
|
||||||
|
return subgraph == SimulateSubgraph.time ? timeSpan(n, true) : n.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatY: (value: number) => string = ({
|
||||||
|
[SimulateSubgraph.time]: timeSpan,
|
||||||
|
[SimulateSubgraph.count]: (value: number) => tr.statisticsReviews({ reviews: Math.round(value) }),
|
||||||
|
[SimulateSubgraph.memorized]: (value: number) =>
|
||||||
|
tr.statisticsMemorized({ memorized: Math.round(value).toFixed(0) }),
|
||||||
|
})[subgraph];
|
||||||
|
|
||||||
|
const perDay = ({
|
||||||
|
[SimulateSubgraph.count]: tr.statisticsReviewsPerDay,
|
||||||
|
[SimulateSubgraph.time]: ({ count }: { count: number }) => timeSpan(count),
|
||||||
|
[SimulateSubgraph.memorized]: tr.statisticsCardsPerDay,
|
||||||
|
})[subgraph];
|
||||||
|
|
||||||
|
function legendMouseMove(e: MouseEvent, d: number) {
|
||||||
|
const data = subgraph_data.filter(datum => datum.label == d);
|
||||||
|
|
||||||
|
const total = subgraph == SimulateSubgraph.memorized
|
||||||
|
? data[data.length - 1].memorized - data[0].memorized
|
||||||
|
: sumBy(data, d => d.y);
|
||||||
|
const average = total / (data?.length || 1);
|
||||||
|
|
||||||
|
showTooltip(
|
||||||
|
`#${d}:<br/>
|
||||||
|
${tr.statisticsAverage()}: ${perDay({ count: average })}<br/>
|
||||||
|
${tr.statisticsTotal()}: ${formatY(total)}`,
|
||||||
|
e.pageX,
|
||||||
|
e.pageY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _renderSimulationChart(
|
||||||
|
svgElem,
|
||||||
|
bounds,
|
||||||
|
subgraph_data,
|
||||||
|
x,
|
||||||
|
yTickFormat,
|
||||||
|
formatY,
|
||||||
|
legendMouseMove,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _renderSimulationChart<T extends { x: any; y: any; label: number }>(
|
||||||
|
svgElem: SVGElement,
|
||||||
|
bounds: GraphBounds,
|
||||||
|
subgraph_data: T[],
|
||||||
|
x: any,
|
||||||
|
yTickFormat: (n: number) => string,
|
||||||
|
formatY: (n: number) => string,
|
||||||
|
legendMouseMove: (e: MouseEvent, d: number) => void,
|
||||||
|
): TableDatum[] {
|
||||||
|
const svg = select(svgElem);
|
||||||
|
svg.selectAll(".lines").remove();
|
||||||
|
svg.selectAll(".hover-columns").remove();
|
||||||
|
svg.selectAll(".focus-line").remove();
|
||||||
|
svg.selectAll(".legend").remove();
|
||||||
|
if (subgraph_data.length == 0) {
|
||||||
|
setDataAvailable(svg, false);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const trans = svg.transition().duration(600) as any;
|
||||||
|
|
||||||
|
svg.select<SVGGElement>(".x-ticks")
|
||||||
|
.call((selection) => selection.transition(trans).call(axisBottom(x).ticks(7).tickSizeOuter(0)))
|
||||||
|
.attr("direction", "ltr");
|
||||||
|
// y scale
|
||||||
|
|
||||||
const yMax = max(subgraph_data, d => d.y)!;
|
const yMax = max(subgraph_data, d => d.y)!;
|
||||||
const y = scaleLinear()
|
const y = scaleLinear()
|
||||||
.range([bounds.height - bounds.marginBottom, bounds.marginTop])
|
.range([bounds.height - bounds.marginBottom, bounds.marginTop])
|
||||||
|
@ -110,7 +166,7 @@ export function renderSimulationChart(
|
||||||
.attr("fill", "currentColor");
|
.attr("fill", "currentColor");
|
||||||
|
|
||||||
// x lines
|
// x lines
|
||||||
const points = subgraph_data.map((d) => [x(d.date), 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 groups = rollup(points, v => Object.assign(v, { z: v[0][2] }), d => d[2]);
|
||||||
|
|
||||||
const color = schemeCategory10;
|
const color = schemeCategory10;
|
||||||
|
@ -157,13 +213,6 @@ export function renderSimulationChart(
|
||||||
hideTooltip();
|
hideTooltip();
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatY: (value: number) => string = ({
|
|
||||||
[SimulateSubgraph.time]: timeSpan,
|
|
||||||
[SimulateSubgraph.count]: (value: number) => tr.statisticsReviews({ reviews: Math.round(value) }),
|
|
||||||
[SimulateSubgraph.memorized]: (value: number) =>
|
|
||||||
tr.statisticsMemorized({ memorized: Math.round(value).toFixed(0) }),
|
|
||||||
})[subgraph];
|
|
||||||
|
|
||||||
function mousemove(event: MouseEvent, d: any): void {
|
function mousemove(event: MouseEvent, d: any): void {
|
||||||
pointer(event, document.body);
|
pointer(event, document.body);
|
||||||
const date = x.invert(d[0]);
|
const date = x.invert(d[0]);
|
||||||
|
@ -212,29 +261,6 @@ export function renderSimulationChart(
|
||||||
.on("mousemove", legendMouseMove)
|
.on("mousemove", legendMouseMove)
|
||||||
.on("mouseout", hideTooltip);
|
.on("mouseout", hideTooltip);
|
||||||
|
|
||||||
const perDay = ({
|
|
||||||
[SimulateSubgraph.count]: tr.statisticsReviewsPerDay,
|
|
||||||
[SimulateSubgraph.time]: ({ count }: { count: number }) => timeSpan(count),
|
|
||||||
[SimulateSubgraph.memorized]: tr.statisticsCardsPerDay,
|
|
||||||
})[subgraph];
|
|
||||||
|
|
||||||
function legendMouseMove(e: MouseEvent, d: number) {
|
|
||||||
const data = subgraph_data.filter(datum => datum.label == d);
|
|
||||||
|
|
||||||
const total = subgraph == SimulateSubgraph.memorized
|
|
||||||
? data[data.length - 1].memorized - data[0].memorized
|
|
||||||
: sumBy(data, d => d.y);
|
|
||||||
const average = total / (data?.length || 1);
|
|
||||||
|
|
||||||
showTooltip(
|
|
||||||
`#${d}:<br/>
|
|
||||||
${tr.statisticsAverage()}: ${perDay({ count: average })}<br/>
|
|
||||||
${tr.statisticsTotal()}: ${formatY(total)}`,
|
|
||||||
e.pageX,
|
|
||||||
e.pageY,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
legend.append("rect")
|
legend.append("rect")
|
||||||
.attr("x", bounds.width - bounds.marginRight + 36)
|
.attr("x", bounds.width - bounds.marginRight + 36)
|
||||||
.attr("width", 12)
|
.attr("width", 12)
|
||||||
|
|
Loading…
Reference in a new issue