Skip to content

Commit

Permalink
Render points on Vega map with pan/zoom
Browse files Browse the repository at this point in the history
  • Loading branch information
ar-jan committed Jul 10, 2024
1 parent e49773f commit 07a5576
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 7 deletions.
3 changes: 1 addition & 2 deletions frontend/src/app/visualization/map/map.component.html
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
<p>Map component</p>
<div>{{ results | json }}</div>
<div id="vega-map"></div>
164 changes: 159 additions & 5 deletions frontend/src/app/visualization/map/map.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
import { Component, ElementRef, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { View, ViewOptions, parse } from 'vega';

import { Corpus, CorpusField, GeoDocument, QueryModel } from '../../models';
import { VisualizationService } from '../../services';
Expand All @@ -25,7 +26,10 @@ export class MapComponent implements OnChanges {

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

constructor(private visualizationService: VisualizationService) { }
constructor(
private visualizationService: VisualizationService,
private el: ElementRef
) { }

get readyToLoad() {
return (
Expand All @@ -44,8 +48,6 @@ export class MapComponent implements OnChanges {
this.queryModel.update.subscribe(this.loadData.bind(this));
}
this.loadData();
} else {
// this.makeChart();
}
}

Expand All @@ -60,15 +62,167 @@ export class MapComponent implements OnChanges {
)
.then(geoData => {
this.results = geoData;
this.renderChart();
})
.catch(this.emitError.bind(this))
);
}

makeChart(geoData: GeoDocument[]) {
getVegaSpec(): object {
// Returns a Vega map specification
// Uses pan/zoom signals from https://vega.github.io/vega/examples/zoomable-world-map/
return {
"$schema": "https://vega.github.io/schema/vega/v5.json",
"description": "An interactive map supporting pan and zoom.",
"width": 600,
"height": 400,
"autosize": "none",

"signals": [
{ "name": "tx", "update": "width / 2" },
{ "name": "ty", "update": "height / 2" },
{
"name": "scale",
"value": 500,
"on": [{
"events": {"type": "wheel", "consume": true},
"update": "clamp(scale * pow(1.0005, -event.deltaY * pow(16, event.deltaMode)), 150, 3000)"
}]
},
{
"name": "angles",
"value": [0, 0],
"on": [{
"events": "pointerdown",
"update": "[rotateX, centerY]"
}]
},
{
"name": "cloned",
"value": null,
"on": [{
"events": "pointerdown",
"update": "copy('projection')"
}]
},
{
"name": "start",
"value": null,
"on": [{
"events": "pointerdown",
"update": "invert(cloned, xy())"
}]
},
{
"name": "drag", "value": null,
"on": [{
"events": "[pointerdown, window:pointerup] > window:pointermove",
"update": "invert(cloned, xy())"
}]
},
{
"name": "delta", "value": null,
"on": [{
"events": {"signal": "drag"},
"update": "[drag[0] - start[0], start[1] - drag[1]]"
}]
},
{
"name": "rotateX", "value": 0,
"on": [{
"events": {"signal": "delta"},
"update": "angles[0] + delta[0]"
}]
},
{
"name": "centerY", "value": 35,
"on": [{
"events": {"signal": "delta"},
"update": "clamp(angles[1] + delta[1], -60, 60)"
}]
}
],

"projections": [
{
"name": "projection",
"type": "mercator",
"scale": {"signal": "scale"},
"rotate": [{"signal": "rotateX"}, 0, 0],
"center": [15, {"signal": "centerY"}],
"translate": [{"signal": "tx"}, {"signal": "ty"}],
}
],

"data": [
{
"name": "world",
"url": "https://unpkg.com/world-atlas@2/land-50m.json",
"format": {"type": "topojson", "mesh": "land"}
},
{
"name": "places",
"format": {"type": "json"},
"values": this.results
}
],

"marks": [
{
"type": "shape",
"from": {"data": "world"},
"encode": {
"enter": {
"strokeWidth": {"value": 0.75},
"stroke": {"value": "#BDAE8A"},
"fill": {"value": "#E5D3B3"}
}
},
"transform": [
{"type": "geoshape", "projection": "projection"}
]
},
{
"type": "shape",
"from": { "data": "places" },
"encode": {
"enter": {
"width": 3,
"height": 3,
"fill": { "value": "red" },
},
},
"transform": [
{
"type": "geoshape",
"projection": "projection",
}
]
}
]
};
}


renderChart(): Promise<View> {
console.log('this.results length: ', this.results.length)
const spec = this.getVegaSpec();
const options: ViewOptions = {
renderer: 'canvas', // renderer (canvas or svg)
container: '#vega-map', // parent DOM container
hover: true // enable hover processing
};
const view = new View(
parse(spec),
options
);
const width = this.el.nativeElement['offsetWidth'] * 0.9;
const aspectRatio = spec['height'] / spec['width'] || 1;
view.width(width);
view.height(width * aspectRatio);
return view.runAsync();
}

emitError(error: { message: string }) {
this.mapError.emit(error?.message);
}
Expand Down

0 comments on commit 07a5576

Please sign in to comment.