Lets say I already build a component with google chart in ReactJS, and I want to make the Legend to show/hide data by using a js script shown here.
How and where should I put this piece of code to react with my chart?
My component look something like this:
PlayerCountStat
//Skipping all the imports and useEffect code...
return (
<Chart
chartType="LineChart"
width="100%"
height="400px"
data={dateData}
options={options}
chartPackages={["corechart", "controls"]}
controls={[
{
controlType: "ChartRangeFilter",
options: {
filterColumnIndex: 0,
ui: {
chartType: "LineChart",
chartOptions: {
chartArea: { width: "90%", height: "50%" },
hAxis: { baselineColor: "none" },
},
},
},
controlPosition: "bottom",
controlWrapperParams: {
state: {
range: {
start: {startFilterDate},
end: {endFilterDate}
},
},
},
},
]}
/>
);
}
export default PlayerCountStat
And the Routing is like this
function App() {
return (
<BrowserRouter>
<Route path="/PlayerCountStat">
<Navbar/>
<Page content={PlayerCountStat}/>
</Route>
</Switch>
</BrowserRouter>
);
}
export default App;
Assuming you're using React Google Charts, there is a prop called chartEvents
<Chart
chartType="LineChart"
width="100%"
height="400px"
data={data}
options={options}
chartEvents={[
{
eventName: 'select' // Change this to applicable event,
callback: ({ chartWrapper }) => {
// Add your logic
}
}
]}
/>
I found this codepen that might be what you're looking to accomplish: https://codesandbox.io/s/d9drj?file=/index.js
Related
I'm trying to overwrite the default behaviour of 'noOptionsMessage' (default for noOptionsMessage: noOptionsMessage?: (obj: { inputValue: string }) => string | null;) of react-select using the components prop.
This is what it is stated in the code for react-select:
/*
This complex object includes all the compositional components that are used
in `react-select`. If you wish to overwrite a component, pass in an object
with the appropriate namespace.
*/
components?: SelectComponentsConfig<OptionType>;
This is what I currently have:
const components: Props['components'] = {
LoadingIndicator() {
return null;
},
DropdownIndicator() {
return null;
},
IndicatorSeparator() {
return null;
},
ClearIndicator(props) {
return (
<reactSelectComponents.ClearIndicator {...props}>
<XIcon size="small" />
</reactSelectComponents.ClearIndicator>
);
},
Input(props) {
return (
<reactSelectComponents.Input
{...props}
aria-describedby={(props as any).selectProps['aria-describedby']}
/>
);
},
LoadingMessage() {
return (
<Flex justify="center" marginY="xlarge">
<LoadingDots label="Loading options" />
</Flex>
);
},
ValueContainer(props) {
return (
<reactSelectComponents.ValueContainer {...props}>
<span
css={{
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
width: 40,
}}
>
<SearchIcon color="dim" size="small" />
</span>
{props.children}
</reactSelectComponents.ValueContainer>
);
},
};
export function Autocomplete<Option extends BaseOption>(
props: AutocompleteProps<Option
) {
return (
<Select
components={components}
/>
);
}
I tried moving "const components: Props['components']" inside my Autocomplete component so I have access to the props. But this isn't working as expected. Any thoughts on how I can achieve this??
Currently I have a simple material-table like this:
<MaterialTable
options={myOptions}
title="MyTitle"
columns={state.columns}
data={state.data}
tableRef={tableRef} // Not working
editable={{
onRowAdd: ...,
onRowDelete: ...,
onRowUpdate: ...
}}
/>;
where I'm trying to a create new add button (not edit the current one): each Row in the Bar Column should have a custom add button. I've looked through the MaterialTable source code but I couldn't reproduce the code that is used for the default add button which is:
calculatedProps.actions.push({
icon: calculatedProps.icons.Add,
tooltip: localization.addTooltip,
position: "toolbar",
disabled: !!this.dataManager.lastEditingRow,
onClick: () => {
this.dataManager.changeRowEditing();
this.setState({
...this.dataManager.getRenderState(),
showAddRow: !this.state.showAddRow,
});
},
});
in particular I can't get to access the dataManager variable.
That is how the current table looks like, and I need to add the add button where there is the red sign.
I think this is what you are looking for:
The Actions column represents the default actions set. I added an specific button using custom column rendering (docs):
//..previous columns definition
{
title: "Custom Add",
field: "internal_action",
editable: false,
render: (rowData) =>
rowData && (
<IconButton
color="secondary"
onClick={() => addActionRef.current.click()}
>
<AddIcon />
</IconButton>
)
}
*Using rowData as conditional, prevents from rendering while filling the addition row.
Then I triggered the add action as shown here:
const MyComponent() {
const addActionRef = React.useRef();
return (
<>
<button onClick={() => addActionRef.current.click()}>
Add new item
</button>
<MaterialTable
//...
components={{
Action: props => {
//If isn't the add action
if (typeof props.action === typeof Function || props.action.tooltip !== 'Add') {
return <MTableAction {...props} />
} else {
return <div ref={addActionRef} onClick={props.action.onClick}/>;
}}
}}
editable={{
onRowAdd: (newData, oldData) => Promise.resolve(); //your callback here
}}
/>
</>
);
}
I extended the original snippet in order to complete the addition cycle. If you need to handle different types of actions, I think Editable section from the oficial docs would be handy.
Hope this works for you! Full code and sandbox here:
import React, { Fragment, useState } from "react";
import MaterialTable, { MTableAction } from "material-table";
import AddIcon from "#material-ui/icons/AddAlarm";
import IconButton from "#material-ui/core/IconButton";
export default function CustomEditComponent(props) {
const tableRef = React.createRef();
const addActionRef = React.useRef();
const tableColumns = [
{ title: "Client", field: "client" },
{ title: "Name", field: "name" },
{ title: "Year", field: "year" },
{
title: "Custom Add",
field: "internal_action",
editable: false,
render: (rowData) =>
rowData && (
<IconButton
color="secondary"
onClick={() => addActionRef.current.click()}
>
<AddIcon />
</IconButton>
)
}
];
const [tableData, setTableData] = useState([
{
client: "client1",
name: "Mary",
year: "2019"
},
{
client: "client2",
name: "Yang",
year: "2018"
},
{
client: "client3",
name: "Kal",
year: "2019"
}
]);
return (
<Fragment>
<MaterialTable
tableRef={tableRef}
columns={tableColumns}
data={tableData}
title="Custom Add Mode"
options={{
search: false
}}
components={{
Action: (props) => {
//If isn't the add action
if (
typeof props.action === typeof Function ||
props.action.tooltip !== "Add"
) {
return <MTableAction {...props} />;
} else {
return <div ref={addActionRef} onClick={props.action.onClick} />;
}
}
}}
actions={[
{
icon: "save",
tooltip: "Save User",
onClick: (event, rowData) => alert("You saved " + rowData.name)
}
]}
editable={{
onRowAdd: (newData) =>
Promise.resolve(setTableData([...tableData, newData]))
}}
/>
</Fragment>
);
Basically I'm running a Reactjs application with Highcharts using the highcharts-react-official wrapper. I'm defining a highchart Component and the goal is to generate a report page with a dozen charts with different settings and data sources. I also want to add highstock stock tools components to it. The problem I'm finding is that when I click on Indicators button in a component (first option, top left) popop is triggered in all components.
Chart Component
// (...) Imports
class CustomGUIChart extends Component {
constructor(props) {
super(props);
const { data } = this.props;
this.state = {
options: {
legend: {
enabled: true
},
tooltip: {
enabled: false
},
series: data.map((set, index) => ({
...set,
id: `series-${index}`,
type: "line",
cursor: "pointer",
marker: {
enabled: false
}
})),
stockTools: {
gui: {
enabled: true
}
}
}
};
}
render() {
const { options } = this.state;
return (
<Card>
<CardContent style={{ padding: 0 }}>
<HighchartsReact
highcharts={Highcharts}
constructorType="stockChart"
containerProps={{ className: "chart" }}
options={options}
allowChartUpdate
/>
</CardContent>
</Card>
);
}
}
CustomGUIChart.propTypes = {
data: PropTypes.array.isRequired
};
export default CustomGUIChart;
Page Content calling chart component multiple times
// (...) Imports
const chartData = mockData.map((series) => ({
...series,
data: series.data.map((point) => {
const dateArr = point[0].split('-');
return [
Date.UTC(dateArr[0], dateArr[1], dateArr[2]),
point[1] * 100
];
})
}));
function SectionContent() {
return (
<>
<Grid item xs={12} sm={12}>
<Paper>
<CustomGUIChart
data={chartData}
/>
</Paper>
</Grid>
<Grid item xs={12} sm={12}>
<Paper>
<CustomGUIChart
data={chartData}
/>
</Paper>
</Grid>
<Grid item xs={12} sm={12}>
<Paper>
<CustomGUIChart
data={chartData}
/>
</Paper>
</Grid>
</>
);
}
export default Content;
How to handle this unexpected event trigger in different components/charts?
Here is a working Demo:
You need to declare unique bindingsClassName per each chart.
navigation: {
bindingsClassName: 'chart2'
}
Live demo: https://jsfiddle.net/BlackLabel/y5wxvukr/1/
Related thread: https://github.com/highcharts/highcharts/issues/10599
AP Reference: https://api.highcharts.com/highstock/navigation.bindingsClassName
I have a React Component named Chart. I want to render different charts in it based on the prop passed from their parent, but having the exactly same config. What is the best way to do it?
class Chart extends Component {
static propTypes = {
type: PropTypes.string.isRequired
}
state = {
chartData: {
// some data here
}
}
render(){
return(
this.props.type === 'line' ?
{ <Line
data={this.state.chartData}
options={{
title: {
display: true,
text: 'Cities I\'ve lived in',
fontSize: 25
},
legend: {
display: true,
position: 'right',
labels: {
fontColor: '#000'
}
}
}}
/> } : this.props.type === 'bar' ?
{ <Bar
//same stuff here as <Line />
/>
} : this.props.type === 'pie' ?
{ <Pie
//same stuff here as <Line />
/> }
}
);
}
}
Don't think it matters since the question is generic, but I'm using react-chartjs-2 / chart.js 2 library for rendering charts.
Note: I've tried using a variable name in place of Line/Bar/Pie, but it doesn't work if I'm using JSX or rendering a non-html tag. Better ways to solve it while using JSX and non-html tags are also welcome.
Two things:
Use stateless component, in your example you are not using state.
Use switch instead of if statements.
Your new component, ...chart are the destructured props. Read more about destructuring on MDN.
// Chart.js - new component
export const Chart = ({ type, ...chart }) => {
switch(type) {
case 'line':
return <Line {...chart} />
case 'bar':
return <Bar {...chart} />
case 'pie':
return <Pie {...chart} />
default:
return null;
}
}
Example usage
// App.js
render() {
return (
<div>
<Chart type="line" options={optionsObject} whateverProp="whatever" />
<Chart type="bar" options={optionsObject} whateverProp="whatever" />
<Chart type="pie" options={optionsObject} whateverProp="whatever" />
</div>
)
}
import React, { Component } from 'react';
class Chart extends Component {
components = {
Line: Line,
Bar: Bar,
Pie: Pie
};
render() {
const TagName = this.components[this.props.tag || 'Line'];
return
<TagName
data={this.state.chartData}
options={{
title: {
display: true,
text: 'Cities I\'ve lived in',
fontSize: 25
},
legend: {
display: true,
position: 'right',
labels: {
fontColor: '#000'
}
}
}}
/>
}
}
export default Chart;
// Call MyComponent using prop tag with required prop Line or Bar or Pie
<Chart tag='Line' />
NOTE:- You could create it as a stateless functional component as well.
I prefer to have each chart as a component, so you can do chart's specific configuration in that component and your code will be more close to React codes modularity goal, so for Line chart as example:
class LineChart extends Component {
const chartLegend = {
display: true,
position: 'right',
labels: {
fontColor: '#000'
}};
const chartTitle = {
display: true,
text: 'Cities I\'ve lived in',
fontSize: 25
};
const chartOptions = {
title: chartTitle ,
legend: chartLegend
}
render(){
return(
<Line
data={this.props.chartData}
options={chartOptions }
/>
);
}
}
Now for chat Component as container component you can use switch case or guard conditions as #loelsonk also said, I prefer to have guard conditions besides switch:
class Chart extends Component {
static propTypes = {
type: PropTypes.string.isRequired
}
state = {
chartData: {
// some data here
}
}
render(){
if (this.props.type === 'line')
return <LineChart chartData={...}/>;
if (this.props.type === 'bar')
return <BarChart chartData={...}/>;
if (this.props.type === 'pie')
return <PieChart chartData={...}/>;
}
}
this way you can easily replace any chart implementation any time that needed.
I know how to do it in previous versions of React Router but I absolutely do not know how to do it in new React Router v4. Can somebody help me?
What do I want?
When you type /image/1 in browser url, page will appear as normal.
When you click on <Link>Image as modal</Link> with state modal: true, modal with image will appear BUT behind modal must be the previous content + url in browser === /image/1... Then If you press F5, page will appear as normal.
Example: instagram... etc
What do I think I'm doing wrong?
I do not know how to display the previous content. That is all I guess.
Code:
const Images = (props) => {
return (
<div>
<h2>Images</h2>
<ul>
<li><Link to={{
pathname: '/image/1',
state: {
modal: true
}
}}>Image as modal</Link></li>
<li><Link to="/image/2">Image</Link></li>
</ul>
</div>
)
}
const Image = (props) => {
return (
<div>
<h2>Image {props.match.params.id}</h2>
<ul>
<li><Link to="/images">Back to Images</Link></li>
</ul>
</div>
)
}
ReactDOM.render(
<Provider store={store}>
<Router>
<div>
<Route path='/images' component={Images} />
<Route path='/image/:id' component={Image} />
</div>
</Router>
</Provider>,
document.getElementById('digital')
)
I had the same problem, so I created:
http://npmjs.com/package/react-router-modal
It allows you to attach modals to routes. You would use it as follows:
import { ModalContainer, ModalRoute } from 'react-router-modal';
// ...
ReactDOM.render(
<Provider store={store}>
<Router>
<div>
<Route path='/images' component={Images} />
<ModalRoute path='/image/:id' component={Image} />
</div>
</Router>
<ModalContainer />
</Provider>,
document.getElementById('digital')
)
There are a few simple examples at https://davidmfoley.github.io/react-router-modal-examples/
Hope it helps.
I was able to use the solution described here, which refers to this code example:
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
// This example shows how to render two different screens
// (or the same screen in a different context) at the same url,
// depending on how you got there.
//
// Click the colors and see them full screen, then "visit the
// gallery" and click on the colors. Note the URL and the component
// are the same as before but now we see them inside a modal
// on top of the old screen.
class ModalSwitch extends React.Component {
// We can pass a location to <Switch/> that will tell it to
// ignore the router's current location and use the location
// prop instead.
//
// We can also use "location state" to tell the app the user
// wants to go to `/img/2` in a modal, rather than as the
// main page, keeping the gallery visible behind it.
//
// Normally, `/img/2` wouldn't match the gallery at `/`.
// So, to get both screens to render, we can save the old
// location and pass it to Switch, so it will think the location
// is still `/` even though its `/img/2`.
previousLocation = this.props.location;
componentWillUpdate(nextProps) {
let { location } = this.props;
// set previousLocation if props.location is not modal
if (
nextProps.history.action !== "POP" &&
(!location.state || !location.state.modal)
) {
this.previousLocation = this.props.location;
}
}
render() {
let { location } = this.props;
let isModal = !!(
location.state &&
location.state.modal &&
this.previousLocation !== location
); // not initial render
return (
<div>
<Switch location={isModal ? this.previousLocation : location}>
<Route exact path="/" component={Home} />
<Route path="/gallery" component={Gallery} />
<Route path="/img/:id" component={ImageView} />
</Switch>
{isModal ? <Route path="/img/:id" component={Modal} /> : null}
</div>
);
}
}
const IMAGES = [
{ id: 0, title: "Dark Orchid", color: "DarkOrchid" },
{ id: 1, title: "Lime Green", color: "LimeGreen" },
{ id: 2, title: "Tomato", color: "Tomato" },
{ id: 3, title: "Seven Ate Nine", color: "#789" },
{ id: 4, title: "Crimson", color: "Crimson" }
];
function Thumbnail({ color }) {
return (
<div
style={{
width: 50,
height: 50,
background: color
}}
/>
);
}
function Image({ color }) {
return (
<div
style={{
width: "100%",
height: 400,
background: color
}}
/>
);
}
function Home() {
return (
<div>
<Link to="/gallery">Visit the Gallery</Link>
<h2>Featured Images</h2>
<ul>
<li>
<Link to="/img/2">Tomato</Link>
</li>
<li>
<Link to="/img/4">Crimson</Link>
</li>
</ul>
</div>
);
}
function Gallery() {
return (
<div>
{IMAGES.map(i => (
<Link
key={i.id}
to={{
pathname: `/img/${i.id}`,
// this is the trick!
state: { modal: true }
}}
>
<Thumbnail color={i.color} />
<p>{i.title}</p>
</Link>
))}
</div>
);
}
function ImageView({ match }) {
let image = IMAGES[parseInt(match.params.id, 10)];
if (!image) return <div>Image not found</div>;
return (
<div>
<h1>{image.title}</h1>
<Image color={image.color} />
</div>
);
}
function Modal({ match, history }) {
let image = IMAGES[parseInt(match.params.id, 10)];
if (!image) return null;
let back = e => {
e.stopPropagation();
history.goBack();
};
return (
<div
onClick={back}
style={{
position: "absolute",
top: 0,
left: 0,
bottom: 0,
right: 0,
background: "rgba(0, 0, 0, 0.15)"
}}
>
<div
className="modal"
style={{
position: "absolute",
background: "#fff",
top: 25,
left: "10%",
right: "10%",
padding: 15,
border: "2px solid #444"
}}
>
<h1>{image.title}</h1>
<Image color={image.color} />
<button type="button" onClick={back}>
Close
</button>
</div>
</div>
);
}
function ModalGallery() {
return (
<Router>
<Route component={ModalSwitch} />
</Router>
);
}
export default ModalGallery;
a simple modal route example with javascript that can use with any routing system
<button onClick={() => {
this.setState({ modal: true });
window.history.pushState("","","/gallery/image/img_1233")
}}>
Open Modal
</button>
//Link Button
<Link href="/gallery/image/img_1233">
<a>Open Page</a>
</Link>
complete example here: https://github.com/mohammad-amin-hesam/react-modal-route-example