How to properly use appendData in ng-apexcharts (angular)? - html

Project is Angular/FastAPI app. I'm looking for way to update data in my single serie. Series data should be updated when new data arrive from websocket and almost everything is working. I'm checking if data is arriving and it's done properly. One single problem is fact that I can't add any data to my series and have tried many ways to do it.
I'm trying this way. This is my constructor in component. It's worth to mention that I'm checking if chart is already rendered before appending data to my series and I checked if arriving data is valid - it is every time. Chart is also working properly if I specify data for my serie statically in chartOptions. Console.log(this.chart.series) every time shows that series length isn't changing, it's always 1:
constructor(private binance: BinanceService) {
this.chartOptions = {
series: [
{
name: "candle",
data: [
{
x: new Date(1538778600000),
y: [6629.81, 6650.5, 6623.04, 6633.33]
}
]
}
],
chart: {
type: "candlestick",
height: '800',
width: '100%',
redrawOnParentResize: true,
},
title: {
text: "CandleStick Chart",
align: "left"
},
xaxis: {
type: "datetime"
},
yaxis: {
tooltip: {
enabled: true
}
},
tooltip: {
enabled: true,
custom: function({ seriesIndex, dataPointIndex, w }) {
const o = w.globals.seriesCandleO[seriesIndex][dataPointIndex]
const h = w.globals.seriesCandleH[seriesIndex][dataPointIndex]
const l = w.globals.seriesCandleL[seriesIndex][dataPointIndex]
const c = w.globals.seriesCandleC[seriesIndex][dataPointIndex]
return (
'<div class="apexcharts-tooltip-candlestick">' +
'<div>Open: <span class="value">' +
o +
'</span></div>' +
'<div>High: <span class="value">' +
h +
'</span></div>' +
'<div>Low: <span class="value">' +
l +
'</span></div>' +
'<div>Close: <span class="value">' +
c +
'</span></div>' +
'</div>'
)
},
theme: 'dark'
}
};
this.socketSubscription = this.binance.onNewCandle().subscribe(params => {
if(this.rendered) {
console.log(params)
this.chart.appendData([
{
data:
{
x: new Date(params.date),
y: [params.open, params.high, params.low, params.close]
}
}
]);
console.log(this.chart.series)
}
}
);
}
This is mainchart.component.html file:
<apx-chart #chart [series]="chartOptions.series"
[chart]="chartOptions.chart"
[xaxis]="chartOptions.xaxis"
[yaxis]="chartOptions.yaxis"
[title]="chartOptions.title"
[tooltip]="chartOptions.tooltip">
</apx-chart>
I have tried many ways to append this data (and rerender or update chart) and I'm out of ideas. If someone knows how to do this properly I have another couple of questions.
I want append existing data if it's x value isn't already in series - if it already is, it should be updated with new value, but I understand it well it will works like this with appendData(). Am I right?
In case I have more series and want to append more of them it works like this, right?:
this.chart.appendData([
{ |
data: |
{ |
x: new Date(params.date), | This object updating
y: [params.open, params.high, params.low, params.close] | series[0]
} |
},
|
{ |
data: |
{ |
x: new Date(params.date), | This object updating
y: [params.open, params.high, params.low, params.close] | series[1]
} |
} |
]);

Related

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

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

Ajax Response Capitalizations Rules

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

Merging 2 REST endpoints to a single GraphQL response

New to graphQL, I'm Using the following schema:
type Item {
id: String,
valueA: Float,
valueB: Float
}
type Query {
items(ids: [String]!): [Item]
}
My API can return multiple items on a single request of each type (A & B) but not for both, i.e:
REST Request for typeA : api/a/items?id=[1,2]
Response:
[
{"id":1,"value":100},
{"id":2,"value":30}
]
REST Request for typeB : api/b/items?id=[1,2]
Response:
[
{"id":1,"value":50},
{"id":2,"value":20}
]
I would like to merge those 2 api endpoints into a single graphQL Response like so:
[
{
id: "1",
valueA: 100,
valueB: 50
},
{
id: "2",
valueA: 30,
valueB: 20
}
]
Q: How would one write a resolver that will run a single fetch for each type (getting multiple items response) making sure no unnecessary fetch is triggered when the query is lacking the type i.e:
{items(ids:["1","2"]) {
id
valueA
}}
The above example should only fetch api/a/items?id=[1,2] and the graphQL response should be:
[
{
id: "1",
valueA: 100
},
{
id: "2",
valueA: 30
}
]
So I assumed you are using JavaScript as the language. What you need in this case is not to use direct query, rather use fragments
So the query would become
{
items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
}
}
Next in the resolver we need to access these fragments to find the fields which are part of the fragment and then resolve the data based on the same. Below is a simple nodejs file with same
const util = require('util');
var { graphql, buildSchema } = require('graphql');
var schema = buildSchema(`
type Item {
id: String,
valueA: Float,
valueB: Float
}
type Query {
items(ids: [String]!): [Item]
}
`);
var root = { items: (source, args, root) => {
var fields = root.fragments.data.selectionSet.selections.map(f => f.name.value);
var ids = source["ids"];
var data = ids.map(id => {return {id: id}});
if (fields.indexOf("valueA") != -1)
{
// Query api/a/items?id=[ids]
//append to data;
console.log("calling API A")
data[0]["valueA"] = 0.12;
data[1]["valueA"] = 0.15;
}
if (fields.indexOf("valueB") != -1)
{
// Query api/b/items?id=[ids]
//append to data;
console.log("calling API B")
data[0]["valueB"] = 0.10;
data[1]["valueB"] = 0.11;
}
return data
},
};
graphql(schema, `{items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
}
`, root).then((response) => {
console.log(util.inspect(response, {showHidden: false, depth: null}));
});
If we run it, the output is
calling API A
{ data:
{ items: [ { id: '1', valueA: 0.12 }, { id: '2', valueA: 0.15 } ] } }
If we change the query to
{
items(ids:["1","2"]) {
...data
}}
fragment data on Item {
id
valueA
valueB
}
}
The output is
calling API A
calling API B
{ data:
{ items:
[ { id: '1', valueA: 0.12, valueB: 0.1 },
{ id: '2', valueA: 0.15, valueB: 0.11 } ] } }
So this demonstrates how you can avoid call for api A/B when their fields are not needed. Exactly as you had asked for

Highcharts and JSON

Let me know how to declare multiple data in Y because my script doesn't work.
My JSON file is:
[{"metier":"Administratif","annee2005":182373,"annee2006":188153,"annee2007":194636},
{"metier":"Igenieur","annee2005":9140,"annee2006":9483,"annee2007":4321},
{"metier":"Chef de chantier","annee2005":25118,"annee2006":29417,"annee2007":32081},
{"metier":"Commercial","annee2005":13122,"annee2006":16294,"annee2007":17706}]
My Javascript is
$.getJSON('dataTest.json', function(data) {
var output = [];
$.each(data,function(i,d){
output.push(
{
name: d.metier,
y: [d.annee2005,d.annee2006]
}
);
});
$('#container').highcharts({
chart:{
type:'column'
},
xAxis:{
type:'category'
},
series: [{
name: 'Métiers',
data: output
}
]
});
});
In output.y you have array of two values but it must be one value for y, you need create 2 series for output 2 graphics

Ember Data: Saving relationships

I need to save a deep object to the server all at once and haven't been able to find any examples online that use the latest ember data (1.0.0-beta.4).
For example, with these models:
(jsfiddle)
App.Child = DS.Model.extend({
name: DS.attr('string'),
age: DS.attr('number'),
toys: DS.hasMany('toy', {async:true, embedded:'always'}),
});
App.Toy = DS.Model.extend({
name: DS.attr('string'),
child: DS.belongsTo('child')
});
And this code:
actions: {
save: function(){
var store = this.get('store'),
child, toy;
child = store.createRecord('child', {
name: 'Herbert'
});
toy = store.createRecord('toy', {
name: 'Kazoo'
});
child.set('toys', [toy]);
child.save();
}
}
It only saves the JSON for the child object but not any of the toys -- not even side loaded:
{
child: {
age: null
name: "Herbert"
}
}
Do I have to manually save the toys too? Is there anyway that I can have it send the following JSON to the server:
{
child: {
age: null
name: "Herbert",
toys: [{
name: "Kazoo"
}]
}
}
Or
{
child: {
age: null
name: "Herbert",
toys: [1]
}
}
See JSFiddle: http://jsfiddle.net/jgillick/LNXyp/2/
The answers here are out of date. Ember Data now supports embedded records, which allows you to do exactly what you're looking to do, which is to get and send the full object graph in one big payload. For example, if your models are set up like this:
App.Child = DS.Model.extend({
name: DS.attr('string'),
age: DS.attr('number'),
toys: DS.hasMany('toy')
});
App.Toy = DS.Model.extend({
name: DS.attr('string'),
child: DS.belongsTo('child')
});
You can define a custom serializer for your Child model:
App.ChildSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
toys: {embedded: 'always'}
}
});
This tells Ember Data that you'd like 'toys' to be included as part of the 'child' payload. Your HTTP GET response from your API should look like this:
{
"child": {
"id": 1,
"name": "Todd Smith",
"age": 5,
"toys": [
{"id": 1, "name": "boat"},
{"id": 2, "name": "truck"}
]
}
}
And when you save your model, Ember Data will send this to the server:
{
"child":{
"name":"Todd Smith",
"age":5,
"toys":[
{
"id":"1",
"name":"boat",
"child":"1"
},
{
"id":"2",
"name":"truck",
"child":"1"
}
]
}
}
Here is a JSBin that demonstrates this.
http://emberjs.jsbin.com/cufaxe/3/edit?html,js,output
In the JSbin, when you click the 'Save' button, you'll need to use the Dev Inspector to view the request that's sent to the server.
toys can't be both async and embedded always, those are contradicting options. Embedded only exists on the active model serializer currently.
toys: DS.hasMany('toy', {embedded:'always'})
the toys are a ManyToOne relationship, and since the relationship exists on the belongsTo side it is more efficient to save the relationship during the toy's save. That being said, if you are creating it all at once, then want to save it in one big chunk that's where overriding comes into play.
serializeHasMany: function(record, json, relationship) {
var key = relationship.key;
var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany' ||
relationshipType === 'manyToOne') {
json[key] = get(record, key).mapBy('id');
// TODO support for polymorphic manyToNone and manyToMany relationships
}
},
And your save should be like this
var store = this.get('store'),
child, toy;
child = store.createRecord('child', {
name: 'Herbert'
});
toy = store.createRecord('toy', {
name: 'Kazoo'
});
child.get('toys').pushObject(toy);
child.save().then(function(){
toy.save();
},
function(err){
alert('error', err);
});
I needed a deep object, instead of a side-loaded one, so based on kingpin2k's answer, I came up with this:
DS.JSONSerializer.reopen({
serializeHasMany: function(record, json, relationship) {
var key = relationship.key,
property = Ember.get(record, key),
relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
if (property && relationshipType === 'manyToNone' || relationshipType === 'manyToMany' ||
relationshipType === 'manyToOne') {
// Add each serialized nested object
json[key] = [];
property.forEach(function(item, index){
json[key].push(item.serialize());
});
}
}
});
Now when you call child.serialize(), it will return this object:
{
child: {
name: "Herbert",
toys: [
{
name: 'Kazoo'
}
]
}
}
Which is what I need. Here's the jsfiddle with it in action: http://jsfiddle.net/jgillick/LNXyp/8/