So I got my MapBox going, and would like to dynamically add markers (using vue/nuxt). For some reason, the markers will not appear on the map, and I do not know why.
As you can see from the code below, console.log() prints the correct coordinates, so this really got me confused.
Any ideas?
Code:
<template>
<div id="map" class="h-screen w-screen">
</div>
</template>
<script>
import mapboxgl from "mapbox-gl";
const turbines = [
{
"TurbineName": "Unit 1",
"InstallationID": 100,
"PriceArea": "DK1",
"Latitude": 56.2000000000,
"Longitude": 8.6000000000,
"Value": 10.0
},
{
"TurbineName": "Unit 2",
"InstallationID": 101,
"PriceArea": "DK1",
"Latitude": 56.3000000000,
"Longitude": 8.6000000000,
"Value": -20.0
},
{
"TurbineName": "Unit 3",
"InstallationID": 102,
"PriceArea": "DK1",
"Latitude": 56.4000000000,
"Longitude": 8.6000000000,
"Value": -30.0
}]
export default {
name: 'MapBox',
mounted() {
this.createMap()
},
methods: {
createMap() {
mapboxgl.accessToken = 'pk.eyJ1IjoicmFzaiIsImEiOiJjbDQ2eTc4dXMwMDRrM2NwY2k4bnJpcXA3In0.gMSUw0D2RzfuJUxlKWKqAA';
const map = new mapboxgl.Map({
container: "map",
style: 'mapbox://styles/mapbox/streets-v11',
center: [10.676524188468528, 55.88490923940639],
zoom: 6.75
})
for (const turbine of turbines) {
// create a HTML element for each turbine
const el = document.createElement('div');
el.className = 'marker';
// make a marker for each turbine and add to the map
new mapboxgl.Marker(el)
.setLngLat([turbine.Longitude, turbine.Latitude])
.setPopup(
new mapboxgl.Popup({ offset: 25 }) // add popups
.setHTML(
`<h3>${turbine.TurbineName}</h3><p>${turbine.InstallationID}</p>`
)
)
.addTo(map);
console.log([turbine.Longitude, turbine.Latitude])
}
}
}
}
</script>
SOLUTION:
Needed to add:
<link href="https://api.mapbox.com/mapbox-gl-js/v2.8.2/mapbox-gl.css" rel="stylesheet">
In the top
Related
I am using vue-chartjs to create charts for my application. I am passing the chartData as a prop. My chart doesn't render at first but does when I resize the window. Here is my code. First the chart component:
<script>
import { Doughnut, mixins } from "vue-chartjs";
const { reactiveProp } = mixins;
export default {
extends: Doughnut,
mixins: [reactiveProp],
mounted() {
this.render();
},
methods: {
render() {
console.log(this.chartData)
let options = {
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
};
this.renderChart(this.chartData, options);
},
},
};
</script>
Now here is the code from the component where the chart is displayed:
template part
<v-container>
<ProjectDoughnutChart :chart-data="chartData" />
</v-container>
script part
components: {
ProjectDoughnutChart,
},
data() {
return {
chartData: {
labels: [],
datasets: [
{
backgroundColor: [],
hoverBackgroundColor: [],
data: [],
},
],
},
};
},
setChartsTimesheets() {
this.timesheets.forEach((timesheet) => {
let typeTotal = 0;
this.timesheets
.filter((timesheet1) => timesheet1.type==timesheet.type)
.forEach((timesheet1) => {
typeTotal+=timesheet1.billableAmount;
});
if (this.chartData.labels.indexOf(timesheet.type) === -1) {
let colors = this.getTaskColors(timesheet.type);
this.chartData.labels.push(timesheet.type);
this.chartData.datasets[0].data.push(typeTotal);
this.chartData.datasets[0].backgroundColor.push(colors.color);
this.chartData.datasets[0].hoverBackgroundColor.push(colors.hover);
}
});
},
Solved the problem using a similar solution as "Chart with API data" from the documentation.
TL;DR: Adding a v-if on the chart
For people, that have similar problem, but not using vue.js or the official solution doesnt cut it. I had to chart.update() the graph to show values, that were added after the graph was created.
See the example. If you comment the chart.update() line, the graph will not refresh until the window is resized.
let chart = new Chart(document.getElementById("chart"), {
type: "line",
data: {
labels: ["a", "b", "c", "d", "e", "f"],
datasets: [{
label: 'Dataset 1',
data: [1, 5, 12, 8, 2, 3],
borderColor: 'green',
}]
},
options: {
interaction: {
mode: 'index',
intersect: true,
},
stacked: false,
responsive: true,
}
});
// adding data to graph after it was created (like data from API or so...)
chart.data.labels.push("new data");
chart.data.datasets[0].data.push(9);
// with chart.update(), the changes are shown right away
// without chart.update(), you need to resize window first
chart.update();
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
<canvas id="chart"></canvas>
I am trying to use a method to fetch data from a json file and add it to my chart.js chart. I keep getting a "Maximum call stack size exceeded", this is specifically caused by the this.chartData.push(el.value); line, I've tried changing naming around to no success as well as using this.$data.chartData.
I am using vue3, chart.js v3 and j-t-mcc/vue3-chartjs
here is a codesandbox.io of the code with the error.
Child (chart) component
<template>
<div class="card card-body bg-dark">
<div class="col" id="chart">
<vue3-chart-js
ref="chartRef"
:id="sampleChart.id"
:type="sampleChart.type"
:data="sampleChart.data"
:options="sampleChart.options"
></vue3-chart-js>
</div>
</div>
</template>
<script>
import { ref } from 'vue'
import Vue3ChartJs from "#j-t-mcc/vue3-chartjs"
import 'chartjs-adapter-date-fns';
var chartOptions = {
maintainAspectRatio: true,
responsive: true,
animation: {
duration: 500
},
plugins: {
legend: {
display: false,
},
tooltip: {
yAlign: "bottom",
},
},
interaction: {
mode: "index",
intersect: false,
axis: "x",
},
scales: {
x: {
type: "time",
time: {
unit: "minute"
}
},
y: {
beginAtZero: true,
},
},
elements: {
point: {
pointRadius: 5.0,
},
},
layout: {
padding: {
top: 20,
left: 10,
right: 10,
bottom: 10,
},
},
}
export default {
name: "Chart",
components: {
Vue3ChartJs,
},
props: {
chartData: Array,
chartLabels: Array
},
setup(props) {
const chartRef = ref(null)
console.log("area chart data", props.chartData)
const chartDetails = {
labels: props.chartLabels,
fill: true,
datasets: [
{
label: "",
data: props.chartData,
borderColor: "rgb(24, 144, 255)",
tension: 0.1,
fill: true,
},
],
}
const sampleChart = {
id: "line",
type: "line",
data: chartDetails,
options: chartOptions,
}
return {
sampleChart,
chartRef
}
},
watch: {
chartLabels: {
deep: true,
handler() {
this.chartRef.update(250)
}
}
},
}
</script>
<style>
#chart {
position: relative;
margin: auto;
height: 100%;
width: 100%;
}
</style>
Parent component
<template>
<div>
<div class="container-fluid">
<SampleChart :chart-data="chartData" :chart-labels="chartLabels" />
</div>
</div>
</template>
<script>
import SampleChart from "./SampleChart.vue";
export default {
components: { SampleChart },
data() {
return {
chartData: [],
chartLabels: [],
};
},
async beforeMount() {
this.getTimelineData();
},
methods: {
getTimelineData: function () {
fetch("http://localhost:8080/sample.json")
.then((res) => res.json())
.then((data) => {
data.data.forEach((el) => {
this.chartData.push(el.value);
this.chartLabels.push(el.timestamp);
});
});
},
},
};
</script>
Package.json dependencies
"dependencies": {
"#j-t-mcc/vue3-chartjs": "^1.1.2",
"bootstrap": "^5.0.2",
"chart.js": "^3.3.2",
"chartjs-adapter-date-fns": "^2.0.0",
"core-js": "^3.6.5",
"date-fns": "^2.23.0",
"leaflet": "^1.7.1",
"vue": "^3.1.5"
}
The Error Message
Uncaught (in promise) RangeError: Maximum call stack size exceeded
at Object.get (reactivity.esm-bundler.js?a1e9:231)
at toRaw (reactivity.esm-bundler.js?a1e9:743)
at Proxy.instrumentations.<computed> (reactivity.esm-bundler.js?a1e9:223)
at Proxy.value (helpers.segment.js?dd3d:1531)
at Proxy.instrumentations.<computed> (reactivity.esm-bundler.js?a1e9:223)
at Proxy.value (helpers.segment.js?dd3d:1531)
at Proxy.instrumentations.<computed> (reactivity.esm-bundler.js?a1e9:223)
at Proxy.value (helpers.segment.js?dd3d:1531)
at Proxy.instrumentations.<computed> (reactivity.esm-bundler.js?a1e9:223)
at Proxy.value (helpers.segment.js?dd3d:1531)
Sample method without fetch that worked fine
getTestData: function () {
var labels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
var values = [10, 25, 39, 55, 90, 202, 304, 202, 105, 33, 44, 95, 20, 39, 90];
labels.forEach((el) => {
this.chartLabels.push(el);
});
values.forEach((el) => {
this.chartData.push(el);
});
},
Json data sample
{
"data": [
{
"timestamp": 1627382793000,
"value": 121
},
{
"timestamp": 1627383698000,
"value": 203
},
{
"timestamp": 1627387917000,
"value": 15
}
]
}
it's work when adding a simple v-if with a ready property that we turn it true when we finish the foreach of pushing data,
the problem is with your SampleChart.vue componenent , you make chart data inside the setup , so when data changed sampleChart will not be changed in any case , it's already calculated.
you can learn more about computed, ref/reactive
While Hossem's answer will work for the first render, the chart still wont be updated when you add new data.
Oddly enough, downgrading Vue one version from 3.1.5 to 3.1.4 ended up resolving the issue.
I want to use the response coming from the API in v-select. Here is a scenario i want to use the API call from component A to component B, rather than calling it again in the component B.
Component A:
methods: {
forVselect (id) {
this.$http.get(`/type/${id}`)
.then((res) => {
this.icecream = res.data})
.catch((e) => {console.log(e)})
}
},
mounted (){
this.forVselect (this.$route.params.un_id)
}
Component B:
<template>
<div class="The V-select">
<vselect v-model="input1" :options="[{label: 'Vanilla' , value: 'vanilla'}]" :dir="$vs.rtl ? 'rtl' : 'ltr'" />
</div>
</template>
<script>
import vselect from 'vue-select'
...
input1: null,
icecream: null
...
methods: {
forVselect (id) {
this.$http.get(`/type/${id}`)
.then((res) => {
this.icecream = res.data})
.catch((e) => {console.log(e)})
}
},
mounted (){
this.forVselect (this.$route.params.un_id)
}
</script>
As you can see my Component B i have hard coded as 'vanilla' in v-select, rather i want to use the data coming from the API, i want to know how it can be done.
Here is my Api response:
[
{
"id": 3,
"flavor": "vanilla",
"price": "150",
"taste": "super",
"cream": "high",
"investments": null,
},
{
"id": 8,
"flavor": "chocolate",
"price": "250",
"taste": "super high",
"cream": "fantastic",
"investments": "too high",
}
...
]
Please do help me. I tried just by using label: type.flavor but nothing was displayed. And in order to make code effective i want to use the response coming from API call made in component A
use just have to add a variable at the place of option as shown below:
<template>
<div class="The V-select">
<vselect v-model="input1" :options="icecream" :dir="$vs.rtl ? 'rtl' : 'ltr'" />
</div>
</template>
<script>
import vselect from 'vue-select'
...
input1: null,
icecream: null
...
methods: {
forVselect (id) {
this.$http.get(`/type/${id}`)
.then((res) => {
this.icecream = res.data})
.catch((e) => {console.log(e)})
}
},
mounted (){
this.forVselect (this.$route.params.un_id)
}
</script>
and also you need modify your api response... response like:
[
{
"id": 3,
"flavor": "vanilla",
"price": "150",
"taste": "super",
"cream": "high",
"investments": null,
"label": "Vanilla" ,
"value": "vanilla"
},
{
"id": 8,
"flavor": "chocolate",
"price": "250",
"taste": "super high",
"cream": "fantastic",
"investments": "too high",
"label": "Chocolate" ,
"value": "chocolate"
}
...
]
you need to modify response like that from server side or client side when response received...
If you don't want to modify your json response so atleat you need to add 2 additional key which is label & value key so that you can use...
I tried to use :getOptionKey="getOptionKey" so I could change the default "id" vue-select was requesting but to me the only think that worked is to consider the object attribute "value" as the default.
So since I was working with array of objects being returned from API, what I did was:
// loading from API
dataUtils.find(this.$route.params.id).then((data) => {
this.mySelectObject = {
name: data.name,
value: data.id
}
and used the following within html:
<v-select
label="name"
:options="myOptions"
v-model="mySelectObject"
#input="setSelected" //created this method to change selection
/>
I'm using Vue FullCalendar 5.3.1. I want to add event on doubleclick on empty date cell and edit event on doubleclick on event. How can I implement this? There are 2 methods by default: dateClick() and eventClick() and it's works fine for me.
My code:
<template>
<div>
<heading class="mb-6">Scheduler</heading>
<card class="custom-card">
<FullCalendar :options="calendarOptions"/>
</card>
</div>
</template>
<script>
import FullCalendar from '#fullcalendar/vue'
import dayGridPlugin from '#fullcalendar/daygrid'
import interactionPlugin from '#fullcalendar/interaction'
import resourceTimelineDay from '#fullcalendar/resource-timeline'
export default {
components: {
FullCalendar // make the <FullCalendar> tag available
},
data() {
return {
calendarOptions: {
dateClick: function(info) {
console.log(info.dateStr)
console.log(info.resource.id)
},
eventClick: function(info) {
console.log(info)
},
height: 250,
plugins: [ dayGridPlugin, interactionPlugin, resourceTimelineDay ],
headerToolbar: {
left: 'today prev,next',
center: 'title',
right: 'resourceTimelineDay,resourceTimelineWeek'
},
initialView: 'resourceTimelineDay',
aspectRatio: 1.5,
editable: true,
resourceAreaColumns: [
{
field: 'title',
headerContent: 'Worker'
}
],
resources: [
{
"id": "worker_a",
"title": "Worker A"
}, {
"id": "worker_b",
"title": "Worker B",
"eventColor": "green"
}, {
"id": "worker_c",
"title": "Worker C",
"eventColor": "orange"
}
],
events: [{
"resourceId": "worker_a",
"title": "Job 5",
"start": "2020-09-15T10:00:00+00:00",
"end": "2020-09-15T15:00:00+00:00"
}, {
"resourceId": "worker_b",
"title": "Job 2",
"start": "2020-09-15T09:00:00+00:00",
"end": "2020-09-15T14:00:00+00:00"
}, {
"resourceId": "worker_b",
"title": "Job 4",
"start": "2020-09-15T15:30:00+00:00",
"end": "2020-09-15T17:30:00+00:00"
},
]
}
}
}
}
</script>
BTW as I noticed that now all calendar settings are passed through :options = "". If you want to pass events like so <FullCalendar :events="events"/> or handle an event like <FullCalendar #dateClick="dateClick"/>, you cannot do this. Everything needs to be passed in the calendarOptions object (documentation)
The fullcalendar doesn't provide this option.
But you can attach the double click handler, when the event object is contructed by using an Event Render Hooks
In the version 5 we can use the funcion eventDidMount
data() {
return {
calendarOptions: {
...
eventDidMount: function(eventInfo){
// Not mandatory, but you can set an id to the object
eventInfo.el.id = eventInfo.event.id;
// Set the dbclick event
eventInfo.el.ondblclick = function(){
console.log(eventInfo.event)
};
}
}
}
}
Note: This works, only because this function is called only one time, in case that you are working in other version check how many times the function is called.
I've implemented autocomplete for my address field, but the json returned from the Google Maps Places Autocomplete doesn't include the geocoded coords for the places.
There are some answers out there that don't seem to fit. For instance, this one refers to things like google.maps.places.Autocomplete(input, options); which I don't think is a thing in React Native.
Other answers appear to be based on react-native-google-places-autocomplete, but I've implemented this myself and I'd love to not do it again using that module.
Here's my method where I call the API.
async handleAddressChange() {
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GoogleAPIKey}&input=${this.state.address}`;
try {
const result = await fetch(url);
const json = await result.json();
this.setState({ addressPredictions: json.predictions });
} catch (err) {
console.error(err);
}
}
setAddress(prediction) {
this.setState({ address: prediction.description, showPredictions: false });
}
The API response doesn't have any place or geometry property on it:
Object {
"description": "1234 Some Avenue Northeast, Washington, DC, USA",
"id": "4c79fba1b3a5ad33478b79b54896a75a4d56ca53",
"matched_substrings": Array [
Object {
"length": 4,
"offset": 0,
},
],
"place_id": "ChIJneQ1fBO5t4kRf8mTw4ieb4Q",
"reference": "ChIJneQ1fBO5t4kRf8mTw4ieb4Q",
"structured_formatting": Object {
"main_text": "1234 Some Avenue Northeast",
"main_text_matched_substrings": Array [
Object {
"length": 4,
"offset": 0,
},
],
"secondary_text": "Washington, DC, USA",
},
"terms": Array [
Object {
"offset": 0,
"value": "1600",
},
Object {
"offset": 5,
"value": "Maryland Avenue Northeast",
},
Object {
"offset": 32,
"value": "Washington",
},
Object {
"offset": 44,
"value": "DC",
},
Object {
"offset": 48,
"value": "USA",
},
],
"types": Array [
"street_address",
"geocode",
],
}
I have found a solution for the same-
Just use like this-
<GooglePlacesAutocomplete
GooglePlacesDetailsQuery={{ fields: "geometry" }}
fetchDetails={true} // you need this to fetch the details object onPress
placeholder="Search"
query={{
key: "API_KEY_GOES_HERE",
language: "en", // language of the results
}}
onPress={(data: any, details: any = null) => {
console.log("data", data);
console.log("details", details);
console.log(JSON.stringify(details?.geometry?.location));
}}
onFail={(error) => console.error(error)} />
Once you have the place id (ChIJneQ1fBO5t4kRf8mTw4ieb4Q for the example in your question), you can do a place details request.
Make sure you include the Places library in your API call: https://maps.googleapis.com/maps/api/js?libraries=places and a valid API key.
function initialize() {
var map = new google.maps.Map(document.getElementById('map-canvas'), {
center: new google.maps.LatLng(0, 0),
zoom: 15
});
var service = new google.maps.places.PlacesService(map);
service.getDetails({
placeId: 'ChIJneQ1fBO5t4kRf8mTw4ieb4Q'
}, function(place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
// Create marker
var marker = new google.maps.Marker({
map: map,
position: place.geometry.location
});
// Center map on place location
map.setCenter(place.geometry.location);
}
});
}
initialize();
#map-canvas {
height: 160px;
}
<div id="map-canvas"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=places&callback=initialize">
</script>