I have an array of data as follows
getDates stores all the dates set by the backend team (01 => 1 April, 02 =>2 April and so on)
getAllApplicantsPerDay stores the number of registrations on each day
Array Name All Data
-------------------------------------------------------------------
getDates '01', '02','03'
getAllApplicantsPerDay 6,4,5,8,7,8
So, basically the data stored are as follows
getDates
Array ( [0] => '01' [1] => '02' [2] => '03')
getAllApplicantsPerDay
Array ( [0] => 6 [1] => 4 [2] => 5 [3] => 8 [4] => 7 [5] => 8 )
Now, as per the requirement of the backend team, they can change the date by adding or removing some dates
So, let's say they add 3 days, the new getDates array would be
getGender
Array ( [0] => '01' [1] => '02' [2] => '03' [3] => '04' [4] => '05' [5] => '06')
In accordance, getAllApplicantsPerDay would also change. I would also want different colours each time (randomly selected color)
However, I cannot show that in a graph. I dont know the syntax to do it.
here is what I tried so far
Script
var config111 = {
type: 'bar',
data: {
labels: [
<?php echo json_encode($getDates); ?>
],
datasets: [{
label: 'DAILY REGISTRATIONS',
backgroundColor:'rgba(178,150,200,0.9)', ------>I want this dynamic too
borderColor: 'rgba(70,158,210,0.9)',
data: [
<?php echo json_encode($getAllApplicantsPerDay ); ?>
],
fill: false,
},
]
},
options: {
responsive: true,
title: {
display: false,
text: 'Daily Registrations'
},
tooltips: {
mode: 'index',
intersect: false,
},
/*hover: {
mode: 'nearest',
intersect: true
},*/
hover: {
mode: 'nearest',
"animationDuration": 0,
intersect: true
},
"animation": {
"duration": 1,
"onComplete": function () {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
var data = dataset.data[index];
ctx.fillText(data, bar._model.x, bar._model.y - 5);
});
});
}
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: ' Monthly Panchayat Samiti'
}
}],
/*yAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Value'
}
}]*/
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
};
$(function () {
var ctx111 = document.getElementById('canvas').getContext('2d');
window.myLine = new Chart(ctx111, config111);
});
Using json_encode would mean that you are encoding the data in JSON format. This format,may not be acceptable by chartjs,as chartjs requires strings for labels, and numbers for data.
Here is what you can do (only the required section of the code)
(Please Note):- Copy and paste this in label tag in chartjs. You can do this exact same thing for data tag as well.
labels: [
<?php
for($i=0;$i<count($getDates);$i++){
echo $getDates[$i];
echo ',';
}
?>
],
Also, make sure to send the data as an array from the backend, if you want to use this method.
As for the random color thing, I suggest you check out this github repository. You can search for other options for random colors as well.
Related
I will attempt to explain my issue as clearly as possible while also avoid making this topic too long. I recently found the Chart.js library, which is excellent for what I need. Now, since I am using Node.js and need a png of the graph, I am utilizing the chartjs-node-canvas library. Having this information in mind, I will try to split my topic into multiple sections for a clearer understanding.
Ultimate Goal
Before getting into the problem itself, I would like to discuss my ultimate goal. This is to give a general idea on what I'm trying to do so the responses are fitted accordingly. To keep this short, I have data in the form of {awardedDate: "2022-06-22T12:21:17.22Z", badgeId: 1234567}, with awardedDate being a timestamp of when the badge was awarded, and the badgeId being the ID of the badge that was awarded (which is irrelevant to the graph, but it exists because it's part of the data). Now, I have a sample with around 2,787 of these objects, with all having different award dates and IDs, and with dates ranging from 2016 to 2022. My objective is to group these badges by month-year, and that month-year will have the amount of badges earned for that month during that year. With that data, I then want to make a waterfall graph which is based on the amount of badges earned that month of that year. As of right now, there isn't a specific structure on how this will look like, but it could range from an object that looks like {"02-2022": 10, "03-2022": 5} to anything else. I can of course restructure this format based on what is required for a waterfall graph.
Actual Questions
Now that you have a general idea of what my ultimate goal is, my actual question is how I'd be able to make a floating (we can leave the waterfall structure stuff for another topic) bar graph with that data. Since the data can have blank periods (it is possible for a dataset to have gaps that are months long), I cannot really utilize labels (unless I am saying something wrong), so an x-y relation works the best. I tried using the structure of {x: "2022-06-22T12:21:17.226Z", y: [10, 15]}, but that didn't really yield any results. As of right now, I am using a sample code to test how the graph reacts with the data, and of course I'll replace the test values with actual values once I have a finished product. Here is my code so far:
const config = {
type: "bar",
data: {
datasets: [{
label: "Badges",
data: [
{
x: "2022-06-22T12:41:17.226Z",
y: [10, 15]
}
],
borderColor: "rgb(75, 192, 192)",
borderSkipped: false
}]
},
options: {
plugins: {
legend: {
display: false
},
title: {
display: true,
text: "Test",
color: "#FFFFFF"
}
},
scales: {
x: {
type: 'time',
title: {
display: true,
text: 'Time',
color: "#FFFFFF"
},
min: "2022-06-22T12:21:17.226Z",
max: "2022-06-22T14:21:17.226Z",
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
},
y: {
title: {
display: true,
text: 'Number of Badges',
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
min: 0,
max: 50,
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
}
}
},
plugins: [
{
id: 'custom_canvas_background_color',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = '#303030';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
}
]
};
const imageBuffer = await canvasRenderService.renderToBuffer(config)
fs.writeFileSync("./chart2.png", imageBuffer)
And this is the graph that the code produces:
What is supposed to happen, of course, is that a float bar should be generated near the start that ranges from 5 to 10, but as seen above, nothing happens. If someone could assist me in my problem, that would be amazing. Thank you very much for your time and help, I greatly appreciate it.
Inspired by this answer, I came up with the following solution.
const baseData = [
{ awardedDate: "2022-06-22T12:21:17.22Z" },
{ awardedDate: "2022-06-18T12:21:17.22Z" },
{ awardedDate: "2022-06-15T12:21:17.22Z" },
{ awardedDate: "2022-05-20T12:21:17.22Z" },
{ awardedDate: "2022-05-10T12:21:17.22Z" },
{ awardedDate: "2022-04-16T12:21:17.22Z" },
{ awardedDate: "2022-04-09T12:21:17.22Z" },
{ awardedDate: "2022-04-03T12:21:17.22Z" },
{ awardedDate: "2022-04-01T12:21:17.22Z" },
{ awardedDate: "2022-02-18T12:21:17.22Z" },
{ awardedDate: "2022-02-12T12:21:17.22Z" },
{ awardedDate: "2022-01-17T12:21:17.22Z" }
];
const badgesPerMonth = baseData
.map(o => o.awardedDate)
.sort()
.map(v => moment(v))
.map(m => m.format('MMM YYYY'))
.reduce((acc, month) => {
const badges = acc[month] || 0;
acc[month] = badges + 1;
return acc;
}, {});
const months = Object.keys(badgesPerMonth);
const labels = months.concat('Total');
const data = [];
let total = 0;
for (let i = 0; i < months.length; i++) {
const vStart = total;
total += badgesPerMonth[months[i]];
data.push([vStart, total]);
}
data.push(total);
const backgroundColors = data
.map((o, i) => 'rgba(255, 99, 132, ' + (i + (11 - data.length)) * 0.1 + ')');
new Chart('badges', {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Badges',
data: data,
backgroundColor: backgroundColors,
barPercentage: 1,
categoryPercentage: 0.95
}]
},
options: {
plugins: {
tooltip: {
callbacks: {
label: ctx => {
const v = data[ctx.dataIndex];
return Array.isArray(v) ? v[1] - v[0] : v;
}
}
}
},
scales: {
y: {
ticks: {
beginAtZero: true,
stepSize: 2
}
}
}
}
});
<script src="https://rawgit.com/moment/moment/2.2.1/min/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.8.0/chart.min.js"></script>
<canvas id="badges" height="95"></canvas>
If you also want to see the gaps, you would first have to initialize badgesPerMonth with following months between the earliest and latest date, each with value zero. Please take a look at this answer to get an idea about how this could be done.
After reading #uminder's reply, I was able to create the following code which solved my problem:
dateGroups = Object.fromEntries(
Object.entries(dateGroups).sort(([d1,],[d2,]) => {return (d1 < d2) ? -1 : ((d1 > d2) ? 1 : 0)})
)
const dateTimesConst = Object.keys(dateGroups)
const dateValuesConst = Object.values(dateGroups)
let dateTimes = []
let dateValues = []
let prevLength = 0
let mostBadgesPerMonth = 0
for (let i = 0; i < dateValuesConst.length; i++) {
const currentMonth = new Date(Date.parse(dateTimesConst[i]))
const previousMonth = new Date(Date.UTC(currentMonth.getUTCFullYear(), currentMonth.getUTCMonth() - 1, 1, 0, 0, 0, 0)).toISOString()
const nextMonth = new Date(Date.UTC(currentMonth.getUTCFullYear(), currentMonth.getUTCMonth() + 1, 1, 0, 0, 0, 0)).toISOString()
// if (!dateTimesConst.includes(previousMonth)) prevLength = 0
const length = dateValuesConst[i].length
dateValues.push([prevLength, length])
dateTimes.push(dateTimesConst[i])
prevLength = length
if (length > mostBadgesPerMonth) mostBadgesPerMonth = length
// if (!dateTimesConst.includes(nextMonth) && i !== dateValuesConst.length - 1) {
// dateTimes.push(nextMonth)
// dateValues.push([length, 0])
// prevLength = 0
// }
}
function barColorCode() {
return (ctx) => {
const start = ctx.parsed._custom.start
const end = ctx.parsed._custom.end
return start <= end ? "rgba(50, 168, 82, 1)" : (start > end) ? "rgba(191, 27, 27, 1)" : "black"
}
}
const config = {
type: "bar",
data: {
labels: dateTimes,
datasets: [{
label: "Badges",
data: dateValues,
elements: {
bar: {
backgroundColor: barColorCode()
}
},
barPercentage: 1,
categoryPercentage: 0.95,
borderSkipped: false
}]
},
options: {
plugins: {
legend: {
display: false
},
title: {
display: true,
text: "Test",
color: "#FFFFFF"
}
},
scales: {
x: {
type: 'time',
title: {
display: true,
text: 'Date',
color: "#FFFFFF"
},
time: {
unit: "month",
round: "month"
},
min: dateTimesConst[0],
max: dateTimesConst[dateTimesConst.length - 1],
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
},
y: {
title: {
display: true,
text: 'Number of Badges',
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
min: 0,
max: mostBadgesPerMonth + 1,
grid: {
borderColor: "#FFFFFF",
color: "#FFFFFF"
},
ticks: {
color: "#FFFFFF"
}
}
}
},
plugins: [
{
id: 'custom_canvas_background_color',
beforeDraw: (chart) => {
const ctx = chart.ctx;
ctx.save();
ctx.fillStyle = '#303030';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
}
]
};
const imageBuffer = await canvasRenderService.renderToBuffer(config)
fs.writeFileSync("./chart2.png", imageBuffer)
Again, big thanks to #uminder for the inspiration.
I need after each value to be a percent symbol ( % ) For example: 12% instead of 12
The below code in write in laravel php for the chartpie.
<script src="{{asset('assets/admin/js/vendor/apexcharts.min.js')}}"></script>
<script src="{{asset('assets/admin/js/vendor/chart.js.2.8.0.js')}}"></script>
<script>
var ctx = document.getElementById('tokenomi');
var myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels:<?=$label?>,
datasets: [{
data:<?=$values?>,
],
borderColor: [
'rgba(231, 80, 90, 0.75)'
],
borderWidth: 0,
}]
},
options: {
aspectRatio: 1,
responsive: true,
maintainAspectRatio: true,
elements: {
line: {
tension: 0 // disables bezier curves
}
},
scales: {
xAxes: [{
display: false
}],
yAxes: [{
display: false
}]
},
legend: {
display: false,
}
}
});
</script>
Check the current chart pie below, I tried a lot but I can't find a solution.
How do I add percent sign (%) behind of all values
Thanks
You need to define a tooltips.callback.label function as shown below. For further details please consult Chart.js v. 2.8.0 documentation here.
options: {
...
tooltips: {
callbacks: {
label: (tooltipItem, data) => data.datasets[0].data[tooltipItem.index] + '%'
}
},
...
Please note that you're using a rather old version of Chart.js, the today latest stable version is 3.7.0.
This question has been asked a few times but I've not been able to use the answers previously provided to finish my first dynamic Charts.js. I'm trying to build a simple example which works and once i've managed to do that I can expand on it. Currently I have the following:
1) A simple dropdown menu which you can pick a year from:
<form>
<div class="row">
<div class="label"><b>Select Year</b></div>
<select id="year" name="year" style = "width:100%">
<option value=2017>2017</option>
<option value=2018>2018</option>
</select>
</div>
</form>
<br>
2) Some javascript which I use to call my MySQL query on.change:
<script type="text/javascript">
jQuery(document).ready( function($) {
var valueCheck;
jQuery('#year').on( 'change', function () {
year = $('#year').val();
jQuery.ajax({
type: "POST",
url: "/wp-admin/admin-ajax.php",
data: {
action: 'call_chart_data',
year: year,
},
success:function(output){
jQuery('#y_data1').html( output );
}
});
}).change();
});
</script>
3) PHP which queries the MySQL database:
function get_chart_data(){
global $wpdb;
$year = $_POST['year'];
$myQuery = $wpdb->get_results('SELECT dreamTeamPoints FROM afl_master WHERE player_id = "CD_I270963" AND year = '.$year);
$data = array();
foreach ($myQuery as $result) {
$data[] = $result->dreamTeamPoints;
}
wp_die();
}
add_action('wp_ajax_nopriv_call_chart_data', 'get_chart_data');
add_action('wp_ajax_call_chart_data', 'get_chart_data');
The code to this point is successful in returning an array of values, as per below (when 2017 is selected):
Array ( [0] => 68 [1] => 152 [2] => 139 [3] => 143 [4] => 132 [5] => 155
[6] => 65 [7] => 59 [8] => 111 [9] => 157 [10] => 92 [11] => 62
[12] => 89 [13] => 83 [14] => 105 [15] => 34 [16] => 134 [17] => 47
[18] => 124 [19] => 97 [20] => 153 [21] => 149 [22] => 76 [23] => 97 )
Now finally, I have a static graph which I want to convert to dynamic by replacing the variable y_data1 (third line in following code) with the MySQL response, however I don't know how to feed this into the javascript.
<script type="text/javascript">
var x_time = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,"QF","SF","PF","GF"];
var y_data1 = [91,115,67,46,,,,,,,,,,,68,111,111,77,95,106,99,93];
var y_data2 = [84,74,64,79,,,,,,,,,,,82,84,80,82,88,87,78,79];
new Chart(document.getElementById("myChart"), {
type: 'bar',
data: {
labels: x_time,
datasets: [{
type:'line',
label: 'Fantasy Points',
fill:false,
backgroundColor: 'orange',
borderColor: 'orange',
data: y_data1,
yAxisID: 'left-axis',
pointRadius: 5,
pointHoverRadius: 7
}, {
label: 'Time On Ground (%)',
fill: true,
backgroundColor: 'black',
borderColor: 'black',
data: y_data2,
yAxisID: 'right-axis'
}]
},
options: {
title: {
display: false},
legend: { display: true },
maintainAspectRatio:true,
responsive: true,
tooltips: {mode: 'index', intersect: false},
hover: {mode: 'nearest', intersect: true},
scales: {
xAxes: [{display: true, stacked:true, scaleLabel: {display: true, labelString: 'Round'}}],
yAxes: [{
type:'linear',
id:'left-axis',
display: true,
position: 'left',
scaleLabel: {display: true, labelString: 'Fantasy Points'},
gridLines: {drawOnChartArea:false},
ticks: {beginAtZero: true}
},{
type:'linear',
id:'right-axis',
display: true,
position: 'right',
stacked:false,
scaleLabel: {display: true, labelString: 'Time On Ground (%)'},
gridLines: {drawOnChartArea:false},
ticks: {beginAtZero: true}
}]
}
}
});
</script>
Appreciate any help people can provide.
I'd be returning the values as a string, rather than an array:
$data = '';
foreach ($myQuery as $result) {
$data .= $result->dreamTeamPoints.',';
}
Then assigning it to a value on the successful ajax post:
success:function(output){
//jQuery('#y_data1').html( output );
var out = output;
}
and lastly putting the 'out' variable inside the assignment:
var y_data1 = [ out ];
You may be able to simplify it more - Go the Eagles.
I have datasets with x, y values x denotes date y denotes values.and i have successfully generated a chart. look at the pic attached below
I'm using this code to generate the chart (generated using the above chart)
var config = {
type: 'scatter',
data: {
//labels: this.HoursStrings,
datasets: [{
data: yserires, // example data [{x:2019/01/02,y:12},{x:2019/01/02,y:12}}]
fill: true,
borderColor: "#3e82f7",
yAxesGroup: "1"
}]
},
options: {
responsive: true,
title: {
display: false,
},
legend: {
display: false
},
showLines: true,
tooltips: {
mode: 'index',
intersect: true,
callbacks: {
label: function (tooltipItem, data) {
var value = data.datasets[0].data[tooltipItem.index];
var day = moment(new Date(value.x)).format(self.timeformat);
var point = value.y + " " + self.unityType
return point;
}
} //
},
hover: {
mode: 'nearest',
intersect: true
},
scales: {
xAxes: [{
display: true,
type: 'time',
time: {
unit: self.timeUnit
},
ticks: {
beginAtZero: true
}
}],
yAxes: [{
display: true,
ticks: {
beginAtZero: true,
userCallback: function (value, index, values) {
return self.roundValues(value) + " " + self.unityType
}
}
}]
},
elements: {
line: {
tension: .1, // bezier curves
}
}
}
};
setTimeout(() => {
var ctx = document.getElementById("canvas");
this.chart = new Chart(ctx, config);
this.chart.update()
}, 50)
Expected Result
how can group different y-axis values against the same x-axis values (should be one single value like the average of y-axis) is there any build in functions available in chart js or I need to group those value before binding the chart.
Note: The date should be grouped (averaged) by day/hour/week/month
like below image
You can preprocess your array getting the mean value from the data before feeding it to the graph:
// Your data array
let array = [{ x: '2019/01/02', y: 12 }, { x: '2019/01/02', y: 13 }]
// The output data for Chart js
var output = [];
// Get the non-unique values and add them to an array
array.forEach(function (item) {
var existing = output.filter(function (v, i) {
return v.x == item.x;
});
if (existing.length) {
var existingIndex = output.indexOf(existing[0]);
if (!Array.isArray(output[existingIndex].y))
output[existingIndex].y = [output[existingIndex].y]
output[existingIndex].y = output[existingIndex].y.concat(item.y);
} else {
if (typeof item.value == 'string')
item.value = [item.value];
output.push(item);
}
});
// Compute the mean
console.dir(output.map(val => {
val.y = (val.y).reduce((a, b) => a + b, 0) / (val.y).length
return val
}));
I am not familiar with chart js, but from my experience with D3 and time series visualization in general, the result you are seeing is the expected behavior. I.e. the time series visualization is constructed as a continuous line intersecting all data points, in the order the data points are specified.
So to answer your question about grouping, I would say yes, you need to group the data yourself before creating a chart.
First, however, you need to consider what grouping means - is it an average, a sum, a minimum, a maximum, or something different. I'm guessing you'll want an average, but it will depend on your use case and analysis needs.
I've written a short bit of code, that groups an array of entries by calculating the average of reoccurring x values:
const data = [
{ x: '2019/01/28', y: 19 },
{ x: '2019/01/29', y: 15 },
{ x: '2019/02/02', y: 12 },
{ x: '2019/02/02', y: 13 },
{ x: '2019/02/02', y: 14 },
{ x: '2019/02/02', y: 15 },
{ x: '2019/02/02', y: 18 },
{ x: '2019/02/02', y: 21 },
]
const dataGrouper = {}
data.forEach((dataPoint) => {
if (!dataGrouper[dataPoint.x]) {
dataGrouper[dataPoint.x] = []
}
dataGrouper[dataPoint.x].push(dataPoint)
})
const groupedData = []
Object.keys(dataGrouper).forEach((groupingKey) => {
const sum = dataGrouper[groupingKey].reduce((accumulator, currentValue) => {
return accumulator + currentValue.y
}, 0)
groupedData.push({
x: groupingKey,
y: sum / dataGrouper[groupingKey].length
})
})
console.log({groupedData})
I have these bar charts that will sometimes show zero values to the user. I'd like to show a bit of the bar chart so it does not look completely empty, but the data is connected to the visual representation of the bar. Is there any way I could have the top number say zero but the value be 3(or something small) in each column? Here is the code and a screenshots. One screenshot has data, the other is zero. I'd like the zero graph to show just a bit of the orange and green when at zero. Thanks
<canvas id="bar-chart" width="900" height="350"></canvas>
<script type="text/javascript">
var ctx = document.getElementById("bar-chart");
debugger;
var data = {
labels: ["Page Views", "Data Requests"],
datasets: [{
data: [<?php echo $databaseClass->totalViewsSelectedMonth($user_id, $year, 1);
?>, <?php
echo $databaseClass->isRfYearMonth($user_id, $year, 1);
?>],
backgroundColor: ["orange", "green"]
}]
}
var myChart = new Chart(ctx, {
type: 'bar',
data: data,
options: {
"hover": {
"animationDuration": 0
},
"animation": {
"duration": 1,
"onComplete": function() {
var chartInstance = this.chart,
ctx = chartInstance.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontSize, Chart.defaults.global.defaultFontStyle, Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function(dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
meta.data.forEach(function(bar, index) {
var data = dataset.data[index];
ctx.fillText(data, bar._model.x, bar._model.y - 5);
});
});
}
},
legend: {
"display": false
},
tooltips: {
"enabled": false
},
scales: {
yAxes: [{
display: false,
gridLines: {
display: false
},
ticks: {
max: Math.max(...data.datasets[0].data) + 20,
display: false,
beginAtZero: true
}
}],
xAxes: [{
gridLines: {
display: false
},
ticks: {
beginAtZero: true
}
}]
}
}
});
</script>
It looks like you can update the axes of your charts to a new default. So, using a modified form of their example, something like the following (modified to fit your code):
// ...
scales: {
yAxes: [{
display: false,
gridLines: {
display: false
},
ticks: {
max: Math.max(...data.datasets[0].data) + 20,
min: -3, // ADDED
display: false,
beginAtZero: true
}
}],
// ...
... would do what you're looking for - with the side benefit that 0 and 3 wouldn't look like the same value, the relative bar size should stay accurate.