Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b6336af
add data-navigator dependency
frankelavsky Dec 15, 2024
500791e
add basic dimension navigation to charts
frankelavsky Dec 15, 2024
e1e6124
load view into Navigator, remove excess comments
frankelavsky Dec 16, 2024
be94d24
add rendering property initialization for navigation
frankelavsky Dec 16, 2024
5289911
use latest data-navigator, with dimension compression
frankelavsky Dec 16, 2024
6b090c3
add spatial properties and semantics for dimensions and divisions
frankelavsky Dec 16, 2024
fdea7db
reduce nav dimensions to 2, limit nav to bar
frankelavsky Feb 14, 2025
62da48b
add conditional render of navigator if nav layers exist
frankelavsky Feb 14, 2025
bb18dc0
add navigation event callback to navigator
frankelavsky Feb 16, 2025
c4d8c7a
emit valid vegaId within nav event callback
frankelavsky Feb 16, 2025
0b2eb42
convert navigation to generics for up, down, left, right
frankelavsky Feb 18, 2025
6044ba9
fix placement for stacked bar divisions
frankelavsky Feb 19, 2025
cfa72ba
fix focus indication location for dodged bar
frankelavsky Feb 19, 2025
0588cce
make function to set child spatial properties generic
frankelavsky Feb 19, 2025
7a779a9
feat: add signals and styling for focus
marshallpete Feb 20, 2025
02cdad3
fix: remove debug
marshallpete Feb 20, 2025
c6872f3
feat: add focus to stacked bar dimensions
marshallpete Feb 20, 2025
796e4d1
fix nav element location between dodge, stack, and regular bar
frankelavsky Feb 22, 2025
ed3982a
remove redundant focus indicator on nav element
frankelavsky Feb 22, 2025
7855053
add 2 new elements for mobile fallback
frankelavsky Feb 24, 2025
b28b36d
fix: stop pointer-events on navigation nodes
frankelavsky Feb 24, 2025
6e842c4
remove console log
frankelavsky Mar 1, 2025
589fb35
use divs for better mobile detection, improve division labels
frankelavsky Mar 1, 2025
a6caf04
remove console log
frankelavsky Mar 1, 2025
7aa9ccc
fix: navigation now maintains direction at childmost level
frankelavsky Mar 2, 2025
dd90d05
fix: use the same dimension behavior for all navigation
frankelavsky Mar 2, 2025
2cf6f15
add spatial names for navigation constants
frankelavsky Mar 4, 2025
1e896a4
refactor: build navigation downstream using spec
frankelavsky Mar 4, 2025
5ee8183
remove unused logs
frankelavsky Mar 4, 2025
3cbcc7e
fix: typo on image role semantics
frankelavsky Mar 4, 2025
e4e5a7f
fix: add state for mobile fallback elements
frankelavsky Mar 4, 2025
9b21c83
clean up commented blocks
frankelavsky Mar 4, 2025
2f601eb
fix: remove mobile fallback elements
frankelavsky Mar 4, 2025
bb7afca
hide vega chart from screen readers
frankelavsky Mar 4, 2025
f4af887
fix: hide entry button until focused
frankelavsky Mar 4, 2025
9ba7cda
fix: remove repeated id usage from navigator
frankelavsky Mar 4, 2025
eb0b982
feat: add blur handling to navigation events
frankelavsky Mar 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@
},
"dependencies": {
"d3-format": "^3.1.0",
"data-navigator": "^2.1.0",
"immer": ">= 9.0.0",
"uuid": ">= 9.0.0",
"vega-embed": ">= 6.27.0",
Expand Down
30 changes: 30 additions & 0 deletions src/Chart.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,33 @@ this removes transitions in the vega tooltip
width: auto;
height: 100%;
}

/* navigation elements */
.dn-root {
position: relative;
}
.dn-wrapper {
position: absolute;
top: 0px;
left: 0px;
}
.dn-node {
position: absolute;
padding: 0px;
margin: 0px;
overflow: visible;
}
.dn-node-svg {
position: absolute;
pointer-events: none;
}
.dn-node-path {
fill: none;
stroke: #000000;
stroke-width: 4px;
transform: translateY(2px);
}
.dn-entry-button {
position: relative;
top: -21px;
}
373 changes: 373 additions & 0 deletions src/Navigator.tsx

Large diffs are not rendered by default.

36 changes: 33 additions & 3 deletions src/RscChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
SELECTED_ITEM,
SELECTED_SERIES,
SERIES_ID,
NAVIGATION_ID_KEY
} from '@constants';
import useChartImperativeHandle from '@hooks/useChartImperativeHandle';
import useLegend from '@hooks/useLegend';
Expand All @@ -40,20 +41,24 @@ import {
import { renderToStaticMarkup } from 'react-dom/server';
import { Item } from 'vega';
import { Handler, Position, Options as TooltipOptions } from 'vega-tooltip';
import { addSimpleDataIDs as addNavigationIds } from '../node_modules/data-navigator/dist/structure.js'

import { ActionButton, Dialog, DialogTrigger, View as SpectrumView } from '@adobe/react-spectrum';

import './Chart.css';
import { VegaChart } from './VegaChart';
import { Navigator } from 'Navigator';

import {
ChartHandle,
ColorScheme,
Datum,
LegendDescription,
MarkBounds,
NavigationEvent,
RscChartProps,
TooltipAnchor,
TooltipPlacement,
TooltipPlacement
} from './types';

interface ChartDialogProps {
Expand Down Expand Up @@ -112,6 +117,18 @@ export const RscChart = forwardRef<ChartHandle, RscChartProps>(

const sanitizedChildren = sanitizeRscChartChildren(props.children);

/*
chartLayers and addNavigationIds ensure that our Navigator can correctly build and use both data
and vega's view (rendered) properties for each datum, adding NAVIGATION_ID_KEY allows us to link
the data in our navigation structure to the rendering data vega creates from the spec
Note: chartLayers is mutated/populated by useSpec and addNavigationIds mutates the input data
*/
const chartLayers = [];
addNavigationIds({
idKey: NAVIGATION_ID_KEY,
data,
addIds:true
})
// THE MAGIC, builds our spec
const spec = useSpec({
backgroundColor,
Expand All @@ -130,9 +147,10 @@ export const RscChart = forwardRef<ChartHandle, RscChartProps>(
opacities,
colorScheme,
title,
chartLayers,
UNSAFE_vegaSpec,
});

// see controlledHoveredIdSignal for pattern to update focus signal
const { controlledHoveredIdSignal, controlledHoveredGroupSignal } = useSpecProps(spec);
const chartConfig = useMemo(() => getChartConfig(config, colorScheme), [config, colorScheme]);

Expand All @@ -149,7 +167,7 @@ export const RscChart = forwardRef<ChartHandle, RscChartProps>(
}, [isPopoverOpen]);

useChartImperativeHandle(forwardedRef, { chartView, title });

const {
legendHiddenSeries,
setLegendHiddenSeries,
Expand Down Expand Up @@ -224,12 +242,17 @@ export const RscChart = forwardRef<ChartHandle, RscChartProps>(
if (legendIsToggleable) {
signals.hiddenSeries = legendHiddenSeries;
}
// see "selected"
signals[SELECTED_ITEM] = selectedData?.[idKey] ?? null;
signals[SELECTED_SERIES] = selectedData?.[SERIES_ID] ?? null;

return signals;
}, [colorScheme, idKey, legendHiddenSeries, legendIsToggleable]);

const navigationEventCallback = (navData: NavigationEvent) => {
console.log("RSC chart can use this navData to set signals", navData)
// set signals here!
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update for @marshallpete:

I solved our valid ID issue and am emitting events from the Navigator. (This function in RSCChart is passed down to our Navigator and is where we want to now call a function to set a signal.)

So basically, we now just need to create a new signal and manipulate it here.

The NavigationEvent type sends the following:

    eventType: "focus" | "blur" | "selection" | "enter" | "exit" | "help";
    // the data navigator ID of the node being focused, blurred, exited from, selected, etc
    nodeId: string;
    // the vega-compatible ID of the node being focused, blurred, exited from, selected, etc
    vegaId: string;
    nodeLevel: "dimension" | "division" | "child";

This allows us to turn our soon-to-be-made focus signal on/off, activate a "selection" signal (and run functions related to selection, if those exist?), as well as watch for enter, exit, and help events (which I am sure will matter in the future, but right now probably don't matter).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! I've cleared my schedule to work on this today.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woohoo! Awesome. Stoked to see what you assemble. Let me know if you have questions or anything, otherwise I'll wait to see how I can build off of this tomorrow morning.

Also as a note: "focused" has 1 s (not "focussed").

Copy link
Member

@marshallpete marshallpete Feb 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fun fact about me. I exclusively commit code with typos. 😂

}
return (
<>
<div
Expand Down Expand Up @@ -274,6 +297,7 @@ export const RscChart = forwardRef<ChartHandle, RscChartProps>(
if (legendIsToggleable) {
view.signal('hiddenSeries', legendHiddenSeries);
}
// this is where the magic happens
setSelectedSignals({
idKey,
selectedData: selectedData.current,
Expand Down Expand Up @@ -309,6 +333,12 @@ export const RscChart = forwardRef<ChartHandle, RscChartProps>(
popover={popover}
/>
))}
{chartLayers.length ? <Navigator
data={data}
chartView={chartView}
chartLayers={chartLayers}
navigationEventCallback={navigationEventCallback}
></Navigator> : <div/>}
</>
);
}
Expand Down
92 changes: 92 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,95 @@ export enum INTERACTION_MODE {
export const HOVER_SIZE = 3000;
export const HOVER_SHAPE_COUNT = 3;
export const HOVER_SHAPE = 'diamond';

// navigation
export const NAVIGATION_ID_KEY = "dnNodeId"
export const NAVIGATION_SEMANTICS = {
BAR: {
CHILD: "Bar",
DIVISION: "Division",
CHART: "Bar chart"
},
LINE: {
CHILD: "Line",
DIVISION: "Division",
CHART: "Line chart"
},
AREA: {
CHILD: "Area",
DIVISION: "Division",
CHART: "Area chart"
},
PIE: {
CHILD: "Slice",
DIVISION: "Division",
CHART: "Pie chart"
},
STACK: {
CHILD: "Stack",
DIVISION: "Division",
CHART: "Stacked bar chart"
},
SCATTER: {
CHILD: "Data point",
DIVISION: "Division",
CHART: "Scatter plot"
},
DODGE: {
CHILD: "Group",
DIVISION: "Division",
CHART: "Dodged bar chart"
},
COMBO: {
CHILD: "Group",
DIVISION: "Division",
CHART: "Combo chart"
}
}
export const NAVIGATION_RULES = {
// utility keybinds
"exit": {key: 'Escape', direction: 'target'},
"help": {key: 'Quote', direction: 'target'},
"undo": {key: 'SemiColon', direction: 'target'},
// generics
"left": {key: 'Period', direction: 'source'},
"right": {key: 'Comma', direction: 'target'},
"child": {key: 'Enter', direction: 'target'},
// dimension
"previous-dimension": {direction: 'source', key: 'ArrowLeft'},
"next-dimension": {direction: 'target', key: 'ArrowRight'},
"parent-dimension": {direction: 'source', key: 'Backspace'},
// metric
"previous-metric": {direction: 'source', key: 'ArrowUp'},
"next-metric": {direction: 'target', key: 'ArrowDown'},
"parent-metric": {direction: 'source', key: 'Slash'},
// color
"previous-color": {direction: 'source', key: 'BracketLeft'},
"next-color": {direction: 'target', key: 'BracketRight'},
"parent-color": {direction: 'source', key: 'BackSlash'}
}
export const NAVIGATION_PAIRS = {
DIMENSION: {
parent_child: ["parent-dimension","child"],
sibling_sibling: ["previous-dimension","next-dimension"],
},
METRIC: {
parent_child: ["parent-metric","child"],
sibling_sibling: ["previous-metric","next-metric"],
},
COLOR: {
parent_child: ["parent-color","child"],
sibling_sibling: ["previous-color","next-color"],
}
}
/*
child : {key: 'Enter', direction: 'target'}
left : {key: 'ArrowLeft', direction: 'source'}
next-browser : {direction: 'target', key: 'RightBracket'}
next-downloads : {direction: 'target', key: 'Backslash'}
parent-browser : {direction: 'source', key: 'KeyW'}
parent-downloads : {direction: 'source', key: 'KeyJ'}
previous-browser : {direction: 'source', key: 'LeftBracket'}
previous-downloads : {direction: 'source', key: 'Slash'}
right : {key: 'ArrowRight', direction: 'target'}
*/
2 changes: 2 additions & 0 deletions src/hooks/useSpec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default function useSpec({
symbolShapes,
symbolSizes,
title,
chartLayers,
UNSAFE_vegaSpec,
}: SanitizedSpecProps): Spec {
return useMemo(() => {
Expand Down Expand Up @@ -70,6 +71,7 @@ export default function useSpec({
opacities,
symbolShapes,
symbolSizes,
chartLayers,
title,
})
)
Expand Down
1 change: 1 addition & 0 deletions src/specBuilder/chartSpecBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const defaultSpecProps: SanitizedSpecProps = {
symbolShapes: ['rounded-square'],
symbolSizes: ['XS', 'XL'],
title: '',
chartLayers: [],
UNSAFE_vegaSpec: undefined,
};

Expand Down
Loading