I'm using Vue Chart JS v3.5.1 in my Nuxt JS/Vue project, and I've noticed that when trying to use options and pass them as a prop, nothing happens, the chart defaults back to the chart's default settings despite me overwriting settings.
I've got several files:
plugins/LineChart.js
components/LineChart.vue
plugins/LineChart.js
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
mixins: [reactiveProp],
computed: {
localOptions: function() {
return this.chartOptions
},
localData: function() {
console.log(`data: ${this.chartData}`)
return this.chartData
}
},
mounted () {
this.renderLineChart()
},
methods: {
/*
** Render a line chart
*/
renderLineChart () {
// this.chartdata is created in the mixin.
// If you want to pass options please create a local options object
this.renderChart(this.localData, this.localOptions)
}
},
watch: {
chartData: {
handler: function (val, oldVal) {
this._data._chart.destroy()
this.renderLineChart()
},
deep: true
},
chartOptions: {
handler: function (val, oldVal) {
this.localOptions = val
},
deep: true
}
}
}
components/LineChart.vue
<template>
<div>
<line-chart :chart-data="customChartData" :chart-options="customChartOptions" class="data-chart"></line-chart>
</div>
</template>
<style>
.data-chart canvas {
width: 100% !important;
height: auto !important;
}
</style>
<script>
import LineChart from '~/plugins/LineChart.js'
export default {
components: {
LineChart
},
props: {
labels: {
type: Array,
default: null
},
datasets: {
type: Array,
default: null
},
options: {
type: Object,
default: () => ({})
}
},
data () {
return {
customChartData: {},
customChartOptions: {}
}
},
mounted () {
this.fillData()
},
methods: {
fillData () {
this.customChartData = {
labels: this.labels,
datasets: this.datasets
}
this.customChartOptions = {
options: this.options
}
}
}
}
</script>
My usage, is then reasonably simple, yet I'm not getting my options to show?
<LineChart
:options="{
responsive: true,
maintainAspectRatio: false,
legend: {
display: false
},
scales: {
xAxes: [{
gridLines: {
display: false
},
ticks: {
autoSkip: true,
maxTicksLimit: 3,
maxRotation: 0,
minRotation: 0
}
}],
yAxes: [{
display: true,
gridLines: {
display: true,
color: '#f3f5f6'
}
}]
},
elements: {
point: {
radius: 0,
hitRadius: 35
}
}
}"
:labels="['test']"
:datasets="[{
fill: false,
borderWidth: 2.5,
pointBackgroundColor: '#fff',
borderColor: '#5046e5',
data: [500,
}]"
/>
What am I doing wrong?
UPDATE
In addition, I seem to only have the first chart out of many charts on the page show data, why would only one chart in a series of charts show data, I've got a key on each one.
in your fillData it looks like you assign the options wrong. Vue Chartjs expects an object with the options in it and not an object with an field options with the options.
If you change: this.customChartOptions = {options: this.options} to: this.customChartOptions = this.options it should work
Related
I am trying to hide the legend of my chart created with Chart.js.
According to the official documentation (https://www.chartjs.org/docs/latest/configuration/legend.html), to hide the legend, the display property of the options.display object must be set to false.
I have tried to do it in the following way:
const options = {
legend: {
display: false,
}
};
But it doesn't work, my legend is still there. I even tried this other way, but unfortunately, without success.
const options = {
legend: {
display: false,
labels: {
display: false
}
}
}
};
This is my full code.
import React, { useEffect, useState } from 'react';
import { Line } from "react-chartjs-2";
import numeral from 'numeral';
const options = {
legend: {
display: false,
},
elements: {
point: {
radius: 1,
},
},
maintainAspectRatio: false,
tooltips: {
mode: "index",
intersect: false,
callbacks: {
label: function (tooltipItem, data) {
return numeral(tooltipItem.value).format("+0,000");
},
},
},
scales: {
xAxes: [
{
type: "time",
time: {
format: "DD/MM/YY",
tooltipFormat: "ll",
},
},
],
yAxes: [
{
gridLines: {
display: false,
},
ticks: {
callback: function(value, index, values) {
return numeral(value).format("0a");
},
},
},
],
},
};
const buildChartData = (data, casesType = "cases") => {
let chartData = [];
let lastDataPoint;
for(let date in data.cases) {
if (lastDataPoint) {
let newDataPoint = {
x: date,
y: data[casesType][date] - lastDataPoint
}
chartData.push(newDataPoint);
}
lastDataPoint = data[casesType][date];
}
return chartData;
};
function LineGraph({ casesType }) {
const [data, setData] = useState({});
useEffect(() => {
const fetchData = async() => {
await fetch("https://disease.sh/v3/covid-19/historical/all?lastdays=120")
.then ((response) => {
return response.json();
})
.then((data) => {
let chartData = buildChartData(data, casesType);
setData(chartData);
});
};
fetchData();
}, [casesType]);
return (
<div>
{data?.length > 0 && (
<Line
data={{
datasets: [
{
backgroundColor: "rgba(204, 16, 52, 0.5)",
borderColor: "#CC1034",
data: data
},
],
}}
options={options}
/>
)}
</div>
);
}
export default LineGraph;
Could someone help me? Thank you in advance!
PD: Maybe is useful to try to find a solution, but I get 'undefined' in the text of my legend and when I try to change the text like this, the text legend still appearing as 'Undefindex'.
const options = {
legend: {
display: true,
text: 'Hello!'
}
};
As described in the documentation you linked the namespace where the legend is configured is: options.plugins.legend, if you put it there it will work:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderColor: 'pink'
}
]
},
options: {
plugins: {
legend: {
display: false
}
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.0/chart.js"></script>
</body>
On another note, a big part of your options object is wrong, its in V2 syntax while you are using v3, please take a look at the migration guide
Reason why you get undefined as text in your legend is, is because you dont supply any label argument in your dataset.
in the newest versions this code works fine
const options = {
plugins: {
legend: {
display: false,
},
},
};
return <Doughnut data={data} options={options} />;
Import your options value inside the charts component like so:
const options = {
legend: {
display: false
}
};
<Line data={data} options={options} />
I want to have three LineCharts below each other. They are sharing same x-axis (time). I was able to create this:
However, it is very useless to have x-axis on the top and middle chart. I would prefer not to display the x-axis for the top and middle. When I tried it, I got this:
But as you can see, the last grid for 12AM is not visible for the top and middle chart, and the grids are not aligned (it looks weird). Each chart is rendered as a separate component using React.
Here are my TimelinePlot component which is rendering each LineChart (sorry for the mess, but I did not yet refactor it):
import React from 'react';
import { Line } from 'react-chartjs-2';
import { GetColors } from '../../utils/PlotDescriptionHelper';
class TimelinePlot extends React.Component {
MapPropsToData(props) {
const colors = GetColors(props.series.length);
return props.series.map((dataset, index) => {
return {
label: dataset.name,
data: dataset.data,
borderWidth: 1,
fill: false,
borderColor: colors[index],
//pointBackgroundColor: 'rgb(255,255,255)',
};
});
}
MapPropsToOptions(props) {
return {
elements: {
point: {
radius: 0,
},
},
legend: {
// display: props.showLegend,
position: 'top',
align: 'end',
},
scales: {
yAxes: [
{
ticks: props.yAxisTicks,
scaleLabel: {
display: true,
labelString: props.yAxisName + props.yAxisUnit,
},
},
],
xAxes: [
{
type: 'time',
// position: props.xAxisPosition,
time: {
parser: 'YYYY-MM-DD HH:mm',
tooltipFormat: 'll HH:mm',
},
// scaleLabel: {
// display: true,
// //labelString: 'Date',
// },
ticks: {
min: props.xAxisStart,
max: props.xAxisEnd,
//display: true,
display: props.xAxisDisplay,
},
},
],
},
};
}
render() {
const dataset = { datasets: this.MapPropsToData(this.props) };
return (
<div className='measurement-row'>
<Line
data={dataset}
options={this.MapPropsToOptions(this.props)}
position='absolute'
height='15%'
width='80%'
/>
</div>
);
}
}
And here is the render method of the parent using TimelinePlot component:
render() {
var plots = Object.keys(this.state.timeSeries).map((key, index) => {
return (
<TimelinePlot
key={key + index}
series={this.state.timeSeries[key]}
yAxisName={FirstLetterToUpper(key)}
yAxisUnit={MapKeyToUnit(key)}
xAxisDisplay={index === Object.keys(this.state.timeSeries).length - 1}
xAxisPosition={index === 0 ? 'top' : 'bottom'}
xAxisStart={this.state.startTime}
xAxisEnd={this.state.endTime}
showLegend={index === 0}
yAxisTicks={MapYAxisTicks(key)}
/>
);
});
return (
<div className='width-90'>
<TimelineDashboardHeader />
<div className='dashboard__column'>{plots}</div>
</div>
);
}
I understand that I need to set Chart.defaults.global.animation.duration = some value. I just don't know where I place this line. I've previously declared it everywhere and it doesn't seem to change anything. I thought that I could set it globally after importing Chart.
Here's the Chart Component.
<script>
import Chart from 'chart.js'
import config from '../config.js'
Chart.defaults.global.animation.duration = 3000
export default {
name: 'Chart',
props: ["positions"],
data() {
return {
}
},
created() {
this.loadData()
this.createChart('canvas', config)
},
methods: {
loadData() {
this.positions.forEach((position) => {
config.data.labels.push(position.symbol)
config.data.datasets[0].data.push(position.closePrice)
})
},
createChart(id, data) {
const ctx = document.getElementById(id)
new Chart(ctx, {
type: data.type,
data: data.data,
options: data.options,
});
}
}
}
</script>
The chart's configuration.
const config = {
type: 'pie',
data: {
labels: [],
datasets: [
{
label: '',
data: [],
backgroundColor: [
'#48beff',
'#3dfaff',
'#43c59e',
'#3d7068',
'#14453d',
],
borderWidth: 1
}
]
},
options: {
legend: {
display: false
},
},
}
export default config
I solved this. The problem was that the closing <style/> tag in my Component was missing. The styles were applied once it was added, and setting Chart.defaults.global.animation.duration now works as expected.
I have several vuejs reporting components. My intention is to use a common config object to create some consistency across the board and then expand on that on a per-chart basis. The problem I'm running into is that when 1 chart "extends" the configuration, it's impacting others.
For example, one component contains the following JS object which adds a callback for format the label as USD.
defaultOptions.options.tooltips = {
mode: 'index',
intersect: false,
callbacks: {
label: function(tooltipItem, data) {
// Prefix the tooltip with a dollar sign
return DefaultGraphOptions.formatLabelToDollars(tooltipItem.yLabel);
}
}
};
...but this impacts all charts on the page, not just the chart that contains financials.
DefaultGraphOptions
export default {
options: {
scales: {
xAxes: [{
display: true,
}],
yAxes: [{
display: true,
ticks: {
callback: (label) => {
// format all numbers with commas
let formatter = new Intl.NumberFormat('en-US');
return formatter.format(label);
}
}
}]
},
tooltips: {
callbacks: {
label: (tooltipItem, data) => {
// Format all tooltip figures with commas and such if they're larger numbers
let formatter = new Intl.NumberFormat('en-US');
return formatter.format(tooltipItem.yLabel);
}
}
}
},
formatLabelToDollars: (value) => {
if(parseInt(value) >= 1000){
return '$' + parseInt(value).toLocaleString();
} else {
return '$' + value;
}
},
cancellationReasonColors: () => {
return [
Colors.TEAL,
Colors.BLUE,
Colors.ORANGE,
Colors.PURPLE,
Colors.YELLOW,
Colors.LIME
];
}
}
Here's the component:
import { Bar } from 'vue-chartjs'
import DefaultGraphOptions from './graph-defaults';
import * as Colors from './colors';
export default {
extends: Bar,
data () {
return {
labels: [
{
label: 'Stripe Annual',
borderColor: Colors.PURPLE,
backgroundColor: Colors.PURPLE,
},
{
label: 'Stripe Monthly',
borderColor: Colors.YELLOW,
backgroundColor: Colors.YELLOW,
},
{
label: 'Paypal Annual',
borderColor: Colors.LIME,
backgroundColor: Colors.LIME,
},
],
}
},
mounted () {
// All components clone this object in this way
let defaultOptions = {... DefaultGraphOptions};
defaultOptions.options.title = {
display: true,
text: 'Sales'
};
// Give us summarized tooltips (showing all data sets together)
defaultOptions.options.tooltips = {
mode: 'index',
intersect: false,
callbacks: {
label: function(tooltipItem, data) {
// Prefix the tooltip with a dollar sign
return "$" + tooltipItem.yLabel.toFixed(2);
}
}
};
this.renderChart(DefaultGraphOptions.fromDailyStats(window.salesGrowth, this.labels), defaultOptions.options)
}
}
How can I use the imported DefaultGraphOptions as a clone on each vuejs compnent so one config doesn't impact another? It was my understanding that let objClone = { ...obj }; would create a clone of a js object
Export a method instead of an object. This is known as a factory pattern.
export function createFoo(){
return {foo:new Date()}
}
import createFoo from 'foo'
const foo = createFoo()
This is exactly why Vue's data must be a function.
Building with React... Trying to use outside method called on button click
Attempting to set mappoints to invisible. I have tried using:
.hide()
.setVisible
and using .update() to set 'visible' to false
I am able to reference the chart and the mappoint using the included definition in the package markdown:
let chart = this.refs.chart.getChart();
and in my render:
<ReactHighmaps config={config} ref="chart" />
Attempted:
chart.series[2].event.update({visible: false}) >> Error: Highchart.js:27 Uncaught TypeError: Cannot read property 'update' of
undefined
chart.plotOptions.mappoint.events.hide() >> TypeError: Cannot read property 'mappoint' of undefined
chart.series[2].setVisible(false, true) >> No Error in Console but nothing happens
chart.series[2].hide() >> No Error in Console but nothing happens And a few other variations.
ReactHighmaps.Highcharts.hide(chart.series[0]) >> Error that this isn't a function
There is a lot going on for me to make a JSbin...
Testing in Chrome
When I am inspecting the console.log of the mappoints I see that the method .setVisible() project path is:
.proto.proto.proto.setVisible
chart configuration (options):
const config = {
title: {
text: 'ZCTA with Metric Data'
},
chart: {
height: '600 px',
borderWidth: 1,
borderColor: 'silver',
borderRadius: 3,
shadow: true
},
mapNavigation: {
enabled: true
},
tooltip: {
enabled: false
},
plotOptions: {
map: {
showInLegend: false
},
mappoint: {
showInLegend: false,
},
mapline: {
enabledMouseTracking: false,
showInLegend: false
}
},
series: [{
mapData: MapData,
name: 'test',
data: County,
joinBy: ['fips', 'code'],
animation: true,
tooltip: {
pointFormat: '<b>{point.name}</b>'
},
borderColor: 'black',
borderWidth: 0.2,
states: {
hover: {
borderWidth: 0.5
},
select: {
color: 'yellow'
}
},
allowPointSelect: true,
cursor: 'pointer'
},
{
type: 'mapline',
name: 'State borders',
data: lines,
color: 'black',
states: {
hover: {
borderWidth: 0.5
}
},
allowPointSelect: false
},
{
type: 'mappoint',
name: 'zcta',
color: Highcharts.getOptions().colors[1],
data: Data,
boostThreshold: 500,
}]
For component render:
<ReactHighmaps config={config} ref="chart" />
Any suggestions?
Using React, Highcharts, and React-Highcharts (npm)
I was unable to get any Highcharts(Highmaps) API methods to work, but was able to drill down to update the state object.
This led me to Redux as a state management tool because I realize it would be better to have this controlled in an application state versus React component.
Here is my action and reducer:
export const SHOW_POINTS = 'SHOW_POINTS'
export function showPoints(configuration){
return {
type: SHOW_POINTS,
payload: configuration
};
}
const PointsMapConfig = (state = initialState, action) => {
switch (action.type){
case SHOW_POINTS:
return console.log(state.mapConfig.series[2]), { ...state,
mapConfig: {
series: [
...state.mapConfig.series.filter((el, index) => index !== 2), {
...state.mapConfig.series[2],
visible: true
}
]
}
}
default:
return state;
}
}
export default PointsMapConfig
onClick access with mapDispatchToProps:
function mapDispatchToProps(dispatch){
return bindActionCreators({
showPoints: showPoints
}, dispatch);
}
<Buttons style="success" classN="btn btn-secondary" text="test" onButtonClick={()=> this.props.showPoints( )} />