I'm not sure this is possible without doing something along the line of: Create links in HTML canvas but let's make sure.
Is there a way to (relatively) simply turn Chart.js labels into links? The chart in question is the radar chart: http://www.chartjs.org/docs/#radar-chart
(So far I've been using the legend for that, works fine with a little library modification, but now I should use the labels themselves.)
You can listen to the click event and then loop through all the pointLabels check if the click is in that box. If this is the case you get the corresponding label from the array containing all the labels.
Then you can open use window.location = link or window.open(link) to go to your link.
Example that searches the color on google on click:
const options = {
type: 'radar',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'orange'
},
{
label: '# of Points',
data: [7, 11, 5, 8, 3, 7],
borderColor: 'pink'
}
]
},
options: {
onClick: (evt, activeEls, chart) => {
const {
x,
y
} = evt;
let index = -1;
for (let i = 0; i < chart.scales.r._pointLabelItems.length; i++) {
const {
bottom,
top,
left,
right
} = chart.scales.r._pointLabelItems[i];
if (x >= left && x <= right && y >= top && y <= bottom) {
index = i;
break;
}
}
if (index === -1) {
return;
}
const clickedLabel = chart.scales.r._pointLabels[index];
window.open(`https://www.google.com/search?q=color%20${clickedLabel}`); // Blocked in stack snipet. See fiddle
console.log(clickedLabel)
}
}
}
const ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.6.0/chart.js"></script>
</body>
Fiddle: https://jsfiddle.net/Leelenaleee/fnqr4c7j/22/
It's really late but bencekd's answer and provided code to the very similar how to "Add Link to X-Label Chart.js" solved it for me.
Note: I'm forced to use the old Chart.js v1 and it's for a bar chart, not radar. You'll have to modify it accordingly as per the current version Chart.js. I also don't see why it shouldn't work for a radar with a little refactoring.
Code (one minor chang by me, comments by "/*" indicate my lines):
$("#canvas").click(
function(evt){
var ctx = document.getElementById("canvas").getContext("2d");
// from the endPoint we get the end of the bars area
var base = myBar.scale.endPoint;
var height = myBar.chart.height;
var width = myBar.chart.width;
// only call if event is under the xAxis
if(evt.pageY > base){
// how many xLabels we have
var count = myBar.scale.valuesCount;
var padding_left = myBar.scale.xScalePaddingLeft;
var padding_right = myBar.scale.xScalePaddingRight;
// calculate width for each label
var xwidth = (width-padding_left-padding_right)/count;
// determine what label were clicked on AND PUT IT INTO bar_index
var bar_index = (evt.offsetX - padding_left) / xwidth;
// don't call for padding areas
if(bar_index > 0 & bar_index < count){
bar_index = parseInt(bar_index);
// either get label from barChartData
//console.log("barChartData:" + barChartData.labels[bar_index]);
// or from current data
/* barChartData didn't work for me, so disabled above */
var ret = [];
for (var i = 0; i < myBar.datasets[0].bars.length; i++) {
ret.push(myBar.datasets[0].bars[i].label)
};
console.log("current data:" + ret[bar_index]);
// based on the label you can call any function
/* I made to go where I wanted here with a window.location.href
and taking the label (ret[bar_index]) as an argument */
}
}
}
);
And their provided Fiddle.
Related
I'm trying to plot an image/heatmap with a slider that will change the opacity (of the heatmap), and a second slider that will modify a custom parameter on each "onchange" event.
Once this image/heatmap is rendered, no computation should be done, and moving the sliders should be instantaneous. But from what I have tried, moving one slider is very slow (1 second lag between each position), and uses max CPU %.
I'm looking for a JS-only solution (no Python for this part).
How to make a faster slider rendering with Plotly JS?
var z = [], steps = [], i;
for (i = 0; i < 500; i++)
z.push(Array.from({length: 600}, () => Math.floor(Math.random() * 100)));
for (i = 0; i < 100; i++)
steps.push({ label: i, method: 'restyle', args: ['line.color', 'red']});
var data = [{z: z, colorscale: 'YlGnBu', type: 'heatmap'}];
var layout = {title: '', sliders: [{
pad: {t: 5},
len: 1,
x: 0,
currentvalue: {
xanchor: 'right',
prefix: 'i: ',
font: {
color: '#888',
size: 20
}
},
steps: steps
}]};
Plotly.newPlot('myDiv', data, layout);
<script src="https://cdn.plot.ly/plotly-2.16.2.min.js"></script>
<div id="myDiv"></div>
This is because you are using method: 'restyle' with the wrong args, ie. ['line.color', 'red'] the syntax is not correct and there is no line so I guess Plotly (without knowing what to restyle exactly) just redraws the whole plot whenever the slider moves, which is slow.
Basically, you can use the same slider configuration in javascript and in python for the same task (in the end the same Plotly.js slider component will be used).
For example, one can set the opacity of an image according to the slider's position, but for the changes to be applied instantly one needs to set the proper method and args in the slider' steps configuration, excactly as explained in this post :
steps.push({
label: i,
execute: true,
method: 'restyle',
args: [{opacity: i/100}]
});
Here is a full example with two sliders : one that changes the opacity of the heatmap and another one that doesn't touch the plot but only triggers a specific handler :
const z = [];
for (let i=0; i<500; i++) {
z.push(Array.from({length: 600}, () => Math.floor(Math.random() * 100)));
}
const data = [{z: z, colorscale: 'YlGnBu', type: 'heatmap'}];
// Steps for the heatmap opacity slider
const opacity_steps = [];
for (let i = 0; i <= 100; i++) {
opacity_steps.push({
label: i + '%',
execute: true,
method: 'restyle',
args: [{opacity: i/100}]
});
}
// Steps for the custom slider
const custom_steps = [];
for (let i = 50; i <= 200; i++) {
custom_steps.push({
label: i,
execute: false,
method: 'skip',
});
}
const layout = {
title: '',
sliders: [{
name: 'opacity_slider',
steps: opacity_steps,
active: 100,
pad: {t: 30},
currentvalue: {prefix: 'opacity: '}
}, {
name: 'custom_slider',
steps: custom_steps,
pad: {t: 120},
currentvalue: {prefix: 'i: '}
}]
};
Plotly.newPlot('graph', data, layout);
// Retrieve the graph div
const gd = document.getElementById('graph');
// Attach 'plotly_sliderchange' event listener to it (note that we can't specify
// which slider the handler is taking care of using a secondary selector)
gd.on('plotly_sliderchange', function(event) {
// ... so we have to distinguish between the two sliders here.
if (event.slider.name != 'custom_slider')
return;
const slider = event.slider; // the slider emitting the event (object)
const step = event.step; // the currently active step (object)
const prev = event.previousActive; // index of the previously active step
const value = step.value; // captain obvious was here
const index = step._index; // index of the current step
// ...
});
<script src="https://cdn.plot.ly/plotly-2.16.2.min.js"></script>
<div id="graph"></div>
I'm using C3 charts library to draw charts. I send data to the chart using two arrays, which are 'timeArray' and 'dataArray', one for the X-Axis and the other one for Y-Axis respectively. This simple logic was working fine.
Later I had to implement a change such that I had to take average of every three elements of an array and then make a new array and then plot the graph using averaged values.
I started facing a problem that a spurious point was being plotted on the graph. Whenever this error occurs, only one spurious point is added in the end. I've checked the arrays that are used to plot the graph, they do not have that spurious point. When I take the average of every three elements, I face this problem almost every time, however when I take average of 500 or 1000 points I face this error only sometimes.
As you can see in the code I have already tried removing the last point of the final array since the spurious point that was being added was always the last point in the chart. I've also tried changing the graph type, it did not help.
socket.on('get-avg-graph', function(data) {
// dataPoints = Points for Y-Axis
// mili = Points for X-Axis
var dataPoints = data.dataPoints;
var mili = data.mili;
var sumX = 0;
var sumY = 0;
var avgXGraph = 0;
var avgYGraph = 0;
var avgXArray = [];
var avgYArray = [];
for (var i = 0; i < dataPoints.length - 999; i++) {
for (var j = i; j < i + 999; j++) {
sumX = sumX + mili[j];
sumY = sumY + dataPoints[j];
}
if (sumY !== 0) {
avgXGraph = ( sumX / 1000 );
avgXArray.push(avgXGraph);
avgYGraph = ( sumY / 1000 );
avgYArray.push(avgYGraph);
sumX = 0;
sumY = 0;
avgXGraph = 0;
avgYGraph = 0;
}
}
io.emit('get-avg-graph-response', avgXArray, avgYArray);
});
socket.on('get-avg-graph-response', function(avgXArray, avgYArray) {
plot_X_axis = [];
plot_Y_axis = [];
drawChart();
avgXArray.splice( -1, 1);
avgYArray.splice( -1, 1);
plot_X_axis.push.apply(plot_X_axis, avgXArray);
plot_Y_axis.push.apply(plot_Y_axis, avgYArray);
drawChart();
});
function drawChart() {
var graphTitle = $("#test_type_show").val();
dataArray = [];
dataArray[0] = "PRESSURE";
dataArray.push.apply(dataArray, plot_Y_axis);
timeArray = [];
timeArray[0] = "TIME";
timeArray.push.apply(timeArray, plot_X_axis);
if (chart==null) {
chart = c3.generate({
bindto: '#chart1',
title: {
text: graphTitle
},
data: {
x: 'TIME',
columns: [
timeArray,
dataArray
],
type: 'spline'
},
axis: {
x: {show:false},
y: {show: true}
},
grid: {
x: {
show: true
},
y: {
show: true
}
},
point: {
show: false
}
});
} else {
chart.load({
x: 'TIME',
columns: [
timeArray,
dataArray
],
type: 'spline'
});
}
chart.internal.xAxis.g.attr('transform', "translate(0," + chart.internal.y(0) + ")");
chart.internal.yAxis.g.attr('transform', "translate(" + chart.internal.x(0) + ", 0)");
}
I expect the output of the code to be the actual graph without any spurious data added anywhere.
I have this jsfiddle that will illustrate my problem: http://jsfiddle.net/qner9hwc/1/
Whenever you add a series that doesn't have the highest integer value, whether it would be at the bottom or in the middle somewhere, the top values get pushed off the chart area or the categories just don't fit inside the graph area at all.
The categories are being added dynamically and expect integer values. I have them being sorted in ascending order.
Try this use case:
Add a series "10", then "5" (you will see my problem here), then add "15" and it will correct itself
$('#addSeries').on('click', function () {
//first check if value is already a series
var seriesid = document.getElementById('txtValue').value;
getSeriesIDIndex(seriesid, function (idindex) {
//if it doesnt exist, add it to chart
if (idindex == -1) {
categories.push(parseInt(seriesid));
categories = categories.sort(sortNumber);
console.log(categories);
chart.yAxis[0].setCategories(categories, false);
getCategoryIndex(parseInt(seriesid), function (cindex) {
chart.addSeries({
name: seriesid,
data: [{
x: currentDate.getTime(),
y: cindex
}]
}, false);
for (i = 0; i < chart.series.length; i++) {
var cur_series = chart.series[i];
var cur_cindex;
getCategoryIndex(chart.series[i].name, function (cindex) {
cur_cindex = cindex;
});
for (var z = 0; z < cur_series.data.length; z++) {
if (cur_series.data[z].y >= cindex && cur_series.name !== seriesid) {
console.log('change!' + cur_series.data[z].y);
cur_series.data[z].y = cur_cindex;
}
}
}
console.log(chart.yAxis[0].categories);
chart.redraw();
});
}
});
});
This line is reponsible for your troubles:
cur_series.data[z].y = cur_cindex;
Proper code:
cur_series.data[z].update(cur_cindex, false);
You are updating only value, while you should be using point.update() for updating point. Demo: http://jsfiddle.net/qner9hwc/2/
I have this in my code
series: [{
name: 'Velocidad',
turboThreshold: 5000,
data: (function() {
// generate an array of random data
var data = [],
i;
for (i = 0; i < valor; i++) {
data.push({
x: dataUtc[i],
y: dataVel[i],
id: i
});
}
return data;
})(),
tooltip: {
valueDecimals: 0,
valueSuffix: 'Kms/H'
}
}]
When I try to get the maximum value I only get the x or y value.
I need to get the id value though.
What can I do?
I tried the following:
var chart = $('#containerVelocidad').highcharts();
min = chart.xAxis[0].min;
max = chart.xAxis[0].max;
Unfortunately that didn't help.
First of all, use yAxis.dataMax not yAxis.max. The second one is returning actual extremes, when sometimes you may have different extremes, than just from one series (zooming, setting extremes, multiple series etc.).
Demo: http://jsfiddle.net/ez4hjazk/1/
And code:
var max = chart.yAxis[0].dataMax,
maxIndex = chart.series[0].yData.indexOf(max), //get index from yData, read more about .indexOf() !
data = chart.series[0].data;
// make sure point is within actual extremes
if(maxIndex > -1) {
console.log("max = " + max, 'id = ' + data[maxIndex].id);
}
Do you need to get the maximum/minimum of id?
If so, try chart.series[0].data[i].id. Here's an example: http://jsfiddle.net/ez4hjazk/
i'm using jqplot to draw charts, in one chart it will have 2 line series, but the values are quite different, one is about 2000, and the other might be more than 1,000,000,
how can i specify it to have 2 lines with 2 y axis in a way that can be displayed in one chart fine?
currently i just saw one line that's just a plain line in the bottom? my codes are something like below
$.ajax({url: myurl,
success:function(json){
$.jqplot.config.enablePlugins = true;
var data = eval(json);
var ssList = data[0];
var sdList = data[1];
var dataOption = [ssList];
if(sdList.length > 0){
dataOption[1] = sdList;
}
var seriesOption = {lineWidth:2, markerOptions:{style:'square'}};
if(sdList.length > 0){
seriesOption[1] = {renderer:$.jqplot.OHLCRenderer, rendererOptions:{candleStick:true}};
}
var axisOption ={};
axisOption.xaxis = {
renderer:$.jqplot.DateAxisRenderer,
tickOptions:{formatString:'%Y-%m-%d'}
};
axisOption.yaxis = {autoscale:true,label: "time"};
if(sdList.length > 0){
axisOption.y2axis = {autoscale:true,label: "index"};
}
var plot2 = $.jqplot('chart2', dataOption, {
title:'水木社区股票版人气指数',
axes:axisOption,
series:seriesOption,
highlighter:{
showMarker:false,
tooltipAxes: 'xy',
yvalues: 1,
formatString:'<table class="jqplot-highlighter"><tr><td>日期:%s</td></tr><tr><td>人气指数:%s</td></tr></table>'
}
});
}});
You can create plots with more 'y' axes. Second example here http://www.jqplot.com/tests/zooming.php
Don't use autoscale, try to set max and min values for each one and you will have your plot more readable.