I've found a weird behavior of a site that I'm developing. It is being developed using Laravel 5.8 and uses the React preset to build all the front end. This project has a section of stats, and in some of them you need to show a total field.
When I use the php artisan serve to run my project, and access the stats, the results are displayed and calculated correctly. The problem comes when I deploy this site on Apache using AMPPS. When I do this, the total is calculated as a string, so for example, if I have a sum of 1+0, instead of get a 1, I'm getting a 10. It is concatenating the integers as strings.
This is the result when I'm using the php artisan serve
And this one when I use apache:
This is my client code:
constructor(props) {
super(props);
this.statsRoute = 'attendants/classification';
this.state = {
barData: {
labels: [],
datasets: [
{
backgroundColor: [],
borderWidth: 1,
hoverBackgroundColor: [],
data: []
}
],
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
legend: {
display: false,
}
},
tableRepresentation: {
header: ['Clasificación', 'Número de Asistentes'],
rows: []
}
},
// Pie chart data
pieData: {
labels: [],
datasets: [
{
backgroundColor: [],
borderWidth: 1,
hoverBackgroundColor: [],
data: [],
total: 0
}
],
options: {
legend: {
display: true,
position: 'bottom',
},
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
let dataset = data.datasets[tooltipItem.datasetIndex];
let currentValue = dataset.data[tooltipItem.index].toFixed(2);
let label = data.labels[tooltipItem.index];
return ` ${label}: ${currentValue}%`;
}
}
}
},
tableRepresentation: {
header: ['Clasificación', 'Porcentaje'],
rows: []
}
}
}
}
componentWillMount() {
this.props.statService.getStats(this.statsRoute)
.then(res => {
let total = 0;
let barData = this.state.barData;
let pieData = this.state.pieData;
let colors = this.props.randomColorService.getArrayOfColors(res.length);
for ( let i = 0; i < res.length; i++ ) {
let item = res[i];
total += item.count;
barData.tableRepresentation.rows.push([
item.classification,
item.count
]);
barData.labels.push(item.classification);
barData.datasets[0].data.push(item.count);
pieData.datasets[0].data.push(item.count * 100);
}
// Assign labels and colors to pie chart
pieData.labels = barData.labels;
barData.datasets[0].backgroundColor = colors['withAlfa'];
barData.datasets[0].hoverBackgroundColor = colors['withoutAlfa'];
// Assign colors to bar chart
pieData.datasets[0].backgroundColor = colors['withAlfa'];
pieData.datasets[0].hoverBackgroundColor = colors['withoutAlfa'];
for ( let i = 0; i < pieData.datasets[0].data.length; i++ ) {
pieData.datasets[0].data[i] /= total;
let value = pieData.datasets[0].data[i];
pieData.tableRepresentation.rows.push([
pieData.labels[i],
`${value.toFixed(2)}%`
]);
}
barData.tableRepresentation.rows.push([
"",
`Total: ${total}`
]);
this.setState({
barData: barData,
pieData: pieData
});
})
.catch(err => {
console.log(err);
});
}
And I doubt this is a problem from the backend, because when I do the query I got a JSON like this:
{
"data": [
{
"item": "Invitado",
"count": 6
},
{
"item": "Asistente",
"count": 7
}
]
}
So, the backend does not make the total sum, I do that on the client.
Why this behaviour only happens when I'm using apache as server?
Try replacing total += item.count; with total += +item.count;.
+ will explicitly convert it into int, right now for the apache version, the numbers are being treated as strings and are concatenated rather than added.
Using chart.js 2.6 Is there a way to dynamically change the bars in my chart for values above zero and below zero? The graph series data is being generated via a call to a method. Right now its just a random number generator but will be a DB call.
function changeWOWData(chart) {
var datasets = chart.data.datasets;
var labelLen = chart.data.labels.length;
if (datasets[0]) {
for (i = 0, len = datasets.length; i < len; i++) {
try {
for (j = 0, len = labelLen; j < len; j++) {
datasets[i].data[j] = getRandomInt(-100, 100);
}
} catch (e) {
console.log(e.message);
}
}
}
}
Chart looks like this:
I want the chart bars above zero to be blue, the bars below zero to be red.
Any/all replies appreciated. Thanks in advance!
Griff
** Edit ** Added the code from the answer below as such:
var myBarChart = new Chart(wowChart, {
type: 'bar',
data: wowData,
plugins: [{
beforeDraw: function (c) {
var data = c.data.datasets[0].data;
for (var i in data) {
try {
var bar = c.data.datasets[0]._meta[0].data[i]._model;
if (data[i] > 0) {
bar.backgroundColor = '#07C';
} else bar.backgroundColor = '#E82020';
} catch (ex) {
console.log(ex.message);
}
console.log(data[i]);
}
}
}],
options: wowOptions
});
Every other line of the console I see the data element along with the exception
You could accomplish that using the following chart plugin :
plugins: [{
beforeDraw: function(c) {
var data = c.data.datasets[0].data;
for (let i in data) {
let bar = c.data.datasets[0]._meta['0'].data[i]._model;
if (data[i] > 0) {
bar.backgroundColor = '#07C';
} else bar.backgroundColor = '#E82020';
}
}
}]
add this followed by your chart options
ᴅᴇᴍᴏ
var ctx = document.getElementById("canvas").getContext('2d');
var myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'LEGEND',
data: [9, 14, -4, 15, -8, 10]
}]
},
options: {},
plugins: [{
beforeDraw: function(c) {
var data = c.data.datasets[0].data;
for (let i in data) {
let bar = c.data.datasets[0]._meta['0'].data[i]._model;
if (data[i] > 0) {
bar.backgroundColor = '#07C';
} else bar.backgroundColor = '#E82020';
}
}
}]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="canvas" height="180"></canvas>
in chartjs v3 you can use Simply scriptable option
example:
datasets: [
{
data: this.chartData,
backgroundColor(context) {
const index = context.dataIndex
const value = context.dataset.data[index]
return value < 0 ? 'red' : 'blue'
}
}
]
visit https://www.chartjs.org/docs/latest/general/options.html#scriptable-options
I have created a bubble chart using chart.js,which looks like the below
Is there a way to name each and every bubble in the chart? I am planning to put a data box below this chart. On clicking each bubble data box should display info associated with each bubble. Each bubble will have its own data like maturity_date,bond_type,credit_rating,symbol,etc... How can I name each bubble? These bubbles are created dynamically. This is the code I use to create the chart
$(document).ready(function(){
$.ajax({url: "xxxxxxxx.x.xx", success: function(result){
var dataObj = {};
dataObj.datasets = [];
var object = {};
object.label = 'First Dataset';
object.backgroundColor = [];
object.hoverBackgroundColor = [];
object.data = [];
var resultData = result.data;
var currentYear = new Date().getFullYear();
for (var i=0; i<resultData.length; i++) {
if(resultData[i].hasOwnProperty("maturity_date") && resultData[i].hasOwnProperty("ask_ytm")) {
var maturity_date = resultData[i].maturity_date.split("-");
var matYear = new Date(maturity_date[1]+"-"+maturity_date[0]+"-"+maturity_date[2]).getFullYear();
if (resultData[i].bond_type == "Tax Free" )
{
object.backgroundColor.push("#34A10C");
object.hoverBackgroundColor.push("#34A10C");
}
else
{
object.backgroundColor.push("#1130E8");
object.hoverBackgroundColor.push("#1130E8");
}
object.data.push({x: (matYear - currentYear), y: resultData[i].ask_ytm, r: 4});
}
}
dataObj.datasets.push(object);
var ctx = document.getElementById("myChart");
var myBubbleChart = new Chart(ctx,{
type: 'bubble',
data : dataObj,
legend: {
display: false
},
responsive: true,
maintainAspectRatio: true,
}
});
}});
});
In your data declaration, you can add custom properties if you need to :
data: [{
x: 20,
y: 30,
r: 15,
symbol: "£",
bond_type: "corporate"
}, {
x: 40,
y: 10,
r: 10,
symbol: "$",
bond_type: "james"
} /* ... */]
Since this data is dynamic, you need to do it from your back-end of course.
Afterwards, you can access these new properties in your callback (onClick event for instance) :
options: {
onClick: function(e) {
var element = this.getElementAtEvent(e);
if (element.length > 0) {
var data = this.config.data.datasets[element[0]._datasetIndex].data[element[0]._index];
console.log(data);
// You can have the following for instance :
// data -> { x:40, y:10, r:10, symbol:"$", bond_type:"james" }
}
}
}
Can anyone explain to me how to pass a variable HERE:
series: {regions: [{values:{Variable: '#B8E186',}}]},
Commented parts is what I tried so far. Syntax there works like this {SE:'#000FFF',}
//var myArray = [];
//myArray.push({ 'NO': '#000FFF' });
//var a = [], b = parsedData.Item1, c= '#000FFF';
//a.push('b', 'c');
//var cdata = {
map: 'world_mill',
scaleColors: ['#C8E111', '#007FFF'],
normalizeFunction: 'polynomial',
hoverOpacity: 0.7,
hoverColor: false,
markerStyle: {
initial: {
fill: '#F8E111',
stroke: '#383f47'
}
},
series: {
regions: [{
values:
{
}
}]
},
regionStyle: {
initial: {
fill: '#B8E186'
}
},
backgroundColor: '#FFFFF',
//markers: [
// { latLng: [67.16, 14.24], name: 'Norway' },
//]
//});
//};
//jQuery.each(cdata.mapData.paths, function() {
// var continentCodes = {};
// continentCodes['NO'] = "#128da7";
//cdata.series.regions[0].setValues(continentCodes);
});
//var mapObject = $('#world-map').vectorMap('camel', 'mapObject');
//mapObject.series.regions[0].setValues(parsedData.Item1) = '#000FFF';
//});
});
Assuming that series will have multiple regions, and value of region will have multiple variable values
You will need two for loops
var series = {};
for (i = 0; i < regions.length; i++) {
{
var regions = [];
var values = {};
for (j = 0; j < ValuesInRegion.length; j++) {
{
values[ValuesInRegion[j]['key']] = ValuesInRegion[j]['value'];
}
regions.push(values);
}
series['regions'] = regions;
finally
cdata.series = series;
I'm building irregular time graphs with HighCharts that at the moment look like so:
And I'm wondering if it's possible to create an 'average' line for the three (or possibly more in future) lines.
It would start following the blue line, then go closer to the green line mid-January, etc.
At the moment the code I'm working with looks like:
$('#chart').highcharts({
chart: { type: 'spline' },
title: { text: '' },
xAxis: { type: 'datetime' },
yAxis: {
title: { text: '' }
}
series: [{
name: 'Line 1',
data: [
[Date.UTC(2014,0,16), 173.33],
[Date.UTC(2014,0,23), 163.33],
[Date.UTC(2014,0,30), 137.67],
[Date.UTC(2014,1,6), 176.33],
[Date.UTC(2014,1,13), 178.67],
[Date.UTC(2014,1,27), 167.33],
],
color: 'purple'
},
{
name: 'Line 2',
data: [
[Date.UTC(2014,0,11), 156.33],
[Date.UTC(2014,1,15), 167.67],
],
color: 'green'
},
{
name: 'Line 3',
data: [
[Date.UTC(2014,0,1), 135],
[Date.UTC(2014,0,5), 146.33],
[Date.UTC(2014,0,27), 146.75],
],
color: 'blue'
}]
});
What you are describing is called a trend or regression line. Highcharts doesn't have a built in ability to add these lines, but the math isn't too difficult (and besides, it's more fun to do it yourself). I've coded up the simplest example I can using least squared linear regression.
/////////////////////
//utility functions//
////////////////////
// linear regression
// given array of x values and array of y values
// returns rV object with slope/intercept
lineFit = function(xs, ys, rV){
rV.slope = 0.0;
rV.intercept = 0.0;
rV.rSquared = 1.0; // assume perfection
if (xs.length < 2)
{
return false;
}
if (xs.Count != ys.Count)
{
return false;
}
var N = xs.length;
var sumX = sumFunc(xs,null);
var sumY = sumFunc(ys,null);
var funcSq = function(i){return (i*i);}
var funcSq2 = function(i,j){return (i*j);}
var sumXx = sumFunc(xs, funcSq);
var sumYy = sumFunc(ys, funcSq);
var sumXy = sumFunc(zip(xs,ys),funcSq2);
rV.slope = ((N * sumXy) - (sumX * sumY)) / (N * sumXx - (sumX*sumX));
rV.intercept = (sumY - rV.slope * sumX) / N;
rV.rSquared = Math.abs((rV.slope * (sumXy - (sumX * sumY) / N)) / (sumYy - ((sumY * sumY) / N)));
return true;
}
// sums arrays with optional function transformation
sumFunc = function(arr, func){
var total = 0;
$.each(arr, function(i,k){
if ($.isArray(k)){
if (func == null){
k = k[0] + k[1];
}else{
k = func(k[0],k[1]);
}
} else {
if (func != null){
k = func(k);
}
}
total += k;
});
return total;
}
// python style zip function
// to pair to array together
zip = function(arr1,arr2) {
var rV = [];
for(var i=0; i<arr1.length; i++){
rV.push([arr1[i],arr2[i]]);
}
return rV;
}
The lineFit function will return the rV object (by reference) with attributes of slope and intercept. After that you can add a line to Highcharts with good old fashioned y = slope * x + intercept and minX is the starting value for the regression line and maxX is the ending value.
{
name: 'Regression Line',
data: [[minX, reg.slope * minX + reg.intercept],
[maxX, reg.slope * maxX + reg.intercept]],
color: 'red',
marker:{enabled:false},
lineWidth: 5
}
Working fiddle here.
Based on ideas provided by the answer from Mark, I wrote some code to generate a custom fourth line, using the data from all three lines, and calculating the required value for each point.
My new code is as follows:
line1 = [
[Date.UTC(2014,0,16), 173.33],
[Date.UTC(2014,0,23), 163.33],
[Date.UTC(2014,0,30), 137.67],
[Date.UTC(2014,1,6), 176.33],
[Date.UTC(2014,1,13), 178.67],
[Date.UTC(2014,1,27), 167.33],
];
line2 = [
[Date.UTC(2014,0,11), 156.33],
[Date.UTC(2014,1,15), 167.67],
];
line3 = [
[Date.UTC(2014,0,1), 135],
[Date.UTC(2014,0,5), 146.33],
[Date.UTC(2014,0,27), 146.75],
[Date.UTC(2014,2,2), 168.75]
];
function average(array, index) {
sum = array[0][1];
for(var i = 1; i <= index; i++) {
sum += array[i][1];
}
value = sum / (index + 1);
return parseFloat(value.toFixed(2));
}
// Make a fourth line with all of the data points for the other
// three lines, sorted by date
all_lines = line1.concat(line2).concat(line3);
all_lines.sort(function(a, b) { return a[0] - b[0]});
// Calculate the value for each data point in the fourth line -
// the average of all the values before it
average_line = [];
for(var i = 0; i < all_lines.length; i++) {
average_line.push([all_lines[i][0], average(all_lines, i)])
}
$('#chart').highcharts({
chart: { type: 'spline' },
title: {
text: '',
},
xAxis: {
type: 'datetime'
},
yAxis: {
title: {
text: ''
}
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle',
borderWidth: 0
},
series: [{
name: 'Line 1',
data: line1,
color: 'purple'
},
{
name: 'Line 2',
data: line2,
color: 'green'
},
{
name: 'Line 3',
data: line3,
color: 'blue'
},
{
name: 'Average',
data: average_line,
color: 'red'
}]
});
The graph as it looks now (with one extra data point on the blue line) is: