Skip to content
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
9 changes: 9 additions & 0 deletions src/i18n/StringManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,21 @@ export class StringManager {
simulationStringProperty: ReadOnlyProperty<string>;
enableAutoTrackingStringProperty: ReadOnlyProperty<string>;
enableAutoTrackingDescriptionStringProperty: ReadOnlyProperty<string>;
showVelocityStringProperty: ReadOnlyProperty<string>;
showVelocityDescriptionStringProperty: ReadOnlyProperty<string>;
showAccelerationStringProperty: ReadOnlyProperty<string>;
showAccelerationDescriptionStringProperty: ReadOnlyProperty<string>;
} {
return {
simulationStringProperty: this.stringProperties.preferences.simulationStringProperty,
enableAutoTrackingStringProperty: this.stringProperties.preferences.enableAutoTrackingStringProperty,
enableAutoTrackingDescriptionStringProperty:
this.stringProperties.preferences.enableAutoTrackingDescriptionStringProperty,
showVelocityStringProperty: this.stringProperties.preferences.showVelocityStringProperty,
showVelocityDescriptionStringProperty: this.stringProperties.preferences.showVelocityDescriptionStringProperty,
showAccelerationStringProperty: this.stringProperties.preferences.showAccelerationStringProperty,
showAccelerationDescriptionStringProperty:
this.stringProperties.preferences.showAccelerationDescriptionStringProperty,
};
}

Expand Down
6 changes: 5 additions & 1 deletion src/i18n/strings_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@
"preferences": {
"simulation": "Simulation",
"enableAutoTracking": "Enable Auto-Tracking",
"enableAutoTrackingDescription": "When enabled, shows the auto-tracking checkbox in the control panel, allowing OpenCV-based automatic object tracking. When disabled, the checkbox is hidden and only manual digitizing is available."
"enableAutoTrackingDescription": "When enabled, shows the auto-tracking checkbox in the control panel, allowing OpenCV-based automatic object tracking. When disabled, the checkbox is hidden and only manual digitizing is available.",
"showVelocity": "Show Velocity",
"showVelocityDescription": "When enabled, velocity components (vx, vy, speed) are available as choices on the kinematics graph axes.",
"showAcceleration": "Show Acceleration",
"showAccelerationDescription": "When enabled, acceleration components (ax, ay, |a|) are available as choices on the kinematics graph axes."
},
"autoTracker": {
"dragToSelect": "Drag on video to select object to track"
Expand Down
6 changes: 5 additions & 1 deletion src/i18n/strings_fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@
"preferences": {
"simulation": "Simulation",
"enableAutoTracking": "Activer le suivi automatique",
"enableAutoTrackingDescription": "Lorsque activé, affiche la case à cocher du suivi automatique dans le panneau de contrôle, permettant le suivi automatique d'objets basé sur OpenCV. Lorsque désactivé, la case est cachée et seule la numérisation manuelle est disponible."
"enableAutoTrackingDescription": "Lorsque activé, affiche la case à cocher du suivi automatique dans le panneau de contrôle, permettant le suivi automatique d'objets basé sur OpenCV. Lorsque désactivé, la case est cachée et seule la numérisation manuelle est disponible.",
"showVelocity": "Afficher la vitesse",
"showVelocityDescription": "Lorsque activé, les composantes de vitesse (vx, vy, rapidité) sont disponibles comme choix sur les axes du graphique cinématique.",
"showAcceleration": "Afficher l'accélération",
"showAccelerationDescription": "Lorsque activé, les composantes d'accélération (ax, ay, |a|) sont disponibles comme choix sur les axes du graphique cinématique."
},
"autoTracker": {
"dragToSelect": "Faire glisser sur la vidéo pour sélectionner l'objet à suivre"
Expand Down
19 changes: 18 additions & 1 deletion src/preferences/TrackLabPreferencesModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* TrackLabPreferencesModel - Model for trackLab simulation preferences.
*
* Manages user preferences for the trackLab simulation, including whether
* the auto-tracking feature is enabled.
* the auto-tracking feature is enabled and which kinematic quantities appear
* on the graph axes.
*/

import { BooleanProperty } from "scenerystack/axon";
Expand All @@ -15,12 +16,28 @@ export class TrackLabPreferencesModel {
*/
public readonly enableAutoTrackingProperty: BooleanProperty;

/**
* Whether velocity quantities (vx, vy, speed) appear in the graph axis selectors.
*/
public readonly showVelocityInGraphProperty: BooleanProperty;

/**
* Whether acceleration quantities (ax, ay, |a|) appear in the graph axis selectors.
*/
public readonly showAccelerationInGraphProperty: BooleanProperty;

public constructor() {
// By default, auto-tracking checkbox is hidden
this.enableAutoTrackingProperty = new BooleanProperty(false);

// By default, velocity and acceleration are shown on the graph
this.showVelocityInGraphProperty = new BooleanProperty(true);
this.showAccelerationInGraphProperty = new BooleanProperty(false);
}

public reset(): void {
this.enableAutoTrackingProperty.reset();
this.showVelocityInGraphProperty.reset();
this.showAccelerationInGraphProperty.reset();
}
}
55 changes: 53 additions & 2 deletions src/preferences/TrackLabPreferencesNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
* TrackLabPreferencesNode - Custom preferences UI panel for the trackLab simulation.
*
* Renders the simulation-specific preferences content shown in the Preferences dialog.
* Currently includes a checkbox to enable/disable the auto-tracking feature.
* Includes checkboxes to:
* - Enable/disable the auto-tracking feature
* - Show/hide velocity quantities on the kinematics graph
* - Show/hide acceleration quantities on the kinematics graph
*/

import { HStrut, Text, VBox } from "scenerystack/scenery";
Expand Down Expand Up @@ -46,10 +49,58 @@ export class TrackLabPreferencesNode extends VBox {
},
);

const showVelocityCheckbox = new Checkbox(
preferencesModel.showVelocityInGraphProperty,
new VBox({
align: "left",
spacing: 2,
children: [
new Text(prefStrings.showVelocityStringProperty, {
font: new PhetFont(14),
fill: TrackLabColors.preferencesTextProperty,
}),
new Text(prefStrings.showVelocityDescriptionStringProperty, {
font: new PhetFont(11),
fill: TrackLabColors.preferencesTextSecondaryProperty,
maxWidth: 500,
}),
],
}),
{
checkboxColor: TrackLabColors.checkboxColorProperty,
checkboxColorBackground: TrackLabColors.checkboxColorBackgroundProperty,
spacing: 8,
},
);

const showAccelerationCheckbox = new Checkbox(
preferencesModel.showAccelerationInGraphProperty,
new VBox({
align: "left",
spacing: 2,
children: [
new Text(prefStrings.showAccelerationStringProperty, {
font: new PhetFont(14),
fill: TrackLabColors.preferencesTextProperty,
}),
new Text(prefStrings.showAccelerationDescriptionStringProperty, {
font: new PhetFont(11),
fill: TrackLabColors.preferencesTextSecondaryProperty,
maxWidth: 500,
}),
],
}),
{
checkboxColor: TrackLabColors.checkboxColorProperty,
checkboxColorBackground: TrackLabColors.checkboxColorBackgroundProperty,
spacing: 8,
},
);

super({
align: "left",
spacing: 12,
children: [header, new HStrut(600), enableAutoTrackingCheckbox],
children: [header, new HStrut(600), enableAutoTrackingCheckbox, showVelocityCheckbox, showAccelerationCheckbox],
});
}
}
31 changes: 30 additions & 1 deletion src/screen-name/graph/ConfigurableGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export default class ConfigurableGraph extends Node {
// Module instances
private readonly dataManager: GraphDataManager;
private readonly interactionHandler: GraphInteractionHandler;
private readonly controlsPanel: GraphControlsPanel;

// Title panel with combo boxes (needs to be on top of header bar)
private readonly titlePanel: Node;
Expand Down Expand Up @@ -300,12 +301,13 @@ export default class ConfigurableGraph extends Node {
});

// Create controls panel helper
const controlsPanel = new GraphControlsPanel(
this.controlsPanel = new GraphControlsPanel(
availableProperties,
this.xPropertyProperty,
this.yPropertyProperty,
this.graphWidth,
);
const controlsPanel = this.controlsPanel;

// Create title panel with combo boxes for axis selection
// Note: titlePanel is added to 'this' (not graphContentNode) after headerBar
Expand Down Expand Up @@ -494,6 +496,7 @@ export default class ConfigurableGraph extends Node {
this.graphVisibleProperty.unlink(graphVisibleListener);
isDraggingProperty.unlink(isDraggingListener);
isResizingProperty.unlink(isResizingListener);
this.controlsPanel.dispose();
this.xPropertyProperty.dispose();
this.yPropertyProperty.dispose();
this.graphVisibleProperty.dispose();
Expand Down Expand Up @@ -654,6 +657,32 @@ export default class ConfigurableGraph extends Node {
return this.graphVisibleProperty;
}

/**
* Update the set of quantities available in the axis-selector combo boxes.
*
* If the currently selected X or Y property is no longer in the new list it
* is reset to the first available property before the combo boxes are rebuilt.
*
* @param newProperties - The replacement list of plottable properties.
*/
public setAvailableProperties(newProperties: PlottableProperty[]): void {
if (newProperties.length === 0) {
return;
}

const first = newProperties[0]!;

// Reset selections that are no longer available.
if (!newProperties.includes(this.xPropertyProperty.value)) {
this.xPropertyProperty.value = first;
}
if (!newProperties.includes(this.yPropertyProperty.value)) {
this.yPropertyProperty.value = first;
}

this.controlsPanel.rebuildComboBoxes(newProperties);
}

public override dispose(): void {
this.disposeConfigurableGraph();
super.dispose();
Expand Down
90 changes: 80 additions & 10 deletions src/screen-name/graph/GraphControlsPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,20 @@ const HEADER_LINE_WIDTH = 2;
const HEADER_DARKEN_FACTOR = 0.1;

export default class GraphControlsPanel {
private readonly availableProperties: PlottableProperty[];
private availableProperties: PlottableProperty[];
private readonly xPropertyProperty: Property<PlottableProperty>;
private readonly yPropertyProperty: Property<PlottableProperty>;
private readonly graphWidth: number;

// Stored references needed to rebuild combo boxes without recreating the whole panel.
private listParent: Node | null = null;
private titleHBox: HBox | null = null;
private xComboBox: ComboBox<PlottableProperty> | null = null;
private yComboBox: ComboBox<PlottableProperty> | null = null;
private leftParenNode: Text | null = null;
private vsTextNode: Text | null = null;
private rightParenNode: Text | null = null;

public constructor(
availableProperties: PlottableProperty[],
xPropertyProperty: Property<PlottableProperty>,
Expand Down Expand Up @@ -62,9 +71,9 @@ export default class GraphControlsPanel {
}

/**
* Create title panel with "(Y vs X)" format where Y and X are combo boxes
* Build ComboBox items for the X-axis selector from the current available properties.
*/
public createTitlePanel(listParent: Node): Node {
private buildXComboBox(): ComboBox<PlottableProperty> {
const xItems = this.availableProperties.map((prop) => ({
value: prop,
createNode: () =>
Expand All @@ -75,7 +84,7 @@ export default class GraphControlsPanel {
tandemName: `${this.sanitizeTandemName(prop.name)}Item`,
}));

const xComboBox = new ComboBox(this.xPropertyProperty, xItems, listParent, {
return new ComboBox(this.xPropertyProperty, xItems, this.listParent!, {
cornerRadius: COMBO_BOX_CORNER_RADIUS,
xMargin: COMBO_BOX_X_MARGIN,
yMargin: COMBO_BOX_Y_MARGIN,
Expand All @@ -85,7 +94,12 @@ export default class GraphControlsPanel {
listStroke: TrackLabColors.controlPanelStrokeProperty,
highlightFill: TrackLabColors.controlPanelStrokeProperty,
});
}

/**
* Build ComboBox items for the Y-axis selector from the current available properties.
*/
private buildYComboBox(): ComboBox<PlottableProperty> {
const yItems = this.availableProperties.map((prop) => ({
value: prop,
createNode: () =>
Expand All @@ -96,7 +110,7 @@ export default class GraphControlsPanel {
tandemName: `${this.sanitizeTandemName(prop.name)}Item`,
}));

const yComboBox = new ComboBox(this.yPropertyProperty, yItems, listParent, {
return new ComboBox(this.yPropertyProperty, yItems, this.listParent!, {
cornerRadius: COMBO_BOX_CORNER_RADIUS,
xMargin: COMBO_BOX_X_MARGIN,
yMargin: COMBO_BOX_Y_MARGIN,
Expand All @@ -106,32 +120,88 @@ export default class GraphControlsPanel {
listStroke: TrackLabColors.controlPanelStrokeProperty,
highlightFill: TrackLabColors.controlPanelStrokeProperty,
});
}

/**
* Create title panel with "(Y vs X)" format where Y and X are combo boxes.
* Stores internal references so that `rebuildComboBoxes` can update the panel
* in place when the set of available properties changes.
*/
public createTitlePanel(listParent: Node): Node {
this.listParent = listParent;

this.xComboBox = this.buildXComboBox();
this.yComboBox = this.buildYComboBox();

// Create title in format "(Y vs X)"
const leftParen = new Text("(", {
this.leftParenNode = new Text("(", {
font: TITLE_FONT,
fill: TrackLabColors.textProperty,
});

const vsText = new Text(
this.vsTextNode = new Text(
new DerivedProperty([StringManager.getInstance().getControls().graphVsStringProperty], (vs: string) => ` ${vs} `),
{
font: TITLE_FONT,
fill: TrackLabColors.textProperty,
},
);

const rightParen = new Text(")", {
this.rightParenNode = new Text(")", {
font: TITLE_FONT,
fill: TrackLabColors.textProperty,
});

// Arrange in horizontal layout: (Y vs X)
return new HBox({
this.titleHBox = new HBox({
spacing: TITLE_SPACING,
align: "center",
children: [leftParen, yComboBox, vsText, xComboBox, rightParen],
children: [this.leftParenNode, this.yComboBox, this.vsTextNode, this.xComboBox, this.rightParenNode],
});

return this.titleHBox;
}

/**
* Swap out the combo boxes to reflect a new set of available properties.
* The title HBox is updated in place — no parent-level re-parenting needed.
*
* Must be called after `createTitlePanel`.
*/
public rebuildComboBoxes(newProperties: PlottableProperty[]): void {
if (!this.titleHBox || !this.xComboBox || !this.yComboBox) {
return;
}

this.availableProperties = newProperties;

const oldX = this.xComboBox;
const oldY = this.yComboBox;

this.xComboBox = this.buildXComboBox();
this.yComboBox = this.buildYComboBox();

// Update the HBox children with the new combo boxes, keeping the static text nodes.
this.titleHBox.children = [
this.leftParenNode!,
this.yComboBox,
this.vsTextNode!,
this.xComboBox,
this.rightParenNode!,
];

// Dispose old combo boxes AFTER swapping children to avoid dangling references.
oldX.dispose();
oldY.dispose();
}

/**
* Dispose the combo boxes owned by this panel.
* Should be called when the parent ConfigurableGraph is disposed.
*/
public dispose(): void {
this.xComboBox?.dispose();
this.yComboBox?.dispose();
}

/**
Expand Down
Loading