Skip to content

Commit

Permalink
Add selected highlight to bars
Browse files Browse the repository at this point in the history
  • Loading branch information
bgoldowsky committed Sep 17, 2024
1 parent f32d814 commit 7041be4
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 26 deletions.
33 changes: 21 additions & 12 deletions src/plugins/bar-graph/bar-graph-content.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { types, Instance } from "mobx-state-tree";
import { isNumber } from "lodash";
import { isObject } from "lodash";
import { ITileContentModel, TileContentModel } from "../../models/tiles/tile-content";
import { kBarGraphTileType, kBarGraphContentType } from "./bar-graph-types";
import { kBarGraphTileType, kBarGraphContentType, BarInfo } from "./bar-graph-types";
import { getSharedModelManager } from "../../models/tiles/tile-environment";
import { SharedDataSet, SharedDataSetType } from "../../models/shared/shared-data-set";
import { clueDataColorInfo } from "../../utilities/color-utils";
Expand Down Expand Up @@ -72,30 +72,38 @@ export const BarGraphContentModel = TileContentModel
return cases.reduce((acc, caseID) => {
const cat = displayValue(dataSet.getStrValue(caseID.__id__, primary));
const subCat = displayValue(dataSet.getStrValue(caseID.__id__, secondary));
const selected = dataSet.isCaseSelected(caseID.__id__);
const index = acc.findIndex(r => r[primary] === cat);
if (index >= 0) {
const cur = acc[index][subCat];
acc[index][subCat] = (isNumber(cur) ? cur : 0) + 1;
if (isObject(cur)) {
acc[index][subCat] = { count: cur.count + 1, selected: cur.selected || selected };
} else {
acc[index][subCat] = { count: 1, selected };
}
} else {
const newRow = { [primary]: cat, [subCat]: 1 };
const newRow = { [primary]: cat, [subCat]: { count: 1, selected } };
acc.push(newRow);
}
return acc;
}, [] as { [key: string]: number | string }[]);
}, [] as { [key: string]: BarInfo | string }[]);
} else {
// One-dimensional data
return cases.reduce((acc, caseID) => {
const cat = displayValue(dataSet.getStrValue(caseID.__id__, primary));
const selected = dataSet.isCaseSelected(caseID.__id__);
const index = acc.findIndex(r => r[primary] === cat);
if (index >= 0) {
const cur = acc[index].value;
acc[index].value = isNumber(cur) ? cur + 1 : 1;
if (isObject(cur)) {
acc[index].value = { count: cur.count + 1, selected: cur.selected || selected };
}
} else {
const newRow = { [primary]: cat, value: 1 };
const newRow = { [primary]: cat, value: { count: 1, selected } };
acc.push(newRow);
}
return acc;
}, [] as { [key: string]: number | string }[]);
}, [] as { [key: string]: BarInfo | string }[]);
}
}
}))
Expand All @@ -106,13 +114,14 @@ export const BarGraphContentModel = TileContentModel
return self.dataArray.map(d => d[primary] as string);
},
get secondaryKeys() {
const primary = self.primaryAttribute;
if (!primary) return [];
return Array.from(new Set(self.dataArray.flatMap(d => Object.keys(d)).filter(k => k !== primary)));
const dataSet = self.sharedModel?.dataSet;
const secondary = self.secondaryAttribute;
if (!secondary || !dataSet || !self.cases) return [];
return Array.from(new Set(self.cases.map(caseID => displayValue(dataSet.getStrValue(caseID.__id__, secondary)))));
},
get maxDataValue(): number {
return self.dataArray.reduce((acc, row) => {
const rowValues = Object.values(row).filter(v => isNumber(v)) as number[];
const rowValues = Object.values(row).map(v => isObject(v) ? v.count : 0);
const maxInRow = Math.max(...rowValues);
return Math.max(maxInRow, acc);
}, 0);
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/bar-graph/bar-graph-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export const kBarGraphTileType = "BarGraph";
export const kBarGraphContentType = "BarGraphContentModel";

export const kBarGraphDefaultHeight = 320;

export type BarInfo = { count: number, selected: boolean };
68 changes: 54 additions & 14 deletions src/plugins/bar-graph/chart-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { GridRows } from "@visx/grid";
import { Group } from "@visx/group";
import { scaleBand, scaleLinear } from "@visx/scale";
import { Bar, BarGroup } from "@visx/shape";
import { PositionScale } from "@visx/shape/lib/types";
import { useBarGraphModelContext } from "./bar-graph-content-context";
import { CategoryPulldown } from "./category-pulldown";
import EditableAxisLabel from "./editable-axis-label";
import { logBarGraphEvent, roundTo5 } from "./bar-graph-utils";
import { BarInfo } from "./bar-graph-types";

const margin = {
top: 7,
Expand Down Expand Up @@ -96,16 +98,13 @@ export const ChartArea = observer(function BarGraphChart({ width, height }: IPro
<Group>
{data.map((d) => {
const key = d[primary] as string;
const val = d.value as number;
const info = d.value as BarInfo;
const x = primaryScale(key) || 0;
const y = countScale(info.count);
const w = primaryScale.bandwidth();
const h = yMax - countScale(info.count);
return (
<Bar
key={key}
x={primaryScale(key) || 0}
y={countScale(val)}
width={primaryScale.bandwidth()}
height={yMax - countScale(val)}
fill={color}
/>
<BarWithHighlight key={key} x={x} y={y} width={w} height={h} color={color} selected={info.selected} />
);
})}
</Group>
Expand All @@ -122,21 +121,25 @@ export const ChartArea = observer(function BarGraphChart({ width, height }: IPro
x0={(d) => d[primary] as string}
x0Scale={primaryScale}
x1Scale={secondaryScale}
yScale={countScale}
yScale={((info: BarInfo) => countScale(info?.count||0)) as PositionScale}
>
{(barGroups) =>
<Group className="visx-bar-group">
{barGroups.map((barGroup) => (
<Group key={`bar-group-${barGroup.index}-${barGroup.x0}`} left={barGroup.x0}>
<Group key={`bar-group-${barGroup.index}`} left={barGroup.x0}>
{barGroup.bars.map((bar) => {
if (!bar.value) return null;
return <Bar
key={`bar-group-bar-${barGroup.index}-${bar.index}-${bar.value}-${bar.key}`}
// BarGroup really expects the values to be pure numeric, but we're using objects.
// Alternatively, we could drop BarGroup and build the bars manually.
const val = bar.value as unknown as BarInfo;
return <BarWithHighlight
key={`bar-group-bar-${barGroup.index}-${bar.index}`}
x={bar.x}
y={bar.y}
width={bar.width}
height={bar.height}
fill={bar.color}
color={bar.color}
selected={val.selected}
/>;
})}
</Group>
Expand Down Expand Up @@ -192,3 +195,40 @@ export const ChartArea = observer(function BarGraphChart({ width, height }: IPro
</svg>
);
});

interface IBarHighlightProps {
x: number;
y: number;
width: number;
height: number;
}

function BarHighlight({ x, y, width, height }: IBarHighlightProps) {
return(
<rect
x={x - 4}
y={y - 4}
width={width + 8}
height={height + 8}
fill="#14F49E"
/>
);
}

interface IBarWithHighlightProps {
x: number;
y: number;
width: number;
height: number;
color: string;
selected: boolean;
}

function BarWithHighlight({ x, y, width, height, color, selected }: IBarWithHighlightProps) {
return (
<Group>
{selected && <BarHighlight x={x} y={y} width={width} height={height} />}
<Bar x={x} y={y} width={width} height={height} fill={color} />
</Group>
);
}

0 comments on commit 7041be4

Please sign in to comment.