Commit b3b24b01 authored by Laura Morel's avatar Laura Morel
Browse files

Add interactive legend

Allow the user to highlight an annotation legend by hovering over the color on the heatmap.

* Cleaner layout with bootstrap
* Functional cookies for Django sessions (needed for legend)
parent 9feb9464
......@@ -25,3 +25,5 @@ Django/venv
Django/VizFaDa/__pycache__
Django/VizFaDa/settings/__pycache__
Angular/dist/
Django/VizFaDa/db.sqlite3
/node_modules
/e2e
# Stage 0, "build-stage", based on Node.js, to build and compile the frontend
FROM node:14.13.1
# Stage 1 : build
FROM node:14.13.1 as build
WORKDIR /app
COPY Angular/package*.json /app/
COPY package.json /app
RUN npm install
COPY Angular/ /app/
ARG configuration=production
RUN npm run build --configuration $configuration
COPY . /app/
RUN npm run build --prod
# Stage 2: serve
FROM nginx:1.17.1
COPY --from=build /app/dist/VizFaDa /usr/share/nginx/html
......@@ -24,6 +24,7 @@
"src/assets"
],
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
],
"scripts": []
......
This diff is collapsed.
......@@ -12,15 +12,20 @@
"private": true,
"dependencies": {
"@angular/animations": "~10.0.5",
"@angular/cdk": "^10.2.4",
"@angular/common": "~10.0.5",
"@angular/compiler": "~10.0.5",
"@angular/core": "~10.0.5",
"@angular/flex-layout": "^10.0.0-beta.32",
"@angular/forms": "~10.0.5",
"@angular/localize": "~10.0.5",
"@angular/platform-browser": "~10.0.5",
"@angular/platform-browser-dynamic": "~10.0.5",
"@angular/router": "~10.0.5",
"@ng-bootstrap/ng-bootstrap": "^7.0.0",
"@nguniversal/express-engine": "^10.1.0",
"angularjs-slider": "^7.0.0",
"bootstrap": "^4.5.2",
"ng-multiselect-dropdown": "^0.2.10",
"ngx-color-picker": "^10.0.1",
"ngx-cookie-service": "^10.1.1",
......
......@@ -3,6 +3,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { NgMultiSelectDropDownModule } from 'ng-multiselect-dropdown';
import { ColorPickerModule } from 'ngx-color-picker';
......@@ -18,6 +19,7 @@ import { LegendComponent } from './main/view/heatmap/legend/legend.component';
import { ViewComponent } from './main/view/view.component';
import { MainComponent } from './main/main.component';
import { CanvasComponent } from './main/view/heatmap/canvas/canvas.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [
......@@ -40,6 +42,8 @@ import { CanvasComponent } from './main/view/heatmap/canvas/canvas.component';
ColorPickerModule,
NgxSpinnerModule,
BrowserAnimationsModule,
NgbModule,
FlexLayoutModule,
],
providers: [CookieService],
bootstrap: [AppComponent],
......
.fhl {
border-radius: 5px;
border-color: black;
}
control-container {
height: 100vh;
overflow-y: auto;
}
form {
margin: 5px;
height: 100%;
}
<ng-container name="form">
<form [formGroup]="formGroup" novalidate (ngSubmit)=onSubmit()>
<div class="control-container">
<form [formGroup]="formGroup" novalidate (ngSubmit)=onSubmit()
fxLayout="column" fxLayoutAlign="space-between stretch">
<ngb-accordion #acc="ngbAccordion" name="form" activeIds="ngb-panel-0, ngb-panel-3">
<ngb-panel title="Species">
<ng-template ngbPanelContent>
<select id="species"
formControlname="species"
class="form-control"
(change)="changeSpecies($event)">
<option value="">-- choose species --</option>
<option *ngFor="let sp of SPECIES" [ngValue]="sp">{{sp}}</option>
</select>
</ng-template>
</ngb-panel>
<label for="species">Species : </label>
<select id="species" formControlname="species" (change)="changeSpecies($event)">
<option value="">-- choose species --</option>
<option *ngFor="let sp of SPECIES" [ngValue]="sp">{{sp}}</option>
</select>
<ngb-panel title="Filters"><form [formGroup]="formGroup" novalidate (ngSubmit)=onSubmit()
fxLayout="column" fxLayoutAlign="space-between stretch
<ng-template ngbPanelContent>
<div formArrayName="filters">
<p>Filters :</p>
<div class="fhl" *ngFor="let control of formFilters.controls; let i = index">
<button type="button" (click)="delFromArray('filters', i)">Delete</button>
<app-filter [formGroup] = "formGroup.controls.filters.controls[i]"
[METADATA] = "METADATA"></app-filter>
</div>
<button type="button" (click)="addFilter()">Add Filter</button>
</div>
</ng-template>
</ngb-panel>
<div formArrayName="filters">
<p>Filters :</p>
<div *ngFor="let control of formFilters.controls; let i = index">
<button type="button" (click)="delFromArray('filters', i)">Delete</button>
<app-filter [formGroup] = "formGroup.controls.filters.controls[i]"
[METADATA] = "METADATA"></app-filter>
</div>
<button type="button" (click)="addFilter()">Add Filter</button>
</div>
<ngb-panel title="Highlights">
<ng-template ngbPanelContent>
<div formArrayName="highlights">
<p>Highlights :</p>
<div class="fhl" *ngFor="let control of formHighlights.controls; let i = index">
<button type="button" (click)="delFromArray('highlights', i)">Delete</button>
<app-highlight [formGroup] = "formGroup.controls.highlights.controls[i]"
[METADATA] = "METADATA"></app-highlight>
</div>
<button type="button" (click)="addHighlight()">Add Highlight</button>
</div>
</ng-template>
</ngb-panel>
<div formArrayName="highlights">
<p>Highlights :</p>
<div *ngFor="let control of formHighlights.controls; let i = index">
<button type="button" (click)="delFromArray('highlights', i)">Delete</button>
<app-highlight [formGroup] = "formGroup.controls.highlights.controls[i]"
[METADATA] = "METADATA"></app-highlight>
</div>
<button type="button" (click)="addHighlight()">Add Highlight</button>
</div>
<ngb-panel title="Annotation">
<ng-template ngbPanelContent>
<label for="annotated">Annotate : </label>
<select class="form-control" id="annotated" formControlname="annotated" (change)="changeAnnotation($event)">
<option value="">-- choose field --</option>
<option *ngFor="let f of fields" [ngValue]="f">{{f}}</option>
</select>
<app-legend *ngIf="formGroup.controls['annotated'] != ''"></app-legend>
</ng-template>
<label for="annotated">Annotate : </label>
<select id="annotated" formControlname="annotated" (change)="changeAnnotation($event)">
<option value="">-- choose field --</option>
<option *ngFor="let f of fields" [ngValue]="f">{{f}}</option>
</select>
</ngb-panel>
<label for="size">Size : </label>
<select id="size" formControlname="options" (change)="changeSize($event)">
<option value="">-- choose size --</option>
<option *ngFor="let sz of SIZES" [ngValue]="sz">{{sz}}</option>
</select>
<ngb-panel title="Graph options">
<ng-template ngbPanelContent>
<label for="size">Size : </label>
<select class="form-control" id="size" formControlname="options" (change)="changeSize($event)">
<option value="">-- choose size --</option>
<option *ngFor="let sz of SIZES" [ngValue]="sz">{{sz}}</option>
</select>
</ng-template>
</ngb-panel>
<button type="submit">Submit</button>
</form>
</ng-container>
</ngb-accordion>
<button fxFlexAlign="end" type="submit">Submit</button>
</form>
</div>
......@@ -3,8 +3,10 @@ import { Validators, FormGroup, FormArray, FormBuilder, FormControl } from '@ang
import { NgxSpinnerService } from "ngx-spinner";
import { LegendComponent } from '../view/heatmap/legend/legend.component';
import { FilterComponent } from './filter/filter.component';
import { DataService } from '../data.service';
import { HeatmapInfoService } from '../heatmap-info.service';
@Component({
selector: 'app-controller',
......@@ -13,6 +15,8 @@ import { DataService } from '../data.service';
})
export class ControllerComponent implements OnInit {
@ViewChild(LegendComponent) legend: LegendComponent;
public SPECIES = ["Gallus_gallus", "Bos_taurus", "Ovis_aries"];
public SIZES = ["10", "15", "20"];
public METADATA: Object;
......@@ -23,7 +27,8 @@ export class ControllerComponent implements OnInit {
constructor(private builder: FormBuilder,
private dataService: DataService,
private spinner: NgxSpinnerService) { }
private spinner: NgxSpinnerService,
private heatmapInfoService: HeatmapInfoService,) { }
ngOnInit(): void {
let newForm = this.builder.group({
......@@ -33,7 +38,7 @@ export class ControllerComponent implements OnInit {
annotated: "",
options: this.builder.group({})
});
console.log(newForm)
console.log(newForm);
this.formGroup = newForm;
}
......@@ -105,6 +110,7 @@ export class ControllerComponent implements OnInit {
console.log("Submitting data")
console.log(this.formGroup.value);
this.dataService.submit_data(this.formGroup.value);
this.heatmapInfoService.img_loaded(false);
}
}
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, Subject } from 'rxjs';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, Subject, ReplaySubject, BehaviorSubject} from 'rxjs';
import { CookieService } from "ngx-cookie-service";
......@@ -11,21 +11,10 @@ export class DataService {
private baseURL = 'http://127.0.0.1:8000';
public submittedData = new Subject;
private headers: HttpHeaders;
constructor(private http: HttpClient, private cookieService: CookieService) {
}
get_img(species, size): Observable<any> {
const sp = species.replace(" ", "_");
const imgurl: string = `${this.baseURL}/${sp}/img/${size}`;
console.log("Fetching image at ", imgurl);
const options = {responseType: 'blob' as 'blob'};
const result = this.http.get(imgurl, { responseType:'text' as 'text'});
console.log(result)
return result;
}
get_legend(): Observable<Object> {
let url = `${this.baseURL}/api/legend`;
console.log("Fetching legend at ", url);
......@@ -43,7 +32,7 @@ export class DataService {
url = url + `?q=${JSON.stringify(data)}`;
console.log("Fetching image at ", url);
// return url;
return this.http.get(`${url}`, {headers: this.headers, responseType: 'blob', reportProgress: true});
return this.http.get(`${url}`, {responseType: 'blob', reportProgress: true, withCredentials: true});
}
get_fields(data: Object): Observable<Object> {
......
import { TestBed } from '@angular/core/testing';
import { HeatmapInfoService } from './heatmap-info.service';
describe('HeatmapInfoService', () => {
let service: HeatmapInfoService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(HeatmapInfoService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import { ReplaySubject, BehaviorSubject} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class HeatmapInfoService {
public pxcolor = new ReplaySubject;
public imgLoaded = new BehaviorSubject(false);
constructor() { }
pixel_color(color: string): void {
this.pxcolor.next(color);
}
img_loaded(bool: boolean): void {
this.imgLoaded.next(bool);
}
}
div {
height: 100%;
}
app-controller {
height: 100%;
}
<app-controller></app-controller>
<div fxLayout="row" fxLayoutGap="20px" fxLayoutAlign="start stretch">
<app-controller fxFlex="1 1 25%"></app-controller>
<app-view></app-view>
</div>
import { Component, OnInit, AfterViewInit, ViewChild, Input, ElementRef } from '@angular/core';
import { Component, OnInit, AfterViewInit, ViewChild, Input, Output, ElementRef } from '@angular/core';
import { fromEvent, ReplaySubject } from 'rxjs';
import { HeatmapInfoService } from '../../../heatmap-info.service';
@Component({
selector: 'app-canvas',
......@@ -6,47 +9,50 @@ import { Component, OnInit, AfterViewInit, ViewChild, Input, ElementRef } from '
styleUrls: ['./canvas.component.css']
})
export class CanvasComponent implements OnInit {
@Input() img: HTMLImageElement;
private hide: boolean;
@ViewChild('canvas', {static: false}) canvas: HTMLCanvasElement;
private img: HTMLImageElement = new Image();
public hide: boolean;
@ViewChild('canvas', {static: false}) canvas: ElementRef<HTMLCanvasElement>;
public context: CanvasRenderingContext2D;
public pxcolor: string = "white";
constructor() { }
constructor(private heatmapInfoService: HeatmapInfoService,) { }
ngOnInit(): void {
console.log("Canvas Init");
this.hide = true;
this.img.addEventListener("load", () => {
this.draw_img()
})
}
ngAfterViewInit(): void {
}
get_pixel_color(e): void {
console.log(this.canvas);
let offset = this.canvas;
let x = (e.pageX - offset.left);
let y = (e.pageY - offset.top);
let x = e.pageX - this.canvas.nativeElement.offsetLeft;
let y = e.pageY - this.canvas.nativeElement.offsetTop;
let imgdata = this.context.getImageData(x, y, 1, 1);
let pixel = imgdata.data;
this.pxcolor = `rgb(${pixel[0]}, ${pixel[1]}, ${pixel[2]})`
console.log(this.pxcolor)
this.heatmapInfoService.pixel_color(`rgb(${pixel[0]}, ${pixel[1]}, ${pixel[2]})`);
}
setImg(img: HTMLImageElement): void {
this.img = img;
this.draw_img();
setImg(src: string): void {
console.log("Loading image for canvas");
this.img = new Image();
this.img.addEventListener("load", () => {
console.log("Image loaded for canvas");
this.draw_img()
})
console.log("Source image for canvas: ", src);
this.img.src = src;
}
draw_img(): void {
console.log("Canvas drawing image: ", this.img);
console.log(this.img);
console.log(this.img.alt);
console.log(this.img.src);
console.log(this.img.height, this.img.width);
this.canvas.nativeElement.height = this.img.height;
this.canvas.nativeElement.width = this.img.width;
......@@ -56,6 +62,10 @@ export class CanvasComponent implements OnInit {
this.context = this.canvas.nativeElement.getContext('2d');
this.context.drawImage(this.img, 0, 0);
this.hide = false;
let mousemove$ = fromEvent(this.canvas.nativeElement, 'mousemove');
mousemove$.subscribe((event) => {
this.get_pixel_color(event);
})
}
......
import { Component, HostListener, Directive, HostBinding, Input} from '@angular/core';
@Directive({selector: '[canvasDir]'})
export class HostDirective {
@HostBinding('title') title = 'placeholder';
@HostBinding('data-toggle')
constructor(private elementRef: ElementRef){}
}
<app-canvas></app-canvas>
<ng-container *ngIf="!imgLoading; else elseTemplate">
<div *ngIf="img == null">
<div *ngIf="src == no_heatmap">
<p> No heatmap </p>
</div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment