Chart.js only last point [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I want to show only the last point. Can somebody help with this?
Expected
Actual

Well, hello!
You can set the pointRadius to zero, eg:
var myChart = new Chart(
ctx, {
type: 'line',
data: {
labels: [...]
datasets: [
{
data: [...],
pointRadius: 0, # <<< Here.
}
]
},
options: {}
})
For many points (like your question) change pointRadius to array:
pointRadius: [0, 0, 0, 0, 0, 0, 5]
Follow my full example in Fiddle
If this helped you, check it out

Update
I just saw Edi's answer as I submitted mine and he mentioned the pointRadius. I noticed this too messing around and as he stated, you can pass in an array to simplify this.
I wrote a method below to "grow" the array (scaling with the data) in order to ensure that the last data point has a radius > 0.
The custom Line chart appears to render much smoother because the native draw function has been modified.
Chart.js 2.0 introduces the concept of controllers for each dataset. Like scales, new controllers can be written as needed.
I copied the source for controller.line.js and modified the loop for the draw() method to only draw the last point. This is the easiest solution without having to figure out how to set an array of radius values.
/** Initialize array with radius of n and queue values ahead of it */
function pointRadiusLast(radius, length, initialArray) {
var result = initialArray || [ radius ];
while (result.length < length) result.unshift(0); // Place zeros in front
return result;
}
// https://www.chartjs.org/docs/latest/developers/charts.html
var valueOrDefault = Chart.helpers.valueOrDefault;
const lineEnabled = (dataset, options) => valueOrDefault(dataset.showLine, options.showLines)
Chart.defaults.derivedLine = Chart.defaults.line;
var customController = Chart.controllers.line.extend({
/** If you want to EXTEND the draw function.
draw: function(ease) {
Chart.controllers.line.prototype.draw.call(this, ease); // Override the super method
var meta = this.getMeta();
var pt0 = meta.data[0];
var radius = pt0._view.radius;
var ctx = this.chart.chart.ctx;
ctx.save();
// Custom drawing...
ctx.restore();
}
If you want to OVERRIDE the draw function...
*/
draw: function(ease) {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var points = meta.data || [];
var area = chart.chartArea;
var ilen = points.length;
var halfBorderWidth;
var i = 0;
if (lineEnabled(me.getDataset(), chart.options)) {
halfBorderWidth = (meta.dataset._model.borderWidth || 0) / 2;
Chart.helpers.canvas.clipArea(chart.ctx, {
left: area.left,
right: area.right,
top: area.top - halfBorderWidth,
bottom: area.bottom + halfBorderWidth
});
meta.dataset.draw();
Chart.helpers.canvas.unclipArea(chart.ctx);
}
// Draw the points
for (; i < ilen; ++i) {
if (i === ilen - 1) { // Only the last or simply... points[ilen - 1].draw(area)
points[i].draw(area);
}
}
}
});
Chart.controllers.derivedLine = customController; // Specify type on chart below
var data = [
{ month : 'January' , low : 25, high : 43 },
{ month : 'February' , low : 27, high : 47 },
{ month : 'March' , low : 35, high : 56 },
{ month : 'April' , low : 44, high : 67 },
{ month : 'May' , low : 54, high : 76 },
{ month : 'June' , low : 63, high : 85 }
];
var additionalData = [
{ month : 'July' , low : 68, high : 89 },
{ month : 'August' , low : 66, high : 87 },
{ month : 'September' , low : 59, high : 81 },
{ month : 'October' , low : 46, high : 69 },
{ month : 'November' , low : 37, high : 59 },
{ month : 'December' , low : 30, high : 48 }
];
var defaults = {
fill: false,
lineTension: 0.1,
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBorderWidth: 2,
//pointRadius: 0, <-- Do not specify this, we will supply it below
pointHitRadius: 10
};
/** Initialize array with radius of n and queue values ahead of it */
function pointRadiusLast(radius, length, initialArray) {
var result = initialArray || [ radius ];
while (result.length < length) result.unshift(0);
return result;
}
var dataSets = [
Object.assign({
label: "Low",
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(75,192,192,1)",
pointBorderColor: "rgba(75,192,192,1)",
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
data: data.map(item => item.low)
}, defaults),
Object.assign({
label: "High",
backgroundColor: "rgba(192,75,75,0.4)",
borderColor: "rgba(192,75,75,1)",
pointBorderColor: "rgba(192,75,75,1)",
pointHoverBackgroundColor: "rgba(192,75,75,1)",
pointHoverBorderColor: "rgba(192,75,75,1)",
data: data.map(item => item.high)
}, defaults)
];
var chartOptions = {
title : {
text : 'Weather Averages for Washington, DC',
display : true
},
showLines: true
};
var defaultChart = Chart.Line(document.getElementById('default-line-chart'), {
data: {
labels: data.map(item => item.month),
datasets: dataSets.map(dataset => $.extend(true, {}, dataset, {
pointRadius : pointRadiusLast(5, data.length)
}))
},
options : $.extend(true, {}, chartOptions, {
title : {
text : chartOptions.title.text + ' (Default)'
}
})
});
var customChart = new Chart(document.getElementById('custom-line-chart'), {
type: 'derivedLine',
data: {
labels: data.map(item => item.month),
datasets: dataSets.map(dataset => $.extend(true, {}, dataset, {
pointRadius : 5
}))
},
options : $.extend(true, {}, chartOptions, {
title : {
text : chartOptions.title.text + ' (Custom)'
}
})
});
setTimeout(function() {
var category = 'month', fields = [ 'low', 'high' ];
var counter = additionalData.length;
var intervalId = setInterval(function() {
if (counter --> 0) {
var record = additionalData[additionalData.length - counter - 1];
addData(defaultChart, record, category, fields);
addData(customChart, record, category, fields);
} else {
clearInterval(intervalId);
}
}, 1000); // Update every second
}, 1000); // 1 second delay
function addData(chart, data, xField, yFields) {
chart.data.labels.push(data[xField]);
chart.data.datasets.forEach((dataset, i) => {
dataset.data.push(data[yFields[i]]);
if (chart.config.type === 'line') { // Only the normal Line chart
dataset.pointRadius = pointRadiusLast(5, dataset.data.length);
}
});
chart.update();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script>
<canvas id="default-line-chart" width="400" height="120"></canvas>
<canvas id="custom-line-chart" width="400" height="120"></canvas>

Related

Chart.js (chartjs-node-canvas) create date-based floating bar graph

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.

Is there a better way to create an 'n' number of charts in ChartJS and ASP.NET C#?

EDIT: I have narrowed it down to something like this:
for (i = 0; i < data.length; i++) {
const newCanvas = document.createElement("canvas");
newCanvas.id = data[i].design_name;
const currentDiv = document.getElementById("chartSpace");
var parentDiv = document.getElementById("gridHere");
parentDiv.insertBefore(newCanvas, currentDiv);
createChart([data[i].design_name], [data[i].design_start, data[i].design_end]);
}
With the create chart making the chart id = to the array 'labels':
const myChart = new Chart(
document.getElementById(labels),
config
);
I am attempting to create a tool that creates an 'n' number of charts in ChartJS and save each of them as images. Currently, designButtonClick() sends the 'event_fky' value to
getDesigns(event_fky) in my controller. This method returns all designs with that foreign key. In turn, the chart plots each design on the chart. I need to evolve this into
something that can make a group individual charts for each design based on how many designs there are. My current solution, still conceptual, is to have methods in my controller
create chart variables 'chartData [data here]' and 'labels[datahere]' while looping through the designs returned from getDesigns, and sending those back to the JS script createChart
'n' number of times for each design. It would also send html chart/html element ids based on the design_name attribute to send back to createChart. This way, it is create a unique
chart 'n' number of times.
To save the charts as images, I would use the same set of element ids generated by getDesigns to send the charts to images using JS' toBase64Image() function and saving them to the
user's system.
Is this the best way of solving this problem? Or is this spaghetti, and is there a better method for this? My attempts to find better online answers have only resulted in docs on
updating one chart dynamically, not creating a dynamic number of charts. Much help is appreciated, code is below as well as a screenshot of the current chart output.
JavaScript:
var labels = [];
var cData = [];
function designButtonClick() {
var event_fky = 3;
$.ajax({
url: 'Tree/getDesigns',
type: 'POST',
data: { event_fky }
}).done(function (data) {
for (i = 0; i < data.length; i++) {
labels.push(data[i].design_name);
cData.push([data[i].design_start, data[i].design_end])
}
createChart(labels, cData);
});
}
function createChart(labels, cData) {
const data = {
labels: labels,
datasets: [{
barThickness: 2,
categoryPercentage: .5,
label: 'Design Time',
data: cData,
backgroundColor: [
'rgba(255, 26, 104, 0.2)'
],
borderColor: [
'rgba(255, 26, 104, 1)'
],
borderWidth: 1,
borderSkipped: false,
borderRadius: 20
}]
};
const config = {
type: 'bar',
data,
options: {
indexAxis: 'y',
scales: {
y: {
beginAtZero: true
},
x: {
min: 0,
max: 6000,
ticks: {
stepSize: 1000
}
}
}
}
};
const myChart = new Chart(
document.getElementById('myChart'),
config
);
}
C# Controller:
public ActionResult getDesigns(int? event_fky)
{
var designs = from e in _context.designs
where (event_fky.HasValue ? e.event_fky == event_fky : e.event_fky == null)
select new
{
design_pky = e.design_pky,
design_name = e.design_name,
design_start = e.design_start,
design_end = e.design_end
};
return this.Json(designs, JsonRequestBehavior.AllowGet);
}
Designs Table:
--------Design--------
design_pky |int
event_fky |int
design_name |varchar
design_start |number
design_end |number
Screenshot of Chart
This is a working answer for the javascript:
var eventList = function () {
var tmp = null;
$.ajax({
'async': false,
url: 'Tree/getEventIDs',
type: 'POST',
data: {},
'success': function (data) {
tmp = data;
}
});
return tmp;
}();
for (var i = 0; i < eventList.length; i++) {
event_fky = eventList[i].event_pky;
event_name = eventList[i].event_name;
event_length = eventList[i].event_end;
var designList = function () {
var tmpi = null;
$.ajax({
'async': false,
url: 'Tree/getDesigns',
type: 'POST',
data: {event_fky},
'success': function (data1) {
tmpi = data1;
}
});
console.log(event_fky);
console.log(tmpi);
return tmpi;
}();
var dLabels = [];
var dLengths = [];
for (var j = 0; j < designList.length; j++) {
dLabels.push(designList[j].design_name);
dLengths.push([designList[j].design_start, designList[j].design_end]);
}
const newCanvas = document.createElement("canvas");
newCanvas.id = event_name;
const currentDiv = document.getElementById("chartSpace");
var parentDiv = document.getElementById("gridHere");
parentDiv.insertBefore(newCanvas, currentDiv);
if (dLabels.length != 0) {
createChart(dLabels, dLengths, event_name, event_length);
}
}
}
function createChart(labels, cData, evName, evLen) {
// setup
const data = {
labels: labels,
datasets: [{
barThickness: 4,
categoryPercentage: .5,
label: evName,
data: cData,
backgroundColor: [
'rgba(' + Math.random() * 85 + ', ' + Math.random() * 170 + ', ' + Math.random() * 255 + ', 1)'
],
borderColor: [
'rgba(255, 26, 104, 1)'
],
borderWidth: 0,
borderSkipped: false,
borderRadius: 20
}]
};
// config
const config = {
type: 'bar',
data,
options: {
indexAxis: 'y',
scales: {
y: {
beginAtZero: true
},
x: {
min: 0,
max: evLen,
ticks: {
stepSize: 100
}
}
}
}
};
// render init block
const myChart = new Chart(
document.getElementById(evName),
config
);
return myChart;
}

Chartjs - make line start at ... but maintain x-axis data

I created a dynamic line chart based on some input data. The intention is that the customer can indicate with a dropdown on which month the "Investment" should start.
So, for example, if the "Investment" does not start until month 6, then that line should only start at 6 on the x-axis. But the other lines "Case" and "ROI" should still just start at 1.
I've tried several things but to no avail.
I tried changing the x-axis "min ticks" based on the selection the user made, but that makes all lines start at another point instead of the "Investment" line only. Another problem is that every number before the selection then dissapears from the x-axis. But I really want to keep every number from 1-60, even if the user chooses to start the "Investment" on month 10, for example.
I would really appreciate some help! Thanks.
Here's my fiddle: https://jsfiddle.net/js5pha24/
var options = {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Case',
data: [],
backgroundColor: 'rgba(152,164,135, 0.5)',
borderColor: 'rgb(152,164,135)',
fill: false
}, {
label: 'Case',
data: [],
backgroundColor: 'rgba(145,139,167, 0.5)',
borderColor: 'rgb(145,139,167)',
fill: false
}, {
label: 'Case',
data: [],
backgroundColor: 'rgba(206,157,206, 0.5)',
borderColor: 'rgb(206,157,206)',
fill: false
}]
},
options: {
legend: {
display: true,
position: "top"
},
scales: {
xAxes: [{
ticks: {
beginAtZero: true,
autoSkip: true,
maxRotation: 0,
minRotation: 0
}
}],
yAxes: [{
ticks: {
callback: value => {
return "€ " + value;
}
}
}]
}
}
}
for (let i = 1; i <= 60; i++) {
options.data.labels.push(i);
const caseMonth = 118187 * i;
options.data.datasets.find(set => set.label === "Case").data.push(caseMonth);
const investMonth = 500000 + (20000 * i);
options.data.datasets.find(set => set.label === "Investment").data.push(investMonth);
const roiMonth = caseMonth - investMonth;
options.data.datasets.find(set => set.label === "ROI").data.push(roiMonth);
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
canvas { background-color : #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.js"></script>
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
</body>
You can put null values on the chart data so one line can start after the others. For example if you want the investment line start at month 10, you can replace the the first ten investMonth values with null.
If understood correctly you still want to use the investMonth value in the roiMonth calculation so I created "investMonthValue" so only investment will get null if it is less than investmentStartMonth.
let investmentStartMonth = 10
for (let i = 1; i <= 60; i++) {
options.data.labels.push(i);
const caseMonth = 118187 * i;
options.data.datasets.find(set => set.label === "Case").data.push(caseMonth);
let investMonth = 500000 + (20000 * i);
let investMonthValue = i<investmentStartMonth?null:investMonth
options.data.datasets.find(set => set.label === "Investment").data.push(investMonthValue);
const roiMonth = caseMonth - investMonth;
options.data.datasets.find(set => set.label === "ROI").data.push(roiMonth);
}

How can I delete an instance of a chart using chart.js

My web page loads and automatically creates a chart with data it pulls from an API I wrote.
Ive also got a HTML input that allows me to select the month. I have added an event listener to that input that triggers a function to draw a new chart based on the month i have selected (it recalls the api too with these new parameters).
It looked like it worked, but on further inspection, I realised that the previous chart was behind the new chart.
Is there a way i can remove the old chart?
<div class="chart_div" style="max-height: 400px; max-width: 800px; margin: 5px">
<label for="monthSelector">Start month:</label>
<input
type="month"
id="monthSelector"
name="start"
min="{{min_date}}"
max="{{today_date}}"
value="{{today_date}}"
/>
<canvas id="myChart" width="400" height="400"> </canvas>
</div>
<script>
var canvas = document.getElementById("myChart");
const context = canvas.getContext("2d");
var monthSelector = document.getElementById("monthSelector");
// event listener for month slider
monthSelector.addEventListener("input", function () {
selected_date = monthSelector.value + "-01";
drawChart(selected_date);
});
var today = monthSelector.value + "-01";
// Draw chart upon loading page
drawChart(today);
function drawChart(date) {
x_labels = [];
data_set_scratches = [];
data_set_medical_scores = [];
context.clearRect(0, 0, canvas.width, canvas.height);
var url_scratches =
"http://127.0.0.1:8000/api/get-daily-scratch-count/" + date + "/";
var url_medical_scores =
"http://127.0.0.1:8000/api/get-daily-medical-score/" + date + "/";
// get x label based on dates of selected month
var date_vals = date.split("-");
var num_days = getDaysInMonth(date_vals[1], date_vals[0]);
console.log(num_days);
for (var i = 1; i <= num_days; i++) {
var num = minTwoDigits(i);
x_labels.push(num);
}
// call api to fetch the data
Promise.all([
fetch(url_scratches)
.then((res) => res.json())
.then(function (data) {
var scratches = data;
var dateIndex = 0;
var scratchesIndex = 0;
while (scratchesIndex < scratches.length) {
var scratchDates = scratches[scratchesIndex].date.split("-"); // Splits date into list ["YYYY", "MM", "DD"]
// if dates are equal, push total and increase both index
if (scratchDates[2] == x_labels[dateIndex]) {
data_set_scratches.push(scratches[scratchesIndex].total);
dateIndex += 1;
scratchesIndex += 1;
// if dates are not equal, push 0 and increase only date index
} else {
data_set_scratches.push(0);
dateIndex += 1;
}
}
console.log(data_set_scratches);
}),
fetch(url_medical_scores)
.then((res) => res.json())
.then(function (data) {
var medicalScores = data;
var dateIndex = 0;
var scoreIndex = 0;
while (scoreIndex < medicalScores.length) {
var scoreDates = medicalScores[scoreIndex].date.split("-"); // Splits date into list ["YYYY", "MM", "DD"]
// if dates are equal, push score then increase both index
if (scoreDates[2] == x_labels[dateIndex]) {
data_set_medical_scores.push(medicalScores[scoreIndex].score);
dateIndex += 1;
scoreIndex += 1;
// if dates are not equal, push 0 and increase only date index
} else {
data_set_medical_scores.push(0);
dateIndex += 1;
}
}
console.log(data_set_medical_scores);
}),
]).then(function () {
// Creat chart from api Data
let chartTest = new Chart(myChart, {
type: "line",
data: {
labels: x_labels,
datasets: [
{
label: "Scratch Total",
fill: false,
data: data_set_scratches,
borderColor: "green",
borderWidth: 1,
lineTension: 0,
backgroundColor: "red",
pointBackgroundColor: "red",
pointBorderColor: "red",
pointHoverBackgroundColor: "red",
pointHoverBorderColor: "red",
},
{
data: data_set_medical_scores,
label: "Medical Score",
fill: false,
borderColor: "orange",
borderWidth: 1,
lineTension: 0,
backgroundColor: "#e755ba",
pointBackgroundColor: "#55bae7",
pointBorderColor: "#55bae7",
pointHoverBackgroundColor: "#55bae7",
pointHoverBorderColor: "#55bae7",
},
],
},
options: {
title: {
display: true,
text: "Daily Scratches/Medical Scores",
},
scales: {
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
xAxis: [
{
ticks: {
stepSize: 1,
autoSkip: false,
},
},
],
},
},
});
});
}
// function to get num of days in month
function getDaysInMonth(month, year) {
return new Date(year, month, 0).getDate();
}
function minTwoDigits(n) {
return (n < 10 ? "0" : "") + n;
}
</script>
What I would really like to do is delete the existing chart before the api is called again? Any help would be greatly appreciated.
call the destroy method of the chart object
.destroy()
Use this to destroy any chart instances that are created. This will clean up any references stored to the chart object within Chart.js, along with any associated event listeners attached by Chart.js. This must be called before the canvas is reused for a new chart.
// Destroys a specific chart instance
myLineChart.destroy();
https://www.chartjs.org/docs/latest/developers/api.html?h=destroy

Chart.JS multiple plugins do not operate

I am working with chart.js and am trying to add 2 plugins to the same chart, but when applied, both of the plugins disappear and there is no direct error in the console.
Does anyone know how to implement two plugins to the same graph?
First pulgin
Second plugin referance
Basically, the graph has to show data labels on a line chart and at the same time, draw yAxis lines, but only starting from the points on the line chart.
For some reason, the chart will not show either of them when both are applied.
Chart.helpers.merge(Chart.defaults.global.plugins.datalabels, {
color : '#ffffff'
})
// Chart.plugins.unregister(ChartDataLabels);
// var chart = new Chart(ctx, {
// / plugins: [ChartDataLabels],
// options: {
// // ...
// }
// })
var ctx = document.getElementById('myChart').getContext('2d');
var chart = new Chart(ctx, {
// The type of chart we want to create
type : 'line',
// The data for our dataset
data : {
labels : [ '', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun', '' ],
datasets : [ {
label : 'My first dataset',
borderWidth : 3,
borderColor : 'rgb(255,0, 0)',
data : data1,
} ]
},
// Configuration options go here
options : {
elements : {
point : {
radius : 3
}
},
legend : {
display : false,
labels : {
fontColor : "white",
fontSize : 18
}
},
scales : {
yAxes : [ {
ticks : {
fontSize : 0,
beginAtZero : false,
max : 40,
},
gridLines : {
display : false,
drawBorder : false,
}
} ],
xAxes : [ {
ticks : {
fontColor : "white",
fontSize : 14,
beginAtZero : 0,
},
gridLines : {
display : false,
}
} ]
},
plugins : [ { // this is the magical bit :)
afterRender : function(c, options) {
let
meta = c.getDatasetMeta(0), max;
c.ctx.save();
c.ctx.strokeStyle = c.config.options.scales.xAxes[0].gridLines.color;
c.ctx.lineWidth = c.config.options.scales.xAxes[0].gridLines.lineWidth;
c.ctx.beginPath();
meta.data
.forEach(function(e) {
if (max == undefined
|| c.config.data.datasets[0].data[e._index] > max) {
max = c.config.data.datasets[0].data[e._index];
}
c.ctx.moveTo(e._model.x,
meta.dataset._scale.bottom);
c.ctx
.lineTo(e._model.x,
e._model.y);
});
c.ctx.textBaseline = 'top';
c.ctx.textAlign = 'right';
c.ctx.fillStyle = 'black';
c.ctx.fillText('Max value: ' + max,
c.width - 10, 10);
c.ctx.stroke();
c.ctx.restore();
}
} ],
tooltips : {
callbacks : {
label : function(tooltipItem) {
console.log(tooltipItem)
return tooltipItem.yLabel;
}
}
}
}
});
var data1 = [ 1, 9, 12, 3, 15, 8, 2, -5, 3, 4, 5, 7 ];
myChart(data1);
HTML .js code
<script src="js/chart.js"></script>
<!-- data label .js -->
<script
src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#0.5.0"></script>
<!-- yAxis lines .js -->
<script
src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script>
<canvas id="myChart"></canvas>
var chart = new Chart(document.getElementById('chart'), {
type : 'line',
data : {
labels : [ '', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun', '' ],
datasets : [ {
label : 'My first dataset',
borderWidth : 1,
borderColor : 'rgb(255,0, 0)',
data : [ 1, 9, 12, 3, 15, 8, 2, -5, 3, 4, 5, 7 ],
datalabels: {
align: 'end',
anchor: 'end',
backgroundColor: 'rgb(255, 120, 12, .2)',
borderRadius: 20
}
}]
},
options : {
scales : {
xAxes : [ {
gridLines : {
display : false,
color: 'rgba(255, 120, 12, .2)',
lineWidth: 5
}
} ],
yAxes : [{
gridLines : {
display : false,
color: 'rgba(255, 120, 12, .2)',
lineWidth: 5
},
ticks : {
beginAtZero: true
}
}]
},
},
plugins : [{ // this is the magical bit :)
afterRender : function(c, options) {
let meta = c.getDatasetMeta(0), max;
c.ctx.save();
c.ctx.strokeStyle = c.config.options.scales.xAxes[0].gridLines.color;
c.ctx.lineWidth = c.config.options.scales.xAxes[0].gridLines.lineWidth;
c.ctx.beginPath();
meta.data.forEach(function(e)
{
if (max == undefined || c.config.data.datasets[0].data[e._index] > max) {
max = c.config.data.datasets[0].data[e._index];
}
c.ctx.moveTo(e._model.x,meta.dataset._scale.bottom);
c.ctx.lineTo(e._model.x,e._model.y);
});
c.ctx.textBaseline = 'top';
c.ctx.textAlign = 'right';
c.ctx.fillStyle = 'black';
c.ctx.fillText('Max value: ' + max, c.width - 10, 10);
c.ctx.stroke();
c.ctx.restore();
}
}],
tooltips : {
callbacks : {
label : function(tooltipItem) {
console.log(tooltipItem)
return tooltipItem.yLabel;
}
}
}
});
>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels#0.6.0/dist/chartjs-plugin-datalabels.min.js"></script>
<canvas id="chart"></canvas>

Categories