Export chart toBase64Image on Vue3 composition API - chart.js

"vue": "^3.2.37",
"vue-chartjs": "^4.1.2",
Using this code I would like download the chart as base64 image, but I cannot find the instance of chartJs, to invoke toBase64Image() method.
Any help please ? Thanks
<script setup lang="ts">
import { Line } from 'vue-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, ChartData, ChartOptions } from 'chart.js'
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
const lineChartRef = ref()
const exportChart = () => {
console.log(lineChartRef.value.chartInstance)
lineChartRef.value.chartInstance.toBase64Image()
}
...
<template>
<n-card :bordered="true" class="shadow-sm rounded-xl">
<button type="submit" #click="exportChart">Export Chart as PNG</button>
<Line ref="lineChartRef" chart-id="lineChart" :chart-data="props.chartData" :chart-options="props.chartOptions" :height="250" />
</n-card>
</template>

Related

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

aws amplify withAuth0 HOC: the auth0 client is not configured error

Having some trouble following the docs here federated with auth0 to add an extra login button that works to an existing project.
I have added the config object and created the app on the auth0 platform and configured cognito to use it as a federated login, but getting the button to work using the AWS Amplify withAuth0 HOC does not seem to work as expected. Either that or the docs are not clear.
I am getting the following error when clicking the button: the auth0 client is not configured
index.js
import "#babel/polyfill";
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import AppWithAuth from './appWithAuth';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore } from 'redux'
import rootReducer from './reducers'
const store = createStore(rootReducer);
ReactDOM.render(
<Provider store={store}>
<AppWithAuth />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
App.js
import React, { Component } from 'react';
import CssBaseline from '#material-ui/core/CssBaseline';
import { MuiThemeProvider, createMuiTheme, withStyles } from '#material-ui/core/styles';
import { BrowserRouter as Router } from "react-router-dom";
import { MuiPickersUtilsProvider } from '#material-ui/pickers';
import MomentUtils from '#date-io/moment';
import 'typeface-roboto';
import { connect } from 'react-redux';
import { setUserMetaLoaded, setPremiumUser, setAdminUser } from './actions';
import API from './api/appsync';
import Header from './components/header';
import MyRouter from './router';
import { library } from '#fortawesome/fontawesome-svg-core';
import { faLink } from '#fortawesome/free-solid-svg-icons';
import Amplify from 'aws-amplify';
library.add(faLink)
/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
Amplify.configure(AWS_CONFIG);
const theme = createMuiTheme({
typography: { useNextVariants: true },
palette: {
primary: {
light: '#FDCF2A',
main: '#fe9e18',
dark: '#CC690B',
contrastText: '#ffffff',
}
},
});
const styles = theme => ({
root: {
display: 'flex',
flex: 1,
justifyContent: 'center',
padding: 20,
maxWidth: 1000,
margin: '0 auto',
flexDirection: 'column'
},
headerRoot: {
position: 'sticky',
top: 0,
paddingBottom: 3,
zIndex: theme.zIndex.appBar,
backgroundColor: theme.palette.background.default
}
})
class App extends Component {
async componentDidMount() {
const isAdmin = await API...
this.props.dispatch(setAdminUser(isAdmin));
const userMeta = await API...;
if(!userMeta) {
this.props.dispatch(setUserMetaLoaded(true));
this.props.dispatch(setPremiumUser(false));
return;
}
if(userMeta.hasOwnProperty('SubEnd')) {
const now = Math.floor(Date.now() / 1000);
if(userMeta['SubEnd'] > now) {
this.props.dispatch(setPremiumUser(true));
} else {
this.props.dispatch(setPremiumUser(false));
}
}
this.props.dispatch(setUserMetaLoaded(true));
}
render() {
const {classes} = this.props;
if (this.props.authState !== "signedIn") {
return null;
}
return (
<MuiThemeProvider theme={theme}>
<MuiPickersUtilsProvider utils={MomentUtils}>
<Router>
<CssBaseline />
<div className={classes.headerRoot}>
<Header />
</div>
<div className={classes.root}>
<MyRouter/>
</div>
</Router>
</MuiPickersUtilsProvider>
</MuiThemeProvider>
);
}
}
export default connect()(
withStyles(styles)(App)
);
appWithAuth.js
import React from "react";
import { SignIn, SignUp, forgotPassword, Greetings } from "aws-amplify-react";
import { default as CustomSignIn } from "./components/login";
import App from "./App";
import { Authenticator } from "aws-amplify-react/dist/Auth";
/*global AWS_CONFIG */
/*eslint no-undef: "error"*/
const config = AWS_CONFIG;
class AppWithAuth extends React.Component {
render() {
return (
<div>
<Authenticator hide={[SignIn, SignUp, forgotPassword, Greetings]} amplifyConfig={config}>
<CustomSignIn />
<App />
</Authenticator>
</div>
);
}
}
export default AppWithAuth;
The component to override the default login page from amplify
components/login/index.js
import React from "react";
import Button from '#material-ui/core/Button';
import CssBaseline from '#material-ui/core/CssBaseline';
import TextField from '#material-ui/core/TextField';
import Link from '#material-ui/core/Link';
import Grid from '#material-ui/core/Grid';
import Typography from '#material-ui/core/Typography';
import { withStyles } from '#material-ui/core/styles';
import Container from '#material-ui/core/Container';
import { orange } from '#material-ui/core/colors';
import { SignIn } from "aws-amplify-react";
import ButtonAuth0 from "./../buttons/auth0"
const styles = theme => ({
'#global': {
body: {
backgroundColor: theme.palette.common.white,
},
},
paper: {
marginTop: theme.spacing(8),
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
form: {
width: '100%', // Fix IE 11 issue.
marginTop: theme.spacing(1)
},
cognito: {
margin: theme.spacing(3, 0, 2),
color: theme.palette.getContrastText(orange[600]),
backgroundColor: orange[600],
'&:hover': {
backgroundColor: orange[700]
}
}
});
class CustomSignIn extends SignIn {
constructor(props) {
super(props);
this._validAuthStates = ["signIn", "signedOut", "signedUp"];
}
showComponent(theme) {
const {classes} = this.props;
return (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<ButtonAuth0 />
<Typography component="h1" variant="h5">
Sign in
</Typography>
<form className={classes.form} noValidate>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="username"
label="Username"
name="username"
autoFocus
onChange={this.handleInputChange}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
onChange={this.handleInputChange}
/>
<Button
type="submit"
fullWidth
variant="contained"
color="inherit"
className={classes.cognito}
onClick={(event) => super.signIn(event)}
>
Sign In With Cognito
</Button>
<Grid container>
<Grid item xs>
<Link href="#" variant="body2" onClick={() => super.changeState("forgotPassword")}>
Reset password?
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2" onClick={() => super.changeState("signUp")}>
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</form>
</div>
</Container>
);
}
}
export default (withStyles(styles)(CustomSignIn));
The button component:
components/buttons/auht0.js
import React from 'react';
import { withAuth0 } from 'aws-amplify-react';
import Button from '#material-ui/core/Button';
import { red } from '#material-ui/core/colors';
import { withStyles } from '#material-ui/core';
import { Auth } from 'aws-amplify';
const auth0 = {
"domain": "<...>",
"clientID": "<...>",
"redirectUri": "http://localhost:3000",
"audience": "",
"responseType": "token id_token", // for now we only support implicit grant flow
"scope": "openid profile email", // the scope used by your app
"returnTo": "http://localhost:3000"
};
Auth.configure({
auth0
});
const styles = theme => ({
auth0: {
margin: theme.spacing(3, 0, 2),
color: theme.palette.getContrastText(red[700]),
backgroundColor: red[700],
'&:hover': {
backgroundColor: red[800]
}
}
});
const Auth0Button = (props) => (
<div>
<Button
fullWidth
variant="contained"
color="inherit"
className={props.classes.auth0}
onClick={props.auth0SignIn}
>
Sign In With auth0
</Button>
</div>
)
export default withAuth0(withStyles(styles)(Auth0Button));
Is this supposed to work on its own or do steps 1 to 4 of the docs in the link above still need to be followed?
Answering my own question here. Strangely I had somehow not tried this.
Looking at the source code, I tried passing the auth0 configuration object to the withAuth0 initialisation and got rid of that error message.
export default withAuth0(withStyles(styles)(Auth0Button), auth0);

Props and Templates

I'm just creating my first custom component, and I'm really struggling with the basics. My component:
<template>
<StackLayout>
<Label :text="title" />
<Label :text="slate.description" />
</StackLayout>
</template>
<script>
var slate;
export default {
name: "SlateComponent",
props:
['slate', 'title'],
data() {
return {
slate: slate,
};
},
}
</script>
This component is to be updated regularly, and occupy a good chunk of the app home page:
<template>
<Page class="Page" actionBarHidden="true" backgroundSpanUnderStatusBar="true" >
<StackLayout>
<StackLayout row="0">
...
</StackLayout>
<StackLayout row="1">
<SlateComponent :title="title" :slate="slate" />
</StackLayout>
...
</Page>
</template>
<script>
...
import SlateComponent from "./SlateComponent";
var slateTitle;
var title;
var gameSlates;
var currentSlate;
var slate;
data() {
return {
events: events,
title: title,
slate: slate,
};
},
async created() {
this.gameSlates = await getGameSlates();
this.currentSlate = this.gameSlates[2];
this.title = this.currentSlate.description;
console.info("The title is: " + this.title);
this.slate = this.currentSlate;
}
};
Result: No matter what I do, no props object passes to the component.
If I comment out the
the app compiles and runs fine, logs currentSlate or its property, description and displays the component, including title.
But, when I include that line, it blows up, with the error: slate is undefined.
(I know that
props:
['slate', 'title'],
is not proper according to the style guide. But I couldn't get the preferred format to work either.)
What am I missing here?
When accessing props anywhere outside of the template, this is required
data() {
return {
slate: slate,
};
}
Should be
data() {
return {
slate: this.slate
};
},

How to hide native android keyboard in IONIC 2 when clicking on a text box?

How to hide native android keyboard when clicking on text box using IONIC 2? I have installed IONIC keyboard plugin from https://ionicframework.com/docs/native/keyboard/ link and uses this.keyboard.close();
But still keyboard is opening. Help me how to close the keyboard. I am basically showing DOB from the datepicker plugin in a TEXTBOXenter image description here.
This is the ts file(datepicker1.ts)
import { Component } from '#angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { DatePicker } from '#ionic-native/date-picker';
import { Keyboard } from '#ionic-native/keyboard';
#IonicPage()
#Component({
selector: 'page-datepicker1',
templateUrl: 'datepicker1.html',
})
export class Datepicker1Page {
public today:any;
constructor(public navCtrl: NavController, public navParams: NavParams,private datePicker: DatePicker,private keyboard: Keyboard) {
}
ionViewDidLoad() {
console.log('ionViewDidLoad Datepicker1Page');
}
openDatepicker()
{
this.keyboard.close();
this.datePicker.show({
date: new Date(),
mode: 'date',
androidTheme: this.datePicker.ANDROID_THEMES.THEME_DEVICE_DEFAULT_LIGHT
}).then(
date => {
this.today=date.getDate()+'/'+date.getMonth()+'/'+date.getFullYear()},
err => console.log('Error occurred while getting date: ', err)
);
}
}
And this is the datepicker1.html page
<ion-header>
<ion-navbar>
<ion-title>datepicker page</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-item>
<ion-label>DOB</ion-label>
<ion-input type="text" name="DOB" (click)="openDatepicker()" [(ngModel)]="today" ng-readonly></ion-input>
</ion-item>
</ion-content>
You have missed to declare the today variable in the class and you missed to add disabled="true" in ion-input tag. Everything is working fine and I have tested it.
TS File
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { Keyboard } from '#ionic-native/keyboard';
import { DatePicker } from '#ionic-native/date-picker';
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController, public keyboard : Keyboard, public datePicker : DatePicker) {
}
today : any;
openDatepicker(){
this.keyboard.close();
this.datePicker.show({
date: new Date(),
mode: 'date',
androidTheme: this.datePicker.ANDROID_THEMES.THEME_DEVICE_DEFAULT_LIGHT
}).then(
date => {
this.today=date.getDate()+'/'+date.getMonth()+'/'+date.getFullYear()},
err => console.log('Error occurred while getting date: ', err)
);
}
}
HTML File
<ion-header>
<ion-navbar>
<ion-title>
Ionic Blank
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-item>
<ion-label>DOB</ion-label>
<ion-input disabled="true" type="text" name="DOB" (click)="openDatepicker()" [(ngModel)]="today" ng-readonly></ion-input>
</ion-item>
</ion-content>
1) import { DatePicker } from '#ionic-native/date-picker/ngx'; in app.module.ts and keyboard.page.ts
2) public keyboard : Keyboard, in your constructor inject
3) https://ionicframework.com/docs/native/keyboard Take reference from this Official site
openDatepickerStart(){
setTimeout(() => {
this.keyboard.hide();
}, 100);
this.datePicker.show({
date: new Date(),
mode: 'date',
androidTheme: this.datePicker.ANDROID_THEMES.THEME_DEVICE_DEFAULT_LIGHT
}).then(
date => {
this.SelectDateModelDetails.get('StartTime').patchValue(date.getDate()+'/'+date.getMonth()+'/'+date.getFullYear())},
err => console.log('Error occurred while getting date: ', err)
);
}

Vue.js Vuex Unit test on user input, can I consider my test OK when coverage is 100%

I am currently testing one of my children components
I already tested successfully all vuex actions, mutations and getters
When I run this ChangeTitleComonent.spec.js , it passes with 100% coverage.. thanks for feedback
Do I need to write some additional tests when it's 100% covered ? or is this test bad written ?
PARENT
ShoppingListComponent.vue
<template>
<div>
<h2>{{ title }}</h2>
<add-item-component :id='id' #add="addItem"></add-item-component>
<items-component :items="items" :id="id"></items-component>
<div class="footer">
<hr />
<change-title-component :title="title" :id="id"></change-title-component>
</div>
</div>
</template>
<script>
import AddItemComponent from './AddItemComponent'
import ItemsComponent from './ItemsComponent'
import ChangeTitleComponent from './ChangeTitleComponent'
export default {
components: {
AddItemComponent,
ItemsComponent,
ChangeTitleComponent
},
props: ['id', 'title', 'items'],
methods: {
addItem (text) {
this.items.push({
text: text,
checked: false
})
}
}
}
</script>
<style scoped>
.footer {
font-size: 0.7em;
margin-top: 20vh;
}
</style>
CHILDREN
ChangeTitleComponent
<template>
<div>
<em>Change the title of your shopping list here</em>
<input :value="title" #input="onInput({ title: $event.target.value, id: id })"/>
</div>
</template>
<style scoped>
</style>
<script>
import { mapActions } from 'vuex'
export default {
props: ['title', 'id'],
methods: mapActions({ // dispatching actions in components
onInput: 'changeTitle'
})
}
</script>
UNIT TEST
ChangeTitleComponent.spec.js
import Vue from 'vue'
import ChangeTitleComponent from '#/components/ChangeTitleComponent'
import store from '#/vuex/store'
describe('ChangeTitleComponent.vue', () => {
describe('changeTitle', () => {
var component
beforeEach(() => {
var vm = new Vue({
template: '<change-title-component :title="title" :id="id" ref="changetitlecomponent">' +
'</change-title-component></div>',
components: {
ChangeTitleComponent
},
props: ['title', 'id'],
store
}).$mount()
component = vm.$refs.changetitlecomponent
})
it('should change the title', () => {
// check component label text
expect(component.$el.textContent).to.equal('Change the title of your shopping list here ')
// simulate input Enter event
const input = component.$el.querySelector('input')
input.value = 'My New Title'
const enterEvent = new window.Event('keypress', { which: 13 })
input.dispatchEvent(enterEvent)
component._watcher.run()
})
})
})