Related
I'm brand new to Angular and typescript and still trying to make it through but now I can't.
I bought a template for Angular (VEX) and I would like to integrate data from firebase into a datatable already present in the template.
In the template, this table is fed by static data and I would like to replace that with my call to firebase.
I am really lost and I would like to understand how I can do to get there.
Here is the manage-users-component.ts
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '#angular/core';
import { Observable, of, ReplaySubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Customer } from './interfaces/customer.model';
import { MatTableDataSource } from '#angular/material/table';
import { MatPaginator } from '#angular/material/paginator';
import { MatSort } from '#angular/material/sort';
import { MatDialog } from '#angular/material/dialog';
import { TableColumn } from '../../../#vex/interfaces/table-column.interface';
import { aioTableData, aioTableLabels } from '../../static-data/aio-table-data';
import { CustomerCreateUpdateComponent } from './customer-create-update/customer-create-update.component';
import icEdit from '#iconify/icons-ic/twotone-edit';
import icDelete from '#iconify/icons-ic/twotone-delete';
import icSearch from '#iconify/icons-ic/twotone-search';
import icAdd from '#iconify/icons-ic/twotone-add';
import icFilterList from '#iconify/icons-ic/twotone-filter-list';
import { SelectionModel } from '#angular/cdk/collections';
import icMoreHoriz from '#iconify/icons-ic/twotone-more-horiz';
import icFolder from '#iconify/icons-ic/twotone-folder';
import { fadeInUp400ms } from '../../../#vex/animations/fade-in-up.animation';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldDefaultOptions } from '#angular/material/form-field';
import { stagger40ms } from '../../../#vex/animations/stagger.animation';
import { FormControl } from '#angular/forms';
import { UntilDestroy, untilDestroyed } from '#ngneat/until-destroy';
import { MatSelectChange } from '#angular/material/select';
import icPhone from '#iconify/icons-ic/twotone-phone';
import icMail from '#iconify/icons-ic/twotone-mail';
import icMap from '#iconify/icons-ic/twotone-map';
import firebase from 'firebase';
import { UserManageService } from '../../services/user-manage.service';
#UntilDestroy()
#Component({
selector: 'vex-manage-users',
templateUrl: './manage-users.component.html',
styleUrls: ['./manage-users.component.scss'],
animations: [
fadeInUp400ms,
stagger40ms
],
providers: [
{
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: {
appearance: 'standard'
} as MatFormFieldDefaultOptions
}
]
})
export class ManageUsersComponent implements OnInit, AfterViewInit {
layoutCtrl = new FormControl('boxed');
/**
* Simulating a service with HTTP that returns Observables
* You probably want to remove this and do all requests in a service with HTTP
*/
subject$: ReplaySubject<Customer[]> = new ReplaySubject<Customer[]>(1);
data$: Observable<Customer[]> = this.subject$.asObservable();
customers: Customer[];
#Input()
columns: TableColumn<Customer>[] = [
{ label: 'Checkbox', property: 'checkbox', type: 'checkbox', visible: true },
{ label: 'ShipTo', property: 'extId', type: 'text', visible: true },
{ label: 'uid', property: 'uid', type: 'text', visible: true },
{ label: 'Compagny', property: 'compagny', type: 'text', visible: true },
{ label: 'Name', property: 'name', type: 'text', visible: true, cssClasses: ['font-medium'] },
{ label: 'First Name', property: 'firstName', type: 'text', visible: false },
{ label: 'Last Name', property: 'lastName', type: 'text', visible: false },
{ label: 'Email', property: 'email', type: 'text', visible: true },
{ label: 'Phone', property: 'phone', type: 'text', visible: true },
{ label: 'Role', property: 'role', type: 'text', visible: true },
{ label: 'Actions', property: 'actions', type: 'button', visible: true }
];
pageSize = 10;
pageSizeOptions: number[] = [5, 10, 20, 50];
dataSource: MatTableDataSource<Customer> | null;
selection = new SelectionModel<Customer>(true, []);
searchCtrl = new FormControl();
labels = aioTableLabels;
icPhone = icPhone;
icMail = icMail;
icMap = icMap;
icEdit = icEdit;
icSearch = icSearch;
icDelete = icDelete;
icAdd = icAdd;
icFilterList = icFilterList;
icMoreHoriz = icMoreHoriz;
icFolder = icFolder;
#ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
#ViewChild(MatSort, { static: true }) sort: MatSort;
constructor(private dialog: MatDialog,
private usersManageService: UserManageService ) {
}
get visibleColumns() {
return this.columns.filter(column => column.visible).map(column => column.property);
}
/**
* Example on how to get data and pass it to the table - usually you would want a dedicated service with a HTTP request for this
* We are simulating this request here.
*/
getData() {
return of(aioTableData.map(customer => new Customer(customer)));
}
ngOnInit() {
const users = this.usersManageService.getUsers();
console.log(users);
this.getData().subscribe(customers => {
this.subject$.next(customers);
});
console.log(aioTableData);
this.dataSource = new MatTableDataSource();
this.data$.pipe(
filter<Customer[]>(Boolean)
).subscribe(customers => {
this.customers = customers;
this.dataSource.data = customers;
});
this.searchCtrl.valueChanges.pipe(
untilDestroyed(this)
).subscribe(value => this.onFilterChange(value));
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
createCustomer() {
this.dialog.open(CustomerCreateUpdateComponent).afterClosed().subscribe((customer: Customer) => {
/**
* Customer is the updated customer (if the user pressed Save - otherwise it's null)
*/
if (customer) {
/**
* Here we are updating our local array.
* You would probably make an HTTP request here.
*/
this.customers.unshift(new Customer(customer));
this.subject$.next(this.customers);
}
});
}
updateCustomer(customer: Customer) {
this.dialog.open(CustomerCreateUpdateComponent, {
data: customer
}).afterClosed().subscribe(updatedCustomer => {
/**
* Customer is the updated customer (if the user pressed Save - otherwise it's null)
*/
if (updatedCustomer) {
/**
* Here we are updating our local array.
* You would probably make an HTTP request here.
*/
const index = this.customers.findIndex((existingCustomer) => existingCustomer.uid === updatedCustomer.uid);
this.customers[index] = new Customer(updatedCustomer);
this.subject$.next(this.customers);
}
});
}
deleteCustomer(customer: Customer) {
/**
* Here we are updating our local array.
* You would probably make an HTTP request here.
*/
this.customers.splice(this.customers.findIndex((existingCustomer) => existingCustomer.uid === customer.uid), 1);
this.selection.deselect(customer);
this.subject$.next(this.customers);
}
deleteCustomers(customers: Customer[]) {
/**
* Here we are updating our local array.
* You would probably make an HTTP request here.
*/
customers.forEach(c => this.deleteCustomer(c));
}
onFilterChange(value: string) {
if (!this.dataSource) {
return;
}
value = value.trim();
value = value.toLowerCase();
this.dataSource.filter = value;
}
toggleColumnVisibility(column, event) {
event.stopPropagation();
event.stopImmediatePropagation();
column.visible = !column.visible;
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
trackByProperty<T>(index: number, column: TableColumn<T>) {
return column.property;
}
// onLabelChange(change: MatSelectChange, row: Customer) {
// const index = this.customers.findIndex(c => c === row);
// this.customers[index].labels = change.value;
// this.subject$.next(this.customers);
// }
}
Here is the Firebase Realtime database "users" that I would like to insert in my datatable
Here is the function I imaginated for do the job but I dont know where to put it into
getUsers() {
firebase.database().ref('/users').once('value').then((snapshot) => {
const users = snapshot.val();
return users;
});
}
I am completely stuck and I thank very much in advance anyone who can help me.
I made some progress in my case and I was able to recover the values I wanted.
The array returned by my function matches the array of the example template.
getData() {
const users = [];
firebase.database().ref('/users').once('value').then((snapshot) => {
users.push(snapshot.val()) ;
const users2 = users[0];
const mapped = Object.keys(users2).map(key => (users2[key]));
console.log(mapped.map(customer => new Customer(customer)));
// console.log(aioTableData.map(customer => new Customer(customer)));
return of(mapped.map(customer => new Customer(customer)));
});
// return of(aioTableData.map(customer => new Customer(customer)));
The problem is that in ngOnInit, when the call to the function is done, it expects a subscribe and I don't know how to do it.
ngOnInit() {
this.getData().subscribe(customers => {
this.subject$.next(customers);
});
this.dataSource = new MatTableDataSource();
this.data$.pipe(
filter<Customer[]>(Boolean)
).subscribe(customers => {
this.customers = customers;
this.dataSource.data = customers;
});
this.searchCtrl.valueChanges.pipe(
untilDestroyed(this)
).subscribe(value => this.onFilterChange(value));
}
An idea?
I am currently using react-chartjs-2 to be able to insert a chart into a react component. I am importing the data and options of the chart which are located in another js file. In my app, I am also making a POST call which returns some data in it's body which I want to use as the data for the chart and the chart to be able to update every time a POST request is called. The POST response is currently stored in the state called RESTResponse. So to access the response data outside of the chart in react, I normally call {this.state.RESTresponse.total}. I want to be able to use {this.state.RESTresponse.total} as the data in my chart. How would I be able to do this? Would it be easier if I didn't use two separate js files for the chart and main component? Thank you!
Here is the code where the chart is being called:
import {HorizontalBar} from "react-chartjs-2";
import {Row} from "reactstrap";
// Importing the chart data and options to be used in <HorizontalBar>
import {stackedChart} from "variables/charts.js";
class Dashboard extends React.Component {
constructor(props) {
super(props);
this.state = {
RESTresponse: []
};
}
async onTodoChange(event){
let updateJSON = {...this.state.RESTresponse, [event.target.name] : val}
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updateJSON)
};
const response = await fetch('/api/calculate', requestOptions);
const body = await response.json();
this.setState({RESTresponse : body });
}
render() {
return (
<>
<div className="content">
<Row>
<HorizontalBar
data={stackedChart.data}
options={stackedChart.options}
/>
</Row>
</div>
</>
);
}
}
And here is the code where the data and options are defined:
let stackedChart = {
data: canvas => {
return {
datasets: [
{
label: ' Total Value',
data: [10], //<--- I want to dynamically update this data value from the POST response
backgroundColor: '#C4D156'
}
]
};
},
options: {
maintainAspectRatio: false,
legend: {
display: true,
labels: {
usePointStyle: true,
borderWidth: 0,
filter: function(legendItem, chartData) {
if (legendItem.datasetIndex === 3) {
return false;
}
return true;
}
}
},
tooltips: {enabled: false},
hover: {mode: null},
responsive: true,
scales: {
yAxes: [
{stacked: true},
],
xAxes: [{stacked: true,
ticks: {
display: false
}
},
]
}
}
};
module.exports = {
stackedChart
};
When data is a function, is it invoked with node where the chart is mounted. However you don't seem to be having need for it currently.
Declare data to receive Server response data.
dataFactory: data => {
return {
datasets: [
{
label: ' Total Value',
data,
backgroundColor: '#C4D156'
}
]
};
},
Then invoke it in render method of your component with data stored in state.
<HorizontalBar
data={stackedChart.dataFactory(this.state.RESTresponse)}
options={stackedChart.options}
/>
I'm trying to update a chart using VueJS and ChartJS and so far i can access every property of the object but if i try to change the object's property i get an error :
[Vue warn]: Error in mounted hook: "TypeError: _chart_data_js__WEBPACK_IMPORTED_MODULE_5__.planetChartData.update is not a function"
I went to ChartJS's tutorial section and issues sections but i couldn't find any clue for this problem.
What i find strange is that the 'push' function is working perfectly fine.
So far what i'v try is :
.vue file
<template>
<div id="app" style="position: relative; height:500px; width:500px">
<canvas :width="300" :height="300" id="planet-chart"></canvas>
</div>
</template>
...
import { mapActions, mapState } from 'vuex'
import Chart from 'chart.js';
import {planetChartData,pie} from './chart-data.js';
// import { mapActions } from 'vuex'
// import { connectionsAlive } from '../../api/mkt-api.js'
export default {
mounted() {
var x=this.createChart('planet-chart', this.planetChartData)
planetChartData.data.labels.push('Janvier', 'Février')
planetChartData.update();
},
data () {
return {
planetChartData: planetChartData,
}
},
methods: {
createChart(chartId, chartData) {
const ctx = document.getElementById(chartId);
const myChart = new Chart(ctx, {
type: chartData.type,
data: chartData.data,
options: chartData.options,
});
}
}
}
</script>
And .js file
export const planetChartData = {
type: 'bar',
data: {
labels: ['Janvier', 'Février', 'Mars', 'Avril'],
datasets: [
{ // one line graph
label: 'Number of users',
data: [3018, 3407, 3109,1060],
backgroundColor: [
'rgba(54,73,93,.5)', // Blue
'rgba(54,73,93,.5)',
'rgba(54,73,93,.5)',
'rgba(54,73,93,.5)'
],
borderColor: [
'#36495d',
'#36495d',
'#36495d',
'#36495d'
],
borderWidth: 3
},
]
},
options: {
responsive: true,
lineTension: 1,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
padding: 40,
}
}]
}
}
}
Maybe i'm using the wrong syntax, if anyone has an idea let me know, thanks.
Regards.
In the vue file, planetChartData is a reference to the object "planetChartData" from your js file. It is not a reference to the chart you create in createChart()
What you want is to return the created chart, so you can call update() on it:
createChart(chartId, chartData) {
const ctx = document.getElementById(chartId);
const myChart = new Chart(ctx, {
type: chartData.type,
data: chartData.data,
options: chartData.options,
});
return myChart // <<< this returns the created chart
}
Then in mounted you can do this:
var chart = this.createChart('planet-chart', planetChartData)
chart.update();
I want to access getValue method from Chart object, but I get function undefined.
<template>
<div>
<canvas width="600" height="400" ref="canvas"></canvas>
</div>
</template>
<script>
import Vue from 'vue';
import Chart from 'chart.js';
import Axios from 'axios';
export default {
mixins: [DateRangeMixin],
props: {
// other props...
callback: false,
},
data() {
return {
chart: '',
};
},
mounted() {
// ...
},
methods: {
//other methods...,
getValue(data) {
if (data === 1) {
return 'Up'
} else if(data === 0) {
return 'Down';
}
},
render(data) {
this.chart = new Chart(this.$refs.canvas, {
type: 'line',
data: {
labels: Object.keys(data),
datasets: [{
// a lot of data ....
data: Object.values(data),
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback(label, index, labels) {
return this.getValue(label); // <-- Tried this and got: 'this.getValue is not a function'. I understand it bounces to new Chart object, but how to resolve this?
}
}
}]
}
}
});
},
},
};
</script>
I understand that it's because Chart is an object and this is pointing to it, but how do I resolve this and access my method from the callback ?
I imagine if that export default... would be set to a variable, then I could access my method via variable.methods.getValue , but in this scenario How can I achieve my goal ?
Right before you create the new Chart() assign this to a variable self: var self = this;.
You can then access your component properties throughself.
I'm trying to create dynamically a chart using ng2-chart,
I get information from an angular 2 service, when I change only labels of chart it works and when I change data only it works, but When I change both just data are updated in the chart. have any one an explication for this strange behavior.
my template :
<canvas baseChart height="130" width="180"
[data]="doughnutChartData"
[labels]="doughnutChartLabels"
[chartType]="doughnutChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
my class :
export class PlDoughnutComponent implements OnInit {
constructor(private homeService: TileServiceService) { }
ngOnInit() {
this.updatePLdoughnut();
}
public util : UtilService = new UtilService();
public doughnutChartLabels:string[] = ['Download Sales'];
public doughnutChartData:number[] = [0,0,100];
public doughnutChartType:string = 'doughnut';
public updatePLdoughnut(){
this.homeService.getTile().
then(res => {
this.doughnutChartLabels = res.PLtypes;
this.doughnutChartData = this.util.objectToIntArray(res.PLByTypes);
})
}
}
Apparently, if you do not modify the original reference to the labels array, it seems to work, at least for me. I mean, if you want a completely different set of labels, you should do something like this:
In the template:
<canvas baseChart
[datasets]="lineChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[chartType]="'line'"></canvas>
In the ts component:
this.lineChartLabels.length = 0;
for (let i = tempLabels.length - 1; i >= 0; i--) {
this.lineChartLabels.push(tempLabels[i]);
}
Or, using new ECMAScript syntax:
this.lineChartLabels.length = 0;
this.lineChartLabels.push(...tempLabels);
The key is maybe the this.lineChartLabels.length = 0; statement, which practically 'empties' your array by setting its length to 0, without modifying the reference.
Hope this helps!
Recently i had to use ng2-charts and i was having a very big issues with updating my data untill i found this sollution:
<div class="chart">
<canvas baseChart [datasets]="datasets_lines" [labels]="labels_line" [colors]="chartColors" [options]="options" [chartType]="lineChartType">
</canvas>
</div>
and here what i have in my component :
import { Component, OnInit, Pipe, ViewChild, ElementRef } from '#angular/core';
import { BaseChartDirective } from 'ng2-charts/ng2-charts';
#Component({
moduleId: module.id,
selector: 'product-detail',
templateUrl: 'product-detail.component.html'
})
export class ProductDetailComponent {
#ViewChild(BaseChartDirective) chart: BaseChartDirective;
private datasets_lines: { label: string, backgroundColor: string, borderColor: string, data: Array<any> }[] = [
{
label: "Quantities",
data: Array<any>()
}
];
private labels_line = Array<any>();
private options = {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
};
constructor() { }
ngOnInit() {
this.getStats();
}
getStats() {
this._statsService.getStatistics(this.startDate, this.endDate, 'comparaison')
.subscribe(
res => {
console.log('getStats success');
this.stats = res;
this.labels_line = this.getDates();
this.datasets_lines = [];
let arr: any[];
arr = [];
for (let stat of this.stats) {
arr.push(stat.quantity);
}
this.datasets_lines.push({
label: 'title',
data: arr
});
this.refresh_chart();
},
err => {
console.log("getStats failed from component");
},
() => {
console.log('getStats finished');
});
}
refresh_chart() {
setTimeout(() => {
console.log(this.datasets_lines_copy);
console.log(this.datasets_lines);
if (this.chart && this.chart.chart && this.chart.chart.config) {
this.chart.chart.config.data.labels = this.labels_line;
this.chart.chart.config.data.datasets = this.datasets_lines;
this.chart.chart.update();
}
});
}
getDates() {
let dateArray: string[] = [];
let currentDate: Date = new Date();
currentDate.setTime(this.startDate.getTime());
let pushed: string;
for (let i = 1; i < this.daysNum; i++) {
pushed = currentDate == null ? '' : this._datePipe.transform(currentDate, 'dd/MM/yyyy');
dateArray.push(pushed);
currentDate.setTime(currentDate.getTime() + 24 * 60 * 60 * 1000);
}
re
turn dateArray;
}
}
i m sure this is the right way to do it, and hope this would be helpfull
Like Deyd pointed out before, this is caused by a combination of Angular 2+'s change detection and a bug in ng2-charts.
According to my own observations (correct me if I'm wrong), Angular merges several changes within a very short timeframe into a single collection (changes: SimpleChanges) when ngOnChanges is called.
Unfortunately, ng2-charts only checks if the dataset has been changed with this collection and updates it. Otherwise it completely rebuilds the entire chart. However, because of the way the change detection works, more than one property might have been changed. Then, only the dataset gets updated even if the labels and possibly other properties have been updated as well. See ngOnChanges in ng2-charts:
valor-software/ng2-charts/src/charts/charts.ts
And if you don't want to have a separate copy of ng2-charts in your app and fix the problem yourself, a possible workaround for this problem is to set the dataset with a short delay using JavaScript's built-in function setTimeout(callback: () => void, delay: number).
Before:
#Component({
selector: 'app-root',
template: `
<select (change)="onChange($event.target.value)">
<option value="" disabled selected>Select your option</option>
<option value="0">Option 0</option>
<option value="1">Option 1</option>
</select>
<canvas baseChart
chartType="bar"
[datasets]="barChartData"
[labels]="barChartLabels"
[colors]="barChartColors">
</canvas>
`
})
export class AppComponent implements OnInit {
chartData: string[];
chartLabels: string[];
chartColors: string[];
onChange(id: string) {
getFromApiById(id)
.then(result => this._setChart(result.data, result.labels, result.colors));
}
private _setChart(data: string[], labels: string[], colors: string[]) {
this.chartData = data;
this.chartLabels = labels;
this.chartColors = colors;
}
}
After:
#Component({
selector: 'app-root',
template: `
<select (change)="onChange($event.target.value)">
<option value="" disabled selected>Select your option</option>
<option value="0">Option 0</option>
<option value="1">Option 1</option>
</select>
<canvas baseChart
chartType="bar"
[datasets]="barChartData"
[labels]="barChartLabels"
[colors]="barChartColors">
</canvas>
`
})
export class AppComponent implements OnInit {
chartData: string[];
chartLabels: string[];
chartColors: string[];
onChange(id: string) {
getFromApiById(id)
.then(result => this._setChart(result.data, result.labels, result.colors));
}
private _setChart(data: string[], labels: string[], colors: string[]) {
this.chartLabels = labels;
this.chartColors = colors;
setTimeout(() => {
this.chartData = data;
}, 50);
}
}
Using BaseChartDirective i did chart update and it served the purpose. Sample below:
import { BaseChartDirective } from 'ng2-charts/ng2-charts';
inside the class add as below
#ViewChild(BaseChartDirective) chart: BaseChartDirective;
While you have the values to be changed, add as below
setTimeout(() => {
if (this.chart && this.chart.chart && this.chart.chart.config) {
this.chart.chart.config.data.labels = this.labels_pie;
this.chart.chart.update();
}
});
The trick is in clearing the label and data array, the below code didnt work for me :(
```
clearCharts() {
this.barChartLabels= [];
this.barChartData= [
{data: [], label: 'label1'},
{data: [], label: 'label2'}
];
}
However when I changed the way I cleared the data helped me (Using object reference)
clearCharts() {
this.barChartLabels= [];
this.emptyChartData(this.barChartData);
}
emptyChartData(obj) {
obj[0].data = [];
obj[1].data = [];
obj[0].label = 'label1';
obj[1].label = 'label2';
}
```
Using BaseChartDirective i did chart update and it served the purpose. Sample below:
import { BaseChartDirective } from 'ng2-charts';
inside the class add as below
#ViewChild(BaseChartDirective) chart: BaseChartDirective;
While you have the values to be changed, add as below
this.chart.ngOnChanges({});
This is an issue in the library ng2-charts, to resolve it I have cloned the github of ng2-charts in my app directory and have done following steps :
npm install
in appmodule import ng-2charts.ts from src directory.
add this updateChartLabels function to chart.ts file
call it in the onChanges function.
public ngOnChanges(changes: SimpleChanges): void {
if (this.initFlag) {
if(changes.hasOwnProperty('labels')){
console.log('labels changes ...');
this.updateChartLabels(changes['labels'].currentValue);
}
//..
//...
}
private updateChartLabels(newLabelsValues: string[] | any[]): void {
this.chart.data.labels = newLabelsValues;
}
For those looking for a walk around, for now you can put your labels and data in an object and put that object in an array and just loop through the array in your html. This will redraw the element every time your array changes.
in your type script every time there's a change.
data = [...]; labels = [...]; chartArray = [{data , labels }]
in your html
<canvas *ngFor="let chartData of chartArray " [datasets]="chartData.data" [labels]="chartData.labels" > </canvas>
This is an issue with the current ng2-charts library.
Try the new ng4-charts library which has fixed this issue.
https://www.npmjs.com/package/ng4-charts
There is another way to do it:
In your HTML you have
<canvas baseChart
[datasets]="ChartData"
//...other stuff >
</canvas>
and in the component I have a function which update the chart with new data, and then I clone the datasets and re-assign it
drawChart(){
this.ChartData=[{data: this.dataX, label: 'X'}]; // this.dataX has new values from some place in my code
//nothing happened with my chart yet, until I add this lines:
let clone = JSON.parse(JSON.stringify(this.ChartData));
this.ChartData=clone;
//other stuff like labels etc.
}
this works for me, hope it works for you too
I was able to fix this issue by turning the handler into an arrow function
export class HistogramChartComponent implements OnInit {
constructor(private dataService: MyFruitService ) { }
barChartOptions: ChartOptions = { responsive: true };
barChartLabels: Label[] = ['Apple', 'Banana', 'Kiwifruit', 'Blueberry', 'Orange', 'Grapes'];
barChartType: ChartType = 'bar';
barChartLegend = true;
barChartPlugins = [];
barChartData: ChartDataSets[] = [
{ data: [45, 37, 60, 70, 46, 33], label: 'Best Fruits' }
];
ngOnInit() {
this.dataService
.getDocument("Foobar")
.subscribe(this.handleResponse);
}
handleResponse = (doc: MyFruitDocument) => {
console.log('document: ', doc);
let labels = doc.dataPoints.map(p => p.fruitName);
let data = { data: doc.dataPoints.map(p => p.value), label: 'Best Fruits' };
this.barChartLabels = labels;
this.barChartData = [ data ];
}
}
I have also faced this issue when trying to update the labels, (specifically when trying to send a shorter array), And this solved it:
#ViewChild(BaseChartDirective) chart!: BaseChartDirective;
and then, when updating the labels:
this.chart.chart!.config.data.labels = [...]
No need to call the update() method.
Since I didn't manage to get one of the above solutions to work properly, I want to contribute my solution, in case someone stumbles across this post and also got stuck with the present approaches.
I have the HTML similar to #mustafa918:
<div>
<canvas #canvas id="canvas"
baseChart [datasets]="lineChartData"
[labels]="lineChartLabels"
[colors]="lineChartColors"
[options]="lineChartOptions"
[chartType]="lineChartType"
[legend]="lineChartLegend"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)">
</canvas>
</div>
And for the initialisation of the charts data in typescript i have:
public lineChartData: Array<any> = [
{ data: this.heights, label: 'Heights History', type: 'line', fill: false},
{ data: this.widths, label: 'Widths History', type: 'line', fill: false }];
And for me it worked only by setting the data and labels at the same time and don't use chart.update() - chart is the reference to the BaseChartDirective.
I loaded the respective data and labels beforehand, so that in this.heights, this.width and this.lineChartLabels are corresponding data.
E.g. : The entries on heights[i], widths[i] and lineChartLabels[i] match with the element in my elementArray at index i => element ={ "height":30, "width":20, "label":"box"}
setDatasets() {
//store data in chart references
var arrHeights = [];
for (var i in this.heights) {
arrHeights.push({ x: this.lineChartLabels[i], y: this.heights[i] });
}
var arrWidths= [];
for (var i in this.widths) {
arrWidths.push({ x: this.lineChartLabels[i], y: this.widths[i] });
}
this.lineChartData[0].data = arrHeights;
this.lineChartData[1].data = arrWidths;
}
I hope this helps someone :) Good Luck!
Today i struggled with similar problem, it appears there is a huge bug inside the updateChartData function of ng2-charts library version 1.6.0.
Here is the original function:
updateChartData = function (newDataValues) {
if (Array.isArray(newDataValues[0].data)) {
this.chart.data.datasets.forEach(function (dataset, i) {
dataset.data = newDataValues[i].data;
if (newDataValues[i].label) {
dataset.label = newDataValues[i].label;
}
});
}
else {
this.chart.data.datasets[0].data = newDataValues;
}
}
As you can see this updates only the data and the label, but all other properties are left behind. In my case i wanted to update also the pointBorderColor so i decided to override this.
First i get a reference to the ng2-charts library:
import { BaseChartDirective } from 'ng2-charts';
#ViewChild(BaseChartDirective) chart: any;
It is very important the type is "any", because otherwise typescript will not allow me to override a private function.
Then i fix the bug in the function and override it in afterVIew init:
ngAfterViewInit(){
if (this.chart) {
this.chart.updateChartData = function (newDataValues) {
if (Array.isArray(newDataValues[0].data)) {
this.chart.data.datasets.forEach(function (dataset, i) {
dataset.data = newDataValues[i].data;
if (newDataValues[i].pointBorderColor) {
dataset.pointBorderColor = newDataValues[i].pointBorderColor;
}
if (newDataValues[i].label) {
dataset.label = newDataValues[i].label;
}
});
}
else {
this.chart.data.datasets[0].data = newDataValues;
}
}.bind(this.chart);
}
}
Based on above answers, I extended this function and everything works fine now!
TS Code:
Declare : import { BaseChartDirective } from 'ng2-charts';
#ViewChild(BaseChartDirective) chart: BaseChartDirective;
public lineChartData: ChartDataSets[] = [
{ data: [0, 0, 0, 0, 0, 0, 0], label: 'Data 1' },
{ data: [0, 0, 0, 0, 0, 0, 0], label: 'Data 2' }
];
public lineChartLabels: Label[] = ['Label1', 'Label2', 'Label3', 'Label4',
'Label5', 'Label6';
TS Function:
refresh_chart(){
setTimeout(() => {
if (this.chart && this.chart.chart && this.chart.chart.config) {
this.chart.chart.config.data.datasets.forEach(x => {
x.data = [];
});
let index = 0;
this.chart.chart.config.data.datasets.forEach(x => {
x.data = this.lineChartData[index].data;
index++;
});
this.chart.chart.update();
}
}, 500);
}
HTML Code:
<canvas baseChart [datasets]="lineChartData" class="canvas-wh" [labels]="lineChartLabels"
[options]="lineChartOptions" [colors]="lineChartColors" [legend]="lineChartLegend"
[chartType]="lineChartType" [plugins]="lineChartPlugins">
For me, it worked only after using ViewChildren and not ViewChild.
TS:
#ViewChildren('baseLineChart1') chart !: QueryList<BaseChartDirective>;
this.chart.forEach((child) => { child.update() })
HTML:
<canvas class="card-line-chart" baseChart #baseLineChart1>