Skip to content

Commit

Permalink
Annotation labels (#27)
Browse files Browse the repository at this point in the history
* adding legend capabilities

* add hover state and improve legend

* toggle icon bar

* defaulting grid to off

* editing improvements

* add time and freq labels to annotations

* add check for impossible annotations to freq/time layers

* fix compressed annotations

* add layer toggles for ms, KHz, species
  • Loading branch information
BryonLewis authored Jan 30, 2024
1 parent 22717a9 commit 0a76060
Show file tree
Hide file tree
Showing 9 changed files with 617 additions and 19 deletions.
5 changes: 0 additions & 5 deletions client/src/components/SpectrogramViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ export default defineComponent({
type: String as PropType<string | null>,
required: true,
},
grid: {
type: Boolean,
default: false,
},
},
emits: ["update:annotation", "create:annotation", "selected", "geoViewerRef", "hoverData", 'set-mode',],
setup(props, { emit }) {
Expand Down Expand Up @@ -198,7 +194,6 @@ export default defineComponent({
:spectro-info="spectroInfo"
:annotations="annotations"
:selected-id="selectedId"
:grid="grid"
@selected="clickSelected($event)"
@update:annotation="updateAnnotation($event)"
@create:annotation="createAnnotation($event)"
Expand Down
52 changes: 44 additions & 8 deletions client/src/components/geoJS/LayerManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { geojsonToSpectro, SpectroInfo } from "./geoJSUtils";
import EditAnnotationLayer from "./layers/editAnnotationLayer";
import RectangleLayer from "./layers/rectangleLayer";
import LegendLayer from "./layers/legendLayer";
import TimeLayer from "./layers/timeLayer";
import FreqLayer from "./layers/freqLayer";
import SpeciesLayer from "./layers/speciesLayer";
import { cloneDeep } from "lodash";
import useState from "../../use/useState";
export default defineComponent({
Expand All @@ -31,14 +34,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
grid: {
type: Boolean,
default: false,
}
},
emits: ['selected', 'update:annotation', 'create:annotation', 'set-cursor', 'set-mode'],
setup(props, { emit }) {
const { annotationState, setAnnotationState } = useState();
const { annotationState, setAnnotationState, layerVisibility, } = useState();
const selectedAnnotationId: Ref<null | number> = ref(null);
const hoveredAnnotationId: Ref<null | number> = ref(null);
const localAnnotations: Ref<SpectrogramAnnotation[]> = ref(cloneDeep(props.annotations));
Expand All @@ -47,6 +46,9 @@ export default defineComponent({
let rectAnnotationLayer: RectangleLayer;
let editAnnotationLayer: EditAnnotationLayer;
let legendLayer: LegendLayer;
let timeLayer: TimeLayer;
let freqLayer: FreqLayer;
let speciesLayer: SpeciesLayer;
const displayError = ref(false);
const errorMsg = ref('');
Expand Down Expand Up @@ -180,7 +182,30 @@ export default defineComponent({
rectAnnotationLayer.redraw();
}
if (!props.thumbnail) {
if (layerVisibility.value.includes('grid')) {
legendLayer.setGridEnabled(true);
} else {
legendLayer.setGridEnabled(false);
}
legendLayer.redraw();
if (layerVisibility.value.includes('time')) {
timeLayer.formatData(localAnnotations.value);
timeLayer.redraw();
} else {
timeLayer.disable();
}
if (layerVisibility.value.includes('freq')) {
freqLayer.formatData(localAnnotations.value);
freqLayer.redraw();
} else {
freqLayer.disable();
}
if (layerVisibility.value.includes('species')) {
speciesLayer.formatData(localAnnotations.value);
speciesLayer.redraw();
} else {
speciesLayer.disable();
}
}
if (editing.value && editingAnnotation.value) {
setTimeout(() => {
Expand All @@ -201,6 +226,7 @@ export default defineComponent({
nextTick(() => {
if (editAnnotationLayer && editAnnotationLayer.getMode() === 'disabled' && props.selectedId === null) {
emit('set-mode', 'disabled');
editAnnotationLayer.featureLayer.clear();
}
});
Expand All @@ -218,13 +244,23 @@ export default defineComponent({
rectAnnotationLayer.redraw();
if (!props.thumbnail) {
legendLayer = new LegendLayer(props.geoViewerRef, event, props.spectroInfo);
timeLayer = new TimeLayer(props.geoViewerRef, event, props.spectroInfo);
timeLayer.formatData(localAnnotations.value);
freqLayer = new FreqLayer(props.geoViewerRef, event, props.spectroInfo);
freqLayer.formatData(localAnnotations.value);
speciesLayer = new SpeciesLayer(props.geoViewerRef, event, props.spectroInfo);
speciesLayer.formatData(localAnnotations.value);
legendLayer.redraw();
timeLayer.disable();
freqLayer.disable();
speciesLayer.disable();
}
}
});
watch(() => props.grid, () => {
watch(layerVisibility, () => {
if (!props.thumbnail && legendLayer) {
legendLayer.setGridEnabled(props.grid);
triggerUpdate();
}
});
Expand Down Expand Up @@ -272,5 +308,5 @@ export default defineComponent({
</v-card-actions>
</v-card>
</v-dialog>
<div />
</template>
./layers/timeLalyer
1 change: 0 additions & 1 deletion client/src/components/geoJS/layers/editAnnotationLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ export default class EditAnnotationLayer {
this.skipNextExternalUpdate = false;
this.featureLayer.removeAllAnnotations(false, true);
this.setMode(null);
this.featureLayer.clear();
this.shapeInProgress = null;
if (this.selectedHandleIndex !== -1) {
this.selectedHandleIndex = -1;
Expand Down
192 changes: 192 additions & 0 deletions client/src/components/geoJS/layers/freqLayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/* eslint-disable class-methods-use-this */
import { SpectrogramAnnotation } from "../../../api/api";
import { SpectroInfo, spectroToGeoJSon } from "../geoJSUtils";
import { LayerStyle } from "./types";

interface LineData {
line: GeoJSON.LineString;
thicker?: boolean;
grid?: boolean;
}

interface TextData {
text: string;
x: number;
y: number;
offsetY?: number;
offsetX?: number;
}

export default class FreqLayer {
lineData: LineData[];

// eslint-disable-next-line @typescript-eslint/no-explicit-any
lineLayer: any;

textData: TextData[];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
textLayer: any;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
geoViewerRef: any;


// eslint-disable-next-line @typescript-eslint/no-explicit-any
event: (name: string, data: any) => void;

spectroInfo: SpectroInfo;

textStyle: LayerStyle<TextData>;
lineStyle: LayerStyle<LineData>;


// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
geoViewerRef: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
event: (name: string, data: any) => void,
spectroInfo: SpectroInfo
) {
this.geoViewerRef = geoViewerRef;
this.lineData = [];
this.spectroInfo = spectroInfo;
this.textData = [];
this.event = event;
//Only initialize once, prevents recreating Layer each edit
const layer = this.geoViewerRef.createLayer("feature", {
features: ["text", "line"],
});
this.textLayer = layer
.createFeature("text")
.text((data: TextData) => data.text)
.position((data: TextData) => ({ x: data.x, y: data.y }));

this.lineLayer = layer.createFeature("line");

this.textStyle = this.createTextStyle();
this.lineStyle = this.createLineStyle();
}


formatData(annotationData: SpectrogramAnnotation[]) {
this.textData = [];
this.lineData = [];
const lineDist = 16;
annotationData.forEach((annotation: SpectrogramAnnotation) => {
const polygon = spectroToGeoJSon(annotation, this.spectroInfo);
const {low_freq, high_freq } = annotation;
const [xmin, ymin] = polygon.coordinates[0][0];
const [xmax, ymax] = polygon.coordinates[0][2];
// For the compressed view we need to filter out default or NaN numbers
if (Number.isNaN(xmax) || Number.isNaN(xmin) || Number.isNaN(ymax) || Number.isNaN(ymin)) {
return;
}
if (xmax === -1 && ymin === -1 && ymax === -1 && xmin === -1) {
return;
}
// We create two small lines for the beginning/end of annotation
this.lineData.push({
line: {
type: "LineString",
coordinates: [
[xmax, ymin],
[xmax + lineDist, ymin],
],
},
thicker: true,
});
this.lineData.push({
line: {
type: "LineString",
coordinates: [
[xmax, ymax],
[xmax + lineDist, ymax],
],
},
thicker: true,
});
// Now we need to create the text Labels
this.textData.push({
text: `${(low_freq/1000).toFixed(1)}KHz`,
x: xmax + lineDist,
y: ymin ,
offsetX: 0,
offsetY: 0,
});
this.textData.push({
text: `${(high_freq/1000).toFixed(1)}KHz`,
x: xmax + lineDist,
y: ymax,
offsetX: 0,
offsetY: 0,
});
});
}

redraw() {
// add some styles
this.lineLayer
.data(this.lineData)
.line((d: LineData) => d.line.coordinates)
.style(this.createLineStyle())
.draw();
this.textLayer.data(this.textData).style(this.createTextStyle()).draw();
}

disable() {
this.lineLayer.data([]).draw();
this.textLayer.data([]).draw();
}


createLineStyle(): LayerStyle<LineData> {
return {
...{
strokeColor: "#00FFFF",
strokeWidth: 2.0,
antialiasing: 0,
stroke: true,
uniformPolygon: true,
fill: false,
},
strokeOpacity: (_point, _index, data) => {
// Reduce the rectangle opacity if a polygon is also drawn
if (data.grid) {
return 0.5;
}
return 1.0;
},

strokeWidth: (_point, _index, data) => {
if (data.thicker) {
return 4.0;
}
if (data.grid) {
return 1.0;
}
return 2.0;
},
};
}
createTextStyle(): LayerStyle<TextData> {
return {
...{
strokeColor: "yellow",
strokeWidth: 2.0,
antialiasing: 0,
stroke: true,
uniformPolygon: true,
fill: false,
},
color: () => {
return "white";
},
offset: (data) => ({
x: data.offsetX || 0,
y: data.offsetY || 0,
}),
textAlign: 'starts',
};
}
}
4 changes: 2 additions & 2 deletions client/src/components/geoJS/layers/rectangleLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ export default class RectangleLayer {
}
if (xmax === -1 && ymin === -1 && ymax === -1 && xmin === -1) {
return;
}
const newAnnotation: RectGeoJSData = {
}
const newAnnotation: RectGeoJSData = {
id: annotation.id,
selected: annotation.id === selectedIndex,
editing: annotation.editing,
Expand Down
Loading

0 comments on commit 0a76060

Please sign in to comment.