src/components/table/TableView.svelte (239 lines of code) (raw):

<script> import { Button, ButtonGroup } from '@graph-paper/button'; import { isEmpty } from 'lodash'; import DataTable from './DataTable.svelte'; import Row from './Row.svelte'; import Cell from './Cell.svelte'; import ProportionSM from './ProportionSM.svelte'; import CategoricalMenu from '../explore/CategoricalMenu.svelte'; import Pagination from '../controls/Pagination.svelte'; import { formatCount, formatPercentDecimal, ymd, timecode, formatPercent, } from '../../utils/formatters'; import { convertValueToPercentage } from '../../utils/probe-utils'; import { backwards } from '../../utils/iterables'; import { store } from '../../state/store'; export let data; // nested as key, aggregation_type export let aggregationLevel = 'build_id'; export let key = 'proportions'; export let keyFormatter = (v) => v; export let valueFormatter = formatPercentDecimal; export let tooltipFormatter = () => undefined; export let visibleBuckets; export let colorMap; // bucketColorMap export let pageSize = 10; export let bucketTypeLabel = 'Categories'; export let bucketOptions; export let densityMetricType; let showHistogramData = false; let categoricalHistograms = ['categorical', 'enumerated']; let largestAudience; $: largestAudience = Math.max(...data.map((d) => d.audienceSize)); let totalPages = 0; let currentPage = 0; let updatedData = []; $: if ($store.productDimensions.normalizationType) { // due to the way the data is structured, we need to // empty non-normalized data let filtered = $store.productDimensions.normalizationType === 'non_normalized' ? data.filter((d) => !isEmpty(d.non_norm_histogram)) : data; updatedData = filtered.map((d) => ({ ...d, tableHistogramData: d[densityMetricType].length > 1 && convertValueToPercentage(d[densityMetricType]), })); totalPages = Math.ceil(updatedData.length / pageSize); } </script> <style> span.h { font-weight: bold; color: var(--cool-gray-700); font-size: var(--text-01); } span.bucket { font-weight: normal; } </style> <div style="border-bottom: var(--space-base) solid var(--cool-gray-100);"> <div style=" margin-top: var(--space-2x); margin-bottom: var(--space-2x); padding-left: var(--space-4x); padding-right: var(--space-4x); " > <Pagination on:page={(evt) => { currentPage = evt.detail.page; store.setField('currentPage', currentPage || 0); }} {totalPages} currentPage={Number($store.currentPage)} /> </div> {#if bucketTypeLabel === 'percentiles'} <div style="display: flex; justify-content: flex-end; padding: 1em;"> <ButtonGroup> <Button tooltip="Show Percentile Data" on:click={() => { showHistogramData = false; }} label="Percentile Data" toggled={!showHistogramData} level="medium" compact /> <Button tooltip="Show Histogram Data" on:click={() => { showHistogramData = true; }} label="Histogram Data" level="medium" toggled={showHistogramData} compact /> </ButtonGroup> </div> {/if} {#if categoricalHistograms.includes($store.probe.kind) && updatedData.length > 0} <div style="display: flex; justify-content: flex-end; padding: 1em;"> <CategoricalMenu {updatedData} activeBuckets={$store.activeBuckets} bucketColorMap={colorMap} {bucketOptions} /> </div> {/if} <DataTable overflowX={true}> <thead> <Row header> <Cell backgroundColor="var(--cool-gray-subtle)" topBorder={true} bottomBorderThickness="2px" freezeX size="max" tooltip="the {aggregationLevel === 'build_id' ? ' build id' : 'version'} associated with this row" > <span class="h"> {#if aggregationLevel === 'build_id'}Build ID{:else}Version{/if} </span> </Cell> <Cell topBorder rightBorder backgroundColor="var(--cool-gray-subtle)" bottomBorderThickness="2px" freezeX align="left" tooltip="the total number of clients associated with this {aggregationLevel === 'build_id' ? ' build id' : 'version'}" > <span class="h"> Clients </span> </Cell> <!-- <Cell freezeX rightBorder></Cell> --> {#if showHistogramData} {#each updatedData[0].tableHistogramData as bucket (bucket.bin)} <Cell backgroundColor="var(--cool-gray-subtle)" size="small" text topBorder={true} bottomBorderThickness="2px" > <span class="percentile-label-block" style="background-color: black" /> <span class="bucket">{bucket.bin}</span> </Cell> {/each} {:else} <!-- show percentile data --> {#each visibleBuckets as bucket} <Cell backgroundColor="var(--cool-gray-subtle)" tooltip={tooltipFormatter(bucket)} size="small" text topBorder={true} bottomBorderThickness="2px" > <span class="percentile-label-block" style="background-color: {colorMap(bucket)}" /> <span class="bucket">{keyFormatter(bucket)}</span> </Cell> {/each} {/if} </Row> </thead> <tbody> {#each [...backwards(updatedData)].slice(currentPage * pageSize, (currentPage + 1) * pageSize) as row, i (row.version + ymd(row.label) + timecode(row.label))} <Row> <Cell freezeX backgroundColor="white"> <div class="build-version"> {#if aggregationLevel === 'build_id'} <span style="font-weight: bold; color: var(--cool-gray-550);"> {ymd(row.label)} </span> <div>{timecode(row.label)}</div> {:else} <div>{row.version}</div> {/if} </div> </Cell> <Cell rightBorder freezeX> <div style="padding: var(--space-base);"> {formatCount(row.audienceSize)} <ProportionSM value={row.audienceSize / largestAudience} /> </div> </Cell> <!-- <Cell freezeX rightBorder> </Cell> --> {#if showHistogramData} {#each row.tableHistogramData as bucket} <Cell size="tiny" backgroundColor="white"> <span> {formatCount(bucket.value * row.audienceSize)} ({formatPercent( bucket.value )}) </span> </Cell> {/each} {:else} {#each visibleBuckets as bucket, j} <Cell size="tiny" backgroundColor="white"> <span style="color:{formatPercentDecimal(row[key][bucket]) !== '0.00%' ? 'var(--cool-gray-700)' : 'var(--cool-gray-200)'};" > {valueFormatter(row[key][bucket])} </span> </Cell> {/each} {/if} </Row> {/each} </tbody> </DataTable> </div>