mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Move dispatch logic from Histogram to individual graphs
This commit is contained in:
parent
f767a5e6ca
commit
759ed17963
14 changed files with 98 additions and 71 deletions
|
@ -65,7 +65,7 @@ class DeckBrowser:
|
|||
|
||||
def _linkHandler(self, url):
|
||||
if ":" in url:
|
||||
(cmd, arg) = url.split(":")
|
||||
(cmd, arg) = url.split(":", 1)
|
||||
else:
|
||||
cmd = url
|
||||
if cmd == "open":
|
||||
|
|
|
@ -1092,7 +1092,6 @@ message GraphsOut {
|
|||
uint32 scheduler_version = 5;
|
||||
/// Seconds to add to UTC timestamps to get local time.
|
||||
int32 local_offset_secs = 7;
|
||||
bool bridge_commands_supported = 8;
|
||||
}
|
||||
|
||||
message GraphPreferences {
|
||||
|
@ -1104,6 +1103,7 @@ message GraphPreferences {
|
|||
}
|
||||
Weekday calendar_first_day_of_week = 1;
|
||||
bool card_counts_separate_inactive = 2;
|
||||
bool browser_links_supported = 3;
|
||||
}
|
||||
|
||||
message RevlogEntry {
|
||||
|
|
|
@ -44,7 +44,6 @@ impl Collection {
|
|||
next_day_at_secs: timing.next_day_at as u32,
|
||||
scheduler_version: self.sched_ver() as u32,
|
||||
local_offset_secs: local_offset_secs as i32,
|
||||
bridge_commands_supported: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -52,6 +51,7 @@ impl Collection {
|
|||
Ok(pb::GraphPreferences {
|
||||
calendar_first_day_of_week: self.get_first_day_of_week() as i32,
|
||||
card_counts_separate_inactive: self.get_card_counts_separate_inactive(),
|
||||
browser_links_supported: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import HistogramGraph from "./HistogramGraph.svelte";
|
||||
import GraphRangeRadios from "./GraphRangeRadios.svelte";
|
||||
import TableData from "./TableData.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let i18n: I18n;
|
||||
|
@ -17,13 +18,15 @@
|
|||
let tableData: TableDatum[] = [];
|
||||
let graphRange: GraphRange = GraphRange.Month;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let addedData: GraphData | null = null;
|
||||
$: if (sourceData) {
|
||||
addedData = gatherData(sourceData);
|
||||
}
|
||||
|
||||
$: if (addedData) {
|
||||
[histogramData, tableData] = buildHistogram(addedData, graphRange, i18n);
|
||||
[histogramData, tableData] = buildHistogram(addedData, graphRange, i18n, dispatch);
|
||||
}
|
||||
|
||||
const title = i18n.tr(i18n.TR.STATISTICS_ADDED_TITLE);
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
export let i18n: I18n;
|
||||
export let preferences: PreferenceStore;
|
||||
|
||||
let { cardCountsSeparateInactive } = preferences;
|
||||
let { cardCountsSeparateInactive, browserLinksSupported } = preferences;
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let svg = null as HTMLElement | SVGElement | null;
|
||||
|
@ -89,7 +89,7 @@
|
|||
<!-- prettier-ignore -->
|
||||
<td>
|
||||
<span style="color: {d.colour};">■ </span>
|
||||
{#if sourceData.bridgeCommandsSupported}
|
||||
{#if browserLinksSupported}
|
||||
<span class="search-link" on:click={() => dispatch('search', { query: d.query })}>{d.label}</span>
|
||||
{:else}
|
||||
<span>{d.label}</span>
|
||||
|
|
|
@ -6,15 +6,18 @@
|
|||
import type { I18n } from "anki/i18n";
|
||||
import type { TableDatum } from "./graph-helpers";
|
||||
import TableData from "./TableData.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let i18n: I18n;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let histogramData = null as HistogramData | null;
|
||||
let tableData: TableDatum[] = [];
|
||||
|
||||
$: if (sourceData) {
|
||||
[histogramData, tableData] = prepareData(gatherData(sourceData), i18n);
|
||||
[histogramData, tableData] = prepareData(gatherData(sourceData), i18n, dispatch);
|
||||
}
|
||||
|
||||
const title = i18n.tr(i18n.TR.STATISTICS_CARD_EASE_TITLE);
|
||||
|
|
|
@ -9,10 +9,13 @@
|
|||
import HistogramGraph from "./HistogramGraph.svelte";
|
||||
import GraphRangeRadios from "./GraphRangeRadios.svelte";
|
||||
import TableData from "./TableData.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let i18n: I18n;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let graphData = null as GraphData | null;
|
||||
let histogramData = null as HistogramData | null;
|
||||
let tableData: TableDatum[] = [] as any;
|
||||
|
@ -28,7 +31,8 @@
|
|||
graphData,
|
||||
graphRange,
|
||||
backlog,
|
||||
i18n
|
||||
i18n,
|
||||
dispatch,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,13 @@
|
|||
import HistogramGraph from "./HistogramGraph.svelte";
|
||||
import type { TableDatum } from "./graph-helpers";
|
||||
import TableData from "./TableData.svelte";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let sourceData: pb.BackendProto.GraphsOut | null = null;
|
||||
export let i18n: I18n;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let intervalData: IntervalGraphData | null = null;
|
||||
let histogramData = null as HistogramData | null;
|
||||
let tableData: TableDatum[] = [];
|
||||
|
@ -26,7 +29,7 @@
|
|||
}
|
||||
|
||||
$: if (intervalData) {
|
||||
[histogramData, tableData] = prepareIntervalData(intervalData, range, i18n);
|
||||
[histogramData, tableData] = prepareIntervalData(intervalData, range, i18n, dispatch);
|
||||
}
|
||||
|
||||
const title = i18n.tr(i18n.TR.STATISTICS_INTERVALS_TITLE);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type pb from "anki/backend_proto";
|
||||
import { extent, histogram, sum } from "d3-array";
|
||||
import { extent, histogram, sum, Bin } from "d3-array";
|
||||
import { scaleLinear, scaleSequential } from "d3-scale";
|
||||
import type { HistogramData } from "./histogram-graph";
|
||||
import { interpolateBlues } from "d3-scale-chromatic";
|
||||
|
@ -28,10 +28,22 @@ export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
|||
return { daysAdded };
|
||||
}
|
||||
|
||||
function makeQuery(start: number, end: number): string {
|
||||
const include = `"added:${start}"`;
|
||||
|
||||
if (start === 1) {
|
||||
return include;
|
||||
}
|
||||
|
||||
const exclude = `-"added:${end}"`;
|
||||
return `${include} AND ${exclude}`;
|
||||
}
|
||||
|
||||
export function buildHistogram(
|
||||
data: GraphData,
|
||||
range: GraphRange,
|
||||
i18n: I18n
|
||||
i18n: I18n,
|
||||
dispatch: any,
|
||||
): [HistogramData | null, TableDatum[]] {
|
||||
// get min/max
|
||||
const total = data.daysAdded.length;
|
||||
|
@ -102,17 +114,11 @@ export function buildHistogram(
|
|||
return `${day}:<br>${cards}<br>${total}: ${totalCards}`;
|
||||
}
|
||||
|
||||
function makeQuery(data: HistogramData, binIdx: number): string {
|
||||
const start = Math.abs(data.bins[binIdx].x0!) + 1;
|
||||
const include = `"added:${start}"`;
|
||||
|
||||
if (start === 1) {
|
||||
return include;
|
||||
}
|
||||
|
||||
const end = Math.abs(data.bins[binIdx].x1!) + 1;
|
||||
const exclude = `-"added:${end}"`;
|
||||
return `${include} AND ${exclude}`;
|
||||
function onClick(bin: Bin<number, number>): void {
|
||||
const start = Math.abs(bin.x0!) + 1;
|
||||
const end = Math.abs(bin.x1!) + 1;
|
||||
const query = makeQuery(start, end);
|
||||
dispatch("search", { query });
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -121,7 +127,7 @@ export function buildHistogram(
|
|||
bins,
|
||||
total: totalInPeriod,
|
||||
hoverText,
|
||||
makeQuery,
|
||||
onClick,
|
||||
colourScale,
|
||||
showArea: true,
|
||||
},
|
||||
|
|
|
@ -114,7 +114,6 @@ export function renderCalendar(
|
|||
maxCount = count;
|
||||
}
|
||||
}
|
||||
console.log("sourceData", sourceData, dayMap);
|
||||
|
||||
if (!maxCount) {
|
||||
setDataAvailable(svg, false);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type pb from "anki/backend_proto";
|
||||
import { extent, histogram, sum } from "d3-array";
|
||||
import { extent, histogram, sum, Bin } from "d3-array";
|
||||
import { scaleLinear, scaleSequential } from "d3-scale";
|
||||
import { CardType } from "anki/cards";
|
||||
import type { HistogramData } from "./histogram-graph";
|
||||
|
@ -26,9 +26,21 @@ export function gatherData(data: pb.BackendProto.GraphsOut): GraphData {
|
|||
return { eases };
|
||||
}
|
||||
|
||||
function makeQuery(start: number, end: number): string {
|
||||
if (start === end) {
|
||||
return `"prop:ease=${start / 100}"`;
|
||||
}
|
||||
|
||||
const fromQuery = `"prop:ease>=${start / 100}"`;
|
||||
const tillQuery = `"prop:ease<${end / 100}"`;
|
||||
|
||||
return `${fromQuery} AND ${tillQuery}`;
|
||||
}
|
||||
|
||||
export function prepareData(
|
||||
data: GraphData,
|
||||
i18n: I18n
|
||||
i18n: I18n,
|
||||
dispatch: any,
|
||||
): [HistogramData | null, TableDatum[]] {
|
||||
// get min/max
|
||||
const allEases = data.eases;
|
||||
|
@ -61,19 +73,11 @@ export function prepareData(
|
|||
});
|
||||
}
|
||||
|
||||
function makeQuery(data: HistogramData, binIdx: number): string {
|
||||
const bin = data.bins[binIdx];
|
||||
function onClick(bin: Bin<number, number>): void {
|
||||
const start = bin.x0!;
|
||||
const end = bin.x1! - 1;
|
||||
|
||||
if (start === end) {
|
||||
return `"prop:ease=${start / 100}"`;
|
||||
}
|
||||
|
||||
const fromQuery = `"prop:ease>=${start / 100}"`;
|
||||
const tillQuery = `"prop:ease<${end / 100}"`;
|
||||
|
||||
return `${fromQuery} AND ${tillQuery}`;
|
||||
const query = makeQuery(start, end);
|
||||
dispatch("search", { query });
|
||||
}
|
||||
|
||||
const xTickFormat = (num: number): string => `${num.toFixed(0)}%`;
|
||||
|
@ -90,7 +94,7 @@ export function prepareData(
|
|||
bins,
|
||||
total,
|
||||
hoverText,
|
||||
makeQuery,
|
||||
onClick,
|
||||
colourScale,
|
||||
showArea: false,
|
||||
xTickFormat,
|
||||
|
|
|
@ -72,11 +72,23 @@ export interface FutureDueOut {
|
|||
tableData: TableDatum[];
|
||||
}
|
||||
|
||||
function makeQuery(start: number, end: number): string {
|
||||
if (start === end) {
|
||||
return `"prop:due=${start}"`;
|
||||
}
|
||||
else {
|
||||
const fromQuery = `"prop:due>=${start}"`;
|
||||
const tillQuery = `"prop:due<=${end}"`;
|
||||
return `${fromQuery} AND ${tillQuery}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function buildHistogram(
|
||||
sourceData: GraphData,
|
||||
range: GraphRange,
|
||||
backlog: boolean,
|
||||
i18n: I18n
|
||||
i18n: I18n,
|
||||
dispatch: any,
|
||||
): FutureDueOut {
|
||||
const output = { histogramData: null, tableData: [] };
|
||||
// get min/max
|
||||
|
@ -145,19 +157,11 @@ export function buildHistogram(
|
|||
return `${days}:<br>${cards}<br>${totalLabel}: ${cumulative}`;
|
||||
}
|
||||
|
||||
function makeQuery(data: HistogramData, binIdx: number): string {
|
||||
const bin = data.bins[binIdx];
|
||||
function onClick(bin: Bin<number, number>): void {
|
||||
const start = bin.x0!;
|
||||
const end = bin.x1! - 1;
|
||||
|
||||
if (start === end) {
|
||||
return `"prop:due=${start}"`;
|
||||
}
|
||||
|
||||
const fromQuery = `"prop:due>=${start}"`;
|
||||
const tillQuery = `"prop:due<=${end}"`;
|
||||
|
||||
return `${fromQuery} AND ${tillQuery}`;
|
||||
const query = makeQuery(start, end);
|
||||
dispatch("search", { query });
|
||||
}
|
||||
|
||||
const periodDays = xMax! - xMin!;
|
||||
|
@ -186,7 +190,7 @@ export function buildHistogram(
|
|||
bins,
|
||||
total,
|
||||
hoverText,
|
||||
makeQuery,
|
||||
onClick,
|
||||
showArea: true,
|
||||
colourScale,
|
||||
binValue,
|
||||
|
|
|
@ -25,7 +25,7 @@ export interface HistogramData {
|
|||
cumulative: number,
|
||||
percent: number
|
||||
) => string;
|
||||
makeQuery?: (data: HistogramData, binIdx: number) => string;
|
||||
onClick?: (data: Bin<number, number>) => void;
|
||||
showArea: boolean;
|
||||
colourScale: ScaleSequential<string>;
|
||||
binValue?: (bin: Bin<any, any>) => number;
|
||||
|
@ -36,7 +36,6 @@ export function histogramGraph(
|
|||
svgElem: SVGElement,
|
||||
bounds: GraphBounds,
|
||||
data: HistogramData | null,
|
||||
dispatch: any
|
||||
): void {
|
||||
const svg = select(svgElem);
|
||||
const trans = svg.transition().duration(600) as any;
|
||||
|
@ -162,11 +161,9 @@ export function histogramGraph(
|
|||
})
|
||||
.on("mouseout", hideTooltip);
|
||||
|
||||
if (data.makeQuery) {
|
||||
if (data.onClick) {
|
||||
hoverzone
|
||||
.attr("class", "clickable")
|
||||
.on("click", function (this: any, _d: any, idx: number) {
|
||||
dispatch("search", { query: data.makeQuery!(data, idx) });
|
||||
});
|
||||
.on("click", data.onClick);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type pb from "anki/backend_proto";
|
||||
import { extent, histogram, quantile, sum, mean } from "d3-array";
|
||||
import { extent, histogram, quantile, sum, mean, Bin } from "d3-array";
|
||||
import { scaleLinear, scaleSequential } from "d3-scale";
|
||||
import { CardType } from "anki/cards";
|
||||
import type { HistogramData } from "./histogram-graph";
|
||||
|
@ -56,10 +56,22 @@ export function intervalLabel(
|
|||
}
|
||||
}
|
||||
|
||||
function makeQuery(start: number, end: number): string {
|
||||
if (start === end) {
|
||||
return `"prop:ivl=${start}"`;
|
||||
}
|
||||
|
||||
const fromQuery = `"prop:ivl>=${start}"`;
|
||||
const tillQuery = `"prop:ivl<=${end}"`;
|
||||
|
||||
return `${fromQuery} AND ${tillQuery}`;
|
||||
}
|
||||
|
||||
export function prepareIntervalData(
|
||||
data: IntervalGraphData,
|
||||
range: IntervalRange,
|
||||
i18n: I18n
|
||||
i18n: I18n,
|
||||
dispatch: any,
|
||||
): [HistogramData | null, TableDatum[]] {
|
||||
// get min/max
|
||||
const allIntervals = data.intervals;
|
||||
|
@ -134,19 +146,11 @@ export function prepareIntervalData(
|
|||
return `${interval}<br>${total}: \u200e${percent.toFixed(1)}%`;
|
||||
}
|
||||
|
||||
function makeQuery(data: HistogramData, binIdx: number): string {
|
||||
const bin = data.bins[binIdx];
|
||||
function onClick(bin: Bin<number, number>): void {
|
||||
const start = bin.x0!;
|
||||
const end = bin.x1! - 1;
|
||||
|
||||
if (start === end) {
|
||||
return `"prop:ivl=${start}"`;
|
||||
}
|
||||
|
||||
const fromQuery = `"prop:ivl>=${start}"`;
|
||||
const tillQuery = `"prop:ivl<=${end}"`;
|
||||
|
||||
return `${fromQuery} AND ${tillQuery}`;
|
||||
const query = makeQuery(start, end);
|
||||
dispatch("search", { query });
|
||||
}
|
||||
|
||||
const meanInterval = Math.round(mean(allIntervals) ?? 0);
|
||||
|
@ -163,7 +167,7 @@ export function prepareIntervalData(
|
|||
bins,
|
||||
total: totalInPeriod,
|
||||
hoverText,
|
||||
makeQuery,
|
||||
onClick,
|
||||
colourScale,
|
||||
showArea: true,
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue