@awsui/components-react
Version:
On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en
218 lines • 10.3 kB
JavaScript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { useCallback, useMemo, useState } from 'react';
import { KeyCode } from '../../internal/keycode';
import { circleIndex } from '../../internal/utils/circle-index';
import { findNavigableSeries, isXThreshold, isYThreshold, nextValidDomainIndex } from '../utils';
export function useNavigation({ series, visibleSeries, scaledSeries, barGroups, xScale, yScale, highlightedPoint, highlightedGroupIndex, highlightedSeries, isHandlersDisabled, pinPopover, highlightSeries, highlightGroup, highlightPoint, highlightX, verticalMarkerX, isRtl, horizontalBars, }) {
const [targetX, setTargetX] = useState(null);
const [xIndex, setXIndex] = useState(0);
// There are two different types of navigation:
// 1) Group navigation for any chart that contains a bar series
// 2) Line navigation for any chart that only contains lines and thresholds
const isGroupNavigation = useMemo(() => visibleSeries.some(({ series }) => series.type === 'bar'), [visibleSeries]);
// Make a list of series that can be navigated between. Bar series are treated as one.
const { navigableSeries } = useMemo(() => findNavigableSeries(visibleSeries), [visibleSeries]);
const containsMultipleSeries = navigableSeries.length > 1;
const onBarGroupFocus = () => {
const groupIndex = highlightedGroupIndex !== null && highlightedGroupIndex !== void 0 ? highlightedGroupIndex : 0;
setTargetX(xScale.domain[groupIndex]);
highlightGroup(groupIndex);
};
const onLineFocus = () => {
var _a;
if (verticalMarkerX === null) {
const index = !isRtl ? 0 : allUniqueX.length - 1;
if (containsMultipleSeries) {
moveToLineGroupIndex(index);
}
else {
moveBetweenSeries(0, (_a = allUniqueX[index].datum) === null || _a === void 0 ? void 0 : _a.x);
}
}
};
const onFocus = () => {
if (isGroupNavigation) {
onBarGroupFocus();
}
else {
onLineFocus();
}
};
// Returns all the unique X coordinates in scaledSeries.
// Assumes scaledSeries is sorted by `x`.
const allUniqueX = useMemo(() => {
const result = [];
for (let i = 0; i < scaledSeries.length; i += 1) {
const point = scaledSeries[i];
if (point !== undefined && (!result.length || result[result.length - 1].scaledX !== point.x)) {
result.push({ scaledX: point.x, datum: point.datum });
}
}
return result;
}, [scaledSeries]);
const moveBetweenSeries = useCallback((direction, startFrom) => {
var _a, _b, _c, _d, _e;
if (isGroupNavigation) {
return;
}
const xOffset = xScale.isCategorical() ? Math.max(0, xScale.d3Scale.bandwidth() - 1) / 2 : 0;
const MAX_SERIES_INDEX = navigableSeries.length - 1;
// Find the index of the currently highlighted series (if any)
let previousSeriesIndex = -1;
if (highlightedSeries) {
previousSeriesIndex = navigableSeries.indexOf(highlightedSeries);
}
// Move forwards or backwards to the new series
// If index === -1, show all data points from all series at the given X instead of one single series
const firstPossibleIndex = containsMultipleSeries ? -1 : 0;
let nextSeriesIndex = 0;
if (previousSeriesIndex !== null) {
nextSeriesIndex = previousSeriesIndex + direction;
if (nextSeriesIndex > MAX_SERIES_INDEX) {
nextSeriesIndex = firstPossibleIndex;
}
else if (nextSeriesIndex < firstPossibleIndex) {
nextSeriesIndex = MAX_SERIES_INDEX;
}
}
if (nextSeriesIndex === -1) {
highlightSeries(null);
highlightPoint(null);
return;
}
const nextSeries = navigableSeries[nextSeriesIndex];
const nextInternalSeries = series.filter(({ series }) => series === nextSeries)[0];
const actualTargetX = (_a = targetX !== null && targetX !== void 0 ? targetX : startFrom) !== null && _a !== void 0 ? _a : null;
// 2. Find point in the next series
let targetXPoint = ((_b = xScale.d3Scale(actualTargetX)) !== null && _b !== void 0 ? _b : NaN) + xOffset;
if (!isFinite(targetXPoint)) {
targetXPoint = 0;
}
if (nextSeries.type === 'line') {
const nextScaledSeries = scaledSeries.filter(it => it.series === nextSeries);
const closestNextSeriesPoint = nextScaledSeries.reduce((prev, curr) => (Math.abs(curr.x - targetXPoint) < Math.abs(prev.x - targetXPoint) ? curr : prev), { x: -Infinity, y: -Infinity });
highlightPoint(Object.assign(Object.assign({}, closestNextSeriesPoint), { color: nextInternalSeries.color, series: nextSeries }));
}
else if (isYThreshold(nextSeries)) {
const scaledTargetIndex = scaledSeries.map(it => { var _a; return ((_a = it.datum) === null || _a === void 0 ? void 0 : _a.x) || null; }).indexOf(actualTargetX);
highlightPoint({
x: targetXPoint,
y: (_c = yScale.d3Scale(nextSeries.y)) !== null && _c !== void 0 ? _c : NaN,
color: nextInternalSeries.color,
series: nextSeries,
datum: (_d = scaledSeries[scaledTargetIndex]) === null || _d === void 0 ? void 0 : _d.datum,
});
}
else if (isXThreshold(nextSeries)) {
highlightPoint({
x: (_e = xScale.d3Scale(nextSeries.x)) !== null && _e !== void 0 ? _e : NaN,
y: yScale.d3Scale.range()[0],
color: nextInternalSeries.color,
series: nextSeries,
datum: { x: nextSeries.x, y: NaN },
});
}
}, [
isGroupNavigation,
xScale,
navigableSeries,
highlightedSeries,
containsMultipleSeries,
highlightSeries,
highlightPoint,
series,
targetX,
scaledSeries,
yScale,
]);
const moveWithinSeries = useCallback((direction) => {
var _a;
const series = highlightedSeries || visibleSeries[0].series;
if (series.type === 'line' || isYThreshold(series)) {
const targetScaledSeries = scaledSeries.filter(it => it.series === series);
const previousPoint = highlightedPoint || targetScaledSeries[0];
const indexOfPreviousPoint = targetScaledSeries.map(it => it.x).indexOf(previousPoint.x);
const nextPointIndex = circleIndex(indexOfPreviousPoint + direction, [0, targetScaledSeries.length - 1]);
const nextPoint = targetScaledSeries[nextPointIndex];
setTargetX(((_a = nextPoint.datum) === null || _a === void 0 ? void 0 : _a.x) || null);
setXIndex(nextPointIndex);
highlightPoint(nextPoint);
}
else if (series.type === 'bar') {
const xDomain = xScale.domain;
const MAX_GROUP_INDEX = xDomain.length - 1;
let nextGroupIndex = 0;
if (highlightedGroupIndex !== null) {
if (isRtl && !horizontalBars) {
direction = -direction;
}
// find next group
nextGroupIndex = highlightedGroupIndex + direction;
if (nextGroupIndex > MAX_GROUP_INDEX) {
nextGroupIndex = 0;
}
else if (nextGroupIndex < 0) {
nextGroupIndex = MAX_GROUP_INDEX;
}
}
const nextDomainIndex = nextValidDomainIndex(nextGroupIndex, barGroups, direction);
setTargetX(xDomain[nextDomainIndex]);
highlightGroup(nextDomainIndex);
}
}, [
highlightedSeries,
visibleSeries,
scaledSeries,
highlightedPoint,
highlightPoint,
xScale.domain,
highlightedGroupIndex,
barGroups,
highlightGroup,
isRtl,
horizontalBars,
]);
const moveToLineGroupIndex = useCallback((index) => {
var _a, _b, _c, _d;
const point = allUniqueX[index];
setXIndex(index);
setTargetX(((_a = point.datum) === null || _a === void 0 ? void 0 : _a.x) || null);
highlightX({ scaledX: (_b = point === null || point === void 0 ? void 0 : point.scaledX) !== null && _b !== void 0 ? _b : null, label: (_d = (_c = point.datum) === null || _c === void 0 ? void 0 : _c.x) !== null && _d !== void 0 ? _d : null });
}, [allUniqueX, highlightX]);
const moveWithinXAxis = useCallback((direction) => {
if (highlightedSeries || isGroupNavigation) {
moveWithinSeries(direction);
}
else {
const nextPointGroupIndex = circleIndex(xIndex + direction, [0, allUniqueX.length - 1]);
moveToLineGroupIndex(nextPointGroupIndex);
}
}, [highlightedSeries, isGroupNavigation, moveWithinSeries, xIndex, allUniqueX.length, moveToLineGroupIndex]);
const onKeyDown = useCallback((event) => {
const keyCode = event.keyCode;
if (keyCode !== KeyCode.up &&
keyCode !== KeyCode.right &&
keyCode !== KeyCode.down &&
keyCode !== KeyCode.left &&
keyCode !== KeyCode.space &&
keyCode !== KeyCode.enter) {
return;
}
event.preventDefault();
if (isHandlersDisabled) {
return;
}
if (keyCode === KeyCode.down || keyCode === KeyCode.up) {
moveBetweenSeries(keyCode === KeyCode.down ? 1 : -1);
}
else if (keyCode === KeyCode.left || keyCode === KeyCode.right) {
moveWithinXAxis(keyCode === KeyCode.right ? 1 : -1);
}
else if (keyCode === KeyCode.enter || keyCode === KeyCode.space) {
pinPopover();
}
}, [isHandlersDisabled, moveBetweenSeries, moveWithinXAxis, pinPopover]);
return { isGroupNavigation, onFocus, onKeyDown, xIndex };
}
//# sourceMappingURL=use-navigation.js.map