Major and minor ticks in FLOT - javascript

Some charting/plotting libraries, e.g. matplotlib for Python, has the concept of major and minor ticks (with corresponding major and minor gridlines). I have been looking around, and I think this doesn't exist in FLOT. It seems that there is only one category of tick.
Isn't it possible to make something like below, and if yes, then how?
E.g., as in the illustration below, major ticks for every 1.0, and minor ticks for every 0.2.

You are correct that flot does not support this natively.
To replicate your drawing, I would use grid markings and add a thicker line at each whole number tick:
$.plot("#placeholder", [ d1 ], {
xaxis: {
tickSize: 0.2 // regular tick at 0.2
},
grid: {
markings: function (axes) {
var markings = [];
var xTicks = axes.xaxis.ticks;
for (var i = 0; i < xTicks.length; i++){ // loop all the ticks and add a black line at each whole number
if (xTicks[i].v % 1 === 0){
markings.push({ xaxis: { from: xTicks[i].v - 0.005, to: xTicks[i].v + 0.005 }, color: 'black' });
}
}
return markings;
}
}
});
Produces (example here):

If anyone wants to add your own unlabelled minor ticks at the minortick divisions you want you can do this. (Default minor ticks is 5 and can't easily be overidden)
My data is time based showing two years with markers every three months.
xaxis: {
mode: "time",
timeformat: "%b %Y",
minTickSize: [3, "month"],
showMinorTicks:false,
},
grid: {
markings: function (axes) {
var markings = [];
var month_time= 2629700; // year in seconds divided by 12
var offset=0;
var linespread=36000; // set line thickness
var xTicks = axes.xaxis.ticks;
for (var i = 0; i < xTicks.length; i++){
// loop all the ticks and add a dark line at each main group
markings.push({ xaxis: { from: xTicks[i].v - linespread, to: xTicks[i].v + linespread }, color: '#aaa' });
for (var k = 1; k < 3; k++){
//use subdvision to add minor ticks
offset=month_time*k;
markings.push({ xaxis: { from: xTicks[i].v + offset - linespread, to: xTicks[i].v + offset + linespread }, color: '#ddf' });
}
}
return markings;
}
}

Related

Skip overlapping tick labels in Plotly Javascript

We are using scatter plots in Plotly.JS to display 2D graph data over a large X range, so we use logarithmic scaling. Zooming and panning works very well, except for one small issue: the X tick labels are confusing because Plotly uses single-digit labels for minor (non-powers of 10) ticks:
I can use tickFormat: '0.1s' to show real numbers (which is what users want) instead of single digits, but then there are cases where these labels can overlap:
I can also add dtick: 'D2' which only displays subticks at positions 2 and 5 and not all digits, but this is then fixed and doesn't adjust to scaling any more.
Ideally, I could specify subtick-label digits where to skip the label (but not the vertical line) completely, without having to resort to tickmode: array and having to specify all tick labels manually, and still benefit from automatic tick adjustment depending on scaling.
For example, if all subtick digits are displayed, I would say I'd like to have tick labels at positions 1, 2, 3, 5, 7, the result would look like this:
The other display modes (digits 2 & 5 only, or just the power of 10) would not change.
Is there a way to do this? If so, how? I'm not afraid of patching Plotly if required, but right now I don't know where to start looking.
Usually I solve this by rotating the labels by some 35-45 degrees. That way they are all there and still readable.
https://plotly.com/javascript/reference/#layout-xaxis-tickangle
tickangle
Parent: layout.xaxis
Type: angle
Default: "auto"
Sets the angle of the tick labels with respect to the horizontal. For example, a `tickangle` of -90 draws the tick labels vertically.
OK, so I looked deeply into the Plotly configuration options and there are options that are semi-automatically modified 'live' depending on zoom levels, but the conditions when those modifications happen are hardcoded.
So here's a patch (for Plotly v1.36, because that's what we currently use). I modified some of the hardcoded conditions when to change from 1,10,100,... to 1,2,5,10,20,50,100,... to 1,2,3,4,5,... labeling on logarithmic axes and removed the tick labels at 4, 6, 8 and 9 for the last case to avoid text overlapping.
For my application, this now works well and I could not find any more overlapping of tick labels. Also, I got rid of the single digits between powers of ten which confused some users.
--- a/plotly-latest.v1.36.js 2021-05-24 21:45:28.000000000 +0100
+++ b/plotly-latest.v1.36.js 2022-02-02 10:21:08.000000000 +0100
## -127302,13 +127302,13 ##
if(!nt) {
if(ax.type === 'category') {
minPx = ax.tickfont ? (ax.tickfont.size || 12) * 1.2 : 15;
nt = ax._length / minPx;
}
else {
- minPx = ax._id.charAt(0) === 'y' ? 40 : 80;
+ minPx = ax._id.charAt(0) === 'y' ? 40 : 100;
nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
}
// radial axes span half their domain,
// multiply nticks value by two to get correct number of auto ticks.
if(ax._name === 'radialaxis') nt *= 2;
## -127395,14 +127395,21 ##
// Start with it cleared and mark that we're in calcTicks (ie calculating a
// whole string of these so we should care what the previous date head was!)
ax._prevDateHead = '';
ax._inCalcTicks = true;
var ticksOut = new Array(vals.length);
- for(var i = 0; i < vals.length; i++) ticksOut[i] = axes.tickText(ax, vals[i]);
+ // if scaling == log, skip some intermediate tick labels to avoid overlapping text
+ var skipTexts = /^[4689]/;
+ var text;
+ for(var i = 0; i < vals.length; i++) {
+ text = axes.tickText(ax, vals[i]);
+ if(ax.type == "log" && ax.dtick == "D1" && text.text.match(skipTexts)) text.text = "";
+ ticksOut[i] = text;
+ }
ax._inCalcTicks = false;
return ticksOut;
};
function arrayTicks(ax) {
## -127535,18 +127542,20 ##
// ticks on a linear scale, labeled fully
roughDTick = Math.abs(Math.pow(10, rng[1]) -
Math.pow(10, rng[0])) / nt;
base = getBase(10);
ax.dtick = 'L' + roundDTick(roughDTick, base, roundBase10);
+ ax.tickformat = '';
}
else {
// include intermediates between powers of 10,
// labeled with small digits
// ax.dtick = "D2" (show 2 and 5) or "D1" (show all digits)
- ax.dtick = (roughDTick > 0.3) ? 'D2' : 'D1';
+ ax.dtick = (roughDTick > 0.4) ? 'D2' : 'D1';
+ ax.tickformat = '0.1s';
+ ax.hoverformat = '0.2s'; // Workaround to fix hoverinfo label formatting
}
}
else if(ax.type === 'category') {
ax.tick0 = 0;
ax.dtick = Math.ceil(Math.max(roughDTick, 1));
}
The patch is hereby released as Public Domain to make it as easy as possible to use this for direct integration into Plotly (maybe as a "D3" formatting option?).

Charts.js line chart: Show the most recent date on x-axis when setting ticks.maxTicksLimit

I'm currently using making a line chart with Charts.js over the past 30 years.
Although I have a data point for every year, I can't put a label for each year on the x-axis. 30 years won't fit there. So, I've chosen to only list some years, by setting
ticks: {
maxTicksLimit: SOME_LIMIT_I_MADE
}
Here is a photo of what my chart looks like
The most recent data point is in 2020 and the oldest is in 1991.
It seems that setting maxTicksLimit starts the scale at the earliest year. Then the scale skips forwards in regular intervals.
What I really want is for the most recent year 2020 to be displayed on the scale (so that users can see that the data is new). Is there a way to make the scale start at the max year, and then the skip backwards from there (the opposite of how my graph currently is)?
Thanks!
This could be solved by somehow implement your own ticks.maxTicksLimit on the xAxis. You would have to proceed as follows.
Define the xAxis as a time cartesian axis that accepts the data as an array of data points using an object containing x and y properties.
Generate a labels array out of the years contained in your data. This array should contain the starting year and end year together with a number of equally spread years between both (see function createLabels in the code snippet below).
Tell Chart.js to generate ticks on the xAxis from given labels by defining tick.sources: 'labels'.
const data = [];
for (let year = new Date().getFullYear() - 29; year <= new Date().getFullYear(); year++) {
data.push({
x: year.toString(),
y: Math.floor((Math.random() * 6) + 1)
})
}
const maxTicksLimit = 6;
function createLabels() {
const years = data.map(o => Number(o.x));
const startTime = years[0];
const endTime = years[years.length - 1];
const tickGap = (endTime - startTime) / (maxTicksLimit - 1);
const labels = [startTime];
for (let i = 1; i < maxTicksLimit - 1; i++) {
labels.push(Math.round(startTime + i * tickGap));
}
labels.push(endTime);
return labels.map(l => l.toString());
}
new Chart('myChart', {
type: 'line',
data: {
labels: createLabels(),
datasets: [{
label: 'Demo',
fill: false,
data: data,
borderColor: 'blue'
}]
},
options: {
scales: {
xAxes: [{
type: 'time',
time: {
parser: 'YYYY',
unit: 'year'
},
ticks: {
source: 'labels'
}
}],
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<canvas id="myChart" height="80"></canvas>

How to show first and last label of x axis Highcharts

I tried some solution on other answers but I couldn't get the result what I want.. please help me.
I want to show first x axis label and last one in highcharts.
I tried {endOnTick: true, showLastLabel: true} from this answer(Force Highcharts to show last x-axis label) but it shows just some number on the last.. not the actual last label.
This is my x axis option
xAxis: {
type: 'Month',
// tickInterval is 5 this time
tickInterval: <?php echo number_format($num_results/5, 0);?>,
endOnTick: true, // it shows "25" at the end of the label. not "2019-06"
showLastLabel: true, // Default is true though..
labels: {
autoRotation: 0
},
//I get those categories from server
//so it could be different every time
//but this time I just write the result of it.
categories: ["2017-07","2017-08","2017-09","2017-10","2017-11","2017-12","2018-01","2018-02","2018-03","2018-04","2018-05","2018-06","2018-07","2018-08","2018-09","2018-10","2018-11","2018-12","2019-01","2019-02","2019-03","2019-04","2019-05","2019-06"]
}
expected x axis labels are
"2017-07 2017-12 2018-05 2018-10 2019-03 2019-06"
the actual result is
"2017-07 2017-12 2018-05 2018-10 2019-03 25"
Ahh I used tickPositioner and solved it!
xAxis: {
type: 'Linear',
tickmarkPlacement: 'on',
tickPositioner: function() {
var positions = [],
ext = this.getExtremes(),
xMax = Math.round(ext.max),
xMin = Math.round(ext.min);
for (var i = xMin; i < xMax; i++) {
if (i % <?php echo number_format($num_results/3,0);?> == 0) {
positions.push(i);
}
}
positions.push(xMax);
return positions;
},
labels: {
autoRotation: 0,
},
categories: ["2017-07","2017-08","2017-09","2017-10","2017-11","2017-12","2018-01","2018-02","2018-03","2018-04","2018-05","2018-06","2018-07","2018-08","2018-09","2018-10","2018-11","2018-12","2019-01","2019-02","2019-03","2019-04","2019-05","2019-06"]
}
You are missing 2 categories to show 2019-06
...
categories: ["2017-07","2017-08","2017-09","2017-10","2017-11",
"2017-12","2018-01","2018-02","2018-03","2018-04",
"2018-05","2018-06","2018-07","2018-08","2018-09",
"2018-10","2018-11","2018-12","2019-01","2019-02",
"2019-03","2019-04","2019-05","2019-06","24th","the 25th"]
},
Fiddle

Adding color to the max value in a dynamically generated chart?

The jsfiddle link is here http://jsfiddle.net/MzQE8/110/
The problem here I feel is in my JavaScript.
I input values to the series object in a HighChart from an array. On the array I am trying to find index and the value of the maximum element and then I am saving the maximum array element back with this modification
yArr[index] = {
y: value,
color: '#aaff99'
};
So that It appears as a diferent color from the rest of the points on the graph which is a dynamic one. That is its sliding one.
Here is my code
$(function () {
$(document).ready(function () {
Highcharts.setOptions({
global: {
useUTC: false
}
});
var chart;
$('#container').highcharts({
chart: {
type: 'spline',
animation: Highcharts.svg, // don't animate in old IE
marginRight: 10,
events: {
load: function () {
// set up the updating of the chart each second
var series = this.series[0];
//As this graph is generated due to random values. I am creating an Array with random values.
var yArr = [];
yArr[0] = Math.random();
yArr[1] = Math.random();
yArr[2] = Math.random();
yArr[3] = Math.random();
setInterval(function () {
console.log(yArr.length);
var x = (new Date()).getTime(), // current time
y = Math.random();
var index = findIndexOfGreatest(yArr);
var value = yArr[index];
yArr[index] = {
y: value,
color: '#aaff99'
};
series.addPoint([x, yArr.shift()], true, true);
yArr.push(Math.random());
}, 1000);
}
}
},
title: {
text: 'Live random data'
},
xAxis: {
type: 'datetime',
tickPixelInterval: 450
},
yAxis: {
title: {
text: 'Value'
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
plotOptions: {
series: {
lineWidth: 1
}
},
tooltip: {
formatter: function () {
return '<b>' + this.series.name + '</b><br/>' + Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' + Highcharts.numberFormat(this.y, 2);
}
},
legend: {
enabled: false
},
exporting: {
enabled: false
},
series: [{
name: 'Random Data',
data: (function () {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -19; i <= 0; i++) {
data.push({
x: time + i * 1000,
y: yArr.shift()
});
}
return data;
})(),
color: 'red'
}]
});
});
function findIndexOfGreatest(array) {
var greatest;
var indexOfGreatest;
for (var i = 0; i < array.length; i++) {
if (!greatest || array[i] > greatest) {
greatest = array[i];
indexOfGreatest = i;
}
}
return indexOfGreatest;
}
});
I feel my idea is correct but there are big holes in my implementation. I guess.
Thanks
See demo: http://jsfiddle.net/MzQE8/350/
All y-values are stored in series.yData, so you don't have to create another array for that. Now just update point which is the highest one, and add new points. Something like above demo, code:
events: {
load: function () {
// set up the updating of the chart each second
var series = this.series[0],
index = series.yData.indexOf(series.yData.max());
// mark first max points
this.series[0].prevMax = this.series[0].data[index];
this.series[0].prevMax.update({
color: '#aaff99'
});
setInterval(function () {
var x = (new Date()).getTime(), // current time
y = Math.random(),
color = null,
index, max;
if (series.prevMax && series.prevMax.update) {
// remove previously colored point
if (y > series.prevMax.y) {
series.prevMax.update({
color: null
}, false);
color = '#aaff99';
// store max, which is last point
series.prevMax = series.data[series.yData.length];
}
} else {
max = series.yData.max();
index = series.yData.indexOf(max);
if(y > max) {
color = '#aaff99';
series.prevMax = series.data[series.yData.length];
} else {
series.prevMax = series.data[index];
series.prevMax.update({
color: '#aaff99'
}, false)
}
}
// add new point
series.addPoint({
x: x,
y: y,
color: color
}, true, true);
}, 1000);
}
}
im not to sure what your seeking but if i understand correctly you want the bullet marks to be a different color to differentiate from the others. i wont include source code because what you have included in your js fiddle is pretty advanced stuff and you will be able to figure this out no problem.
calculate (all total values added together / 256 + i) * 255 (or something similar)
the i represents the bullet increment in a loop. please for give the algorithm i haven't slept in 32 hours and i know its def. suggested you check it.
Oh.. kay here is the updated solution working out on paper
http://i966.photobucket.com/albums/ae147/Richard_Grant/Untitled-1_zps7766a939.png
I PUT A LOT OF WORK INTO THIS! so please!!! use it well -.-'
What is happening here is i drew out a graph with values going from 0 - 200 and i labeled the graph results 1 - 4, i also made this the x coordinates cause im lazy.
In the workflow RES = means results or X, Val = Value or Y
These are used to calculate RGB values and i solved the first 3 results. the first result tangent should always be 0 because any number divisible by 0 is 0, and any number multiplied by 0 is 0.
When i say tangent i mean the angle of the point by the 200, 0 value (invisible point). in this formula the angle would not be perfected because the x and y on the graph are not equal, one is 200 max and the other is 4 max. if i wanted this to be accurate i would have turned the tangent into a percent and multiplied it by 255. but i didn't.
I feel i have given you the necessary tools to complete your request on your own now :) if you do not understand how i did this or found this algorithm, just fall back to random colors.
Thanks John doe, your question helped me. This is the function i used to change the color of the maximum value of my y axis:
function () {
var maximum = chart.yAxis[0].dataMax;
var index = series.yData.indexOf(maximum);
chart.series[0].data[index].update({ color: '#aaff99' });
}
It may help other people too.

Flot: Show a minimum amount of ticks

Using flot charts I would like to specify a minimum of ticks to show on the y-axis. In my case I would like to always show at least 10 ticks (values 1-10), but if my y-axis max exceeds 10 then I would like flot to draw the chart with its normal tick algorithm. I currently have it sort of working by specifying a function for the ticks paramater.
ticks: function(axis){
var ticks = [];
if(axis.max < 10){
ticks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
}
else{
for(var i = 1; i < axis.max; i++){
ticks.push(i);
}
}
return ticks;
},
The problem with this is that I get a lot more ticks than I want when the axis.max is greater than 10. Is there a way to avoid this?
I originally thought I could return null, but flot is expecting an array to be returned. :(
If you want the default functionality for tick generation you can call the tickGenerator function of the axes. It is much cleaner than working with a piece of code you don't maintain. So in your case, you could do this:
ticks: function(axis){
return axis.max < 10 ? [1,2,3,4,5,6,7,8,9,10] : axis.tickGenerator(axis);
}
It seems that default Flot algorithm goes like that (see setupTickGeneration function:
noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
so maybe something like that would work here (DISCLAIMER - not really tested, not sure if you have all needed vars in this scope and the math is just to sketch the solution:
ticks: function(axis){
var ticks = [];
if(axis.max < 10){
ticks = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
}
else{
noTicks = 0.3 * Math.sqrt(canvasHeight);
interval = Math.round(axis.max / noTicks);
for(var i = 1; i <= noTicks; i++){
ticks.push(i * interval);
}
}
return ticks;
},
I found this:
ticks: 16
but I haven't tried it.

Categories