Skip to content

Commit

Permalink
Merge pull request #1657 from UUDigitalHumanitieslab/feature/mapdatar…
Browse files Browse the repository at this point in the history
…esults

Use MapDataResults class to manage fetching, queryModel subscription
  • Loading branch information
ar-jan authored Oct 4, 2024
2 parents 652d5ab + 6256fbf commit 7b8cfdd
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 33 deletions.
77 changes: 77 additions & 0 deletions frontend/src/app/models/map-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { map, of, Observable, switchMap, withLatestFrom } from 'rxjs';
import { Results } from './results';
import { GeoDocument, GeoLocation } from './search-results';
import { Params } from '@angular/router';
import { VisualizationService } from '@services';
import { Store } from '../store/types';
import { QueryModel } from './query';
import { CorpusField } from './corpus';
import { findByName } from '@utils/utils';


interface MapDataParameters {
field: CorpusField;
}


interface MapData {
mapCenter: GeoLocation;
geoDocuments: GeoDocument[];
}


export class MapDataResults extends Results<MapDataParameters, MapData> {
private mapCenter$: Observable<GeoLocation>;

constructor(
store: Store,
query: QueryModel,
private visualizationService: VisualizationService
) {
super(store, query, ['visualizedField']);
this.connectToStore();
this.mapCenter$ = this.state$.pipe(
map(state => state.field),
switchMap(field => field
? this.visualizationService.getGeoCentroid(field.name, this.query.corpus)
: of(null)
)
);
this.getResults();
}

fetch(): Observable<MapData> {
const field = this.state$.value.field;
if (!field) {
return of({ geoDocuments: [], mapCenter: null });
}

const geoDocuments$ = this.visualizationService.getGeoData(
field.name,
this.query,
this.query.corpus
);

return geoDocuments$.pipe(
withLatestFrom(this.mapCenter$),
map(([geoDocuments, mapCenter]) => ({
geoDocuments,
mapCenter
}))
);
}

protected stateToStore(state: MapDataParameters): Params {
return { visualizedField: state.field?.name || null };
}

protected storeToState(params: Params): MapDataParameters {
const fieldName = params['visualizedField'];
const field = findByName(this.query.corpus.fields, fieldName);
return { field };
}

protected storeOnComplete(): Params {
return {};
}
}
4 changes: 2 additions & 2 deletions frontend/src/app/services/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ export class ApiService {
return this.http.post<MostFrequentWordsResult[]>(url, data);
}

public geoData(data: WordcloudParameters): Promise<GeoDocument[]> {
public geoData(data: WordcloudParameters): Observable<GeoDocument[]> {
const url = this.apiRoute(this.visApiURL, 'geo');
return this.http.post<GeoDocument[]>(url, data).toPromise();
return this.http.post<GeoDocument[]>(url, data);
}

public geoCentroid(data: {corpus: string, field: string}): Promise<GeoLocation> {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/services/visualization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export class VisualizationService {
});
}

public async getGeoData(fieldName: string, queryModel: QueryModel, corpus: Corpus):
Promise<GeoDocument[]> {
public getGeoData(fieldName: string, queryModel: QueryModel, corpus: Corpus):
Observable<GeoDocument[]> {
const query = queryModel.toAPIQuery();
return this.apiService.geoData({
...query,
Expand Down
54 changes: 25 additions & 29 deletions frontend/src/app/visualization/map/map.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import embed, { VisualizationSpec } from 'vega-embed';

import { Corpus, CorpusField, GeoDocument, GeoLocation, QueryModel } from '@models';
import { VisualizationService } from '@services';
import { showLoading } from '@utils/utils';
import { MapDataResults } from '@models/map-data';
import { RouterStoreService } from 'app/store/router-store.service';


@Component({
Expand All @@ -22,12 +23,15 @@ export class MapComponent implements OnChanges {

@Output() mapError = new EventEmitter();

mapCenter: GeoLocation;
mapCenter: GeoLocation | null;
results: GeoDocument[];

isLoading$ = new BehaviorSubject<boolean>(false);

private mapDataResults: MapDataResults;

constructor(
private routerStoreService: RouterStoreService,
private visualizationService: VisualizationService
) { }

Expand All @@ -42,42 +46,34 @@ export class MapComponent implements OnChanges {

ngOnChanges(changes: SimpleChanges) {
if (this.readyToLoad) {
this.loadMapCenter();

if ( changes.corpus || changes.visualizedField || changes.queryModel ) {
this.queryModel.update.subscribe(this.loadData.bind(this));
this.loadData();
if (changes.corpus || changes.visualizedField || changes.queryModel) {
this.mapDataResults?.complete();

this.mapDataResults = new MapDataResults(
this.routerStoreService,
this.queryModel,
this.visualizationService
);

this.mapDataResults.result$.subscribe(data => {
this.results = data.geoDocuments;
this.mapCenter = data.mapCenter;
this.renderChart();
});


this.mapDataResults.error$.subscribe(error => this.emitError(error));
}
}
}


loadMapCenter() {
this.visualizationService.getGeoCentroid(this.visualizedField.name, this.corpus)
.then(centroid => {
this.mapCenter = centroid;
})
.catch(this.emitError.bind(this));
ngOnDestroy(): void {
this.mapDataResults?.complete();
}


loadData() {
showLoading(
this.isLoading$,
this.visualizationService
.getGeoData(
this.visualizedField.name,
this.queryModel,
this.corpus,
)
.then(geoData => {
this.results = geoData;
this.renderChart();
})
.catch(this.emitError.bind(this))
);
}

getVegaSpec(): VisualizationSpec {
// Returns a Vega map specification
// Uses pan/zoom signals from https://vega.github.io/vega/examples/zoomable-world-map/
Expand Down

0 comments on commit 7b8cfdd

Please sign in to comment.