**When i drawChartData the yAxis of chartjs is not displayed. What is the problem, can you help me **
Before
After
there is my script
<script lang="ts">
import { Vue, Component } from "nuxt-property-decorator";
import BarChartComponent from "#/components/chart/BarChartComponent";
import LineChartComponent from "#/components/chart/LineChartComponent";
import moment from "moment"
import traffic from '#/modules/traffic'
import { TrafficCarrierSummary, TrafficCarrierDetail } from "#/type/traffic-carrier-summary";
const FONT_COLOR = "rgba(255, 255, 255, 1)";
const GRID_LINES_SETTING = {
display: true,
drawOnChartArea: true,
color: "rgba(255, 255, 255, .5)",
zeroLineColor: "rgba(255, 255, 255, 1)"
};
#Component({
components: {
BarChartComponent,
LineChartComponent
},
asyncData: async () => {
let searchObject = {
from: moment().subtract(1, 'months').set('date', 1).format('Y-M-D'),
to: moment().subtract(1, 'months').endOf('month').format('Y-M-D'),
}
const response = await traffic.package(searchObject)
return {
searchObject: searchObject,
packageData: response.data,
}
}
})
export default class extends Vue {
width: number = 575
packageData!: Array<TrafficCarrierSummary>
packageDaliy: TrafficCarrierDetail | null = null
selectedPackage: string | null = null
isDayPackage: boolean = false
selectedDaliyDate: any
loading: boolean = false
lineColors = [
'rgba(120, 0, 0,1)',
'rgba(176, 0, 10,1)',
'rgba(234, 57, 51,1)',
'rgba(251,184, 43,1)',
'rgba(255,234, 97,1)',
'rgba( 92, 98, 91,1)',
'rgba( 35, 37, 35,1)',
]
searchObject = {
from: null,
to: null,
} as any
chartDataPackage: any = {};
chartDataCount: any = {};
chartPackageOptions: Chart.ChartOptions = {
responsive: true,
maintainAspectRatio: false,
title: {
display: false,
},
scales: {
yAxes: [
{
scaleLabel: {
display: true,
labelString: 'Data [GB]',
fontColor: "red",
fontSize: 16
},
ticks: {
suggestedMax: 1000,
suggestedMin: 0,
stepSize: 100,
callback: function(value, index, values) {
return value + "GB";
}
}
}
]
}
};
created() {
this.searchObject.from = new Date(moment().subtract(1, 'months').set('date', 1).toDate())
this.searchObject.to = new Date(moment().subtract(0, 'months').endOf('month').toDate())
this.selectedDaliyDate = this.searchObject.from
this.drawChartPackage()
}
async search() {
this.loading = true
let from = moment(this.searchObject.from).format('Y-MM-DD')
let to = moment(this.searchObject.to).format('Y-MM-DD')
const response = await traffic.package({
from: from,
to: to
})
this.packageData = response.data
this.drawChartPackage()
this.loading = false
}
drawChartPackage() {
let dataArr: Array<any> = []
let labels: Array<string> = []
let obj: any = {}
this.packageData.forEach(element => {
if(!labels.includes(element.target_date)){
labels.push(element.target_date)
}
if(!obj[element.package]){
obj[element.package] = []
}
obj[element.package].push(Number(element.data_total) / 1024 / 1024 / 1024)
})
let index = 0
Object.keys(obj).map(key => {
dataArr.push({
label: key,
data: obj[key],
borderColor: this.lineColors[index],
backgroundColor: 'rgba(0,0,0,0)',
type: 'line'
})
index++
})
this.chartDataPackage = {
labels: labels,
datasets: dataArr
}
}
}
</script>
Related
I will attempt to explain my issue as clearly as possible while also avoid making this topic too long. I recently found the Chart.js library, which is excellent for what I need. Now, since I am using Node.js and need a png of the graph, I am utilizing the chartjs-node-canvas library. Having this information in mind, I will try to split my topic into multiple sections for a clearer understanding.
Ultimate Goal
Before getting into the problem itself, I would like to discuss my ultimate goal. This is to give a general idea on what I'm trying to do so the responses are fitted accordingly. To keep this short, I have data in the form of {awardedDate: "2022-06-22T12:21:17.22Z", badgeId: 1234567}, with awardedDate being a timestamp of when the badge was awarded, and the badgeId being the ID of the badge that was awarded (which is irrelevant to the graph, but it exists because it's part of the data). Now, I have a sample with around 2,787 of these objects, with all having different award dates and IDs, and with dates ranging from 2016 to 2022. My objective is to group these badges by month-year, and that month-year will have the amount of badges earned for that month during that year. With that data, I then want to make a waterfall graph which is based on the amount of badges earned that month of that year. As of right now, there isn't a specific structure on how this will look like, but it could range from an object that looks like {"02-2022": 10, "03-2022": 5} to anything else. I can of course restructure this format based on what is required for a waterfall graph.
Actual Questions
Now that you have a general idea of what my ultimate goal is, my actual question is how I'd be able to make a floating (we can leave the waterfall structure stuff for another topic) bar graph with that data. Since the data can have blank periods (it is possible for a dataset to have gaps that are months long), I cannot really utilize labels (unless I am saying something wrong), so an x-y relation works the best. I tried using the structure of {x: "2022-06-22T12:21:17.226Z", y: [10, 15]}, but that didn't really yield any results. As of right now, I am using a sample code to test how the graph reacts with the data, and of course I'll replace the test values with actual values once I have a finished product. Here is my code so far:
const config = {
type: "bar",
data: {
datasets: [{
label: "Badges",
data: [
{
x: "2022-06-22T12:41:17.226Z",
y: [10, 15]
}
],
borderColor: "rgb(75, 192, 192)",
borderSkipped: false
}]
},
options: {
plugins: {
legend: {
display: false
},
title: {
display: true,
text: "Test",
color: "#FFFFFF"
}
},
scales: {
x: {
type: 'time',
title: {
display: true,
text: 'Time',
color: "#FFFFFF"
},
min: "2022-06-22T12:21:17.226Z",
max: "2022-06-22T14:21:17.226Z",
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
},
y: {
title: {
display: true,
text: 'Number of Badges',
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
min: 0,
max: 50,
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
}
}
},
plugins: [
{
id: 'custom_canvas_background_color',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = '#303030';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
}
]
};
const imageBuffer = await canvasRenderService.renderToBuffer(config)
fs.writeFileSync("./chart2.png", imageBuffer)
And this is the graph that the code produces:
What is supposed to happen, of course, is that a float bar should be generated near the start that ranges from 5 to 10, but as seen above, nothing happens. If someone could assist me in my problem, that would be amazing. Thank you very much for your time and help, I greatly appreciate it.
Inspired by this answer, I came up with the following solution.
const baseData = [
{ awardedDate: "2022-06-22T12:21:17.22Z" },
{ awardedDate: "2022-06-18T12:21:17.22Z" },
{ awardedDate: "2022-06-15T12:21:17.22Z" },
{ awardedDate: "2022-05-20T12:21:17.22Z" },
{ awardedDate: "2022-05-10T12:21:17.22Z" },
{ awardedDate: "2022-04-16T12:21:17.22Z" },
{ awardedDate: "2022-04-09T12:21:17.22Z" },
{ awardedDate: "2022-04-03T12:21:17.22Z" },
{ awardedDate: "2022-04-01T12:21:17.22Z" },
{ awardedDate: "2022-02-18T12:21:17.22Z" },
{ awardedDate: "2022-02-12T12:21:17.22Z" },
{ awardedDate: "2022-01-17T12:21:17.22Z" }
];
const badgesPerMonth = baseData
.map(o => o.awardedDate)
.sort()
.map(v => moment(v))
.map(m => m.format('MMM YYYY'))
.reduce((acc, month) => {
const badges = acc[month] || 0;
acc[month] = badges + 1;
return acc;
}, {});
const months = Object.keys(badgesPerMonth);
const labels = months.concat('Total');
const data = [];
let total = 0;
for (let i = 0; i < months.length; i++) {
const vStart = total;
total += badgesPerMonth[months[i]];
data.push([vStart, total]);
}
data.push(total);
const backgroundColors = data
.map((o, i) => 'rgba(255, 99, 132, ' + (i + (11 - data.length)) * 0.1 + ')');
new Chart('badges', {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Badges',
data: data,
backgroundColor: backgroundColors,
barPercentage: 1,
categoryPercentage: 0.95
}]
},
options: {
plugins: {
tooltip: {
callbacks: {
label: ctx => {
const v = data[ctx.dataIndex];
return Array.isArray(v) ? v[1] - v[0] : v;
}
}
}
},
scales: {
y: {
ticks: {
beginAtZero: true,
stepSize: 2
}
}
}
}
});
<script src="https://rawgit.com/moment/moment/2.2.1/min/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.min.js"></script>
<canvas id="badges" height="95"></canvas>
If you also want to see the gaps, you would first have to initialize badgesPerMonth with following months between the earliest and latest date, each with value zero. Please take a look at this answer to get an idea about how this could be done.
After reading #uminder's reply, I was able to create the following code which solved my problem:
dateGroups = Object.fromEntries(
Object.entries(dateGroups).sort(([d1,],[d2,]) => {return (d1 < d2) ? -1 : ((d1 > d2) ? 1 : 0)})
)
const dateTimesConst = Object.keys(dateGroups)
const dateValuesConst = Object.values(dateGroups)
let dateTimes = []
let dateValues = []
let prevLength = 0
let mostBadgesPerMonth = 0
for (let i = 0; i < dateValuesConst.length; i++) {
const currentMonth = new Date(Date.parse(dateTimesConst[i]))
const previousMonth = new Date(Date.UTC(currentMonth.getUTCFullYear(), currentMonth.getUTCMonth() - 1, 1, 0, 0, 0, 0)).toISOString()
const nextMonth = new Date(Date.UTC(currentMonth.getUTCFullYear(), currentMonth.getUTCMonth() + 1, 1, 0, 0, 0, 0)).toISOString()
// if (!dateTimesConst.includes(previousMonth)) prevLength = 0
const length = dateValuesConst[i].length
dateValues.push([prevLength, length])
dateTimes.push(dateTimesConst[i])
prevLength = length
if (length > mostBadgesPerMonth) mostBadgesPerMonth = length
// if (!dateTimesConst.includes(nextMonth) && i !== dateValuesConst.length - 1) {
// dateTimes.push(nextMonth)
// dateValues.push([length, 0])
// prevLength = 0
// }
}
function barColorCode() {
return (ctx) => {
const start = ctx.parsed._custom.start
const end = ctx.parsed._custom.end
return start <= end ? "rgba(50, 168, 82, 1)" : (start > end) ? "rgba(191, 27, 27, 1)" : "black"
}
}
const config = {
type: "bar",
data: {
labels: dateTimes,
datasets: [{
label: "Badges",
data: dateValues,
elements: {
bar: {
backgroundColor: barColorCode()
}
},
barPercentage: 1,
categoryPercentage: 0.95,
borderSkipped: false
}]
},
options: {
plugins: {
legend: {
display: false
},
title: {
display: true,
text: "Test",
color: "#FFFFFF"
}
},
scales: {
x: {
type: 'time',
title: {
display: true,
text: 'Date',
color: "#FFFFFF"
},
time: {
unit: "month",
round: "month"
},
min: dateTimesConst[0],
max: dateTimesConst[dateTimesConst.length - 1],
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
},
y: {
title: {
display: true,
text: 'Number of Badges',
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
min: 0,
max: mostBadgesPerMonth + 1,
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
}
}
},
plugins: [
{
id: 'custom_canvas_background_color',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = '#303030';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
}
]
};
const imageBuffer = await canvasRenderService.renderToBuffer(config)
fs.writeFileSync("./chart2.png", imageBuffer)
Again, big thanks to #uminder for the inspiration.
I'm attempting make a chart with data that is being fetched from an API, but a problem i'm having is that i've been unable to properly render the chart due to an asynchronous call im making for the data. I believe the chart is executing before the data can be filled because upon loading the window nothing renders. However when I tried hard coding values in place of 'timestamps' and 'prices' in the data variable, the chart immediately renders. Does anyone know how I can format the rest of the code so that the chart renders only after the timestamp and price arrays have been filled?
import { useEffect } from 'react';
import { Line } from 'react-chartjs-2';
const MarketAPI = require('market-api');
const Client = new MarketAPI();
function GRAPH(){
const timestamps = [];
const prices = [];
var getData = async() => {
const fromTS = (Date.now() / 1000 | 0 ) - 86400; // 1D from current timestamp
const toTS = Date.now() / 1000 | 0; // current timestamp
let get = await Client.assets.fetchAssetHist('MSFT', {
from: fromTS,
to: toTS,
});
for(let i = 0; i < get.data.prices.length; i++){
timestamps.push(get.data.prices[i].[0]);
prices.push(get.data.prices[i].[1]);
}
console.log(timestamps);
console.log(prices);
}
const data = {
labels: timestamps,
datasets: [
{
data: prices,
fill: true,
backgroundColor: 'rgba(243, 230, 200, 0.2)',
borderColor: 'rgba(243, 210, 18)',
},
],
};
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
grid: {
display: false
},
display:true
},
y: {
grid: {
display: false
},
display: false
}
},
elements: {
point:{
radius: 0
}
},
};
useEffect(()=>{
getData()
},[]);
return(
<div>
<Line data={data} options={options}/>
</div>
);
}
function App() {
return (
<GRAPH/>
);
}
Use the useState Hook to store your data instead.
import { useEffect, useState } from 'react';
import { Line } from 'react-chartjs-2';
const MarketAPI = require('market-api');
const Client = new MarketAPI();
function GRAPH(){
const timestamps = [];
const prices = [];
const [data, setData] = useState({})
var getData = async() => {
const fromTS = (Date.now() / 1000 | 0 ) - 86400; // 1D from current timestamp
const toTS = Date.now() / 1000 | 0; // current timestamp
let get = await Client.assets.fetchAssetHist('MSFT', {
from: fromTS,
to: toTS,
});
for(let i = 0; i < get.data.prices.length; i++){
timestamps.push(get.data.prices[i].[0]);
prices.push(get.data.prices[i].[1]);
}
console.log(timestamps);
console.log(prices);
}
setData({
labels: timestamps,
datasets: [
{
data: prices,
fill: true,
backgroundColor: 'rgba(243, 230, 200, 0.2)',
borderColor: 'rgba(243, 210, 18)',
},
],
})
const options = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
grid: {
display: false
},
display:true
},
y: {
grid: {
display: false
},
display: false
}
},
elements: {
point:{
radius: 0
}
},
};
useEffect(()=>{
getData()
},[]);
return(
<div>
<Line data={data} options={options}/>
</div>
);
}
function App() {
return (
<GRAPH/>
);
}
I'm using vue-chartjs to show a line chart on my website. When I click on a button it's supposed to add another value to the chart. The dataset is updated but the chart is not.
LinieChart.vue
<script>
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: {
options: {
type: Object,
default: null
}
},
mounted () {
this.renderChart(this.chartData, this.options)
},
methods: {
}
}
</script>
I added this to my dashboard.vue
Where the chart is displayed
<line-chart
ref="LineChart"
:chart-data="chartdata"
:options="options"
class="h-64"
/>
The JavaScript:
<script>
export default {
middleware: 'auth',
layout: 'dashboard',
name: 'Dashboard',
components: {
temporaryDisableModal,
LineChart
},
data: () => ({
sensors: [],
sensorTypes: [],
valueTypes: [],
measurements: [],
websockets: [],
lastMeasurements: null,
loaded: false,
chartdata: null,
options: {
maintainAspectRatio: false,
responsive: true
}
}),
beforeMount () {
},
async mounted () {
this.fillData()
const self = this
// self.fillData()
await self.getSensors()
self.loaded = false
self.loaded = true
console.log('Sensors:')
console.log(self.sensors)
console.log(self.sensors.length)
for (let i = 0; i < self.sensors.length; i++) {
console.log(i)
console.log('Subscribed to ' + self.sensors[i].id)
self.measurements[self.sensors[i].id] = []
console.log(self.measurements)
window.Echo.channel('measurement.' + self.sensors[i].id).listen('.App\\Events\\WebsocketMeasurements', (e) => {
console.log('Data:')
console.log(e)
self.websockets.push(e.measurement)
self.measurements[self.sensors[i].id].push(e.measurement)
console.log(self.measurements)
})
}
},
methods: {
log () {
this.chartdata.labels.push(10)
this.chartdata.datasets[0].data.push(10)
this.chartdata.datasets[1].data.push(10)
},
fillData () {
this.chartdata = {
labels: this.getRandomInt(),
datasets: [
{
label: 'value_1',
borderColor: 'rgba(131, 24, 48, 1)',
backgroundColor: 'rgba(131, 24, 48, 0.3)',
data: this.getRandomInt()
},
{
label: 'value_2',
borderColor: 'rgba(216, 42, 81, 1)',
backgroundColor: 'rgba(216, 42, 81, 0.3)',
data: this.getRandomInt()
}
]
}
},
getRandomInt () {
let array = []
for (let i = 0; i < 100; i++) {
array.push(Math.floor(Math.random() * (6) * Math.random() * 3 * Math.random() * 9) + 15)
}
console.log(array)
return array
},
// other functions...
</script>
So when I execute the function log() it should add the value 10 as label and a dot in the line. It is added to the dataset, just not to the chart.
When I executed the log() function i get an error when hovering the chart:
Chart.js?30ef:6719 Uncaught TypeError: Cannot read property 'skip' of undefined
This does not happen when I haven't executed the function yet.
This is a known problem with vuecharts, instead of modifying nested elements on data reassign them all along, so instead of this:
this.chartdata.labels.push(10)
this.chartdata.datasets[0].data.push(10)
this.chartdata.datasets[1].data.push(10)
Do this:
this.chartdata.labels = MyNewArrayOfLabels
this.chartdata.datasets= [ //Me new array of datasets
{
label: 'value_1',
borderColor: 'rgba(131, 24, 48, 1)',
backgroundColor: 'rgba(131, 24, 48, 0.3)',
data: this.getRandomInt()
},
{
label: 'value_2',
borderColor: 'rgba(216, 42, 81, 1)',
backgroundColor: 'rgba(216, 42, 81, 0.3)',
data: this.getRandomInt()
}
]
I have plotted bar chart using echarts:
How can I show a normal distribution curve on bar chart line shown in the below image:
export class MainComponent implements OnInit, AfterViewInit, OnDestroy {
binsCount: number = 20;
histogramDetails: any = [];
//#endregion
constructor(private router: Router,
private store: Store<any>,
private projectService: ProjectService,
private taskService: TaskService,
private messageService: MessageService, ) { }
async createNewPlot(task: Task) {
if(this.selectedPlotType.name === 'Histogram') {
plotOption = await this.loadHistogramPlotData(task) ;
}
}
loadHistogramPlotData(task) {
if (!task || !this.selectedVariableX) {
return
}
return new Promise((resolve, reject) => {
this.taskService.getOutputVariablesHistogramPlot(task.id, this.selectedVariableX.id).subscribe(
response => {
//reset data
log.debug(`response = ${JSON.stringify(response)}`);
const plotData = this.setHistogramDetails(response.hist_plot_data);
resolve(plotData);
},
error => {
log.error(error);
reject(error)
}
);
})
}
setHistogramDetails(histogramDetails: any) {
// histogramDetails ? this.histogramDetails.push(histogramDetails) : null ;
const nums = histogramDetails.realization
let min = Math.floor(Math.min(...nums));
let max = Math.ceil(Math.max(...nums));
const binSize = (max - min) / this.binsCount;
let xaxisData: number[] = [];
let yseries = [];
let previousNumber = min;
for (let i = 0; i <= this.binsCount; i++) {
xaxisData.push(parseFloat(previousNumber.toFixed(1)));
yseries.push(0);
previousNumber = previousNumber + binSize;
}
for (const num of nums) {
for (let i = 1; i < xaxisData.length; i++) {
if (num < xaxisData[i]) {
yseries[i]++;
break;
}
}
}
const plotData: number[] = yseries;
const options = {
grid: {
left: 30,
top: 10,
bottom: 100
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#eee'
}
}
},
legend: {
orient: 'vertical',
left: '5%',
bottom: 30,
itemHeight: 3,
itemGap: 14,
textStyle: {
fontSize: 10,
color: '#333333'
},
data: ['Specified Distribution', 'Simulated Distribution']
},
xAxis: [
{
type: 'category',
data: [xaxisData[0] * 10, ...xaxisData, xaxisData[xaxisData.length - 1] * 10],
boundaryGap: ['40%', '40%'],
axisTick: {
alignWithLabel: true
},
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
splitNumber: 5,
axisLabel: {
formatter: '{value}',
fontSize: 10
}
}
],
dataZoom: [{
type: 'inside',
throttle: 50
}],
series: [
{
name: 'Simulated Distribution',
type: 'bar',
color: '#2DA8D8',
large: true,
data: plotData,
}
],
histogramDetails: histogramDetails
};
return options;
};
}
Echarts has no built-in function normal distribution. You need to calculate it based on your data and add like usual line series or MarkLine for bar.
Adding normal distribution is an open Github issue on the echarts-stat library:
https://github.com/ecomfe/echarts-stat/issues/4
Also you can use this:
function normalDist(theta, x) {
return 1 / (theta * Math.sqrt(2 * Math.PI)) * Math.exp(- x * x / 2 / theta / theta);
}
HTML
<div nz-row *ngIf="tempThermometer | async as temp">
<div *ngFor="let data of temp;let i = index;" nz-col nzXs="24" nzSm="12" nzMd="12" nzXl="8" nzXXl="6">
<nz-spin nzTip="Loading..." [nzSize]="'large'" [nzSpinning]="data.spinning">
<div echarts [options]="chartOption[i]" [autoResize]="true" style="height: 270px;"></div>
</nz-spin>
</div>
</div>
TS
tempLoading = false;
tempThermometer = new BehaviorSubject<any>([]);
getRoomList() {
this.tempLoading = true;
this.subscription = this.global
.getData(`/conditions/latest?length=${this.pageSize}`)
.pipe(take(1))
.subscribe((res: any) => {
this.tempThermometer.next(Object.values(res['data'].map((obj: any) => {
return {
...obj,
spinning: true
};
})));
this.tempLoading = false;
this.lineChart(this.tempThermometer.value);
});
}
lineChart(params?: any) {
const _this = this;
const list: any = [];
params.forEach((param: any) => {
const url = encodeURIComponent(param.sensor);
// List URL
list.push(`/conditions?length=${this.length}&sensor=${url}`);
});
// Promise requests
const promises = list.map(
(url: any) =>
new Promise(resolve => {
this.subscription = this.global.getData(url).pipe(take(1)).subscribe((res) => {
resolve(res);
}, (err: Error) => {
return reject(err);
});
})
);
// Retrieve each data as per promise
Promise.all(promises).then(results => {
const dataRoom: any = [];
results.map((result) => {
const date: any = [], temperature: any = [], humidity: any = [], newRoomData: any = [];
const param = result['data'];
const roomData = orderBy(param, ['date'], ['asc']);
const room = roomData.slice(-1)[0];
const timeEnd = room.date.slice(0, 19);
const timeStart = subHours(timeEnd, 7);
const dataHour = roomData.filter((data: TemplogRecord) => {
return !isBefore(data.date, timeStart) && !isAfter(data.date, timeEnd);
});
// console.log(roomData);
const hash = Object.create(null);
dataHour.forEach((data: any) => {
const key = data.date.slice(0, 13);
if (!hash[key]) {
hash[key] = {
sensor: data.sensor, temperature: data.temperature,
humidity: data.humidity, date: key + ':00:00'
};
newRoomData.push(hash[key]);
}
});
for (let x = 0; x < newRoomData.length; x++) {
temperature.push(newRoomData[x].temperature);
humidity.push(newRoomData[x].humidity);
date.push(newRoomData[x].date);
}
dataRoom.push({
date: date,
humidity: humidity,
temperature: temperature
});
});
dataRoom.forEach((param: any, index: number) => {
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
animation: false
},
backgroundColor: 'rgba(245, 245, 245, 0.8)',
borderWidth: 1,
borderColor: '#ccc',
padding: 10,
textStyle: {
color: '#000'
},
formatter: function (prm: any) {
let rec = prm[0].name.slice(0, 10) + '<br/>' + prm[0].name.slice(11, 19) + '<br/>';
for (let x = 0; x < prm.length; x++) {
if (prm[x].axisIndex !== 1) {
rec += prm[x].marker + ' ' + prm[x].seriesName + ': '
+ prm[x].data + _this.units['Celcius'] + '<br/>';
} else {
rec += prm[x].marker + ' ' + prm[x].seriesName + ': '
+ prm[x].data + '%' + '<br/>';
}
}
return rec;
}
},
...this.echart.roomChart,
dataZoom: [{
type: 'inside',
show: false,
bottom: 0,
width: '84%',
xAxisIndex: [0, 1],
zoomOnMouseWheel: false,
},
{
type: 'slider',
bottom: 0,
show: false,
width: '84%',
xAxisIndex: [0, 1],
zoomLock: false,
}],
xAxis: [{
type: 'category',
boundaryGap: false,
scale: true,
axisLine: {
show: false
},
axisTick: {
show: false
},
data: param.date.map((str: any) => {
return format(str, 'YYYY-MM-DD hh:mm a');
}),
splitLine: {
show: true,
lineStyle: {
color: 'rgba(182, 202, 227)'
}
},
axisLabel: {
show: true,
interval: 0,
rotate: 90,
formatter: ((data: any) => {
return (data).slice(11, 19);
})
}
},
{
gridIndex: 1,
show: false,
scale: true,
type: 'category',
boundaryGap: false,
axisLine: {
show: false
},
data: param.date,
axisTick: {
show: false
},
splitLine: {
show: true
}
}],
series: [{
name: 'Humidity',
data: param.humidity,
type: 'line',
itemStyle: {
color: 'rgba(0, 101, 144, 1)'
},
markPoint: {
type: 'Pin',
data: [
{
type: 'max',
itemStyle: {
color: 'rgba(0, 101, 144)'
}
},
{
type: 'min',
itemStyle: {
color: 'rgb(110, 151, 204)'
}
}
]
},
smooth: true,
xAxisIndex: 1,
yAxisIndex: 1
},
{
name: 'Temperature',
data: param.temperature,
type: 'line',
itemStyle: {
color: 'rgba(255, 0, 0, 1)'
},
markPoint: {
type: 'Pin',
data: [
{
type: 'max',
itemStyle: {
color: 'rgba(255, 5, 0)'
}
},
{
type: 'min',
itemStyle: {
color: 'rgb(255, 87, 86)'
}
}
]
},
smooth: true
},
]
};
this.chartOption.push(option);
this.notScrolly = true;
this.tempThermometer.value.filter((x: any) => params.map((y: any) => {
if (y.id === x.id) {
return y.spinning = false;
}
}));
});
});
}
The problem here is, when it's loading it will fetch all the data before it will display them all.
What I want to do here is when the first item that already fetch, it will display while the others still fetching the data.
for example there's 5 items which is the area 1, area 2, area 3, area 4 and area 5.
when the area 1 already finish fetching the data then it will display it. while the other still loading/fetching.
like the area 1 already finish fetching then next the area 3 then area 2 then 5 then 4.
who ever finish fetching it will display automatically.
I would approach this in one of two ways. Neither would involve promises.
I'm not going to attempt to use your code example, as it is far too large, instead I will focus on the core of your problem - run an array of observables and process results as they are returned.
1. Merge observables
The RxJS merge function will run multiple observables together and call your subscribe callback with the individual values immediately.
const observables: Observable<any>[] = this.getObservables();
merge(...observables).subscribe(result => {
// individual results are logged here as soon as the observable returns them
console.log(result);
}, err => {
// empty error callback.
// This is here to allow us to add the complete callback
}, () => {
console.log('complete');
});
Pro: simple to process results in the subscribe
Con: have to run complete code in the complete callback
2. Process results in the pipe
You can run the observables in parallel in a forkJoin. The subscribe will only be called when the final observable completes, but you can process results in individual tap operators.
const observables: Observable<any>[] = this.getObservables()
.map(x => x.pipe(
// tap is run as soon as the observable is returned
tap(result => console.log(result))
));
forkJoin(observables).subscribe(result => {
console.log('complete');
});
Pro: simple complete handling in the subscribe
Con processing results in individual tap operators can get a bit messy
Conclusion
Both of these approaches are fairly equivalent. I would probably prefer the forkJoin approach, but I wanted to demonstrate the power and flexibility of RxJS by giving you a second example.
DEMO: https://stackblitz.com/edit/angular-7rrmtn
The demo sets up 5 observables, each with a different delay. The observables are processed as the results are returned, and they both log when they complete.
As you can see, they are functionally equivalent.
Try to add change detection strategy
and run detectChanges function as soon as you update variable with the first data point