mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
switch the card counts to a pie graph
This commit is contained in:
parent
d079307536
commit
4086042970
3 changed files with 72 additions and 69 deletions
|
@ -53,6 +53,7 @@
|
||||||
"@fluent/bundle": "^0.15.1",
|
"@fluent/bundle": "^0.15.1",
|
||||||
"d3-array": "^2.4.0",
|
"d3-array": "^2.4.0",
|
||||||
"d3-axis": "^1.0.12",
|
"d3-axis": "^1.0.12",
|
||||||
|
"d3-interpolate": "^1.4.0",
|
||||||
"d3-scale": "^3.2.1",
|
"d3-scale": "^3.2.1",
|
||||||
"d3-scale-chromatic": "^1.5.0",
|
"d3-scale-chromatic": "^1.5.0",
|
||||||
"d3-selection": "^1.4.2",
|
"d3-selection": "^1.4.2",
|
||||||
|
|
|
@ -10,19 +10,14 @@
|
||||||
let svg = null as HTMLElement | SVGElement | null;
|
let svg = null as HTMLElement | SVGElement | null;
|
||||||
|
|
||||||
let bounds = defaultGraphBounds();
|
let bounds = defaultGraphBounds();
|
||||||
bounds.height = 20;
|
bounds.width = 225;
|
||||||
bounds.marginTop = 0;
|
bounds.marginBottom = 0;
|
||||||
|
|
||||||
let activeIdx: null | number = null;
|
|
||||||
function onHover(idx: null | number): void {
|
|
||||||
activeIdx = idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
let graphData = (null as unknown) as GraphData;
|
let graphData = (null as unknown) as GraphData;
|
||||||
let tableData = (null as unknown) as TableDatum[];
|
let tableData = (null as unknown) as TableDatum[];
|
||||||
$: {
|
$: {
|
||||||
graphData = gatherData(sourceData, i18n);
|
graphData = gatherData(sourceData, i18n);
|
||||||
tableData = renderCards(svg as any, bounds, graphData, onHover);
|
tableData = renderCards(svg as any, bounds, graphData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const total = i18n.tr(i18n.TR.STATISTICS_COUNTS_TOTAL_CARDS);
|
const total = i18n.tr(i18n.TR.STATISTICS_COUNTS_TOTAL_CARDS);
|
||||||
|
@ -33,8 +28,14 @@
|
||||||
transition: opacity 1s;
|
transition: opacity 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.counts-outer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.counts-table {
|
.counts-table {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,26 +46,24 @@
|
||||||
.right {
|
.right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="graph" id="graph-card-counts">
|
<div class="graph" id="graph-card-counts">
|
||||||
<h1>{graphData.title}</h1>
|
<h1>{graphData.title}</h1>
|
||||||
|
|
||||||
|
<div class="counts-outer">
|
||||||
<svg
|
<svg
|
||||||
bind:this={svg}
|
bind:this={svg}
|
||||||
viewBox={`0 0 ${bounds.width} ${bounds.height}`}
|
viewBox={`0 0 ${bounds.width} ${bounds.height}`}
|
||||||
|
width={bounds.width}
|
||||||
|
height={bounds.height}
|
||||||
style="opacity: {graphData.totalCards ? 1 : 0}">
|
style="opacity: {graphData.totalCards ? 1 : 0}">
|
||||||
<g class="days" />
|
<g class="counts" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div class="counts-table">
|
<div class="counts-table">
|
||||||
<table>
|
<table>
|
||||||
{#each tableData as d, idx}
|
{#each tableData as d, idx}
|
||||||
<tr class:bold={activeIdx === idx}>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span style="color: {d.colour};">■</span>
|
<span style="color: {d.colour};">■</span>
|
||||||
{d.label}
|
{d.label}
|
||||||
|
@ -74,7 +73,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<tr class:bold={activeIdx === null}>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span style="visibility: hidden;">■</span>
|
<span style="visibility: hidden;">■</span>
|
||||||
{total}
|
{total}
|
||||||
|
@ -82,8 +81,7 @@
|
||||||
<td class="right">{graphData.totalCards}</td>
|
<td class="right">{graphData.totalCards}</td>
|
||||||
<td />
|
<td />
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,8 @@ import { schemeGreens, schemeBlues } from "d3-scale-chromatic";
|
||||||
import "d3-transition";
|
import "d3-transition";
|
||||||
import { select } from "d3-selection";
|
import { select } from "d3-selection";
|
||||||
import { scaleLinear } from "d3-scale";
|
import { scaleLinear } from "d3-scale";
|
||||||
|
import { pie, arc } from "d3-shape";
|
||||||
|
import { interpolate } from "d3-interpolate";
|
||||||
import { GraphBounds } from "./graphs";
|
import { GraphBounds } from "./graphs";
|
||||||
import { cumsum } from "d3-array";
|
import { cumsum } from "d3-array";
|
||||||
import { I18n } from "../i18n";
|
import { I18n } from "../i18n";
|
||||||
|
@ -24,7 +26,6 @@ export interface GraphData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): GraphData {
|
export function gatherData(data: pb.BackendProto.GraphsOut, i18n: I18n): GraphData {
|
||||||
// fixme: handle preview cards
|
|
||||||
const totalCards = data.cards.length;
|
const totalCards = data.cards.length;
|
||||||
let newCards = 0;
|
let newCards = 0;
|
||||||
let young = 0;
|
let young = 0;
|
||||||
|
@ -116,8 +117,7 @@ export interface TableDatum {
|
||||||
export function renderCards(
|
export function renderCards(
|
||||||
svgElem: SVGElement,
|
svgElem: SVGElement,
|
||||||
bounds: GraphBounds,
|
bounds: GraphBounds,
|
||||||
sourceData: GraphData,
|
sourceData: GraphData
|
||||||
onHover: (idx: null | number) => void
|
|
||||||
): TableDatum[] {
|
): TableDatum[] {
|
||||||
const summed = cumsum(sourceData.counts, (d) => d[1]);
|
const summed = cumsum(sourceData.counts, (d) => d[1]);
|
||||||
const data = Array.from(summed).map((n, idx) => {
|
const data = Array.from(summed).map((n, idx) => {
|
||||||
|
@ -129,13 +129,42 @@ export function renderCards(
|
||||||
total: n,
|
total: n,
|
||||||
} as SummedDatum;
|
} as SummedDatum;
|
||||||
});
|
});
|
||||||
// ensuring a non-zero range makes a better animation
|
// ensuring a non-zero range makes the percentages not break
|
||||||
// in the empty data case
|
// in an empty collection
|
||||||
const xMax = Math.max(1, summed.slice(-1)[0]);
|
const xMax = Math.max(1, summed.slice(-1)[0]);
|
||||||
const x = scaleLinear().domain([0, xMax]);
|
const x = scaleLinear().domain([0, xMax]);
|
||||||
const svg = select(svgElem);
|
const svg = select(svgElem);
|
||||||
|
const paths = svg.select(".counts");
|
||||||
|
const pieData = pie()(sourceData.counts.map((d) => d[1]));
|
||||||
|
const radius = bounds.height / 2 - bounds.marginTop - bounds.marginBottom;
|
||||||
|
const arcGen = arc().innerRadius(0).outerRadius(radius);
|
||||||
const trans = svg.transition().duration(600) as any;
|
const trans = svg.transition().duration(600) as any;
|
||||||
|
|
||||||
|
paths
|
||||||
|
.attr("transform", `translate(${radius},${radius + bounds.marginTop})`)
|
||||||
|
.selectAll("path")
|
||||||
|
.data(pieData)
|
||||||
|
.join(
|
||||||
|
(enter) =>
|
||||||
|
enter
|
||||||
|
.append("path")
|
||||||
|
.attr("fill", function (d, i) {
|
||||||
|
return barColour(i);
|
||||||
|
})
|
||||||
|
.attr("d", arcGen as any),
|
||||||
|
function (update) {
|
||||||
|
return update.call((d) =>
|
||||||
|
d.transition(trans).attrTween("d", (d) => {
|
||||||
|
const interpolator = interpolate(
|
||||||
|
{ startAngle: 0, endAngle: 0 },
|
||||||
|
d
|
||||||
|
);
|
||||||
|
return (t): string => arcGen(interpolator(t)) as string;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
x.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
|
x.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
|
||||||
|
|
||||||
const tableData = data.map((d, idx) => {
|
const tableData = data.map((d, idx) => {
|
||||||
|
@ -148,30 +177,5 @@ export function renderCards(
|
||||||
} as TableDatum;
|
} as TableDatum;
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateBar = (sel: any): any => {
|
|
||||||
return sel
|
|
||||||
.on("mousemove", function (this: any, d: SummedDatum) {
|
|
||||||
onHover(d.idx);
|
|
||||||
})
|
|
||||||
.transition(trans)
|
|
||||||
.attr("x", (d: SummedDatum) => x(d.total - d.count))
|
|
||||||
.attr("width", (d: SummedDatum) => x(d.count) - x(0));
|
|
||||||
};
|
|
||||||
|
|
||||||
svg.select("g.days")
|
|
||||||
.selectAll("rect")
|
|
||||||
.data(data)
|
|
||||||
.join(
|
|
||||||
(enter) =>
|
|
||||||
enter
|
|
||||||
.append("rect")
|
|
||||||
.attr("height", 10)
|
|
||||||
.attr("y", bounds.marginTop)
|
|
||||||
.attr("fill", (d: SummedDatum): any => barColour(d.idx))
|
|
||||||
.on("mouseout", () => onHover(null))
|
|
||||||
.call((d) => updateBar(d)),
|
|
||||||
(update) => update.call((d) => updateBar(d))
|
|
||||||
);
|
|
||||||
|
|
||||||
return tableData;
|
return tableData;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue