How to import and use a custom Chart.js plugin in Nuxt? (Chartjs-vuejs v2.9.4) - html

I am trying to import a custom plugin into my chart.
Got this plugin from my previous question: Question
Its a plugin so that I can use Grace in my version of Chart.js.
The version of Chart.js I am using is V2.9.4.
I am using vue-chartjs in Nuxt!.
Couldn't really find an answer anywhere else.
This is how it looks now
This is how I want it to look
Thanks in advance. :)
I made a Component called 'BarChart' in my Components folder.
I made a normal .vue file in my pages directory. In the <template> tag I added my <Barchart/> component.
In that same .vue file I added a script in the <script> tag.
The plugin code is included in the codes below, I didn't include it anywhere yet.
Barchart.vue (Component)
<script>
import {Bar} from "vue-chartjs";
export default {
extends: Bar,
props: {
data: {
type: String,
default: () => {},
},
options: {
type: Object,
default: () => {},
},
},
computed: {
Chart() {
return['data', 'options'];
},
},
mounted() {
this.renderChart(this.data, this.options);
},
};
</script>
.vue file (Include component)
<div class="chart">
<BarChart :data="barChartData" :options="barChartOptions" :height="200"/>
</div>
.vue file (script tags)
<script>
import BarChart from "~/components/plugins/BarChart";
export default {
components: {
BarChart,
},
data() {
return {
barChartData: {
labels: ["Verzonden", "Ontvangen", "Geopend", "Kliks"],
datasets: [
{
data: [25, 20, 20, 18],
backgroundColor: [
'#7782FF',
'#403DD3',
'#FFB930',
'#00E437',
],
barThickness : 50,
},
],
},
barChartOptions: {
responsive: true,
legend: {
display: false,
},
scales: {
xAxes: [
{
gridLines: {
display: false,
},
ticks: {
fontColor: "black",
fontSize: 14,
},
},
],
yAxes: [
{
ticks: {
beginAtZero: true,
min: 0,
stepSize: 5,
fontColor: '#ABACB3',
},
gridLines: {
display: true,
borderDash: [4, 4],
color: '#EEEDFB',
drawBorder: false,
},
},
],
},
},
};
},
};
</script>
Plugin code (where do I put this and how do I make it work?)
const plugin = {
id: "customScale",
beforeLayout: (chart, options, c) => {
let max = Number.MIN_VALUE;
let min = Number.MAX_VALUE
let grace = options.grace || 0
chart.data.datasets.forEach((dataset) => {
max = Math.max(max, Math.max(...dataset.data));
min = Math.min(min, Math.min(...dataset.data))
})
if (typeof grace === 'string' && grace.includes('%')) {
grace = Number(grace.replace('%', '')) / 100
chart.options.scales.yAxes[0].ticks.suggestedMax = max + (max * grace)
chart.options.scales.yAxes[0].ticks.suggestedMin = min - (min * grace)
} else if (typeof grace === 'number') {
chart.options.scales.yAxes[0].ticks.suggestedMax = max + grace
chart.options.scales.yAxes[0].ticks.suggestedMin = min - grace
}
}
}

According to the vue-chartjs documentation you can do this in 2 ways.
If you want the plugin to be available for all your charts you can use the global registration like so:
import Chart from 'chart.js'
Chart.pluginService.register({
id: "customScale",
beforeLayout: (chart, options, c) => {
let max = Number.MIN_VALUE;
let min = Number.MAX_VALUE
let grace = options.grace || 0
chart.data.datasets.forEach((dataset) => {
max = Math.max(max, Math.max(...dataset.data));
min = Math.min(min, Math.min(...dataset.data))
})
if (typeof grace === 'string' && grace.includes('%')) {
grace = Number(grace.replace('%', '')) / 100
chart.options.scales.yAxes[0].ticks.suggestedMax = max + (max * grace)
chart.options.scales.yAxes[0].ticks.suggestedMin = min - (min * grace)
} else if (typeof grace === 'number') {
chart.options.scales.yAxes[0].ticks.suggestedMax = max + grace
chart.options.scales.yAxes[0].ticks.suggestedMin = min - grace
}
}
});
This way of importing and registering should work from anywhere in your app.
The second way is an inline plugin. This needs to be done in your BarChart.vue and goes like this:
mounted() {
this.addPlugin(
Chart.pluginService.register({
id: "customScale",
beforeLayout: (chart, options, c) => {
let max = Number.MIN_VALUE;
let min = Number.MAX_VALUE
let grace = options.grace || 0
chart.data.datasets.forEach((dataset) => {
max = Math.max(max, Math.max(...dataset.data));
min = Math.min(min, Math.min(...dataset.data))
})
if (typeof grace === 'string' && grace.includes('%')) {
grace = Number(grace.replace('%', '')) / 100
chart.options.scales.yAxes[0].ticks.suggestedMax = max + (max * grace)
chart.options.scales.yAxes[0].ticks.suggestedMin = min - (min * grace)
} else if (typeof grace === 'number') {
chart.options.scales.yAxes[0].ticks.suggestedMax = max + grace
chart.options.scales.yAxes[0].ticks.suggestedMin = min - grace
}
}
});
)
}

Related

Getting usable numbers from Nested Array Json

My data that I'm given is formulated like the following. I'm struggling to get any usable data out of it using reduce or map
const data = [
{
id: 25,
status: 1,
description: "No Description",
length: 4,
data: [
{
id: 43,
comment: "Comment1",
eventTimestamp: 1541027189000,
intensity: 29
},
{
comment: "Comment2",
eventTimestamp: 1541027191000,
intensity: 33
},
{
id: 45,
comment: "Comment3",
eventTimestamp: 1541027193000,
intensity: 30
}
],
tTypes: [
{
id: 3,
label: "Johnny",
certainty: "TEST",
comment: "Test Purposes Only",
icon: "bottle",
number: 0
}
]
}
];
I've tried flatting, I've tried iterating the JSON twice and I just seem to end up with either "NaN" or Undefined. I'd like to be able to order them in time order (using time stamp), get the mix/max/ave from the intensity values and more. I have that figured out for the length which is a level higher, but just can't seem to figure out the rest. Can someone point me in the right direction?
export default function App() {
let tTypesArray = data.map((a) => a.tTypes);
let Walker = tTypesArray.reduce((a, tTypes) => tTypes.label === "Johnny" ? ++a : a, 0);
console.log(Walker);
console.log(tTypesArray[0].label);
console.log([].concat(...data)
.map(data => data.tTypes.number)
.reduce((a, b) => a + b))
console.log([].concat(...data).reduce((a, { tTypes: { id }}) => id, 0))
return <div className="App">ARG!</div>;
}
Are some of the examples I've tried.
https://codesandbox.io/s/purple-cache-ivz1y?file=/src/App.js
Is the link to the sandbox.
What I understood from you question is that you need to loop data and for each element in data you want to extract values and do some calculations.
First you need to loop your data input. I will use Array.forEach:
data.forEach(element => { ... })
Now that we have a loop we can access each element property and extract the information we want. For instance lets say you want to sort the comments by timestamp in ascending order:
const sortedComments = element.data.sort((a, b) => a.eventTimestamp - b.eventTimestamp);
console.log(sortedComments)
Now let's say you want the min, max, and average intensity from the comments. There are several ways to get it. Here is an algorithm for that:
let min = Infinity;
let max = -Infinity;
let sum = 0;
for(comment of sortedComments) {
if(comment.intensity < min) {
min = comment.intensity;
}
if(comment.intensity > max) {
max = comment.intensity;
}
sum += comment.intensity;
}
const avg = sum / sortedComments.length;
console.log({min, max, avg})
Putting it all together:
const data = [
{
id: 25,
confirmationStatus: 1,
description: "No Description",
length: 4,
data: [
{
id: 43,
comment: "Comment1",
eventTimestamp: 1541027189000,
intensity: 29
},
{
comment: "Comment2",
eventTimestamp: 1541027191000,
intensity: 33
},
{
id: 45,
comment: "Comment3",
eventTimestamp: 1541027193000,
intensity: 30
}
],
tTypes: [
{
id: 3,
label: "Johnny",
certainty: "TEST",
comment: "Test Purposes Only",
icon: "bottle",
number: 0
}
]
}
];
data.forEach(element => {
const sortedComments = element.data.sort((a, b) => a.eventTimestamp - b.eventTimestamp);
console.log(sortedComments);
let min = Infinity;
let max = -Infinity;
let sum = 0;
for(comment of sortedComments) {
if(comment.intensity < min) {
min = comment.intensity;
}
if(comment.intensity > max) {
max = comment.intensity;
}
sum += comment.intensity;
}
const avg = sum / sortedComments.length;
console.log({min, max, avg});
let walker = element.tTypes.reduce(
(a, tType) => (tType.label === "Johnny" ? ++a : a), 0
);
console.log(walker)
});
I hope it puts you in the right direction.

Ajax Response Capitalizations Rules

A was using DataTables plugin, and when displaying my columns I needed to put the first letter to LowerCase for it to recognize the property/object, e.g:
// Object name is actually: EngineeringChange.Responsible
{
title: "Responsible",
data: "engineeringChange.responsible",
className: "columnName columnHeader",
},
I just assumed the first letter would always be capitalized to LowerCase. I tried creating a new property inside EngineeringChange named ECRNumber, so I tried:
{
title: "ECR Number",
data: "engineeringChange.eCRNumber",
className: "columnName columnHeader",
},
Isn't recognized as a parameter... After a bit of searching I find out the Json response I get on AJAX it's called ecrNumber. So now I'm actually lost on which are the rules that are automatically applied to the Json Response. How does it turn ecr to LowerCase and Number to UpperCase (the N)??
Edit
Sry, can´t think of any easy way to exactly reproduce my problem on a demo
TableDesign/Creation
table = $('#tblEntries2').DataTable({
order: [[0, 'desc']],
deferRender: true,
ajax: {
url: '#Url.Action("GetEntries", "AlteracaoEngenharia")',
method: 'GET',
dataSrc: '',
beforeSend: function () {
onBegin();
$content.hide();
$loader.show();
},
complete: function (jsonResponse) {
console.log(jsonResponse);
onComplete();
$loader.hide();
$content.fadeIn();
$('#ExcelExport').show();
table.column(5).visible(false);
table.column(6).visible(false);
table.column(7).visible(false);
table.column(9).visible(false);
table.column(10).visible(false);
table.column(11).visible(false);
}
},
dom: "<'row'<'col-2'l><'col-7 text-center'B><'col-3'f>>" +
"<'row'<'col-12'tr>>" +
"<'row'<'col-5'i><'col-7'p>>",
lengthMenu: [
[10, 25, 50, 100, -1],
['10', '25', '50', '100', 'Todos'],
],
columns: [
{
title: "Id (yr-id)",
data: "creationYear",
className: "columnNumber columnHeader",
},
{
title: "ECR Number",
data: "engineeringChange.ecrNumber",
className: "columnNumber columnHeader",
},
{
title: "Criador Alteração de Engenharia",
data: "engineeringChange.responsible",
className: "columnName columnHeader",
},
...
Handler
public IActionResult GetEntries()
{
GetDataEntries();
int count = 0;
int currentYear = 0;
foreach (var entry in EntriesList)
{
EngineeringChangesListViewModel h = new EngineeringChangesListViewModel
{
EngineeringChange = entry
};
if (currentYear != entry.DataCriacao.Year)
{
count = 1;
currentYear = entry.DataCriacao.Year;
}
else
{
count++;
}
h.DeadLine = entry.FinishedGood.Week + " - " + entry.DataCriacao.Year.ToString();
if (entry.OldPart == null)
{
h.EngineeringChange.OldPart = new Part();
}
if (entry.NewPart == null)
{
h.EngineeringChange.NewPart = new Part();
}
if (entry.FinishedGood == null)
{
h.EngineeringChange.FinishedGood = new FinishedGood();
}
if (entry.OldPart != null && entry.OldPart.CDP.HasValue)
h.OldPartValue = entry.OldPart.CDP * entry.OldPart.Stock;
if (entry.NewPart != null && entry.NewPart.CDP.HasValue)
h.NewPartValue = entry.NewPart.CDP * entry.NewPart.Stock;
h.EngineeringChange.ECRNumber = entry.ECRNumber;
//toString("D4") padds the number to always be 4 digits
h.CreationYear = entry.DataCriacao.Year.ToString() + "_" + count.ToString("D4");
h.IdYear = count;
EntriesListaViewModel.Add(h);
}
var errorsJson = JsonConvert.SerializeObject(EntriesListaViewModel);
Console.WriteLine(errorsJson);
return new JsonResult(EntriesListaViewModel);
}
HANDLER OUTPUT
[{"CreationYear":"2021_0001","IdYear":1,"OldPartValue":null,"NewPartValue":2061.09155,"DeadLine":"15 - 2021","EngineeringChange":{"Id":8,"DataCriacao":"2021-03-11T16:15:24.6630956","Responsible":"José António","ECRNumber":"X1232","State":"Aberto","Comment":"Teste","UserId":1,"User":null,"Component":null,"FinishedGood":{"Id":31,"Week":15,"EngineeringChangeId":8},"OldPart":{"Id":5,"Reference":"FS12848AC","Stock":null,"CDP":1.43584776,"LastExpired":null},"NewPart":{"Id":6,"Reference":"FS12848AD","Stock":1650,"CDP":1.24914646,"LastExpired":"2021-03-11T00:00:00"},"Transformation":{"Id":188,"Possible":true,"Cost":1090.0,"WillBeTransformed":true,"TransformationDate":"2021-03-13T08:48:00","Responsible":"Miguel","EngineeringChangeId":8}}},]
PAGE/AJAX OUTPUT outputs are created by console.log() and Console.WriteLine() displayed above.
ECRNumber gets named to ecrNumber...

Loss and mean squared error values not showing during training performance. Not getting predicted line

Currently we are trying to input uber data that includes time of day and ride fare into our TensorFlow.js model. We noticed that when we ran the model on the browser, the points are showing up on our scatterplot but during the training the loss and mean squared error values are not showing up and most importantly our model is not displaying a prediction line.
var userData = [
{
"City": "San Francisco",
"Product_Type": "UberEATS Marketplace",
"Trip_or_Order_Status": "COMPLETED",
"Request_Time": "2019-06-16 04:10:44 +0000 UTC",
"Begin_Trip_Time": "2019-06-16 04:44:40 +0000 UTC",
"Begin_Trip_Lat": "37.7352602",
"Begin_Trip_Lng": "-122.4203465",
"Begin_Trip_Address": "",
"Dropoff_Time": "2019-06-16 04:44:40 +0000 UTC",
"Dropoff_Lat": "37.7352602",
"Dropoff_Lng": "-122.4203465",
"Dropoff_Address": "",
"Distance_miles": "2.04",
"Fare_Amount": "32.34",
"Fare_Currency": "USD"
}...]
async function getData() {
const carsData = await userData;
// Here we map out the values for each car and filter out the list item that do not have an day or a pay value
const cleaned = carsData.map(car => ({
timeInMinutes: calculateMins(car.Request_Time),
pay_rate: normalizeUberPrice(car.Distance_miles, car.Fare_Amount),
}))
.filter(car => (car.day != null && car.pay != null));
return cleaned;
}
async function run() {
const data = await getData();
const values = data.map(d => ({
x: d.day,
y: d.pay,
}));
tfvis.render.scatterplot(
{ name: 'Horsepower v MPG' },
{ values },
{
xAxisDomain: [0, 1600],
yAxisDomain: [0,10],
xLabel: 'Day',
yLabel: 'Pay',
height: 300
}
);
const model = createModel();
tfvis.show.modelSummary({ name: 'Model Summary' }, model);
// Convert the data to a form we can use for training.
const tensorData = convertToTensor(data);
console.log(tensorData)
const { inputs, labels } = tensorData;
// Train the model
await trainModel(model, inputs, labels);
console.log('Done Training');
testModel(model, data, tensorData);
}
function createModel() {
const model = tf.sequential();
model.add(tf.layers.dense({ inputShape: [1], units: 25, useBias: true }));
model.add(tf.layers.dense({ units: 50, activation: "sigmoid" }));
model.add(tf.layers.dense({ units: 1, useBias: true }));
return model;
}
function convertToTensor(data) {
return tf.tidy(() => {
tf.util.shuffle(data);
const inputs = data.map(d => d.pay)
const labels = data.map(d => d.day);
const inputTensor = tf.tensor2d(inputs, [inputs.length, 1]);
const labelTensor = tf.tensor2d(labels, [labels.length, 1]);
//Step 3. Normalize the data to the range 0 - 1 using min-max scaling
const inputMax = inputTensor.max();
const inputMin = inputTensor.min();
const labelMax = labelTensor.max();
const labelMin = labelTensor.min();
const normalizedInputs = inputTensor.sub(inputMin).div(inputMax.sub(inputMin));
const normalizedLabels = labelTensor.sub(labelMin).div(labelMax.sub(labelMin));
return {
inputs: normalizedInputs,
labels: normalizedLabels,
// Return the min/max bounds so we can use them later.
inputMax,
inputMin,
labelMax,
labelMin,
}
});
}
async function trainModel(model, inputs, labels) {
model.compile({
optimizer: tf.train.adam(),
loss: tf.losses.meanSquaredError,
metrics: ['mse'],
});
const batchSize = 32;
const epochs = 30;
callbacks: tfvis.show.fitCallbacks(
{ name: 'Training Performance' },
['loss', 'mse'],
{
xAxisDomain: [0, 100],
yAxisDomain: [0,1],
height: 200,
callbacks: ['onEpochEnd'] }
// ',onBatchEnd'
),
history: tfvis.show.history({
name: 'History'},
history,
["loss","mse"])
});
}
function testModel(model, inputData, normalizationData) {
const { inputMax, inputMin, labelMin, labelMax } = normalizationData;
const [xs, preds] = tf.tidy(() => {
const xs = tf.linspace(0, 1, 100);
const preds = model.predict(xs.reshape([100, 1]));
const unNormXs = xs
.mul(inputMax.sub(inputMin))
.add(inputMin);
const unNormPreds = preds
.mul(labelMax.sub(labelMin))
.add(labelMin);
return [unNormXs.dataSync(), unNormPreds.dataSync()];
});
const predictedPoints = Array.from(xs).map((val, i) => {
return { x: val, y: preds[i] }
});
const originalPoints = inputData.map(d => ({
x: d.pay, y: d.day,
}));
console.log("ORIGINAL POINTS:")
console.log(originalPoints)
tfvis.render.scatterplot(
{ name: 'Model Predictions vs Original Data' },
{ values: [originalPoints, predictedPoints], series: ['original', 'predicted'] },
{
xAxisDomain: [0,10],
yAxisDomain: [0,1600],
xLabel: 'Horsepower',
yLabel: 'MPG',
height: 1000
}
);
}
document.addEventListener('DOMContentLoaded', run);
Basically we want to see a predicted line for our data but were not getting anything back.
It worked when we used data like this:
var userData = [{
day: 1
pay: 20
},...]
The data processing is not well performed. Thus, the values used for prediction contain NaN and Infinity. As a result, the error computed by model.fit is NaN and could therefore not be displayed on the chart of tfjs-vis.
The filtering
.filter(car => (car.day != null && car.pay != null));
is not removing NaN and Infinity. Instead, this condition can be used:
.filter(car => isFinite(car.pay + car.day) && !isNaN(car.pay + car.day));
Though, the NaN and Infinity are found within the values of car.day, here a general filtering is made over car.pay and car.day - thus the additional operation - to make sure that these values will not appear anywhere in the cleaned data.
here you can see how to display the loss.

VueJS Child component GMAP not rerender when parent data updates

Hi I'm new with Vue and I bump to this problem where when I update the location it doesn't reflect on the child component. I've used computed and watch but still not updating. forgive me as I don't have a strong knowledge about VueJS.
so in my code I have location(computed) which listen to localLocation(data) that is bind to the project.location. I update the location using the method setPlace()
hope anyone can help. here's my code below:
<template lang="pug">
.google-maps-wrapper
template(v-if="!location")
f7-list.no-margin-top
f7-list-button(title='Add Location' #click="isAutocompleteOpen = true")
template(v-if="location")
f7-list.no-margin(inline-labels no-hairlines-md)
f7-list-item.short-text Address: {{ location.formattedAddress }}
div
gmap-map.main-map(ref="googleMap" :options="mapOptions" :center="location.position" :zoom="16" :map-type-id="mapTypeId")
gmap-marker(:position="location.position" :clickable="false")
f7-actions(ref='mapsAction')
f7-actions-group
f7-actions-group
f7-actions-button(#click="copyToClipboard()") Copy to Clipboard
f7-actions-button(#click="isAutocompleteOpen = true") Change Address
f7-actions-button(v-if="$root.$device.ios || $root.$device.macos" #click="$refs.navigateActions.f7Actions.open()") Navigate
f7-actions-button(v-else #click="googleMapsNavigate()") Navigate
f7-actions-group
f7-actions-button
b Cancel
f7-actions(ref='navigateActions')
f7-actions-group
f7-actions-group
f7-actions-button(#click="googleMapsNavigate()") Google Maps
f7-actions-button(#click="appleMapsNavigation()") Apple Maps
f7-actions-group
f7-actions-button
b Cancel
f7-popup.locate-project(:opened='isAutocompleteOpen' #popup:closed='closeAutocomplete()')
f7-page
f7-navbar
f7-nav-title Search Location
f7-nav-right
f7-link(#click="closeAutocomplete()") Close
f7-searchbar.searchbar(search-container=".search-list" search-in=".item-title" #input="searchLocation($event)" placeholder="Enter Location" clear-button)
f7-list.not-found(v-if="!pendingSearch && !suggestedLocations.length && searchedLocation")
f7-list-item(title="Nothing found")
f7-block-title.gmap-preloader(v-if="pendingSearch")
f7-preloader(size="16")
f7-list.search-list.searchbar-found.gmap-search-list(v-if="!pendingSearch && suggestedLocations.length" media-list)
f7-list-item.item-button(v-for='(location, index) in suggestedLocations' :title="location.structured_formatting.main_text" :subtitle="location.structured_formatting.secondary_text" #click="updateLocation(location)")
</template>
<script>
import { debounce } from 'lodash-es'
import { Plugins } from '#capacitor/core'
const { Clipboard } = Plugins
const { Browser } = Plugins
const debounceSearch = debounce(run => {
run()
}, 500)
import defaultMixin from '#/mixins/default'
import {
f7Actions,
f7ActionsLabel,
f7ActionsGroup,
f7ActionsButton,
f7Popup,
f7Page,
f7NavRight,
f7NavTitle,
f7Navbar,
f7Block,
f7BlockTitle,
f7Label,
f7Link,
f7Preloader,
f7List,
f7ListButton,
f7ListItem,
f7ListInput,
f7Icon,
f7Searchbar
} from 'framework7-vue'
import { gmapApi } from 'vue2-google-maps'
export default {
name: "google-maps",
mixins: [defaultMixin],
props: ['project'],
components: {
f7Actions,
f7ActionsLabel,
f7ActionsGroup,
f7ActionsButton,
f7Popup,
f7Page,
f7NavRight,
f7NavTitle,
f7Navbar,
f7Block,
f7BlockTitle,
f7Label,
f7Link,
f7Preloader,
f7List,
f7ListButton,
f7ListItem,
f7ListInput,
f7Icon,
f7Searchbar
},
data() {
return {
mapTypeId: "terrain",
directionsService: undefined,
directionsDisplay: undefined,
autocompleteService: undefined,
autocompleteRequest: undefined,
navigate: false,
localLocation: this.project.location,
mapOptions: {
disableDefaultUI: true,
backgroundColor: '#d3d3d3',
draggable: false,
zoomControl: false,
fullscreenControl: false,
streetViewControl: false,
clickableIcons: false
},
isAutocompleteOpen: false,
suggestedLocations: [],
pendingSearch: false,
origin: '',
searchedLocation: ''
}
},
computed: {
location() {
return this.localLocation
},
google: gmapApi
},
methods: {
appleMapsNavigation(){
window.open(`http://maps.apple.com/?daddr=${encodeURI(this.project.location.formattedAddress)}`)
},
googleMapsNavigate(){
if(this.$root.$device.ios){
window.open(`comgooglemaps://?daddr=${encodeURI(this.project.location.formattedAddress)}`)
}else{
window.open(`https://www.google.com/maps/dir//${encodeURI(this.project.location.formattedAddress)}`)
}
},
closeAutocomplete() {
this.isAutocompleteOpen = false
this.searchedLocation = ''
this.suggestedLocations = []
this.$f7.searchbar.clear('.searchbar')
},
updateLocation( location ){
this.getGeocode(location.place_id, output => {
this.suggestedLocations = []
this.setPlace(output[0])
})
},
getGeocode( placeId, callback ){
const geocoder = new google.maps.Geocoder()
this.$f7.dialog.preloader()
geocoder.geocode({placeId}, output => {
callback(output)
this.closeAutocomplete()
this.$f7.dialog.close()
})
},
searchLocation( event ) {
this.pendingSearch = true
this.searchedLocation = event.target.value
debounceSearch(() => {
if(!this.searchedLocation) {
this.pendingSearch = false
this.suggestedLocations = []
return
}
const autocompleteService = new google.maps.places.AutocompleteService()
autocompleteService.getPlacePredictions({input: this.searchedLocation}, output => {
if(this.pendingSearch){
this.suggestedLocations = output || []
this.pendingSearch = false
}
})
})
},
setPlace( selectedLocation ) {
if(!selectedLocation.formatted_address) return;
const data = {
location: {
formattedAddress: selectedLocation.formatted_address,
position: {
lat: selectedLocation.geometry.location.lat(),
lng: selectedLocation.geometry.location.lng()
}
}
};
this.$f7.popup.close('.add-location')
if(this.$refs.autocomplete) this.$refs.autocomplete.$el.disabled = true
this.localLocation = data.location
db.collection("projects")
.doc(this.project.id)
.set(data, {
merge: true
})
.then()
},
copyToClipboard() {
Clipboard.write({
string: this.project.location.formattedAddress
});
}
}
}
</script>
Code Summary (just the summary of the whole code above)
Template that displays the address and map
.google-maps-wrapper
template(v-if="!location")
f7-list.no-margin-top
f7-list-button(title='Add Location' #click="isAutocompleteOpen = true")
template(v-if="location")
f7-list.no-margin(inline-labels no-hairlines-md)
f7-list-item.short-text Address: {{ location.formattedAddress }}
div
gmap-map.main-map(ref="googleMap" :options="mapOptions" :center="location.position" :zoom="16" :map-type-id="mapTypeId")
gmap-marker(:position="location.position" :clickable="false")
Last line of the template where it updates the location
f7-list-item.item-button(v-for='(location, index) in suggestedLocations' :title="location.structured_formatting.main_text" :subtitle="location.structured_formatting.secondary_text" #click="updateLocation(location)")
Script that updates the location
updateLocation( location ){
this.getGeocode(location.place_id, output => {
this.suggestedLocations = []
this.setPlace(output[0])
})
},
setPlace( selectedLocation ) {
if(!selectedLocation.formatted_address) return;
const data = {
location: {
formattedAddress: selectedLocation.formatted_address,
position: {
lat: selectedLocation.geometry.location.lat(),
lng: selectedLocation.geometry.location.lng()
}
}
};
this.$f7.popup.close('.add-location')
if(this.$refs.autocomplete) this.$refs.autocomplete.$el.disabled = true
this.localLocation = data.location
db.collection("projects")
.doc(this.project.id)
.set(data, {
merge: true
})
.then()
},
Initial Page No Address yet
Actual Output after adding location
Expected Output

How do I use a class variable in the function() method?

Class Variable Name: addPointY
"addPointY" Using Function:
setInterval(function () {
var y = Math.round(Math.random() * 100);
series.addPoint(this.addPointY, true, true);
}, 3000);
I have to find a way to use it.
This is a customer requirement and has not been resolved.
Please tell me another way.
The class variable must be used in any of its methods.
But I could not get the class variable.
Do not you have a smart developer who solved the same problem?
#Injectable()
export class HighChartService implements ChartService {
private addPointY: number = 0;
shiftAddPoint(data: number) {
this.addPointY = data;
console.log(this.addPointY);
}
/**
* #see DynamicChart start function
* #param obj chart Object
* #param title Top Title
* #param type ChartType
* #param yAxisTitle Left Title
* #param series Chart data
* #author jskang
* #since 2017/10/12
*/
dynamicInitOptions(title: string, type: string, yAxisTitle: string, series: Object[]) {
if (!type) { type = "line"; }
let obj = new Chart({
chart: {
type: type,
events: {
load: function () {
// set up the updating of the chart each second
var series = this.series[0];
setInterval(function () {
var y = Math.round(Math.random() * 100);
series.addPoint(this.addPointY, true, true);
}, 3000);
}
}
},
title: { text: title },
xAxis: {
categories: [0,1,2,3,4,5,6],
labels: {
formatter: function () {
let xAxis = "";
if(this.value % 7 == 0){ xAxis = "일"; }
else if(this.value % 7 == 1){ xAxis = "월"; }
else if(this.value % 7 == 2){ xAxis = "화"; }
else if(this.value % 7 == 3){ xAxis = "수"; }
else if(this.value % 7 == 4){ xAxis = "목"; }
else if(this.value % 7 == 5){ xAxis = "금"; }
else if(this.value % 7 == 6){ xAxis = "토"; }
return xAxis;
}
}
},
yAxis: {
title: {
text: yAxisTitle
},
labels: {
formatter: function () {
return this.value;
}
}
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle'
},
series: series
});
return obj;
}
}
The this inside your callback function for setInterval does not point to the current class instance because when you use function () {} syntax it creates its own binding for this based on how it is called.
To fix this use arrow functions which preserves the context and you can access your class properties inside the callback:
load: () => { // Notice arrow function here
// set up the updating of the chart each second
var series = this.series[0];
setInterval(() => { // Notice arrow function here
var y = Math.round(Math.random() * 100);
series.addPoint(this.addPointY, true, true);
}, 3000);
}
Another way you can solve this is by using the that pattern where you capture your this where it points to your class instance and use it wherever you need to refer to your instance:
dynamicInitOptions(title: string, type: string, yAxisTitle: string, series: Object[]) {
if (!type) { type = "line"; }
let that = this; // Capture `this` here
let obj = new Chart({
chart: {
type: type,
events: {
load: function () {
// set up the updating of the chart each second
var series = this.series[0];
setInterval(function () {
var y = Math.round(Math.random() * 100);
series.addPoint(that.addPointY, true, true); // Use `that` instead of `this here
}, 3000);
}
}
}
// ...
});
}