When I am listing the new data it updates but then the chart only updates the first time. I don't have access inside the Line component so I can't do componentWillReceiveProps(nextProps) for that one. How can I update the chart with each new state change?
import React, { Component } from 'react';
import {Line} from 'react-chartjs-2';
class Chart extends Component {
componentWillReceiveProps(nextProps) {
debugger
this.setState({ data: nextProps.info });
}
constructor(props) {
super(props);
this.state = {
data: []
}
}
render() {
let chart = {
chartData:{
labels: this.state.data,
datasets:[
{
label: 'Weight',
data: this.state.data,
backgroundColor:[
'rgba(54, 162, 235, 0.6)',
'rgba(255, 206, 86, 0.6)',
]
}
]
}
}
return (
<div className="chart" style={{height: 200 + "px", width: 100 + "%"}}>
<Line
data={chart.chartData}
width={100}
height={50}
options={{
maintainAspectRatio: false
}}
/>
<ul>
{this.state.data.map(val=> <li>{val}</li>)}
</ul>
</div>
);
}
}
Related
I'm trying to add tooltips to a chart I made with Layer Cake, the graphics framework for Svelte. I looked at the Map example on the Layer Cake site, as that one has tooltips, but I can't figure out how to adapt for my bar chart.
I can't even get a string to show up, much less any data. Any help would be greatly appreciated. I think I must be missing something pretty obvious.
Below is a minimal example with dummy data.
You can see the code working in this REPL:
https://svelte.dev/repl/e8bb579754e6405ea19363b5d13d7f54?version=3.55.1
Thanks!
App.svelte:
<script>
import { LayerCake, Svg, Html } from "layercake";
import Bar from "./Bar.svelte";
import AxisX from "./AxisX.svelte";
import AxisY from "./AxisY.svelte";
import Tooltip from "./Tooltip.html.svelte";
import { scaleBand } from "d3-scale";
let data = [
{
fruit: "Apple",
number: 364,
},
{
fruit: "Banana",
number: 263,
},
{
fruit: "Mango",
number: 872,
},
{
fruit: "Pear",
number: 156,
},
]
data.forEach((d) => {
d[xKey] = +d[xKey];
});
const xKey = "number";
const yKey = "fruit";
let evt;
let hideTooltip = false;
</script>
<div class="chart-container">
<LayerCake
padding={{ top: 20, bottom: 80, left: 60, right:40 }}
x={xKey}
y={yKey}
yScale={scaleBand().paddingInner([0.15])}
xDomain={[0, null]}
data={data}
>
<Svg>
<AxisX gridlines={true} baseline={true} snapTicks={true} ticks="4" />
<AxisY gridlines={false} />
<Bar
/>
</Svg>
<Html
pointerEvents={false}
>
{#if hideTooltip !== true}
<Tooltip
{evt}
>
{#const tooltipData = {data}}
{#each Object.entries(tooltipData) as [key, value]}
{console.log('tooltipData',tooltipData)}
<div class="row">hi is this showing up?</div>
{/each}
</Tooltip>
{/if}
</Html>
</LayerCake>
</div>
<style>
.chart-container {
width: 600px;
height: 300px;
}
</style>
The other components are taken directly from the LayerCake framework.
I figured it out. Most important thing was to add a dispatch event in the Bar component, and create mouseover, movemove, focus events for the rects there.
Updated REPL: https://svelte.dev/repl/09725c92e4104d0cad53d0387a762269?version=3.55.1
App.svelte:
<script>
import { LayerCake, Svg, Html } from "layercake";
import Bar from "./Bar.svelte";
import AxisX from "./AxisX.svelte";
import AxisY from "./AxisY.svelte";
import Tooltip from "./Tooltip.html.svelte";
import { scaleBand } from "d3-scale";
let data = [
{
fruit: "Apple",
number: 364,
},
{
fruit: "Banana",
number: 263,
},
{
fruit: "Mango",
number: 872,
},
{
fruit: "Pear",
number: 156,
},
]
data.forEach((d) => {
d[xKey] = +d[xKey];
});
const xKey = "number";
const yKey = "fruit";
let evt;
let hideTooltip = false;
</script>
<div id="chart-container">
<LayerCake
padding={{ top: 20, bottom: 80, left: 60, right:40 }}
x={xKey}
y={yKey}
yScale={scaleBand().paddingInner([0.15])}
xDomain={[0, null]}
data={data}
>
<Svg>
<AxisX gridlines={true} baseline={true} snapTicks={true} ticks="4" />
<AxisY gridlines={false} />
<Bar
on:mousemove={event => evt = hideTooltip = event}
on:mouseout={() => (hideTooltip = true)}
/>
</Svg>
<Html
pointerEvents={false}
>
{#if hideTooltip !== true}
<Tooltip
{evt}
let:detail
>
{#const tooltipData = { ...detail.props }}
<div class="row">{tooltipData.fruit}: {tooltipData.number}</div>
</Tooltip>
{/if}
</Html>
</LayerCake>
</div>
<style>
#chart-container {
width: 600px;
height: 300px;
}
</style>
Bar.svelte:
<script>
import { getContext, createEventDispatcher } from "svelte";
const { data, xGet, yGet, xScale, yScale } = getContext('LayerCake');
export let fill = '#ef4136';
let hideTooltip = false;
const dispatch = createEventDispatcher();
function handleMousemove(feature) {
return function handleMousemoveFn(e) {
raise(this);
// When the element gets raised, it flashes 0,0 for a second so skip that
if (e.layerX !== 0 && e.layerY !== 0) {
dispatch("mousemove", { e });
}
};
}
</script>
<g
class="bar-group"
on:mouseout={(e) => dispatch("mouseout")}
on:blur={(e) => dispatch("mouseout")}
>
{#each $data as d, i}
<rect
class='group-rect'
data-id="{i}"
x="{$xScale.range()[0]}"
y="{$yGet(d)}"
height={$yScale.bandwidth()}
width="{$xGet(d)}"
{fill}
on:mouseover={(e) => dispatch("mousemove", { e, props: d })}
on:focus={(e) => dispatch("mousemove", { e, props: d })}
on:mousemove={(e) => handleMousemove(e, d)}
></rect>
{/each}
</g>
Tooltip.html.svelte, AxisY.svelte, and AxisX.svelte stay the same as they were (as they are in Layer Cake).
I am building an app in Ag-grid React
I would like the grid to highlight a row if the user has tagged it by clicking on a checkbox. I am using rowClassRules, and it works fine: if the user edits the value of the tag field for a row from false to true, the row becomes highlighted
When I add in a cell renderer to make the tag field a checkbox it stops working, see code below
Any advice on what I am doing wrong would be appreciated
index.js
import React, { useState } from "react";
import { render } from "react-dom";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-alpine.css";
import "./index.css"
const App = () => {
const AgGridCheckbox = (props) => {
const boolValue = props.value && props.value.toString() === "true";
const [isChecked, setIsChecked] = useState(boolValue);
const onChanged = () => {
props.setValue(!isChecked);
setIsChecked(!isChecked);
};
return (
<div>
<input
type="checkbox"
checked={isChecked}
onChange={onChanged}
/>
</div>
);
};
const [rowData] = useState([
{ tag: true, make: "Toyota", model: "Celica", price: 35000 },
{ tag: false, make: "Ford", model: "Mondeo", price: 32000 },
{ tag: false, make: "Porsche", model: "Boxter", price: 72000 },
]);
const [columnDefs] = useState([
{ field: "tag", cellRenderer: AgGridCheckbox },
// { field: "tag", editable: true },
{ field: "make" },
{ field: "model" },
{ field: "price" },
]);
const gridOptions = {
rowClassRules: {
"row-tagged": (params) => params.api.getValue("tag", params.node),
},
};
return (
<div className="ag-theme-alpine" style={{ height: 400, width: 800 }}>
<AgGridReact
gridOptions={gridOptions}
rowData={rowData}
columnDefs={columnDefs}
></AgGridReact>
</div>
);
};
render(<App />, document.getElementById("root"));
index.css
.row-tagged {
background-color: #91bd80 !important;
}
I've done some more research and if I add redrawRows() to the onChanged() handler in the cell renderer thus:
const onChanged = () => {
props.setValue(!isChecked);
setIsChecked(!isChecked);
setRowData(rowData);
console.log(props);
props.api.redrawRows({ rowNodes: [props.node] });
};
It now works. Note that passing { rowNodes: [props.node] } means (I assume) that it only redraws a single row.
Supplementary Question: Is this the right way to go? Is there a more efficient way?
I am using high chart wrapper in my angular5 app with the help of below link.
high chart wrapper
but how can I use addSeries() to add series into the existing chart and how can I update the properties of existing chart.
how can I use addSeries() to add series into the existing chart and
how can I update the properties of existing chart.
When using highcharts-angular wrapper it is not recommended to use chart methods like addSeries() or update() directly on chart reference.
You have to update a whole component, not only chart properties. It can be achieved by updating chartOptions object (add new series, point, title etc) and setting updateFlag = true. Check the code and demo posted below.
app.module.ts:
import { BrowserModule } from "#angular/platform-browser";
import { NgModule } from "#angular/core";
import { HighchartsChartModule } from "highcharts-angular";
import { ChartComponent } from "./chart.component";
import { AppComponent } from "./app.component";
#NgModule({
declarations: [AppComponent, ChartComponent],
imports: [BrowserModule, HighchartsChartModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
chart.component.html:
<div class="boxChart__container">
<div>
<highcharts-chart
id="container"
[Highcharts]="Highcharts"
[constructorType]="chartConstructor"
[options]="chartOptions"
[callbackFunction]="chartCallback"
[(update)]="updateFlag"
[oneToOne]="true"
style="width: 100%; height: 400px; display: block;"
>
</highcharts-chart>
<button (click)="updateChart()">Update Chart</button>
</div>
</div>
chart.component.ts:
import { Component, OnInit } from "#angular/core";
import * as Highcharts from "highcharts";
import * as HighchartsMore from "highcharts/highcharts-more";
import * as HighchartsExporting from "highcharts/modules/exporting";
HighchartsMore(Highcharts);
HighchartsExporting(Highcharts);
#Component({
selector: "app-chart",
templateUrl: "./chart.component.html"
})
export class ChartComponent implements OnInit {
title = "app";
chart;
updateFlag = false;
Highcharts = Highcharts;
chartConstructor = "chart";
chartCallback;
chartOptions = {
series: [
{
data: [1, 2, 3, 6, 9]
}
],
exporting: {
enabled: true
},
yAxis: {
allowDecimals: false,
title: {
text: "Data"
}
}
};
constructor() {
const self = this;
this.chartCallback = chart => {
// saving chart reference
self.chart = chart;
};
}
ngOnInit() {}
updateChart() {
const self = this,
chart = this.chart;
chart.showLoading();
setTimeout(() => {
chart.hideLoading();
self.chartOptions.series = [
{
data: [10, 25, 15]
},
{
data: [12, 15, 10]
}
];
self.chartOptions.title = {
text: "Updated title!"
};
self.updateFlag = true;
}, 2000);
}
}
Demo:
https://codesandbox.io/s/oomo7424pz
Docs reference:
updateFlag - https://github.com/highcharts/highcharts-angular#options-details
here is a very useful answer for learning how to updata a highchart.
https://www.highcharts.com/demo/chart-update
it explains a method chart.update
chart.update({
chart: {
inverted: false,
polar: false
},
subtitle: {
text: 'Plain'
}
});
For adding series the following method is used
chart.addSerie(serie,true);
flag 'true' here is equivalent to chart.redraw();
OR
var chart = new Highcharts.Chart(options);
chart.addSeries({
name: array.name,
data: array.value
});
If you are going to add several series you should set the redraw flag to false and then call redraw manually after as that will be much faster.
var chart = new Highcharts.Chart(options);
chart.addSeries({
name: 'Bill',
data: [1,2,4,6]
}, false);
chart.addSeries({
name: 'John',
data: [4,6,4,6]
}, false);
chart.redraw();
For more information and methods you can visit the Official Highcharts API page:
https://api.highcharts.com/class-reference/Highcharts.Chart
When using angular-highcharts wrapper as
import { Chart } from 'angular-highcharts';
create charts as below
chart = new Chart({
chart: {
type: 'line'
},
title: {
text: 'Linechart'
},
credits: {
enabled: false
},
series: [
{
name: 'Line 1',
data: [1, 2, 3]
}
]
});
now you can call all API methods on this
I am receiving there error "Maximum Call Stack Size Exceeded". After adding a conditional to the navigation file in my code. The stack I am using is React-Native with Expo, Redux, GraphQL and my navigation library is react-navigation. I am working on iOS simulator. This is the code that breaks the
app:
//navigations.js
if (!this.props.user.isAuthenticated) {
return <AuthenticationScreen />;
}
and this is the entirety of the navigations.js
import React, { Component } from "react";
import {
addNavigationHelpers,
StackNavigator,
TabNavigator
} from "react-navigation";
import { connect } from "react-redux";
import { FontAwesome } from "#expo/vector-icons";
import HomeScreen from "./screens/HomeScreen";
import ExploreScreen from "./screens/ExploreScreen";
import FavoritesScreen from "./screens/FavoritesScreen";
import ProfileScreen from "./screens/ProfileScreen";
import AuthenticationScreen from "./screens/AuthenticationScreen";
import { colors } from "./utils/constants";
const TAB_ICON_SIZE = 20;
const Tabs = TabNavigator(
{
Home: {
screen: HomeScreen,
navigationOptions: () => ({
headerTitle: "Saga",
tabBarIcon: ({ tintColor }) => (
<FontAwesome size={TAB_ICON_SIZE} color={tintColor} name="home" />
)
})
},
Explore: {
screen: ExploreScreen,
navigationOptions: () => ({
headerTitle: "Explore",
tabBarIcon: ({ tintColor }) => (
<FontAwesome size={TAB_ICON_SIZE} color={tintColor} name="search" />
)
})
},
Favorites: {
screen: FavoritesScreen,
navigationOptions: () => ({
headerTitle: "Favorites",
tabBarIcon: ({ tintColor }) => (
<FontAwesome
size={TAB_ICON_SIZE}
color={tintColor}
name="file-video-o"
/>
)
})
},
Profile: {
screen: ProfileScreen,
navigationOptions: () => ({
headerTitle: "Profile",
tabBarIcon: ({ tintColor }) => (
<FontAwesome size={TAB_ICON_SIZE} color={tintColor} name="user" />
)
})
}
},
{
lazy: true,
tabBarPosition: "bottom",
swipeEnabled: false,
tabBarOptions: {
showIcon: true,
showLabel: false,
activeTintColor: colors.PRIMARY,
inactiveTintColor: colors.LIGHT_GRAY,
style: {
backgroundColor: colors.BASE_GRAY,
height: 50,
paddingVertical: 5
}
}
}
);
const AppMainNav = StackNavigator(
{
Home: {
screen: Tabs
}
},
{
cardStyle: {
backgroundColor: "#F1F6FA"
},
navigationOptions: () => ({
headerStyle: {
backgroundColor: colors.BASE_GRAY
},
headerTitleStyle: {
fontWeight: "bold",
fontSize: 24,
color: colors.BLUE
}
})
}
);
class AppNavigator extends Component {
render() {
const nav = addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav
});
if (!this.props.user.isAuthenticated) {
return <AuthenticationScreen />;
}
return <AppMainNav navigation={nav} />;
}
}
export default connect(state => ({
nav: state.nav,
user: state.user
}))(AppNavigator);
export const router = AppMainNav.router;
For good measure this is the user.js reducer:
const initialState = {
token: null,
isAuthenticated: false,
info: null
};
export default (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
I can't see what would be causing an infinite loop.
Update #1
AuthenticationScreen.js (Minus Imports)
const Root = styled.View``;
const T = styled.Text``;
class AuthenticationScreen extends Component {
state = {};
render() {
return (
<Root>
<T>AuthenticationScreen</T>
</Root>
);
}
}
export default AuthenticationScreen;
As I understand it, you've isolated the problem to this code...
if (!this.props.user.isAuthenticated) {
return <AuthenticationScreen />;
}
return <AppMainNav navigation={nav} />;
And if you remove...
if (!this.props.user.isAuthenticated) {
return <AuthenticationScreen />;
}
it works fine, which means <AppMainNav> must be fine. So the infinite loop must be in your <AuthenticationScreen /> code.
I am using React with React Router on top of Rails to handle the front end of an app that is supposed to return info about whatever gem the user searches for, however, once I hit submit, the child component, who manages it's own state, causes a re-render for the parent component.
EXPECTED RESULT: SavedGems.jsx re-renders
ACTUAL RESULT: Search.jsx re-renders
Here is my code:
StaticPage.jsx
export default class StaticPage extends React.Component {
render() {
return (
<BrowserRouter>
<div style={{display: 'flex', flexDirection: 'row'}}>
<Route exact path='/' render={() => <Search />}/>
<Route path='/favorites' render={() => <Favorites/>} />
</div>
</BrowserRouter>
);
}
}
Search.jsx
export default class Search extends React.Component {
render() {
return (
<div style = {{display: 'flex', flexDirection: 'row'}}>
<Sidebar/>
<div style = {{display: 'flex', flexDirection: 'column'}}>
<Header name = "Search Gems"/>
<Form/>
<SavedGems/>
</div>
</div>
)
}
}
SavedGems.jsx
export default class SavedGems extends React.Component {
constructor() {
super();
this.state = {saved_gems : []};
console.log(this.saved_gems);
}
componentDidMount() {
$.getJSON('/api/v1/saved_gems.json', (response) => { this.setState({ saved_gems: response }) });
}
render() {
var saved_gems= this.state.saved_gems.map((saved_gem) => {
return (
<div key={saved_gem.id}>
<h3>{saved_gem.name}</h3>
<h3>{saved_gem.info}</h3>
<h3>{saved_gem.dependencies}</h3>
</div>
)
});
return (
<div>
{saved_gems}
</div>
)
}
}
_form.jsx
export default class Form extends React.Component {
constructor() {
super();
this._handleClick = this._handleClick.bind(this);
this.state = {formBorderColor : "#5F5F5F"};
}
render() {
return (
<div>
<form>
<label>
<input ref='name'
type="text"
placeholder='Search'
style= {{fontFamily: 'Lato-Regular',
fontSize: 18,
height:89,
width: 780,
paddingLeft: 20,
backgroundColor: 'white',
border: '1px solid',
borderColor: this.state.formBorderColor,
borderRadius: 100}}/>
</label>
<input type="image" src='/assets/magnifying-glass.png'
style={{marginLeft: -70}}
onClick={this._handleClick}/>
</form>
</div>
)
}
_handleClick(event) {
const name = this.refs.name.value;
const info = '';
const dependencies = '';
$.ajax({
url: '/api/v1/saved_gems',
type: 'POST',
data: { saved_gem: { name, info, dependencies } },
success: (saved_gem) => {
this.props.handleSubmit(saved_gem);
this.refs.name.value = '';
this.refs.info.value = '';
this.refs.dependencies.value = '';
},
error: (xhr) => {
this.setState = ({formBorderColor : 'red'}).bind(this);
alert("Sorry! That is not a valid gem");
}
})
}
}