Vue computed property not updating object literal - vuejs2

I'm building a GeoJSON editor which displays a preview in vue2-google-maps. One of the challenges is converting the GeoJSON's coordinates into LatLngLiteral's to be used as props in <GmapMarker>, <GmapPolygon>, or <GmapPolyline>. My solution is to use computed properties. So when the GeoJSON's changed, the map objects get changed too. Like so:
computed: {
mapObject() {
// If the geometry is a point, just assign geometry.coordinates into `lat` and `lng`
if (this.$data.geometry.type === "Point") return ({
lat: this.$data.geometry.coordinates[1],
lng: this.$data.geometry.coordinates[0],
});
// If it's a polygon, grab the first set of coordinates (we aren't supporting polygons with holes for now).
// then splice off the last element (Google Maps' polygon automatically loop the shape, unlike GeoJSON's convention),
// then we map the values into an array of LatLngLiterals.
if (this.$data.geometry.type === "Polygon") return this.$data.geometry.coordinates[0].splice(-1, 1).map(e => ({lat: e[1], lng: e[0]}));
// If it's a LineString, do as above, without the splicing.
if (this.$data.geometry.type === "LineString") return this.$data.geometry.coordinates.map(e => ({lat: e[1], lng: e[0]}));
}
},
Problem is, this won't update for Point types. This seems to be working fine for Polygon and LineString types, though. Any help would be appreciated.

Related

Is it possible to get the data table column names in Vega-lite as an array?

Is there a Vega/Vega-lite equivalent of pandas' df.columns?
I would like to get an array with all column names in Vega-lite. For this dataset:
{"data": { "values" : [{"a":4, "b":5},{"a":6, "b":7}] }}
I would like to get an array ["a","b"]. This would be extremely helpful when using the fold transform with large datasets.
Here is one way to get a Vega dataset fieldnames using Vega API.
In Vega spec, add signal declaration, e.g.:
"signals":[
{"name": "signal_data_table_fieldnames"
}
In javacript, use Vega view API methods signal and data to retrieve the first record of the dataset table and obtain the fieldnames as an array with javascript Object.keys()
When using the view.signal, the Vega API method can both get and set the internal Vega signal value. The updated signal value can be used anywhere within the Vega spec.
Vega API docs for signal and data methods:
https://vega.github.io/vega/docs/signals/
https://vega.github.io/vega/docs/api/view/#signals
https://vega.github.io/vega/docs/api/view/#data-and-scales
Example code using
https://vega.github.io/vega/usage/
with bar chart example
https://vega.github.io/vega/examples/bar-chart/ :
var view;
fetch('https://vega.github.io/vega/examples/bar-chart.vg.json')
.then(res => res.json())
.then(spec => render(spec))
.catch(err => console.error(err));
function render(spec) {
view = new vega.View(vega.parse(spec), {
renderer: 'canvas', // renderer (canvas or svg)
container: '#view', // parent DOM container
hover: true // enable hover processing
});
return view.runAsync();
}
// Vega API
view.signal("signal_data_table_fieldnames", Object.keys(view.data("table")[0]));
console.log(view.signal("signal_data_table_fieldnames"));

How to limit search results to an extent with Esri Javascript map?

I believe by default the 4.13 Esri Javascript map will search addresses by the current map view extent. The issue is that if the user zooms in or out too far, the search results return addresses VERY far away. Here is my code:
function initESRIMap() {
require(["esri/Map", "esri/views/MapView", "esri/widgets/Search", "esri/layers/FeatureLayer", "esri/widgets/Popup", "esri/geometry/Extent", "esri/geometry/Geometry"], function (
Map,
MapView,
Search,
FeatureLayer,
Popup,
Extent,
Geometry
) {
var esriMap = new Map({
basemap: "streets",
});
var esriView = new MapView({
container: "map-div",
map: esriMap,
center: LatLong,
zoom: 11,
});
var search = new Search({
view: esriView,
});}
I want to be able to get the same search results REGARDLESS of my map view location. BUT limit results to a specific area. Therefore if I'm viewing another country I'll still see search results from my extent.
A search widget can be customized to use custom sources, and a custom source can be customized to use a filter, which can include a map extent.
For example, let's define a custom SearchSource - we'll just use the same search endpoint as the standard ArcGIS World Geocoder service, but we'll add a filter:
const source = {
locator: new Locator({
url: "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer"
}),
filter: {
geometry: new Extent({
xmax: -12921954.804910611,
xmin: -13126806.04071496,
ymax: 3909898.0736186495,
ymin: 3801204.619397087,
spatialReference: {
wkid: 102100
}
})
},
maxSuggestions: 10
};
Now when we're defining our search widget, we'll include this in the source property array, and we'll turn off includeDefaultSources so that the original, unbounded source, is not used:
const search = new Search({
view,
includeDefaultSources: false,
sources: [source]
});
Working Codesandbox
In this example, I set the extent in the filter to be around the area of san diego / tijuana. Wherever you pan or zoom on the map, you'll still only get results from that area. For example, move the map over to seattle, or china, or whatever, and search for something generic ("park", "road", whatever), and you'll see that no matter what the map's extent is, the search results will be limited to what you put in the search source's filter.
Note this answer was written with 4.18.

How to access or get value of specific graph on chart plot by click event?

I use vue-chartjs to draw some chart like line, bar, etc.
In my project, there are many cases using specific value or lable of data in chart.
Using tooltip option of vue-chartjs, I can check that value or label of data item when hovered.
I want to know how to access or get information of specific data matched with point on graph when clicked(not hovered).
Here is my code about chart options.
chartOptions: {
responsive: false,
onClick: function(evt){
//Some logic to get value of label of specific data...(type, label, value, ...)
}
In my case, I use 'onclick' option to access specific data on point triggered 'click' event. In 'onClick' callback, I checked all of chart elements and dataset, etc.
How can I get value of label specific dataItem on point of graph(like line) or bar of graph(like bar) when triggered click event?
I was not able to find a solution that worked for me, but I dug a little bit and this is what I came up with.
onClick: function(evt, array) {
if (array.length != 0) {
var position = array[0]._index;
var activeElement = this.tooltip._data.datasets[0].data[position]
console.log(activeElement);
} else {
console.log("You selected the background!");
}
}
This will get the position in the array that you clicked and grab the data from what position you clicked. This may not be the prettiest or best example, but it worked for me.
This solution use the getElementAtEvent method of chartjs, but to use that you need reference to the Chart itself, not the Vue component. We can get that from the $data._chart property. To use this in a parent Vue component, we use the $refs as seen below`.
So parent defines the chart options
{
...
options: {
onClick: this.handleChartClick
}
...
}
and then parent method, using $refs with $data._chart to get the chart. We get the datasetIndex and value and also the tooltip
handleChartClick(evt, elements) {
var chart = this.$refs.periodChart.$data._chart;
const chartIndex = chart.getElementAtEvent(evt);
if (chartIndex.length !== 0) {
const datasetIndex = chartIndex[0]._datasetIndex;
const position = chartIndex[0]._index;
const info = {
datasetIndex: datasetIndex,
valueIndex: position,
label: chart.tooltip._data.labels[position],
value: chart.tooltip._data.datasets[datasetIndex].data[position]
};
console.log(info);
} else {
console.log("Background clicked");
}

Vue.js - Highmaps - Redraw map on series change

I have a highmaps 'chart' and the only thing that I want is to redraw the whole map inside an external function. Let me explain better. The map draws itself immediatly when the page loads up but I fetch some data from an external service and set it to a variable. Then I would like to just redraw the chart so that the new data appears in the map itself. Below is my code.
<template>
<div>
<highmaps :options="chartOptions"></highmaps>
</div>
</template>
<script>
import axios from 'axios';
import HighCharts from 'vue-highcharts';
import json from '../map.json'
let regions = [];
export default {
data: function () {
return {
chartOptions: {
chart: {
map: json, // The map data is taken from the .json file imported above
},
map: {
/* hc-a2 is the specific code used, you can find all codes in the map.json file */
joinBy: ['hc-key', 'code'],
allAreas: false,
tooltip: {
headerFormat: '',
pointFormat: '{point.name}: <b>{series.name}</b>'
},
series: [
{
borderColor: '#a0451c',
cursor: 'pointer',
name: 'ERROR',
color: "red",
data: regions.map(function (code) {
return {code: code};
}),
}
],
}
},
created: function(){
let app = this;
/* Ajax call to get all parameters from database */
axios.get('http://localhost:8080/devices')
.then(function (response) {
region.push(response.parameter)
/* I would like to redraw the chart right here */
}).catch(function (error){
console.error("Download Devices ERROR: " + error);
})
}
}
</script>
As you can see I import my map and the regions variable is set to an empty array. Doing this results in the map having only the borders and no region is colored in red. After that there is the created:function() function that is used to make the ajax call and retrieve data. After that I just save the data pushing it into the array and then obviously nothing happens but I would like to redraw the map so that the newly imported data will be shown. Down here is the image of what I would like to create.
If you have any idea on how to implement a thing like this or just want to suggest a better way of handling the problem, please comment.
Thanks in advance for the help. Cheers!
After a few days without any answer I found some marginal help online and came to a pretty satisfying conclusion on this problem so I hope it can help someone else.
So the first thing I did was to understand how created and mounted were different in Vue.js. I used the keyword created at first when working on this project. Because of that, inside this function, I placed my ajax call that gave me data which I then loaded inside the 'chart' by using the .addSeries method of the chart itself.
To reference the chart itself I used this: let chart: this.$refs.highcharts.chart. This searches for the field refs in any of your components/html elements and links it to the variable. So in the html there was something like this:
<template>
<div>
<highmaps :options="chartOptions" ref="highcharts"></highmaps>
</div>
</template>
The real problem was that the chart didn't even start rendering while all this process was going on so I changed the created keyword with mounted which means that it executes all the code when all of the components are correctly mounted and so my chart would be already rendered.
To give you (maybe) a better idea of what I am talking about I will post some code down below
mounted: function(){
let errorRegions = [];
let chart = this.$refs.highcharts.chart;
axios.get('localhost:8080/foo').then(function(response)
{
/* Code to work on data */
response.forEach(function(device){
errorRegions.push(device);
}
chart.addSeries({
name: "ERROR",
color: "red",
data: errorRegions
}
/* ...Some more code... */
})
}
And this is the result (have been adding some more series in the same exact manner)
Really hoping I have been of help to someone else. Cheers!

Panning the map to certain extent javascript API

I want to limit map extent to the initial extent of the map and limit user from panning more than certain extent.
I tried following but nothing has changed:
map = new Map( "map" , {
basemap: "gray",
center: [-85.416, 49.000],
zoom : 6,
logo: false,
sliderStyle: "small"
});
dojo.connect(map, "onExtentChange", function (){
var initExtent = map.extent;
var extent = map.extent.getCenter();
if(initExtent.contains(extent)){}
else{map.setExtent(initExtent)}
});
Just to flesh out Simon's answer somewhat, and give an example. Ideally you need two variables at the same scope as map:
initExtent to store the boundary of your valid extent, and
validExtent to store the last valid extent found while panning, so that you can bounce back to it.
I've used the newer dojo.on event syntax as well for this example, it's probably a good idea to move to this as per the documentation's recommendation - I assume ESRI will discontinue the older style at some point.
var map;
var validExtent;
var initExtent;
[...]
require(['dojo/on'], function(on) {
on(map, 'pan', function(evt) {
if ( !initExtent.contains(evt.extent) ) {
console.log('Outside bounds!');
} else {
console.log('Updated extent');
validExtent = evt.extent;
}
});
on(map, 'pan-end', function(evt) {
if ( !initExtent.contains(evt.extent) ) {
map.setExtent(validExtent);
}
});
});
You can do the same with the zoom events, or use extent-change if you want to trap everything. Up to you.
It looks like your extent changed function is setting the initial extent variable to the maps current extent and then checking if that extent contains the current extents centre point - which of course it always will.
Instead, declare initExtent at the same scope of the map variable. Then, change the on load event to set this global scope variable rather than a local variable. In the extent changed function, don't update the value of initExtent, simply check the initExtent contains the entire of the current extent.
Alternatively you could compare each bound of the current extent to each bound of the initExtent, e.g. is initExtent.xmin < map.extent.xmin and if any are, create a new extent setting any exceeded bounds to the initExtent values.
The only problem is these techniques will allow the initExtent to be exceeded briefly, but will then snap the extent back once the extent changed function fires and catches up.
I originally posted this solution on gis.stackexchange in answer to this question: https://gis.stackexchange.com/a/199366
Here's a code sample from that post:
//This function limits the extent of the map to prevent users from scrolling
//far away from the initial extent.
function limitMapExtent(map) {
var initialExtent = map.extent;
map.on('extent-change', function(event) {
//If the map has moved to the point where it's center is
//outside the initial boundaries, then move it back to the
//edge where it moved out
var currentCenter = map.extent.getCenter();
if (!initialExtent.contains(currentCenter) &&
event.delta.x !== 0 && event.delta.y !== 0) {
var newCenter = map.extent.getCenter();
//check each side of the initial extent and if the
//current center is outside that extent,
//set the new center to be on the edge that it went out on
if (currentCenter.x < initialExtent.xmin) {
newCenter.x = initialExtent.xmin;
}
if (currentCenter.x > initialExtent.xmax) {
newCenter.x = initialExtent.xmax;
}
if (currentCenter.y < initialExtent.ymin) {
newCenter.y = initialExtent.ymin;
}
if (currentCenter.y > initialExtent.ymax) {
newCenter.y = initialExtent.ymax;
}
map.centerAt(newCenter);
}
});
}
And here's a working jsFiddle example: http://jsfiddle.net/sirhcybe/aL1p24xy/