Trying to create a gradient background for my Line chart. The solution I tried to implement is not working. See screenshot:
I implemented canvas's createLinearGradient() to no avail. What else could I be doing wrong?
Edit: I added all my imports, including the Filler. This way it still doesn't work.
import { Line } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler,
ScriptableContext,
} from "chart.js";
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler
);
function Linechart() {
const labels = ["January", "February", "March", "April", "May", "June"];
const dataLineChart = {
labels: labels,
datasets: [
{
backgroundColor: (context: ScriptableContext<"line">) => {
const ctx = context.chart.ctx;
const gradient = ctx.createLinearGradient(0, 0, 0, 200);
gradient.addColorStop(0, "rgba(250,174,50,1)");
gradient.addColorStop(1, "rgba(250,174,50,0)");
return gradient;
},
label: "UST",
fill: "start",
borderColor: "#8E95DF",
data: [0, 10, 5, 2, 20, 30, 45],
},
],
};
const configLineChart = {
type: "line",
dataLineChart,
elements: {
line: {
tension: 0.35,
},
},
plugins: {
filler: {
propagate: false,
},
},
};
return (
<div>
<div className="px-5 py-3">Fund Profits</div>
<Line data={dataLineChart} options={configLineChart}></Line>
</div>
);
}
export default Linechart;
Edit: When changing this const gradient = ctx.createLinearGradient(0, 0, 0, 200); to const gradient = ctx.createLinearGradient(0, 0, 0, 500); my gradient becomes visible. Can someone explain how that value works?
Related
import React from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend
} from 'chart.js';
import { Bar } from 'react-chartjs-2';
ChartJS.register(
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip,
Legend,
);
const options = {
responsive: true,
elements: {
bar: {
borderWidth: 2,
},
},
plugins: {
scales: {
x: {
grid: {
display: false
}
},
y: {
grid: {
display: false
}
},
},
legend: {
display: false,
},
title: {
display: false,
// text: 'Top Application Accessed',
},
},
};
const labels = ["a", "b", "c", "d0", "d", "e", "t"];
const v = [8, 10, 15, 2, 4, 11, 17]
const data = {
labels,
datasets: [
{
label: "Total no of errors",
data: v,
backgroundColor: 'blue',
}
],
};
export default function App() {
return <Bar options={options} data={data} />;
}
In the above code All the code inside scales is not having any effect.
I want to hide grids from my chart.
I want to also add some features to my chart but anything I add to this code having no effect in the results.
Instead of full grids I want dotted grid only parallel to x axis.
I also want to add different colors to all the bars.
You have putted your scale condif in the plugins section of the options object. This is incorrect, you need to place the scales section in the root of the options object. Then it will work fine.
So instead of options.plugins.scales.x and options.plugins.scales.y you will get: options.scales.x and options.scales.y
I referred to "How to change line segment color of a line graph in Chart.js" and extended the chart to redraw the line segment with a different color.
But after adding plugin "chartjs-plugin-zoom" to support zoom in/out the chart, I could see the line chart is out of axis boundary. See the following code.
https://jsfiddle.net/sd9rx84g/2/
var ctx = document.getElementById('myChart').getContext('2d');
//adding custom chart type
Chart.defaults.multicolorLine = Chart.defaults.line;
Chart.controllers.multicolorLine = Chart.controllers.line.extend({
draw: function(ease) {
var
startIndex = 0,
meta = this.getMeta(),
points = meta.data || [],
colors = this.getDataset().colors,
area = this.chart.chartArea,
originalDatasets = meta.dataset._children
.filter(function(data) {
return !isNaN(data._view.y);
});
function _setColor(newColor, meta) {
meta.dataset._view.borderColor = newColor;
}
if (!colors) {
Chart.controllers.line.prototype.draw.call(this, ease);
return;
}
for (var i = 2; i <= colors.length; i++) {
if (colors[i-1] !== colors[i]) {
_setColor(colors[i-1], meta);
meta.dataset._children = originalDatasets.slice(startIndex, i);
meta.dataset.draw();
startIndex = i - 1;
}
}
meta.dataset._children = originalDatasets.slice(startIndex);
meta.dataset.draw();
meta.dataset._children = originalDatasets;
points.forEach(function(point) {
point.draw(area);
});
}
});
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'multicolorLine',
// The data for our dataset
data: {
labels: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
datasets: [{
label: "My First dataset",
borderColor: 'rgb(255, 99, 132)',
data: [35, 10, 5, 2, 20, 30, 45, 0, 10, 5, 2, 20, 30, 45],
//first color is not important
colors: ['', 'red', 'green', 'blue', 'red', 'brown', 'black']
}]
},
// Configuration options go here
options: {
plugins: {
zoom: {
pan: {
// pan options and/or events
enabled: true,
mode: 'xy'
},
zoom: {
enabled: true,
drag: false,
mode: 'x',
speed: 1
}
}
}
}
});
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.3"></script>
<script src="https://cdn.jsdelivr.net/npm/hammerjs#2.0.8"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-zoom#0.7.7"></script>
<canvas id="myChart"></canvas>
line chart out of axis boundary
How can I resolve this issue?
Thanks
Forrest
I am using react chartjs 2. I need to increase margin between legend and chart. Here I found many solutions that are not for react or nextjs. That's why I need to solve it with react environment. Please help me.
Here is my code-
import { Box, Typography } from "#mui/material";
import { Line } from 'react-chartjs-2';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
} from 'chart.js';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend
);
const options = {
responsive: true,
plugins: {
legend: {
labels: {
boxHeight: 2,
boxWidth: 50
},
},
}
};
const data = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
id: 1,
label: 'iPhone',
data: [65, 59, 80, 81, 56, 55, 40],
}
]
};
const DownloadChart = () => {
return (
<Box>
<Typography variant="h5" component="h5">
Device Download
</Typography>
<Line
datasetIdKey='id'
data={data}
options={options}
/>
</Box>
);
};
export default DownloadChart;
I see that there are available beforeInit and afterInit function. But I am not knowing that How can I apply it. Please help me.
You can use a custom plugin:
ChartJS.register({
id: 'customSpacingLegend',
beforeInit(chart) {
// Get reference to the original fit function
const originalFit = chart.legend.fit;
// Override the fit function
chart.legend.fit = function fit() {
// Call original function and bind scope in order to use `this` correctly inside it
originalFit.bind(chart.legend)();
// Change the height as suggested in another answers
this.height += 15;
}
};
});
I am having issue with the default tooltip that chartjs provides as I can not add html inside the tooltips. I had been looking at how i can add the html/jsx inside the tooltip. I see an example with using customized tooltips here Chart JS Show HTML in Tooltip.
can someone point me an example how to achieve the same with react-chartjs-2 library?
You have to use the custom callback in the tooltip property to define your own positioning and set the hovered dataset in the component state
state = {
top: 0,
left: 0,
date: '',
value: 0,
};
_chartRef = React.createRef();
setPositionAndData = (top, left, date, value) => {
this.setState({top, left, date, value});
};
render() {
chartOptions = {
"tooltips": {
"enabled": false,
"mode": "x",
"intersect": false,
"custom": (tooltipModel) => {
// if chart is not defined, return early
chart = this._chartRef.current;
if (!chart) {
return;
}
// hide the tooltip when chartjs determines you've hovered out
if (tooltipModel.opacity === 0) {
this.hide();
return;
}
const position = chart.chartInstance.canvas.getBoundingClientRect();
// assuming your tooltip is `position: fixed`
// set position of tooltip
const left = position.left + tooltipModel.caretX;
const top = position.top + tooltipModel.caretY;
// set values for display of data in the tooltip
const date = tooltipModel.dataPoints[0].xLabel;
const value = tooltipModel.dataPoints[0].yLabel;
this.setPositionAndData({top, left, date, value});
},
}
}
return (
<div>
<Line data={data} options={chartOptions} ref={this._chartRef} />
{ this.state.showTooltip
? <Tooltip style={{top: this.state.top, left: this.state.left}}>
<div>Date: {this.state.date}</div>
<div>Value: {this.state.value}</div>
</Tooltip>
: null
}
</div>
);
}
You can use the tooltips supplied by React Popper Tooltip or roll your own - pass the top and left to the tooltip for positioning, and the date and value (in my example) should be used to show the data in the tooltip.
If anyone looking answer customization of tooltip and gradient chart here is my code:
My Packages:
"react": "^17.0.2"
"chart.js": "^3.7.1"
"react-chartjs-2": "^4.1.0"
"tailwindcss": "^3.0.23"
ToopTip Component:
import React, { memo } from "react";
import { monetarySuffix } from "#src/helpers/util";
// tooltip.js
const GraphTooltip = ({ data, position, visibility }) => {
return (
<div
className={`absolute px-4 py-3.5 rounded-lg shadow-lg bg-chart-label-gradient text-white overflow-hidden transition-all duration-300 hover:!visible
${visibility ? "visible" : "invisible"}
`}
style={{
top: position?.top,
left: position?.left,
}}
>
{data && (
<>
<h5 className="w-full mb-1.5 block text-[12px] uppercase">
{data.title}
</h5>
<ul className="divide-y divide-gray-100/60">
{data.dataPoints.map((val, index) => {
return (
<li
key={index}
className="m-0 py-1.5 text-base font-rubik font-medium text-left capitalize last:pb-0"
>
{val?.dataset.label}
{":"} {monetarySuffix(val?.raw)}
</li>
);
})}
</ul>
</>
)}
</div>
);
};
export default memo(GraphTooltip);
Chart Component
import React, { useMemo, useState, useRef, useCallback } from 'react';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import GraphTooltip from './chart-tooltip';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
Filler
);
const GradientChart = () => {
const [tooltipVisible, setTooltipVisible] = useState(false);
const [tooltipData, setTooltipData] = useState(null);
const [tooltipPos, setTooltipPos] = useState(null);
const chartRef = useRef(null);
const data = {
labels: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'Agust',
'September',
'October',
'November',
'December',
],
datasets: [
{
fill: true,
backgroundColor: (context) => {
const chart = context.chart;
const { ctx, chartArea } = chart;
if (!chartArea) {
return;
}
return createGradient(
ctx,
chartArea,
'#F46079',
'#F46079',
'rgba(255,255,255,0)'
);
},
borderColor: '#F46079',
lineTension: 0.4,
pointRadius: 5,
pointHoverRadius: 10,
pointBackgroundColor: '#FE5670',
pointBorderColor: '#ffffff',
pointBorderWidth: 1.5,
label: 'Sales',
data: [
4500, 2800, 4400, 2800, 3000, 2500, 3500, 2800, 3000, 4000, 2600,
3000,
],
},
{
fill: true,
backgroundColor: (context) => {
const chart = context.chart;
const { ctx, chartArea } = chart;
if (!chartArea) {
return;
}
return createGradient(
ctx,
chartArea,
'#2f4b7c',
'#2f4b7c',
'rgba(255,255,255,0)'
);
},
borderColor: '#2f4b7c',
lineTension: 0.4,
pointRadius: 5,
pointHoverRadius: 10,
pointBackgroundColor: '#FE5670',
pointBorderColor: '#ffffff',
pointBorderWidth: 1.5,
label: 'Commision',
data: [
5000, 3500, 3000, 5500, 5000, 3500, 6000, 1500, 2000, 1800, 1500,
2800,
],
},
{
fill: true,
backgroundColor: (context) => {
const chart = context.chart;
const { ctx, chartArea } = chart;
if (!chartArea) {
return;
}
return createGradient(
ctx,
chartArea,
'#665191',
'#665191',
'rgba(255,255,255,0)'
);
},
borderColor: '#665191',
lineTension: 0.4,
pointRadius: 5,
pointHoverRadius: 10,
pointBackgroundColor: '#FE5670',
pointBorderColor: '#ffffff',
pointBorderWidth: 1.5,
label: 'Transaction',
data: [
1000, 2000, 1500, 2000, 1800, 1500, 2800, 2800, 3000, 2500, 3500,
2800,
],
},
],
};
const createGradient = (ctx, chartArea, c1, c2, c3) => {
const chartWidth = chartArea.right - chartArea.left;
const chartHeight = chartArea.bottom - chartArea.top;
const gradient = '';
const width = '';
const height = '';
if (!gradient || width !== chartWidth || height !== chartHeight) {
width = chartWidth;
height = chartHeight;
gradient = ctx.createLinearGradient(
0,
chartArea.bottom,
0,
chartArea.top
);
gradient.addColorStop(0, c3);
gradient.addColorStop(0.5, c2);
gradient.addColorStop(1, c1);
}
return gradient;
};
const customTooltip = useCallback((context) => {
if (context.tooltip.opacity == 0) {
// hide tooltip visibilty
setTooltipVisible(false);
return;
}
const chart = chartRef.current;
const canvas = chart.canvas;
if (canvas) {
// enable tooltip visibilty
setTooltipVisible(true);
// set position of tooltip
const left = context.tooltip.x;
const top = context.tooltip.y;
// handle tooltip multiple rerender
if (tooltipPos?.top != top) {
setTooltipPos({ top: top, left: left });
setTooltipData(context.tooltip);
}
}
});
const options = useMemo(() => ({
responsive: true,
scales: {
y: {
grid: {
display: false,
},
},
},
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
legend: {
display: false,
},
title: {
display: false,
},
tooltip: {
enabled: false,
position: 'nearest',
external: customTooltip,
},
},
}));
return (
<div className="grad-chart-wrapper w-full relative">
<Line options={{ ...options }} data={data} ref={chartRef} />
{tooltipPos && (
<GraphTooltip
data={tooltipData}
position={tooltipPos}
visibility={tooltipVisible}
/>
)}
</div>
);
};
export default GradientChart;
Remember to think in React here (which is not always easy). Use the mycustomtooltipfunction to set state in your React class (specifically, add the tooltip that is passed to mycustometooltipfunction to the state - this will result in render being invoked. Now in the render function of your class, check if that state exists and add the JSX for your tooltip.
class MyChart extends Component {
constructor(props) {
super(props);
this.state = {
tooltip : undefined
};
}
showTooltip = (tooltip) => {
if (tooltip.opacity === 0) {
this.setState({
tooltip : undefined
});
} else {
this.setState({
tooltip
});
}
}
render() {
const { tooltip } = this.state;
let options = {
...
tooltips : {
enabled : false,
custom : this.showTooltip,
}
}
let myTooltip;
if (tooltip) {
// MAKE YOUR TOOLTIP HERE - using the tooltip from this.state.tooltip, or even have a tooltip JSX class
}
return (
<div>
{myTooltip}
<Line ref="mygraph" key={graphKey} data={data} options={options} height={graphHeight} width={graphWidth}/>
</div>
)
}
}
`
this.chart.chart_instance.canvas.getBoundingClientRect();
If you get some error with chart_instance you should check parent of element value.
Try this:
this.chart.chartInstance.canvas.getBoundingClientRect();
I'm building a responsive app that using angular-chart.js.
(Not enough repo, sorry)
http://i.imgur.com/g648Iws.png
http://i.imgur.com/LK1eQCo.png
As you can see, some of the text were too long (y-axis) and it squeezed my chart.
I tried to truncate the text, but it doesn't look nice if you have larger devices.
Is there any way to handle this situation or (truncate dynamically) ?
ChartJsProvider.setOptions({
responsive: true,
barShowStroke: false,
maintainAspectRatio: false // I tried with 'true' but still looks the same
});
Note that I did not add the width and height attribute because I want the graph to fit responsively.
<canvas id="bar" class="chart chart-bar" data="data" labels="labels" legend="true" series="series"></canvas>
Any help is appreciated.
You can trim the labels depending on the screen width, like so
Chart.types.Bar.extend({
name: "BarAlt",
initialize: function (data) {
var originalLabels;
// figure out the best label length for the current window size
if (this.options.responsiveLabelLengths !== undefined) {
var maxLabelLength;
this.options.responsiveLabelLengths
.sort(function (a, b) {
return b.uptoWidth - a.uptoWidth;
})
.forEach(function (spec) {
if (screen.width < spec.uptoWidth)
maxLabelLength = spec.maxLabelLength;
})
// update the labels if required
if (maxLabelLength !== undefined) {
originalLabels = Chart.helpers.clone(data.labels);
data.labels.forEach(function (label, i) {
if (data.labels[i].length > maxLabelLength)
data.labels[i] = data.labels[i].substring(0, maxLabelLength) + '...';
})
}
}
Chart.types.Bar.prototype.initialize.apply(this, arguments);
// optional - now restore the original labels for the tooltips
if (originalLabels !== undefined) {
this.datasets.forEach(function (dataset) {
dataset.bars.forEach(function (bar, i) {
bar.label = originalLabels[i];
})
})
}
}
});
var data = {
labels: ["Really Really Really Really Really Long Text", "February", "March", "April", "May", "June", "July"],
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.5)",
strokeColor: "rgba(220,220,220,0.8)",
highlightFill: "rgba(220,220,220,0.75)",
highlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 80, 81, 56, 55, 40]
},
{
label: "My Second dataset",
fillColor: "rgba(151,187,205,0.5)",
strokeColor: "rgba(151,187,205,0.8)",
highlightFill: "rgba(151,187,205,0.75)",
highlightStroke: "rgba(151,187,205,1)",
data: [28, 48, 40, 19, 86, 27, 90]
}
]
};
var ctx = document.getElementById("myChart").getContext("2d");
var myLineChart = new Chart(ctx).BarAlt(data, {
responsiveLabelLengths: [
{
uptoWidth: 480,
maxLabelLength: 5
},
{
uptoWidth: 768,
maxLabelLength: 10
},
{
uptoWidth: 992,
maxLabelLength: 20
},
{
uptoWidth: 1200,
maxLabelLength: undefined
},
]
});
Since we are using screen width you need to use Chrome Developer tools and choose a proper viewport sizing to see the effect. For the same reason, this is not going to have any effect if you are resizing your window - if you need that, just replace screen.width by window.innerWidth.
To add this as a new directive to angular-chart is pretty simple. After the above block of code (and after the script include for angular-chart.js) add a new directive for BarAlt, like so
angular.module('chart.js')
.directive('chartBarAlt', ['ChartJsFactory', function (ChartJsFactory) { return new ChartJsFactory('BarAlt'); }]);
Then use the directive like so
<canvas id="line" class="chart chart-bar-alt" data="data" options="options"
labels="labels"></canvas>
Notice options? You will need to include the responsiveLabelLengths in your options for BarAlt to kick in (otherwise it just acts like a normal bar chart). You do that like this (in your controller)
$scope.options = {
responsiveLabelLengths: [
{
uptoWidth: 480,
maxLabelLength: 5
},
{
uptoWidth: 768,
maxLabelLength: 10
},
{
uptoWidth: 992,
maxLabelLength: 20
},
{
uptoWidth: 1200,
maxLabelLength: undefined
},
]
};
Note that you need to use Chrome Developer tools and choose a proper viewport sizing to see the effect
Fiddle - http://jsfiddle.net/mqo24Lnx/
Here's how the trimming works on different screen sizes