Initialize several charts in a loop - javascript

On my page, I want to show multiple charts that are loaded via ajax. So I get html that is something like this:
<h4>Chart 1</h4>
<canvas data-type="bar" data-labels='["Label 1", "Label 2"]' data-data='[10,20]'></canvas>
<h4>Chart 2</h4>
<canvas data-type="pie" data-labels='["Label 3", "Label 4"]' data-data='[30,40]'></canvas>
As you can see, the charts can be of different types and I have an object holding all the configuration of charts for each type
const chartConfigs = {
bar: {
type: 'bar',
data: {
labels: [],
datasets: [{
data: [],
pointRadius: 2,
backgroundColor: '',
maxBarThickness: 50
}]
},
options: {
legend: {
display: false
},
scales: barScalesOptions
}
},
pie: {
type: 'pie',
data: {
labels: [],
datasets: [{
data: [],
backgroundColor: ['#84c267', '#c9556a'],
borderWidth: 0
}],
},
options: {
legend: {
labels: {
fontColor: '#CCC',
}
},
aspectRatio: 1,
responsive: true
}
}
}
And I loop through all the canvases to initialize them.
container.querySelectorAll('canvas').forEach(canv => {
const chartOptions = chartConfigs[canv.dataset.type];
chartOptions.data.datasets[0].data = JSON.parse(canv.dataset.data);
if(canv.dataset.labels != undefined)
chartOptions.data.labels = JSON.parse(canv.dataset.labels);
console.log(JSON.stringify(chartOptions));
new Chart(canv, chartOptions);
});
But the problem is that all the charts are rendered the same - Labels and Data. I'm assuming its because chartOptions is a copy by reference. Its a pretty difficult task to do a deep copy as this is a nested object and I also need functions in them. But even if I somehow did this task, it would be a memory management nightmare as there are many charts on the page.
If you have done something like this before, please share a better way of doing this.

A quick solution is to clone the needed part of the object, with the handy function(s) JSON.parse and JSON.stringify, it makes sure it breaks all references (as mentioned on mdn).
container.querySelectorAll('canvas').forEach(canv => {
const chartOptions = JSON.parse(JSON.stringify(chartConfigs[canv.dataset.type]));
chartOptions.data.datasets[0].data = JSON.parse(canv.dataset.data);
if(canv.dataset.labels != undefined){
chartOptions.data.labels = JSON.parse(canv.dataset.labels);
console.log(JSON.stringify(chartOptions));
new Chart(canv, chartOptions);
});
Since I can't see any functions in the object chartOptions the serializing and deserializing should be no problem?
Update, for object with functions (for your specific case):
I see two easy options,
just extract the functions from the base object and just pass the current object
Or if you don't want to alter the chartConfigs object, just use the prototype function, call (link to documentation). With other words change the function calls to:
// clone
const chartOptions = JSON.parse(JSON.stringify(chartConfigs[canv.dataset.type]));
...
let id = 1;
let value = 100;
// call the function
chartConfigs[chartOptions.typ].testFunction.call(chartOptions, id, value);
...
(if testFunction would be a function, with 2 parameters ( id, value))
Is not very sexy, but is a fast solution, that will need little code modifications.

Related

Why prettier put a comma ',' at the last element of the object

In Visual studio code, When I am using chart.js in my app, prettier always put a comma at the end of the last data of the object 'label'. I think, it's create a bug which unable to show my chart on my browser. it show blank. Code is given bellow.
let massPopChart2 = new Chart(myChart2, {
type: "bar", // bar, horizontalBar, pie, line, doughnut, radar, polarArea
data: {
labels: [
"1st Day",
"2nd Day",
"3rd Day",
"4th Day",
"5th Day",
"6th Day",
"7th Day",
],
},
});
can anyone help me figure out why this happening?
JavaScript has allowed trailing commas in array literals since the
beginning, and later added them to object literals (ECMAScript 5) and
most recently (ECMAScript 2017) to function parameters.
This is a relatively new change in syntax, but the basic idea is that by putting a comma on each line allows for:
Easier to add an item or re-order items. Before you always had to check the trailing comma and make sure it was present or removed depending on location.
Removes the need to have one line item be special because it lacks the ,.
Allows for cleaner Git diffs.
You can read up on the full documentation if you like - it goes into further detail:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas
As far as the issue with your chart not displaying, unless you are using a very old browser, a trailing comma should not cause an error/information to not display.
You need to update the configuration of prettier extension.
There are two ways. Below one is mostly used.
Create a .prettierrc file at the root of your project and
specifying the below configuration.
{ "trailingComma": "es5" }
In order to honor the configuration make sure to enable the below
setting in vs code configuration.
"prettier.requireConfig": true
Prettier adds those commas at the end just because if you wanna add another data after that you don't need to type a comma. it does the same for semicolons(;).
you got the error because you haven't provided datasets.
data takes an object which contains labels & datasets values.
{/* <canvas id="myChart" width="400" height="400"></canvas> */}
// var ctx = document.getElementById('myChart');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['1','2'],
datasets: [
{
label: '1st',
data: '120',
borderColor: Utils.CHART_COLORS.red,
backgroundColor: Utils.transparentize(Utils.CHART_COLORS.red, 0.5),
},
{
label: '2',
data: '240',
borderColor: Utils.CHART_COLORS.red,
backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
}
]
},
// options: {
// indexAxis: 'y',
// elements: {
// bar: {
// borderWidth: 2,
// }
// },
// responsive: true,
// plugins: {
// legend: {
// position: 'right',
// },
// title: {
// display: true,
// text: 'Chart.js Horizontal Bar Chart'
// }
// }
// },
// };
you can know more about it on official docs https://www.chartjs.org/docs/latest/charts/bar.html

How to wrap this behavior in a plugin?

Currently I have a request to have a Bullet Chart with two targets (min and max).
To do it I am simply using a normal Bullet Chart with a Scatter series to draw the other target. I would like to wrap this behavior inside the bullet chart, so it would have something like the following options:
series: [{
data: [{
y: 275,
target: 250,
minTarget: 100
}]
},
And then, on the wrap, I would get this minTarget and make a scatter plot automatically. How can I do it?
Here's the fiddle I have so far: http://jsfiddle.net/gwkxd02p/
I do not think that render is a good method to add another series - anyway, you can try to do it like this:
Highcharts.wrap(Highcharts.seriesTypes.bullet.prototype, 'render', function(p) {
if (!this.hasRendered) {
const scatterData = this.points
.map(({ x, y, options }) => ({
x,
y: options.minTarget !== undefined ? options.minTarget : null
}))
if (scatterData.length) {
const scatter = this.chart.addSeries({
type: 'scatter',
data: scatterData,
marker: {
symbol: 'line',
lineWidth: 3,
radius: 8,
lineColor: '#000'
}
}, false)
scatter.translate()
scatter.render()
}
}
p.call(this)
})
And data for bullet:
series: [{
data: [{
y: 275,
target: 250,
minTarget: 100
}, {
y: 100,
target: 50
}, {
y: 500,
target: 600,
minTarget: 20
}]
live example: http://jsfiddle.net/n4p0ezzw/
I think that the better place is bullet's init method but in that method the points do not exist yet - so you would have to match the x values (if it is needed) on your own.
My suggestion is - do not wrap Highcharts if you don't have to. A better (simpler, safer, cleaner, easier to debug, it does not change Highcharts internal code) practice would be to wrap the Highcharts constructor in a function and parse the options inside it and then call the chart constructor with new options, like this:
function customBullet(container, options) {
const newOptions = {} // parse options, check for minTarget, etc. and create new options
return Highcharts.chart(container, newOptions)
}

Chart.js - Mouseover causes graphs to flicker and resize

To start, I have made a short video to show exactly what I'm running into.
To summarize the video: while using Chart.js (2.6.0), I can create my charts without issue; but when I mouse-over the bars/points, the chart will resize its elements and flicker. The weird thing is that it's totally inconsistent. Sometimes when I refresh, it doesn't have this behaviour at all; but if I hover over something and it starts doing it, it won't stop until I refresh again or close out of the tab (it is inconsistent with this, also). I don't change anything in the code when this occurs, it does this all on its own.
In an attempt to fix it, I've referenced many other threads here on SO, as well as the Chart.js documentation. Among my solutions: I have made a point to add in a specified Height/Width to the Divs & Canvas creating the graphs; Set the Animation duration to 0, the Hover Animation duration to 0, and the Responsive Animation duration to 0; I've ensured that Responsive is set to true, and have kept Maintain Aspect Ratio as true, changed the tooltip mode... I've tried all of these, among other little things that seem to have little-to-no effect.
I'm stumped!
Here is one of my charts' code (without how I'm grabbing the JSON data etc, just the Chart):
new Chart($("#runwayChart"), {
type: "horizontalBar",
data: {
labels: runwayLabels,
datasets: [{
label: "Months Left", fill: true,
backgroundColor: "#3333ff",
borderColor: "#3333ff",
data: score
}, {
label: "Expenses",
fill: true,
backgroundColor: "#aa2222",
borderColor: "#aa2222",
data: expenses
}, {
label: "Revenue",
fill: true,
backgroundColor: "#2222aa",
borderColor: "#2222aa",
data: revenues
}]
},
options: {
tooltips: {
mode: 'index'
},
responsive: true,
maintainAspectRatio: true,
animation: {
duration: 0,
},
hover: {
animationDuration: 0,
},
responsiveAnimationDuration: 0
}
});
I'd appreciate any help you all may have!
Thanks =)
I see that it has been a while since somebody wrote an answer to this post. I solved my flickering issue by applying two things.
First one
When I declare the chart I use:
var ctx = document.getElementById('chart').getContext('2d');
window.chart = new Chart(ctx, {}) ...
rather than var chart = new Chart(ctx, {})..
In this way, we make sure that the chart has been appended to the window. object.
Secondly
Before drawing the new diagram (For example for data update) we need to make sure that the previous canvas has been destroyed. And we can check that with the code below:
if(window.chart && window.chart !== null){
window.chart.destroy();
}
It was actually a really simple, and odd solution.
When the data point was near the top of the chart, the chart would try to resize depending on the div. As the chart lived in a larger canvas, putting inside its own div solved this issue.
<div>
<canvas id="chart"></canvas>
</div>
Formatting it like this was the solution =)
Try This :
var myLineChart = null;
function createChart() {
var ctx1 = document.getElementById("barcanvas").getContext("2d");
myLineChart = new Chart(ctx1, {
type: 'horizontalBar',
data: {
labels: runwayLabels
, datasets: [{
label: "Months Left"
, fill: true
, backgroundColor : "#3333ff"
, borderColor: "#3333ff"
, data: score
}, {
label: "Expenses"
, fill: true
, backgroundColor : "#aa2222"
, borderColor: "#aa2222"
, data: expenses
}, {
label: "Revenue"
, fill: true
, backgroundColor : "#2222aa"
, borderColor: "#2222aa"
, data: revenues
}]
}
options:
{
scales: {
xAxes: [{
ticks: {
callback: function (tick) {
var characterLimit = 20;
if (tick.length >= characterLimit) {
return tick.slice(0, tick.length).substring(0, characterLimit - 1).trim() + '...';
}
return tick;
}
}
}]
},
tooltips: {
callbacks: {
// We'll edit the `title` string
title: function (tooltipItem) {
// `tooltipItem` is an object containing properties such as
// the dataset and the index of the current item
// Here, `this` is the char instance
// The following returns the full string
return this._data.labels[tooltipItem[0].index];
}
}
},
title:
{
display: true,
text: "Your Chart Title"
},
responsive: true,
maintainAspectRatio: true
}
});
}
I had the same issue with my angular application(angular v6 and chartjs 2.9.4).
After adding delay and destroying the chart instance before redrawing the chart resolved my issue.
public redraw() {
setTimeout(() => {
if (this.chart && this.chart != null) {
this.chart.destroy()
}
this.chart = new Chart(this.chartId, this.chartConfig);
}, 500);
}

Javascript: how can I populate an array of property values dynamically?

I have an array of values, which I want to insert into a property of an object, but I'm not sure how. Here's my object. The property is called "values" (located at the very bottom), and as you can see, I'm trying to insert a dynamic list of data (called "result") into it:
var myConfig = {
globals: {
fontFamily: "Roboto"
},
type: "bar",
backgroundColor: "#f4f2f2",
plotarea: {
backgroundColor: "#fff"
},
scaleX: {
lineColor: "#7d7d7d",
tick: {
lineColor: "#7d7d7d"
},
guide: {
visible: false
},
values: [result[0]["Heading"], result[1]["Heading"], result[2]["Heading"], ...],
}};
Is there any way I can set this up to dynamically place this result["Heading"] data into my "values" property?
Thanks
So, assuming results is an array of objects that have the Heading property, you can get an array of only those, using the map function, like this:
values: result.map(function(item){ return item.Heading; })
map is a new-ish function, defined in ECMAScript 5.1, but all major browsers support it. Basically, for every item in the array, it will execute the provided selector function, and return the result. So, you're starting with an array of objects having a Heading property, and ending up with an array of the Heading property values themselves.
Make another function to do that.
It's an Array.
You should traverse it at least once.
function getHeading( arr ) {
var aa = [];
for( var i = 0, size = arr.length ; i < size ; i++ ) {
aa.push( arr[i].Heading );
}
return aa;
}
var myConfig = {
globals: {
fontFamily: "Roboto"
},
type: "bar",
backgroundColor: "#f4f2f2",
plotarea: {
backgroundColor: "#fff"
},
scaleX: {
lineColor: "#7d7d7d",
tick: {
lineColor: "#7d7d7d"
},
guide: {
visible: false
},
values: getHeading( result ),
}};

Highcharts navigator not working with data set

Can someone please take a look at this example? It works when I use a smaller data set, but when I use a larger historical data set, it stops working and the data series does not render. Please help.
Small data set example - http://jsfiddle.net/Yrygy/250/
Large data set example (SERIES DOES NOT RENDER) - http://jsfiddle.net/Yrygy/249
var chart = new Highcharts.StockChart({
chart: {
renderTo: 'container',
height: 120
},
navigator: {
series: {
data: chartData
}
},
series: [{
data: [null],
markers: {
enabled:true
}
}]
});
You need to have your data sorted in ascending time order. Currently your "large" data set is not.
There is a couple of problems. First of all, as said #wergeld, you need to sort your data:
chartData.sort(function(a,b) { return a[0] - b[0]; });
Then the problem is with setting option for navigator:
navigator: {
series: [{
name: 'MSFT',
data: chartData
}]
},
When should be an object, not an array:
navigator: {
series: {
name: 'MSFT',
data: chartData
}
},
And the last one, do you really need to set xAxis.min/max ? Escpecially to values 2 and 4. when you have timestamps like
Working demo: http://jsfiddle.net/Yrygy/253/

Categories

Resources