Related
I added the zoom and pan functionality to a graph.
This works in Chrome and Safari. But when embedded in a SwiftUI WKWebview the entire graph zooms as if it is an image. I do not get the proper axis scaling.
I basically followed this tutorial to add the zoom. https://www.youtube.com/watch?v=QUzVVPK1Nks&ab_channel=ChartJS
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.min.js" integrity="sha512-UXumZrZNiOwnTcZSHLOfcTs0aos2MzBWHXOHOuB0J/R44QB0dwY5JgfbvljXcklVf65Gc4El6RjZ+lnwd2az2g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-zoom/1.2.1/chartjs-plugin-zoom.min.js" integrity="sha512-klQv6lz2YR+MecyFYMFRuU2eAl8IPRo6zHnsc9n142TJuJHS8CG0ix4Oq9na9ceeg1u5EkBfZsFcV3U7J51iew==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<div id="container" style="position: relative; height:95vh; width:95vw">
<canvas id="myCanvas"></canvas>
</div>
<script>
//var incomingData = __#data__;
var ctx = document.getElementById("myCanvas").getContext('2d');
var gradient = ctx.createLinearGradient(0, 0, 0, 500);
gradient.addColorStop(0, 'rgba(158,200,62,1)');
gradient.addColorStop(1, 'rgba(188,190,50,1)');
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["5:30 PM","5:45 PM","6:00 PM","6:15 PM","6:30 PM","6:45 PM","7:00 PM","7:15 PM","7:30 PM","7:45 PM","8:00 PM","8:15 PM","8:30 PM","8:45 PM","9:00 PM","9:15 PM","9:30 PM","9:45 PM","10:00 PM","10:15 PM","10:30 PM","10:45 PM","11:00 PM","11:15 PM","11:30 PM","11:45 PM","12:00 AM","12:00 AM","12:15 AM","12:30 AM","12:45 AM","1:00 AM","1:15 AM","1:30 AM","1:45 AM","2:00 AM","2:15 AM","2:30 AM","2:45 AM","3:00 AM","3:15 AM","3:30 AM","3:45 AM","4:00 AM","4:15 AM","4:30 AM","4:45 AM","5:00 AM","5:15 AM","5:30 AM","5:45 AM","6:00 AM","6:15 AM","6:30 AM","6:45 AM","7:00 AM","7:15 AM","7:30 AM","7:45 AM","8:00 AM","8:15 AM","8:30 AM","8:45 AM","9:00 AM","9:15 AM","9:30 AM","9:45 AM","10:00 AM","10:15 AM","10:30 AM","10:45 AM","11:00 AM","11:15 AM","11:30 AM","11:45 AM","12:00 PM","12:15 PM","12:30 PM","12:45 PM","1:00 PM","1:15 PM","1:30 PM","1:45 PM","2:00 PM","2:15 PM","2:30 PM","2:45 PM","3:00 PM","3:15 PM","3:30 PM","3:45 PM","4:00 PM","4:15 PM","4:30 PM","4:45 PM","5:00 PM"],
datasets: [{
backgroundColor : gradient, // Put the gradient here as a fill color
label: 'Temp',
data: [21.62,21.54,21.66,21.39,23,22.31,21.18,21.12,28.18,22.1633333333333333,20.66,20.18,19.7266666666666667,19.35,32.04,30.9533333333333333,33.18,31.9733333333333333,43.035,32.6225,31.7033333333333333,48.32,33.58,33.64,22.37,20.9,20.56,20.56,20.43,20.39,20.25,20,19.87,19.68,19.62,19.5,19.355,19.18,19.06,19,18.87,18.79,18.68,18.68,18.62,18.56,18.5,18.385,18.25,18.12,18,17.91,17.81,17.75,17.62,17.5,17.5,17.5,17.5,18.43,18.68,18.85,19.12,19.25,19.31,19.37,19.43,NaN,NaN,NaN,NaN,19.6,19.68,19.81,19.87,19.9533333333333333,20,NaN,NaN,NaN,NaN,NaN,NaN,NaN,NaN,0,NaN,NaN,NaN,NaN,NaN,NaN,NaN,22.18,22.37,22.31],
borderColor: '#9ec83e',
borderWidth: '1',
fill: true,
}]
},
options: {
plugins: {
legend:{
display: false,
},
zoom: {
limits: {
x: {min: 0, max: 400, minRange: 50},
y: {min: 0, max: 200, minRange: 50}
},
pan: {
enabled: true,
mode: 'xy',
},
zoom: {
wheel: {
enabled: true,
},
pinch: {
enabled: true
},
mode: 'xy',
}
}
},
maintainAspectRatio: false,
animation: {
duration: 0, // general animation time
},
elements: {
point: {
radius: 0
}
},
scales: {
x: {
grid: {
display:false
}
},
y: {
grid: {
display:false
},
ticks: {
autoSkip: true,
maxTicksLimit: 4
}
}
},
}
});
Chart.register(zoomPlugin);
</script>
</body>
</html>
I have tried the following in Swift
wkWebview.scrollView.delegate = self
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return nil
}
I coded an app that will create a modal that gives the option to alert reception that a customer is coming. However, when running the app on two phones, all changes are local. Why is that? I want to be able to "Alert Reception" and have reception receive a notification of the customer and be able to click More Info and see all the details about it.
We scan a customer QR code, and allow the security guard to validate and then alert reception and send the info to them.
Any ideas? We are using AWS and React Native. I would like to alert everyone and then be able to have the reception see the customer is coming and get the info.
import { StyleSheet, Text, View, Image, Button, Modal, Platform, ScrollView } from 'react-native';
import { BarCodeScanner } from 'expo-barcode-scanner';
import Constants from 'expo-constants';
import * as Notifications from 'expo-notifications';
import { Audio, Video } from 'expo-av';
//For push notifications
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
export default function App() {
//QR Scanner
const [hasPermission, setHasPermission] = useState(null);
const [scanned, setScanned] = useState(false);
const [newData, setNewData] = useState('');
//Modal Views
const [homeScreenVisible, setHomeScreenVisible] = useState(true);
const [customerModalVisible, setCustomerModalVisible] = useState(false);
const [customerModalDetailedVisible, setCustomerModalDetailedVisible] = useState(false);
const [qrScannerVisible, setQRScannerVisible] = useState(false);
//Push notifications
const [expoPushToken, setExpoPushToken] = useState('');
const [notification, setNotification] = useState(false);
const notificationListener = useRef();
const responseListener = useRef();
//useEffects are rendered upon app start. We use the [] to make sure they are only rendered once. If you want them to update, add in the array / state var and upon update the useEffect will run again.
//For Bar Code Scanner
useEffect(() => {
(async () => {
const { status } = await BarCodeScanner.requestPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
//For Push Notifications
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
// This listener is fired whenever a notification is received while the app is foregrounded
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
console.log(response);
console.log('Someone clicked on the push notification')
});
return () => {
Notifications.removeNotificationSubscription(notificationListener.current);
Notifications.removeNotificationSubscription(responseListener.current);
};
}, []);
//Bar Code scanner function, reads the data (which is a aws s3 endpoint served through cloudfront), then I run a fetch on the url (aka data), and then turn the string response into a json. Then I set newData equal to the JSON object via React Hooks. Then I turn the QR scanner visible off, and then open the customer modal.
const handleBarCodeScanned = ({ type, data }) => {
setScanned(true);
console.log(data) // Should be the url JSON endpoint
fetch(data)
.then(response => response.json())
.then(response => {
console.log(response)
setNewData(response)
});
setQRScannerVisible(false);
setCustomerModalVisible(true);
// const playbackObject = new Audio.Sound();
};
if (hasPermission === null) {
return <Text>Requesting for camera permission</Text>;
}
if (hasPermission === false) {
return <Text>No access to camera</Text>;
}
// We are returning a View with multiple JS {} fragments that turn off and on depending on state. There is an issue with setScanned(false) that sometimes the scanner is not set to false and it will not rescan. If you run into that problem, try adding setScanned(false).
return (
<ScrollView style={{flex: 1, backgroundColor: 'black'}}>
<View id="view" style={styles.container}>
{homeScreenVisible &&
<>
<Image
source={{
uri: "https://d1s68zh8fdz4eb.cloudfront.net/logo.png"
}}
style={{
height: 500,
width: '100%',
// borderWidth: 5,
// borderColor: '#fff',
marginTop: 30,
marginBottom: 20
}}
/>
<Button title="Scan QR Code" style={styles.moreInfo} onPress={() => {
setScanned(false)
setHomeScreenVisible(false)
setQRScannerVisible(true)
}} />
</>
}
{qrScannerVisible &&
<>
<Image
source={{
uri: "https://d1s68zh8fdz4eb.cloudfront.net/logo.png"
}}
style={{
height: 100,
width: 100,
borderWidth: 5,
marginBottom: 60
}}
/>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
// style={StyleSheet.absoluteFillObject}
style={{width: 300, height: 300, borderWidth: 5, borderColor: '#fff'}}
//If you want to switch to a absolute fill object, add the target area on the screen for scanning and try to add a sound.
// If you want to use the front facing or rear facing, include type={'front'} or put 'back'
/>
<Text style={{color: '#fff', fontSize: 30, marginTop: 30, marginBottom: 30}}>Scan QR Code</Text>
<Button title="Home" onPress={() => {
setHomeScreenVisible(true)
setQRScannerVisible(false)
setScanned(false)
}} />
</>
}
{/* Used for re scanning things. It is commented out because someoen can always go back to home and rescan. However, if you want to reintroduce a rescan button, you can do it with the below code.*/}
{/* {scanned && <Button title={'Tap to Scan Again'} onPress={() => {
setScanned(false)
}} />}
*/}
{customerModalVisible &&
<Modal style={styles.modal}>
<View style={styles.modalView}>
<Image
source={{
uri: "https://d1s68zh8fdz4eb.cloudfront.net/logo.png"
}}
style={{
height: 100,
width: 100,
borderWidth: 5,
marginBottom: 60
}}
/>
<View style={styles.modalInside}>
<Image
style={{
height: 300,
width: 300,
marginBottom: 20,
borderWidth: 5,
borderColor: 'gold'
}}
source={{
uri: newData.photo,
}}
/>
<Text style={styles.modalTextName}>{newData.name}</Text>
<Text style={styles.modalTextSecondary}>Favorite Drink: {newData.favoriteDrink}</Text>
<Text style={styles.modalTextSecondary}>Lead Contact: Leah</Text>
</View>
<Button title="Alert Reception" style={styles.closeButton} onPress={async () => {
await sendPushNotification(expoPushToken);
alert('Reception has been notified.')
}} />
<Button title="More Info" style={styles.moreInfo} onPress={() => {
// PushCustomerStatus();
setCustomerModalDetailedVisible(true)
setCustomerModalVisible(false)
setScanned(false)
setQRScannerVisible(false)
// Add modal # 2 or page navigation here
}} />
<Button title="Close" style={styles.closeButton} onPress={() => {
setHomeScreenVisible(true)
setCustomerModalVisible(false)
setScanned(false)
setQRScannerVisible(false)
}} />
</View>
</Modal>}
{customerModalDetailedVisible &&
<>
<Image
source={{
uri: "https://d1s68zh8fdz4eb.cloudfront.net/logo.png"
}}
style={{
height: 100,
width: 100,
// borderWidth: 5,
// borderColor: '#fff'
}}
/>
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'space-around',
width: '95%',
borderWidth: 5,
borderColor: 'gold',
}}>
{/* <Text>Your expo push token: {expoPushToken}</Text> */}
<View style={{ alignItems: 'flex-start', justifyContent: 'center', backgroundColor: '#fff', flex: 1, width: '100%', padding: 15}}>
<Image
style={{
height: 300,
width: 300,
marginBottom: 20,
// borderWidth: 5,
// borderColor: 'gold'
}}
source={{
uri: newData.photo,
}}
/>
<Text>Customer: {newData.name}</Text>
<Text>Arm Length: {newData.armLength}</Text>
<Text>Address: {newData.address}</Text>
<Text>Phone: {newData.phone}</Text>
<Text>Email: {newData.email}</Text>
<Text>Favorite Drink: {newData.favoriteDrink}</Text>
<Text>Height: {newData.height}</Text>
<Text>Inseam: {newData.inseam}</Text>
<Text>Neck Length: {newData.neckLength}</Text>
<Text>Customer: {newData.name}</Text>
<Text>Arm Length: {newData.armLength}</Text>
<Text>Address: {newData.address}</Text>
<Text>Phone: {newData.phone}</Text>
<Text>Email: {newData.email}</Text>
<Text>Favorite Drink: {newData.favoriteDrink}</Text>
<Text>Height: {newData.height}</Text>
<Text>Inseam: {newData.inseam}</Text>
<Text>Neck Length: {newData.neckLength}</Text>
<Text>Customer: {newData.name}</Text>
<Text>Arm Length: {newData.armLength}</Text>
<Text>Address: {newData.address}</Text>
<Text>Phone: {newData.phone}</Text>
<Text>Email: {newData.email}</Text>
<Text>Favorite Drink: {newData.favoriteDrink}</Text>
<Text>Height: {newData.height}</Text>
<Text>Inseam: {newData.inseam}</Text>
<Text>Neck Length: {newData.neckLength}</Text>
<Text>Customer: {newData.name}</Text>
<Text>Arm Length: {newData.armLength}</Text>
<Text>Address: {newData.address}</Text>
<Text>Phone: {newData.phone}</Text>
<Text>Email: {newData.email}</Text>
<Text>Favorite Drink: {newData.favoriteDrink}</Text>
<Text>Height: {newData.height}</Text>
<Text>Inseam: {newData.inseam}</Text>
<Text>Neck Length: {newData.neckLength}</Text>
{/* I can return it as an object above and use dot notation or do something similar below but use a string */}
{/* <Text>Data: {notification && JSON.stringify(notification.request.content.data)}</Text> */}
<Button
title="Press to Send Notification"
onPress={async () => {
await sendPushNotification(expoPushToken);
}}
/>
<Button
title="Home"
onPress={() => {
setHomeScreenVisible(true)
setScanned(false)
setCustomerModalDetailedVisible(false)
setQRScannerVisible(false)
}}
/>
</View>
</View>
</>
}
</View>
</ScrollView>
);
}
// Can use this function below, OR use Expo's Push Notification Tool-> https://expo.io/notifications
async function sendPushNotification(expoPushToken) {
const message = {
to: expoPushToken,
sound: 'default',
title: 'Customer Arriving!',
body: 'Please greet them at the door.',
};
await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
}
async function registerForPushNotificationsAsync() {
let token;
if (Constants.isDevice) {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!');
return;
}
token = (await Notifications.getExpoPushTokenAsync()).data;
console.log(token);
} else {
alert('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
return token;
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'center',
backgroundColor: 'black',
paddingTop: 60,
},
modal: {
// marginTop: 100,
},
modalView: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black'
},
modalInside: {
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 20
},
modalTextName: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 5,
color: 'white'
},
modalTextSecondary: {
color: '#fff',
marginBottom: 5,
},
closeButton: {
marginTop: 100,
},
moreInfo: {
marginTop: 100,
}
});
By live alerts, I assume you mean text notifications. One way to push out text notifications using AWS is using the Simple Notification Service. Because you are using a React app, use the AWS SDK for JavaScript. Using the SDK, you can create app logic to fire off text notifications in response to certain events.
You can find examples here:
https://github.com/awsdocs/aws-doc-sdk-examples/tree/master/javascriptv3/example_code/sns/src
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 use HighChart library in the Segment, my segment have 2 tab DASHBOARD and NEW, my Chart in the DASHBOARD tab. First run: My Chart run, but i click to New tab and come back DASHBOARD tab => My chart not run ?
[sorry, i'm not good english]
-- My code html:
<div class="segment-chart">
<ion-segment [(ngModel)]="pet">
<ion-segment-button value="dashboard" (ionSelect)="selectedFriends()">
DASHBOARD
</ion-segment-button>
<ion-segment-button value="new">
NEW
</ion-segment-button>
</ion-segment>
</div>
<div [ngSwitch]="pet">
<div class="chart" *ngSwitchCase="'dashboard'">
<!--View Chart-->
<div #chart>
<chart type="StockChart" [options]="options"></chart>
</div>
</div>
<ul *ngSwitchCase="'new'" style="list-style-type:none" class="div-new-body">
<li class="div-new-li" *ngFor="let new of lsNews">
<div class="div-new-detail">
<div class="div-new-title">
{{new.date}}
</div>
<div class="div-new-content">
{{new.title}}
</div>
</div>
<div class="div-new-nav">></div>
</li>
</ul>
</div>
My code file ts:
export class ChartPage implements AfterViewInit, OnDestroy {
private _chart: any;
lastData: any
lstData: any = []
pet : any
lsNews : any = []
opts : any;
#ViewChild('chart') public chartEl: ElementRef;
//chartOption: any
// Destroy Chart
ngOnDestroy(): void {
// throw new Error("Method not implemented.");
console.log("OnDestroy run")
var chart = this._chart
chart.destroy();
}
// option Chart
ngAfterViewInit() {
if (this.chartEl && this.chartEl.nativeElement) {
this.opts.chart = {
// type: 'area',
renderTo: this.chartEl.nativeElement,
backgroundColor: {
linearGradient: [0, 0, 500, 500],
stops: [
[0, '#3d4d64'],
[1, '#3d4d64']
]
},
height: '90%',
spacingBottom: 15,
spacingTop: 10,
spacingLeft: 10,
spacingRight: 10,
};
console.log('chart create ss')
this._chart = new Highcharts.StockChart(this.opts);
}
}
constructor(public navCtrl: NavController, public navParams: NavParams, public service: Service) {
const me = this;
this.pet= 'dashboard';
setInterval(function () {
if (me._chart) {
me._chart['series'][0].addPoint([
(new Date()).getTime(), // gia tri truc x
//5// gia tri truc y
me.getData()
],
true,
true);
}
}, 3000);
this.opts = {
credits: {
enabled: false
},
xAxis: {
type: 'datetime',
tickPixelInterval: 150,
labels: {
style: {
color: '#705f43' // color time
}
},
lineColor: '#705f43' // 2 line cuoi mau trang
},
yAxis: {
gridLineColor: '#705f43', //line gach ngang
labels: {
style: {
color: '#fff'
}
},
lineColor: '#ff0000',
minorGridLineColor: '#ff0000',
tickColor: '#fff',
tickWidth: 1,
title: {
style: {
color: '#ff0000'
}
}
},
navigator: {
enabled: false
},
rangeSelector: {
buttons: [{
count: 1,
type: 'minute',
text: '1M'
}, {
count: 5,
type: 'minute',
text: '5M'
}, {
type: 'all',
text: 'All'
}],
inputEnabled: false,
selected: 0,
},
series: [{
name: 'Random data',
data: (function () {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -50; i <= 0; i += 1) {
data.push([
time + i * 1000,
Math.round(Math.random() * 100)
]);
}
return data;
}()),
zones: [{
color: '#f8ad40'
}]
}]
};
//end contructor
}
In case you have not been able to resolve this issue, I had the same issue using ion-segment and I was able to resolve it when I replaced ngSwitch with the [hidden] property.
The problem is that the canvas only get rendered once. The canvas is also lost once you switch between your segments, the only solution is to hide you segment when switching between segments
Edit your HTML code to the one below and you should be okay
<div class="segment-chart">
<ion-segment [(ngModel)]="pet">
<ion-segment-button value="dashboard" (ionSelect)="selectedFriends()">
DASHBOARD
</ion-segment-button>
<ion-segment-button value="new">
NEW
</ion-segment-button>
</ion-segment>
</div>
<div >
<div class="chart" [hidden] = "pet != 'dashboard'">
<!--View Chart-->
<div #chart>
<chart type="StockChart" [options]="options"></chart>
</div>
</div>
<ul [hidden] = "pet != 'new'" style="list-style-type:none" class="div-new-body">
<li class="div-new-li" *ngFor="let new of lsNews">
<div class="div-new-detail">
<div class="div-new-title">
{{new.date}}
</div>
<div class="div-new-content">
{{new.title}}
</div>
</div>
<div class="div-new-nav">></div>
</li>
</ul>
</div>
That should solve the issue.
Is there a straightforward way to enable an entire row of ng-grid to edit mode, in the click of a button. I know if you set gridOptions for enableCellEditOnFocus or enableCellEdit, you can click/doubleclick to edit a particular cell. But what I want is to have a button in each row, and when clicked on that the whole row should be editable.
The code I have right now is this, but it doesn't achieve what I want.
vm.grid.childServicesGridOptions = {
data: "vm.grid.childServices",
rowHeight: 35,
enableCellSelection: false,
enableRowSelection: true,
multiSelect: false,
columnDefs: [
{ field: 'serviceId', displayName: 'Service Id', visible: false },
{ field: 'location.locationName', displayName: 'Location Name', width: "25%" },
{ field: 'serviceDisplayName', displayName: 'Product/Service Display Name', width: "25%" },
{ field: 'duration', displayName: 'Duration', width: "10%" },
{
field: '', displayName: 'Action', width: "10%",
cellTemplate: '<button ng-click="vm.grid.editChildService(row)"><span class="glyphicon glyphicon-pencil"></span></button>'
}
],
pagingOptions: { pageSizes: [10, 20, 30], pageSize: 10, currentPage: 1 },
totalServerItems: 'vm.grid.childServices.length',
};
vm.grid.editChildService = function (row) {
row.entity.edit = !row.entity.edit;
}
It seems like there's no straightforward way to do this, I had to add a cell template and set it to edit in the [Edit] button click. Following is what I did:
vm.holidayProperties.childHolidayGridOptions = {
data: "holidayProperties.selectedHoliday.childHolidays",
rowHeight: 35,
enableCellSelection: false,
enableRowSelection: true,
multiSelect: false,
columnDefs: [
{ field: 'holidayId', displayName: localize.getLocalizedString('_HolidayId_'), visible: false },
{ field: 'location.locationName', displayName: localize.getLocalizedString('_LocationName_'), width: "15%" },
{ field: 'holidayName', displayName: localize.getLocalizedString('_Holidayname_'), width: "15%" },
{
field: 'isAllDay', displayName: localize.getLocalizedString('_IsAllday_'), width: "10%",
cellTemplate: '<input type="checkbox" ng-model="row.entity.isAllDay" ng-change="holidayProperties.setEndDateDisabled()" ng-disabled="!row.editable">'
},
{
field: '', displayName: localize.getLocalizedString('_Action_'), width: "10%",
cellTemplate: '<button ng-show="!row.editable" ng-click="holidayProperties.setRowEditable(row)"><span class="glyphicon glyphicon-pencil"></span></button>' +
'<button ng-show="row.editable" ng-click="holidayProperties.reset(row)"><span class="glyphicon glyphicon-arrow-left"></span></button>'
}
],
enablePaging: true,
showFooter: true,
showFilter: true,
pagingOptions: { pageSizes: [10, 20, 30], pageSize: 10, currentPage: 1 },
totalServerItems: 'holidayProperties.selectedHoliday.childHolidays.length',
};
vm.holidayProperties.setRowEditable = function (row) {
row.editable = true;
}
vm.holidayProperties.reset = function (row) {
clientcontext.rejectChangesForEntity(row.entity);
row.editable = false;
}
Using the row.editable field, I set the disabled property of the field to be edited to true or false.