LCOV - code coverage report
Current view: top level - dashql-app/src/connections/hyper - hyper_log_chart_specs.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 105 105
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import type { TopLevelSpec } from 'vega-lite';
       2              : 
       3              : import type { ChartColors } from '../../ui/vegalite/vegalite_theme.js';
       4              : 
       5              : /// The params of the bounds of the time window, in milliseconds since the epoch
       6            1 : export const WINDOW_START_SIGNAL = 'windowStart';
       7            1 : export const WINDOW_END_SIGNAL = 'windowEnd';
       8              : 
       9              : /// The dataset of the rows of a chart
      10            1 : export const ROWS_DATASET = 'rows';
      11              : 
      12              : // The axis labels are compact for small charts and leave out the zero of the baseline
      13              : 
      14              : /// An axis label of a duration in seconds, e.g. 850µs, 12ms, 1.2s
      15            1 : const DURATION_LABEL = "datum.value == 0 ? '' : datum.value >= 1 ? format(datum.value, '~r') + 's'"
      16              :     + " : datum.value >= 0.001 ? format(datum.value * 1000, '~r') + 'ms'"
      17              :     + " : format(datum.value * 1000000, '~r') + 'µs'";
      18              : /// An axis label of a memory size in MB, e.g. 64MB, 1.5GB
      19            1 : const MEMORY_LABEL = "datum.value == 0 ? '' : datum.value >= 1024 ? format(datum.value / 1024, '~r') + 'GB' : format(datum.value, '~r') + 'MB'";
      20              : /// An axis label of a count, e.g. 12, 1.2k, 3M
      21            1 : const COUNT_LABEL = "datum.value == 0 ? '' : format(datum.value, '~s')";
      22              : /// An axis label of a load, e.g. 50%
      23            1 : const LOAD_LABEL = "datum.value == 0 ? '' : format(datum.value, '.0%')";
      24              : 
      25              : /// The y axis of a small chart: a gridline at the baseline and one above it
      26            1 : const Y_AXIS = { tickCount: 2, labelPadding: 3 };
      27              : 
      28            1 : const WINDOW_PARAMS = [
      29            1 :     { name: WINDOW_START_SIGNAL, value: 0 },
      30            1 :     { name: WINDOW_END_SIGNAL, value: 1 },
      31            1 : ];
      32              : 
      33              : /// The x channel of the time of a row in the sliding window
      34            1 : const TIME_X = {
      35            1 :     field: 't',
      36            1 :     type: 'temporal',
      37            1 :     title: null,
      38            1 :     scale: { domainMin: { expr: WINDOW_START_SIGNAL }, domainMax: { expr: WINDOW_END_SIGNAL } },
      39            1 :     axis: { format: '%H:%M', tickCount: 3, labelPadding: 3, labelSeparation: 8 },
      40            1 : } as const;
      41              : 
      42              : /// The kind of a value of queries, it picks the axis labels
      43              : export type QueryValueKind = 'duration' | 'count' | 'memory';
      44              : 
      45            1 : const QUERY_VALUE_LABELS: Record<QueryValueKind, string> = {
      46            1 :     duration: DURATION_LABEL,
      47            1 :     count: COUNT_LABEL,
      48            1 :     memory: MEMORY_LABEL,
      49            1 : };
      50              : 
      51              : /// A rule at the row nearest to the pointer with a tooltip of that row
      52            1 : function hoverRule(colors: ChartColors, tooltip: object[]) {
      53            1 :     return {
      54            1 :         data: { name: ROWS_DATASET },
      55            1 :         params: [{
      56            1 :             name: 'hover',
      57            1 :             select: { type: 'point', encodings: ['x'], nearest: true, on: 'pointerover', clear: 'pointerout' },
      58            1 :         }],
      59            1 :         mark: { type: 'rule', color: colors.title },
      60            1 :         encoding: {
      61            1 :             x: TIME_X,
      62            1 :             opacity: { condition: { param: 'hover', empty: false, value: 1 }, value: 0 },
      63            1 :             tooltip,
      64            1 :         },
      65            1 :     };
      66            1 : }
      67              : 
      68              : /// A field of a row in the tooltip of a chart
      69              : export interface TooltipField {
      70              :     /// The field of the row
      71              :     field: string;
      72              :     /// The title in the tooltip
      73              :     title: string;
      74              : }
      75              : 
      76              : /// A value per bucket of time as bars that span their bucket, with a tooltip of the bucket on its bar.
      77              : /// The rows are `{ t0, t1, clock, ... }` with the bucket from t0 to t1 and its time of day as clock.
      78              : /// A chart draws one bar per bucket, its marks do not grow with the queries of the window.
      79            1 : export function bucketChartSpec(field: string, kind: QueryValueKind, tooltip: readonly TooltipField[]): TopLevelSpec {
      80            1 :     return {
      81            1 :         params: WINDOW_PARAMS,
      82            1 :         data: { name: ROWS_DATASET },
      83              :         // A gap of 1px between the bars
      84            1 :         mark: { type: 'bar', clip: true, xOffset: 0.5, x2Offset: -0.5 },
      85            1 :         encoding: {
      86            1 :             x: { ...TIME_X, field: 't0' },
      87            1 :             x2: { field: 't1' },
      88              :             // The bars span a range of time, they need a range of values as well to not draw horizontally
      89            1 :             y2: { datum: 0 },
      90            1 :             y: {
      91            1 :                 field,
      92            1 :                 type: 'quantitative',
      93            1 :                 title: null,
      94            1 :                 axis: { ...Y_AXIS, labelExpr: QUERY_VALUE_LABELS[kind], ...(kind === 'count' ? { tickMinStep: 1 } : {}) },
      95            1 :             },
      96            1 :             tooltip: [{ field: 'clock', title: 'Time' }, ...tooltip],
      97            1 :         },
      98            1 :     } as TopLevelSpec;
      99            1 : }
     100              : 
     101              : /// The kind of the values of a series chart, it picks the axis labels and the tooltip format
     102              : export type SeriesKind = 'memory' | 'load';
     103              : 
     104            1 : const SERIES_LABELS: Record<SeriesKind, string> = {
     105            1 :     memory: MEMORY_LABEL,
     106            1 :     load: LOAD_LABEL,
     107            1 : };
     108            1 : const SERIES_TOOLTIP_FORMATS: Record<SeriesKind, string> = {
     109            1 :     memory: '.4~r',
     110            1 :     load: '.0%',
     111            1 : };
     112              : 
     113              : /// A series of a series chart
     114              : export interface ChartSeries {
     115              :     /// The name of the series in the rows and the tooltip
     116              :     name: string;
     117              :     /// The color of the series
     118              :     color: (colors: ChartColors) => string;
     119              :     /// Draw an area wash under the line
     120              :     area?: boolean;
     121              : }
     122              : 
     123              : /// A line per series over time, with a rule and a tooltip of all series at the time nearest to the pointer.
     124              : /// The rows are `{ t, series, value }`.
     125            1 : export function seriesSpec(colors: ChartColors, series: readonly ChartSeries[], kind: SeriesKind): TopLevelSpec {
     126            1 :     const color = {
     127            1 :         field: 'series',
     128            1 :         type: 'nominal',
     129            1 :         scale: { domain: series.map(s => s.name), range: series.map(s => s.color(colors)) },
     130            1 :         legend: null,
     131            1 :     };
     132            1 :     const y = {
     133            1 :         field: 'value',
     134            1 :         type: 'quantitative',
     135            1 :         title: null,
     136            1 :         scale: kind === 'load' ? { domain: [0, 1] } : { zero: true },
     137            1 :         axis: { ...Y_AXIS, labelExpr: SERIES_LABELS[kind] },
     138            1 :     };
     139            1 :     const areas = series.filter(s => s.area).map(s => s.name);
     140            1 :     return {
     141            1 :         params: WINDOW_PARAMS,
     142            1 :         layer: [
     143            1 :             ...(areas.length > 0 ? [{
     144            1 :                 data: { name: ROWS_DATASET },
     145            1 :                 transform: [{ filter: { field: 'series', oneOf: areas } }],
     146            1 :                 mark: { type: 'area', opacity: 0.1, clip: true },
     147            1 :                 encoding: { x: TIME_X, y, color },
     148            1 :             }] : []),
     149            1 :             {
     150            1 :                 data: { name: ROWS_DATASET },
     151            1 :                 mark: { type: 'line', clip: true },
     152            1 :                 encoding: { x: TIME_X, y, color },
     153            1 :             },
     154            1 :             {
     155            1 :                 ...hoverRule(colors, [
     156            1 :                     { field: 't', type: 'temporal', format: '%H:%M:%S', title: 'Time' },
     157            1 :                     ...series.map(s => ({ field: s.name, type: 'quantitative', format: SERIES_TOOLTIP_FORMATS[kind], title: s.name })),
     158            1 :                 ]),
     159            1 :                 transform: [{ pivot: 'series', value: 'value', groupby: ['t'] }],
     160            1 :             },
     161            1 :         ],
     162            1 :     } as TopLevelSpec;
     163            1 : }
        

Generated by: LCOV version 2.0-1