Skip to content

Commit

Permalink
Merge pull request #2399 from concord-consortium/185294648-bar-graph-…
Browse files Browse the repository at this point in the history
…legend

Bar graph responsive styling
  • Loading branch information
bgoldowsky authored Sep 16, 2024
2 parents 91a6e2d + d8acf33 commit 1813675
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 69 deletions.
29 changes: 20 additions & 9 deletions cypress/e2e/functional/tile_tests/bar_graph_tile_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ context('Bar Graph Tile', function () {
it('Can link data ', function () {
beforeTest();

clueCanvas.addTile('bargraph');
barGraph.getTiles().click();
barGraph.getYAxisLabel().should('have.text', 'Counts');
barGraph.getXAxisPulldown().should('have.text', 'Categories');
barGraph.getYAxisTickLabel().should('not.exist');
barGraph.getXAxisTickLabel().should('not.exist');
barGraph.getLegendArea().should('not.exist');
barGraph.getBar().should('not.exist');

// Table dataset for testing:
// 4 instances of X / Y / Z
// 2 instances of XX / Y / Z
Expand All @@ -88,16 +97,8 @@ context('Bar Graph Tile', function () {
['X', 'Y', 'Z'],
]);

clueCanvas.addTile('bargraph');
barGraph.getTiles().click();
barGraph.getYAxisLabel().should('have.text', 'Counts');
barGraph.getXAxisPulldown().should('have.text', 'Categories');
barGraph.getYAxisTickLabel().should('not.exist');
barGraph.getXAxisTickLabel().should('not.exist');
barGraph.getLegendArea().should('not.exist');
barGraph.getBar().should('not.exist');

cy.log('Link bar graph');
barGraph.getTile().click();
clueCanvas.clickToolbarButton('bargraph', 'link-tile');
cy.get('select').select('Table Data 1');
cy.get('.modal-button').contains("Graph It!").click();
Expand All @@ -111,6 +112,16 @@ context('Bar Graph Tile', function () {
barGraph.getSortByMenuButton().should('have.text', 'None');
barGraph.getSecondaryValueName().should('have.length', 1).and('have.text', 'x');

cy.log('Legend should move to bottom when tile is narrow');
barGraph.getTileContent().should('have.class', 'horizontal').and('not.have.class', 'vertical');
clueCanvas.addTileByDrag('table', 'right');
clueCanvas.addTileByDrag('table', 'right');
barGraph.getTileContent().should('have.class', 'vertical').and('not.have.class', 'horizontal');
clueCanvas.getUndoTool().click(); // undo add table
clueCanvas.getUndoTool().click(); // undo add table
tableTile.getTableTile().should('have.length', 1);
barGraph.getTileContent().should('have.class', 'horizontal').and('not.have.class', 'vertical');

cy.log('Change Sort By');
barGraph.getSortByMenuButton().click();
barGraph.getChakraMenuItem().should('have.length', 3);
Expand Down
4 changes: 4 additions & 0 deletions cypress/support/elements/tile/BarGraphTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ class BarGraphTile {
return this.getTile(tileIndex, workspaceClass).find(`.editable-tile-title-text`);
}

getTileContent(tileIndex = 0, workspaceClass) {
return this.getTile(tileIndex, workspaceClass).find(`[data-testid="bar-graph-content"]`);
}

getChakraMenuItem(tileIndex = 0, workspaceClass) {
return cy.get(`body .chakra-portal button`).filter(':visible');
}
Expand Down
56 changes: 44 additions & 12 deletions src/plugins/bar-graph/bar-graph-tile.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,80 @@
import React from "react";
import React, { useRef } from "react";
import classNames from "classnames";
import { observer } from "mobx-react";
import { useResizeDetector } from "react-resize-detector";

import { ChartArea } from "./chart-area";
import { LegendArea } from "./legend-area";
import { BasicEditableTileTitle } from "../../components/tiles/basic-editable-tile-title";
import { ITileProps } from "../../components/tiles/tile-component";
import { ChartArea } from "./chart-area";
import { BarGraphModelContext } from "./bar-graph-content-context";
import { isBarGraphModel } from "./bar-graph-content";
import { TileToolbar } from "../../components/toolbar/tile-toolbar";
import { LegendArea } from "./legend-area";

import "./bar-graph.scss";

import "./bar-graph-toolbar";

const legendWidth = 190;
const legendHeight = 190; // FIXME

export const BarGraphComponent: React.FC<ITileProps> = observer((props: ITileProps) => {

const { model, readOnly } = props;
const { model, readOnly, onRequestRowHeight } = props;
const content = isBarGraphModel(model.content) ? model.content : null;

const {height: containerHeight, width: containerWidth, ref} = useResizeDetector();
const requestedHeight = useRef<number|undefined>(undefined);

const onResize = (width: number|undefined, height: number|undefined) => {
let desiredTileHeight;
if (height) {
if (legendBelow) {
const desiredLegendHeight = height;
desiredTileHeight = 300 + desiredLegendHeight;

Check warning on line 31 in src/plugins/bar-graph/bar-graph-tile.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/bar-graph/bar-graph-tile.tsx#L30-L31

Added lines #L30 - L31 were not covered by tests
} else {
const desiredLegendHeight = Math.max(height, 260); // Leave room for at least 5 rows per spec
desiredTileHeight = desiredLegendHeight + 66;

Check warning on line 34 in src/plugins/bar-graph/bar-graph-tile.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/bar-graph/bar-graph-tile.tsx#L33-L34

Added lines #L33 - L34 were not covered by tests
}
if (requestedHeight.current !== desiredTileHeight) {
requestedHeight.current = desiredTileHeight;
onRequestRowHeight(model.id, desiredTileHeight);

Check warning on line 38 in src/plugins/bar-graph/bar-graph-tile.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/bar-graph/bar-graph-tile.tsx#L37-L38

Added lines #L37 - L38 were not covered by tests
}
}
};

// We use two resize detectors to track the size of the container and the size of the legend area
const { height: containerHeight, width: containerWidth, ref: containerRef } = useResizeDetector();

const { height: legendHeight, ref: legendRef } = useResizeDetector({
refreshMode: 'debounce',
refreshRate: 500,
skipOnMount: false,
onResize
});

let svgWidth = 10, svgHeight = 10;
// Legend is on the right if the width is >= 450px, otherwise below
const legendBelow = containerWidth && containerWidth < 450;
if (containerWidth && containerHeight) {
// Legend is on the right if the width is >= 450px
svgWidth = legendBelow ? containerWidth : containerWidth-legendWidth;
svgHeight = legendBelow ? containerHeight-legendHeight : containerHeight;
if (legendBelow) {
const vertPadding = 18;
svgWidth = containerWidth;
svgHeight = containerHeight - vertPadding - (legendHeight || 0);
} else {
svgWidth = containerWidth - legendWidth;
svgHeight = containerHeight;
}
}

return (
<BarGraphModelContext.Provider value={content}>
<BasicEditableTileTitle />
<TileToolbar tileType="bargraph" readOnly={!!readOnly} tileElement={props.tileElt} />
<div
ref={ref}
ref={containerRef}
className={classNames("bar-graph-content", legendBelow ? "vertical" : "horizontal", { "read-only": readOnly })}
data-testid="bar-graph-content"
>
<ChartArea width={svgWidth} height={svgHeight} />
<LegendArea />
<LegendArea legendRef={legendRef} />
</div>
</BarGraphModelContext.Provider>
);
Expand Down
52 changes: 40 additions & 12 deletions src/plugins/bar-graph/bar-graph.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,7 @@
align-items: center;
overflow: auto;

&.vertical {
flex-direction: column;
}

svg.bar-graph-svg {
// width: 100%;
// height: 100%;

.visx-bar-group .visx-bar {
stroke: black;
Expand Down Expand Up @@ -104,12 +98,46 @@
}
}

&.vertical div.bar-graph-legend {
width: 100%;
height: 186px;
border-top: 1.5px solid #0592af;
border-left: none;
padding: 8px 0 0 0;
// Overrides for vertical (legend underneath) layout
&.vertical {
flex-direction: column;

div.bar-graph-legend {
width: 100%;
height: auto;
border-top: 1.5px solid #0592af;
border-left: none;
padding: 8px 0 0 0;

.dataset-header {
margin-left: 5px;
align-items: center;

.dataset-label-text, .dataset-name {
display: inline-block;
margin-right: 5px;
}
}

.sort-by {
margin: 0 0 10px 20px;

button.chakra-menu__menu-button {
width: auto;
margin: 5px 0 0 5px;
}
}

.secondary-values {
display: flex;
flex-wrap: wrap;
margin-left: 8px;

.color-button {
margin: 0 7px 0 7px;
}
}
}
}

button.chakra-menu__menu-button {
Expand Down
75 changes: 40 additions & 35 deletions src/plugins/bar-graph/legend-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import { LegendSecondaryRow } from './legend-secondary-row';
import RemoveDataIcon from "../../assets/remove-data-icon.svg";
import DropdownCaretIcon from "../../assets/dropdown-caret.svg";

interface IProps {
legendRef: React.RefObject<HTMLDivElement>;
}

export const LegendArea = observer(function LegendArea () {
export const LegendArea = observer(function LegendArea ({legendRef}: IProps) {
const model = useBarGraphModelContext();

function unlinkDataset() {
Expand Down Expand Up @@ -38,44 +41,46 @@ export const LegendArea = observer(function LegendArea () {

return (
<div className="bar-graph-legend">
<div className="dataset-header">
<div className="dataset-icon">
<a onClick={unlinkDataset} aria-label={`Unlink ${model.dataSet.name}`}>
<RemoveDataIcon/>
</a>
<div className="inner-container" ref={legendRef}>
<div className="dataset-header">
<div className="dataset-icon">
<a onClick={unlinkDataset} aria-label={`Unlink ${model.dataSet.name}`}>
<RemoveDataIcon />
</a>
</div>
<div className="dataset-label">
<span className="dataset-label-text">Data from:</span>
<span className="dataset-name">{model.dataSet.name}</span>
</div>
</div>
<div className="dataset-label">
<span className="dataset-label-text">Data from:</span>
<span className="dataset-name">{model.dataSet.name}</span>
</div>
</div>

<div className="sort-by">
<div>
Sort by:
<div className="sort-by">
<span>
Sort by:
</span>
<Menu boundary="scrollParent">
<MenuButton>
<span className="button-content">
<span className="button-text">{currentLabel}</span>
<DropdownCaretIcon />
</span>
</MenuButton>
<Portal>
<MenuList>
<MenuItem onClick={() => setSecondaryAttribute(undefined)}>None</MenuItem>

Check warning on line 70 in src/plugins/bar-graph/legend-area.tsx

View check run for this annotation

Codecov / codecov/patch

src/plugins/bar-graph/legend-area.tsx#L70

Added line #L70 was not covered by tests
{availableAttributes.map((a) => (
<MenuItem key={a.id} onClick={() => setSecondaryAttribute(a.id)}>{a.name}</MenuItem>
))}
</MenuList>
</Portal>
</Menu>
</div>
<Menu boundary="scrollParent">
<MenuButton>
<span className="button-content">
<span className="button-text">{currentLabel}</span>
<DropdownCaretIcon/>
</span>
</MenuButton>
<Portal>
<MenuList>
<MenuItem onClick={() => setSecondaryAttribute(undefined)}>None</MenuItem>
{availableAttributes.map((a) => (
<MenuItem key={a.id} onClick={() => setSecondaryAttribute(a.id)}>{a.name}</MenuItem>
))}
</MenuList>
</Portal>
</Menu>
</div>

<div className="secondary-values">
{currentSecondary
? secondaryKeys.map((key) => <LegendSecondaryRow key={key} attrValue={key} />)
: <LegendSecondaryRow attrValue={currentPrimary.name}/> }
<div className="secondary-values">
{currentSecondary
? secondaryKeys.map((key) => <LegendSecondaryRow key={key} attrValue={key} />)
: <LegendSecondaryRow attrValue={currentPrimary.name} />}
</div>
</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/graph/graph-registration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { GraphWrapperComponent } from "./components/graph-wrapper-component";
import { createGraphModel, GraphModel } from "./models/graph-model";
import { updateGraphContentWithNewSharedModelIds, updateGraphObjectWithNewSharedModelIds }
from "./utilities/graph-utils";
import { AppConfigModelType } from "../../models/stores/app-config-model";

import Icon from "./assets/graph-icon.svg";
import HeaderIcon from "./assets/graph-tile-id.svg";
import { AppConfigModelType } from "../../models/stores/app-config-model";

function graphAllowsMultipleDataSets(appConfig: AppConfigModelType) {
return !!appConfig.getSetting("defaultSeriesLegend", "graph");
Expand Down

0 comments on commit 1813675

Please sign in to comment.