I am trying to follow the vue documentation on ApexCharts website.
https://apexcharts.com/vue-chart-demos/line-charts/realtime/
and the way they structured the component isn't practical. and seem like they have some missing methods in the example.
I wish to create a chart as in the example above that fetches data every second (1 second worth data)
<template>
<VueApexCharts
type="line"
height="350"
width="100%"
:options="chartOptions"
:series="series"
ref="chart"
/>
</template>
<script>
import { ref } from "vue";
import VueApexCharts from "vue3-apexcharts";
import { useStore } from "vuex";
export default {
name: "ChartApex",
components: { VueApexCharts },
props: ["session"],
setup(/* props */) {
let store = useStore();
let chartStream = undefined;
let series = [
{
name: "data1",
data: ref([]),
},
{
name: "data2",
data: [],
},
];
let chartOptions = {
chart: {
// id: "realtime",
width: "100%",
height: 350,
type: "line",
animations: {
enabled: true,
easing: "linear",
dynamicAnimation: {
speed: 1000,
},
},
toolbar: {
show: true,
},
zoom: {
enabled: true,
},
},
dataLabels: {
enabled: true,
},
stroke: {
curve: "straight",
},
title: {
text: "Chart",
align: "left",
},
grid: {
row: {
colors: ["#f3f3f3", "transparent"],
opacity: 0.5,
},
},
xaxis: {
type: "numeric",
show: true,
range: 3,
},
yaxis: {
min: -32768,
show: true,
max: 32768,
},
};
return {
chartOptions,
series,
store,
chartStream,
};
},
watch: {
"session.device.isDataStream"(newVal) {
if (newVal) this.startStream();
else this.endStream();
},
},
methods: {
startStream() {
console.log("starting chart stream");
this.chartStream = setInterval(() => {
// Push data
this.series[0].data.value.push(
...this.store.state.session.samples[0].at(-1)
);
}, 1000);
},
endStream() {
console.log("end chart srtrem");
clearInterval(this.chartStream);
},
},
};
</script>
I wish to display a range of 10 seconds on the X-axis (as in the example above).
When getting a new 1 second worth data: i would like my chart to show extra data and move one second ahead (x-axis) so it always remain as 10 seconds in total.
I am fetching data from my store which keeps updating in chunks. unfortunatly it stacks all in one chart that doesn't move. I wish to display one chunk every second.
I would prefer linking my data directly to the store variable. I am not doing this because i would need to use computed method, and i think it's less efficient (is it?)
the store.state.samples[0] is an array of chunks with around 1 second worth of data in each, structured as apex-charts use: [ts, sample] - looks like this:
// samples[0]
[
// Chunk 1 of almost a second
// [0] =>
[
[ // sample
8.75,
0
],
[ // sample
17.5,
-1
],
[ // etc
26.25,
-2
],
[
35,
-3
],...
],
[ // Chunk 2 of almost a second.
[
958.6875,
-12747
],
[
962.375,
-12808
],
[
966.0625,
-12789
],...
]
]
so that basically i push a chunk worth a second to my chart (..every second)
A lot recommend on apex-charts as a go-to charts library for vue, but seems they lack of proper documentation.
-
Here is link to the source code of the sample. Check there how it was built.
To show the range of 10 seconds use the xaxis.range setting:
const TICKINTERVAL = 1000;
const XAXISRANGE = 10000;
const ARRAYLENGTH = XAXISRANGE / TICKINTERVAL + 1;
xaxis: {
type: 'datetime',
range: XAXISRANGE,
position: 'bottom',
To move the X-Axis you should slice you data array, like the resetData() function does:
function resetData(){
data = data.slice(data.length - 10, data.length);
}
I did it with my ARRAYLENGTH constant
data.push(...new Data...);
if (data.length > ARRAYLENGTH) data = data.slice(-ARRAYLENGTH);
Related
Last few days I've been trying to convert this code to pure JS, but no luck until now...
This code basically starts a new Chart.JS instance when called via jQuery object.
(function ($) {
$.fn.createChartLine = function (labels, datasets, options) {
var settings = $.extend({}, $.fn.createChartLine.defaults, options);
this.each(function () {
let ctx = $(this);
new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: datasets
},
options: {
scales: {
y: {
title: {
display: true,
text: settings.labelString
},
beginAtZero: true
}
},
plugins: {
legend: {
display: settings.display_legend,
position: settings.legend_position,
labels: {
usePointStyle: settings.legend_pointStyle
}
}
}
}
});
});
};
$.fn.createChartLine.defaults = {
display_legend: true,
legend_position: 'top',
legend_pointStyle: true,
labelString: 'Clicks'
};
})(jQuery);
The initialization of a new chartline using the code above:
$("#chart1").createChartLine(['1', '2', '3'], [{
label: 'Clicks',
backgroundColor: Chart.helpers.color('#007bff').alpha(0.75).rgbString(),
borderColor: '#007bff',
data: [34, 56, 28],
borderWidth: 2,
tension: 0.4
}], {display_legend: false});
I tried thousands of ways to remove jQuery from it, but no luck. The intention is to get rid of jQuery in some pages, but this script is essential because with it I can create many Chart.JS instances in same page without needing repeating that amount of code.
I'm aiming to get something like this:
document.getElementById('chart1').createChartLine(...);
Is it possible?
This assumes you are using a version of ChartJS that accepts HTMLCanvasElement and other non-jQuery wrapped Elements to the new Chart() constructor.
You shouldn't extend the prototype of native elements, instead you should create a new function that gets the element(s) passed in.
// Instead of
document.getElementById('chart1').createChartLine(...);
// You'll want
createChartLine(document.getElementById('chart1'), ...);
or something similar.
I'd actually not pass in the element, but rather a selector, since that most closely matches how the jQuery plugin is working.
function createChartLine(selector, labels, datasets, options = {}) {
const settings = Object.assign(
{},
{
display_legend: true,
legend_position: "top",
legend_pointStyle: true,
labelString: "Clicks",
},
options
);
const elements = document.querySelectorAll(selector);
const charts = [];
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const newChart = new Chart(element, {
type: "line",
data: {
labels: labels,
datasets: datasets,
},
options: {
scales: {
y: {
title: {
display: true,
text: settings.labelString,
},
beginAtZero: true,
},
},
plugins: {
legend: {
display: settings.display_legend,
position: settings.legend_position,
labels: {
usePointStyle: settings.legend_pointStyle,
},
},
},
},
});
charts.push(newChart);
}
return charts;
}
Here is how you'd call the function.
createChartLine(
"#chart1", // This is the new argument, the selector of the element you are initializing
["1", "2", "3"],
[
{
label: "Clicks",
backgroundColor: Chart.helpers.color("#007bff").alpha(0.75).rgbString(),
borderColor: "#007bff",
data: [34, 56, 28],
borderWidth: 2,
tension: 0.4,
},
],
{ display_legend: false }
);
I am currently trying to build a barchart with ApexCharts. Everything works fine when the data-array has more than one entry. Somehow, whenever data only contains one element, the diagram messes up the width of the bar.
Correct state with multiple entries:
Buggy state with one entry
My goal is to display a single value with the same xaxis scale and the same barwitdh as the barchart displays multiple values.
I already tried using an array of timestamps for each hour as catigories-prop but this didn't work for me.
I use 'dateime' as type for my xaxis.
Data is an array that can contain 0, 1 or x entries. It is given to the Barchart-Component as data-prop.
Categories contains a the timestamp to each entry of data. Therefore it can also have 0, 1, or x values. Categories is used as the Barcharts categories-prop.
Here are my configurations (For the buggy state):
var options = {
series: [
{
name: "Servings",
data: [[1669082400000, 44]]
}
],
chart: {
height: 350,
type: "bar",
animations: {
enabled: false
},
zoom: {
enabled: false
}
},
dataLabels: {
enabled: false
},
xaxis: {
type: 'datetime',
labels: {
datetimeUTC: false,
datetimeFormatter: {
year: 'yyyy',
month: 'MMM',
day: 'dd',
hour: 'HH:mm'
}
},
categories: [
1669082400000,
],
min: 1669075200000,
max: 1669161600000
},
tooltip: {
intersect: false,
shared: true
},
yaxis: {
reversed: false,
}
};
var chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
Would be nice if somebody could help me out, I'm tripping over this one.
I currently have a chart that shows real-time information. I tried to disable the animation for the y-axis, because the dots hopped around along the y-axis which creates a weird effect. But I still want that new dots fade in smoothly along the x-axis.
I tried it with this configuration:
const chartOptions: ChartOptions = {
animations: {
x: {
duration: 500
},
y: false,
},
// ...
};
The result is no animation at all. Not on the y-axis, but also not on the x-axis. It doesn't look smooth anymore.
And after 25 data points I shift()/push(newDataPoint) in a separate array and then replace the whole data array for the chart as I use ChartJS with the vue-chartjs library.
I need the exact same behavior like in the GIF above, except that it should not stutter but scrolling smooth along the x-axis.
Whole vue-chartjs example for reference
<script setup lang="ts">
const chartData = ref<ChartData<'line'>>({
labels: [],
datasets: []
})
const chartLabels: string[] = Array(maxDataPoints).fill('');
const chartDataPoints: number[] = Array(maxDataPoints).fill(18.3);
function fillData() {
if (chartDataPoints.length > maxDataPoints) {
chartLabels.shift();
chartDataPoints.shift();
}
chartLabels.push(new Date().toLocaleString())
chartDataPoints.push(Number((currentDistance.value * 0.1).toFixed(1)))
const updatedChartData: ChartData<'line'> = {
labels: [...chartLabels],
datasets: [
{
label: 'Distance',
tension: 0.5,
data: [...chartDataPoints],
}
]
}
chartData.value = { ...updatedChartData }
}
onMounted(() => {
fillData();
setInterval(() => fillData(), 500)
})
const chartOptions: ChartOptions = {
responsive: true,
maintainAspectRatio: false,
//animation: false,
animations: {
x: {
duration: 500
},
y: false,
},
scales:{
x: {
display: false,
},
y: {
suggestedMin: 0,
suggestedMax: 20,
}
},
plugins: {
legend: {
display: false
},
},
}
</script>
<template>
<LineChart :chartData="chartData" :chartOptions="chartOptions" />
</template>
In the end I used the chartjs-streaming-plugin by nagix which does exactly what I was looking for.
I am a bit out of my comfort zone, since I normally do analytics and not fancy front-ends. However, I would like to have a real-time demo of some of my work, so it becomes easier to understand and not just numbers in a matrix. I have looked around and found something semi-relevant and come this far:
(It has four series like I want to and it iterates - to some degree)
https://jsfiddle.net/023sre9r/
var series1 = this.series[0],
series2 = this.series[1],
series3 = this.series[2],
series4 = this.series[3];
But I am totally lost on how to remove the random number generators without loosing nice things like the number of data points in a view (seems to depend on the for loop?!). Remove the extra title "Values" right next to my real y-axis title. And of cause how to get a new data point from a XML-file every second.
Ideally I want to have an XML-file containing 4 values, which I update approximately every 200ms in MATLAB. And every second I would like my 4 series chart to update. Is it not relatively easy, if you know what you are doing?! :-)
Thanks in advance!
I simplified your example and added clear code showing how to fetch data from server and append it to your chart using series.addPoint method. Also if you want to use XML, just convert it to JS object / JSON.
const randomData = () => [...Array(12)]
.map((u, i) => [new Date().getTime() + i * 1000, Math.random()])
Highcharts.chart('container', {
chart: {
renderTo: 'container',
type: 'spline',
backgroundColor: null,
animation: Highcharts.svg, // don't animate in old IE
marginRight: 10,
events: {
load () {
const chart = this
setInterval(() => {
// Fetch example below (working example: https://github.com/stpoa/live-btc-chart/blob/master/app.js)
// window.fetch('https://api.cryptonator.com/api/ticker/btc-usd').then((response) => {
// return response.json()
// }).then((data) => {
// chart.series[0].addPoint({ x: data.timestamp * 1000, y: Number(data.ticker.price) })
// })
chart.series.forEach((series) => series.addPoint([new Date().getTime(), Math.random()], true, true))
}, 3000)
}
}
},
title: {
text: null
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150
},
yAxis: [{
title: {
text: 'Temperature [°C]',
margin: 30
},
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, 4);
}
},
legend: {
enabled: true
},
exporting: {
enabled: false
},
rangeSelector: {
enabled: false
},
navigator: {
enabled: false
},
scrollbar: {
enabled: false
},
series: [{
name: 'Setpoint',
data: randomData()
}, {
name: 'Return',
data: randomData()
}, {
name: 'Supply',
data: randomData()
}, {
name: 'Output',
data: randomData()
}]
})
Live example: https://jsfiddle.net/9gw4ttnt/
Working one with external data source: https://jsfiddle.net/111u7nxs/
Here's what I'm trying to do:
Use papa parse to parse a CSV file.
Create two JS Objects(ohlc and volume) with that parsed data.
Then that data is used to create a highstocks chart.
Parsing with papa parse example:
function doStuff(data) {
//do stuff here
console.log(data);
}
function parseData(url, callBack) {
Papa.parse(url, {
download: true,
dynamicTyping: true,
complete: function(results) {
callBack(results.data);
}
});
}
parseData("https://www.quandl.com/api/v3/datasets/WIKI/AAPL.csv", doStuff);
A working Highchart example: jsfiddle
Me trying to combine the top two examples: jsfiddle
$(function () {
var ohlc = [],
volume = [],
dataLength = data.length,
// set the allowed units for data grouping
groupingUnits = [[
'week', // unit name
[1] // allowed multiples
], [
'month',
[1, 2, 3, 4, 6]
]],
i = 1;
function parseData(url, callBack) {
Papa.parse(url, {
download: true,
dynamicTyping: true,
complete: function(results) {
callBack(results.data);
}
});
}
function setObjects(data) {
console.log(data[i][0]);
for (i; i < dataLength; i += 1) {
ohlc.push([
data[i][0], // the date
data[i][1], // open
data[i][2], // high
data[i][3], // low
data[i][4] // close
]);
volume.push([
data[i][0], // the date
data[i][5] // the volume
]);
}
}
parseData("https://www.quandl.com/api/v3/datasets/WIKI/AAPL.csv", setObjects);
// create the chart
$('#container').highcharts('StockChart', {
rangeSelector: {
selected: 1
},
title: {
text: 'AAPL Historical'
},
yAxis: [{
labels: {
align: 'right',
x: -3
},
title: {
text: 'OHLC'
},
height: '60%',
lineWidth: 2
}, {
labels: {
align: 'right',
x: -3
},
title: {
text: 'Volume'
},
top: '65%',
height: '35%',
offset: 0,
lineWidth: 2
}],
series: [{
type: 'candlestick',
name: 'AAPL',
data: ohlc,
dataGrouping: {
units: groupingUnits
}
}, {
type: 'column',
name: 'Volume',
data: volume,
yAxis: 1,
dataGrouping: {
units: groupingUnits
}
}]
});
});
Can anyone help me out with what I am doing wrong? I know two things that haven't been done. The data needs to be reverse so that it is in ascending order by date. And the date needs to be converted to milliseconds. However it would help me to get the current data at least inserting to the objects first and then go from there.
This bit
var i = 1;
dataLength = data.length;
Should be in the first lines of the setObjects function, where data is present and the value dataLength is actually used.