add 'no data' overlay when graph empty

This commit is contained in:
Damien Elmes 2020-07-06 14:01:49 +10:00
parent 19541c4a9d
commit 0d287330c3
24 changed files with 244 additions and 150 deletions

View file

@ -157,3 +157,5 @@ statistics-hours-range = From { $hourStart }:00~{ $hourEnd }:00
statistics-hours-correct = { $correct }/{ $total } correct ({ $percent }%) statistics-hours-correct = { $correct }/{ $total } correct ({ $percent }%)
statistics-hours-title = Hourly Breakdown statistics-hours-title = Hourly Breakdown
statistics-hours-subtitle = Review success rate for each hour of the day. statistics-hours-subtitle = Review success rate for each hour of the day.
# shown when graph is empty
statistics-no-data = NO DATA

View file

@ -44,8 +44,7 @@
const subtitle = i18n.tr(i18n.TR.STATISTICS_ADDED_SUBTITLE); const subtitle = i18n.tr(i18n.TR.STATISTICS_ADDED_SUBTITLE);
</script> </script>
{#if histogramData} <div class="graph">
<div class="graph">
<h1>{title}</h1> <h1>{title}</h1>
<div class="range-box-inner"> <div class="range-box-inner">
@ -69,6 +68,5 @@
<div class="subtitle">{subtitle}</div> <div class="subtitle">{subtitle}</div>
<HistogramGraph data={histogramData} /> <HistogramGraph data={histogramData} {i18n} />
</div> </div>
{/if}

View file

@ -4,6 +4,7 @@
import { gatherData, GraphData, renderButtons } from "./buttons"; import { gatherData, GraphData, renderButtons } from "./buttons";
import pb from "../backend/proto"; import pb from "../backend/proto";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
import NoDataOverlay from "./NoDataOverlay.svelte";
export let sourceData: pb.BackendProto.GraphsOut | null = null; export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n; export let i18n: I18n;
@ -29,5 +30,6 @@
<g class="bars" /> <g class="bars" />
<g class="hoverzone" /> <g class="hoverzone" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
</svg> </svg>
</div> </div>

View file

@ -1,9 +1,8 @@
<script lang="typescript"> <script lang="typescript">
import ReviewsGraph from "./ReviewsGraph.svelte"; import NoDataOverlay from "./NoDataOverlay.svelte";
import AxisTicks from "./AxisTicks.svelte"; import AxisTicks from "./AxisTicks.svelte";
import { defaultGraphBounds, RevlogRange } from "./graphs"; import { defaultGraphBounds, RevlogRange } from "./graphs";
import { GraphData, gatherData, renderCalendar, ReviewRange } from "./calendar"; import { GraphData, gatherData, renderCalendar } from "./calendar";
import pb from "../backend/proto"; import pb from "../backend/proto";
import { timeSpan, MONTH, YEAR } from "../time"; import { timeSpan, MONTH, YEAR } from "../time";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
@ -76,6 +75,7 @@
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}> <svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
<g class="days" /> <g class="days" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
</svg> </svg>
</div> </div>

View file

@ -4,7 +4,7 @@
import pb from "../backend/proto"; import pb from "../backend/proto";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
export let sourceData: pb.BackendProto.GraphsOut | null = null; export let sourceData: pb.BackendProto.GraphsOut;
export let i18n: I18n; export let i18n: I18n;
let svg = null as HTMLElement | SVGElement | null; let svg = null as HTMLElement | SVGElement | null;
@ -15,27 +15,31 @@
bounds.marginRight = 20; bounds.marginRight = 20;
bounds.marginTop = 0; bounds.marginTop = 0;
let graphData: GraphData | null = null; let graphData: GraphData;
$: if (sourceData) { $: {
graphData = gatherData(sourceData, i18n); graphData = gatherData(sourceData, i18n);
}
$: if (graphData) {
renderCards(svg as any, bounds, graphData); 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);
</script> </script>
{#if graphData} <style>
<div class="graph"> svg {
transition: opacity 1s;
}
</style>
<div class="graph">
<h1>{graphData.title}</h1> <h1>{graphData.title}</h1>
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}> <svg
bind:this={svg}
viewBox={`0 0 ${bounds.width} ${bounds.height}`}
style="opacity: {graphData.totalCards ? 1 : 0}">
<g class="days" /> <g class="days" />
</svg> </svg>
<div class="centered">{total}: {graphData.totalCards}</div> <div class="centered">{total}: {graphData.totalCards}</div>
</div> </div>
{/if}

View file

@ -19,12 +19,10 @@
const subtitle = i18n.tr(i18n.TR.STATISTICS_CARD_EASE_SUBTITLE); const subtitle = i18n.tr(i18n.TR.STATISTICS_CARD_EASE_SUBTITLE);
</script> </script>
{#if histogramData} <div class="graph">
<div class="graph">
<h1>{title}</h1> <h1>{title}</h1>
<div class="subtitle">{subtitle}</div> <div class="subtitle">{subtitle}</div>
<HistogramGraph data={histogramData} /> <HistogramGraph data={histogramData} {i18n} />
</div> </div>
{/if}

View file

@ -52,9 +52,7 @@
const backlogLabel = i18n.tr(i18n.TR.STATISTICS_BACKLOG_CHECKBOX); const backlogLabel = i18n.tr(i18n.TR.STATISTICS_BACKLOG_CHECKBOX);
</script> </script>
{#if histogramData} <div class="graph">
<div class="graph">
<h1>{title}</h1> <h1>{title}</h1>
<div class="range-box-inner"> <div class="range-box-inner">
@ -83,7 +81,6 @@
<div class="subtitle">{subtitle}</div> <div class="subtitle">{subtitle}</div>
<HistogramGraph data={histogramData} /> <HistogramGraph data={histogramData} {i18n} />
</div> </div>
{/if}

View file

@ -141,13 +141,15 @@
</div> </div>
<div class="range-box-pad" /> <div class="range-box-pad" />
<TodayStats {sourceData} {i18n} /> {#if sourceData}
<CardCounts {sourceData} {i18n} /> <TodayStats {sourceData} {i18n} />
<CalendarGraph {sourceData} {revlogRange} {i18n} {nightMode} /> <CardCounts {sourceData} {i18n} />
<FutureDue {sourceData} {revlogRange} {i18n} /> <CalendarGraph {sourceData} {revlogRange} {i18n} {nightMode} />
<ReviewsGraph {sourceData} {revlogRange} {i18n} /> <FutureDue {sourceData} {revlogRange} {i18n} />
<IntervalsGraph {sourceData} {i18n} /> <ReviewsGraph {sourceData} {revlogRange} {i18n} />
<EaseGraph {sourceData} {i18n} /> <IntervalsGraph {sourceData} {i18n} />
<HourGraph {sourceData} {i18n} /> <EaseGraph {sourceData} {i18n} />
<ButtonsGraph {sourceData} {i18n} /> <HourGraph {sourceData} {i18n} />
<AddedGraph {sourceData} {revlogRange} {i18n} /> <ButtonsGraph {sourceData} {i18n} />
<AddedGraph {sourceData} {revlogRange} {i18n} />
{/if}

View file

@ -2,15 +2,16 @@
import { HistogramData, histogramGraph } from "./histogram-graph"; import { HistogramData, histogramGraph } from "./histogram-graph";
import AxisTicks from "./AxisTicks.svelte"; import AxisTicks from "./AxisTicks.svelte";
import { defaultGraphBounds } from "./graphs"; import { defaultGraphBounds } from "./graphs";
import NoDataOverlay from "./NoDataOverlay.svelte";
import { I18n } from "../i18n";
export let data: HistogramData | null = null; export let data: HistogramData | null = null;
export let i18n: I18n;
let bounds = defaultGraphBounds(); let bounds = defaultGraphBounds();
let svg = null as HTMLElement | SVGElement | null; let svg = null as HTMLElement | SVGElement | null;
$: if (data) { $: histogramGraph(svg as SVGElement, bounds, data);
histogramGraph(svg as SVGElement, bounds, data);
}
</script> </script>
<svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}> <svg bind:this={svg} viewBox={`0 0 ${bounds.width} ${bounds.height}`}>
@ -18,4 +19,5 @@
<g class="hoverzone" /> <g class="hoverzone" />
<path class="area" /> <path class="area" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
</svg> </svg>

View file

@ -4,6 +4,7 @@
import { gatherData, GraphData, renderHours } from "./hours"; import { gatherData, GraphData, renderHours } from "./hours";
import pb from "../backend/proto"; import pb from "../backend/proto";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
import NoDataOverlay from "./NoDataOverlay.svelte";
export let sourceData: pb.BackendProto.GraphsOut | null = null; export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let i18n: I18n; export let i18n: I18n;
@ -30,5 +31,6 @@
<path class="area" /> <path class="area" />
<g class="hoverzone" /> <g class="hoverzone" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
</svg> </svg>
</div> </div>

View file

@ -34,8 +34,7 @@
const subtitle = i18n.tr(i18n.TR.STATISTICS_INTERVALS_SUBTITLE); const subtitle = i18n.tr(i18n.TR.STATISTICS_INTERVALS_SUBTITLE);
</script> </script>
{#if histogramData} <div class="graph intervals">
<div class="graph intervals">
<h1>{title}</h1> <h1>{title}</h1>
<div class="range-box-inner"> <div class="range-box-inner">
@ -44,17 +43,11 @@
{month} {month}
</label> </label>
<label> <label>
<input <input type="radio" bind:group={range} value={IntervalRange.Percentile50} />
type="radio"
bind:group={range}
value={IntervalRange.Percentile50} />
50% 50%
</label> </label>
<label> <label>
<input <input type="radio" bind:group={range} value={IntervalRange.Percentile95} />
type="radio"
bind:group={range}
value={IntervalRange.Percentile95} />
95% 95%
</label> </label>
<label> <label>
@ -72,6 +65,5 @@
<div class="subtitle">{subtitle}</div> <div class="subtitle">{subtitle}</div>
<HistogramGraph data={histogramData} /> <HistogramGraph data={histogramData} {i18n} />
</div> </div>
{/if}

View file

@ -0,0 +1,14 @@
<script lang="typescript">
import { I18n } from "../i18n";
import { GraphBounds } from "./graphs";
export let bounds: GraphBounds;
export let i18n: I18n;
const noData = i18n.tr(i18n.TR.STATISTICS_NO_DATA);
</script>
<g class="no-data">
<rect x="0" y="0" width={bounds.width} height={bounds.height} />
<text x="{bounds.width / 2}," y={bounds.height / 2} letter-spacing="3">
{noData}
</text>
</g>

View file

@ -6,6 +6,7 @@
import pb from "../backend/proto"; import pb from "../backend/proto";
import { timeSpan, MONTH, YEAR } from "../time"; import { timeSpan, MONTH, YEAR } from "../time";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
import NoDataOverlay from "./NoDataOverlay.svelte";
export let sourceData: pb.BackendProto.GraphsOut | null = null; export let sourceData: pb.BackendProto.GraphsOut | null = null;
export let revlogRange: RevlogRange = RevlogRange.Month; export let revlogRange: RevlogRange = RevlogRange.Month;
@ -93,6 +94,7 @@
<path class="area" /> <path class="area" />
<g class="hoverzone" /> <g class="hoverzone" />
<AxisTicks {bounds} /> <AxisTicks {bounds} />
<NoDataOverlay {bounds} {i18n} />
</svg> </svg>
</div> </div>

View file

@ -7,7 +7,7 @@
*/ */
import pb from "../backend/proto"; import pb from "../backend/proto";
import { extent, histogram } from "d3-array"; import { extent, histogram, sum } from "d3-array";
import { scaleLinear, scaleSequential } from "d3-scale"; import { scaleLinear, scaleSequential } from "d3-scale";
import { HistogramData } from "./histogram-graph"; import { HistogramData } from "./histogram-graph";
import { interpolateBlues } from "d3-scale-chromatic"; import { interpolateBlues } from "d3-scale-chromatic";
@ -69,6 +69,11 @@ export function buildHistogram(
.domain(scale.domain() as any) .domain(scale.domain() as any)
.thresholds(scale.ticks(desiredBars))(data.daysAdded); .thresholds(scale.ticks(desiredBars))(data.daysAdded);
// empty graph?
if (!sum(bins, (bin) => bin.length)) {
return null;
}
const colourScale = scaleSequential(interpolateBlues).domain([xMin!, xMax]); const colourScale = scaleSequential(interpolateBlues).domain([xMin!, xMax]);
function hoverText( function hoverText(

View file

@ -13,7 +13,7 @@ import { select, mouse } from "d3-selection";
import { scaleLinear, scaleBand, scaleSequential } from "d3-scale"; import { scaleLinear, scaleBand, scaleSequential } from "d3-scale";
import { axisBottom, axisLeft } from "d3-axis"; import { axisBottom, axisLeft } from "d3-axis";
import { showTooltip, hideTooltip } from "./tooltip"; import { showTooltip, hideTooltip } from "./tooltip";
import { GraphBounds } from "./graphs"; import { GraphBounds, setDataAvailable } from "./graphs";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
type ButtonCounts = [number, number, number, number]; type ButtonCounts = [number, number, number, number];
@ -103,6 +103,13 @@ export function renderButtons(
const svg = select(svgElem); const svg = select(svgElem);
const trans = svg.transition().duration(600) as any; const trans = svg.transition().duration(600) as any;
if (!yMax) {
setDataAvailable(svg, false);
return;
} else {
setDataAvailable(svg, true);
}
const xGroup = scaleBand() const xGroup = scaleBand()
.domain(["learning", "young", "mature"]) .domain(["learning", "young", "mature"])
.range([bounds.marginLeft, bounds.width - bounds.marginRight]); .range([bounds.marginLeft, bounds.width - bounds.marginRight]);

View file

@ -12,7 +12,7 @@ import "d3-transition";
import { select, mouse } from "d3-selection"; import { select, mouse } from "d3-selection";
import { scaleLinear, scaleSequential } from "d3-scale"; import { scaleLinear, scaleSequential } from "d3-scale";
import { showTooltip, hideTooltip } from "./tooltip"; import { showTooltip, hideTooltip } from "./tooltip";
import { GraphBounds } from "./graphs"; import { GraphBounds, setDataAvailable } from "./graphs";
import { timeDay, timeYear, timeWeek } from "d3-time"; import { timeDay, timeYear, timeWeek } from "d3-time";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
@ -80,6 +80,14 @@ export function renderCalendar(
maxCount = count; maxCount = count;
} }
} }
if (!maxCount) {
setDataAvailable(svg, false);
return;
} else {
setDataAvailable(svg, true);
}
// fill in any blanks // fill in any blanks
const startDate = timeYear(nowForYear); const startDate = timeYear(nowForYear);
for (let i = 0; i < 366; i++) { for (let i = 0; i < 366; i++) {

View file

@ -94,12 +94,14 @@ export function renderCards(
total: n, total: n,
}; };
}); });
const xMax = summed.slice(-1)[0]; // ensuring a non-zero range makes a better animation
// in the empty data case
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 trans = svg.transition().duration(600) as any; const trans = svg.transition().duration(600) as any;
x.range([bounds.marginLeft, bounds.width - bounds.marginRight]); x.range([bounds.marginLeft, bounds.width - bounds.marginRight - bounds.marginLeft]);
const tooltipText = (d: any): string => { const tooltipText = (d: any): string => {
const pct = ((d.count[1] / xMax) * 100).toFixed(2); const pct = ((d.count[1] / xMax) * 100).toFixed(2);
@ -113,7 +115,7 @@ export function renderCards(
showTooltip(tooltipText(d), x, y); showTooltip(tooltipText(d), x, y);
}) })
.transition(trans) .transition(trans)
.attr("width", (d) => x(d.total) - bounds.marginLeft); .attr("width", (d) => x(d.total));
}; };
data.reverse(); data.reverse();

View file

@ -88,6 +88,11 @@ export function buildHistogram(
.domain(x.domain() as any) .domain(x.domain() as any)
.thresholds(x.ticks(desiredBars))(data.entries() as any); .thresholds(x.ticks(desiredBars))(data.entries() as any);
// empty graph?
if (!sum(bins, (bin) => bin.length)) {
return null;
}
const adjustedRange = scaleLinear().range([0.8, 0.3]); const adjustedRange = scaleLinear().range([0.8, 0.3]);
const colourScale = scaleSequential((n) => const colourScale = scaleSequential((n) =>
interpolateGreens(adjustedRange(n)) interpolateGreens(adjustedRange(n))

View file

@ -126,6 +126,16 @@ body.night-mode {
color: $night-fg; color: $night-fg;
} }
.no-data {
text {
text-anchor: middle;
fill: grey;
}
rect {
fill: $day-bg;
}
}
.night-mode { .night-mode {
.graph-tooltip { .graph-tooltip {
background: $night-bg; background: $night-bg;
@ -139,6 +149,9 @@ body.night-mode {
fill: $night-fg; fill: $night-fg;
opacity: 0.1; opacity: 0.1;
} }
.no-data rect {
fill: $night-bg;
}
} }
.centered { .centered {

View file

@ -7,6 +7,7 @@
@typescript-eslint/ban-ts-ignore: "off" */ @typescript-eslint/ban-ts-ignore: "off" */
import pb from "../backend/proto"; import pb from "../backend/proto";
import { Selection } from "d3-selection";
async function fetchData(search: string, days: number): Promise<Uint8Array> { async function fetchData(search: string, days: number): Promise<Uint8Array> {
const resp = await fetch("/_anki/graphData", { const resp = await fetch("/_anki/graphData", {
@ -66,3 +67,14 @@ export function defaultGraphBounds(): GraphBounds {
marginBottom: 40, marginBottom: 40,
}; };
} }
export function setDataAvailable(
svg: Selection<SVGElement, any, any, any>,
available: boolean
): void {
svg.select(".no-data")
.attr("pointer-events", available ? "none" : "all")
.transition()
.duration(600)
.attr("opacity", available ? 0 : 1);
}

View file

@ -13,7 +13,7 @@ import { scaleLinear, ScaleLinear, ScaleSequential } from "d3-scale";
import { axisBottom, axisLeft } from "d3-axis"; import { axisBottom, axisLeft } from "d3-axis";
import { area, curveBasis } from "d3-shape"; import { area, curveBasis } from "d3-shape";
import { showTooltip, hideTooltip } from "./tooltip"; import { showTooltip, hideTooltip } from "./tooltip";
import { GraphBounds } from "./graphs"; import { GraphBounds, setDataAvailable } from "./graphs";
export interface HistogramData { export interface HistogramData {
scale: ScaleLinear<number, number>; scale: ScaleLinear<number, number>;
@ -33,13 +33,20 @@ export interface HistogramData {
export function histogramGraph( export function histogramGraph(
svgElem: SVGElement, svgElem: SVGElement,
bounds: GraphBounds, bounds: GraphBounds,
data: HistogramData data: HistogramData | null
): void { ): void {
const binValue = data.binValue ?? ((bin: any): number => bin.length as number);
const svg = select(svgElem); const svg = select(svgElem);
const trans = svg.transition().duration(600) as any; const trans = svg.transition().duration(600) as any;
if (!data) {
setDataAvailable(svg, false);
return;
} else {
setDataAvailable(svg, true);
}
const binValue = data.binValue ?? ((bin: any): number => bin.length as number);
const x = data.scale.range([bounds.marginLeft, bounds.width - bounds.marginRight]); const x = data.scale.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
svg.select<SVGGElement>(".x-ticks") svg.select<SVGGElement>(".x-ticks")
.transition(trans) .transition(trans)

View file

@ -13,7 +13,7 @@ import { select, mouse } from "d3-selection";
import { scaleLinear, scaleBand, scaleSequential } from "d3-scale"; import { scaleLinear, scaleBand, scaleSequential } from "d3-scale";
import { axisBottom, axisLeft } from "d3-axis"; import { axisBottom, axisLeft } from "d3-axis";
import { showTooltip, hideTooltip } from "./tooltip"; import { showTooltip, hideTooltip } from "./tooltip";
import { GraphBounds } from "./graphs"; import { GraphBounds, setDataAvailable } from "./graphs";
import { area, curveBasis } from "d3-shape"; import { area, curveBasis } from "d3-shape";
import { I18n } from "../i18n"; import { I18n } from "../i18n";
@ -66,6 +66,13 @@ export function renderHours(
const svg = select(svgElem); const svg = select(svgElem);
const trans = svg.transition().duration(600) as any; const trans = svg.transition().duration(600) as any;
if (!yMax) {
setDataAvailable(svg, false);
return;
} else {
setDataAvailable(svg, true);
}
const x = scaleBand() const x = scaleBand()
.domain(data.map((d) => d.hour.toString())) .domain(data.map((d) => d.hour.toString()))
.range([bounds.marginLeft, bounds.width - bounds.marginRight]) .range([bounds.marginLeft, bounds.width - bounds.marginRight])

View file

@ -7,7 +7,7 @@
*/ */
import pb from "../backend/proto"; import pb from "../backend/proto";
import { extent, histogram, quantile } from "d3-array"; import { extent, histogram, quantile, sum } from "d3-array";
import { scaleLinear, scaleSequential } from "d3-scale"; import { scaleLinear, scaleSequential } from "d3-scale";
import { CardQueue } from "../cards"; import { CardQueue } from "../cards";
import { HistogramData } from "./histogram-graph"; import { HistogramData } from "./histogram-graph";
@ -98,6 +98,11 @@ export function prepareIntervalData(
.domain(scale.domain() as any) .domain(scale.domain() as any)
.thresholds(scale.ticks(desiredBars))(allIntervals); .thresholds(scale.ticks(desiredBars))(allIntervals);
// empty graph?
if (!sum(bins, (bin) => bin.length)) {
return null;
}
// start slightly darker // start slightly darker
const shiftedMin = xMin! - Math.round((xMax - xMin!) / 10); const shiftedMin = xMin! - Math.round((xMax - xMin!) / 10);
const colourScale = scaleSequential(interpolateBlues).domain([shiftedMin, xMax]); const colourScale = scaleSequential(interpolateBlues).domain([shiftedMin, xMax]);

View file

@ -18,7 +18,7 @@ import { select, mouse } from "d3-selection";
import { scaleLinear, scaleSequential } from "d3-scale"; import { scaleLinear, scaleSequential } from "d3-scale";
import { axisBottom, axisLeft } from "d3-axis"; import { axisBottom, axisLeft } from "d3-axis";
import { showTooltip, hideTooltip } from "./tooltip"; import { showTooltip, hideTooltip } from "./tooltip";
import { GraphBounds } from "./graphs"; import { GraphBounds, setDataAvailable } from "./graphs";
import { area, curveBasis } from "d3-shape"; import { area, curveBasis } from "d3-shape";
import { min, histogram, sum, max, Bin, cumsum } from "d3-array"; import { min, histogram, sum, max, Bin, cumsum } from "d3-array";
import { timeSpan, dayLabel } from "../time"; import { timeSpan, dayLabel } from "../time";
@ -116,6 +116,9 @@ export function renderReviews(
showTime: boolean, showTime: boolean,
i18n: I18n i18n: I18n
): void { ): void {
const svg = select(svgElem);
const trans = svg.transition().duration(600) as any;
const xMax = 1; const xMax = 1;
let xMin = 0; let xMin = 0;
// cap max to selected range // cap max to selected range
@ -144,8 +147,13 @@ export function renderReviews(
.domain(x.domain() as any) .domain(x.domain() as any)
.thresholds(x.ticks(desiredBars))(sourceMap.entries() as any); .thresholds(x.ticks(desiredBars))(sourceMap.entries() as any);
const svg = select(svgElem); // empty graph?
const trans = svg.transition().duration(600) as any; if (!sum(bins, (bin) => bin.length)) {
setDataAvailable(svg, false);
return;
} else {
setDataAvailable(svg, true);
}
x.range([bounds.marginLeft, bounds.width - bounds.marginRight]); x.range([bounds.marginLeft, bounds.width - bounds.marginRight]);
svg.select<SVGGElement>(".x-ticks") svg.select<SVGGElement>(".x-ticks")