I have a line graph with lot of points to plot
I want x axis to be scrollable
I have already looked few solutions but they are providing solution with old versions of chart js.
Is there any option to get scrollable x axis in chart.js version 2?
And
How can i get width of content in y axis in chart.js version 2?
if there is no direct option to get scrollable x axis, I can copy content in Y-axis region and draw image in other canvas.
My answer on a related question will help you. In my example I have made the Y axis scrollable, but this could easily be applied to the X axis too.
https://stackoverflow.com/a/51282003/10060003
JS fiddle - https://jsfiddle.net/EmmaLouise/eb1aqpx8/3/
I am using the animation onComplete and onProgress options to redraw the axis that I want to scroll with the chart. (See https://www.chartjs.org/docs/latest/configuration/animations.html).
$(function () {
var rectangleSet = false;
var canvasTest = $('#chart-Test');
var chartTest = new Chart(canvasTest, {
type: 'bar',
data: chartData,
maintainAspectRatio: false,
responsive: true,
options: {
tooltips: {
titleFontSize: 0,
titleMarginBottom: 0,
bodyFontSize: 12
},
legend: {
display: false
},
scales: {
xAxes: [{
ticks: {
fontSize: 12,
display: false
}
}],
yAxes: [{
ticks: {
fontSize: 12,
beginAtZero: true
}
}]
},
animation: {
onComplete: function () {
if (!rectangleSet) {
var scale = window.devicePixelRatio;
var sourceCanvas = chartTest.chart.canvas;
var copyWidth = chartTest.scales['y-axis-0'].width - 10;
var copyHeight = chartTest.scales['y-axis-0'].height + chartTest.scales['y-axis-0'].top + 10;
var targetCtx = document.getElementById("axis-Test").getContext("2d");
targetCtx.scale(scale, scale);
targetCtx.canvas.width = copyWidth * scale;
targetCtx.canvas.height = copyHeight * scale;
targetCtx.canvas.style.width = `${copyWidth}px`;
targetCtx.canvas.style.height = `${copyHeight}px`;
targetCtx.drawImage(sourceCanvas, 0, 0, copyWidth * scale, copyHeight * scale, 0, 0, copyWidth * scale, copyHeight * scale);
var sourceCtx = sourceCanvas.getContext('2d');
// Normalize coordinate system to use css pixels.
sourceCtx.clearRect(0, 0, copyWidth * scale, copyHeight * scale);
rectangleSet = true;
}
},
onProgress: function () {
if (rectangleSet === true) {
var copyWidth = chartTest.scales['y-axis-0'].width;
var copyHeight = chartTest.scales['y-axis-0'].height + chartTest.scales['y-axis-0'].top + 10;
var sourceCtx = chartTest.chart.canvas.getContext('2d');
sourceCtx.clearRect(0, 0, copyWidth, copyHeight);
}
}
}
}
});
Related
How can I change Chart Js's bar chart shape to have pointy top and bottom like this picture?
One way to get the triangles is to do a stacked chart, and add a line graph on top. To keep it reactive you would need to dynamically resize the pointRadius on the line data.
https://jsfiddle.net/0mcd5s13/3/
Or on line 4640 of chart.js you can change element_rectangle draw function to this:
draw: function() {
var ctx = this._chart.ctx;
var vm = this._view;
var rects = boundingRects(vm);
var outer = rects.outer;
var inner = rects.inner;
//ctx.fillStyle = vm.backgroundColor;
//ctx.fillRect(outer.x, outer.y, outer.w, outer.h);
if (outer.w === inner.w && outer.h === inner.h) {
return;
}
let offset = outer.w / 2;
ctx.save();
ctx.beginPath();
ctx.moveTo(outer.x, outer.y);
ctx.lineTo(outer.x, outer.y + outer.h);
ctx.lineTo(outer.x + offset, outer.y + outer.h + offset);
//ctx.lineTo(outer.x + offset, outer.y + outer.h);
ctx.lineTo(outer.x + outer.w, outer.y + outer.h);
ctx.lineTo(outer.x + outer.w, outer.y);
ctx.lineTo(outer.x + offset, outer.y - offset);
ctx.lineTo(outer.x, outer.y);
ctx.stroke();
//ctx.rect(outer.x, outer.y, outer.w, outer.h);
ctx.clip();
ctx.fillStyle = vm.borderColor;
// ctx.rect(inner.x, inner.y, inner.w, inner.h);
ctx.fill('evenodd');
ctx.restore();
},
which yields this:
more likely to import chart.js from a source, you will need make your own type of chart as an extension of bar chart.
(function(Chart) {
var helpers = Chart.helpers;
Chart.defaults.triBar = {
hover: {
mode: "label"
},
dataset: {
categoryPercentage: 0.8,
barPercentage: 0.9
},
scales: {
xAxes: [{
type: "category",
// grid line settings
gridLines: {
offsetGridLines: true
}
}],
yAxes: [{
type: "linear"
}]
}
};
Chart.controllers.triangleBar = Chart.controllers.bar.extend({
//
// extend element_rectangle draw function here
//
});
}).call(this, Chart);
I want to achieve two things.
1 - In the above image, imagine that the top part does not exist to begin with. If the user were to click on any point on the bottom chart, I want the top chart to be created with a magnified view. And if the bottom part is clicked and dragged, the top part should follow that.
2 - If the bottom chart were zoomed in, I want the top chart to also be zoomed in. Ex: If bottom was 1x and top was 2x, if I zoom bottom by 2x, top should now be 4x.
Are these possible with highcharts?
Yes, that behavior is easily achievable in Highcharts. All you need to do is to use Axis.afterSetExtremes and Point.click event callback function to create or update another chart with proper axis extremes. For example:
var chart1,
scale = 4;
function createOrUpdateDetail() {
var isPointClick = !this.lin2log,
chart = this.chart || this.series.chart,
xMin = chart.xAxis[0].min,
xMax = chart.xAxis[0].max,
xRange = xMax - xMin,
xCenter = (xMax + xMin) / 2,
yMin = chart.yAxis[0].min,
yMax = chart.yAxis[0].max,
yRange = yMax - yMin,
yCenter = (yMax + yMin) / 2,
calcXMin,
calcXMax,
calcYMin,
calcYMax;
if (isPointClick) {
xCenter = this.x;
yCenter = this.y;
}
calcXMin = xCenter - xRange / scale;
calcXMax = xCenter + xRange / scale;
calcYMin = yCenter - yRange / scale;
calcYMax = yCenter + yRange / scale;
if (!chart1) {
chart1 = Highcharts.chart('container', {
series: [{
type: 'scatter',
data: this.series.options.data
}],
xAxis: {
min: calcXMin,
max: calcXMax,
},
yAxis: {
endOnTick: false,
startOnTick: false,
min: calcYMin,
max: calcYMax
}
});
} else {
chart1.update({
xAxis: {
min: calcXMin,
max: calcXMax,
},
yAxis: {
min: calcYMin,
max: calcYMax
}
});
}
}
Highcharts.chart('container2', {
chart: {
zoomType: 'xy',
panning: true,
panKey: 'shift'
},
yAxis: {
endOnTick: false,
startOnTick: false,
},
xAxis: {
events: {
afterSetExtremes: createOrUpdateDetail
}
},
series: [{
type: 'scatter',
data: [...],
point: {
events: {
click: createOrUpdateDetail
}
}
}]
});
Live demo: http://jsfiddle.net/BlackLabel/6m4e8x0y/4982/
API Reference:
https://api.highcharts.com/highcharts/xAxis.events.afterSetExtremes
https://api.highcharts.com/highcharts/series.scatter.events.click
https://api.highcharts.com/class-reference/Highcharts.Chart#update
I'm trying to apply ZoomIn and ZoomOut in a line chart on a mobile device. The goal is to click on a zone of the chart and ZoomIn in the first click and ZoomOut on the second. The sequence will always be this one.
I already live to see the documentation / examples and I can not find anything to solve this situation.
I have already tried using this properties in the chart: property
pinchType : 'y',
zoomType: 'none'
I tried the zoomtype but the behavior is not what I expect. I want to have a click to zoom this specific area of the chart. I do not want to zoom with two fingers.
{
chart: {
pinchType : 'x'
},
legend: {
itemStyle: {
color: '#fff'
}
},
plotOptions: {
series: {
animation: {
duration: 2000
}
}
},
xAxis: {
tickInterval: 1
},
series: [
{
type: 'spline',
color : '#fff'
},
{
dashStyle: 'longdash',
color: '#b3be77'
}
],
}
As simple as clicking to get zoomin and zoomout
Yes, the second challenge can be easily achieved by adding this logic to plotOptions.series.events.click callback function:
chart: {
events: {
load: function() {
this.clickedOnce = false;
},
click: function() {
const chart = this;
if (chart.clickedOnce) {
chart.zoomOut();
chart.clickedOnce = false;
}
}
}
},
plotOptions: {
series: {
events: {
click: function(e) {
const chart = this.chart,
yAxis = chart.yAxis[0],
xAxis = chart.xAxis[0];
let x,
y,
rangeX,
rangeY;
if (!chart.clickedOnce) {
x = xAxis.toValue(e.chartX);
y = yAxis.toValue(e.chartY);
rangeX = xAxis.max - xAxis.min;
rangeY = yAxis.max - yAxis.min;
xAxis.setExtremes(x - rangeX / 10, x + rangeX / 10, false);
yAxis.setExtremes(y - rangeY / 10, y + rangeY / 10, false);
chart.redraw();
chart.clickedOnce = true;
} else {
chart.zoomOut();
chart.clickedOnce = false;
}
}
}
}
}
Demos:
https://jsfiddle.net/BlackLabel/kotgea5n/
https://jsfiddle.net/BlackLabel/s8w2xg3e/1/
This functionality is not implemented in Highcharts by default, but you can easily achieve it by adding your custom logic when the chart area is clicked.
When area is clicked the first time use axis.setExtremes() method to zoom in. On the second click use chart.zoomOut() to zoom out the chart. Check demo and code posted below.
Code:
chart: {
events: {
load: function() {
this.clickedOnce = false;
},
click: function(e) {
const chart = this,
yAxis = chart.yAxis[0],
xAxis = chart.xAxis[0];
let x,
y,
rangeX,
rangeY;
if (!chart.clickedOnce) {
x = xAxis.toValue(e.chartX);
y = yAxis.toValue(e.chartY);
rangeX = xAxis.max - xAxis.min;
rangeY = yAxis.max - yAxis.min;
xAxis.setExtremes(x - rangeX / 10, x + rangeX / 10, false);
yAxis.setExtremes(y - rangeY / 10, y + rangeY / 10, false);
chart.redraw();
chart.clickedOnce = true;
} else {
chart.zoomOut();
chart.clickedOnce = false;
}
}
}
}
Demo:
https://jsfiddle.net/BlackLabel/fxm812k4/
API reference:
https://api.highcharts.com/class-reference/Highcharts.Axis#setExtremes
https://api.highcharts.com/class-reference/Highcharts.Chart#zoomOut
https://api.highcharts.com/highcharts/chart.events.click
Using a customEvents plugin (see: https://github.com/blacklabel/custom_events) and adding plotBand on the whole chart area you can register a callback on click and double click events. Using this approach you can make a zoom in on click event and zoom out on double click (not working on mobile devices).
Demo:
https://jsfiddle.net/BlackLabel/6tpb5q2z/
I'm using Chart.js 2.6 and I have implemented the horizontalLine plugin to show an average value on my bar charts. It works fine, however when the tooltip displays in the spot where it intersects with the line, it is partially covered by the horizontal line itself. I'm trying to figure out how to make the tooltip draw ABOVE the horizontal line.
I understand the tooltip is part of the canvas element, and therefore does not have a z-index property. How can I accomplish this?
Here is what I'm using for my horizontal line plugin.
var horizonalLinePlugin = {
afterDraw: function(chartInstance) {
var yScale = chartInstance.scales["y-axis-0"];
var canvas = chartInstance.chart;
var ctx = canvas.ctx;
var index, line, style, width;
if (chartInstance.options.horizontalLine) {
for (index = 0; index < chartInstance.options.horizontalLine.length; index++) {
line = chartInstance.options.horizontalLine[index];
style = (line.style) ? line.style : "rgba(169,169,169, .6)";
yValue = (line.y) ? yScale.getPixelForValue(line.y) : 0 ;
ctx.lineWidth = (line.width) ? line.width : 3;
if (yValue) {
ctx.beginPath();
ctx.moveTo(chartInstance.chartArea.left, yValue);
ctx.lineTo(canvas.width, yValue);
ctx.strokeStyle = style;
ctx.stroke();
}
if (line.text) {
ctx.fillStyle = style;
ctx.fillText(line.text, 0, yValue + ctx.lineWidth);
}
}
return;
}
}
};
Chart.pluginService.register(horizonalLinePlugin);
... and then I add it to the bar chart options using the following
options: {
...standard option stuff...
"horizontalLine": [{
"y": averageValue,
"style" : colorOfTheLine
}]
}
Which generates a chart that looks like the one below.
..however when you hover on a segment of the chart to display the tooltip, and the tooltip is in the path of the horizontal line, it causes the issue seen below.
Attach your plugin to the afterDatasetDraw hook, instead of afterDraw . This will make the horizontal line to be drawn before the tooltip.
var horizonalLinePlugin = {
afterDatasetDraw: function(chartInstance) {
var yScale = chartInstance.scales["y-axis-0"];
var canvas = chartInstance.chart;
var ctx = canvas.ctx;
var index, line, style, width;
if (chartInstance.options.horizontalLine) {
for (index = 0; index < chartInstance.options.horizontalLine.length; index++) {
line = chartInstance.options.horizontalLine[index];
style = (line.style) ? line.style : "rgba(169,169,169, .6)";
yValue = (line.y) ? yScale.getPixelForValue(line.y) : 0;
ctx.lineWidth = (line.width) ? line.width : 3;
if (yValue) {
ctx.beginPath();
ctx.moveTo(chartInstance.chartArea.left, yValue);
ctx.lineTo(canvas.width, yValue);
ctx.strokeStyle = style;
ctx.stroke();
}
if (line.text) {
ctx.fillStyle = style;
ctx.fillText(line.text, 0, yValue + ctx.lineWidth);
}
}
return;
}
}
};
Chart.pluginService.register(horizonalLinePlugin);
new Chart(canvas, {
type: 'bar',
data: {
labels: ["January", "February"],
datasets: [{
label: "Dataset 1",
data: [80, 50]
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
},
horizontalLine: [{
y: 50,
style: 'red'
}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js"></script>
<canvas id="canvas"></canvas>
I am using highcharts.js for multiple series column chart and I want to show the shared tooltip on the top of group of column(It should take the highest column length)
So far I have tried this https:JSFiddle.
$(function() {
chart = new Highcharts.Chart({
chart: {
renderTo: 'container',
type: 'column',
plotBorderColor: '#CDCDCD',
plotBorderWidth: 1,
},
xAxis: {
type: "category"
},
yAxis: {
endOnTick: true,
startOnTick: true,
tickInterval: null,
},
tooltip: {
useHTML: true,
borderRadius: 0,
borderWidth: 0,
shadow: false,
followPointer: false,
hideDelay: 0,
shared: true,
enabled: true,
backgroundColor: "none",
positioner: function(labelWidth, labelHeight, point) {
var tooltipX, tooltipY;
var chart = this.chart;
tooltipX = point.plotX + chart.plotLeft - 20;
if (point.negative)
tooltipY = point.plotY + chart.plotTop + 20;
else
tooltipY = point.plotY + chart.plotTop - 30;
return {
x: tooltipX,
y: tooltipY
};
},
formatter: function() {
var templateHtmlString = "";
templateHtmlString += '<div class ="ttContainer">';
$.each(this.points, function(index, item) {
templateHtmlString += '<div class = "ttItem">';
templateHtmlString += item.y;
templateHtmlString += '</div>';
});
templateHtmlString += '</div>';
return templateHtmlString;
}
},
series: [{
"color": "red",
"data": [5,-1,17,9,8,19,-2,8,10],
"name": "ABCD"
}, {
"color": "Green",
"data": [8, -7,2,11,28,14,-3,8,-1],
"name": "XYZ"
}]
});
});
.ttItem {
display: inline-block;
padding: 5px;
}
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<div id="container" style="height: 400px"></div>
but it doesn't work when height of second column is greater than the first column. Please suggest how to fix this issue.
Finally, I figured out the solution myself.I have updated the JSFiddle.
I have modified the tooltip.positioner function to set the tooltip position.
positioner: function(labelWidth, labelHeight, point) {
var tooltipX, tooltipY;
var chart = this.chart;
var hoverPoints = this.chart.hoverPoints;
var y = 0;
var x = 0;
var totalColumnWidth = 0;
var deltaX = 0;
this.charts.hoverPoints contains the array of point object being hovered in case of multiple series chart.I loop through each point object in hoverpoints array.
plotY- y coordinate value of each column/point
barX - Starting x coordinate of each column/point
pointWidth - Width of each column/point
I have set the x as start of first point and y as the lowest plotY value among all points(lowest plotY means highest column)
$.each(hoverPoints, function(index, hPoint) {
var plotY = Math.ceil(hPoint.plotY);
if (index === 0) {
x = Math.ceil(hPoint.barX);
y = plotY;
}
totalColumnWidth += Math.ceil(hPoint.pointWidth);
if ((plotY > y && point.negative) || (plotY < y && !point.negative)) {
y = plotY;
}
});
delta = (totalColumnWidth - labelWidth) / 2
delta variable used to center align the tooltip.
tooltipX = x + chart.plotLeft + delta;
If column is on positive axis then add labelHeight so that tooltip wont overlap on column.
if (point.negative)
tooltipY = y + chart.plotTop;
else
tooltipY = y + chart.plotTop - labelHeight;
return {
x: tooltipX,
y: tooltipY
};
}
}
Alternatively, you could find the total height of y-axis and compare that with the points in the tooltipPos in hoverPoints.
$('#div_id').find('.highcharts-container').find('.highcharts-root').find('.highcharts-plot-background').attr('height'));
This way you could position each tooltip accordingly.