d3.js | projection.rotate() return value [NaN, 0, 0] - vue.js

I want to rotate the globe canvas using projection.rotate() but it return value [NaN, 0, 0].
<template lang="html">
<div class="map" style="width:100vw;height:100vh">
<canvas id="globe"></canvas>
</div>
</template>
<script lang="js">
import * as d3 from "d3";
import * as topojson from "topojson-client";
export default {
name: 'vue',
data() {
return {
angles: { x: -20, y: 40, z: 0 },
canvas: null,
context: null,
countries:null,
countryList:null,
autorotate:null,
lastTime : d3.now(),
now:null,
diff:null,
roation:null,
scaleFactor: 0.6,
degPerSec : 6,
degPerMs: this.degPerSec / 1000,
width:null,
height:null,
water : {type: 'Sphere'},
colorWater : '#0099cf',
colorLand : '#77fc2f',
colorGraticule : '#0099cf',
colorCountry : '#77fc2f',
land:null,
currentCountry:null,
graticule: d3.geoGraticule10(),
projection: d3.geoOrthographic().precision(0.1),
path : null,
}
},
mounted() {
this.canvas = document.getElementById('globe');
this.context = this.canvas.getContext('2d');
this.path = d3.geoPath(this.projection).context(this.context);
this.loadData((world, cList)=> {
this.land = topojson.feature(world, world.objects.land);
this.countries = topojson.feature(world, world.objects.countries);
this.countryList = cList;
window.addEventListener('resize', this.scale);
this.scale();
this.autorotate = d3.timer(this.rotate);
});
},
methods: {
loadData(cb) {
d3.json('https://unpkg.com/world-atlas#1/world/110m.json').then(function(world) {
d3.tsv('https://gist.githubusercontent.com/mbostock/4090846/raw/07e73f3c2d21558489604a0bc434b3a5cf41a867/world-country-names.tsv').then( function(countries) {
cb(world, countries)
})
})
},
rotate(elapsed) {
this.now = d3.now();
this.diff = this.now - this.lastTime;
if (elapsed < 300) { //this.diff !== elapsed
let rotation = this.projection.rotate();
console.log(rotation);
rotation[0] += this.diff * this.degPerMs;
this.projection.rotate(rotation);
this.render();
}
this.lastTime = this.now;
},
render() {
this.context.clearRect(0, 0, this.width, this.height);
this.fill(this.water, this.colorWater)
this.stroke(this.graticule, this.colorGraticule)
this.fill(this.land, this.colorLand)
if (this.currentCountry) {
this.fill(this.currentCountry, this.colorCountry)
}
},
fill(obj, color) {
this.context.beginPath()
this.path(obj)
this.context.fillStyle = color
this.context.fill()
},
stroke(obj, color) {
this.context.beginPath()
this.path(obj)
this.context.strokeStyle = color
this.context.stroke()
},
scale() {
this.width = document.querySelector(".map").clientWidth;
this.height = document.querySelector(".map").clientHeight;
this.canvas.setAttribute('width', this.width);
this.canvas.setAttribute('height', this.height);
this.projection
.scale((this.scaleFactor * Math.min(this.width, this.height)) / 2)
.translate([this.width / 2, this.height / 2])
this.render();
}
},
}
</script>
you can check my issue here: https://codesandbox.io/s/globe-issus-q2s1s
example rotate demo: https://codepen.io/Share2U/pen/dyWQoGv
Thank you for your help.

The issue lies in the following line:
degPerMs: this.degPerSec / 1000,
this.degPerSec is not available because you can't access other data properties inside data. Therefore rotation[0] += this.diff * this.degPerMs; is returning NaN.
To fix this you can set a variable before exporting the module.
E.g.:
<script lang="js">
import * as d3 from "d3";
import * as topojson from "topojson-client";
const DEG_PER_SEC = 6;
export default {
name: 'vue',
data() {
return {
...
degPerMs: DEG_PER_SEC / 1000,
...
}
}
}
</script>

Related

Infinite draggable webgl slider

I'm new to webgl and I'm trying to imrpove this infinite draggable webgl slider:
https://codepen.io/ReGGae/pen/povjKxV
setup() {
const { ww } = store
const state = this.state
const { items, titles } = this.ui
const {
width: wrapWidth,
left: wrapDiff
} = this.el.getBoundingClientRect()
// Set bounding
state.max = -(items[items.length - 1].getBoundingClientRect().right - wrapWidth - wrapDiff)
state.min = 0
// Global timeline
this.tl = gsap.timeline({
paused: true,
defaults: {
duration: 1,
ease: 'linear'
}
})
.fromTo('.js-progress-line-2', {
scaleX: 1
}, {
scaleX: 0,
duration: 0.5,
ease: 'power3'
}, 0)
.fromTo('.js-titles', {
yPercent: 0
}, {
yPercent: -(100 - (100 / titles.length)),
}, 0)
.fromTo('.js-progress-line', {
scaleX: 0
}, {
scaleX: 1
}, 0)
// Cache stuff
for (let i = 0; i < items.length; i++) {
const el = items[i]
const { left, right, width } = el.getBoundingClientRect()
// Create webgl plane
const plane = new Plane()
plane.init(el)
// Timeline that plays when visible
const tl = gsap.timeline({ paused: true })
.fromTo(plane.mat.uniforms.uScale, {
value: 0.65
}, {
value: 1,
duration: 1,
ease: 'linear'
})
// Push to cache
this.items.push({
el, plane,
left, right, width,
min: left < ww ? (ww * 0.775) : -(ww * 0.225 - wrapWidth * 0.2),
max: left > ww ? state.max - (ww * 0.775) : state.max + (ww * 0.225 - wrapWidth * 0.2),
tl,
out: false
})
}
}
What I would like to be able to do is to perfectly loop the words with the slides. Now when dragging it fast the words don't match the pictures (currently there is one more word than the images as workaround).
Plus I would like to find a way to place links on the draggable images.
I would be very grateful for any advice.

VueJS 3 composition API lifecycle not taking onMounted into account

I'm creating a greeting card app with VueJS3. I want to download 2 images (the background and a star sprite), then showing the image with animated sprites.
If I put onMounted"at the end it doesn't work, I have this error:
[Vue warn]: onMounted is called when there is no active component instance to be associated with. Lifecycle injection APIs can only be used during execution of setup(). If you are using async setup(), make sure to register lifecycle hooks before the first await statement.
at <RouterView>
at <App>
If I put onMounted like this, it doesn't show the MOUNTED log...
<template>
<div class="board">
<canvas
id='carte'
ref='carte'
:width="size.w"
:height="size.h"
max-width="100%"
tabindex='0'
style="border:1px solid #000000;"
></canvas>
</div>
<div id="texteRemplacement" v-if="petit">
<p v-for="p in texte.split('\n')" v-bind:key="p">
{{ p }}
</p>
</div>
</template>
<script>
import {
defineComponent,
onMounted,
ref,
reactive,
nextTick,
toRefs,
watch,
} from 'vue';
import axios from 'axios';
export default defineComponent({
name: 'Visionneuse',
props: ['texteEnvoye', 'taille'],
async setup(props) {
const myCanvas = ref(null);
const carte = ref(null);
const { texteEnvoye, taille } = toRefs(props);
const texte = ref('');
const rapport = ref(0);
const petit = ref((window.innerWidth < 750));
const size = reactive({
w: window.innerWidth * taille.value,
h: window.innerHeight * taille.value,
});
const tailleFond = reactive({
w: 0,
h: 0,
});
let fpsInterval;
let startTime;
let now;
let then;
let elapsed;
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
function genererEtoile() {
return ({
taille: getRandomInt(10) + 10,
position: {
x: Math.random(),
y: Math.random(),
},
deviation: {
x: {
valeur: getRandomInt(18) - 9,
sens: 1,
amplitude: 5 + getRandomInt(5),
},
y: {
valeur: getRandomInt(8) - 4,
sens: 1,
amplitude: 5 + getRandomInt(5),
},
},
grossissement: {
valeur: getRandomInt(5) - 3,
sens: 1,
},
});
}
const etoiles = ref(new Array(50).fill(null).map(() => (genererEtoile())));
const date = ref([
{
valeur: {
chiffre: '2',
position: 0,
sens: 1,
},
},
{
valeur: {
chiffre: '0',
position: 3,
sens: -1,
},
},
{
valeur: {
chiffre: '2',
position: -3,
sens: 1,
},
},
{
valeur: {
chiffre: '1',
position: -3,
sens: -1,
},
},
]);
function drawText() {
const fontSize = 0.05 * size.w - 10;
myCanvas.value.font = `${fontSize}px Caviar Dream`;
myCanvas.value.textAlign = 'center';
myCanvas.value.fillStyle = 'white';
myCanvas.value.strokeStyle = 'black';
myCanvas.value.lineWidth = 0.006 * size.w - 10;
const x = (size.w * 9) / 13;
const y = (size.h * 5) / 7;
const lineHeight = fontSize;
const lines = texte.value.split('\n');
for (let i = 0; i < lines.length; i += 1) {
myCanvas.value.fillText(
lines[lines.length - i - 1],
x,
y + ((lines.length - 1) * lineHeight) / 2 - (i * lineHeight),
);
// myCanvas.value.strokeText(
// lines[lines.length - i - 1],
// x,
// y + ((lines.length - 1) * lineHeight) / 2 - (i * lineHeight),
// );
}
}
watch(texteEnvoye, (x) => {
texte.value = x;
// requestAnimationFrame(initCarte);
});
onMounted(() => {
console.log('MOUNTED');
const c = carte.value;
const ctx = c.getContext('2d');
myCanvas.value = ctx;
// requestAnimationFrame(initCarte);
// eslint-disable-next-line
startAnimating(20);
});
async function getImage(chemin) {
await axios.get(`${process.env.VUE_APP_PUBLICPATH}img/${chemin}`, { responseType: 'arraybuffer' })
.then((res) => {
console.log(`data:${res.headers['content-type']};base64,${btoa(String.fromCharCode(...new Uint8Array(res.data)))}`);
return `data:${res.headers['content-type']};base64,${btoa(String.fromCharCode(...new Uint8Array(res.data)))}`;
})
.catch((e) => {
console.log(e);
});
}
const imgFond = await getImage('fond.jpeg');
const imgEtoile = await getImage('etoile.png');
function initCarte() {
console.log('init de la carte');
// changer les chargement
// https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations
requestAnimationFrame(initCarte);
now = Date.now();
elapsed = now - then;
if (elapsed > fpsInterval) {
then = now - (elapsed % fpsInterval);
petit.value = window.innerWidth < 750;
const etoile = new Image();
// etoile.src = `${process.env.VUE_APP_PUBLICPATH}img/etoile.png`;
etoile.src = imgEtoile;
const background = new Image();
// background.src = `${process.env.VUE_APP_PUBLICPATH}img/fond.jpeg`;
background.src = imgFond;
background.onload = () => {
rapport.value = background.naturalWidth / background.naturalHeight;
tailleFond.w = background.naturalWidth;
tailleFond.h = background.naturalHeight;
size.w = window.innerWidth * taille.value;
size.h = size.w / rapport.value;
if (size.w > window.innerWidth) {
size.h = size.w / rapport.value;
} else if (size.h > window.innerHeight) {
size.h = window.innerHeight * taille.value;
size.w = size.h * rapport.value;
}
nextTick(() => {
try {
myCanvas.value.drawImage(background, 0, 0, size.w, size.h);
// on dessine les étoiles
etoiles.value.forEach((e) => {
if (e.grossissement.valeur > 2 && e.grossissement.sens === 1) {
e.grossissement.sens = -1;
}
if (e.grossissement.valeur < -3 && e.grossissement.sens === -1) {
e.grossissement.sens = 1;
}
if (e.deviation.x.valeur > e.deviation.x.amplitude && e.deviation.x.sens === 1) {
e.deviation.x.sens = -1;
}
if (e.deviation.x.valeur < -e.deviation.x.amplitude && e.deviation.x.sens === -1) {
e.deviation.x.sens = 1;
}
if (e.deviation.y.valeur > e.deviation.y.amplitude && e.deviation.y.sens === 1) {
e.deviation.y.sens = -1;
}
if (e.deviation.y.valeur < -e.deviation.y.amplitude && e.deviation.y.sens === -1) {
e.deviation.y.sens = 1;
}
e.deviation.x.valeur += e.deviation.x.sens;
e.deviation.y.valeur += e.deviation.y.sens;
e.grossissement.valeur += e.grossissement.sens;
myCanvas.value.drawImage(etoile,
(e.position.x * size.w) + e.deviation.x.valeur - e.grossissement.valeur / 2,
(e.position.y * size.h) + e.deviation.y.valeur - e.grossissement.valeur / 2,
(e.taille + e.grossissement.valeur) * (size.w / background.naturalWidth),
(e.taille + e.grossissement.valeur) * (size.h / background.naturalHeight));
});
// on dessine 2021
const fontSize = 0.1 * size.w - 10;
myCanvas.value.font = `${fontSize}px Caviar Dream`;
myCanvas.value.textAlign = 'center';
myCanvas.value.fillStyle = 'white';
myCanvas.value.strokeStyle = 'black';
myCanvas.value.lineWidth = 0.006 * size.w - 10;
const x = (size.w * 7) / 12;
const y = (size.h * 4) / 9;
const ecart = 0.07 * size.w - 10;
date.value.forEach((ch, i) => {
if (ch.valeur.position > 6 && ch.valeur.sens === 1) {
ch.valeur.sens = -1;
}
if (ch.valeur.position < -6 && ch.valeur.sens === -1) {
ch.valeur.sens = 1;
}
ch.valeur.position += 1 * ch.valeur.sens;
myCanvas.value.fillText(
ch.valeur.chiffre,
x + (ecart * i),
y + ch.valeur.position,
);
});
} catch (e) {
console.log(`ERREUR DE CHARGEMENT D'IMAGE: ${e}`);
}
if (!petit.value) {
drawText();
}
});
};
}
}
function startAnimating(fps) {
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
console.log(startTime);
initCarte();
}
window.addEventListener('resize', initCarte);
return {
myCanvas,
size,
texte,
petit,
carte,
tailleFond,
};
},
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#font-face {
font-family: 'Caviar Dream';
src: local('Caviar Dream'), url(~#/assets/fonts/CaviarDreams.ttf) format('truetype');
}
#texteRemplacement {
font-size: 20pt;
font-family: 'Caviar Dream';
}
#myCanvas {
border: 1px solid grey;
}
</style>
What did I not understand in this page?

TypeError: Cannot read property 'width' of undefined - Nuxt JS and canvas

I am trying to run a sketch on the canvas element in Nuxt JS but I am having some issues (I am completely new to Vue JS).
While I have no errors in VS Code, there is an error in the browser console:
client.js?06a0:84 TypeError: Cannot read property 'width' of undefined
at Blob.get (Blob_Point.js?a5e5:126)
at Blob.render (Blob_Point.js?a5e5:29)
below is the code for my Vue file:
<template>
<div>
<h1 class="pa-5 text-center">Blob</h1>
<canvas id="myCanvas" width="600" height="400"></canvas>
<!--<v-btn #click="drawRect">Clear</v-btn> -->
</div>
</template>
<script>
import Blob from "../assets/content/Blob_Point"
//import { Point } from "../assets/content/Blob_Point"
//import Point from "../assets/content/Blob_Point"
let oldMousePoint = { x: 0, y: 0 }
let blob = new Blob()
//let point = new Point()
let hover = false
let canvas
export default {
data() {
return {
canvas: null,
x: 0,
y: 0,
isDrawing: false,
rectWidth: 200,
//hover: false,
//oldMousePoint: { x: 0, y: 0 },
}
},
mounted() {
let canvas = document.getElementById("myCanvas")
this.canvas = canvas.getContext("2d")
},
created() {
new Blob("#C09EFF")
blob.canvas = canvas
blob.init()
blob.render()
},
methods: {
/* showCoordinates(e) {
this.x = e.offsetX
this.y = e.offsetY
},
drawLine(x1, y1, x2, y2) {
let ctx = this.vueCanvas
ctx.beginPath()
ctx.strokeStyle = "black"
ctx.lineWidth = 1
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.stroke()
ctx.closePath()
},
draw(e) {
if (this.isDrawing) {
this.drawLine(this.x, this.y, e.offsetX, e.offsetY)
this.x = e.offsetX
this.y = e.offsetY
}
},
beginDrawing(e) {
this.x = e.offsetX
this.y = e.offsetY
this.isDrawing = true
},
stopDrawing(e) {
if (this.isDrawing) {
this.drawLine(this.x, this.y, e.offsetX, e.offsetY)
this.x = 0
this.y = 0
this.isDrawing = false
//windowWidth = 0,
}
}, */
resize() {
document.getElementById("myCanvas").width = window.innerWidth
document.getElementById("myCanvas").height = window.innerHeight
},
/*drawRect() {
this.vueCanvas.clearRect(0, 0, 600, 800)
//this.vueCanvas.beginPath()
//this.vueCanvas.rect(20, 20, this.rectWidth, 100)
//this.vueCanvas.stroke()
},*/
mouseMove(e) {
let pos = blob.center
let diff = { x: e.clientX - pos.x, y: e.clientY - pos.y }
let dist = Math.sqrt(diff.x * diff.x + diff.y * diff.y)
let angle = null
blob.mousePos = {
x: pos.x - e.clientX,
y: pos.y - e.clientY,
}
if (dist < blob.radius && hover === false) {
let vector = {
x: e.clientX - pos.x,
y: e.clientY - pos.y,
}
angle = Math.atan2(vector.y, vector.x)
hover = true
// blob.color = '#77FF00';
} else if (dist > blob.radius && hover === true) {
let vector = {
x: e.clientX - pos.x,
y: e.clientY - pos.y,
}
angle = Math.atan2(vector.y, vector.x)
hover = false
blob.color = null
}
if (typeof angle == "number") {
let nearestPoint = null
let distanceFromPoint = 100
blob.points.forEach((point) => {
if (Math.abs(angle - point.azimuth) < distanceFromPoint) {
// console.log(point.azimuth, angle, distanceFromPoint);
nearestPoint = point
distanceFromPoint = Math.abs(angle - point.azimuth)
}
})
if (nearestPoint) {
let strength = {
x: oldMousePoint.x - e.clientX,
y: oldMousePoint.y - e.clientY,
}
strength =
Math.sqrt(strength.x * strength.x + strength.y * strength.y) * 1
if (strength > 100) strength = 100
nearestPoint.acceleration = (strength / 100) * (hover ? -1 : 1)
}
}
oldMousePoint.x = e.clientX
oldMousePoint.y = e.clientY
},
},
}
</script>
<style scoped>
#myCanvas {
border: 1px solid grey;
}
</style>
and below is the Blob_Point JS file that I am importing:
/* eslint-disable */
// Blob Class
export default class Blob {
// setup function
constructor(color) {
//the objects setup
// 'this' is a reference to the current class
this.points = []
this._color = color
}
init() {
for (let i = 0; i < this.numPoints; i++) {
let point = new Point(this.divisional * (i + 1), this)
//point.acceleration = -1 + Math.random() * 2;
this.push(point)
}
}
render() {
let canvas = this.canvas
let ctx = this.ctx
let position = this.position
let pointsArray = this.points
let radius = this.radius
let points = this.numPoints
let divisional = this.divisional
let center = this.center
ctx.clearRect(0, 0, canvas.width, canvas.height)
pointsArray[0].solveWith(pointsArray[points - 1], pointsArray[1])
let p0 = pointsArray[points - 1].position
let p1 = pointsArray[0].position
let _p2 = p1
// this is the draw
ctx.beginPath()
ctx.moveTo(center.x, center.y)
ctx.moveTo((p0.x + p1.x) / 2, (p0.y + p1.y) / 2)
for (let i = 1; i < points; i++) {
pointsArray[i].solveWith(
pointsArray[i - 1],
pointsArray[i + 1] || pointsArray[0]
)
let p2 = pointsArray[i].position
var xc = (p1.x + p2.x) / 2
var yc = (p1.y + p2.y) / 2
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc)
// ctx.lineTo(p2.x, p2.y);
//ctx.fillStyle = this.color;
// ctx.fillRect(p1.x-2.5, p1.y-2.5, 5, 5);
p1 = p2
}
var xc = (p1.x + _p2.x) / 2
var yc = (p1.y + _p2.y) / 2
ctx.quadraticCurveTo(p1.x, p1.y, xc, yc)
ctx.lineTo(_p2.x, _p2.y)
ctx.closePath()
ctx.fillStyle = this.color
ctx.fill()
ctx.strokeStyle = this.color
// ctx.stroke();
/*
ctx.fillStyle = '#000000';
if(this.mousePos) {
let angle = Math.atan2(this.mousePos.y, this.mousePos.x) + Math.PI;
}*/
//requestAnimationFrame(this.render.bind(this))
}
push(item) {
if (item instanceof Point) {
this.points.push(item)
}
}
set color(value) {
this._color = value
}
get color() {
return this._color
}
set canvas(value) {
if (
value instanceof HTMLElement &&
value.tagName.toLowerCase() === "canvas"
) {
this._canvas = canvas
this.ctx = this._canvas.getContext("2d")
}
}
get canvas() {
return this._canvas
}
set numPoints(value) {
if (value > 10) {
this._points = value
}
}
get numPoints() {
return this._points || 32
}
set radius(value) {
if (value > 0) {
this._radius = value
}
}
get radius() {
return this._radius || 300
}
set position(value) {
if (typeof value == "object" && value.x && value.y) {
this._position = value
}
}
get position() {
return this._position || { x: 0.5, y: 0.5 }
}
get divisional() {
return (Math.PI * 2) / this.numPoints
}
get center() {
return {
x: this.canvas.width * this.position.x,
y: this.canvas.height * this.position.y,
}
}
set running(value) {
this._running = value === true
}
get running() {
return this.running !== false
}
}
// Point Class
export class Point {
constructor(azimuth, parent) {
this.parent = parent
this.azimuth = Math.PI - azimuth
this._components = {
x: Math.cos(this.azimuth),
y: Math.sin(this.azimuth),
}
this.acceleration = -0.3 + Math.random() * 0.6
}
solveWith(leftPoint, rightPoint) {
this.acceleration =
(-0.3 * this.radialEffect +
(leftPoint.radialEffect - this.radialEffect) +
(rightPoint.radialEffect - this.radialEffect)) *
this.elasticity -
this.speed * this.friction
}
set acceleration(value) {
if (typeof value == "number") {
this._acceleration = value
this.speed += this._acceleration * 2
}
}
get acceleration() {
return this._acceleration || 0
}
set speed(value) {
if (typeof value == "number") {
this._speed = value
this.radialEffect += this._speed * 3
}
}
get speed() {
return this._speed || 0
}
set radialEffect(value) {
if (typeof value == "number") {
this._radialEffect = value
}
}
get radialEffect() {
return this._radialEffect || 0
}
get position() {
return {
x:
this.parent.center.x +
this.components.x * (this.parent.radius + this.radialEffect),
y:
this.parent.center.y +
this.components.y * (this.parent.radius + this.radialEffect),
}
}
get components() {
return this._components
}
set elasticity(value) {
if (typeof value === "number") {
this._elasticity = value
}
}
get elasticity() {
return this._elasticity || 0.001
}
set friction(value) {
if (typeof value === "number") {
this._friction = value
}
}
get friction() {
return this._friction || 0.0085
}
}
Any ideas on the why lines 29 and 127 of the Blob_Point.js file are throwing errors?
I attached 2 screens of the developer tools in chrome to show the errors along with the JS its pointing to.
Thanks for any help!
The main issue I can identify is here. I have added comments to the code.
Blob_Point.js
render() {
let canvas = this.canvas // this references the Blob Class (not the Vue instance) and there is no initialised property such as canvas in the class
}
To fix this main issue, you can do something like
Blob_Point.js
export default class Blob {
constructor(color, canvas) {
//the objects setup
// 'this' is a reference to the current class
this.points = []
this._color = color;
this.canvas = canvas
}
}
And then in the .vue file
.Vue
mounted() {
let canvas = document.getElementById("myCanvas");
this.canvas = canvas.getContext("2d");
blob = new Blob("#C09EFF", this.canvas); // now canvas in the Blob Class constructor will refer to the vue instance canvas
blob.canvas = canvas;
blob.init();
blob.render();
},
I have identified another issue here.
set canvas(value) {
if (
value instanceof HTMLElement &&
value.tagName.toLowerCase() === "canvas"
) {
this._canvas = canvas // there is no initialised constructor property as _canvas
this.ctx = this._canvas.getContext("2d") // there is no initialised constructor property such as _canvas
}
}
get canvas() {
return this._canvas // there is no initialised constructor property as _canvas
}

Adding markerclustergroup to leaflet overlay does not update in Vue.js app

I have an overlay control on my leaflet map in a vue.js application. It contains two layers. The ILZipCodes layer renders perfectly. However, the "markers" layer does not appear when I select the checkbox in the layer control. Why is this? I reckon I may be setting up the layer control and populating the clusters in the wrong sequence.
Cheers
<template>
<div>
<div class="mx-auto my-6 loader" v-if="isSearching"></div>
<div class="tooManyResultsError" v-if="tooManyResults">
Your search brought back too many results to display. Please zoom in or refine your search with the text box and
filters.
</div>
<div id="leafletMapId"></div>
</div>
</template>
<script>
//The center coordinate and zoom level that the map should initialize to
// to capture all of the continental United States
const INITIAL_CENTER_LATITUDE = 34;
const INITIAL_CENTER_LONGITUDE = -96;
const INITIAL_ZOOM = 4;
/* Leaflet performs slowly with reactivity ("observable" data object variables).
* To work around this, I removed myMap and markers from the data object.
*/
/* If the user zooms out or pans beyond the boundaries of the previous fetch's dataset, then
* the client fetches location data from the server to replace the previous map data. If the user
* zooms or pans but stays within the boundaries of the dataset currently displayed on the map, then
* the client does not run another fetch.
* */
import axios from "axios";
import store from "../store.js";
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import "leaflet.markercluster/dist/MarkerCluster.css";
import "leaflet.markercluster/dist/MarkerCluster.Default.css";
import "leaflet.markercluster/dist/leaflet.markercluster-src.js";
import "leaflet-ajax/dist/leaflet.ajax.js";
import icon from "leaflet/dist/images/marker-icon.png";
import iconShadow from "leaflet/dist/images/marker-shadow.png";
export default {
name: "ContactsMap",
mounted() {
this.fetchAggregatedDistrictStats();
},
methods: {
async fetchAggregatedDistrictStats() {
axios.get(... // fetches some statistics for the other overlay layers
);
},
createMapWithLeafletAndMapTiler() {
var $this = this;
var streetTilesLayer = L.tileLayer(MAPTILER_STREETS_URL, {
maxZoom: 18,
minZoom: 2,
attribution: MAPBOX_ATTRIBUTION,
tileSize: 512,
zoomOffset: -1
});
// eslint-disable-next-line
var satelliteTilesLayer = L.tileLayer(MAPTILER_SATELLITE_URL, {
maxZoom: 18,
minZoom: 2,
tileSize: 512,
zoomOffset: -1
});
var baseMaps = {
Satellite: satelliteTilesLayer,
Streets: streetTilesLayer
};
if (myMap != undefined) {
myMap.remove();
}
var myMap = L.map("leafletMapId", {
layers: [streetTilesLayer],
}).setView(this.center, this.zoom);
var markers = L.markerClusterGroup({
iconCreateFunction: function(cluster) {
var count = cluster.getChildCount();
var digits = (count + "").length;
return L.divIcon({
html: "<b>" + count + "</b>" + digits,
className: "cluster digits-" + digits,
iconSize: 22 + 10 * digits
});
}
});
async function fetchLocations(shouldProgrammaticallyFitBounds) {
markers.clearLayers();
markers = L.markerClusterGroup({
chunkedLoading: true,
iconCreateFunction: function(cluster) {
var count = cluster.getChildCount();
var digits = (count + "").length;
return L.divIcon({
html: "<b>" + count + "</b>",
className: "cluster digits-" + digits,
iconSize: 22 + 10 * digits
});
}
});
axios
.get("/maps/" + $this.list.list_id, {
//fetches markerclusters
})
.then(
response => {
$this.tooManyResults = false;
var addressPoints = response.data.recordsList;
for (var i = 0; i < addressPoints.length; i++) {
var a = addressPoints[i];
var title = a[2];
var marker = L.marker(L.latLng(a[0], a[1]));
marker.bindPopup(title);
markers.addLayer(marker); // This is where I'm adding the markers and markerclusters to the layer titled "markers"
// myMap.addLayer(markers); //If I uncomment this, then the markers layer is always on the map, i.e. not as an overlay layer
}
},
error => {
$this.isSearching = false;
document.getElementById("leafletMapId").style.display = "block";
store.setErrorMessage("Network error searching list", error);
}
);
}
myMap.on("zoomend", handlerFunction);
myMap.on("dragend", handlerFunction);
var defaultPolygonStyle = {
color: "black",
weight: 1,
opacity: 0.8,
fillOpacity: 0.1
};
var NationalCongressional = new L.geoJson.ajax(
"http://localhost:8080/assets/geo/NationalCongressional.json",
{
style: defaultPolygonStyle,
onEachFeature: function(feature, layer) {
layer.bindPopup(feature.properties.NAMELSAD);
if (feature.properties.NAMELSAD == "Congressional District 8") {
layer.setStyle({ color: "orange" });
}
}
}
);
function getColorFromRedBlueRange(d) {
return d > 0.8
? "#FF0000"
: d > 0.7
? "#FF006F"
: d > 0.55
? "#FF00EF"
: d > 0.45
? "#DE00FF"
: d > 0.3
? "#BC00FF"
: d > 0.2
? "#6600FF"
: "#00009FF";
}
var ILZipCodes = new L.geoJson.ajax(
"https://raw.githubusercontent.com/OpenDataDE/State-zip-code-GeoJSON/master/il_illinois_zip_codes_geo.min.json",
{
// style: defaultPolygonStyle,
onEachFeature: function(feature, layer) {
layer.bindPopup(
feature.properties.ZCTA5CE10 +
", " +
$this.zipData[feature.properties.ZCTA5CE10]
);
layer.setStyle({
color: getColorFromRedBlueRange(
$this.zipData[feature.properties.ZCTA5CE10]
),
weight: 0,
fillOpacity: 0.3
});
}
}
);
var overlays = {
Voters: voterGroup,
ILZipCodes: ILZipCodes,
};
L.control.layers(baseMaps, overlays).addTo(myMap);
fetchLocations(true);
}
}
};
You add voterGroup to your Layers Control, instead of your markers.
Then simply do not re-assign a MarkerClusterGroup into your markers variable (after you use clearLayers) and you should be fine.

Dynamically update lines in Highcharts time series chart

Here I'm working on Highcharts time series chart with live streaming data, based on the sample jsfiddle. In the fiddle there shows 4 lines named as input1, input2, input3, & input 4 and it is updated with live random data but in my actual project the input values are updated via MQTT. In actual project, sometimes, when we push streaming data, there will be increase or decrease in no: of inputs (such as input5, input6 like wise). So how can we add new line or remove line dynamically in time series chart with streaming data.
javascript code :
$(function() {
$(document).ready(function() {
Highcharts.setOptions({
global: {
useUTC: false
}
});
$('#container').highcharts({
chart: {
type: 'spline',
animation: Highcharts.svg, // don't animate in old IE
marginRight: 10,
events: {
load: function() {
// set up the updating of the chart each second
var series = this.series;
var length = series.length;
setInterval(function() {
var x = (new Date()).getTime(), // current time
a0 = Math.random();
a1 = Math.random();
a2 = Math.random();
series[0].addPoint([x, Math.random()], true, true);
for (var i = 1; i < length; i++) {
series[i].addPoint([x, Math.random()], false, true);
}
}, 1000);
}
}
},
title: {
text: 'Live random data'
},
legend: {
enabled: true
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150
},
yAxis: {
title: {
text: 'Value'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
formatter: function() {
return '<b>' + this.series.name + '</b><br/>' +
Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
Highcharts.numberFormat(this.y, 2);
}
},
exporting: {
enabled: false
},
series: [{
name: 'input1',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input2',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input3',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}, {
name: 'input4',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i += 1) {
data.push({
x: time + i * 1000,
y: Math.random()
});
}
return data;
}())
}]
});
});
});