I am attempting to display 3 charts on this page. 2 of the 3 display perfectly, I asked a different question as I thought the issue was related to the syntax of the 2nd chart (cone) however a friendly user pointed out that the syntax is sound there, so alas I post back with full on syntax and hopefully someone can present me with the resolution to have all 3 charts display on my page.
And the exact error I receive is:
Uncaught type error. Can not read property 'data' of undefined.
On this line data: sb }] and the red X in the dev console is directly after the ]
<?php
$option = array();
$option['driver'] = 'mssql';
$option['host'] = 'Server';
$option['user'] = 'User';
$option['password'] = 'Pass';
$option['database'] = 'DB';
$option['prefix'] = '';
$db = JDatabaseDriver::getInstance($option);
$sql = $db->getQuery(true);
$sql = "Select * from green";
$db->setQuery($sql);
$rows = $db->loadRowList();
$output = array();
foreach ($rows as $row) {
array_push($output, $row);
}
$data = json_encode($output[0]);
$sql = "Select * from alpha";
$db->setQuery($sql);
$rows = $db->loadRowList();
$newoutput = array();
foreach ($rows as $row) {
array_push($newoutput, $row);
}
$newop = json_encode($newoutput[0]);
$sql = "Select * from jibjab";
$db->setQuery($sql);
$rows = $db->loadRowList();
$joc = array();
foreach ($rows as $row) {
array_push($joc, $row);
}
$yoytr = json_encode($joc[0]);
?>
<html>
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
</head>
<style>
.doublecolumn { -webkit-column-count: 2; /* Chrome, Safari, Opera */ -moz-column-count: 2; /* Firefox */ column-count: 2;}
</style>
<body>
<h1><?php echo $paramname; ?> Place Header Here</h1>
<div class="doublecolumn">
<div id="container" style="width: 100%;"><canvas width:="100px;" id="canvas"></canvas></div>
<div id="containerone" style="width: 100%;"><canvas width:="100px;" id="cone"></canvas></div></div>
<script>
"use strict";
var jsondata = <?php echo $data; ?>;
var values = [];
for (var i = 0; i < jsondata.length; i++) {
values.push(jsondata[i]);
}
var jdata1 = <?php echo $newop; ?>;
var values1 = [];
for (var i = 0; i < jdata1.length; i++) {
values1.push(jdata1[i]);
}
var jdata2 = <?php echo $yoytr; ?>;
var yoyvalues = [];
for (var i = 0; i < jdata2.length; i++) {
yoyvalues.push(jdata2[i]);
}
var mainlabels = ["Jose 12", "Jose 13", "Jay 12", "Jay 13", "Rob 12", "Rob 13"];
var salesbyperson = [21, 31, 21, 16, 22, 24];
var ctx = document.getElementById('canvas').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: mainlabels,
datasets: [{
label: 'First',
backgroundColor: 'rgba(0, 129, 214, 0.8)',
data: salesbyperson
}]
},
options: {
tooltips: {
callbacks: {
label: function (t, d) {
var xLabel = d.datasets[t.datasetIndex].label;
var yLabel = t.yLabel >= 1000 ? '$' + t.yLabel.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : '$' + t.yLabel;
return xLabel + ': ' + yLabel;
}
}
},
legend: {
display: false,
position: 'top',
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: function (value, index, values) {
if (parseInt(value) >= 1000) {
return '$' + value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else {
return '$' + value;
}
}
}
}]
}
}
});
var ml = ["Jose 12", "Jose 13", "Jay 12", "Jay 13", "Rob 12", "Rob 13"];
var sb = [21, 31, 21, 16, 22, 24];
var ctx = document.getElementById('cone').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ml,
datasets: [{
label: 'Sum of Sales',
backgroundColor: 'rgba(0, 129, 214, 0.8)',
data: sb
}]
},
options: {
tooltips: {
callbacks: {
label: function(t, d) {
var xLabel = d.datasets[t.datasetIndex].label;
var yLabel = t.yLabel >= 1000 ? '$' + t.yLabel.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : '$' + t.yLabel;
return xLabel + ': ' + yLabel;
}
}
},
legend: {
display: false,
position: 'top',
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: function(value, index, values) {
if (parseInt(value) >= 1000) {
return '$' + value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else {
return '$' + value;
}
}
}
}]
}
},
plugins: [{
beforeDraw: function(chart) {
var labels = chart.data.labels;
labels.forEach(function(e, i) {
var bar = chart.data.datasets[0]._meta['0'].data[i]._model;
var dataPoint = e.split(/\s/)[1];
if (dataPoint === '12')
bar.backgroundColor = 'blue';
else if (dataPoint === '13')
bar.backgroundColor = 'green';
});
}
}]
});
</script>
<h1><?php echo $paramname; ?> Place Header Here</h1>
<div id="containerGPPercent" style="width: 50%;"><canvas width:="100px;" id="cavasme"></canvas></div>
<script>
var m2 = ["Jose 12", "Jose 13", "Jay 12", "Jay 13", "Rob 12", "Rob 13"];
var sb1 = [21, 31, 21, 16, 22, 24];
var ctx = document.getElementById('cavasme').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: m2,
datasets: [{
type: 'line',
fill: false,
label: 'Gross Profit',
backgroundColor: 'rgba(255,0,0,1)',
borderColor: 'rgba(255,0,0,1)',
data: sb1,
yAxisID: 'y-axis-1'
}, {
label: 'Total Revenue',
backgroundColor: 'rgba(0, 129, 214, 0.8)',
data: sb
}]
},
options: {
tooltips: {
callbacks: {
label: function (t, d) {
var xLabel = d.datasets[t.datasetIndex].label;
var yLabel = t.yLabel >= 1000 ? '$' + t.yLabel.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : '$' + t.yLabel;
return xLabel + ': ' + yLabel;
}
}
},
legend: {
display: false,
position: 'top',
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: function (value, index, values) {
if (parseInt(value) >= 1000) {
return '$' + value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
} else {
return '$' + value;
}
}
}
}, {
id: 'y-axis-1',
position: 'right',
ticks: {
beginAtZero: true,
callback: function (value, index, values) {
return value + '%';
}
}
}]
}
}
});
</script>
</body>
</html>
</body>
</html>
{/source}
There seems to be an issue with the plugin. TRY using the following one :
plugins: [{
beforeDraw: function(chart) {
var labels = chart.data.labels;
labels.forEach(function(e, i) {
var meta = chart.data.datasets[0]._meta;
var bar = meta[Object.keys(meta)[0]].data[i]._model;
var dataPoint = e.split(/\s/)[1];
if (dataPoint === '12') bar.backgroundColor = 'blue';
else if (dataPoint === '13') bar.backgroundColor = 'green';
});
}
}]
Related
I'm using chart.js to graph my data.
I'm wondering if I can show the flag image/icon PNG for the labels.
function drawChart(obj, columnName, type = 'bar', selectedVal){
var ajax = $.ajax({url: '/query/' + obj + '/'+ columnName + '/' + selectedVal });
ajax.done(function (response) {
console.log('Response from API >>',response);
$('.lds-ripple').fadeOut();
var selector = `chart-${columnName}`;
var chartHtml = `<div class="col-sm-6"><canvas class="chart" id="${selector}" height="200"></canvas></div>`;
$('.charts').append(chartHtml);
keys = [];
values = [];
var length = 0
$.each(response, function(key,val) {
//console.log(key+val);
length++;
if(length<15){
keys.push(key);
values.push(val);
}
});
var showLegend = false;
if(type == 'doughnut' || type == 'radar' || type == 'polarArea' || type == 'pie'){
showLegend = true;
}
let chart = document.getElementById(selector).getContext('2d');
let xAxisTitle = columnName;
var sumOfAllValues = values.reduce((a, b) => a + b, 0);
Chart.defaults.global.defaultFontColor = "#fff";
var ticksDisplay = true;
if(columnName == 'country'){
ticksDisplay = false;
}
const images = ['https://i.stack.imgur.com/2RAv2.png', 'https://i.stack.imgur.com/Tq5DA.png', 'https://i.stack.imgur.com/3KRtW.png', 'https://i.stack.imgur.com/iLyVi.png'];
new Chart(chart, {
type: type, // bar, horizontalBar, pie, line, doughnut, radar, polarArea
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
xAxis.ticks.forEach((value, index) => {
var x = xAxis.getPixelForTick(index);
var y = yAxis.getPixelForTick(index);
var image = new Image();
image.src = images[index],
ctx.drawImage(image, x - 12, yAxis.bottom + 10);
// ctx.drawImage(image, x + 12, yAxis.left - 10);
});
}
}],
data:{
labels: keys,
datasets:[{
label:'Count',
data:values,
borderWidth:2,
hoverBorderWidth:2,
hoverBorderColor:'#fff',
color:'#fff',
backgroundColor: neonBgColors,
borderColor: neonBgBorders,
defaultFontColor: 'white'
}
]
},
options:{
title:{
display:true,
fontSize: 20,
text: columnName + '(' + sumOfAllValues + ')'
},
legend:{
display:showLegend,
position:'right',
labels:{
fontColor:'#000'
}
},
scales: {
xAxes: [{
ticks: {
precision:0,
beginAtZero: true
},
scaleLabel: {
display: true,
labelString: xAxisTitle + ' (' + sumOfAllValues + ')'
}
}],
yAxes: [{
ticks: {
precision:0,
beginAtZero: true,
display: ticksDisplay,
},
scaleLabel: {
display: true,
// labelString: 'Visitor Count'
}
}]
},
layout: {
padding: {
bottom: 30
}
},
}
});
});
}
I kept getting
I adapted the code of this answer and came up with the following solution for your case.
const labels = ['Red Vans', 'Blue Vans', 'Green Vans', 'Gray Vans'];
const images = ['https://i.stack.imgur.com/2RAv2.png', 'https://i.stack.imgur.com/Tq5DA.png', 'https://i.stack.imgur.com/3KRtW.png', 'https://i.stack.imgur.com/iLyVi.png']
.map(png => {
const image = new Image();
image.src = png;
return image;
});
const values = [48, 56, 33, 44];
new Chart(document.getElementById("myChart"), {
type: "horizontalBar",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
yAxis.ticks.forEach((value, index) => {
var y = yAxis.getPixelForTick(index);
ctx.drawImage(images[index], xAxis.left - 40, y - 10);
});
}
}],
data: {
labels: labels,
datasets: [{
label: 'My Dataset',
data: values,
backgroundColor: ['red', 'blue', 'green', 'lightgray']
}]
},
options: {
responsive: true,
layout: {
padding: {
left: 50
}
},
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
display: false
}
}],
xAxes: [{
ticks: {
beginAtZero: true
}
}],
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>
I have a code of euro-dollar exchange via YAHOO FINANCE works with PHP / AJAX. My question is how to integrate the data released chart works with CHARTS.JS
I would like to get this result
labels: ["2016-06-02 12:41:06", "2016-06-02 12:41:08"],
datasets: [{
label: "My Third dataset - No bezier",
data: [1.1200,1.1205],
lineTension: 0,
fill: false,
}]
The result:
{"rate":"1.1200","time":"2016-06-02 12:41:06"}
{"rate":"1.1205","time":"2016-06-02 12:41:08"}
{"rate":"1.1199","time":"2016-06-02 12:41:10"}
{"rate":"1.1199","time":"2016-06-02 12:41:12"}
-
The Full Code:
<script src="Chart.bundle.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<style>
canvas {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
</style>
<?php
if(isset($_GET['fetchOnly'])){
$from = 'eur';
$to = 'usd';
$url = 'http://finance.yahoo.com/d/quotes.csv?e=.csv&f=sl1d1t1&s='. $from . $to .'=X';
$response = array();
$handle = fopen($url, 'r');
if ($handle) {
while (($data = fgetcsv($handle, 1024, ',', '"')) !== FALSE)
{
$response['rate'] = $data[1];
$response['time'] = date("Y-m-d H:i:s");
}
fclose($handle);
}
echo json_encode($response);
die();
}
?>
<div id="responseText"></div>
<script>
// run the function, it will re-run itself
fetchRate();
function fetchRate() {
// create the new AJAX Object
xmlhttp = new XMLHttpRequest();
// this handles the request
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE ) {
// if the request came back successfully
if(xmlhttp.status == 200){
// write the response to a div
div = document.getElementById("responseText")
div.innerHTML = div.innerHTML + '<br />'+ xmlhttp.responseText;
}else{
// if the request had an error
div.innerHTML = div.innerHTML + '<br />Error fetching rates error code : '+xmlhttp.status;
}
// rerun this function to fetch updates
setTimeout(fetchRate,1000);
}
};
// open the AJAX Object
xmlhttp.open("GET", "<?= basename(__FILE__) ?>?fetchOnly", true);
// send the AJAX request
xmlhttp.send();
}
</script>
<div style="width:100%;">
<canvas id="canvas"></canvas>
</div>
<script>
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var randomScalingFactor = function() {
return Math.round(Math.random() * 100 * (Math.random() > 0.5 ? -1 : 1));
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var randomColor = function(opacity) {
return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
};
var config = {
type: 'line',
data: {
labels: ["2016-06-02 12:36:05", "2016-06-02 12:37:05"],
datasets: [{
label: "My Third dataset - No bezier",
data: [1,2],
lineTension: 0,
fill: false,
}]
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
hover: {
mode: 'label'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Month'
}
}],
yAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Value'
}
}]
},
title: {
display: true,
text: 'Chart.js Line Chart - Legend'
}
}
};
$.each(config.data.datasets, function(i, dataset) {
var background = randomColor(0.5);
dataset.borderColor = background;
dataset.backgroundColor = background;
dataset.pointBorderColor = background;
dataset.pointBackgroundColor = background;
dataset.pointBorderWidth = 1;
});
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myLine = new Chart(ctx, config);
};
$('#randomizeData').click(function() {
$.each(config.data.datasets, function(i, dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myLine.update();
});
$('#addDataset').click(function() {
var background = randomColor(0.5);
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
borderColor: background,
backgroundColor: background,
pointBorderColor: background,
pointBackgroundColor: background,
pointBorderWidth: 1,
fill: false,
data: [],
};
for (var index = 0; index < config.data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}
config.data.datasets.push(newDataset);
window.myLine.update();
});
$('#addData').click(function() {
if (config.data.datasets.length > 0) {
var month = MONTHS[config.data.labels.length % MONTHS.length];
config.data.labels.push(month);
$.each(config.data.datasets, function(i, dataset) {
dataset.data.push(randomScalingFactor());
});
window.myLine.update();
}
});
$('#removeDataset').click(function() {
config.data.datasets.splice(0, 1);
window.myLine.update();
});
$('#removeData').click(function() {
config.data.labels.splice(-1, 1); // remove the label first
config.data.datasets.forEach(function(dataset, datasetIndex) {
dataset.data.pop();
});
window.myLine.update();
});
</script>
Thank you.
I have worked with chart.js 1.0 and had my doughnut chart tooltips displaying percentages based on data divided by dataset, but I'm unable to replicate this with chart 2.0.
I have searched high and low and have not found a working solution. I know that it will go under options but everything I've tried has made the pie dysfunctional at best.
<html>
<head>
<title>Doughnut Chart</title>
<script src="../dist/Chart.bundle.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<style>
canvas {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
</style>
</head>
<body>
<div id="canvas-holder" style="width:75%">
<canvas id="chart-area" />
</div>
<script>
var randomScalingFactor = function() {
return Math.round(Math.random() * 100);
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var randomColor = function(opacity) {
return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
};
var config = {
type: 'doughnut',
data: {
datasets: [{
data: [
486.5,
501.5,
139.3,
162,
263.7,
],
backgroundColor: [
"#F7464A",
"#46BFBD",
"#FDB45C",
"#949FB1",
"#4D5360",
],
label: 'Expenditures'
}],
labels: [
"Hospitals: $486.5 billion",
"Physicians & Professional Services: $501.5 billion",
"Long Term Care: $139.3 billion",
"Prescription Drugs: $162 billion",
"Other Expenditures: $263.7 billion"
]
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
title: {
display: false,
text: 'Chart.js Doughnut Chart'
},
animation: {
animateScale: true,
animateRotate: true
}
}
};
window.onload = function() {
var ctx = document.getElementById("chart-area").getContext("2d");
window.myDoughnut = new Chart(ctx, config);{
}
};
</script>
</body>
</html>
Update: The below answer shows a percentage based on total data but #William Surya Permana has an excellent answer that updates based on the shown data https://stackoverflow.com/a/49717859/2737978
In options you can pass in a tooltips object (more can be read at the chartjs docs)
A field of tooltips, to get the result you want, is a callbacks object with a label field. label will be a function that takes in the tooltip item which you have hovered over and the data which makes up your graph. Just return a string, that you want to go in the tooltip, from this function.
Here is an example of what this can look like
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
//get the concerned dataset
var dataset = data.datasets[tooltipItem.datasetIndex];
//calculate the total of this data set
var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) {
return previousValue + currentValue;
});
//get the current items value
var currentValue = dataset.data[tooltipItem.index];
//calculate the precentage based on the total and current item, also this does a rough rounding to give a whole number
var percentage = Math.floor(((currentValue/total) * 100)+0.5);
return percentage + "%";
}
}
}
and a full example with the data you provided
fiddle
var randomScalingFactor = function() {
return Math.round(Math.random() * 100);
};
var randomColorFactor = function() {
return Math.round(Math.random() * 255);
};
var randomColor = function(opacity) {
return 'rgba(' + randomColorFactor() + ',' + randomColorFactor() + ',' + randomColorFactor() + ',' + (opacity || '.3') + ')';
};
var config = {
type: 'doughnut',
data: {
datasets: [{
data: [
486.5,
501.5,
139.3,
162,
263.7,
],
backgroundColor: [
"#F7464A",
"#46BFBD",
"#FDB45C",
"#949FB1",
"#4D5360",
],
label: 'Expenditures'
}],
labels: [
"Hospitals: $486.5 billion",
"Physicians & Professional Services: $501.5 billion",
"Long Term Care: $139.3 billion",
"Prescription Drugs: $162 billion",
"Other Expenditures: $263.7 billion"
]
},
options: {
responsive: true,
legend: {
position: 'bottom',
},
title: {
display: false,
text: 'Chart.js Doughnut Chart'
},
animation: {
animateScale: true,
animateRotate: true
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var total = dataset.data.reduce(function(previousValue, currentValue, currentIndex, array) {
return previousValue + currentValue;
});
var currentValue = dataset.data[tooltipItem.index];
var percentage = Math.floor(((currentValue/total) * 100)+0.5);
return percentage + "%";
}
}
}
}
};
var ctx = document.getElementById("chart-area").getContext("2d");
window.myDoughnut = new Chart(ctx, config); {
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.3/Chart.bundle.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="canvas-holder" style="width:75%">
<canvas id="chart-area" />
</div>
For those who want to display dynamic percentages based on what currently displayed on the chart (not based on total data), you can try this code:
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var meta = dataset._meta[Object.keys(dataset._meta)[0]];
var total = meta.total;
var currentValue = dataset.data[tooltipItem.index];
var percentage = parseFloat((currentValue/total*100).toFixed(1));
return currentValue + ' (' + percentage + '%)';
},
title: function(tooltipItem, data) {
return data.labels[tooltipItem[0].index];
}
}
},
In 3.5 it will be:
options: {
plugins: {
tooltip: {
callbacks: {
label: function(context){
var data = context.dataset.data,
label = context.label,
currentValue = context.raw,
total = 0;
for( var i = 0; i < data.length; i++ ){
total += data[i];
}
var percentage = parseFloat((currentValue/total*100).toFixed(1));
return label + ": " +currentValue + ' (' + percentage + '%)';
}
}
}
}
}
but better, dynamic version:
options: {
plugins: {
tooltip: {
callbacks: {
label: function(context){
var label = context.label,
currentValue = context.raw,
total = context.chart._metasets[context.datasetIndex].total;
var percentage = parseFloat((currentValue/total*100).toFixed(1));
return label + ": " +currentValue + ' (' + percentage + '%)';
}
}
}
}
}
I came across this question because I needed to show percentage on stacked bar charts. The percentage I needed was per stacked columns. I accomplished this by modifying Willian Surya's answer like this:
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var index = tooltipItem.index;
var currentValue = data.datasets[tooltipItem.datasetIndex].data[index];
var total = 0;
data.datasets.forEach(function(el){
total = total + el.data[index];
});
var percentage = parseFloat((currentValue/total*100).toFixed(1));
return currentValue + ' (' + percentage + '%)';
},
title: function(tooltipItem, data) {
return data.datasets[tooltipItem[0].datasetIndex].label;
}
}
}
This is the final result:
The usage has changed in 3.x and higher versions, so I will attach a method for this.
const data: ChartData = {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [
{
data: excerciseData,
backgroundColor: [
"rgba(255, 99, 132, 0.5)",
"rgba(54, 162, 235, 0.5)",
"rgba(255, 206, 86, 0.5)",
"rgba(75, 192, 192, 0.5)",
"rgba(153, 102, 255, 0.5)",
"rgba(255, 159, 64, 0.5)"
]
}
]
};
...
callbacks: {
label: tooltipItem => {
let total = 0;
data.datasets[0].data.forEach(num => {
total += num as number;
});
const currentValue = data.datasets[0].data[tooltipItem.dataIndex] as number;
const percentage = ((currentValue * 100) / total).toFixed(1) + "%";
return `${currentValue}(${percentage})`;
},
title: tooltipItems => {
return tooltipItems[0].label;
}
}
Simply use this:
const options = {
responsive: true,
plugins: {
tooltip: {
callbacks: {
label: (Item) => '%' + (Item.formattedValue) + ' | ' + Item.label
}
}
},
};
I am trying to create a gantt chart representation in highcharts with navigator. I get a JSON response from server (below is a typical response strucutre). In order to create a gantt chart representation I am creating a line between 2 points. Each point has a start_date and end_date and inorder to create this representation I am plotting a line between start_date and end_date of each point (which I have accomplished).
Response Structure from server
{
"took": 312,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 4115,
"max_score": 1,
"hits": [
]
},
"aggregations": {
"top-tags": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "Process1",
"doc_count": 6,
"process": {
"value": {
"1449878649000": {
"start_date": 1449878649000,
"process_initiator": "lol#surg.com",
"end_date": 1449878734000,
"total_seconds": 85
},
"1449879753000": {
"start_date": 1449879753000,
"process_initiator": "lol#surg.com",
"end_date": 1449879850000,
"total_seconds": 97
},
"1449881550000": {
"start_date": 1449881550000,
"process_initiator": "lol#surg.com",
"end_date": 1449881631000,
"total_seconds": 81
}
}
}
},
{
"key": "Process2",
"doc_count": 1,
"process": {
"value": {
"1449971262000": {
"start_date": 1449971262000,
"process_initiator": "lol#surg.com",
"end_date": 1449971266000,
"total_seconds": 4
}
}
}
}
]
}
}
}
Code also sharing a plunker demo
var app = angular.module('app', []);
app.directive('operationalhighstackstock', function() {
return {
restrict: 'E',
scope: true,
link: function postLink(scope, element, attrs) {
scope.$watch('operationHighChartsData', function(values) {
new Highcharts.StockChart(values);
});
}
};
});
//2014-11-30T18:15:25.000-08:00
app.controller('MainCtrl', ['$scope', function($scope) {
$scope.excludeValue = {
data: 0
};
$scope.isExcludeNeeded = true;
var opExcludeMinutes = 1,
AGENT_NAMES = "agent_names",
colorCodes = ["#8CC051", "#967BDC", "#5D9CEC", "#FB6E52", "#EC87BF", "#46CEAD", "#FFCE55", "#193441", "#193441", "#BEEB9F", "#E3DB9A", "#917A56"];
var setSummaryDisplay = function(e) {
if (e.min === null || e.max === null)
$scope.hideRangeSlider = true;
else
$scope.hideRangeSlider = false;
$scope.minimumSelectedValue = e.min;
$scope.maximumSelectedValue = e.max;
}
var getHichartsData = function(result) {
var tasksArr = [],
seriesArr = [],
userArr = [],
processArr = [];
var agentSeries = [],
agentData = {},
processSeries = [],
taskData = {},
idx = 0,
opProcessBucket = esResponse.aggregations["top-tags"].buckets,
seriesData = {};
var opBucketLength = opProcessBucket.length;
for (var opProcessBucketIndex = 0; opProcessBucketIndex < opBucketLength; ++opProcessBucketIndex) {
//opProcessBucket.forEach(function(processEntry) {
//if (opProcessBucket[opProcessBucketIndex]["key"] == $scope.gpDropDownTitle) {
var intervalBucket = opProcessBucket[opProcessBucketIndex]["process"]["value"], //opProcessBucket[opProcessBucketIndex]["top_tag_hits"]["hits"]["hits"],
intervalArr = [],
tasksIntervalArr = [],
opTaskidObj = {},
opTaskidIntervalObj = {},
process_name = null,
sortElementArr = [];
for (var key in intervalBucket) {
//intervalBucket.forEach(function(intervalEntry, intervalIndex) {
var intervalObj = {},
intervalObj2ndpoint = {},
processIntervalObj = {},
tintervalArr = [],
intervalIndex = 0,
start_temp = parseInt(key),
end_temp = intervalBucket[key].end_date; //start_temp = intervalBucket[key].start_date, end_temp = intervalBucket[key].end_date;
//added here since response contains null value and data load will take almost 1 date, verified with Bhavesh
$scope.currentDateTime = new Date().getTime();
if (end_temp == null)
end_temp = $scope.currentDateTime;
var st = new Date(moment(start_temp).valueOf()).getTime();
var et = new Date(moment(end_temp).valueOf()).getTime();
var duration = moment.duration(moment(et).diff(moment(st)));
var minutes = duration.asMinutes();
if (minutes > $scope.excludeValue.data && $scope.isExcludeNeeded) {
if (intervalIndex == 0 || process_name == null) {
process_name = opProcessBucket[opProcessBucketIndex]["key"];
processArr.push(opProcessBucket[opProcessBucketIndex]["key"]);
}
userArr.push(intervalBucket[key].process_initiator);
processIntervalObj["task_id"] = opProcessBucket[opProcessBucketIndex]["key"];
processIntervalObj["from"] = st;
var lFromtime = moment.utc(st).toDate();
lFromtime = moment(lFromtime).format('MM/DD/YY HH:mm');
var lTotime = moment.utc(et).toDate();
lTotime = moment(lTotime).format('MM/DD/YY HH:mm');
processIntervalObj["to"] = et;
processIntervalObj["color"] = "#FFCC4E";
processIntervalObj["fromDateString"] = lFromtime;
processIntervalObj["toDateString"] = lTotime;
processIntervalObj["process_initiator"] = intervalBucket[key].process_initiator == null ? 'Unknown' : intervalBucket[key].process_initiator;
processIntervalObj["total_seconds"] = intervalBucket[key].total_seconds;
//sortElementArr.push(intervalEntry["sort"][0]);
tasksIntervalArr.push(processIntervalObj);
}
}
opTaskidObj["name"] = process_name;
opTaskidIntervalObj["name"] = process_name;
opTaskidObj["data"] = [];
opTaskidIntervalObj["intervals"] = tasksIntervalArr;
opTaskidIntervalObj["intervals"] = tasksIntervalArr;
idx++;
if (tasksIntervalArr.length > 0) {
processSeries.push(opTaskidIntervalObj);
agentSeries.push(opTaskidObj);
}
//}
}
seriesData["title"] = "Test"; //item["key"];
var series = [];
(processSeries.reverse()).forEach(function(task, i) {
var item = {
name: task.name,
data: [],
turboThreshold: 1100000
};
(task.intervals).forEach(function(interval, j) {
item.data.push({
task_id: interval.task_id,
x: interval.from,
y: i,
from: interval.from,
to: interval.to,
color: interval.color,
fromDateString: interval.fromDateString,
toDateString: interval.toDateString,
total_seconds: interval.total_seconds,
process_initiator: interval.process_initiator
}, {
task_id: interval.task_id,
x: interval.to,
y: i,
from: interval.from,
to: interval.to,
color: interval.color,
fromDateString: interval.fromDateString,
toDateString: interval.toDateString,
total_seconds: interval.total_seconds,
process_initiator: interval.process_initiator
});
// add a null value between intervals
if (task.intervals[j + 1]) {
item.data.push([(interval.to + task.intervals[j + 1].from) / 2, null]);
}
});
series.push(item);
})
seriesData["data"] = series;
seriesData["tasks"] = processSeries;
seriesArr.push(seriesData);
return seriesArr;
}
$scope.agentSeriesData = getHichartsData(esResponse);
var tasks = $scope.agentSeriesData[0].tasks;
var seriesData = $scope.agentSeriesData[0].data;
var xAxisStepping = 1 * 3600 * 1000;
var chart = new Highcharts.StockChart({
chart: {
renderTo: 'container',
height: 600,
events: {
load: function(e) {
var max = this.xAxis[0].max;
var range = (24 * 3600 * 1000) * 7; // one day * 7
if ($scope.isInit || $scope.filterReseted) {
$scope.filterReseted = false;
this.xAxis[0].setExtremes(max - range, max);
}
setSummaryDisplay.call(this.xAxis[0], {
trigger: "navigator",
min: this.xAxis[0].min,
max: this.xAxis[0].max
});
}
}
},
title: {},
credits: {
enabled: false
},
xAxis: {
type: 'datetime',
gridLineWidth: 1,
tickInterval: xAxisStepping,
//ordinal:false,
dateTimeLabelFormats: {
month: '%b %e, %Y'
},
events: {
afterSetExtremes: setSummaryDisplay
},
minRange: 1000
},
yAxis: {
tickInterval: 1,
gridLineWidth: 1,
labels: {
enabled: false,
formatter: function() {
if (tasks[this.value]) {
return tasks[this.value].name;
}
}
},
startOnTick: false,
endOnTick: false,
title: {
text: 'Process'
}
},
animation: false,
rangeSelector: {
enabled: false
},
navigator: {
enabled: true
},
legend: {
enabled: false
},
tooltip: {
shared: false,
formatter: function() {
var str = '';
str += 'Process: ' + this.series.name + '<br>';
str += 'From: ' + Highcharts.dateFormat('%m/%d/%y %H:%M:%S', this.point.from) + '<br>';
str += 'To: ' + Highcharts.dateFormat('%m/%d/%y %H:%M:%S', this.point.to) + '<br>';
return str;
}
},
plotOptions: {
line: {
lineWidth: 10,
marker: {
enabled: false
},
dataLabels: {
enabled: false,
borderRadius: 5,
borderWidth: 1,
y: -6,
formatter: function() {
return this.series.name;
}
},
states: {
hover: {
lineWidth: 10
}
}
},
series: {
cursor: 'pointer',
animation: false,
point: {
events: {
click: function() {
$scope.selectedGuide = this.series.name;
//$scope.showTableView();
}
}
},
turboThreshold: 100000000,
dataGrouping: {
enabled: false
}
}
},
scrollbar: {
enabled: false
},
series: seriesData
});
$scope.operationHighChartsData = chart;
}]);
I have sorted data (ascending order) but I am still getting Highcharts error #15: www.highcharts.com/errors/15 errors in thousands (mostly 80k +), which is hanging the browser.
What could be the issue and how can I get rid of it and increase performance? Sharing a plunker which has code and relatively small number of errors.
Note: I am using Highstock JS v2.1.5
There are two problems with this code:
First thing you need to sort the series in ascending order of X. I did not want to debug your code on how do you construct your data so I added a simple loop in the end to sort everything.
for (var i in seriesData) {
seriesData[i].data.sort(function(a, b) {
if (a.x > b.x) {
return 1;
}
if (b.x > a.x) {
return -1;
}
return 0;
});
}
The other problem is that the data array contains in correct data because of this line
if (task.intervals[j + 1]) {
item.data.push([(interval.to + task.intervals[j + 1].from) / 2, null]);
}
so I changed it to this
// add a null value between intervals
if (task.intervals[j + 1]) {
item.data.push({
task_id: interval.task_id,
x: (interval.to + task.intervals[j + 1].from) / 2,
y: null,
from: (interval.to + task.intervals[j + 1].from) / 2,
to: (interval.to + task.intervals[j + 1].from) / 2
});
}
here is the fixed plnkr
http://plnkr.co/edit/OEMuVfTMhHNQsTYGUyuy?p=preview
Please read this link to improve highcharts performance. A few months ago Highcharts released boost.js to improve chart performance with millions of data points.
It seems that over 500 points causes our highstocks markers not to show at all - you can see
http://www.ethnographicedge.com/topic/senkaku-trial-400/ (400 pts) vs http://www.ethnographicedge.com/topic/senkaku-trial-600-s1/ (600pts). The data that's not showing is the "event" represented by the green sphere.
I've looked into editing the turboThreshold, but no luck..
Here's a simplified jsfiddle, and my full code below:
jsfiddle.net/marquex/etdL7/1
when you change the var points = 200; to 400, the red dot doesn't display. Is there a way to force certain mandatory points to show, regardless of the data size?
graphdata = <?php echo topic_data(get_the_ID()); ?>;
extradata = <?php echo extra_data(get_the_ID()); ?>;
eventdata = <?php global $events; echo json_encode($events) ?>;
eventdata.sort(function(a,b){
return a.date - b.date;
});
;(function($){
var parseData,
container = $('#graphcontainer'),
last = false,
m_names = new Array("January", "February", "March",
"April", "May", "June", "July", "August", "September",
"October", "November", "December");
;
if(! container.length)
return;
getEventPoint = function(e,idx){
var color = 'gray';
if(e.forecast == 'fore_successful_down')
color = 'red';
else if(e.forecast == 'fore_successful_up')
color = 'green';
return {
x: parseInt(e.stringdate),
y: parseInt(e.value),
marker: {
enabled: true,
radius:8,
fillColor: color,
lineWidth:3,
lineColor: 'white',
states: {
hover: {
enabled: true,
radius:8,
fillColor: 'white',
lineWidth:3,
lineColor: color
}
}
},
name: idx
}
}
dateDay = function(day){
if( day % 10 == 1)
return day + 'st';
if( day % 10 == 2)
return day + 'nd';
if( day % 10 == 3)
return day + 'rd';
return day + 'th';
}
formatDate = function(timestamp){
var date = new Date(timestamp);
return m_names[date.getMonth()] + ' ' + date.getDay() + ', ' + date.getFullYear();
}
tooltipFormatter = function(data){
var point = data.points[0],
name = point.point.name,
output = '<div class="tooltip-date">' + formatDate(data.x) + '</div><div class="tooltip-value"><span class="tooltip-unit"><?php the_field("value_text") ?>:</span> ' + data.y + '</div>';
if(typeof name !== "undefined"){
var e = eventdata[name];
return '<div class="tooltip-date">' + formatDate(data.x) + '</div>' +
'<div class="tooltip-title">' + e.number + '</div>' +
'<div class="tooltip-trend tooltip-' + e.trend + '"></div> <!-- I will resize the image > background-size: -->' +
'<div class="tooltip-cycle">' + e.cycle + '</div>';
}
if(data.points[1])
output += '<div class="tooltip-secondary><div class="tooltip-value"><span class="tooltip-unit"><?php the_field("extra_value_text") ?>:</span> ' + data.points[1].y + '</div>'
return output;
}
parseData = function(data, eventsData){
var parsedData = [],
eventIdx = 0,
events = eventsData ? eventsData.slice(0) : [],
e = events[0]
;
if(e)
e.stringdate = (new Date(e.date.substring(0,4) + '/' + e.date.substring(4,6) + '/' + e.date.substring(6,8))).getTime();
for (var i = 0; i < data.length; i++) {
var item = data[i],
timestamp = false;
if(item.date && item.date.match(/^\d{4}\/\d{2}\/\d{2}$/) && item.value && parseFloat(item.value) == item.value){
timestamp = (new Date(item.date)).getTime();
if(e && timestamp > e.stringdate > last) {
parsedData.push(getEventPoint(e, eventIdx));
parsedData.push([timestamp, parseFloat(item.value)]);
e = events[++eventIdx];
if(e)
e.stringdate = (new Date(e.date.substring(0,4) + '/' + e.date.substring(4,6) + '/' + e.date.substring(6,8))).getTime();
}
else if(e && timestamp == e.stringdate) {
parsedData.push(getEventPoint(e, eventIdx));
e = events[++eventIdx];
if(e)
e.stringdate = (new Date(e.date.substring(0,4) + '/' + e.date.substring(4,6) + '/' + e.date.substring(6,8))).getTime();
}
else
parsedData.push([timestamp, parseFloat(item.value)]);
}
};
while(e){
parsedData.push(getEventPoint(e, eventIdx));
e = events[++eventIdx];
if(e)
e.stringdate = (new Date(e.date.substring(0,4) + '/' + e.date.substring(4,6) + '/' + e.date.substring(6,8))).getTime();
}
return parsedData;
};
var series = [{
name: 'Topic Data',
data: parseData(graphdata, eventdata),
color: '#666'
}];
if(extradata && extradata.length){
series.push({
name: 'Extra Data',
data: parseData(extradata),
yAxis: 1,
color: '#fbb800'
});
}
var yAxis = [{
title: {
text: '<?php the_field("value_text") ?>',
style: { color: '#BBBBBB', fontSize: '1.2em' },
margin: 12,
},
labels: {
enabled: false,
formatter: function() {
return this.value
}
},
height: 300,
lineColor: '#FFFFFF'
}];
if(extradata && extradata.length){
yAxis[0].lineWidth = 2;
yAxis.push({
title: {
text: '<?php the_field("extra_value_text") ?>'
},
height: 200,
top:350,
offset:0,
lineWidth:2,
lineColor: '#FFFFFF'
});
}
container.highcharts('StockChart', {
chart: {
type: 'spline',
events: {
load: function(e){
$('#tapapubli')
.detach()
.addClass('tapapubli')
.appendTo('#graphcontainer');
}
}
},
rangeSelector: {
enabled: false,
buttons:[
{
type: '<?php echo $initial_zoom["type"] ?>',
count: <?php echo $initial_zoom["count"] ?>,
text: 'Initial'
},
{
type: 'All',
text: 'Reset'
}
],
},
scrollbar: {
enabled: false,
},
navigator: {
maskFill: 'rgba(170, 170, 170, 0.75)',
series: {
color: '#FFD600',
lineColor: '#AE8800'
}
},
xAxis: {
labels: {
enabled: false
},
lineColor: '#FFF', /* BITBUCKET */
tickColor: '#666666',
tickLength: 0,
tickWidth: 1,
tickPosition: 'outside',
type: 'datetime',
dateTimeLabelFormats: { // don't display the dummy year
day: '%e of %b',
}
},
yAxis: yAxis,
plotOptions: {
lineColor: 'green',
spline: {
lineWidth: 3,
states: {
hover: {
lineWidth: 3
}
},
marker: {
enabled: false
},
pointInterval: 36000000000,
point: {
events: {
click: function(){
//console.log(this);
if(this.name)
window.location.href = '#event-' + eventdata[this.name].number;
}
}
}
}
},
series: series,
tooltip: {
formatter: function(){
return tooltipFormatter(this);
},
useHTML: true,
borderColor: '#333',
shadow: false,
borderRadius: 0
}
});
if(!window.chart)
window.chart = container.highcharts();
})(jQuery);
var getPointRecursive = function(date, list){
if(list.length < 5){
var found = false;
for (var i = 0; i < list.length; i++) {
var point = list[i];
};
}
}
var getSeriesPoint = function(date){
}
As #Sebastian Bochan said, you just need to set plotOptions.series.dataGrouping.enabled = false explicitly. Then the problem will disappear.