I achieved animating a plot using Jukka Kurkela example here.
Now I am having trouble customizing this plot further.
Logic of the custom plot
The plot starts animating with the x-axis labels being 0-20. When the plot reaches 20 then update the x-axis to be 20-40. Increment i or 20 until the x-axis reach its limit.
How to apply the logic above to the Example below?
// Generating data
var data = [];
var prev = 100;
for (var i=0;i<200;i++) {
prev += 5 - Math.random()*10;
data.push({x: i, y: prev});
}
var delayBetweenPoints = 100;
var started = {};
var ctx2 = document.getElementById("chart2").getContext("2d");
var chart2 = new Chart(ctx2, {
type: "line",
data: {
datasets: [
{
backgroundColor: "transparent",
borderColor: "rgb(255, 99, 132)",
borderWidth: 1,
pointRadius: 0,
data: data,
fill: true,
animation: (context) => {
var delay = 0;
var index = context.dataIndex;
if (!started[index]) {
delay = index * delayBetweenPoints;
started[index] = true;
}
var {x,y} = index > 0 ? context.chart.getDatasetMeta(0).data[index-1].getProps(['x','y'],
true) : {x: 0, y: 100};
return {
x: {
easing: "linear",
duration: delayBetweenPoints,
from: x,
delay
},
y: {
easing: "linear",
duration: delayBetweenPoints * 500,
from: y,
delay
},
skip: {
type: 'boolean',
duration: delayBetweenPoints,
from: true,
to: false,
delay: delay
}
};
}
}
]
},
options: {
scales: {
x: {
type: 'linear'
}
}
}
});
<div class="chart">
<canvas id="chart2"></canvas>
</div>
<script src="https://www.chartjs.org/dist/master/Chart.js"></script>
Solved it! Instead of incrementing 20 seconds, it is incrementing every 5 seconds ahead of time. Definitely a better experience for the user.
Got help from Rowf Abd's post.
var myData = [];
var prev = 100;
for (var i=0;i<60;i++) {
prev += 5 - Math.random()*10;
myData.push({x: i, y: prev});
}
var ctx = document.getElementById('myChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
data: [myData[0]],
pointRadius: 0,
fill: false,
borderColor: "black",
lineTension: 0
}]
},
options: {
legend: {
onClick: (e) => e.stopPropagation()
},
title:{
fontColor: 'Black'
},
layout: {
padding: {
right: 10
}
},
scales: {
xAxes: [{
type: 'linear',
ticks: {
}
}],
yAxes: [{
scaleLabel: {
// fontFamily: 'Lato',
fontSize: 19,
fontColor: "Black"
}
}]
}
}
});
var next = function() {
var data = chart.data.datasets[0].data;
var count = data.length;
var xabsmin = 20;
var xabsmax = 60;
var incVar = 5;
data[count] = data[count - 1];
chart.update({duration: 0});
data[count] = myData[count];
chart.update();
if (count < myData.length - 1) {
setTimeout(next, 500);
}
if (data[count].x < xabsmin) {
chart.config.options.scales.xAxes[0].ticks.min = xabsmin - xabsmin;
chart.config.options.scales.xAxes[0].ticks.max = xabsmin;
chart.update();
}
if(data[count].x >= xabsmin && data[count].x < (xabsmax)){
var currentT = parseFloat(data[count].x);
var modDiv = (currentT % incVar);
var tempXMax = (currentT) + (incVar - modDiv);
chart.config.options.scales.xAxes[0].ticks.max = tempXMax;
chart.config.options.scales.xAxes[0].ticks.min = tempXMax - xabsmin;
chart.update();
}
}
setTimeout(next, 500);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.3/Chart.min.js"></script>
<canvas id="myChart"></canvas>
Related
I try to make a link if a onclick event on a doughnut chart slice happens. My datasources are 3 arrays with labels, value, and the id for the url.
HTML:
<canvas id="pie-chart" style='display: none;'></canvas>
<!-- Php Arrays to JS -> PIE-CHARTDATA -->
<script type="text/javascript">
var chartIds = [[12,14,17,18]];
var chartValues = [[208.09,296.86,634.975,972.808]];
var chartLabels = [["BTC","AAPL","MSFT","ETH"]];
</script>
JS:
if (chartValues.length != 0 ) {
document.getElementById("pie-chart").style.display= "block";
}
Chart.register(ChartDataLabels);
var chartValuesInt = [];
length = chartValues[0].length;
for (var i = 0; i < length; i++)
chartValuesInt.push(parseInt(chartValues[0][i]));
var data = [{
data: chartValuesInt,
chartIds,
backgroundColor: [
"#f38000",
"#5f44f5",
"#333333",
],
borderColor: "#000"
}];
var options = {
borderWidth: 4,
hoverOffset: 6,
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false,
},
datalabels: {
formatter: (value, ctx) => {
let sum = 0;
let dataArr = ctx.chart.data.datasets[0].data;
dataArr.map(data => {
sum += data;
});
let percentage = (value*100 / sum).toFixed(2)+"%";
return [ctx.chart.data.labels[ctx.dataIndex],
percentage,
'$' + value ] ;
},
textAlign: 'center',
color: '#fff',
borderRadius: 50,
padding:10,
labels: {
title: {
font: {
weight: 'bold',
size: '16px'
}
},
}
}
},
options:{
onClick: (e, activeEls) => {
let datasetIndex = activeEls[0].datasetIndex;
let dataIndex = activeEls[0].index;
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
console.log("In click", datasetLabel, value);
//link to url with:[chartIds]
}
}
};
//IMAGE CENTER
const image = new Image();
image.src = 'img/pie-home2.png';
const plugin = {
id: 'custom_canvas_background_image',
beforeDraw: (chart) => {
if (image.complete) {
const ctx = chart.ctx;
const {top, left, width, height} = chart.chartArea;
const x = left + width / 2 - image.width / 2;
const y = top + height / 2 - image.height / 2;
ctx.drawImage(image, x, y);
} else {
image.onload = () => chart.draw();
}
}
};
var ctx = document.getElementById("pie-chart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: chartLabels[0],
datasets: data,
chartIds
},
options: options,
plugins: [plugin],
});
why does the onclick didn't work ?
how do i get the id with the right index from the slice where the event happens?
I searched already, but couldn't find a answer to these 2 questions.
You onClick function does not work because you define an options object within your options object and put the onClick in there. This is not supported. When you remove the inner options layer it will work:
const options = {
borderWidth: 4,
hoverOffset: 6,
plugins: {
legend: {
display: false
},
tooltip: {
enabled: false,
},
datalabels: {
formatter: (value, ctx) => {
let sum = 0;
let dataArr = ctx.chart.data.datasets[0].data;
dataArr.map(data => {
sum += data;
});
let percentage = (value * 100 / sum).toFixed(2) + "%";
return [ctx.chart.data.labels[ctx.dataIndex],
percentage,
'$' + value
];
},
textAlign: 'center',
color: '#fff',
borderRadius: 50,
padding: 10,
labels: {
title: {
font: {
weight: 'bold',
size: '16px'
}
},
}
}
},
onClick: (e, activeEls) => {
let datasetIndex = activeEls[0].datasetIndex;
let dataIndex = activeEls[0].index;
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
console.log("In click", datasetLabel, value);
//link to url with:[chartIds]
}
};
the issue I have now is that I'm trying to use objects in the "data" field of my Chartjs script. This is my code below:
<canvas id="QGL_Chart"></canvas>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<script>
var interval = window.onload = () => {
var selectedDate;
const buyPriceData = [];
const buyVolData = [];
const sellPriceData = [];
const sellVolData = [];
var ctx = document.getElementById("QGL_Chart").getContext('2d');
ctx.canvas.width = 934;
ctx.canvas.height = 400;
var myChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
label: 'Line A',
data: [{
x: 90,
y: 90
}, {
x: 20,
y: 96
}, {
x: 15,
y: 97
}]
},
{
label: 'Line B',
data: [{
x: 10,
y: 96
}, {
x: 20,
y: 95.8
}, {
x: 100,
y: 99
}]
}
]
},
options: {
title: {
display: true,
text: 'Equilibrum Graph',
fontSize: 18
},
legend: {
display: true,
position: "bottom"
},
scales: {
xAxes: [{
ticks: {
reverse: false,
// stepSize: 6,
min: 0
}
}]
}
}
});
function refreshCurveData () {
selectedDate = document.querySelectorAll(".buyclass .column-date.sorting_1")[1].textContent;
buyPriceTable = document.querySelectorAll(".buyclass td.column-price");
buyVolTable = document.querySelectorAll(".buyclass td.column-volume");
sellPriceTable = document.querySelectorAll(".sellclass td.column-price");
sellVolTable = document.querySelectorAll(".sellclass td.column-volume");
let i = 0;
do {
var buyPriceData1 = buyPriceTable[i].textContent;
var buyVolData1 = buyVolTable[i].textContent;
var sellPriceData1 = sellPriceTable[i].textContent;
var sellVolData1 = sellVolTable[i].textContent;
buyPriceData[i] = `{x: ${buyPriceData1}, y: ${buyVolData1}}`
sellPriceData[i] = `{x: ${sellPriceData1}, y: ${sellVolData1}}`
//buyPriceData[i] = buyPriceData[i].replace(/"/g, "");
//sellPriceData[i] = sellPriceData[i].replace(/"/g, "");
// buyPriceData.push ({ x: buyPriceData1, y: buyVolData1 });
// sellPriceData.push ({ x: sellPriceData1, y: sellVolData1 });
i++;
}
while ( document.querySelectorAll(".column-date.sorting_1")[i].textContent == selectedDate && i < 9);
}
setInterval(() => {
refreshCurveData();
myChart.update();
},2500);
};
</script>
When I replace the codes in the "data" fields with buyPriceData and sellPriceData respectively, the graph does not visualize, I also tried doing:
data: {
datasets: [{
label: 'Line A',
data: Object.values(buyPriceData)
},
{
label: 'Line B',
data: Object.values(sellPriceData)
}
]
},
But I still have the same problem, please can someone help me with this?
I've created a pen here: https://codepen.io/scottfranc/pen/BaLGwZK
Thank you.
it looks like you are saving a String in buyPriceData. It looks like it should be an object
Maybe try this:
buyPriceData[i] = { x: buyPriceData1, y: buyVolData1 };
and then do the same for sellPriceData
I am trying to develop a Crash Game, where a multiplier (Y) increases exponentially and dynamically over time (X), causing the chart to re-render at each tick.
You can see an example of the chart game here
TL;DR: I am trying to achieve a "zoom-out" effect of the chart as my ticks increase in values (x,y).
Where my code fails is when ticks data values (x,y, respectively time and multiplier) surpass suggestedMax tick values. The only reason I am using suggestedMax is to have some labels on the chart at the beginning.
I have tried to achieve this by using both line and scatter chart type, but the final outcome is simply unacceptable from a performance point of view.
Here is my code:
const HomePlaygroundView = () => {
var chart = undefined;
const chartText = useRef(null);
let last_tick_received = 0;
const incrementChart = () => {
last_tick_received += 100;
};
const onServerTickReceived = (multiplier, msLapsed) => {
// Update chart multiplied
if (chart.data.datasets[0].data.length >= 100) {
// Halve the array to save performance (lol)
for (let i = 1; i < 100; i += 2) {
console.log("Reducing chart data");
chart.data.datasets[0].data.splice(i, 1);
}
}
chart.data.datasets[0].data.push({
x: msLapsed,
y: multiplier,
});
// This is basically my zoom out effect implementation...
if (multiplier >= 2.5) { // Increase suggestedMax only if bigger data needs to be fit
chart.options.scales.yAxes[0].ticks.suggestedMax = multiplier;
}
if (msLapsed > 9000) { // Same logic as above
chart.options.scales.xAxes[0].ticks.suggestedMax = msLapsed;
}
if (msLapsed < 10000) {
// Fit msLapsed in the pre-existing 10 seconds labels of x axis (this is a hell of a workaround)
let willInsertAtIndex = undefined;
for (let i = 0; i < chart.data.labels.length; i++) {
let current = chart.data.labels;
if (current < msLapsed) {
// Insert at i + 1? Check the next index if it's bigger than msLapsed
let nextVal = chart.data.labels[i + 1];
if (nextVal) {
if (nextVal > msLapsed) {
willInsertAtIndex = i + 1;
break;
}
} else {
willInsertAtIndex = i + 1;
break;
}
}
}
if (willInsertAtIndex) {
chart.data.labels.splice(willInsertAtIndex, 0, msLapsed);
}
} else {
chart.data.labels.push(msLapsed);
}
// Decimate data every so and so
chartText.current.innerText = `${multiplier}x`;
// Re-render canvas
chart.update();
};
useEffect(() => {
console.log("rendered chart");
var ctx = document.getElementById("myChart").getContext("2d");
ctx.height = "350px";
chart = new Chart(ctx, {
// The type of chart we want to create
type: "scatter",
// The data for our dataset
data: {
labels: [...Array(11).keys()].map((s) => s * 1000),
datasets: [
{
label: "testt",
backgroundColor: "transparent",
borderColor: "rgb(255, 99, 132)",
borderWidth: 10,
showLine: true,
borderJoinStyle: "round",
borderCapStyle: "round",
data: [
{
y: 1,
x: 0,
},
],
},
],
animation: {
duration: 0,
},
responsiveAnimationDuration: 100, // animation duration after a resize
},
// Configuration options go here
options: {
spanGaps: true,
events: [],
responsive: true,
maintainAspectRatio: false,
legend: {
display: false,
},
elements: {
point: {
radius: 0,
},
},
scales: {
xAxes: [
{
type: "linear",
ticks: {
callback: function (value, index, values) {
let s = Math.round(value / 1000);
return s.toString() + "s";
//return value;
},
autoSkipPadding: 100,
autoSkip: true,
suggestedMax: 10000,
stepSize: 100,
min: 0,
},
},
],
yAxes: [
{
ticks: {
// Include a dollar sign in the ticks
callback: function (value, index, values) {
return Math.round(value).toString() + "x"; // Display steps by 0,5
},
min: 1,
suggestedMax: 2.5,
stepSize: 0.01,
autoSkip: true,
autoSkipPadding: 150,
},
},
],
},
},
});
let lastTick = 1.0;
let dateStart = new Date().getTime();
setTimeout(() => {
chartText.current.innerText = "Go!";
setTimeout(() => {
setInterval(() => {
let timePassed = new Date().getTime() - dateStart;
//console.log(timePassed);
let calculateTick = Math.pow(
1.01,
0.00530133800509 * timePassed
).toFixed(2);
console.log(timePassed);
onServerTickReceived(calculateTick, timePassed);
}, 50);
}, 1000);
}, 2000);
});
const classes = useStyles();
return (
<div className={classes.canvasContainer}>
<span ref={chartText} className={classes.canvasText}>
Ready...?
</span>
<canvas id="myChart"></canvas>
</div>
);
};
export default HomePlaygroundView;
My project uses echart to create radar chart and i have to find click event for indicators around radar chart.
It is implemented like this.
createradarchart() {
this.theme.getJsTheme()
.pipe(
takeWhile(() => this.alive),
delay(1),
)
.subscribe(config => {
this.options = {
name: 'KPI Radar',
grid: {
left: '5%',
right: '5%',
top: 0,
bottom: 0
},
// label: {
// distance: 5
// },
type: 'radar',
color: ['red', 'green', 'blue'],
legend: {
bottom: 5,
itemGap: 20,
data: ['Baseline', 'Threshold', 'Actual'],
textStyle: {
color: 'black',
fontSize: 10
}
},
radar: {
indicator: this.indicator,
nameGap: 5,
shape: 'circle',
radius: '43%',
name: {
textStyle: {
color: 'black',
fontSize: 10
}
}
},
tooltip: {
show: true,
textStyle: {fontSize:10},
trigger: 'item',
formatter: (params => {
return params['name']+'-'+params['value'][1];
})
},
series: this.seriesdata,
};
this.ctx = this.echartsIntance.getRenderedCanvas();
this.chart = new Chart(this.ctx,{type:'radar', options: this.options})
// this.ctx = canvas.getContext("2d");
});
}
where data and options are in format, with data being fetched from server:
seriesdata: any = {type: 'radar', data:[{name:'Baseline',value:[1,2,3,4,5]},{name:'Threshold',value:[1,2,3,4,5]},{name:'Actual',value:[1,2,3,4,5]}]};
indicator = [
{ name: 'DL User Thpt_Kbps[CDBH]', max: 100 },
{ name: 'ERAB SSR[CDBH]', max: 100 },
{ name: 'PS DCR %[CDBH]', max: 100 },
{ name: 'VoLTE CSSR', max: 100 },
{ name: 'VoLTE DCR[CBBH]', max: 100 }
];
options: EChartOption = {
name: 'KPI Radar',
grid: {
left: '2%',
right: '2%',
top: 0,
bottom: 0
},
// label:{
// distance: 5
// },
type: 'radar',
color: ['red', 'green', 'blue'],
legend: {
orient: 'vertical',
align: 'left',
right: 20,
data: ['Baseline', 'Threshold', 'Actual'],
textStyle: {
color: 'black',
fontSize: 10
}
},
radar: {
indicator: this.indicator,
nameGap: 5,
shape: 'circle',
radius:'60%',
},
tooltip: {
show: false,
// trigger: 'item',
// formatter: (params => {
// return params['name']+'-'+params['value'][1];
// })
}
};
This is where i want click event to be triggered, having the name of label which is clicked.
Approach i found to do this is this, but it didnt work, i debugged and found that scale.pointLabels is empty.
labelClicked(e:any){
var self = this;
var helpers = Chart.helpers;
var scale = self.chart.scale;
var opts = scale.options;
var tickOpts = opts.ticks;
// Position of click relative to canvas.
var mouseX = e.offsetX;
var mouseY = e.offsetY;
var labelPadding = 5; // number pixels to expand label bounding box by
// get the label render position
// calcs taken from drawPointLabels() in scale.radialLinear.js
var tickBackdropHeight = (tickOpts.display && opts.display) ?
helpers.valueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize)
+ 5: 0;
var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
for (var i = 0; i < scale.pointLabels.length; i++) {
// Extra spacing for top value due to axis labels
var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
// get label size info.
// TODO fix width=0 calc in Brave?
// https://github.com/brave/brave-browser/issues/1738
var plSize = scale._pointLabelSizes[i];
// get label textAlign info
var angleRadians = scale.getIndexAngle(i);
var angle = helpers.toDegrees(angleRadians);
var textAlign = 'right';
if (angle == 0 || angle == 180) {
textAlign = 'center';
} else if (angle < 180) {
textAlign = 'left';
}
// get label vertical offset info
// also from drawPointLabels() calcs
var verticalTextOffset = 0;
if (angle === 90 || angle === 270) {
verticalTextOffset = plSize.h / 2;
} else if (angle > 270 || angle < 90) {
verticalTextOffset = plSize.h;
}
// Calculate bounding box based on textAlign
var labelTop = pointLabelPosition.y - verticalTextOffset - labelPadding;
var labelHeight = 2*labelPadding + plSize.h;
var labelBottom = labelTop + labelHeight;
var labelWidth = plSize.w + 2*labelPadding;
var labelLeft;
switch (textAlign) {
case 'center':
labelLeft = pointLabelPosition.x - labelWidth/2;
break;
case 'left':
labelLeft = pointLabelPosition.x - labelPadding;
break;
case 'right':
labelLeft = pointLabelPosition.x - labelWidth + labelPadding;
break;
default:
console.log('ERROR: unknown textAlign '+textAlign);
}
var labelRight = labelLeft + labelWidth;
// Render a rectangle for testing purposes
self.ctx.save();
self.ctx.strokeStyle = 'red';
self.ctx.lineWidth = 1;
self.ctx.strokeRect(labelLeft, labelTop, labelWidth, labelHeight);
self.ctx.restore();
// compare to the current click
if (mouseX >= labelLeft && mouseX <= labelRight && mouseY <= labelBottom && mouseY >= labelTop) {
alert(scale.pointLabels[i]+' clicked');
// Break loop to prevent multiple clicks, if they overlap we take the first one.
break;
}
}
}
Thanks in advance
Consider the following codesample donut chart using jquery-flot , now as i have added the 'image' class on click of the donut, i want to dynamically add the degree in the 'image' class so that the clicked item will be facing down at the bottom ( like on the -ve side of the y-axis ).`
var data = [{
label: "Pause",
data: 150
}, {
label: "No Pause",
data: 100
}, {
label: "yes Pause",
data: 80
}, {
label: "Sleeping",
data: 250
}];
var options = {
series: {
pie: {
show: true,
innerRadius: 0.5,
radius: 1,
startAngle: 1,
}
},
grid: {
hoverable: true,
clickable: true
},
legend: {
show: false
},
stroke: {
width: 4
},
tooltip: true,
tooltipOpts: {
cssClass: "flotTip",
content: "%s: %p.0%",
defaultTheme: false
}
};
$("#pie-placeholder").bind("plotclick", function(event, pos, obj) {
$("#pie-placeholder").addClass('image')
});
var plot = $.plot($("#pie-placeholder"), data, options);
`
Note:- this is done using Jquery flot
Here you can find my solution to your problem if I got you right.
$("#pie-placeholder").bind("plotclick", function(event, pos, obj) {
if (obj) {
var percentInRads = 0.02;
var currSegmentInRads = percentInRads * obj.datapoint[0]
var currSegmentOffset = currSegmentInRads / 2;
var currSegmentStart = currSegmentOffset >= 0.5 ? -0.5 + currSegmentOffset : 0.5 - currSegmentOffset;
var total = 0;
var beforeTotal = 0;
for (var idx = 0; idx < data.length; idx++) {
var segment = data[idx];
if (idx < obj.seriesIndex) {
beforeTotal += segment.data;
}
total += segment.data;
}
var beforePart = (beforeTotal / total * 100) * percentInRads;
var chartStartAngle = currSegmentStart - beforePart;
options.series.pie.startAngle = chartStartAngle;
$.plot($("#pie-placeholder"), data, options);
console.log(obj.series);
}
});