From e82f3661b6b400e50d2b669d0c17225d2f42a53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ghislain=20J=C3=A9z=C3=A9quel?= Date: Thu, 20 Jun 2024 17:48:14 +0200 Subject: [PATCH 1/5] refactor(figures): use subject to emit rubberband changes --- src/axes.ts | 11 ++++++++--- src/figures.ts | 12 ++++++------ src/interactions.ts | 5 +++++ src/multiplot.ts | 34 +++++++++++++++++----------------- src/remoteFigure.ts | 18 ++++++++++++++++-- 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/axes.ts b/src/axes.ts index c9bf11ea..ba95a81e 100644 --- a/src/axes.ts +++ b/src/axes.ts @@ -4,6 +4,7 @@ import { Vertex, Shape } from "./baseShape" import { Rect, Point } from "./primitives" import { TextParams, Text, RubberBand } from "./shapes" import { EventEmitter } from "events" +import { onAxisSelection, rubberbandChange } from "./interactions" export class TitleSettings { constructor( @@ -76,6 +77,10 @@ export class Axis extends Shape { this.updateOffsetTicks(); this.offsetTitle = 0; this.title = new Text(this.titleText, new Vertex(0, 0), {}); + + rubberbandChange.subscribe((rubberBand) => { + onAxisSelection.next([this, rubberBand]); + }) }; public get drawLength(): number { @@ -621,7 +626,7 @@ export class Axis extends Shape { this.updateRubberBand(); context.setTransform(canvasMatrix); this.rubberBand.draw(context); - if (this.rubberBand.isClicked) this.emitter.emit("rubberBandChange", this.rubberBand); + if (this.rubberBand.isClicked) rubberbandChange.next(this.rubberBand); } protected mouseTranslate(mouseDown: Vertex, mouseCoords: Vertex): void { } @@ -639,7 +644,7 @@ export class Axis extends Shape { private clickOnArrow(mouseDown: Vertex): void { this.is_drawing_rubberband = true; // OLD this.rubberBand.isHovered ? this.rubberBand.mouseDown(mouseDown) : this.rubberBand.reset(); - this.emitter.emit("rubberBandChange", this.rubberBand); + rubberbandChange.next(this.rubberBand); } private clickOnDrawnPath(mouseDown: Vertex): void { @@ -676,7 +681,7 @@ export class Axis extends Shape { this.title.mouseUp(false); this.title.isClicked = false; this.rubberBand.mouseUp(keepState); - if (this.is_drawing_rubberband) this.emitter.emit("rubberBandChange", this.rubberBand); + if (this.is_drawing_rubberband) rubberbandChange.next(this.rubberBand); this.is_drawing_rubberband = false; } diff --git a/src/figures.ts b/src/figures.ts index 32ddaed7..59d61a5f 100644 --- a/src/figures.ts +++ b/src/figures.ts @@ -10,7 +10,7 @@ import { Axis, ParallelAxis } from "./axes" import { ShapeCollection, GroupCollection, PointSet } from "./collections" import { RemoteFigure } from "./remoteFigure" import { DataInterface } from "./dataInterfaces" -import { HighlightData } from "./interactions" +import { HighlightData, rubberbandChange } from "./interactions" export class Figure extends RemoteFigure { constructor( @@ -86,7 +86,7 @@ export class Figure extends RemoteFigure { if (thisAxis.name == otherAxis.name && thisAxis.name != "number") { otherAxis.rubberBand.minValue = thisAxis.rubberBand.minValue; otherAxis.rubberBand.maxValue = thisAxis.rubberBand.maxValue; - otherAxis.emitter.emit("rubberBandChange", otherAxis.rubberBand); + rubberbandChange.next(otherAxis.rubberBand); } }) }) @@ -235,9 +235,9 @@ export class Frame extends Figure { return [new Vertex(this.axes[0].rubberBand.minValue, this.axes[1].rubberBand.minValue), new Vertex(this.axes[0].rubberBand.maxValue, this.axes[1].rubberBand.maxValue)] } - public activateSelection(emittedRubberBand: RubberBand, index: number): void { - super.activateSelection(emittedRubberBand, index) - this.selectionBox.rubberBandUpdate(emittedRubberBand, ["x", "y"][index]); + public activateSelection(axis: Axis, rubberBand: RubberBand): void { + super.activateSelection(axis, rubberBand) + this.selectionBox.rubberBandUpdate(rubberBand, ["x", "y"][this.getAxisIndex(axis)]); } } @@ -417,7 +417,7 @@ export class Histogram extends Frame { if (this.axes[0].name == otherAxis.name) { otherAxis.rubberBand.minValue = this.axes[0].rubberBand.minValue; otherAxis.rubberBand.maxValue = this.axes[0].rubberBand.maxValue; - otherAxis.emitter.emit("rubberBandChange", otherAxis.rubberBand); + rubberbandChange.next(otherAxis.rubberBand); } }) } diff --git a/src/interactions.ts b/src/interactions.ts index 9c717d51..94388eb1 100644 --- a/src/interactions.ts +++ b/src/interactions.ts @@ -1,4 +1,6 @@ import { Subject } from "rxjs" +import { RubberBand } from "./shapes"; +import { Axis } from "./axes"; export interface HighlightData { referencePath: string, @@ -7,3 +9,6 @@ export interface HighlightData { } export const highlightShape: Subject = new Subject(); + +export const rubberbandChange: Subject = new Subject(); +export const onAxisSelection: Subject<[Axis, RubberBand]> = new Subject(); diff --git a/src/multiplot.ts b/src/multiplot.ts index 1fcdb2c5..61b71bad 100644 --- a/src/multiplot.ts +++ b/src/multiplot.ts @@ -176,12 +176,12 @@ export class Multiplot { private activateAxisEvents(figure: Figure): void { figure.axes.forEach(axis => axis.emitter.on('axisStateChange', e => figure.axisChangeUpdate(e))); - figure.axes.forEach((axis, index) => { - axis.emitter.on('rubberBandChange', e => { - figure.activateSelection(e, index); - this.isSelecting = true; - }) - }) + // figure.axes.forEach((axis, index) => { + // axis.emitter.on('rubberBandChange', e => { + // figure.activateSelection(e, index); + // this.isSelecting = true; + // }) + // }) } public selectionOn(): void { @@ -332,16 +332,16 @@ export class Multiplot { this.figures.forEach(figure => figure.axes.forEach(axis => axis.emitter.on('axisStateChange', e => figure.axisChangeUpdate(e)))); } - private listenRubberBandChange(): void { - this.figures.forEach(figure => { - figure.axes.forEach((axis, index) => { - axis.emitter.on('rubberBandChange', e => { - figure.activateSelection(e, index); - this.isSelecting = true; - }) - }) - }) - } + // private listenRubberBandChange(): void { + // this.figures.forEach(figure => { + // figure.axes.forEach((axis, index) => { + // axis.emitter.on('rubberBandChange', e => { + // figure.activateSelection(e, index); + // this.isSelecting = true; + // }) + // }) + // }) + // } private keyDownDrawer(e: KeyboardEvent, ctrlKey: boolean, shiftKey: boolean, spaceKey: boolean): [boolean, boolean, boolean] { if (e.key == "Control") { @@ -474,7 +474,7 @@ export class Multiplot { this.listenAxisStateChange(); - this.listenRubberBandChange(); + // this.listenRubberBandChange(); window.addEventListener('keydown', e => [ctrlKey, shiftKey, spaceKey] = this.keyDownDrawer(e, ctrlKey, shiftKey, spaceKey)); diff --git a/src/remoteFigure.ts b/src/remoteFigure.ts index 6998cff2..8b840c36 100644 --- a/src/remoteFigure.ts +++ b/src/remoteFigure.ts @@ -8,6 +8,7 @@ import { RubberBand, SelectionBox } from "./shapes" import { Axis } from "./axes" import { PointSet, ShapeCollection, GroupCollection } from "./collections" import { DataInterface } from "./dataInterfaces" +import { onAxisSelection } from "./interactions" export class RemoteFigure extends Rect { public context: CanvasRenderingContext2D; @@ -80,6 +81,8 @@ export class RemoteFigure extends Rect { this.relativeObjects = new GroupCollection(); this.absoluteObjects = new GroupCollection(); this.setAxisVisibility(data); + + onAxisSelection.subscribe(([axis, rubberBand]) => this.activateSelection(axis, rubberBand)) } get scale(): Vertex { return new Vertex(this.relativeMatrix.a, this.relativeMatrix.d)} @@ -651,7 +654,15 @@ export class RemoteFigure extends Rect { this.translation = translation; } - public activateSelection(emittedRubberBand: RubberBand, index: number): void { this.is_drawing_rubber_band = true } + public activateSelection(axis: Axis, rubberBand: RubberBand): void { + console.log(this, axis.name, rubberBand.attributeName) + if (this.getAxisIndex(axis) > -1) this.is_drawing_rubber_band = true + } + + protected getAxisIndex(axis): number { + const axisNames = this.axes.map((a) => a.name) + return axisNames.indexOf(axis.name); + } public shiftOnAction(canvas: HTMLElement): void { this.isSelecting = true; @@ -677,7 +688,10 @@ export class RemoteFigure extends Rect { const canvas = document.getElementById(this.canvasID) as HTMLCanvasElement; let ctrlKey = false; let shiftKey = false; let spaceKey = false; - this.axes.forEach((axis, index) => axis.emitter.on('rubberBandChange', e => this.activateSelection(e, index))); + // onAxisSelection.subscribe(([axis, rubberBand]) => { + // console.log("Mouse Listener Remote Figure") + // this.activateSelection(axis, rubberBand) + // }); this.axes.forEach(axis => axis.emitter.on('axisStateChange', e => this.axisChangeUpdate(e))); From da1a9e7bb9c08ebccdbe4a0fc19d4647cd929130 Mon Sep 17 00:00:00 2001 From: GhislainJ Date: Mon, 24 Jun 2024 17:20:38 +0200 Subject: [PATCH 2/5] refactor(interactions): simplify subjects chain --- src/axes.ts | 12 ++++-------- src/figures.ts | 12 ++++++------ src/interactions.ts | 4 +--- src/multiplot.ts | 19 ------------------- src/remoteFigure.ts | 12 +++--------- 5 files changed, 14 insertions(+), 45 deletions(-) diff --git a/src/axes.ts b/src/axes.ts index ba95a81e..dd16be01 100644 --- a/src/axes.ts +++ b/src/axes.ts @@ -4,7 +4,7 @@ import { Vertex, Shape } from "./baseShape" import { Rect, Point } from "./primitives" import { TextParams, Text, RubberBand } from "./shapes" import { EventEmitter } from "events" -import { onAxisSelection, rubberbandChange } from "./interactions" +import { onAxisSelection } from "./interactions" export class TitleSettings { constructor( @@ -77,10 +77,6 @@ export class Axis extends Shape { this.updateOffsetTicks(); this.offsetTitle = 0; this.title = new Text(this.titleText, new Vertex(0, 0), {}); - - rubberbandChange.subscribe((rubberBand) => { - onAxisSelection.next([this, rubberBand]); - }) }; public get drawLength(): number { @@ -626,7 +622,7 @@ export class Axis extends Shape { this.updateRubberBand(); context.setTransform(canvasMatrix); this.rubberBand.draw(context); - if (this.rubberBand.isClicked) rubberbandChange.next(this.rubberBand); + if (this.rubberBand.isClicked) onAxisSelection.next(this); } protected mouseTranslate(mouseDown: Vertex, mouseCoords: Vertex): void { } @@ -644,7 +640,7 @@ export class Axis extends Shape { private clickOnArrow(mouseDown: Vertex): void { this.is_drawing_rubberband = true; // OLD this.rubberBand.isHovered ? this.rubberBand.mouseDown(mouseDown) : this.rubberBand.reset(); - rubberbandChange.next(this.rubberBand); + onAxisSelection.next(this); } private clickOnDrawnPath(mouseDown: Vertex): void { @@ -681,7 +677,7 @@ export class Axis extends Shape { this.title.mouseUp(false); this.title.isClicked = false; this.rubberBand.mouseUp(keepState); - if (this.is_drawing_rubberband) rubberbandChange.next(this.rubberBand); + if (this.is_drawing_rubberband) onAxisSelection.next(this); this.is_drawing_rubberband = false; } diff --git a/src/figures.ts b/src/figures.ts index 59d61a5f..a0ed71c9 100644 --- a/src/figures.ts +++ b/src/figures.ts @@ -10,7 +10,7 @@ import { Axis, ParallelAxis } from "./axes" import { ShapeCollection, GroupCollection, PointSet } from "./collections" import { RemoteFigure } from "./remoteFigure" import { DataInterface } from "./dataInterfaces" -import { HighlightData, rubberbandChange } from "./interactions" +import { HighlightData, onAxisSelection } from "./interactions" export class Figure extends RemoteFigure { constructor( @@ -86,7 +86,7 @@ export class Figure extends RemoteFigure { if (thisAxis.name == otherAxis.name && thisAxis.name != "number") { otherAxis.rubberBand.minValue = thisAxis.rubberBand.minValue; otherAxis.rubberBand.maxValue = thisAxis.rubberBand.maxValue; - rubberbandChange.next(otherAxis.rubberBand); + onAxisSelection.next(otherAxis); } }) }) @@ -235,9 +235,9 @@ export class Frame extends Figure { return [new Vertex(this.axes[0].rubberBand.minValue, this.axes[1].rubberBand.minValue), new Vertex(this.axes[0].rubberBand.maxValue, this.axes[1].rubberBand.maxValue)] } - public activateSelection(axis: Axis, rubberBand: RubberBand): void { - super.activateSelection(axis, rubberBand) - this.selectionBox.rubberBandUpdate(rubberBand, ["x", "y"][this.getAxisIndex(axis)]); + public activateSelection(axis: Axis): void { + super.activateSelection(axis) + this.selectionBox.rubberBandUpdate(axis.rubberBand, ["x", "y"][this.getAxisIndex(axis)]); } } @@ -417,7 +417,7 @@ export class Histogram extends Frame { if (this.axes[0].name == otherAxis.name) { otherAxis.rubberBand.minValue = this.axes[0].rubberBand.minValue; otherAxis.rubberBand.maxValue = this.axes[0].rubberBand.maxValue; - rubberbandChange.next(otherAxis.rubberBand); + onAxisSelection.next(otherAxis); } }) } diff --git a/src/interactions.ts b/src/interactions.ts index 94388eb1..17f2244c 100644 --- a/src/interactions.ts +++ b/src/interactions.ts @@ -1,5 +1,4 @@ import { Subject } from "rxjs" -import { RubberBand } from "./shapes"; import { Axis } from "./axes"; export interface HighlightData { @@ -10,5 +9,4 @@ export interface HighlightData { export const highlightShape: Subject = new Subject(); -export const rubberbandChange: Subject = new Subject(); -export const onAxisSelection: Subject<[Axis, RubberBand]> = new Subject(); +export const onAxisSelection: Subject = new Subject(); diff --git a/src/multiplot.ts b/src/multiplot.ts index 61b71bad..4e962d2b 100644 --- a/src/multiplot.ts +++ b/src/multiplot.ts @@ -176,12 +176,6 @@ export class Multiplot { private activateAxisEvents(figure: Figure): void { figure.axes.forEach(axis => axis.emitter.on('axisStateChange', e => figure.axisChangeUpdate(e))); - // figure.axes.forEach((axis, index) => { - // axis.emitter.on('rubberBandChange', e => { - // figure.activateSelection(e, index); - // this.isSelecting = true; - // }) - // }) } public selectionOn(): void { @@ -332,17 +326,6 @@ export class Multiplot { this.figures.forEach(figure => figure.axes.forEach(axis => axis.emitter.on('axisStateChange', e => figure.axisChangeUpdate(e)))); } - // private listenRubberBandChange(): void { - // this.figures.forEach(figure => { - // figure.axes.forEach((axis, index) => { - // axis.emitter.on('rubberBandChange', e => { - // figure.activateSelection(e, index); - // this.isSelecting = true; - // }) - // }) - // }) - // } - private keyDownDrawer(e: KeyboardEvent, ctrlKey: boolean, shiftKey: boolean, spaceKey: boolean): [boolean, boolean, boolean] { if (e.key == "Control") { ctrlKey = true; @@ -474,8 +457,6 @@ export class Multiplot { this.listenAxisStateChange(); - // this.listenRubberBandChange(); - window.addEventListener('keydown', e => [ctrlKey, shiftKey, spaceKey] = this.keyDownDrawer(e, ctrlKey, shiftKey, spaceKey)); window.addEventListener('keyup', e => [ctrlKey, shiftKey, spaceKey] = this.keyUpDrawer(e, ctrlKey, shiftKey, spaceKey)); diff --git a/src/remoteFigure.ts b/src/remoteFigure.ts index 8b840c36..c9d731d9 100644 --- a/src/remoteFigure.ts +++ b/src/remoteFigure.ts @@ -4,7 +4,7 @@ import { colorHsl } from "./colors" import { PointStyle } from "./styles" import { Vertex, Shape } from "./baseShape" import { Rect } from "./primitives" -import { RubberBand, SelectionBox } from "./shapes" +import { SelectionBox } from "./shapes" import { Axis } from "./axes" import { PointSet, ShapeCollection, GroupCollection } from "./collections" import { DataInterface } from "./dataInterfaces" @@ -82,7 +82,7 @@ export class RemoteFigure extends Rect { this.absoluteObjects = new GroupCollection(); this.setAxisVisibility(data); - onAxisSelection.subscribe(([axis, rubberBand]) => this.activateSelection(axis, rubberBand)) + onAxisSelection.subscribe((axis) => this.activateSelection(axis)) } get scale(): Vertex { return new Vertex(this.relativeMatrix.a, this.relativeMatrix.d)} @@ -654,8 +654,7 @@ export class RemoteFigure extends Rect { this.translation = translation; } - public activateSelection(axis: Axis, rubberBand: RubberBand): void { - console.log(this, axis.name, rubberBand.attributeName) + public activateSelection(axis: Axis): void { if (this.getAxisIndex(axis) > -1) this.is_drawing_rubber_band = true } @@ -688,11 +687,6 @@ export class RemoteFigure extends Rect { const canvas = document.getElementById(this.canvasID) as HTMLCanvasElement; let ctrlKey = false; let shiftKey = false; let spaceKey = false; - // onAxisSelection.subscribe(([axis, rubberBand]) => { - // console.log("Mouse Listener Remote Figure") - // this.activateSelection(axis, rubberBand) - // }); - this.axes.forEach(axis => axis.emitter.on('axisStateChange', e => this.axisChangeUpdate(e))); window.addEventListener('keydown', e => { From a10342609e2a9e0552b9d2173834d5dd210992e9 Mon Sep 17 00:00:00 2001 From: GhislainJ Date: Mon, 24 Jun 2024 19:44:24 +0200 Subject: [PATCH 3/5] refactor(interactions): merge observable to avoid setting isSelecting bool --- cypress/e2e/figures.cy.ts | 6 +-- src/axes.ts | 16 +++++-- src/figures.ts | 92 +++++++++++++++++++-------------------- src/interactions.ts | 4 +- src/multiplot.ts | 27 +++++++----- src/remoteFigure.ts | 2 +- src/shapes.ts | 12 ++--- 7 files changed, 89 insertions(+), 70 deletions(-) diff --git a/cypress/e2e/figures.cy.ts b/cypress/e2e/figures.cy.ts index 6e57f5c8..726efd66 100644 --- a/cypress/e2e/figures.cy.ts +++ b/cypress/e2e/figures.cy.ts @@ -93,7 +93,7 @@ describe("Figure", function() { data["type_"] = "parallelplot"; const parallelPlot = Figure.fromMultiplot(data, canvas.width, canvas.height, canvasID); - scatter.sendRubberBandsMultiplot([scatter2, parallelPlot]); + // scatter.sendRubberBandsMultiplot([scatter2, parallelPlot]); expect(scatter2.axes[0].rubberBand, "scatter2.axes[0].rubberBand").to.not.deep.equal(scatter.axes[1].rubberBand); expect(scatter2.axes[1].rubberBand, "scatter2.axes[1].rubberBand").to.not.deep.equal(scatter.axes[0].rubberBand); @@ -121,7 +121,7 @@ describe("Figure", function() { [scatter.axes[0].name, scatter.axes[0].rubberBand], [scatter.axes[1].name, scatter.axes[1].rubberBand] ]); - scatter.initRubberBandMultiplot(multiplotRubberBands); + // scatter.initRubberBandMultiplot(multiplotRubberBands); scatter.axes.forEach(axis => { expect(multiplotRubberBands.get(axis.name).minValue, `empty rubberband ${axis.name}.minValue`).to.deep.equal(referenceRubberBands.get(axis.name).minValue); @@ -132,7 +132,7 @@ describe("Figure", function() { scatter.axes[0].rubberBand.maxValue = 4; scatter.axes[1].rubberBand.minValue = 2; scatter.axes[1].rubberBand.maxValue = 5; - scatter.updateRubberBandMultiplot(multiplotRubberBands); + // scatter.updateRubberBandMultiplot(multiplotRubberBands); scatter.axes.forEach(axis => { expect(multiplotRubberBands.get(axis.name).minValue, `edited rubberband ${axis.name}.minValue`).to.deep.equal(referenceRubberBands.get(axis.name).minValue); expect(multiplotRubberBands.get(axis.name).maxValue, `edited rubberband ${axis.name}.minValue`).to.deep.equal(referenceRubberBands.get(axis.name).maxValue); diff --git a/src/axes.ts b/src/axes.ts index dd16be01..940140c6 100644 --- a/src/axes.ts +++ b/src/axes.ts @@ -4,7 +4,8 @@ import { Vertex, Shape } from "./baseShape" import { Rect, Point } from "./primitives" import { TextParams, Text, RubberBand } from "./shapes" import { EventEmitter } from "events" -import { onAxisSelection } from "./interactions" +import { onAxisSelection, rubberbandsChange } from "./interactions" +import { combineLatest, combineLatestWith, withLatestFrom } from "rxjs" export class TitleSettings { constructor( @@ -77,6 +78,15 @@ export class Axis extends Shape { this.updateOffsetTicks(); this.offsetTitle = 0; this.title = new Text(this.titleText, new Vertex(0, 0), {}); + + onAxisSelection.pipe(withLatestFrom(rubberbandsChange)) + .subscribe(([axis, rubberbands]) => { + console.log(axis, rubberbands); + const rubberband = rubberbands.get(this.name); + if (!rubberband || this.name == "number") return + this.rubberBand.minValue = rubberband.minValue; + this.rubberBand.maxValue = rubberband.maxValue; + }) }; public get drawLength(): number { @@ -234,9 +244,9 @@ export class Axis extends Shape { this.rubberBand.defaultStyle(); } - public sendRubberBand(rubberBands: Map) { this.rubberBand.selfSend(rubberBands) } + // public sendRubberBand(rubberBands: Map) { this.rubberBand.selfSend(rubberBands) } - public sendRubberBandRange(rubberBands: Map) { this.rubberBand.selfSendRange(rubberBands) } + // public sendRubberBandRange(rubberBands: Map) { this.rubberBand.selfSendRange(rubberBands) } private static nearestFive(value: number): number { const tenPower = Math.floor(Math.log10(Math.abs(value))); diff --git a/src/figures.ts b/src/figures.ts index a0ed71c9..c4e7d046 100644 --- a/src/figures.ts +++ b/src/figures.ts @@ -68,31 +68,31 @@ export class Figure extends RemoteFigure { public receivePointSets(pointSets: PointSet[]): void { this.pointSets = pointSets } - public initRubberBandMultiplot(multiplotRubberBands: Map): void { - this.axes.forEach(axis => axis.sendRubberBand(multiplotRubberBands)); - } - - public updateRubberBandMultiplot(multiplotRubberBands: Map): void { - this.axes.forEach(axis => axis.sendRubberBandRange(multiplotRubberBands)); - } - - public sendRubberBandsMultiplot(figures: Figure[]): void { - figures.forEach(figure => figure.receiveRubberBandFromFigure(this)); - } - - protected sendRubberBandsInFigure(figure: Figure): void { - figure.axes.forEach(otherAxis => { - this.axes.forEach(thisAxis => { - if (thisAxis.name == otherAxis.name && thisAxis.name != "number") { - otherAxis.rubberBand.minValue = thisAxis.rubberBand.minValue; - otherAxis.rubberBand.maxValue = thisAxis.rubberBand.maxValue; - onAxisSelection.next(otherAxis); - } - }) - }) - } - - protected receiveRubberBandFromFigure(figure: Figure): void { figure.sendRubberBandsInFigure(this) } + // public initRubberBandMultiplot(multiplotRubberBands: Map): void { + // this.axes.forEach(axis => axis.sendRubberBand(multiplotRubberBands)); + // } + + // public updateRubberBandMultiplot(multiplotRubberBands: Map): void { + // this.axes.forEach(axis => axis.sendRubberBandRange(multiplotRubberBands)); + // } + + // public sendRubberBandsMultiplot(figures: Figure[]): void { + // figures.forEach(figure => figure.receiveRubberBandFromFigure(this)); + // } + + // protected sendRubberBandsInFigure(figure: Figure): void { + // figure.axes.forEach(otherAxis => { + // this.axes.forEach(thisAxis => { + // if (thisAxis.name == otherAxis.name && thisAxis.name != "number") { + // otherAxis.rubberBand.minValue = thisAxis.rubberBand.minValue; + // otherAxis.rubberBand.maxValue = thisAxis.rubberBand.maxValue; + // onAxisSelection.next(otherAxis); + // } + // }) + // }) + // } + + // protected receiveRubberBandFromFigure(figure: Figure): void { figure.sendRubberBandsInFigure(this) } } export class Frame extends Figure { @@ -404,23 +404,23 @@ export class Histogram extends Frame { super.regulateScale(); } - public initRubberBandMultiplot(multiplotRubberBands: Map): void { - this.axes[0].sendRubberBand(multiplotRubberBands); - } + // public initRubberBandMultiplot(multiplotRubberBands: Map): void { + // this.axes[0].sendRubberBand(multiplotRubberBands); + // } - public updateRubberBandMultiplot(multiplotRubberBands: Map): void { - this.axes[0].sendRubberBandRange(multiplotRubberBands); - } + // public updateRubberBandMultiplot(multiplotRubberBands: Map): void { + // this.axes[0].sendRubberBandRange(multiplotRubberBands); + // } - protected sendRubberBandsInFigure(figure: Figure): void { - figure.axes.forEach(otherAxis => { - if (this.axes[0].name == otherAxis.name) { - otherAxis.rubberBand.minValue = this.axes[0].rubberBand.minValue; - otherAxis.rubberBand.maxValue = this.axes[0].rubberBand.maxValue; - onAxisSelection.next(otherAxis); - } - }) - } +// protected sendRubberBandsInFigure(figure: Figure): void { +// figure.axes.forEach(otherAxis => { +// if (this.axes[0].name == otherAxis.name) { +// otherAxis.rubberBand.minValue = this.axes[0].rubberBand.minValue; +// otherAxis.rubberBand.maxValue = this.axes[0].rubberBand.maxValue; +// onAxisSelection.next(otherAxis); +// } +// }) +// } } export class Scatter extends Frame { @@ -816,13 +816,13 @@ export class Graph2D extends Scatter { public receivePointSets(pointSets: PointSet[]): void {} - public initRubberBandMultiplot(multiplotRubberBands: Map): void {} + // public initRubberBandMultiplot(multiplotRubberBands: Map): void {} public updateRubberBandMultiplot(multiplotRubberBands: Map): void {} - public sendRubberBandsMultiplot(figures: Figure[]): void {} + // public sendRubberBandsMultiplot(figures: Figure[]): void {} - protected receiveRubberBandFromFigure(figure: Figure): void {} + // protected receiveRubberBandFromFigure(figure: Figure): void {}/ } export class ParallelPlot extends Figure { @@ -1177,13 +1177,13 @@ export class Draw extends Frame { public receivePointSets(pointSets: PointSet[]): void {} - public initRubberBandMultiplot(multiplotRubberBands: Map): void {} + // public initRubberBandMultiplot(multiplotRubberBands: Map): void {} public updateRubberBandMultiplot(multiplotRubberBands: Map): void {} - public sendRubberBandsMultiplot(figures: Figure[]): void {} + // public sendRubberBandsMultiplot(figures: Figure[]): void {} - protected receiveRubberBandFromFigure(figure: Figure): void {} + // protected receiveRubberBandFromFigure(figure: Figure): void {} public highlightFromReferencePath(highlightData: HighlightData) { const highlight = highlightData.highlight; diff --git a/src/interactions.ts b/src/interactions.ts index 17f2244c..fc04f07b 100644 --- a/src/interactions.ts +++ b/src/interactions.ts @@ -1,5 +1,6 @@ -import { Subject } from "rxjs" +import { BehaviorSubject, ReplaySubject, Subject } from "rxjs" import { Axis } from "./axes"; +import { RubberBand } from "./shapes"; export interface HighlightData { referencePath: string, @@ -10,3 +11,4 @@ export interface HighlightData { export const highlightShape: Subject = new Subject(); export const onAxisSelection: Subject = new Subject(); +export const rubberbandsChange: BehaviorSubject> = new BehaviorSubject(new Map()); diff --git a/src/multiplot.ts b/src/multiplot.ts index 4e962d2b..089abe66 100644 --- a/src/multiplot.ts +++ b/src/multiplot.ts @@ -5,6 +5,7 @@ import { RubberBand, SelectionBox } from "./shapes" import { SelectionBoxCollection, PointSet } from "./collections" import { Figure, Scatter, Graph2D, ParallelPlot, Draw } from './figures' import { DataInterface, MultiplotDataInterface } from "./dataInterfaces" +import { rubberbandsChange } from "./interactions" /* TODO: Does this inherit from RemoteFigure or the opposite or does this @@ -18,7 +19,7 @@ export class Multiplot { public featureNames: string[]; public nSamples: number; public figures: Figure[]; - public rubberBands: Map; + public rubberBands: Map = new Map(); public figureZones = new SelectionBoxCollection([]); public isSelecting: boolean = false; @@ -46,7 +47,7 @@ export class Multiplot { this.nSamples = this.features.entries().next().value[1].length; this.computeTable(); this.draw(); - this.initRubberBands(); + // this.initRubberBands(); this.mouseListener(); } @@ -194,6 +195,7 @@ export class Multiplot { } public switchSelection(): void { + // Never called. Is this useful ? this.isSelecting ? this.selectionOff() : this.selectionOn(); } @@ -304,17 +306,20 @@ export class Multiplot { public updateHoveredIndices(figure: Figure): void { this.hoveredIndices = figure.sendHoveredIndicesMultiplot() } - public initRubberBands(): void { - this.rubberBands = new Map(); - this.figures.forEach(figure => figure.initRubberBandMultiplot(this.rubberBands)); - } + // public initRubberBands(): void { + // this.rubberBands = new Map(); + // this.figures.forEach(figure => figure.initRubberBandMultiplot(this.rubberBands)); + // } public updateRubberBands(currentFigure: Figure): void { - if (this.isSelecting) { - if (!this.rubberBands) this.initRubberBands(); - currentFigure.sendRubberBandsMultiplot(this.figures); - this.figures.forEach(figure => figure.updateRubberBandMultiplot(this.rubberBands)); - } + console.log(this.isSelecting) + // if (this.isSelecting) { + // if (!this.rubberBands) this.initRubberBands(); + rubberbandsChange.next(this.rubberBands) + // currentFigure.sendRubberBandsMultiplot(this.figures); + + // this.figures.forEach(figure => figure.updateRubberBandMultiplot(this.rubberBands)); + // } } public resetRubberBands(): void { diff --git a/src/remoteFigure.ts b/src/remoteFigure.ts index c9d731d9..3b79d087 100644 --- a/src/remoteFigure.ts +++ b/src/remoteFigure.ts @@ -429,7 +429,7 @@ export class RemoteFigure extends Rect { this.context.restore(); } - public switchSelection(): void { this.isSelecting = !this.isSelecting; this.draw() } + public switchSelection(): void { this.isSelecting = !this.isSelecting; this.draw() } // Never called. Is this useful ? public switchMerge(): void {} diff --git a/src/shapes.ts b/src/shapes.ts index 3d2ea1b7..81334acf 100644 --- a/src/shapes.ts +++ b/src/shapes.ts @@ -907,7 +907,9 @@ export class RubberBand extends Rect { public get isTranslating(): boolean { return !this.minUpdate && !this.maxUpdate && this.isClicked} - public selfSend(rubberBands: Map): void { rubberBands.set(this.attributeName, new RubberBand(this.attributeName, 0, 0, this.isVertical)) } + // public selfSend(rubberBands: Map): void { + // rubberBands.set(this.attributeName, new RubberBand(this.attributeName, 0, 0, this.isVertical)) + // } public defaultStyle(): void { this.lineWidth = 0.1; @@ -916,10 +918,10 @@ export class RubberBand extends Rect { this.alpha = C.RUBBERBAND_ALPHA; } - public selfSendRange(rubberBands: Map): void { - rubberBands.get(this.attributeName).minValue = this.minValue; - rubberBands.get(this.attributeName).maxValue = this.maxValue; - } + // public selfSendRange(rubberBands: Map): void { + // rubberBands.get(this.attributeName).minValue = this.minValue; + // rubberBands.get(this.attributeName).maxValue = this.maxValue; + // } public updateCoords(canvasCoords: Vertex, axisOrigin: Vertex, axisEnd: Vertex): void { const coord = this.isVertical ? "y" : "x"; From 7d1d0db106129b5211930cc0bf099bdc88944769 Mon Sep 17 00:00:00 2001 From: GhislainJ Date: Mon, 24 Jun 2024 20:33:38 +0200 Subject: [PATCH 4/5] refactor(rubberbands): working proposition with subjects --- src/axes.ts | 27 +++++++++++--------- src/figures.ts | 60 --------------------------------------------- src/interactions.ts | 2 +- src/multiplot.ts | 21 +++------------- src/shapes.ts | 9 ------- 5 files changed, 19 insertions(+), 100 deletions(-) diff --git a/src/axes.ts b/src/axes.ts index 940140c6..cdd854cf 100644 --- a/src/axes.ts +++ b/src/axes.ts @@ -5,7 +5,7 @@ import { Rect, Point } from "./primitives" import { TextParams, Text, RubberBand } from "./shapes" import { EventEmitter } from "events" import { onAxisSelection, rubberbandsChange } from "./interactions" -import { combineLatest, combineLatestWith, withLatestFrom } from "rxjs" +import { filter, withLatestFrom } from "rxjs" export class TitleSettings { constructor( @@ -79,13 +79,20 @@ export class Axis extends Shape { this.offsetTitle = 0; this.title = new Text(this.titleText, new Vertex(0, 0), {}); - onAxisSelection.pipe(withLatestFrom(rubberbandsChange)) - .subscribe(([axis, rubberbands]) => { - console.log(axis, rubberbands); - const rubberband = rubberbands.get(this.name); - if (!rubberband || this.name == "number") return - this.rubberBand.minValue = rubberband.minValue; - this.rubberBand.maxValue = rubberband.maxValue; + onAxisSelection.pipe( + filter((axis) => this.name !== "number" && this.name === axis.name), + withLatestFrom(rubberbandsChange) + ).subscribe(([axis, rubberbands]) => { + let rubberband = rubberbands.get(this.name); + if (!rubberband) { + rubberband = new RubberBand(axis.name, axis.rubberBand.minValue, axis.rubberBand.maxValue, this.isVertical) + } else { + rubberband.minValue = axis.rubberBand.minValue; + rubberband.maxValue = axis.rubberBand.maxValue; + } + rubberbands.set(axis.name, rubberband) + this.rubberBand.minValue = rubberband.minValue; + this.rubberBand.maxValue = rubberband.maxValue; }) }; @@ -244,10 +251,6 @@ export class Axis extends Shape { this.rubberBand.defaultStyle(); } - // public sendRubberBand(rubberBands: Map) { this.rubberBand.selfSend(rubberBands) } - - // public sendRubberBandRange(rubberBands: Map) { this.rubberBand.selfSendRange(rubberBands) } - private static nearestFive(value: number): number { const tenPower = Math.floor(Math.log10(Math.abs(value))); const normedValue = Math.floor(value / Math.pow(10, tenPower - 2)); diff --git a/src/figures.ts b/src/figures.ts index c4e7d046..f1ee1b1b 100644 --- a/src/figures.ts +++ b/src/figures.ts @@ -67,32 +67,6 @@ export class Figure extends RemoteFigure { } public receivePointSets(pointSets: PointSet[]): void { this.pointSets = pointSets } - - // public initRubberBandMultiplot(multiplotRubberBands: Map): void { - // this.axes.forEach(axis => axis.sendRubberBand(multiplotRubberBands)); - // } - - // public updateRubberBandMultiplot(multiplotRubberBands: Map): void { - // this.axes.forEach(axis => axis.sendRubberBandRange(multiplotRubberBands)); - // } - - // public sendRubberBandsMultiplot(figures: Figure[]): void { - // figures.forEach(figure => figure.receiveRubberBandFromFigure(this)); - // } - - // protected sendRubberBandsInFigure(figure: Figure): void { - // figure.axes.forEach(otherAxis => { - // this.axes.forEach(thisAxis => { - // if (thisAxis.name == otherAxis.name && thisAxis.name != "number") { - // otherAxis.rubberBand.minValue = thisAxis.rubberBand.minValue; - // otherAxis.rubberBand.maxValue = thisAxis.rubberBand.maxValue; - // onAxisSelection.next(otherAxis); - // } - // }) - // }) - // } - - // protected receiveRubberBandFromFigure(figure: Figure): void { figure.sendRubberBandsInFigure(this) } } export class Frame extends Figure { @@ -403,24 +377,6 @@ export class Histogram extends Frame { this.scaleY = 1; super.regulateScale(); } - - // public initRubberBandMultiplot(multiplotRubberBands: Map): void { - // this.axes[0].sendRubberBand(multiplotRubberBands); - // } - - // public updateRubberBandMultiplot(multiplotRubberBands: Map): void { - // this.axes[0].sendRubberBandRange(multiplotRubberBands); - // } - -// protected sendRubberBandsInFigure(figure: Figure): void { -// figure.axes.forEach(otherAxis => { -// if (this.axes[0].name == otherAxis.name) { -// otherAxis.rubberBand.minValue = this.axes[0].rubberBand.minValue; -// otherAxis.rubberBand.maxValue = this.axes[0].rubberBand.maxValue; -// onAxisSelection.next(otherAxis); -// } -// }) -// } } export class Scatter extends Frame { @@ -815,14 +771,6 @@ export class Graph2D extends Scatter { public multiplotSelectedIntersection(multiplotSelected: number[], isSelecting: boolean): [number[], boolean] { return [multiplotSelected, isSelecting] } public receivePointSets(pointSets: PointSet[]): void {} - - // public initRubberBandMultiplot(multiplotRubberBands: Map): void {} - - public updateRubberBandMultiplot(multiplotRubberBands: Map): void {} - - // public sendRubberBandsMultiplot(figures: Figure[]): void {} - - // protected receiveRubberBandFromFigure(figure: Figure): void {}/ } export class ParallelPlot extends Figure { @@ -1177,14 +1125,6 @@ export class Draw extends Frame { public receivePointSets(pointSets: PointSet[]): void {} - // public initRubberBandMultiplot(multiplotRubberBands: Map): void {} - - public updateRubberBandMultiplot(multiplotRubberBands: Map): void {} - - // public sendRubberBandsMultiplot(figures: Figure[]): void {} - - // protected receiveRubberBandFromFigure(figure: Figure): void {} - public highlightFromReferencePath(highlightData: HighlightData) { const highlight = highlightData.highlight; const shapes = this.getShapesFromPath(highlightData.referencePath); diff --git a/src/interactions.ts b/src/interactions.ts index fc04f07b..a003215d 100644 --- a/src/interactions.ts +++ b/src/interactions.ts @@ -11,4 +11,4 @@ export interface HighlightData { export const highlightShape: Subject = new Subject(); export const onAxisSelection: Subject = new Subject(); -export const rubberbandsChange: BehaviorSubject> = new BehaviorSubject(new Map()); +export const rubberbandsChange: Subject> = new Subject(); diff --git a/src/multiplot.ts b/src/multiplot.ts index 089abe66..171a1fb1 100644 --- a/src/multiplot.ts +++ b/src/multiplot.ts @@ -180,6 +180,7 @@ export class Multiplot { } public selectionOn(): void { + // Never called. Is this useful ? this.isSelecting = true; this.figures.forEach(figure => figure.isSelecting = true); this.canvas.style.cursor = 'crosshair'; @@ -306,22 +307,6 @@ export class Multiplot { public updateHoveredIndices(figure: Figure): void { this.hoveredIndices = figure.sendHoveredIndicesMultiplot() } - // public initRubberBands(): void { - // this.rubberBands = new Map(); - // this.figures.forEach(figure => figure.initRubberBandMultiplot(this.rubberBands)); - // } - - public updateRubberBands(currentFigure: Figure): void { - console.log(this.isSelecting) - // if (this.isSelecting) { - // if (!this.rubberBands) this.initRubberBands(); - rubberbandsChange.next(this.rubberBands) - // currentFigure.sendRubberBandsMultiplot(this.figures); - - // this.figures.forEach(figure => figure.updateRubberBandMultiplot(this.rubberBands)); - // } - } - public resetRubberBands(): void { this.rubberBands.forEach(rubberBand => rubberBand.reset()); this.figures.forEach(figure => figure.resetRubberBands()); @@ -411,7 +396,7 @@ export class Multiplot { } else this.resizeWithMouse(absoluteMouse, clickedObject); this.updateHoveredIndices(this.figures[this.hoveredFigureIndex]); - this.updateRubberBands(this.figures[this.hoveredFigureIndex]); + rubberbandsChange.next(this.rubberBands); this.updateSelectedIndices(); return [canvasMouse, frameMouse, absoluteMouse, canvasDown, hasLeftFigure] } @@ -428,7 +413,7 @@ export class Multiplot { if (!(this.figures[this.hoveredFigureIndex] instanceof Graph2D || this.figures[this.hoveredFigureIndex] instanceof Draw)) { this.clickedIndices = this.figures[this.hoveredFigureIndex].clickedIndices; } - this.updateRubberBands(this.figures[this.hoveredFigureIndex]); + rubberbandsChange.next(this.rubberBands); hasLeftFigure = this.resetStateAttributes(shiftKey, ctrlKey); clickedObject = null; this.updateSelectedIndices(); diff --git a/src/shapes.ts b/src/shapes.ts index 81334acf..1171b354 100644 --- a/src/shapes.ts +++ b/src/shapes.ts @@ -907,10 +907,6 @@ export class RubberBand extends Rect { public get isTranslating(): boolean { return !this.minUpdate && !this.maxUpdate && this.isClicked} - // public selfSend(rubberBands: Map): void { - // rubberBands.set(this.attributeName, new RubberBand(this.attributeName, 0, 0, this.isVertical)) - // } - public defaultStyle(): void { this.lineWidth = 0.1; this.fillStyle = C.RUBBERBAND_COLOR; @@ -918,11 +914,6 @@ export class RubberBand extends Rect { this.alpha = C.RUBBERBAND_ALPHA; } - // public selfSendRange(rubberBands: Map): void { - // rubberBands.get(this.attributeName).minValue = this.minValue; - // rubberBands.get(this.attributeName).maxValue = this.maxValue; - // } - public updateCoords(canvasCoords: Vertex, axisOrigin: Vertex, axisEnd: Vertex): void { const coord = this.isVertical ? "y" : "x"; this.canvasMin = Math.max(canvasCoords.min, axisOrigin[coord]); From 257ac399616a194f0857ff19d8299320b7c6d944 Mon Sep 17 00:00:00 2001 From: GhislainJ Date: Mon, 24 Jun 2024 21:07:33 +0200 Subject: [PATCH 5/5] fix(rubberbands): also emit axis when selection box is used --- src/figures.ts | 4 +++- src/multiplot.ts | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/figures.ts b/src/figures.ts index f1ee1b1b..61f5fecb 100644 --- a/src/figures.ts +++ b/src/figures.ts @@ -5,7 +5,7 @@ import { colorHsl } from "./colors" import { PointStyle } from "./styles" import { Vertex, Shape } from "./baseShape" import { Rect, Point, LineSequence } from "./primitives" -import { ScatterPoint, Bar, RubberBand, SelectionBox } from "./shapes" +import { ScatterPoint, Bar, SelectionBox } from "./shapes" import { Axis, ParallelAxis } from "./axes" import { ShapeCollection, GroupCollection, PointSet } from "./collections" import { RemoteFigure } from "./remoteFigure" @@ -196,6 +196,8 @@ export class Frame extends Figure { this.axes[1].rubberBand.minValue = Math.min(frameDown.y, frameMouse.y); this.axes[0].rubberBand.maxValue = Math.max(frameDown.x, frameMouse.x); this.axes[1].rubberBand.maxValue = Math.max(frameDown.y, frameMouse.y); + onAxisSelection.next(this.axes[0]); + onAxisSelection.next(this.axes[1]); super.updateSelectionBox(...this.rubberBandsCorners); } diff --git a/src/multiplot.ts b/src/multiplot.ts index 171a1fb1..33c3957a 100644 --- a/src/multiplot.ts +++ b/src/multiplot.ts @@ -47,7 +47,6 @@ export class Multiplot { this.nSamples = this.features.entries().next().value[1].length; this.computeTable(); this.draw(); - // this.initRubberBands(); this.mouseListener(); }