Skip to content

Commit

Permalink
feat: allow for adding reference plots to a comparison graph (#550)
Browse files Browse the repository at this point in the history
Fixes #549
  • Loading branch information
chrispcampbell authored Oct 8, 2024
1 parent d8ce779 commit 4f0495a
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 57 deletions.
15 changes: 14 additions & 1 deletion examples/sample-check-tests/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,20 @@ export function getConfigOptions(bundleL: Bundle, bundleR: Bundle, opts?: Config
thresholds: [1, 5, 10],
specs: comparisonSpecs,
datasets: {
renamedDatasetKeys
renamedDatasetKeys,
referencePlotsForDataset: (dataset, scenario) => {
if (dataset.key === 'Model__output_x' && scenario.title.startsWith('Input A')) {
return [
{
datasetKey: 'StaticData__static_s',
color: 'orange',
style: 'dashed',
lineWidth: 2
}
]
}
return []
}
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions packages/check-core/src/comparison/config/comparison-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ import { parseComparisonSpecs } from './parse/comparison-parser'
import type { ComparisonResolvedDefs } from './resolve/comparison-resolver'
import { resolveComparisonSpecs } from './resolve/comparison-resolver'

/**
* Describes an extra plot to be shown in a comparison graph.
*/
export interface ComparisonPlot {
/** The dataset key for the plot. */
datasetKey: DatasetKey
/** The plot color. */
color: string
/** The plot style. If undefined, defaults to 'normal'. */
style?: 'normal' | 'dashed'
/** The plot line width, in px units. If undefined, a default width will be used. */
lineWidth?: number
}

export interface ComparisonDatasetOptions {
/**
* The mapping of renamed dataset keys (old or "left" name as the map key,
Expand All @@ -25,6 +39,13 @@ export interface ComparisonDatasetOptions {
* datasets (for example, to omit datasets that are not relevant).
*/
datasetKeysForScenario?: (allDatasetKeys: DatasetKey[], scenario: ComparisonScenario) => DatasetKey[]
/**
* An optional function that allows for including additional reference plots
* on a comparison graph for a given dataset and scenario. By default, no
* additional reference plots are included, but if a custom function is
* provided, it can return an array of `ComparisonPlot` objects.
*/
referencePlotsForDataset?: (dataset: ComparisonDataset, scenario: ComparisonScenario) => ComparisonPlot[]
/**
* An optional function that allows for customizing the set of context graphs
* that are shown for a given dataset and scenario. By default, all graphs in
Expand Down
23 changes: 22 additions & 1 deletion packages/check-core/src/comparison/config/comparison-datasets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { BundleGraphId, ModelSpec } from '../../bundle/bundle-types'
import type { OutputVar } from '../../bundle/var-types'
import type { DatasetKey } from '../../_shared/types'
import type { ComparisonDataset, ComparisonScenario } from '../_shared/comparison-resolved-types'
import type { ComparisonDatasetOptions } from './comparison-config'
import type { ComparisonDatasetOptions, ComparisonPlot } from './comparison-config'

/**
* Provides access to the set of dataset definitions (`ComparisonDataset` instances) that are used
Expand All @@ -30,6 +30,15 @@ export interface ComparisonDatasets {
*/
getDatasetKeysForScenario(scenario: ComparisonScenario): DatasetKey[]

/**
* Return the reference plots that should be shown in the comparison graph for the
* given dataset and scenario.
*
* @param datasetKey The key for the dataset.
* @param scenario The scenario for which the dataset will be displayed.
*/
getReferencePlotsForDataset(datasetKey: DatasetKey, scenario: ComparisonScenario): ComparisonPlot[]

/**
* Return the context graph IDs that should be shown for the given dataset and scenario.
*
Expand Down Expand Up @@ -157,6 +166,18 @@ class ComparisonDatasetsImpl implements ComparisonDatasets {
}
}

// from ComparisonDatasets interface
getReferencePlotsForDataset(datasetKey: DatasetKey, scenario: ComparisonScenario): ComparisonPlot[] {
if (this.datasetOptions?.referencePlotsForDataset !== undefined) {
// Delegate to the custom function
const dataset = this.getDataset(datasetKey)
if (dataset !== undefined) {
return this.datasetOptions.referencePlotsForDataset(dataset, scenario)
}
}
return []
}

// from ComparisonDatasets interface
getContextGraphIdsForDataset(datasetKey: DatasetKey, scenario: ComparisonScenario): BundleGraphId[] {
const dataset = this.getDataset(datasetKey)
Expand Down
3 changes: 2 additions & 1 deletion packages/check-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ export * from './comparison/config/comparison-spec-types'
export type {
ComparisonConfig,
ComparisonDatasetOptions,
ComparisonOptions
ComparisonOptions,
ComparisonPlot
} from './comparison/config/comparison-config'
export type { ComparisonScenarios } from './comparison/config/comparison-scenarios'
export type { ComparisonDatasets } from './comparison/config/comparison-datasets'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import type {
ScenarioSpec
} from '@sdeverywhere/check-core'

import type { ComparisonGraphViewModel, PlotStyle, Point, RefPlot } from '../../graphs/comparison-graph-vm'
import type {
ComparisonGraphPlot,
ComparisonGraphPlotStyle,
ComparisonGraphViewModel,
Point
} from '../../graphs/comparison-graph-vm'
import { pointsFromDataset } from '../../graphs/comparison-graph-vm'

let requestId = 1
Expand Down Expand Up @@ -219,26 +224,45 @@ export class CheckSummaryGraphBoxViewModel {
}
}

// Add reference lines
const refPlots: RefPlot[] = []
// Add the primary plot
const plots: ComparisonGraphPlot[] = []
plots.push({
points: primaryPoints,
color: 'deepskyblue',
style: 'normal'
})

const addRefPlot = (op: CheckPredicateOp, style: PlotStyle | undefined, delta = 0) => {
// Add the primary and reference plots
const addRefPlot = (
op: CheckPredicateOp,
style: ComparisonGraphPlotStyle | undefined,
delta = 0,
lineWidth?: number
) => {
const color = 'green'
if (lineWidth === undefined) {
lineWidth = 1
}
const constantRef = this.opConstantRefs.get(op)
if (constantRef !== undefined) {
if (minPredTime === maxPredTime) {
// Add a single point
refPlots.push({
plots.push({
points: [{ x: minPredTime, y: constantRef + delta }],
style
color,
style,
lineWidth
})
} else {
// Add a line segment for the constant
refPlots.push({
plots.push({
points: [
{ x: minPredTime, y: constantRef + delta },
{ x: maxPredTime, y: constantRef + delta }
],
style
color,
style,
lineWidth
})
}
return
Expand All @@ -254,9 +278,11 @@ export class CheckSummaryGraphBoxViewModel {
return { x: p.x, y: p.y + delta }
})
}
refPlots.push({
plots.push({
points: filtered,
style
color,
style,
lineWidth
})
}
}
Expand All @@ -271,21 +297,19 @@ export class CheckSummaryGraphBoxViewModel {
addRefPlot('gte', hasLt ? 'fill-to-next' : 'fill-above')
addRefPlot('lt', hasGt ? 'normal' : 'fill-below')
addRefPlot('lte', hasGt ? 'normal' : 'fill-below')
addRefPlot('eq', 'wide')
addRefPlot('eq', 'normal', 0, 5)

// Handle `approx` specially by adding two reference lines (one for the
// lower bound and one for the upper bound)
const tolerance = this.predicateReport.tolerance || 0.1
addRefPlot('approx', 'fill-to-next', -tolerance)
addRefPlot('approx', 'normal', tolerance)
addRefPlot('approx', 'dashed')
addRefPlot('approx', 'dashed', 0)

// Create the comparison graph view model
const comparisonGraphViewModel: ComparisonGraphViewModel = {
key: this.baseRequestKey,
refPlots,
pointsL: [],
pointsR: primaryPoints,
plots,
xMin: undefined,
xMax: undefined
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import { diffDatasets } from '@sdeverywhere/check-core'
import { getBucketIndex } from '../_shared/buckets'
import { datasetSpan } from '../_shared/spans'

import type { ComparisonGraphViewModel, Point } from '../../graphs/comparison-graph-vm'
import type {
ComparisonGraphPlot,
ComparisonGraphPlotStyle,
ComparisonGraphViewModel,
Point
} from '../../graphs/comparison-graph-vm'
import { pointsFromDataset } from '../../graphs/comparison-graph-vm'

let requestId = 1
Expand Down Expand Up @@ -69,11 +74,15 @@ export class CompareDetailBoxViewModel {
}
this.dataRequested = true

const datasetKeys: DatasetKey[] = [this.datasetKey]
const refPlots = this.comparisonConfig.datasets.getReferencePlotsForDataset(this.datasetKey, this.scenario)
datasetKeys.push(...refPlots.map(plot => plot.datasetKey))

this.dataCoordinator.requestDatasetMaps(
this.requestKey,
this.scenario.specL,
this.scenario.specR,
[this.datasetKey],
datasetKeys,
(datasetMapL, datasetMapR) => {
if (!this.dataRequested) {
return
Expand Down Expand Up @@ -141,6 +150,31 @@ export class CompareDetailBoxViewModel {
const pointsL = pointsFromDataset(datasetMapL?.get(this.datasetKey))
const pointsR = pointsFromDataset(datasetMapR?.get(this.datasetKey))

const plots: ComparisonGraphPlot[] = []
function addPlot(points: Point[], color: string, style?: ComparisonGraphPlotStyle, lineWidth?: number): void {
plots.push({
points,
color,
style: style || 'normal',
lineWidth
})
}

// Add the primary plots. We add the right data points first so that they are drawn
// on top of the left data points.
// TODO: Use the colors defined in CSS (or make them configurable through other means);
// these should not be hardcoded here
addPlot(pointsR, 'deepskyblue')
addPlot(pointsL, 'crimson')

// Add the secondary/reference plots
// TODO: Currently these are always shown behind the primary plots; might need to make
// this configurable
for (const refPlot of refPlots) {
const points = pointsFromDataset(datasetMapR?.get(refPlot.datasetKey))
addPlot(points, refPlot.color, refPlot.style, refPlot.lineWidth)
}

// Find the min and max y values for all datasets
let yMin = Number.POSITIVE_INFINITY
let yMax = Number.NEGATIVE_INFINITY
Expand All @@ -154,8 +188,9 @@ export class CompareDetailBoxViewModel {
}
}
}
setExtents(pointsL)
setExtents(pointsR)
for (const plot of plots) {
setExtents(plot.points)
}
this.writableYRange.set({
min: yMin,
max: yMax
Expand All @@ -164,9 +199,7 @@ export class CompareDetailBoxViewModel {
// Create the graph view model
const comparisonGraphViewModel: ComparisonGraphViewModel = {
key: this.requestKey,
refPlots: [],
pointsL,
pointsR,
plots,
xMin,
xMax,
yMin: this.activeYMin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import type { ChartDataSets } from 'chart.js'
import { Chart } from 'chart.js'
import type { ComparisonGraphViewModel, PlotStyle, Point } from './comparison-graph-vm'
import type { ComparisonGraphPlot, ComparisonGraphViewModel, Point } from './comparison-graph-vm'

const gridColor = '#444'
const fontFamily = 'Roboto Condensed'
Expand Down Expand Up @@ -119,20 +119,10 @@ function createChart(canvas: HTMLCanvasElement, viewModel: ComparisonGraphViewMo
}

let dataMaxX = Number.NEGATIVE_INFINITY
function addPlot(points: Point[], color: string, style?: PlotStyle): void {
const normalWidth = 3
let borderWidth = normalWidth
if (style) {
// Use thin reference lines
borderWidth = 1
}

function addPlot(plot: ComparisonGraphPlot): void {
let borderDash: number[]
let fill: string | boolean = false
switch (style) {
case 'wide':
borderWidth = normalWidth * 2
break
switch (plot.style) {
case 'dashed':
borderDash = [8, 2]
break
Expand All @@ -152,28 +142,28 @@ function createChart(canvas: HTMLCanvasElement, viewModel: ComparisonGraphViewMo
let backgroundColor = undefined
if (fill !== false) {
// Make the fill less translucent when there is only a single point
const opacity = points.length > 1 ? 0.1 : 0.3
const opacity = plot.points.length > 1 ? 0.1 : 0.3
backgroundColor = `rgba(0, 128, 0, ${opacity})`
}

let pointRadius = 0
let pointBackgroundColor = undefined
if (points.length === 1 && style !== 'dashed') {
if (plot.points.length === 1 && plot.style !== 'dashed') {
pointRadius = 5
pointBackgroundColor = color
pointBackgroundColor = plot.color
}

// Find the maximum x value in the datasets
for (const p of points) {
for (const p of plot.points) {
if (p.x > dataMaxX) {
dataMaxX = p.x
}
}

datasets.push({
data: points,
borderColor: color,
borderWidth,
data: plot.points,
borderColor: plot.color,
borderWidth: plot.lineWidth !== undefined ? plot.lineWidth : 3,
borderDash,
backgroundColor,
fill,
Expand All @@ -187,12 +177,8 @@ function createChart(canvas: HTMLCanvasElement, viewModel: ComparisonGraphViewMo

// Add the right data points first so that they are drawn on top of the
// left data points
// TODO: Use the colors defined in CSS (or make them configurable through other means);
// these should not be hardcoded here
addPlot(viewModel.pointsR, 'deepskyblue')
addPlot(viewModel.pointsL, 'crimson')
for (const refPlot of viewModel.refPlots) {
addPlot(refPlot.points, 'green', refPlot.style || 'normal')
for (const plot of viewModel.plots) {
addPlot(plot)
}

// Customize the x-axis range
Expand Down
Loading

0 comments on commit 4f0495a

Please sign in to comment.