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

React UI: ZOrder implementation #1176

Merged
merged 15 commits into from
Feb 25, 2020
5 changes: 2 additions & 3 deletions cvat-canvas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Canvas itself handles:

interface Canvas {
html(): HTMLDivElement;
setZLayer(zLayer: number | null): void;
setup(frameData: any, objectStates: any[]): void;
activate(clientID: number, attributeID?: number): void;
rotate(rotation: Rotation, remember?: boolean): void;
Expand Down Expand Up @@ -147,9 +148,6 @@ Standard JS events are used.
});
```

## States

![](images/states.svg)

## API Reaction

Expand All @@ -170,3 +168,4 @@ Standard JS events are used.
| dragCanvas() | + | - | - | - | - | - | + | - |
| zoomCanvas() | + | - | - | - | - | - | - | + |
| cancel() | - | + | + | + | + | + | + | + |
| setZLayer() | + | + | + | + | + | + | + | + |
5 changes: 5 additions & 0 deletions cvat-canvas/src/typescript/canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import '../scss/canvas.scss';

interface Canvas {
html(): HTMLDivElement;
setZLayer(zLayer: number | null): void;
setup(frameData: any, objectStates: any[]): void;
activate(clientID: number | null, attributeID?: number): void;
rotate(rotation: Rotation, remember?: boolean): void;
Expand Down Expand Up @@ -66,6 +67,10 @@ class CanvasImpl implements Canvas {
return this.view.html();
}

public setZLayer(zLayer: number | null): void {
this.model.setZLayer(zLayer);
}

public setup(frameData: any, objectStates: any[]): void {
this.model.setup(frameData, objectStates);
}
Expand Down
5 changes: 5 additions & 0 deletions cvat-canvas/src/typescript/canvasController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {

export interface CanvasController {
readonly objects: any[];
readonly zLayer: number | null;
readonly focusData: FocusData;
readonly activeElement: ActiveElement;
readonly drawData: DrawData;
Expand Down Expand Up @@ -105,6 +106,10 @@ export class CanvasControllerImpl implements CanvasController {
this.model.geometry = geometry;
}

public get zLayer(): number | null {
return this.model.zLayer;
}

public get objects(): any[] {
return this.model.objects;
}
Expand Down
19 changes: 19 additions & 0 deletions cvat-canvas/src/typescript/canvasModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export enum UpdateReasons {
IMAGE_FITTED = 'image_fitted',
IMAGE_MOVED = 'image_moved',
GRID_UPDATED = 'grid_updated',
SET_Z_LAYER = 'set_z_layer',

OBJECTS_UPDATED = 'objects_updated',
SHAPE_ACTIVATED = 'shape_activated',
Expand Down Expand Up @@ -113,6 +114,7 @@ export enum Mode {
export interface CanvasModel {
readonly image: HTMLImageElement | null;
readonly objects: any[];
readonly zLayer: number | null;
readonly gridSize: Size;
readonly focusData: FocusData;
readonly activeElement: ActiveElement;
Expand All @@ -124,6 +126,7 @@ export interface CanvasModel {
geometry: Geometry;
mode: Mode;

setZLayer(zLayer: number | null): void;
zoom(x: number, y: number, direction: number): void;
move(topOffset: number, leftOffset: number): void;

Expand Down Expand Up @@ -163,6 +166,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
rememberAngle: boolean;
scale: number;
top: number;
zLayer: number | null;
drawData: DrawData;
mergeData: MergeData;
groupData: GroupData;
Expand Down Expand Up @@ -204,6 +208,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
rememberAngle: false,
scale: 1,
top: 0,
zLayer: null,
drawData: {
enabled: false,
initialState: null,
Expand All @@ -222,6 +227,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
};
}

public setZLayer(zLayer: number | null): void {
this.data.zLayer = zLayer;
this.notify(UpdateReasons.SET_Z_LAYER);
}

public zoom(x: number, y: number, direction: number): void {
const oldScale: number = this.data.scale;
const newScale: number = direction > 0 ? oldScale * 6 / 5 : oldScale * 5 / 6;
Expand Down Expand Up @@ -515,11 +525,20 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel {
));
}

public get zLayer(): number | null {
return this.data.zLayer;
}

public get image(): HTMLImageElement | null {
return this.data.image;
}

public get objects(): any[] {
if (this.data.zLayer !== null) {
return this.data.objects
.filter((object: any): boolean => object.zOrder <= this.data.zLayer);
}

return this.data.objects;
}

Expand Down
65 changes: 57 additions & 8 deletions cvat-canvas/src/typescript/canvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,16 @@ export class CanvasViewImpl implements CanvasView, Listener {

private onDrawDone(data: object, continueDraw?: boolean): void {
if (data) {
const { zLayer } = this.controller;
const event: CustomEvent = new CustomEvent('canvas.drawn', {
bubbles: false,
cancelable: true,
detail: {
// eslint-disable-next-line new-cap
state: data,
state: {
...data,
zOrder: zLayer || 0,
},
continue: continueDraw,
},
});
Expand Down Expand Up @@ -364,6 +368,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}


private setupObjects(states: any[]): void {
const { offset } = this.controller.geometry;
const translate = (points: number[]): number[] => points
Expand Down Expand Up @@ -403,6 +408,7 @@ export class CanvasViewImpl implements CanvasView, Listener {

this.addObjects(created, translate);
this.updateObjects(updated, translate);
this.sortObjects();

if (this.controller.activeElement.clientID !== null) {
const { clientID } = this.controller.activeElement;
Expand Down Expand Up @@ -685,12 +691,12 @@ export class CanvasViewImpl implements CanvasView, Listener {
this.setupObjects([]);
this.moveCanvas();
this.resizeCanvas();
} else if (reason === UpdateReasons.IMAGE_ZOOMED || reason === UpdateReasons.IMAGE_FITTED) {
} else if ([UpdateReasons.IMAGE_ZOOMED, UpdateReasons.IMAGE_FITTED].includes(reason)) {
this.moveCanvas();
this.transformCanvas();
} else if (reason === UpdateReasons.IMAGE_MOVED) {
this.moveCanvas();
} else if (reason === UpdateReasons.OBJECTS_UPDATED) {
} else if ([UpdateReasons.OBJECTS_UPDATED, UpdateReasons.SET_Z_LAYER].includes(reason)) {
if (this.mode === Mode.GROUP) {
this.groupHandler.resetSelectedObjects();
}
Expand Down Expand Up @@ -833,6 +839,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
shapeType: state.shapeType,
points: [...state.points],
attributes: { ...state.attributes },
zOrder: state.zOrder,
};
}

Expand All @@ -851,6 +858,15 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}

if (drawnState.zOrder !== state.zOrder) {
if (state.shapeType === 'points') {
this.svgShapes[clientID].remember('_selectHandler').nested
.attr('data-z-order', state.zOrder);
} else {
this.svgShapes[clientID].attr('data-z-order', state.zOrder);
}
}

if (drawnState.occluded !== state.occluded) {
if (state.occluded) {
this.svgShapes[clientID].addClass('cvat_canvas_shape_occluded');
Expand Down Expand Up @@ -961,6 +977,27 @@ export class CanvasViewImpl implements CanvasView, Listener {
}
}

private sortObjects(): void {
// TODO: Can be significantly optimized
const states = Array.from(
this.content.getElementsByClassName('cvat_canvas_shape'),
).map((state: SVGElement): [SVGElement, number] => (
[state, +state.getAttribute('data-z-order')]
));

const needSort = states.some((pair): boolean => pair[1] !== states[0][1]);
if (!states.length || !needSort) {
return;
}

const sorted = states.sort((a, b): number => a[1] - b[1]);
sorted.forEach((pair): void => {
this.content.appendChild(pair[0]);
});

this.content.prepend(...sorted.map((pair): SVGElement => pair[0]));
}

private deactivate(): void {
if (this.activeElement.clientID !== null) {
const { clientID } = this.activeElement;
Expand Down Expand Up @@ -989,6 +1026,8 @@ export class CanvasViewImpl implements CanvasView, Listener {
delete this.svgTexts[clientID];
}

this.sortObjects();

this.activeElement = {
clientID: null,
attributeID: null,
Expand Down Expand Up @@ -1016,6 +1055,10 @@ export class CanvasViewImpl implements CanvasView, Listener {
const [state] = this.controller.objects
.filter((_state: any): boolean => _state.clientID === clientID);

if (!state) {
return;
}

if (state.shapeType === 'points') {
this.svgShapes[clientID].remember('_selectHandler').nested
.style('pointer-events', state.lock ? 'none' : '');
Expand All @@ -1040,7 +1083,13 @@ export class CanvasViewImpl implements CanvasView, Listener {
}

const self = this;
this.content.append(shape.node);
if (state.shapeType === 'points') {
this.content.append(this.svgShapes[clientID]
.remember('_selectHandler').nested.node);
} else {
this.content.append(shape.node);
}

(shape as any).draggable().on('dragstart', (): void => {
this.mode = Mode.DRAG;
if (text) {
Expand Down Expand Up @@ -1197,7 +1246,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
'shape-rendering': 'geometricprecision',
stroke: state.color,
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
zOrder: state.zOrder,
'data-z-order': state.zOrder,
}).move(xtl, ytl)
.addClass('cvat_canvas_shape');

Expand All @@ -1221,7 +1270,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
'shape-rendering': 'geometricprecision',
stroke: state.color,
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
zOrder: state.zOrder,
'data-z-order': state.zOrder,
}).addClass('cvat_canvas_shape');

if (state.occluded) {
Expand All @@ -1244,7 +1293,7 @@ export class CanvasViewImpl implements CanvasView, Listener {
'shape-rendering': 'geometricprecision',
stroke: state.color,
'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale,
zOrder: state.zOrder,
'data-z-order': state.zOrder,
}).addClass('cvat_canvas_shape');

if (state.occluded) {
Expand All @@ -1264,9 +1313,9 @@ export class CanvasViewImpl implements CanvasView, Listener {
const group = basicPolyline.remember('_selectHandler').nested
.addClass('cvat_canvas_shape').attr({
clientID: state.clientID,
zOrder: state.zOrder,
id: `cvat_canvas_shape_${state.clientID}`,
fill: state.color,
'data-z-order': state.zOrder,
}).style({
'fill-opacity': 1,
});
Expand Down
9 changes: 4 additions & 5 deletions cvat-core/src/annotations-collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,11 @@
this.objects = {}; // key is a client id
this.count = 0;
this.flush = false;
this.collectionZ = {}; // key is a frame, {max, min} are values
this.groups = {
max: 0,
}; // it is an object to we can pass it as an argument by a reference
this.injection = {
labels: this.labels,
collectionZ: this.collectionZ,
groups: this.groups,
frameMeta: this.frameMeta,
history: this.history,
Expand Down Expand Up @@ -461,7 +459,7 @@
points: [...objectState.points],
occluded: objectState.occluded,
outside: objectState.outside,
zOrder: 0,
zOrder: objectState.zOrder,
attributes: Object.keys(objectState.attributes)
.reduce((accumulator, attrID) => {
if (!labelAttributes[attrID].mutable) {
Expand Down Expand Up @@ -725,6 +723,7 @@
} else {
checkObjectType('state occluded', state.occluded, 'boolean', null);
checkObjectType('state points', state.points, null, Array);
checkObjectType('state zOrder', state.zOrder, 'integer', null);

for (const coord of state.points) {
checkObjectType('point coordinate', coord, 'number', null);
Expand All @@ -746,7 +745,7 @@
occluded: state.occluded || false,
points: [...state.points],
type: state.shapeType,
z_order: 0,
z_order: state.zOrder,
});
} else if (state.objectType === 'track') {
constructed.tracks.push({
Expand All @@ -763,7 +762,7 @@
outside: false,
points: [...state.points],
type: state.shapeType,
z_order: 0,
z_order: state.zOrder,
}],
});
} else {
Expand Down
4 changes: 4 additions & 0 deletions cvat-core/src/annotations-filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ class AnnotationsFilter {

toJSONQuery(filters) {
try {
if (!Array.isArray(filters) || filters.some((value) => typeof (value) !== 'string')) {
throw Error('Argument must be an array of strings');
}

if (!filters.length) {
return [[], '$.objects[*].clientID'];
}
Expand Down
Loading