uplot is used to dislay timeseries data from VictoriaMetrics database.
For the backend Node-Red is used to forward and recieve the query with node-red-contrib-uibuilder.
It works basically and is very fast.
The problem is, when I try to zoom into the uplot graph, my browser (Chrome, Firefox, Edge) freezes. It seems to run out of memory.
Here are parts of my code, using svelte.
<script>
import { onMount } from "svelte";
import { query } from "../lib/uibStore";
import { transformToUplot } from "../lib/helper";
// import uPlot from "uplot";
import Browsebar from "../components/Browsebar.svelte";
import TimerangeSelect from "../components/TimerangeSelect.svelte";
let uplotdiv; //
let opts = {
title: "Temperaturen1",
id: "chart1",
class: "my-chart",
width: 1000,
height: 600,
series: [
{},
{
show: true, // initial toggled state (optional)
spanGaps: true,
label: "RT",
stroke: "red", // series style
scale: "Temperature",
value: (self, rawValue) => rawValue.toFixed(1) + "°C",
},
{
show: true,
spanGaps: true,
label: "KT",
stroke: "green",
scale: "Temperature",
value: (self, rawValue) => rawValue.toFixed(1) + "°C",
},
{
show: true,
spanGaps: true,
label: "VT",
stroke: "blue",
scale: "Temperature",
value: (self, rawValue) => rawValue.toFixed(1) + "°C",
},
],
scales: {
x: { time: true },
Temperature: {
auto: true,
// range: [-10, 20],
side: 3,
},
},
axes: [
{},
{
scale: "Temperature",
values: (self, ticks) => ticks.map((rawValue) => rawValue.toFixed(1) + "°C"),
},
],
cursor: { drag: { x: true, y: true } },
};
let plot; // = new uPlot(opts);
let uPlot;
let d = [[0], [0], [0], [0]];
let resolved = false;
$: uisend($query); //use uibilder.send, if query changes which occurs when timerange or nav index changes
//send a victoriametrics query to the backend, _q is part of query
function uisend(_q) {
// Example 'uvr_prozess_celsius{ort="1"}&start=-3d&step=60s'
uibuilder.send({ topic: "getVMetrics", payload: _q });
}
onMount(async () => {
uisend($query);
const uplotModule = await import("https://unpkg.com/uplot#1.6.22/dist/uPlot.esm.js");
uPlot = uplotModule.default;
plot = new uPlot(opts, [[0], [0], [0], [0]], uplotdiv);
});
uibuilder.onChange("msg", function (msg) {
// load Metrics via Node-Red's uibuilder, serverside
if (msg.topic === "getVMetrics") {
resolved = true;
if (msg.payload.data.result.length > 0) {
d = transformToUplot(msg.payload.data);
plot.setData(d);
}
}
});
</script>
<svelte:head>
<link rel="stylesheet" href="https://unpkg.com/uplot#1.6.22/dist/uPlot.min.css" />
</svelte:head>
<Browsebar>
<TimerangeSelect />
</Browsebar>
<hr />
<div bind:this={uplotdiv} />
{#if resolved}
<code>{$query}</code>
{:else}
<h4>lade Metriken... {$query}</h4>
{/if}
<hr />
Has anyone experienced freezing with uplot? What did you do?
Lucky me, I found the problem. It had to do with the way I transformed the victoriametrics data. On every timestamp I did Number(timestamp).toFixed(0). Without toFixed(0) it is working now. :)
//transform raw data from metrics query to the uplot format
export function transformToUplot(dt) {
let udata = []; //2d data array, conforming uPlot
let tsd = []; //timestamp array
//from first result take only the timestamps
for (let t of dt.result[0].values) {
// tsd.push(Number(t[0]).toFixed(0)); //this was bad!!!!, it lead to freezing
tsd.push(Number(t[0]));
}
udata.push(tsd);
//then the values
for (let r of dt.result) {
let sd = [];
for (let d of r.values) {
let val = Number(d[1]);
sd.push(val);
}
udata.push(sd);
}
return udata;
}
Thanks for your interest!
Related
I have a typescript extension that needs to run a python file. after a file dialog is opened the extension uses exec to run the following command.
python C:\\Users\\schriste\\.vscode\\extensions\\consolidatedExtension\\src\\testingFile.py C:\\EquipVsCode\\5G_ServingCell_Rsrp_Rsrq_Sinr_RSSI_B974_all.isf
I thought exec would work since ive tired exec using the windows command dir and it worked just fine.
all I need is the right function to run the command shown above. has anyone done this? it seems like such a simple and common task but im having so much difficulty finding information on how this can be done.
below is the code from extension.ts
perhaps someone has information on how the following work for what I am trying to accomplish but I have not found a way for these to work as of right now: execFile spawn
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import { open } from 'fs';
import * as path from 'path';
import { json } from 'stream/consumers';
const { ChildProcess, exec } = require('child_process');
const { execFile } = require('node:child_process');
const { spawn } = require('node:child_process');
const { stdout, stderr } = require('process');
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {
//File Dialog
let disposable = vscode.commands.registerCommand('consolidatedExtension.openFile', function () {
const options = {
canSelectMany: false,
openLabel: 'Open'
};
vscode.window.showOpenDialog(options).then(fileUri => {
if (fileUri && fileUri[0]) {
console.log('Selected file: ' + fileUri[0].fsPath);
let cmd = `python C:\\Users\\schriste\\.vscode\\extensions\\consolidatedExtension\\src\\testingFile.py` + ` ` + fileUri[0].fsPath
exec(cmd2, (...args: any[])=>{
console.log(args);
});
}
});
});
//open graphView
let openWebview = vscode.commands.registerCommand('consolidatedExtension.openWebview', () => {
const panel = vscode.window.createWebviewPanel(
'openWebview', // Identifies the type of the webview. Used internally
'Example Page', // Title of the panel displayed to the user
vscode.ViewColumn.One, // Editor column to show the new webview panel in.
{ // Enable scripts in the webview
enableScripts: true //Set this to true if you want to enable Javascript.
});
//get path to json on disk
const onDiskPath = vscode.Uri.file(
path.join(context.extensionPath, 'src', 'data.json')
);
//get vegaLite js on disk
const JsonDiskPath = vscode.Uri.file(
path.join(context.extensionPath, 'src', 'vegaLiteGraphs.js')
);
//and get the special URI to use with the webview
const jsonSRC = panel.webview.asWebviewUri(onDiskPath);
const jsSRC = panel.webview.asWebviewUri(JsonDiskPath);
panel.webview.html = getWebviewContent(jsonSRC, jsSRC);
});
context.subscriptions.push(openWebview);
context.subscriptions.push(disposable);
}
// this method is called when your extension is deactivated
export function deactivate() {}
function getWebviewContent(jsonSRC:vscode.Uri, jsSRC:vscode.Uri) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/vega#5.21.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite#5.2.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed#6.20.2"></script>
</head>
<body>
<script type = "module">
import users from '${jsonSRC}' assert {type: 'json'};
const graphDataContainer = []
let dataPoints = []
for(let i = 0; i < users.length; i += 2)
{
for (let j = 1; j < users[i].length; j++) {
dataPoints.push({
x: users[i][j],
y: users[i + 1][j]
});
}
graphDataContainer.push(dataPoints)
dataPoints = []
}
console.log(graphDataContainer)
window.onload = function() {
let i = 0
var chart1 = new CanvasJS.Chart("chart1", {
animationEnabled: true,
zoomEnabled: true,
theme: "light2",
"title": { "text": "RSRP" },
axisX: {
gridThickness: 0,
tickLength: 0,
lineThickness: 0,
labelFormatter: function(){ return " "; },
crosshair: {
enabled: true,
color: "black",
labelFontColor: "#F8F8F8"
},
//interval: 10,
//intervalType: "millisecond",
//valueFormatString: "##.##.####"
},
axisY: {
crosshair: {
enabled: true,
color: "black",
labelFontColor: "#F8F8F8"
},
title: "Serving Cell RSRP (dBm)",
minimum: -100,
maximum: 0,
interval: 16.67
},
data: [{
type: "line",
//yValueFormatString: "#,### Units",
dataPoints: graphDataContainer[i]
}],
rangeChanged: syncHandler
});
chart1.render();
i++
var rsrqChart = new CanvasJS.Chart("chart2", {
animationEnabled: true,
zoomEnabled: true,
theme: "light2",
axisX: {
valueFormatString:"##:##:##.###",
crosshair: {
enabled: true,
color: "black",
labelFontColor: "#F8F8F8"
},
},
axisY: {
crosshair: {
enabled: true,
color: "black",
labelFontColor: "#F8F8F8"
},
title: "Serving Cell RSRQ",
minimum: -15,
interval: 2.5
},
data: [{
type: "line",
dataPoints: graphDataContainer[i]
}],
rangeChanged: syncHandler
});
rsrqChart.render();
// var chart3 = new CanvasJS.Chart("chartContainer3", {
// animationEnabled: true,
// theme: "light2",
// data: [{
// type: "line",
// dataPoints: sinrPoints
// }]
// });
// chart3.render();
var charts = [chart1, rsrqChart];
function syncHandler(e){
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
if (!chart.options.axisX)
chart.options.axisX = {};
if (!chart.options.axisY)
chart.options.axisY = {};
if (e.trigger === "reset") {
chart.options.axisX.viewportMinimum = chart.options.axisX.viewportMaximum = null;
chart.options.axisY.viewportMinimum = chart.options.axisY.viewportMaximum = null;
chart.render();
} else if (chart !== e.chart) {
chart.options.axisX.viewportMinimum = e.axisX[0].viewportMinimum;
chart.options.axisX.viewportMaximum = e.axisX[0].viewportMaximum;
chart.options.axisY.viewportMinimum = e.axisY[0].viewportMinimum;
chart.options.axisY.viewportMaximum = e.axisY[0].viewportMaximum;
chart.render();
}
}
}
}
</script>
<script type = "module" src = "${jsSRC}"></script>
<div id="chart1" style="height: 370px; width: 100%;"></div>
<div id="chart2" style="height: 370px; width: 100%;"></div>
<div id="vis"></div>
<!-- <div id="chartContainer3" style="height: 370px; width: 100%;"></div> -->
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"> </script>
</body>
</html>
`;
}
hi i want charting stock data in web page.
i found sample project in github
https://github.com/tvjsx/tvjs-xp
i change code and connect the binance and receive and charting real time data.
i have some problem after add online receive data chart lagend bouttun not work and i cant add layer.
please help me.
thanks
<trading-vue :data="dc" :width="this.width" :height="this.height"
title-txt="TVJS XP" :key="resetkey"
:chart-config="{DEFAULT_LEN:70}"
ref="tvjs"
:legend-buttons="['display', 'settings', 'up', 'down', 'add', 'remove']"
:toolbar="true"
:index-based="index_based"
:color-back="colors.colorBack"
:color-grid="colors.colorGrid"
:color-text="colors.colorText"
:extensions="ext"
:overlays="ovs"
:x-settings="xsett">
</trading-vue>
<span class="gc-mode">
<input type="checkbox" v-model="index_based">
<label>Index Based</label>
</span>
export default {
name: 'DataHelper',
icon: '⚡',
description: 'Real-time updates. Play with DataCube in the console',
props: ['night', 'ext', 'resetkey'],
components: {
TradingVue
},
mounted() {
window.addEventListener('resize', this.onResize)
this.onResize()
// Load the last data chunk & init DataCube:
let now = Utils.now()
this.load_chunk([now - Const.HOUR4, now]).then(data => {
this.dc = new DataCube({
ohlcv: data['dc.data'],
// onchart: [{
// type: 'EMAx6',
// name: 'Multiple EMA',
// data: []
// }],
offchart: [
// {
// type: 'BuySellBalance',
// name: 'Buy/Sell Balance, $lookback',
// data: [],
// settings: {}
// },
{
name: "RSI, 20",
type: "Range",
data: [],
settings: {
"upper": 70,
"lower": 30,
"backColor": "#9b9ba316",
"bandColor": "#666"
}
},
],
datasets: [{
type: 'Trades',
id: 'binance-btcusdt',
data: []
}]
}, { aggregation: 100 })
// Register onrange callback & And a stream of trades
this.dc.onrange(this.load_chunk)
this.$refs.tvjs.resetChart()
this.stream = new Stream(WSS)
this.stream.ontrades = this.on_trades
window.dc = this.dc // Debug
window.tv = this.$refs.tvjs // Debug
})
},
methods: {
onResize(event) {
this.width = window.innerWidth
this.height = window.innerHeight - 50
},
// New data handler. Should return Promise, or
// use callback: load_chunk(range, tf, callback)
async load_chunk(range) {
let [t1, t2] = range
let x = 'BTCUSDT'
let q = `${x}&interval=1m&startTime=${t1}&endTime=${t2}`
let r = await fetch(URL + q).then(r => r.json())
return this.format(this.parse_binance(r))
},
// Parse a specific exchange format
parse_binance(data) {
if (!Array.isArray(data)) return []
return data.map(x => {
for (var i = 0; i < x.length; i++) {
x[i] = parseFloat(x[i])
}
return x.slice(0,6)
})
},
format(data) {
// Each query sets data to a corresponding overlay
return {
'dc.data': data
// other onchart/offchart overlays can be added here,
// but we are using Script Engine to calculate some:
// see EMAx6 & BuySellBalance
}
},
on_trades(trade) {
this.dc.update({
t: trade.T, // Exchange time (optional)
price: parseFloat(trade.p), // Trade price
volume: parseFloat(trade.q), // Trade amount
'datasets.binance-btcusdt': [ // Update dataset
trade.T,
trade.m ? 0 : 1, // Sell or Buy
parseFloat(trade.q),
parseFloat(trade.p)
],
// ... other onchart/offchart updates
})
}
},
beforeDestroy() {
window.removeEventListener('resize', this.onResize)
if (this.stream) this.stream.off()
},
computed: {
colors() {
return this.$props.night ? {} : {
colorBack: '#fff',
colorGrid: '#eee',
colorText: '#333'
}
},
},
data() {
return {
dc: {},
width: window.innerWidth,
height: window.innerHeight,
index_based: false,
xsett: {
'grid-resize': { min_height: 30 }
},
ovs: Object.values(Overlays),
}
}
}
I have two issues when i'm trying to update my ChartJS values / datasets, the first is when a new dataset is added to chart datasets it's placed far from other bars (this happens when there are other datasets with zero values in it) in same dataset, another issue is that the data is not updated for same dataset when it's y value is chaged.
Here is what i've tried:
let API = [{
"totpag": 6.5,
"descrpag": "CONTANTI",
"data": "2022-02-15T10:00:00"
},
{
"totpag": 5.5,
"descrpag": "POS MANUALE",
"data": "2022-02-15T10:00:00"
},
{
"totpag": 25,
"descrpag": "ASSEGNI",
"data": "2022-02-15T10:00:00"
}
]
const optionsPagamentiBar = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: 0,
usePointStyle: true,
callbacks: {
label: function(context) {
let y = context.parsed.y;
if (y) {
return context.dataset.label + ": " + "€" + context.parsed.y.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,').replace(/[,.]/g, m => (m === ',' ? '.' : ','));
}
}
}
}
},
scales: {
y: {
ticks: {
display: true,
beginAtZero: true,
callback: function(value, index, values) {
return "€" + value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
},
grid: {
drawBorder: false,
zeroLineColor: "transparent",
}
},
x: {
display: 1,
ticks: {
padding: 10,
display: true,
fontSize: 10
},
grid: {
display: false
}
}
}
}
const chartBarPayments = new Chart(document.getElementById("chartBarPayments").getContext('2d'), {
type: 'bar',
data: {
labels: [],
datasets: [{
data: [],
}]
},
options: optionsPagamentiBar
});
let periodo = 'giorno'
function getColorsByLabels(labels) {
//const colorScale = d3.interpolateSinebow;
//const colorRangeInfo = {
// colorStart: 0.2,
// colorEnd: 1,
// useEndAsStart: true,
//};
//let COLORS = interpolateColors(labels.length, colorScale, colorRangeInfo);
let COLORS = ["rgb(167, 3, 213)", "rgb(255, 64, 64)", "rgb(24, 114, 244)", "rgb(34, 0, 97)"]
let backgroundColors = {};
COLORS.forEach((color, index) => {
backgroundColors[labels[index]] = color;
})
return backgroundColors;
}
function pagamentiPerFascia(pagamenti) {
let datasets = [];
let timePeriods = [];
let datasetLabels = [];
let indexedData = {};
pagamenti.forEach((pagamento, i) => {
let date = "";
if (periodo == "anno") {
date = moment(pagamento.data).format("MMM YYYY");
} else if (periodo == "mese") {
date = moment(pagamento.data).format("DD MMM");
} else {
date = moment(pagamento.data).format('HH:mm');
}
if (!timePeriods.includes(date)) {
timePeriods.push(date);
}
if (!datasetLabels.includes(pagamento.descrpag)) {
datasetLabels.push(pagamento.descrpag);
}
indexedData[pagamento.descrpag + date] = pagamento.totpag;
})
let backgroundColors = getColorsByLabels(datasetLabels);
datasetLabels.forEach(label => {
const dataset = {
label,
backgroundColor: backgroundColors[label],
data: [],
skipNull: true,
};
timePeriods.forEach(date => {
dataset.data.push({
x: date,
y: indexedData[label + date] || null
})
})
datasets.push(dataset);
})
updateChart(datasets)
}
function updateChart(datasets) {
chartBarPayments.data = {};
datasets.forEach((dataset) => {
// checking if dataset exists in chart
let chartdataset = chartBarPayments.data.datasets.find((data) => data.label === dataset.label);
if (chartdataset) {
dataset.data.forEach((date) => {
// checking if x value exists in chart dataset
let chartdata = chartdataset.data.find((data) => data.x === date.x);
if (chartdata) {
console.log("if", date.y)
// if x value exists i''m setting it with the new value (even if it's the same)
chartdata.y = date.y || null;
chartBarPayments.update();
} else {
// else pushing a new xy value to data
console.log("else", date.y)
chartdataset.data.push({
x: date.x,
y: date.y || null
})
chartBarPayments.update();
}
})
} else {
// if the dataset does not exists i'm pushing it as a new one
chartBarPayments.data.datasets.push(dataset);
chartBarPayments.update();
}
});
}
pagamentiPerFascia(API)
var longPolling = setInterval(() => {
API[0].totpag = 15;
API.push({
"totpag": 16.5,
"descrpag": "VISA",
"data": "2022-02-15T10:00:00"
});
API.push({
"totpag": 16.5,
"descrpag": "VISA",
"data": "2022-02-15T11:00:00"
});
pagamentiPerFascia(API)
}, 5000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.7.0/dist/chart.min.js"></script>
<canvas id="chartBarPayments"></canvas>
How can i update the values and is there a way to hide zero values from the chart?
UPDATE:
Solved the issue with spacing between bars by setting null instead of 0 in empty bars and added skipNull to each dataset.
UPDATE 2:
Solved even the issue with value update by replacing
chartData = date;
with
chartData.y = date.y;
The main issue here is that i have to clear the .data every time which cause animation stuttering even when there is no new data, clearing data is needed as the dataset can change any time with all new Y labels.
I ran into a similar situation and I ended up disabling the animation, which obviously solved the problem, but because it looked a bit less dazzling the customer did not like the solution, in the end I had to utilise a hack.
Not sure if this will work for you, but in my case it did, you do this by using setTimeout to 1 millisecond.
Run the update & then set a timeout for a millisecond & run the update again. If this doesn't work, try setting arbitrary data (instead of emptying the data) & then updating to the correct dataset a millisecond later.
In my instance:
refreshScope( endscope ) {
if(this.current_scope == 'today') {
this.current_scope = `week`; // Opposite (changes data on watcher)
} else {
this.current_scope = `today`; // Opposite again.
}
setTimeout(() => {
this.current_scope = `${endscope}`;
}, 1);
}
There are of course other (better) options you can employ, as outlined by ChartJS' performance page:
https://www.chartjs.org/docs/3.1.1/general/performance.html
And ultimately disabling animation will make it seem accurate - not stutter, albeit less dazzling, and not require hacks.
Disabling animation:
new Chart(ctx, {
type: 'line',
data: data,
options: {
animation: false
}
});
I am trying to hide the legend of my chart created with Chart.js.
According to the official documentation (https://www.chartjs.org/docs/latest/configuration/legend.html), to hide the legend, the display property of the options.display object must be set to false.
I have tried to do it in the following way:
const options = {
legend: {
display: false,
}
};
But it doesn't work, my legend is still there. I even tried this other way, but unfortunately, without success.
const options = {
legend: {
display: false,
labels: {
display: false
}
}
}
};
This is my full code.
import React, { useEffect, useState } from 'react';
import { Line } from "react-chartjs-2";
import numeral from 'numeral';
const options = {
legend: {
display: false,
},
elements: {
point: {
radius: 1,
},
},
maintainAspectRatio: false,
tooltips: {
mode: "index",
intersect: false,
callbacks: {
label: function (tooltipItem, data) {
return numeral(tooltipItem.value).format("+0,000");
},
},
},
scales: {
xAxes: [
{
type: "time",
time: {
format: "DD/MM/YY",
tooltipFormat: "ll",
},
},
],
yAxes: [
{
gridLines: {
display: false,
},
ticks: {
callback: function(value, index, values) {
return numeral(value).format("0a");
},
},
},
],
},
};
const buildChartData = (data, casesType = "cases") => {
let chartData = [];
let lastDataPoint;
for(let date in data.cases) {
if (lastDataPoint) {
let newDataPoint = {
x: date,
y: data[casesType][date] - lastDataPoint
}
chartData.push(newDataPoint);
}
lastDataPoint = data[casesType][date];
}
return chartData;
};
function LineGraph({ casesType }) {
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async() => {
await fetch("https://disease.sh/v3/covid-19/historical/all?lastdays=120")
.then ((response) => {
return response.json();
})
.then((data) => {
let chartData = buildChartData(data, casesType);
setData(chartData);
});
};
fetchData();
}, [casesType]);
return (
<div>
{data?.length > 0 && (
<Line
data={{
datasets: [
{
backgroundColor: "rgba(204, 16, 52, 0.5)",
borderColor: "#CC1034",
data: data
},
],
}}
options={options}
/>
)}
</div>
);
}
export default LineGraph;
Could someone help me? Thank you in advance!
PD: Maybe is useful to try to find a solution, but I get 'undefined' in the text of my legend and when I try to change the text like this, the text legend still appearing as 'Undefindex'.
const options = {
legend: {
display: true,
text: 'Hello!'
}
};
As described in the documentation you linked the namespace where the legend is configured is: options.plugins.legend, if you put it there it will work:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'pink'
}
]
},
options: {
plugins: {
legend: {
display: false
}
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.js"></script>
</body>
On another note, a big part of your options object is wrong, its in V2 syntax while you are using v3, please take a look at the migration guide
Reason why you get undefined as text in your legend is, is because you dont supply any label argument in your dataset.
in the newest versions this code works fine
const options = {
plugins: {
legend: {
display: false,
},
},
};
return <Doughnut data={data} options={options} />;
Import your options value inside the charts component like so:
const options = {
legend: {
display: false
}
};
<Line data={data} options={options} />
I'm trying to show weight_id retrieved from mysql data in a chart.js tooltip (shown as (weight_ids[index]) in the image). And later, I intend to show a modal instead of a tooltip to let users update or delete that data. I presume I cannot achieve that without linking the linechart's point data with id stored in mysql. How can I incorporate this id data?
I would appreciate any help very much.
enter image description here
My code is as follows:
<canvas id="myChart"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.4/dist/Chart.min.js"></script>
{{-- グラフを描画--}}
<script>
//ラベル
const labels = #json($date_labels);
// id
const weight_ids = #json($weight_ids);
//体重ログ
const weight_logs = #json($weight_logs);
const aryMax = function(a, b) {
return Math.max(a, b);
};
const aryMin = function(a, b) {
return Math.min(a, b);
};
let min_label = Math.floor((weight_logs).reduce(aryMin) - 0.5);
let max_label = Math.ceil((weight_logs).reduce(aryMax) + 0.5);
console.log(weight_ids);
console.log(weight_logs);
console.log(min_label, max_label);
//グラフを描画
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'line',
data : {
labels: labels, // x軸ラベル
datasets: [
{
label: `Weight (weight_ids[index])`,
data: weight_logs,
tension: 0,
borderColor: "rgba(37,78,255,1)",
backgroundColor: "rgba(0,0,0,0)",
pointRadius: 3
}
]
},
options: {
title: {
display: false,
text: ''
},
legend: {
display: false,
},
scales: {
yAxes: [
{
ticks: {
min: min_label, // ラベル最小値
max: max_label, // ラベル最大値
},
scaleLabel: {
display: true,
fontSize: 16,
labelString: '体重 (kg)'
}
}
],
},
hover: {
mode: 'point'
},
onClick: function clickHandler(evt) {
var firstPoint = myChart.getElementAtEvent(evt)[0];
if (firstPoint) {
var label = myChart.data.labels[firstPoint._index];
var value = myChart.data.datasets[firstPoint._datasetIndex].data[firstPoint._index];
console.log(label);
console.log(value);
if (value) {
$('#weidhtModal').modal('show');
}
}
}
}
});
</script>
Thank you!
I found a way to retrieve weight_id using the following function.
onClick: function clickHandler(evt, activeElements) {
if (activeElements.length) {
var element = this.getElementAtEvent(evt);
var index = element[0]._index;
var _datasetIndex = element[0]._datasetIndex;
var weightId = weight_ids[index];
var weightLog = weight_logs[index];
console.log(index);
console.log(weightId);
console.log(this.data.labels[index]);
console.log(weightLog);
}
}