move card counts tooltip into permanent table

This commit is contained in:
Damien Elmes 2020-07-31 17:19:31 +10:00
parent 710127d490
commit 1353590a92
2 changed files with 74 additions and 31 deletions

View File

@ -1,6 +1,6 @@
<script lang="typescript"> <script lang="typescript">
import { defaultGraphBounds } from "./graphs"; import { defaultGraphBounds } from "./graphs";
import { gatherData, GraphData, renderCards } from "./card-counts"; import { gatherData, GraphData, renderCards, TableDatum } from "./card-counts";
import pb from "../backend/proto"; import pb from "../backend/proto";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
@ -15,10 +15,16 @@
bounds.marginRight = 20; bounds.marginRight = 20;
bounds.marginTop = 0; bounds.marginTop = 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[];
$: { $: {
graphData = gatherData(sourceData, i18n); graphData = gatherData(sourceData, i18n);
renderCards(svg as any, bounds, graphData); tableData = renderCards(svg as any, bounds, graphData, onHover);
} }
const total = i18n.tr(i18n.TR.STATISTICS_COUNTS_TOTAL_CARDS); const total = i18n.tr(i18n.TR.STATISTICS_COUNTS_TOTAL_CARDS);
@ -28,6 +34,23 @@
svg { svg {
transition: opacity 1s; transition: opacity 1s;
} }
.counts-table {
display: flex;
justify-content: center;
}
table {
border-spacing: 1em 0;
}
.right {
text-align: right;
}
.bold {
font-weight: bold;
}
</style> </style>
<div class="graph" id="graph-card-counts"> <div class="graph" id="graph-card-counts">
@ -40,6 +63,29 @@
<g class="days" /> <g class="days" />
</svg> </svg>
<div class="centered">{total}: {graphData.totalCards}</div> <div class="counts-table">
<table>
{#each tableData as d, idx}
<tr class:bold={activeIdx === idx}>
<td>
<span style="color: {d.colour};"></span>
{d.label}
</td>
<td class="right">{d.count}</td>
<td class="right">{d.percent}</td>
</tr>
{/each}
<tr class:bold={activeIdx === null}>
<td>
<span style="visibility: hidden;"></span>
{total}
</td>
<td class="right">{graphData.totalCards}</td>
<td />
</tr>
</table>
</div>
</div> </div>

View File

@ -10,9 +10,8 @@ import { CardQueue } from "../cards";
import pb from "../backend/proto"; import pb from "../backend/proto";
import { schemeGreens, schemeBlues } from "d3-scale-chromatic"; import { schemeGreens, schemeBlues } from "d3-scale-chromatic";
import "d3-transition"; import "d3-transition";
import { select, mouse } from "d3-selection"; import { select } from "d3-selection";
import { scaleLinear } from "d3-scale"; import { scaleLinear } from "d3-scale";
import { showTooltip, hideTooltip } from "./tooltip";
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";
@ -98,7 +97,7 @@ function barColour(idx: number): string {
} }
} }
interface SummedDatum { export interface SummedDatum {
label: string; label: string;
// count of this particular item // count of this particular item
count: number; count: number;
@ -107,11 +106,19 @@ interface SummedDatum {
total: number; total: number;
} }
export interface TableDatum {
label: string;
count: number;
percent: string;
colour: string;
}
export function renderCards( export function renderCards(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
sourceData: GraphData sourceData: GraphData,
): void { onHover: (idx: null | number) => void
): 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) => {
const count = sourceData.counts[idx]; const count = sourceData.counts[idx];
@ -131,32 +138,20 @@ export function renderCards(
x.range([bounds.marginLeft, bounds.width - bounds.marginRight - bounds.marginLeft]); x.range([bounds.marginLeft, bounds.width - bounds.marginRight - bounds.marginLeft]);
const tooltipText = (current: SummedDatum): string => { const tableData = data.map((d, idx) => {
const rows: string[] = []; const percent = ((d.count / xMax) * 100).toFixed(1);
for (const [idx, d] of data.entries()) { return {
const pct = ((d.count / xMax) * 100).toFixed(2); label: d.label,
const colour = `<span style="color: ${barColour(idx)};">■</span>`; count: d.count,
let label = `${colour} ${d.label}`; percent: `${percent}%`,
if (idx === current.idx) { colour: barColour(idx),
label = `<b>${label}</b>`; } as TableDatum;
} });
const count = d.count;
const pctStr = `${pct}%`;
const row = `<tr>
<td>${label}</td>
<td align=right>${count}</td>
<td align=right>${pctStr}</td>
</tr>`;
rows.push(row);
}
return `<table>${rows.join("")}</table>`;
};
const updateBar = (sel: any): any => { const updateBar = (sel: any): any => {
return sel return sel
.on("mousemove", function (this: any, d: SummedDatum) { .on("mousemove", function (this: any, d: SummedDatum) {
const [x, y] = mouse(document.body); onHover(d.idx);
showTooltip(tooltipText(d), x, y);
}) })
.transition(trans) .transition(trans)
.attr("x", (d: SummedDatum) => x(d.total - d.count)) .attr("x", (d: SummedDatum) => x(d.total - d.count))
@ -173,8 +168,10 @@ export function renderCards(
.attr("height", 10) .attr("height", 10)
.attr("y", bounds.marginTop) .attr("y", bounds.marginTop)
.attr("fill", (d: SummedDatum): any => barColour(d.idx)) .attr("fill", (d: SummedDatum): any => barColour(d.idx))
.on("mouseout", hideTooltip) .on("mouseout", () => onHover(null))
.call((d) => updateBar(d)), .call((d) => updateBar(d)),
(update) => update.call((d) => updateBar(d)) (update) => update.call((d) => updateBar(d))
); );
return tableData;
} }