I created a realtime (updates every 10ms) vertical spline chart using Flot. The chart can be seen here on Codepen. I included the Flot multiple threshold plugin, but I would like for the thresholds to use the x-axis values (on the bottom of the vertical chart) and not the y-axis values (left of the chart). The plot would then paint all values outside of the dashed black lines in red.
In the example you can see that the thresholds use the y-axis to color thresholds (in my case, all values below constraintMax, which is 60).
The operative lines of code are where I set up the options (line 79 in the update function):
var options = {
xaxis: {
position: 'bottom',
min: -10,
max: 100
},
yaxis: {
position: 'left',
min: iterator,
max: updatedData.length-1+iterator,
transform: function (v) { return -v; },
inverseTransform: function (v) { return -v; }
}
};
Where I set up the constraints (line 66 in the update function):
var constraintMax = {
threshold: 60,
color: "rgb(255,0,0)",
evaluate : function(y,threshold){ return y < threshold; }
}
var constraintMin = {
threshold: 25,
color: "rgb(255,0,0)",
evaluate : function(y,threshold){ return y < threshold; }
}
And where I actually plot (line 93 in the update function):
$.plot("#"+elementID, [{data: updatedData, constraints: [constraintMin, constraintMax]}, {data: initialMinData, color: "#000000", dashes: { show: true }}, {data: initialMaxData, color: "#000000", dashes: { show: true }}], options);
Does anyone have any ideas on how to paint the plot points that are outside of the dashed lines red? Thank you in advance.
The multiple threshold plugin only support y-value thresholds out of the box. Therefore you have to change it for your plot. I copied the code into a jsfiddle (I don't like codepen) and changed it there.
1) Your constraintMax threshold is wrong for what you want to do, you need return y > threshold.
2) Changes in the multiple threshold plugin:
if (evaluate(currentPoint[1], threshold)) {
v
if (evaluate(currentPoint[0], threshold)) {
and
function _getPointOnThreshold(threshold, prevP, currP) {
var currentX = currP[0];
var currentY = currP[1];
var prevX = prevP[0];
var prevY = prevP[1];
var slope = (threshold - currentX) / (prevX - currentX);
var yOnConstraintLine = slope * (prevY - currentY) + currentY;
return [threshold, yOnConstraintLine];
}
See the fiddle for the working example.
Related
I'm working on a highcharts solution which includes several different graph types in one single chart. Is it possible to have the exact time of mouse position displayed in the tooltip instead of a calculated range? (We're using highstock together with the boost and xrange module)
Also i need to always show the newest value of a series left from cursor position. Due to having an xRange Series i needed to refresh the tooltip with my own implementation since stickytracking doesnt work with xrange.
But right now the display of the green temperature series constantly switches from the actual value to the first value in the series (e.g when hovering around March 11th it constantly changes from 19.85°C back to 19.68°C which is the very first entry)
So i'm having 2 issues:
displaying the exact time in tooltip
displaying specific values in tooltip
I guess both could be solved with having the exact x position of cursor and for displaying the values i guess i did somewhat the right thing already with refreshing the tooltip on mousemove. Still the values won't always display properly.
I understand that Highcharts makes a best guess on the tooltip time value by displaying the range but to me it seems like it orients itself around the xRange Series.
I already tries to tinker around with the plotoptions.series.stickyTracking and tooltip.snap values but this doesn't really help me at all.
I understand too that this.x in tooltip formatter function will be bound to the closest point. Still i need it to use the current mouse position.
In a first attempt i was filtering through the series in the tooltip formatter itself before i changed to calculating the points on the mousemove event. But there i also couldn't get the right values since x was a rough estimate anyways.
Is there any solution to that?
At the moment i'm using following function onMouseMove to refresh the tooltip:
chart.container.addEventListener('mousemove', function(e) {
const xValue = chart.xAxis[0].toValue(chart.pointer.normalize(e).chartX);
const points = [];
chart.series.filter(s => s.type === "xrange").forEach(s => {
s.points.forEach(p => {
const { x, x2 } = p;
if (xValue >= x && xValue <= x2) points.push(p);
})
})
chart.series.filter(s => s.type !== "xrange").forEach(s => {
const point = s.points.reverse().find(p => p.x <= xValue);
if(point) points.push(point);
})
if (points.length) chart.tooltip.refresh(points, chart.pointer.normalize(e));
})
also i'm using this tooltip configuration and formatter:
tooltip: {
shared: true,
followPointer: true,
backgroundColor: "#FFF",
borderColor: "#AAAAAA",
borderRadius: 5,
shadow: false,
useHTML: true,
formatter: function(){
const header = createHeader(this.x)
return `
<table>
${header}
</table>
`
}
},
const createHeader = x => {
const headerFormat = '%d.%m.%Y, %H:%M Uhr';
const dateWithOffSet = x - new Date(x).getTimezoneOffset() * 60 * 1000;
return `<tr><th colspan="2" style="text-align: left;">${Highcharts.dateFormat(headerFormat, dateWithOffSet)}</th></tr>`
}
See following jsFiddle for my current state (just remove formatter function to see the second issue in action): jsFiddle
(including the boost module throws a script error in jsFiddle. Don't know if this is important so i disabled it for now)
finally found a solution to have access to mouse position in my tooltip:
extending Highcharts with a module (kudos to Torstein Hønsi):
(function(H) {
H.Tooltip.prototype.getAnchor = function(points, mouseEvent) {
var ret,
chart = this.chart,
inverted = chart.inverted,
plotTop = chart.plotTop,
plotLeft = chart.plotLeft,
plotX = 0,
plotY = 0,
yAxis,
xAxis;
points = H.splat(points);
// Pie uses a special tooltipPos
ret = points[0].tooltipPos;
// When tooltip follows mouse, relate the position to the mouse
if (this.followPointer && mouseEvent) {
if (mouseEvent.chartX === undefined) {
mouseEvent = chart.pointer.normalize(mouseEvent);
}
ret = [
mouseEvent.chartX - chart.plotLeft,
mouseEvent.chartY - plotTop
];
}
// When shared, use the average position
if (!ret) {
H.each(points, function(point) {
yAxis = point.series.yAxis;
xAxis = point.series.xAxis;
plotX += point.plotX + (!inverted && xAxis ? xAxis.left - plotLeft : 0);
plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
(!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
});
plotX /= points.length;
plotY /= points.length;
ret = [
inverted ? chart.plotWidth - plotY : plotX,
this.shared && !inverted && points.length > 1 && mouseEvent ?
mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)
inverted ? chart.plotHeight - plotX : plotY
];
}
// Add your event to Tooltip instances
this.event = mouseEvent;
return H.map(ret, Math.round);
}
})(Highcharts)
http://jsfiddle.net/2h951hdj/
Also you can wrap dragStart on the pointer and get exactly mouse position, in this case when you click on the chart area you will have the mouse position on the x-axis.
(function(H) {
H.wrap(H.Pointer.prototype, 'dragStart', function(proceed, e) {
let chart = this.chart;
chart.mouseIsDown = e.type;
chart.cancelClick = false;
chart.mouseDownX = this.mouseDownX = e.chartX;
chart.mouseDownY = this.mouseDownY = e.chartY;
chart.isZoomedByDrag = true;
console.log(chart.mouseDownX);
});
}(Highcharts));
Highcharts.chart('container', {
chart: {
events: {
load: function() {
let chart = this,
tooltip = chart.tooltip;
console.log(tooltip);
}
}
},
series: [{
data: [2, 5, 2, 3, 6, 5]
}],
});
Live demo: https://jsfiddle.net/BlackLabel/1b8rf9hc/
I have a Highcharts activity gauge that has two series. I am trying to place labels at the starting point of each ring. I have it working with hardcoded x,y coordinates, but I'm wondering if there is a way to calculate the location instead. It looks like this currently:
Here is the code I am using to add the labels in the chart render event:
function render() {
var chart = this;
chart.renderer.label('Completed 65%', 24, 25, 'rect', 0, 0, true, true, '')
.add();
chart.renderer.label('Follow-up 45%', 28, 42, 'rect', 0, 0, true, true, '')
.add();
}
I'd like to calculate the x,y values in the chart.renderer.label() function instead of hardcoding them to 24,25 and 28,42. However, I have not been able to find anything in the object model to locate the physical location of the series starting x and y, or the size of the label. I have many of these activity gauges to complete and going through them all and trying to find the magic coordinates seems like the wrong approach.
You can follow the same approach as icons rendered in the official Activity gauge demo here: https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/gauge-activity/
There you will find position calculated using series point shapeArgs. I modified that function to render labels as you expected. Check it in the demo posted below.
Function code:
function renderLabels() {
var offsetTop = 5,
offsetLeft = 5;
if (!this.series[0].label) {
this.series[0].label = this.renderer
.label('Completed 65%', 0, 0, 'rect', 0, 0, true, true)
.add(this.series[1].group);
}
this.series[0].label.translate(
this.chartWidth / 2 - this.series[0].label.width + offsetLeft,
this.plotHeight / 2 - this.series[0].points[0].shapeArgs.innerR -
(this.series[0].points[0].shapeArgs.r - this.series[0].points[0].shapeArgs.innerR) / 2 + offsetTop
);
if (!this.series[1].label) {
this.series[1].label = this.renderer
.label('Follow-up 45%', 0, 0, 'rect', 0, 0, true, true)
.add(this.series[1].group);
}
this.series[1].label.translate(
this.chartWidth / 2 - this.series[1].label.width + offsetLeft,
this.plotHeight / 2 - this.series[1].points[0].shapeArgs.innerR -
(this.series[1].points[0].shapeArgs.r - this.series[1].points[0].shapeArgs.innerR) / 2 + offsetTop
);
}
Function invocation:
chart: {
type: 'solidgauge',
events: {
render: renderLabels
}
}
Demo:
https://jsfiddle.net/BlackLabel/vpj32tmy/
I made a line chart using Chart.js version 2.1.3.
var canvas = $('#gold_chart').get(0);
var ctx = canvas.getContext('2d');
var fillPatternGold = ctx.createLinearGradient(0, 0, 0, canvas.height);
fillPatternGold.addColorStop(0, '#fdca55');
fillPatternGold.addColorStop(1, '#ffffff');
var goldChart = new Chart(ctx, {
type: 'line',
animation: false,
data: {
labels: dates,
datasets: [{
label: '',
data: prices,
pointRadius: 0,
borderWidth: 1,
borderColor: '#a97f35',
backgroundColor: fillPatternGold
}]
},
title: {
position: 'bottom',
text: '\u7F8E\u5143 / \u76CE\u53F8'
},
options: {
legend: {
display: false
},
tooltips: {
callback: function(tooltipItem) {
return tooltipItem.yLabel;
}
},
scales: {
xAxes: [{
ticks: {
maxTicksLimit: 8
}
}]
}
}
});
The output is as follow:
As you can see, I limited the maximum count of ticks to 8 via maxTicksLimit. However, the distribution is not even. How can I make the ticks distribute evenly?
p.s. there are always 289 records in the dataset, and the data is recorded every 5 minutes. Sample values of prices variable are:
[
{"14:10", 1280.3},
{"14:15", 1280.25},
{"14:20", 1282.85}
]
I tried different values of maxTicksLimit, and the results are still not distributed evenly.
Chart.js uses an integral skipRatio (to figure out how many labels to skip). With Chart.js v2.1.x, you can write your own plugin to use a fractional skipRatio
Preview
Script
Chart.pluginService.register({
afterUpdate: function (chart) {
var xScale = chart.scales['x-axis-0'];
if (xScale.options.ticks.maxTicksLimit) {
// store the original maxTicksLimit
xScale.options.ticks._maxTicksLimit = xScale.options.ticks.maxTicksLimit;
// let chart.js draw the first and last label
xScale.options.ticks.maxTicksLimit = (xScale.ticks.length % xScale.options.ticks._maxTicksLimit === 0) ? 1 : 2;
var originalXScaleDraw = xScale.draw
xScale.draw = function () {
originalXScaleDraw.apply(this, arguments);
var xScale = chart.scales['x-axis-0'];
if (xScale.options.ticks.maxTicksLimit) {
var helpers = Chart.helpers;
var tickFontColor = helpers.getValueOrDefault(xScale.options.ticks.fontColor, Chart.defaults.global.defaultFontColor);
var tickFontSize = helpers.getValueOrDefault(xScale.options.ticks.fontSize, Chart.defaults.global.defaultFontSize);
var tickFontStyle = helpers.getValueOrDefault(xScale.options.ticks.fontStyle, Chart.defaults.global.defaultFontStyle);
var tickFontFamily = helpers.getValueOrDefault(xScale.options.ticks.fontFamily, Chart.defaults.global.defaultFontFamily);
var tickLabelFont = helpers.fontString(tickFontSize, tickFontStyle, tickFontFamily);
var tl = xScale.options.gridLines.tickMarkLength;
var isRotated = xScale.labelRotation !== 0;
var yTickStart = xScale.top;
var yTickEnd = xScale.top + tl;
var chartArea = chart.chartArea;
// use the saved ticks
var maxTicks = xScale.options.ticks._maxTicksLimit - 1;
var ticksPerVisibleTick = xScale.ticks.length / maxTicks;
// chart.js uses an integral skipRatio - this causes all the fractional ticks to be accounted for between the last 2 labels
// we use a fractional skipRatio
var ticksCovered = 0;
helpers.each(xScale.ticks, function (label, index) {
if (index < ticksCovered)
return;
ticksCovered += ticksPerVisibleTick;
// chart.js has already drawn these 2
if (index === 0 || index === (xScale.ticks.length - 1))
return;
// copy of chart.js code
var xLineValue = this.getPixelForTick(index);
var xLabelValue = this.getPixelForTick(index, this.options.gridLines.offsetGridLines);
if (this.options.gridLines.display) {
this.ctx.lineWidth = this.options.gridLines.lineWidth;
this.ctx.strokeStyle = this.options.gridLines.color;
xLineValue += helpers.aliasPixel(this.ctx.lineWidth);
// Draw the label area
this.ctx.beginPath();
if (this.options.gridLines.drawTicks) {
this.ctx.moveTo(xLineValue, yTickStart);
this.ctx.lineTo(xLineValue, yTickEnd);
}
// Draw the chart area
if (this.options.gridLines.drawOnChartArea) {
this.ctx.moveTo(xLineValue, chartArea.top);
this.ctx.lineTo(xLineValue, chartArea.bottom);
}
// Need to stroke in the loop because we are potentially changing line widths & colours
this.ctx.stroke();
}
if (this.options.ticks.display) {
this.ctx.save();
this.ctx.translate(xLabelValue + this.options.ticks.labelOffset, (isRotated) ? this.top + 12 : this.options.position === "top" ? this.bottom - tl : this.top + tl);
this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1);
this.ctx.font = tickLabelFont;
this.ctx.textAlign = (isRotated) ? "right" : "center";
this.ctx.textBaseline = (isRotated) ? "middle" : this.options.position === "top" ? "bottom" : "top";
this.ctx.fillText(label, 0, 0);
this.ctx.restore();
}
}, xScale);
}
};
}
},
});
Fiddle - http://jsfiddle.net/bh63pe1v/
A simpler solution until this is permanently fixed by the Chart JS contributors is to include a decimal in maxTicksLimit.
For example:
maxTicksLimit: 8,
produces a huge gap at the end.
maxTicksLimit: 8.1,
Does not produce a huge gap at the end.
Depending on what you want to set your maxTicksLimit to, you need to play around with different decimals to see which one produces the best result.
Just do this:
yAxes: [{
ticks: {
stepSize: Math.round((Math.max.apply(Math, myListOfyValues) / 10)/5)*5,
beginAtZero: true,
precision: 0
}
}]
10 = the number of ticks
5 = rounds tick values to the nearest 5 - all y values will be incremented evenly
Similar will work for xAxes too.
Is it possible to remove the tick bars on the x and y axis on a flot chart?
picture of what I currently have
I want to remove the gray bar between the two series labels
Have you tried to configure your axes like:
xaxis: {
tickLength: 0
}
yaxis: {
tickLength: 0
}
Reference here.
Update in response to your last comment
Since there is no such option one possible workaround could be to color the tickbar the same as your chart background and the ticks like you have it right now.
xaxis: {
color: /* same as your background color */
tickColor: /* different color, like the grayish one you have for the ticks */
}
yaxis: {
color: /* same as your background color */
tickColor: /* different color, like the grayish one you have for the ticks */
}
Hope it helps
I ended up changing the flot source code to allow this to occur.
Here's what I did.
1) added 'tickBar' to the x/yaxis options. (if true, tickBar is shown.. default: true)
2) change the drawGrid function to use this option
drawGrid()
...
//draw the ticks
axes = allAxes();
bw = options.grid.borderWidth;
xBar = (options.xaxis.tickBar !== undefined)? options.xaxis.tickBar:true; //new
yBar = (options.yaxis.tickBar !== undefined)? options.yaxis.tickBar:true; //new
...
if(!axis.innermost){
ctx.strokeStyle = axis.options.color;
ctx.beginPath();
xoff = yoff = 0;
if(axis.direction == "x"){
if(xBar) //new
xoff = plotWidth + 1; // new
} else {
if(yBar) //new
yoff = plotHeight + 1; //new
}
When tickBar is set to false, the offset remains 0 so the line is drawn with a 0 value for width/height so it is not seen.
I am using flot to generate bar graphs.
Here is my code bar graph code
I need to make the y axis tick to disappear.
I need to put some label on the top of each bar
How to do it?
Okay, after a lot of mucking around with Flot and downloading the source, I finally figured out a good starting point for you.
The jsFiddle demo is here.
The guts of the code is using a hook for drawSeries which draws the label:
function drawSeriesHook(plot, canvascontext, series) {
var ctx = canvascontext,
plotOffset = plot.offset(),
labelText = 'TEST', // customise this text, maybe to series.label
points = series.datapoints.points,
ps = series.datapoints.pointsize,
xaxis = series.xaxis,
yaxis = series.yaxis,
textWidth, textHeight, textX, textY;
// only draw label for top yellow series
if (series.label === 'baz') {
ctx.save();
ctx.translate(plotOffset.left, plotOffset.top);
ctx.lineWidth = series.bars.lineWidth;
ctx.fillStyle = '#000'; // customise the colour here
for (var i = 0; i < points.length; i += ps) {
if (points[i] == null) continue;
textWidth = ctx.measureText(labelText).width; // measure how wide the label will be
textHeight = parseInt(ctx.font); // extract the font size from the context.font string
textX = xaxis.p2c(points[i] + series.bars.barWidth / 2) - textWidth / 2;
textY = yaxis.p2c(points[i + 1]) - textHeight / 2;
ctx.fillText(labelText, textX, textY); // draw the label
}
ctx.restore();
}
}
See the comments for where you can customise the label.
To remove the y-axis ticks, that is just a simple option setting. In addition, you can work out the maximum y-value for each of the bar stacks and then add about 100 to that to set a maximum Y value that will allow for the space taken up by the labels. The code for all of that then becomes:
// determine the max y value from the given data and add a bit to allow for the text
var maxYValue = 0;
var sums = [];
$.each(data,function(i,e) {
$.each(this.data, function(i,e) {
if (!sums[i]) {
sums[i]=0;
}
sums[i] += this[1]; // y-value
});
});
$.each(sums, function() {
maxYValue = Math.max(maxYValue, this);
});
maxYValue += 100; // to allow for the text
var plot = $.plot($("#placeholder"), data, {
series: {
stack: 1,
bars: {
show: true,
barWidth: 0.6,
},
yaxis: {
min: 0,
tickLength: 0
}
},
yaxis: {
max: maxYValue, // set a manual maximum to allow for labels
ticks: 0 // this line removes the y ticks
},
hooks: {
drawSeries: [drawSeriesHook]
}
});
That should get you started. You can take it from here, I'm sure.