fix(plugins/tech-radar): list rerenders on hover
Signed-off-by: marcofaggian <m@marcofaggian.com>
This commit is contained in:
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useRef } from 'react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import type { Entry, Quadrant, Ring } from '../../utils/types';
|
||||
import RadarPlot from '../RadarPlot';
|
||||
import type { Ring, Quadrant, Entry } from '../../utils/types';
|
||||
import { adjustQuadrants, adjustRings, adjustEntries } from './utils';
|
||||
import { adjustEntries, adjustQuadrants, adjustRings } from './utils';
|
||||
|
||||
export type Props = {
|
||||
width: number;
|
||||
@@ -28,17 +28,27 @@ export type Props = {
|
||||
svgProps?: object;
|
||||
};
|
||||
|
||||
const Radar = (props: Props): JSX.Element => {
|
||||
const { width, height, quadrants, rings, entries } = props;
|
||||
const Radar = ({ width, height, quadrants, rings, entries, ...props }: Props): JSX.Element => {
|
||||
const [adjustedQuadrants, setAdjustedQuadrants] = useState<Array<Quadrant>>(quadrants);
|
||||
const [adjustedRings, setAdjustedRings] = useState<Array<Ring>>(rings);
|
||||
const [adjustedEntries, setAdjustedEntries] = useState<Array<Entry>>(entries);
|
||||
|
||||
const radius = Math.min(width, height) / 2;
|
||||
|
||||
const [activeEntry, setActiveEntry] = useState<Entry>();
|
||||
const node = useRef<SVGSVGElement>(null);
|
||||
|
||||
// TODO(dflemstr): most of this can be heavily memoized if performance becomes a problem
|
||||
adjustQuadrants(quadrants, radius, width, height);
|
||||
adjustRings(rings, radius);
|
||||
adjustEntries(entries, quadrants, rings, radius, activeEntry);
|
||||
useEffect(() => {
|
||||
setAdjustedQuadrants(adjustQuadrants(quadrants, radius, width, height));
|
||||
}, [quadrants, radius, width, height])
|
||||
|
||||
useEffect(() => {
|
||||
setAdjustedRings(adjustRings(rings, radius));
|
||||
}, [radius, rings])
|
||||
|
||||
useEffect(() => {
|
||||
setAdjustedEntries(adjustEntries(entries, adjustedQuadrants, adjustedRings, radius, activeEntry));
|
||||
}, [entries, adjustedQuadrants, adjustedRings, radius, activeEntry])
|
||||
|
||||
return (
|
||||
<svg ref={node} width={width} height={height} {...props.svgProps}>
|
||||
@@ -46,9 +56,9 @@ const Radar = (props: Props): JSX.Element => {
|
||||
width={width}
|
||||
height={height}
|
||||
radius={radius}
|
||||
entries={entries}
|
||||
quadrants={quadrants}
|
||||
rings={rings}
|
||||
entries={adjustedEntries}
|
||||
quadrants={adjustedQuadrants}
|
||||
rings={adjustedRings}
|
||||
activeEntry={activeEntry}
|
||||
onEntryMouseEnter={entry => setActiveEntry(entry)}
|
||||
onEntryMouseLeave={() => setActiveEntry(undefined)}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import color from 'color';
|
||||
import { forceCollide, forceSimulation } from 'd3-force';
|
||||
import Segment from '../../utils/segment';
|
||||
import type { Ring, Quadrant, Entry } from '../../utils/types';
|
||||
import type { Entry, Quadrant, Ring } from '../../utils/types';
|
||||
|
||||
export const adjustQuadrants = (
|
||||
quadrants: Quadrant[],
|
||||
@@ -81,30 +81,33 @@ export const adjustQuadrants = (
|
||||
},
|
||||
];
|
||||
|
||||
quadrants.forEach((quadrant, index) => {
|
||||
return quadrants.slice().map((quadrant, index) => {
|
||||
const legendParam = legendParams[index % 4];
|
||||
|
||||
quadrant.index = index;
|
||||
quadrant.radialMin = (index * Math.PI) / 2;
|
||||
quadrant.radialMax = ((index + 1) * Math.PI) / 2;
|
||||
quadrant.offsetX = index % 4 === 0 || index % 4 === 3 ? 1 : -1;
|
||||
quadrant.offsetY = index % 4 === 0 || index % 4 === 1 ? 1 : -1;
|
||||
quadrant.legendX = legendParam.x;
|
||||
quadrant.legendY = legendParam.y;
|
||||
quadrant.legendWidth = legendParam.width;
|
||||
quadrant.legendHeight = legendParam.height;
|
||||
return ({
|
||||
...quadrant,
|
||||
index,
|
||||
radialMin: (index * Math.PI) / 2,
|
||||
radialMax: ((index + 1) * Math.PI) / 2,
|
||||
offsetX: index % 4 === 0 || index % 4 === 3 ? 1 : -1,
|
||||
offsetY: index % 4 === 0 || index % 4 === 1 ? 1 : -1,
|
||||
legendX: legendParam.x,
|
||||
legendY: legendParam.y,
|
||||
legendWidth: legendParam.width,
|
||||
legendHeight: legendParam.height
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
export const adjustEntries = (
|
||||
entries: Entry[],
|
||||
_entries: Entry[],
|
||||
quadrants: Quadrant[],
|
||||
rings: Ring[],
|
||||
radius: number,
|
||||
activeEntry?: Entry,
|
||||
) => {
|
||||
let seed = 42;
|
||||
entries.forEach((entry, index) => {
|
||||
const entries = _entries.map((entry, index) => {
|
||||
const quadrant = quadrants.find(q => {
|
||||
const match =
|
||||
typeof entry.quadrant === 'object' ? entry.quadrant.id : entry.quadrant;
|
||||
@@ -123,18 +126,21 @@ export const adjustEntries = (
|
||||
if (!ring) {
|
||||
throw new Error(`Unknown ring ${entry.ring} for entry ${entry.id}!`);
|
||||
}
|
||||
const segment = new Segment(quadrant, ring, radius, () => seed++)
|
||||
const point = segment?.random();
|
||||
|
||||
entry.index = index;
|
||||
entry.quadrant = quadrant;
|
||||
entry.ring = ring;
|
||||
entry.segment = new Segment(quadrant, ring, radius, () => seed++);
|
||||
const point = entry.segment.random();
|
||||
entry.x = point.x;
|
||||
entry.y = point.y;
|
||||
entry.active = activeEntry ? entry.id === activeEntry.id : false;
|
||||
entry.color = entry.active
|
||||
? entry.ring.color
|
||||
: color(entry.ring.color).desaturate(0.5).lighten(0.1).string();
|
||||
return ({
|
||||
...entry,
|
||||
index: index,
|
||||
quadrant: quadrant,
|
||||
ring: ring,
|
||||
segment,
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
color: activeEntry && entry.id === activeEntry?.id
|
||||
? entry.ring.color
|
||||
: color(entry.ring.color).desaturate(0.5).lighten(0.1).string(),
|
||||
})
|
||||
});
|
||||
|
||||
const simulation = forceSimulation()
|
||||
@@ -145,9 +151,9 @@ export const adjustEntries = (
|
||||
|
||||
for (
|
||||
let i = 0,
|
||||
n = Math.ceil(
|
||||
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()),
|
||||
);
|
||||
n = Math.ceil(
|
||||
Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()),
|
||||
);
|
||||
i < n;
|
||||
++i
|
||||
) {
|
||||
@@ -160,13 +166,13 @@ export const adjustEntries = (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
};
|
||||
|
||||
export const adjustRings = (rings: Ring[], radius: number) => {
|
||||
rings.forEach((ring, index) => {
|
||||
ring.index = index;
|
||||
ring.outerRadius = ((index + 2) / (rings.length + 1)) * radius;
|
||||
ring.innerRadius =
|
||||
((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius;
|
||||
});
|
||||
};
|
||||
export const adjustRings = (rings: Ring[], radius: number) => rings.slice().map((ring, index) => ({
|
||||
...ring,
|
||||
index,
|
||||
outerRadius: ((index + 2) / (rings.length + 1)) * radius,
|
||||
innerRadius: ((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius
|
||||
}))
|
||||
|
||||
@@ -14,15 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import GetBBoxPolyfill from '../../utils/polyfills/getBBox';
|
||||
|
||||
import RadarLegend, { Props } from './RadarLegend';
|
||||
import RadarLegend from './RadarLegend';
|
||||
import { RadarLegendProps } from './types';
|
||||
|
||||
const minProps: Props = {
|
||||
const minProps: RadarLegendProps = {
|
||||
quadrants: [{ id: 'languages', name: 'Languages' }],
|
||||
rings: [{ id: 'use', name: 'USE', color: '#93c47d' }],
|
||||
entries: [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,21 +15,10 @@
|
||||
*/
|
||||
import { makeStyles, Theme } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { WithLink } from '../../utils/components';
|
||||
import type { Entry, Quadrant, Ring } from '../../utils/types';
|
||||
import { RadarDescription } from '../RadarDescription';
|
||||
import { RadarLegendQuadrant } from './RadarLegendQuadrant';
|
||||
import { RadarLegendProps } from './types';
|
||||
import { setupSegments } from './utils';
|
||||
|
||||
type Segments = {
|
||||
[k: number]: { [k: number]: Entry[] };
|
||||
};
|
||||
|
||||
export type Props = {
|
||||
quadrants: Quadrant[];
|
||||
rings: Ring[];
|
||||
entries: Entry[];
|
||||
onEntryMouseEnter?: (entry: Entry) => void;
|
||||
onEntryMouseLeave?: (entry: Entry) => void;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
quadrant: {
|
||||
@@ -85,209 +74,26 @@ const useStyles = makeStyles<Theme>(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const RadarLegend = (props: Props): JSX.Element => {
|
||||
const RadarLegend = ({
|
||||
quadrants,
|
||||
rings,
|
||||
entries,
|
||||
onEntryMouseEnter,
|
||||
onEntryMouseLeave,
|
||||
...props
|
||||
}: RadarLegendProps
|
||||
): JSX.Element => {
|
||||
const classes = useStyles(props);
|
||||
|
||||
const getSegment = (
|
||||
segmented: Segments,
|
||||
quadrant: Quadrant,
|
||||
ring: Ring,
|
||||
ringOffset = 0,
|
||||
) => {
|
||||
const quadrantIndex = quadrant.index;
|
||||
const ringIndex = ring.index;
|
||||
const segmentedData =
|
||||
quadrantIndex === undefined ? {} : segmented[quadrantIndex] || {};
|
||||
return ringIndex === undefined
|
||||
? []
|
||||
: segmentedData[ringIndex + ringOffset] || [];
|
||||
};
|
||||
|
||||
type RadarLegendRingProps = {
|
||||
ring: Ring;
|
||||
entries: Entry[];
|
||||
onEntryMouseEnter?: Props['onEntryMouseEnter'];
|
||||
onEntryMouseLeave?: Props['onEntryMouseEnter'];
|
||||
};
|
||||
|
||||
type RadarLegendLinkProps = {
|
||||
url?: string;
|
||||
description?: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
const RadarLegendLink = ({
|
||||
url,
|
||||
description,
|
||||
title,
|
||||
}: RadarLegendLinkProps) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const toggle = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
if (description) {
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classes.entryLink}
|
||||
onClick={handleClickOpen}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyPress={toggle}
|
||||
>
|
||||
<span className={classes.entry}>{title}</span>
|
||||
</span>
|
||||
{open && (
|
||||
<RadarDescription
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
title={title ? title : 'no title'}
|
||||
url={url}
|
||||
description={description}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<WithLink url={url} className={classes.entryLink}>
|
||||
<span className={classes.entry}>{title}</span>
|
||||
</WithLink>
|
||||
);
|
||||
};
|
||||
|
||||
const RadarLegendRing = ({
|
||||
ring,
|
||||
entries,
|
||||
onEntryMouseEnter,
|
||||
onEntryMouseLeave,
|
||||
}: RadarLegendRingProps) => {
|
||||
return (
|
||||
<div data-testid="radar-ring" key={ring.id} className={classes.ring}>
|
||||
<h3 className={classes.ringHeading}>{ring.name}</h3>
|
||||
{entries.length === 0 ? (
|
||||
<p className={classes.ringEmpty}>(empty)</p>
|
||||
) : (
|
||||
<ol className={classes.ringList}>
|
||||
{entries.map(entry => (
|
||||
<li
|
||||
key={entry.id}
|
||||
value={(entry.index || 0) + 1}
|
||||
onMouseEnter={
|
||||
onEntryMouseEnter && (() => onEntryMouseEnter(entry))
|
||||
}
|
||||
onMouseLeave={
|
||||
onEntryMouseLeave && (() => onEntryMouseLeave(entry))
|
||||
}
|
||||
>
|
||||
<RadarLegendLink
|
||||
url={entry.url}
|
||||
title={entry.title}
|
||||
description={entry.description}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type RadarLegendQuadrantProps = {
|
||||
segments: Segments;
|
||||
quadrant: Quadrant;
|
||||
rings: Ring[];
|
||||
onEntryMouseEnter: Props['onEntryMouseEnter'];
|
||||
onEntryMouseLeave: Props['onEntryMouseLeave'];
|
||||
};
|
||||
|
||||
const RadarLegendQuadrant = ({
|
||||
segments,
|
||||
quadrant,
|
||||
rings,
|
||||
onEntryMouseEnter,
|
||||
onEntryMouseLeave,
|
||||
}: RadarLegendQuadrantProps) => {
|
||||
return (
|
||||
<foreignObject
|
||||
key={quadrant.id}
|
||||
x={quadrant.legendX}
|
||||
y={quadrant.legendY}
|
||||
width={quadrant.legendWidth}
|
||||
height={quadrant.legendHeight}
|
||||
data-testid="radar-quadrant"
|
||||
>
|
||||
<div className={classes.quadrant}>
|
||||
<h2 className={classes.quadrantHeading}>{quadrant.name}</h2>
|
||||
<div className={classes.rings}>
|
||||
{rings.map(ring => (
|
||||
<RadarLegendRing
|
||||
key={ring.id}
|
||||
ring={ring}
|
||||
entries={getSegment(segments, quadrant, ring)}
|
||||
onEntryMouseEnter={onEntryMouseEnter}
|
||||
onEntryMouseLeave={onEntryMouseLeave}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
);
|
||||
};
|
||||
|
||||
const setupSegments = (entries: Entry[]) => {
|
||||
const segments: Segments = {};
|
||||
|
||||
for (const entry of entries) {
|
||||
const quadrantIndex = entry.quadrant.index;
|
||||
const ringIndex = entry.ring.index;
|
||||
let quadrantData: { [k: number]: Entry[] } = {};
|
||||
if (quadrantIndex !== undefined) {
|
||||
if (segments[quadrantIndex] === undefined) {
|
||||
segments[quadrantIndex] = {};
|
||||
}
|
||||
|
||||
quadrantData = segments[quadrantIndex];
|
||||
}
|
||||
|
||||
let ringData = [];
|
||||
if (ringIndex !== undefined) {
|
||||
if (quadrantData[ringIndex] === undefined) {
|
||||
quadrantData[ringIndex] = [];
|
||||
}
|
||||
|
||||
ringData = quadrantData[ringIndex];
|
||||
}
|
||||
|
||||
ringData.push(entry);
|
||||
}
|
||||
|
||||
return segments;
|
||||
};
|
||||
|
||||
const { quadrants, rings, entries, onEntryMouseEnter, onEntryMouseLeave } =
|
||||
props;
|
||||
|
||||
const segments: Segments = setupSegments(entries);
|
||||
|
||||
return (
|
||||
<g data-testid="radar-legend">
|
||||
{quadrants.map(quadrant => (
|
||||
<RadarLegendQuadrant
|
||||
key={quadrant.id}
|
||||
segments={segments}
|
||||
segments={setupSegments(entries)}
|
||||
quadrant={quadrant}
|
||||
rings={rings}
|
||||
classes={classes}
|
||||
onEntryMouseEnter={onEntryMouseEnter}
|
||||
onEntryMouseLeave={onEntryMouseLeave}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ClassNameMap } from "@material-ui/core/styles/withStyles";
|
||||
import React from "react";
|
||||
import { WithLink } from "../../utils/components";
|
||||
import { RadarDescription } from "../RadarDescription";
|
||||
|
||||
type RadarLegendLinkProps = {
|
||||
url?: string;
|
||||
description?: string;
|
||||
title?: string;
|
||||
classes: ClassNameMap<string>
|
||||
};
|
||||
|
||||
export const RadarLegendLink = ({
|
||||
url,
|
||||
description,
|
||||
title,
|
||||
classes
|
||||
}: RadarLegendLinkProps) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const toggle = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
if (description) {
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className={classes.entryLink}
|
||||
onClick={handleClickOpen}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyPress={toggle}
|
||||
>
|
||||
<span className={classes.entry}>{title}</span>
|
||||
</span>
|
||||
{open && (
|
||||
<RadarDescription
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
title={title ? title : 'no title'}
|
||||
url={url}
|
||||
description={description}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<WithLink url={url} className={classes.entryLink}>
|
||||
<span className={classes.entry}>{title}</span>
|
||||
</WithLink>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ClassNameMap } from "@material-ui/core/styles/withStyles";
|
||||
import React from "react";
|
||||
import { Quadrant, Ring } from "../../utils/types";
|
||||
import { RadarLegendRing } from "./RadarLegendRing";
|
||||
import { RadarLegendProps, Segments } from "./types";
|
||||
import { getSegment } from "./utils";
|
||||
|
||||
|
||||
|
||||
type RadarLegendQuadrantProps = {
|
||||
segments: Segments;
|
||||
quadrant: Quadrant;
|
||||
rings: Ring[];
|
||||
classes: ClassNameMap<string>,
|
||||
onEntryMouseEnter: RadarLegendProps['onEntryMouseEnter'];
|
||||
onEntryMouseLeave: RadarLegendProps['onEntryMouseLeave'];
|
||||
};
|
||||
|
||||
export const RadarLegendQuadrant = ({
|
||||
segments,
|
||||
quadrant,
|
||||
rings,
|
||||
classes,
|
||||
onEntryMouseEnter,
|
||||
onEntryMouseLeave,
|
||||
}: RadarLegendQuadrantProps) => {
|
||||
return (
|
||||
<foreignObject
|
||||
key={quadrant.id}
|
||||
x={quadrant.legendX}
|
||||
y={quadrant.legendY}
|
||||
width={quadrant.legendWidth}
|
||||
height={quadrant.legendHeight}
|
||||
data-testid="radar-quadrant"
|
||||
>
|
||||
<div className={classes.quadrant}>
|
||||
<h2 className={classes.quadrantHeading}>{quadrant.name}</h2>
|
||||
<div className={classes.rings}>
|
||||
{rings.map(ring => (
|
||||
<RadarLegendRing
|
||||
key={ring.id}
|
||||
ring={ring}
|
||||
classes={classes}
|
||||
entries={getSegment(segments, quadrant, ring)}
|
||||
onEntryMouseEnter={onEntryMouseEnter}
|
||||
onEntryMouseLeave={onEntryMouseLeave}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { ClassNameMap } from "@material-ui/core/styles/withStyles";
|
||||
import React from "react";
|
||||
import { Entry, Ring } from "../../utils/types";
|
||||
import { RadarLegendLink } from "./RadarLegendLink";
|
||||
import { RadarLegendProps } from "./types";
|
||||
|
||||
type RadarLegendRingProps = {
|
||||
ring: Ring;
|
||||
entries: Entry[];
|
||||
classes: ClassNameMap<string>;
|
||||
onEntryMouseEnter?: RadarLegendProps['onEntryMouseEnter'];
|
||||
onEntryMouseLeave?: RadarLegendProps['onEntryMouseEnter'];
|
||||
};
|
||||
|
||||
export const RadarLegendRing = ({
|
||||
ring,
|
||||
entries,
|
||||
classes,
|
||||
onEntryMouseEnter,
|
||||
onEntryMouseLeave,
|
||||
}: RadarLegendRingProps) => {
|
||||
return (
|
||||
<div data-testid="radar-ring" key={ring.id} className={classes.ring}>
|
||||
<h3 className={classes.ringHeading}>{ring.name}</h3>
|
||||
{entries.length === 0 ? (
|
||||
<p className={classes.ringEmpty}>(empty)</p>
|
||||
) : (
|
||||
<ol className={classes.ringList}>
|
||||
{entries.map(entry => (
|
||||
<li
|
||||
key={entry.id}
|
||||
value={(entry.index || 0) + 1}
|
||||
onMouseEnter={
|
||||
onEntryMouseEnter && (() => onEntryMouseEnter(entry))
|
||||
}
|
||||
onMouseLeave={
|
||||
onEntryMouseLeave && (() => onEntryMouseLeave(entry))
|
||||
}
|
||||
>
|
||||
<RadarLegendLink
|
||||
classes={classes}
|
||||
url={entry.url}
|
||||
title={entry.title}
|
||||
description={entry.description}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entry, Quadrant, Ring } from "../../utils/types";
|
||||
|
||||
export type Segments = {
|
||||
[k: number]: { [k: number]: Entry[] };
|
||||
};
|
||||
|
||||
export type RadarLegendProps = {
|
||||
quadrants: Quadrant[];
|
||||
rings: Ring[];
|
||||
entries: Entry[];
|
||||
onEntryMouseEnter?: (entry: Entry) => void;
|
||||
onEntryMouseLeave?: (entry: Entry) => void;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entry, Quadrant, Ring } from "../../utils/types";
|
||||
import { Segments } from "./types";
|
||||
|
||||
export const setupSegments = (entries: Entry[]) => {
|
||||
const segments: Segments = {};
|
||||
|
||||
for (const entry of entries) {
|
||||
const quadrantIndex = entry.quadrant.index;
|
||||
const ringIndex = entry.ring.index;
|
||||
let quadrantData: { [k: number]: Entry[] } = {};
|
||||
if (quadrantIndex !== undefined) {
|
||||
if (segments[quadrantIndex] === undefined) {
|
||||
segments[quadrantIndex] = {};
|
||||
}
|
||||
|
||||
quadrantData = segments[quadrantIndex];
|
||||
}
|
||||
|
||||
let ringData = [];
|
||||
if (ringIndex !== undefined) {
|
||||
if (quadrantData[ringIndex] === undefined) {
|
||||
quadrantData[ringIndex] = [];
|
||||
}
|
||||
|
||||
ringData = quadrantData[ringIndex];
|
||||
}
|
||||
|
||||
ringData.push(entry);
|
||||
}
|
||||
|
||||
return segments;
|
||||
};
|
||||
|
||||
export const getSegment = (
|
||||
segmented: Segments,
|
||||
quadrant: Quadrant,
|
||||
ring: Ring,
|
||||
ringOffset = 0,
|
||||
) => {
|
||||
const quadrantIndex = quadrant.index;
|
||||
const ringIndex = ring.index;
|
||||
const segmentedData =
|
||||
quadrantIndex === undefined ? {} : segmented[quadrantIndex] || {};
|
||||
return ringIndex === undefined
|
||||
? []
|
||||
: segmentedData[ringIndex + ringOffset] || [];
|
||||
};
|
||||
Reference in New Issue
Block a user