src/components/DistributionComparisonModal.svelte (205 lines of code) (raw):
<script>
import SliderSwitch from './controls/SliderSwitch.svelte';
import Modal from './Modal.svelte';
import DistributionComparisonGraph from './explore/DistributionComparisonGraph.svelte';
import DistributionChart from './explore/DistributionChart.svelte';
import { store } from '../state/store';
import routes from '../config/routes';
import { convertValueToPercentage } from '../utils/probe-utils';
export let densityMetricType;
export let topChartData;
export let bottomChartData;
export let distViewButtonId;
let normalized = $store.productDimensions.normalizationType === 'normalized';
let probeType = $store.probe.type;
let isCategoricalProbe =
$store.probe.details && $store.probe.details.kind === 'categorical';
let cumulative = false;
let activeCategoricalProbeLabels = isCategoricalProbe
? $store.probe.details.labels.filter((l) =>
$store.activeBuckets.includes(l)
)
: [];
let valueSelector = 'value';
// Change this value to adjust the minimum tick increment on the chart
export let tickIncrement = 2;
// eslint-disable-next-line prefer-destructuring
let innerHeight = window.innerHeight;
// eslint-disable-next-line prefer-destructuring
let innerWidth = window.innerWidth;
const getTopTick = function (rd, ld) {
let maxRd = rd ? Math.max(...rd.map((di) => di[valueSelector])) : 0;
let maxLd = ld ? Math.max(...ld.map((di) => di[valueSelector])) : 0;
let maxValue = Math.max(maxLd, maxRd);
let maxValPercent = Math.round(maxValue * 100);
let topTick =
(maxValPercent + (tickIncrement - (maxValPercent % tickIncrement))) / 100;
return Math.min(topTick, 1);
};
const roundVal = function (val) {
return Math.round(val * 10000) / 10000;
};
const makeCumulative = function (density) {
let values = density.map((d) => d[valueSelector]);
let cumulVals = [];
values.reduce((acc, curr) => {
let sum = Math.min(roundVal(acc + curr), 1);
cumulVals.push(sum);
return sum;
}, 0);
return cumulVals.map((val, idx) => ({ bin: density[idx].bin, value: val }));
};
const buildDensity = function (chartData) {
let density = chartData[densityMetricType];
if (isCategoricalProbe) {
let categoricalProbeLabels = $store.probe.details.labels;
density = density.filter((v, i) =>
$store.activeBuckets.includes(categoricalProbeLabels[i])
);
}
if (probeType === 'scalar' || !normalized) {
density = convertValueToPercentage(chartData[densityMetricType]);
}
return cumulative ? makeCumulative(density) : density;
};
</script>
<style>
.charts {
display: flex;
flex-direction: column;
}
.outer-flex {
position: relative;
display: flex;
min-width: 97vw;
min-height: 10vh;
max-height: 75vh;
margin-left: auto;
margin-right: auto;
height: auto;
flex-direction: row;
flex: 1;
}
.chart-fixed {
clear: both;
padding: 0.7%;
min-width: fit-content;
}
.chart-fixed > p {
font-size: small;
}
.percentiles {
display: flex;
flex-grow: 1;
padding: 2%;
flex-direction: column;
}
.dist-modal-details {
height: 100%;
}
</style>
<svelte:window bind:innerWidth bind:innerHeight />
{#if topChartData && bottomChartData}
{@const topChartDensity = buildDensity(topChartData)}
{@const topChartSampleCount = topChartData.sample_count}
{@const bottomChartDensity = buildDensity(bottomChartData)}
{@const bottomChartSampleCount = bottomChartData.sample_count}
{@const topTick = getTopTick(bottomChartDensity, topChartDensity)}
<Modal>
<div slot="trigger" let:open>
<button on:click={open} id={distViewButtonId} hidden
>Distribution comparison</button
>
</div>
<div slot="title">Distribution comparison - {$store.probe.name}</div>
<div class="outer-flex">
<div class="charts">
<div style="display: flex; padding: 1em;">
{#if !isCategoricalProbe}
<SliderSwitch
bind:checked={cumulative}
label="Cumulative mode: "
design="slider"
/>
{/if}
</div>
<div class="chart-fixed">
<p>Reference</p>
{#key cumulative}
{#key innerHeight}
{#key innerWidth}
<DistributionComparisonGraph
{innerHeight}
{innerWidth}
density={topChartDensity}
{topTick}
{tickIncrement}
{activeCategoricalProbeLabels}
>
<g slot="glam-body">
{#if bottomChartData}
<DistributionChart
{innerHeight}
{innerWidth}
density={topChartDensity}
{topTick}
{tickIncrement}
sampleCount={topChartSampleCount}
tooltipLocation="bottom"
{activeCategoricalProbeLabels}
/>
{/if}
</g>
</DistributionComparisonGraph>
{/key}
{/key}
{/key}
</div>
<div class="chart-fixed">
<p>Hovered</p>
{#key cumulative}
{#key innerHeight}
{#key innerWidth}
<DistributionComparisonGraph
{innerHeight}
{innerWidth}
density={bottomChartDensity}
{topTick}
{tickIncrement}
{activeCategoricalProbeLabels}
>
<g slot="glam-body">
{#if bottomChartData}
<DistributionChart
{innerHeight}
{innerWidth}
density={bottomChartDensity}
{topTick}
sampleCount={bottomChartSampleCount}
tooltipLocation="top"
{activeCategoricalProbeLabels}
/>
{/if}
</g>
</DistributionComparisonGraph>
{/key}
{/key}
{/key}
</div>
</div>
<div class="percentiles">
<div class="drawer graphic-body__details">
<div class="drawer-section-container dist-modal-details">
<h3 style="align-self: center;">{$store.probe.name}</h3>
<svelte:component
this={routes[$store.product].details}
showLinks={false}
/>
</div>
</div>
<div><hr /></div>
<slot name="comparisonSummary" />
</div>
</div>
</Modal>
{/if}