Svelte and chartjs, pass data - chart.js

I'm trying to create a chart with chart.js and svelte. I call the data in 3 ways and only one works and it's not the one I'm interested in.
In test 1 I have the data in an external js and it works. (data)
In test 2 I put the same array in the svelte file and it doesn't work. (data2)
In test 3 I take the data from an api and configure the array to have the same format as the previous ones. It doesn't work (data_chart)
I need to make the 3rd option work. Any idea why it doesn't like it?
<script>
import { page } from '$app/stores';
import { onMount } from 'svelte';
import { data } from './data.js';
import { Line } from 'svelte-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale } from 'chart.js';
ChartJS.register(Title, Tooltip, Legend, LineElement, LinearScale, PointElement, CategoryScale);
export let param = String($page.params.slug);
let ruta_api = `${import.meta.env.VITE_WP_API}posts?slug=${param}`;
let ruta_api_chart = '';
let value = [];
let value_chart = [];
let data_chart = new Array;
let id_chart = [];
const data2 = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: 'My First dataset',
borderColor: 'rgb(205, 130, 158)',
data: [65, 59, 80, 81, 56, 55, 40],
},
{
label: 'My Second dataset',
borderColor: 'rgb(35, 26, 136)',
data: [28, 48, 40, 19, 86, 27, 90],
},
],
};
onMount(async () => {
await loadData();
});
async function loadData() {
const response = await fetch(ruta_api);
const newData = await response.json();
value = [...value, ...newData];
ifChart();
}
async function ifChart(){
//comprobamos si viene una grafica en el contenido
let posicion_chart = value[0].content.rendered.indexOf('m-chart-container-');
if(posicion_chart >= 0){
const regex = /(?<=m-chart-container-)(.*?)(?=-)/mg;
id_chart = value[0].content.rendered.match(regex);
//recorremos los ids
id_chart.forEach(function(id) {
getChart(id);
})
}
};
export async function getChart(id){
ruta_api_chart = `${import.meta.env.VITE_WP_API}m-chart/` + id;
const response_chart = await fetch(ruta_api_chart);
const newData_chart = await response_chart.json();
value_chart = newData_chart['m-chart'];
data_chart = {'labels' : value_chart['data'][0][0], 'datasets': {'label' : value_chart['x_title'], 'data' : value_chart['data'][0][1]}};
};
</script>
<Line {data} options={{ responsive: true }} />
<Line {data2} options={{ responsive: true }} />
<Line {data_chart} options={{ responsive: true }} />

This might be because in
<Line {data} options={{ responsive: true }} />
{data} is the shorthand for data={data}
In the other cases the variable name is different, so try changing to
<Line data={data2} options={{ responsive: true }} />
<Line data={data_chart} options={{ responsive: true }} />

Related

Why labels on x-asis does not shows well?

Here is my div with canvas for line chart:
<div
x-data="{
lineChartLabels: #entangle('lineChartLabels'),
lineChartData: #entangle('lineChartData'),
init() {
const labels = `${this.lineChartLabels}`;
const data = {
labels: labels,
datasets: [{
backgroundColor: 'lightblue',
data: this.lineChartData,
fill: true,
label: `{{__('site.report.values')}}`,
}]
};
const config = {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
ticks: {
callback: function (value) {
const date = new Date(Number(value) * 1000);
return date.toISOString().substring(11,23);
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
const date = new Date(Number(context.formattedValue) * 1000);
return date.toISOString().substring(11,23);
}
}
}
}
}
};
const myChart = new Chart(
this.$refs.canvas,
config
);
Livewire.on('updateTheChart', () => {
myChart.destroy();
myChart.data.datasets[0].data = this.lineChartData;
myChart.data.labels = this.lineChartLabels;
myChart.update();
})
}
}">
<canvas id="myChart" x-ref="canvas" style="height: 33vh;width: 50vw;"></canvas>
</div>
And here is an example of values(which represent seconds):
[49.66,47.26,46.88,49.81]
and for x axis which represents dates:
["04-Feb-17","06-May-17","28-Oct-17","20-Dec-17"]
But when it renders it shows like this:
Can somebody tell me why chartjs does not show x-asis in the proper way and how to fix it?
The problem is this line const labels = `${this.lineChartLabels}`; you are creating a script, what you need ist the array, so with other words: const labels = this.lineChartLabels; remove the string interpolation.
Disclaimer: I'm guessing abit, because I'm not acquainted with laravel-livewire and alpine.js, but that line of code looks like the culprit to me.
Here the two different Version, side by side:
const labels = ["04-Feb-17","06-May-17","28-Oct-17","20-Dec-17"];
const data = {
labels: labels,
datasets: [{
backgroundColor: 'lightblue',
data: [49.66,47.26,46.88,49.81],
fill: true,
label: `Correct Version`,
}]
};
const config = {
type: 'line',
data: data,
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
ticks: {
callback: function (value) {
const date = new Date(Number(value) * 1000);
return date.toISOString().substring(11,23);
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function (context) {
const date = new Date(Number(context.formattedValue) * 1000);
return date.toISOString().substring(11,23);
}
}
}
}
}
};
new Chart(
document.getElementById('chart'),
config
);
config.data.labels = `${["04-Feb-17","06-May-17","28-Oct-17","20-Dec-17"]}`;
config.data.datasets[0].label = `Incorrect Version`;
let chart2 = new Chart(
document.getElementById('chart2'),
config
);
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<div class="chart" style="float:left;height:184px; width:300px;font-family: Arial">
<canvas id="chart" ></canvas>
</div>
<div class="chart" style="float:left;height:184px; width:300px;font-family: Arial">
<canvas id="chart2" ></canvas>
</div>

How to give the same gradient chart js type bar to each datasets

chart js makes a common gradient for all elements:
enter image description here
I need this:
enter image description here
It's a bit tricky because, using to backgroundColor scriptable option (callback), this is invoked before the element dimension is completely calculated (as far as I have seen, maybe I'm wrong). Anyway, by a workaround, it could be something like in the snippet.
const ctx = document.getElementById("myChart");
const myChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['January', 'Fabruary', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
data: [50, 35, 45, 47, 21, 13, 27],
borderWidth: 0,
backgroundColor(context) {
const {chart, datasetIndex, index} = context;
const ds = chart.data.datasets[datasetIndex];
const value = ds.data[index];
const y = chart.scales.y.getPixelForValue(value);
const meta = chart.getDatasetMeta(datasetIndex);
const data = meta.data[index];
const {x, width, base} = data;
if (x) {
const ctx = chart.ctx;
const gradient = ctx.createLinearGradient(x, y, x + width, base);
gradient.addColorStop(0, 'green');
gradient.addColorStop(0.5, 'yellow');
gradient.addColorStop(1, 'red');
return gradient;
}
}
}]
},
options: {
plugins: {
legend: false
}
}
});
.myChartDiv {
max-width: 600px;
max-height: 400px;
}
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.9.1/dist/chart.min.js"></script>
<html>
<body>
<div class="myChartDiv">
<canvas id="myChart" width="600" height="400"/>
</div>
</body>
</html>

React chartjs-2 - Increase spacing between legend and chart

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;
}
};
});

updating chart.js in ractive

For some data visualisation I use ractive and chart.js. The initial drawing works great, but I can't find a way to update the chart automatically when my data changes. So far I got (simplified):
const Stats = '<canvas id="myChart" width="400" height="400"></canvas>'
new Ractive ({
el: '#stats',
template: Stats,
magic: true,
modifyArrays: true,
data: {docs}, // <= some JSON Data
computed: {
Data1() {
let tempList = this.get('docs');
// rearrange & filter Data
return tempList ;
},
Data2() {
let tempList2 = this.get('docs');
// rearrange & filter Data
return tempList2 ;
},
Data3() {
let tempList3 = this.get('docs');
// rearrange & filter Data
return tempList3 ;
},
}
},
onrender: function () {
let DataSet1 = this.get('Data1');
let DataSet2 = this.get('Data2');
let DataSet3 = this.get('Data3');
let ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ["Data 1", "Data 1", "Data 3"],
datasets: [{
label: 'All my Data',
data: [DataSet1.length, DataSet2.length, DataSet3.length],
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(75, 192, 192, 0.2)'
],
borderColor: [
'rgba(255,99,132,1)',
'rgba(255, 159, 64, 1)',
'rgba(75, 192, 192, 1)'
],
borderWidth: 1
}]
},
options: {
responsive: false
}
});
},
onchange: function () {
let newData = this.get('docs')
addData(myChart, label, newData)
function addData(chart, label, data) {
chart.data.labels.push(label);
chart.data.datasets.forEach((dataset) => {
dataset.data.push(data);
});
chart.update();
}
}
});
Of Course I get an error in line chart.data.labels.push(label);, and also I'm quite sure that I would need my computed values in the onrenderfunction, not the initial Dataset. But I really have no clue how to get those into the update function, or if this function is the correct approach at all...
If you are working with 3rd party plugins I would recommend to use Ractive's decorators.
This post on ractivejs-and-jquery-plugins shows you a starting point how to implement a decorator based on a fileupload control.
In your case I would recommend to build a decorator with the data as parameter and do not forget to implement the UPDATE function (where in your case you gonna call the update method with the new data for your chart)
As I could not figure out how to write my own decorator for Chart.js I thought I post the solution that worked for me. I do not think it's best practice and for sure decorators would be a better way (therefore I do not mark this solution as correct answer), but it works:
const Stats = '<canvas id="myChart" width="400" height="400"></canvas>'
new Ractive ({
el: '#stats',
template: Stats,
magic: true,
modifyArrays: true,
data: {docs}, // <= some JSON Data
computed: {
Data1() {
let tempList = this.get('docs');
// rearrange & filter Data
return tempList ;
},
Data2() {
let tempList2 = this.get('docs');
// rearrange & filter Data
return tempList2 ;
},
Data3() {
let tempList3 = this.get('docs');
// rearrange & filter Data
return tempList3 ;
},
}
},
onrender: function () {
let DataSet1 = this.get('Data1');
let DataSet2 = this.get('Data2');
let DataSet3 = this.get('Data3');
let ctx = document.getElementById("myChart");
myChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ["Data 1", "Data 1", "Data 3"],
datasets: [{
label: 'All my Data',
data: [DataSet1.length, DataSet2.length, DataSet3.length]
}]
}
});
},
onchange: function () {
let changedData1 = this.get('Data1');
let changedData2 = this.get('Data2');
let changedData3 = this.get('Data3');
myChart.data.datasets[0].data[0] = changedData1.length;
myChart.data.datasets[0].data[1] = changedData2.length;
myChart.data.datasets[0].data[2] = changedData3.length;
myChart.update();
}
}
});

custom tooltips with react-chartjs-2 library

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();