Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow for adding reference plots to a comparison graph #550

Merged
merged 1 commit into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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