CanvasJS charts take up entire row when using flexbox - css

I am currently using CanvasJS for pie charts in a project done in ReactJS. When a search for a particular person is done, I render 4 pie charts displaying information. I would like to render the pie charts side-by-side (as the space allows) over multiple rows using Flexbox.
Currently, when the pie charts render, each CanvasJSChart takes up the entire width of the row. In the picture below, you can see that although each pie chart takes up ~1/3 of of the table (the "Trial Version" and "Canvasjs.com" are not fixed in position relative to the pie chart and can move closer to it, depending on the size of the container), the container is the width of the row. I would ideally like each row to contain 2 pie charts (but not fixed to allow the application to work better on small screens).
This is my CSS section.
.container {
display:flex;
flex-wrap: row wrap;
}
When I did not have the flex-wrap: row wrap; line, the charts would individually take up less space and all remain on the same row:
So when I do try to have the charts wrap around, I'm not sure why the size of the container changes to the entire row. Below is my code that renders the charts (in a component named Accuracy when I search a name:
class Contestant extends Component{
constructor(props){
super(props)
}
render(){
var name = this.props.data[0];
var data = this.props.data[1];
let fjText = null
let fj = null
if (data.FJCorrect+data.FJIncorrect>0){
fj = <Accuracy numberCorrect={data.FJCorrect} numberIncorrect={data.FJIncorrect}
overallAccuracy = {data.FJAccuracy}/>
}
let tiebreak = null
let tiebreakText = null
if (data.TiebreakCorrect+data.TiebreakIncorrect>0){
tiebreak = <Accuracy numberCorrect={data.TiebreakCorrect}
numberIncorrect={data.TiebreakIncorrect}overallAccuracy = {data.TiebreakAccuracy} />
}
return(
<div className = "container" >
<Accuracy numberCorrect={data.numberCorrect} numberIncorrect={data.numberIncorrect}
overallAccuracy = {data.overallAccuracy}/>
<Accuracy numberCorrect={data.JCorrect} numberIncorrect={data.JIncorrect}
overallAccuracy = {data.JAccuracy}/>
<Accuracy numberCorrect={data.DJCorrect} numberIncorrect={data.DJIncorrect}
overallAccuracy = {data.DJAccuracy}/>
{fj}
{tiebreak}
</div>
)
}
}
export default Contestant
and below is my code that renders a Contestant component:
class Search extends Component{
constructor(){
super()
this.state = {search: "",
isLoading: true,
result: "",
searchType: null
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleSearch = this.handleSearch.bind(this);
}
handleSearch(searchStr){
//Takes in text and finds data of corresponding person
}
handleChange(event){
//In case someone is searching for a new term, we have to reset search query type
if (this.state.searchType !== null){
this.setState({isLoading: true})
}
this.setState({search: event.target.value});
}
handleSubmit() {
this.setState({isLoading: false, result: this.handleSearch(this.state.search)})
}
render(){
return(
<div>
<input type="text" value={this.state.search} onChange={this.handleChange} placeholder={...} />
<button type="submit" onClick={this.handleSubmit}>Search</button>
{this.state.result.length === 0 ? null: <Contestant data={this.state.result}/>}
</div>
)
}
}
export default Search
My CanvasJS pie chart has the default settings as on the linked website (with only data points changed), so I'm not sure why the size of the charts change depending on whether I wrap my Flexbox cells.
class Accuracy extends Component{
constructor(props){
super(props)
}
render(){
var correctAccuracy = ((this.props.overallAccuracy).toFixed(2)).toString()
var incorrectAccuracy = ((100-this.props.overallAccuracy).toFixed(2)).toString()
var options = {
animationEnabled: false,
animationEnabled: true,
animationDuration: 500,
backgroundColor: "#F0F8FF",
height: 260,
data: [{
type: "pie",
startAngle: 300,
toolTipContent: "{accuracy}%",
indexLabelFontSize: 16,
indexLabel: "{label}:{y}",
dataPoints: [
{ y: this.props.numberCorrect, label: "Correct", accuracy: correctAccuracy },
{ y: this.props.numberIncorrect, label: "Incorrect", accuracy: incorrectAccuracy},
]
}]
}
return (
<CanvasJSChart options = {options}/>
)
}
}
export default Accuracy
Any help would be greatly appreciated! I've been stuck on this for a couple of days

From the first screencap, it seems like your problem is that the width of your pie chart containers is set to 100%. This means that each container will be 100% of its parent--in this case the .container div. Your CSS should look like this:
.container {
display: flex;
flex-wrap: row-wrap;
}
.piechart-container {
width:50%;
}
And of course you need to add the "piechart-container" classname to you piechart container divs. I didn't try this out so let me know if it works. You may have to set the width to be slightly less than 50% or change the max-width property as well. Instead of width:50% you could do flex:0 0 50% which is a flexbox item property meaning "set an initial width of 50% and don't allow any growing or shrinking."
Edit: just now seeing that you want some flexibility. You'd probably be better off setting the container widths in terms of pixels instead of percentages so that when the screen gets small enough they overflow to be one chart per row. I would recommend looking up "media queries" and setting the CSS so that if the screen width goes below a certain number, a different set of attributes takes place (i.e. smaller charts, different wrapping behavior)

Related

Autoheight in MUI DataGrid

I'm using the MUI DataGrid component, and the behavior I hope to have is that:
When there's a small number of rows, the table is only the size it needs to for those rows.
When there's a large number of rows, more than the current viewport can hold (given whatever else is on the screen), the table takes up the available space in the layout (given its flex: 1) and the extra rows scroll inside the table.
I can achieve each of these behaviors, but only one at a time.
If I use the autoHeight property on the DataGrid, then the table will be as small as it can be. BUT it will also be as big as it can be, so with a large number of rows the container scrolls the entire table, rather than the rows scrolling within the table.
If I don't use autoHeight, and wrap the DataGrid in a container with flex: 1, then the table will grow to fill the available space and the rows will scroll within the table. BUT a table with only a few rows will also grow to fill its container, so that there is empty space under the rows (above the footer, "Table rows: #")
You can see the situation in this screenshot, showing the exact same page, with different data.
I've tried what feels like every permutation of heights and flexes under the sun. For example:
Setting autoHeight with a maxHeight (and .MuiDataGrid-main { overflow: scroll; } ) allows few-rows to be small, and many-rows to be not too small, but obviously any discrete maxHeight, be it px or %, is not the flexible layout I'm going for.
Turning off autoHeight (as in scenario #2) and setting flex-grow: 0 on the rows container within the table (.MuiDataGrid-main) just makes the rows disappear since they then shrink to a height of 0.
The code for the component:
const S = {
Wrapper: styled.div`
width: 100%;
display: flex;
flex: 1;
background: white;
border: solid thick red;
`,
DataGrid: styled(DataGridPro)`
&& {
.MuiDataGrid-main {
//overflow: scroll;
//flex-grow: 0;
}
background: lightgreen;
font-size: 14px;
}
`,
};
type Props = {
columns: ReadonlyColumns;
rows: AnyObject[];
filterModel?: GridFilterModel;
} & Omit<DataGridProps, 'columns'>;
const DataTable: React.FC<Props> = ({
columns = [],
rows = [],
filterModel,
className,
...props
}) => {
const memoizedColumns = useMemo(
() =>
columns.map(col => ({
headerClassName: 'columnHeader',
flex: 1, // all columns expand to fill width
...col, // but could override that behavior
})),
[columns],
);
return (
<S.Wrapper className={className}>
<S.DataGrid
// autoHeight
rows={rows}
columns={memoizedColumns}
filterModel={filterModel}
{...props}
/>
</S.Wrapper>
);
};
Using a combination of autoHeight and pageSize will create a table whose height is only as big as needed for the current number of rows as long as the number of rows is <= pageSize. Additional rows will be added to a new page.
<DataGrid
rows={rows}
columns={columns}
pageSize={20} //integer value representing max number of rows
autoHeight={true}
/>
Below solved the issue:
<DataGrid getRowHeight={() => 'auto'} />
Source:
https://mui.com/x/react-data-grid/row-height/#dynamic-row-height
I had a similar issue days ago and I solved recalculating the row height every time a new item was added to my row.
getRowHeight={(props: GridRowHeightParams) => {
const serviceRowHeight = 45 // <-- default height, if I have no data for the row
const addServiceBtnHeight = 45 // <-- a component that's always on the row
const height = props.model?.services // services is each dynamic item for my row, so you should access like props.yourObj
? props.model.services.length * serviceRowHeight + addServiceBtnHeight
: 115
return height < 115 ? 115 : height // final height to be returned
}}

React: Only display items which fully fit within flex box, and dynamically determine the number of items being displayed

The accepted answer here is very very close to being what I want. I have a vertical flexbox with items that have a fixed height. I would like as many items as possible to be displayed, but if an item overflows at all, it should be completely omitted. Try the code snippet in the linked answer to see what I mean. This effect can pretty easily be achieved by setting 'flex-wrap: wrap' and overflow hidden on the container.
However, I have one more issue: instead of just displaying these items, I also need to know HOW MANY are currently being displayed. I'm trying to show as many items as will fit, and then at the bottom a label will say "...and 5 more" if there are five items that don't fit, for example. What's the easiest way to go about this? I would love to avoid just dividing the height of the container by the heights of the individual items if possible. There must be a better way.
Did a lot of digging and finally came to a solution that I'm happy with. Use offsetLeft of the elements in order to determine which ones are wrapped. Do the same but with offsetTop for a horizontal flex box.
CSS:
.container {
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
React:
const items = [...]; // Array of ReactElements
const [numberOfItemsDisplayed, setNumberOfItemsDisplayed] = useState(0);
const ref = useRef(null)
useLayoutEffect(() => {
const cells = ref.current?.children;
if (cells && cells.length > 0) {
const startingOffset = cells[0].offsetLeft;
let numItems = 0;
for (let i = 0; i < cells.length; i++) {
if (cells.offsetLeft > startingOffset) {
break;
}
numItems++;
}
setNumberOfItemsDisplayed(numItems);
}
}, []);
return <div class="container" ref={ref}>{items}</div>

How to remove extra space in the detail grid style in ag-grid master detail setup?

I have an ag-grid master-detail grid where one master row typically has only 1 or 2 sub-rows in the detail grid (there's always 2 columns for the detail grid). When I write up the code matching the examples on ag-grid's documentation, it always leaves huge whitespace under the detail grid and it auto-expands the detail grid's width to the width of the container.
I would like to get rid of the extra whitespace under the detail grid and also size the detail grid so the columns are only as wide as they need to be to fit the data. (Thus there would be whitespace to the right side of the detail-grid)
I've tried looking through the styles in chrome dev tools but I can't figure out what controls those 2 styling aspects. In addition, setting domLayout = 'autoHeight' in the detail grid options just moves the bottom of the detail grid to the last row, but it doesn't get rid of the extra whitespace in the cases where there's only 1 detail-row.
Plunkr MVPs (includes domLayout = 'autoHeight' in detail-grid's options):
React: https://plnkr.co/edit/K76FssFF4Ex538fn
Angular: https://plnkr.co/edit/0n5EllKejfyq9x4E
The original plunkr samples came from the "Simple Example" plunkr on the ag-grid master-detail documentation.
All you need to do is refer the documentation here, as for the additional things needed.
We need to use this method from the ag-grid documentation.
this.getRowHeight = function(params) {
if (params.node && params.node.detail) {
var offset = 80;
var allDetailRowHeight =
params.data.callRecords.length *
params.api.getSizesForCurrentTheme().rowHeight;
var gridSizes = params.api.getSizesForCurrentTheme();
return allDetailRowHeight + gridSizes.headerHeight + offset;
}
};
Also on the detail panel grid options we need to add this property, which will set the height dynamically.
this.detailCellRendererParams = {
detailGridOptions: {
columnDefs: [
{ field: 'callId' },
{
field: 'duration',
valueFormatter: "x.toLocaleString() + 's'",
}
],
defaultColDef: {
flex: 1,
editable: true,
resizable: true,
},
onGridReady: function(params) {
params.api.setDomLayout('autoHeight');
},
},
getDetailRowData: function(params) {
params.successCallback(params.data.callRecords);
},
};
Below is a working example for your reference
Plunkr example
After combining Naren's answer with some css adjustments, this is the best result I could get. I'm not accepting this answer because it's not what I want, but I can live with it until I (or someone else) can figure out something better.
https://plnkr.co/edit/d9emrRiavR9gCpQl
Basically I added these 2 things to the plunkr:
this.getRowHeight = params => {
const defRowHeight = params.api.getSizesForCurrentTheme().rowHeight;
if(params.node.detail) {
const offset = 80;
const detailRowHeight =
params.data.callRecords.length * defRowHeight;
var gridSizes = params.api.getSizesForCurrentTheme();
return detailRowHeight + gridSizes.headerHeight + offset;
} else {
return defRowHeight;
}
}
styles: ['::ng-deep .ag-details-row { width: 50% }']
An acceptable solution would provide a way to resize the detail grid directly to the width of the actual columns (rather than an arbitrary percentage)
Image:
(You can remove that little extra space under the 1st row of the detail grid by overriding min-height of one of the grid's css classes. It only appears if there's 1 row in the detail grid)

React select div height when DOM is loaded

I am having issues with react Plotly displaying correctly when a graph loaded in a hidden tab is selected. It seems this is a known issue where every tab but the default tab will not resize appropriately because it doesn't know the hidden graph's dimensions.
To get around this I would like to dynamically update the tab height to be equal to the height of the default tab. Something like this: Change div height when tab is selected.
The issue is, I am unable to select the tab height value on DOM load. I thought I could add a componentDidMount function with an evenlistener for window load like such:
constructor(props) {
super(props)
this.state = {
value: 0
};
this.handleLoad = this.handleLoad.bind(this);
}
componentDidMount() {
window.addEventListener('load', this.handleLoad);
}
handleLoad() {
console.log("firstTab height: " + $('#firstTab').offsetHeight)
}
This issue is, the console log is outputting firstTab height: undefined.
When i inspect the web page and put the command $('#firstTab').offsetHeight into the console I am able to come up with a height of 697 px.
I don't want to hardcode the tab pixel size. Can anyone guide me as to why I am failing to grab the element height on window load?
Try using clientHeight property.
componentDidMount() {
const height = document.getElementById('firstTab').clientHeight;
}
I think instead of using event listener, you can do something like this.
componentDidMount() {
const height = this.firstTab.clientHeight;
console.log(height) // => gives you height
}
render() {
return (
<div
id="firstTab"
ref={ (firsTabElement) => { this.divElement = firstTabElement } }
>
Tab Content
</div>
)
}
After the first render if you are not hiding or putting any condition to remove the firstTab from the DOM. It will be available to you in componentDidMount lifecycle.

famo.us: Modify content in GridLayout

is it possible to modify the content of a surface (used within GridLayout) without using CSS? For example to center the text?
Basic example:
function createGrid( section, dimensions, menuData ) {
var grid = new GridLayout({
dimensions: dimensions
});
var surfaces = [];
grid.sequenceFrom(surfaces);
for(var i = 0; i < dimensions[1]; i++) {
surfaces.push(new Surface({
content: menuData[i].title,
size: [undefined, undefined],
properties: {
backgroundColor: "#ff0000",
color: "white",
textAlign: 'center',
}
}));
}
return grid;
}
I added the center property, but I also want to have the content in the middle of my surface. Do I have to use CSS or is there another way?
I tried adding another View/Surface within this Surface and added the align/origin modifier. Didn't work: I still had to adjust the origin/align values for the specific (browser) layout ...
I'm not so sure about you first question, but I can answer the second one.
text-alignhelps you horizontally center your content. So the problem is how to do it vertically.
The cleanest way to do it is to set the line-height the same as the containing div's height. In your case, there are two ways to do it:
1) calculate the height of the grid. For example, if you have a 9*9 GridLayout for the whole screen, then we will have gridHeight = window.innerHeight/9. Then you just need to add lineHeight: gridHeight to your properties object.
check http://jsfiddle.net/mrwiredancer/veLpbmmo/2/ for full exmaple
2) if you are not able to calculate the height of the grid beforehand, you can center a fixed-height(smaller than the grid's height) surface in the middle of the grid. For example, your GridLayout is contained in a dynamic view, but you're sure that your grid is no less than 20px high. Then you can do this:
var container, surface, __height = 20;
for(var i = 0; i < dimensions[1]; i++) {
container = new Container({ //View works as well
properties: {
backgroundColor: '#FF0000'
}
})
surface = new Surface({
content: menuData[i].title,
size: [undefined, __height],
properties: {
lineHeight: __height+'px', //same as surface's height
color: "white",
textAlign: 'center',
}
});
container.add(new Modifier({origin: [.5, .5]})).add(surface);
surfaces.push(container);
}
check http://jsfiddle.net/mrwiredancer/uxq30yp9/1/ for full example

Resources