Skip to content

Commit

Permalink
fix: vis is rendered with an invalid config (#172)
Browse files Browse the repository at this point in the history
### Developer Checklist (Definition of Done)

**Issue**

- [ ] All acceptance criteria from the issue are met
- [x] Tested in latest Chrome/Firefox

**UI/UX/Vis**

- [ ] Requires UI/UX/Vis review
  - [ ] Reviewer(s) are notified (_tag assignees_)
  - [ ] Review has occurred (_link to notes_)
  - [ ] Feedback is included in this PR
  - [ ] Reviewer(s) approve of concept and design

**Code**

- [x] Branch is up-to-date with the branch to be merged with, i.e.,
develop
- [x] Code is cleaned up and formatted
- [ ] Unit tests are written (frontend/backend if applicable)
- [ ] Integration tests are written (if applicable)

**PR**

- [x] Descriptive title for this pull request is provided (will be used
for release notes later)
- [x] Reviewer and assignees are defined
- [x] Add type label (e.g., *bug*, *feature*) to this pull request
- [x] Add release label (e.g., `release: minor`) to this PR following
[semver](https://semver.org/)
- [x] The PR is connected to the corresponding issue (via `Closes #...`)
- [x] [Summary of changes](#summary-of-changes) is written


### Summary of changes
We have the issue that we are providing vis an externalConfig that does
not include all possible defaults
```typescript
  const config = {
    type: 'Heatmap plot',
    catColumnsSelected: [
      { id: 'Column 1', name: 'Column 1' },
      { id: 'Column 2', name: 'Column 2' },
    ],
  };
```
This is the different values the vis was being rendered with (causes
errors) when inputing the above config
```
MainApp.tsx: Parent {type: 'Heatmap plot', catColumnsSelected: Array(2)}
MainApp.tsx:40 Parent null
MainApp.tsx:40 Parent {type: 'Heatmap plot', catColumnsSelected: Array(2), color: {…}}
MainApp.tsx:40 Parent {type: 'Heatmap plot', color: {…}, catColumnsSelected: Array(2), numColorScaleType: 'Sequential', xSortedBy: 'CAT_ASC', …}
```

And this with this fix

```
MainApp.tsx: Parent {type: 'Heatmap plot', catColumnsSelected: Array(2)}
MainApp.tsx:40 Parent {type: 'Heatmap plot', color: {…}, catColumnsSelected: Array(2), numColorScaleType: 'Sequential', xSortedBy: 'CAT_ASC', …}
```

We could improve the logic and readabilty of this section of code when
we switch completely to mantine 7 to use
https://mantine.dev/hooks/use-uncontrolled/

### Screenshots

before

![gr-original](https://github.com/datavisyn/visyn_core/assets/51322092/ea036357-6411-4592-aaf2-a1b469ce2d3e)
after

![gr-new](https://github.com/datavisyn/visyn_core/assets/51322092/a9e2aac0-b55a-4395-a25b-7eb0e908fdd0)



### Additional notes for the reviewer(s)

-  
Thanks for creating this pull request 🤗
  • Loading branch information
puehringer authored Feb 16, 2024
2 parents 6f315ba + 7a6266e commit 30da502
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 105 deletions.
160 changes: 61 additions & 99 deletions src/vis/EagerVis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,76 +204,40 @@ export function EagerVis({

const { getVisByType } = useVisProvider();

// Each time you switch between vis config types, there is one render where the config is inconsistent with the type before the merge functions in the useEffect below can be called.
// To ensure that we never render an incosistent config, keep a consistent and a current in the config. Always render the consistent.
// eslint-disable-next-line @typescript-eslint/naming-convention
const [{ consistent: visConfig, current: inconsistentVisConfig }, _setVisConfig] = React.useState<{
consistent: BaseVisConfig;
current: BaseVisConfig;
}>(
externalConfig
? { consistent: null, current: externalConfig }
const [_visConfig, _setVisConfig] = useUncontrolled({
value: externalConfig?.type ? getVisByType(externalConfig?.type)?.mergeConfig(columns, externalConfig) : null,
defaultValue: externalConfig
? null
: columns.filter((c) => c.type === EColumnTypes.NUMERICAL).length > 1
? {
consistent: null,
current: {
type: ESupportedPlotlyVis.SCATTER,
numColumnsSelected: [],
color: null,
numColorScaleType: ENumericalColorScaleType.SEQUENTIAL,
shape: null,
dragMode: EScatterSelectSettings.RECTANGLE,
alphaSliderVal: 0.5,
} as BaseVisConfig,
}
: {
consistent: null,
current: {
type: ESupportedPlotlyVis.BAR,
multiples: null,
group: null,
direction: EBarDirection.HORIZONTAL,
display: EBarDisplayType.ABSOLUTE,
groupType: EBarGroupingType.STACK,
numColumnsSelected: [],
catColumnSelected: null,
aggregateColumn: null,
aggregateType: EAggregateTypes.COUNT,
} as BaseVisConfig,
},
);

const setExternalConfigRef = useSyncedRef(setExternalConfig);
useEffect(() => {
setExternalConfigRef.current?.(visConfig);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(visConfig), setExternalConfigRef]);

const setVisConfig = React.useCallback((newConfig: BaseVisConfig) => {
_setVisConfig((oldConfig) => {
return {
current: newConfig,
consistent: oldConfig.current.type !== newConfig.type ? oldConfig.consistent : newConfig,
};
});
}, []);

React.useEffect(() => {
const vis = getVisByType(inconsistentVisConfig?.type);
if (vis) {
const newConfig = vis.mergeConfig(columns, inconsistentVisConfig);
_setVisConfig({ current: newConfig, consistent: newConfig });
}

// DANGER:: this useEffect should only occur when the visConfig.type changes. adding visconfig into the dep array will cause an infinite loop.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [inconsistentVisConfig?.type, getVisByType]);
? ({
type: ESupportedPlotlyVis.SCATTER,
numColumnsSelected: [],
color: null,
numColorScaleType: ENumericalColorScaleType.SEQUENTIAL,
shape: null,
dragMode: EScatterSelectSettings.RECTANGLE,
alphaSliderVal: 0.5,
} as BaseVisConfig)
: ({
type: ESupportedPlotlyVis.BAR,
multiples: null,
group: null,
direction: EBarDirection.HORIZONTAL,
display: EBarDisplayType.ABSOLUTE,
groupType: EBarGroupingType.STACK,
numColumnsSelected: [],
catColumnSelected: null,
aggregateColumn: null,
aggregateType: EAggregateTypes.COUNT,
} as BaseVisConfig),
onChange: setExternalConfig,
});

useEffect(() => {
if (externalConfig) {
setVisConfig(externalConfig);
}
}, [externalConfig, setVisConfig]);
const setVisConfig = (v: BaseVisConfig) => {
const withDefaults = getVisByType(v.type)?.mergeConfig(columns, v);
_setVisConfig(withDefaults);
};

// Converting the selected list into a map, since searching through the list to find an item is common in the vis components.
const selectedMap: { [key: string]: boolean } = useMemo(() => {
Expand Down Expand Up @@ -309,17 +273,17 @@ export function EagerVis({
};
}, [colors]);

if (!visConfig) {
return <div className="tdp-busy" />;
}

const commonProps = {
showSidebar,
setShowSidebar,
enableSidebar,
};

const Renderer = getVisByType(visConfig?.type)?.renderer;
const Renderer = getVisByType(_visConfig?.type)?.renderer;

if (!_visConfig || !Renderer) {
return null;
}

return (
<Group
Expand All @@ -341,35 +305,33 @@ export function EagerVis({
{enableSidebar && !showSidebar ? <VisSidebarOpenButton onClick={() => setShowSidebar(!showSidebar)} /> : null}

<Stack gap={0} style={{ width: '100%', height: '100%', overflow: 'hidden' }} align="stretch" ref={ref}>
{Renderer ? (
<Renderer
config={visConfig}
dimensions={dimensions}
optionsConfig={{
color: {
enable: true,
},
}}
showDragModeOptions={showDragModeOptions}
shapes={shapes}
setConfig={setVisConfig}
filterCallback={filterCallback}
selectionCallback={selectionCallback}
selectedMap={selectedMap}
selectedList={selected}
columns={columns}
scales={scales}
showSidebar={showSidebar}
showCloseButton={showCloseButton}
closeButtonCallback={closeCallback}
scrollZoom={scrollZoom}
{...commonProps}
/>
) : null}
<Renderer
config={_visConfig}
dimensions={dimensions}
optionsConfig={{
color: {
enable: true,
},
}}
showDragModeOptions={showDragModeOptions}
shapes={shapes}
setConfig={setVisConfig}
filterCallback={filterCallback}
selectionCallback={selectionCallback}
selectedMap={selectedMap}
selectedList={selected}
columns={columns}
scales={scales}
showSidebar={showSidebar}
showCloseButton={showCloseButton}
closeButtonCallback={closeCallback}
scrollZoom={scrollZoom}
{...commonProps}
/>
</Stack>
{showSidebar ? (
<VisSidebarWrapper config={visConfig} setConfig={setVisConfig} onClick={() => setShowSidebar(false)}>
<VisSidebar config={visConfig} columns={columns} filterCallback={filterCallback} setConfig={setVisConfig} />
<VisSidebarWrapper config={_visConfig} setConfig={setVisConfig} onClick={() => setShowSidebar(false)}>
<VisSidebar config={_visConfig} columns={columns} filterCallback={filterCallback} setConfig={setVisConfig} />
</VisSidebarWrapper>
) : null}
</Group>
Expand Down
10 changes: 5 additions & 5 deletions src/vis/heatmap/HeatmapGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,8 @@ export function HeatmapGrid({

return (
<Stack align="center" justify="center" style={{ width: '100%', height: '100%' }} p="sm">
{status === 'pending' ? (
<Loader />
) : !hasAtLeast2CatCols ? (
<InvalidCols headerMessage="Invalid settings" bodyMessage="To create a heatmap chart, select at least 2 categorical columns." />
) : (
{status === 'pending' && <Loader />}
{status === 'success' && hasAtLeast2CatCols && (
<Heatmap
column1={allColumns.catColumn[0]}
column2={allColumns.catColumn[1]}
Expand All @@ -50,6 +47,9 @@ export function HeatmapGrid({
selectionCallback={selectionCallback}
/>
)}
{status === 'success' && !hasAtLeast2CatCols && (
<InvalidCols headerMessage="Invalid settings" bodyMessage="To create a heatmap chart, select at least 2 categorical columns." />
)}
</Stack>
);
}
2 changes: 1 addition & 1 deletion src/vis/heatmap/HeatmapText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function HeatmapText({
isImmediate: boolean;
}) {
const labelSpacing = useMemo(() => {
const maxLabelLength = d3.max(yScale.domain().map((m) => m.length));
const maxLabelLength = d3.max(yScale.domain().map((m) => m?.length));

return maxLabelLength > 5 ? 35 : maxLabelLength * 7;
}, [yScale]);
Expand Down

0 comments on commit 30da502

Please sign in to comment.