Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Feature/cor 1268 modified legends #4592

Merged
merged 5 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
84 changes: 45 additions & 39 deletions packages/app/src/components/interactive-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface SelectOption<T = string> {
metricProperty: T;
label: string;
color: string;
shape?: 'line' | 'circle' | 'square' | 'gapped-area';
shape?: 'line' | 'circle' | 'square' | 'gapped-area' | 'dashed';
legendAriaLabel?: string;
}

Expand All @@ -22,13 +22,7 @@ interface InteractiveLegendProps<T = string> {
onReset?: () => void;
}

export function InteractiveLegend<T = string>({
helpText,
selectOptions,
selection,
onToggleItem,
onReset,
}: InteractiveLegendProps<T>) {
export function InteractiveLegend<T = string>({ helpText, selectOptions, selection, onToggleItem, onReset }: InteractiveLegendProps<T>) {
const { commonTexts } = useIntl();

const hasSelection = selection.length !== 0;
Expand All @@ -42,19 +36,17 @@ export function InteractiveLegend<T = string>({
const isSelected = selection.includes(item.metricProperty);
return (
<Item key={item.label}>
<StyledLabel
htmlFor={`checkboxgroup-${item.label}`}
isActive={hasSelection && isSelected}
borderColor={item.color}
data-text={item.label}
>
<StyledLabel htmlFor={`checkboxgroup-${item.label}`} isActive={hasSelection && isSelected} borderColor={item.color} data-text={item.label}>
{item.label}
{item.shape === 'line' && <Line color={item.color} />}
{item.shape === 'dashed' && (
<DashedContainer>
<Dashed color={item.color} />
</DashedContainer>
)}
{item.shape === 'circle' && <Circle color={item.color} />}
{item.shape === 'square' && <Square color={item.color} />}
{item.shape === 'gapped-area' && (
<GappedArea color={item.color} />
)}
{item.shape === 'gapped-area' && <GappedArea color={item.color} />}
</StyledLabel>
<StyledInput
type="checkbox"
Expand Down Expand Up @@ -124,9 +116,7 @@ const StyledLabel = styled.label<{
pl: 33,
py: 1,
borderRadius: '5px',
boxShadow: `inset 0px 0px 0px ${
isActive ? `3px ${borderColor}` : `1px ${colors.gray4}`
}`,
boxShadow: `inset 0px 0px 0px ${isActive ? `3px ${borderColor}` : `1px ${colors.gray4}`}`,
fontWeight: 'normal',
fontFamily: 'inherit',
fontSize: 1,
Expand Down Expand Up @@ -200,26 +190,49 @@ const ResetButton = styled.button<{ isVisible: boolean }>(({ isVisible }) =>
})
);

const Line = styled.div<{ color: string }>(({ color }) =>
const DashedContainer = styled(Box)`
svg {
display: block;
left: 13px;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
`;

interface DashedProps {
color: string;
}

export const Dashed = ({ color }: DashedProps) => {
return (
<svg width={15} height={15} viewBox={`0 0 ${15} ${15}`}>
<line stroke={color} strokeWidth={3} strokeDasharray={4} strokeLinecap="round" strokeLinejoin="round" x1={2} y1={15 / 2} x2={15 - 2} y2={15 / 2} />
</svg>
DariaKwork marked this conversation as resolved.
Show resolved Hide resolved
);
};

const Shape = styled.div<{ color: string }>((x) =>
css({
display: 'block',
position: 'absolute',
left: 13,
backgroundColor: x.color,
})
);

const Line = styled(Shape)(
css({
top: '50%',
transform: 'translateY(-50%)',
width: '15px',
height: '3px',
borderRadius: '2px',
display: 'block',
position: 'absolute',
left: 13,
backgroundColor: color,
})
);

const Circle = styled.div<{ color: string }>(({ color }) =>
const Circle = styled(Shape)(
css({
display: 'block',
position: 'absolute',
left: 13,
backgroundColor: color,
top: '50%',
transform: 'translateY(-50%)',
width: '10px',
Expand All @@ -228,12 +241,8 @@ const Circle = styled.div<{ color: string }>(({ color }) =>
})
);

const Square = styled.div<{ color: string }>(({ color }) =>
const Square = styled(Shape)(
css({
display: 'block',
position: 'absolute',
left: 13,
backgroundColor: color,
top: '50%',
transform: 'translateY(-50%)',
width: '11px',
Expand All @@ -242,11 +251,8 @@ const Square = styled.div<{ color: string }>(({ color }) =>
})
);

const GappedArea = styled.div<{ color: string }>(({ color }) =>
const GappedArea = styled(Shape)<{ color: string }>(({ color }) =>
css({
display: 'block',
position: 'absolute',
left: 13,
backgroundColor: `${color}30`,
borderTop: `2px solid ${color}`,
top: '50%',
Expand Down
66 changes: 17 additions & 49 deletions packages/app/src/components/legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import css, { SystemStyleObject } from '@styled-system/css';
import { ReactNode } from 'react';
import styled from 'styled-components';

type LegendShape =
| 'line'
| 'square'
| 'circle'
| 'dotted-square'
| 'outlined-square';
type LegendShape = 'line' | 'square' | 'circle' | 'dotted-square' | 'outlined-square';
type LegendLineStyle = 'solid' | 'dashed';

export type LegendItem = {
Expand Down Expand Up @@ -44,15 +39,9 @@ export function Legend({ items, columns }: LegendProps) {
<Item key={i}>
{item.label}
{item.shape === 'square' && <Square color={item.color} />}
{item.shape === 'outlined-square' && (
<OutlinedSquare color={item.color} />
)}
{item.shape === 'dotted-square' && (
<DottedSquare color={item.color} />
)}
{item.shape === 'line' && (
<Line color={item.color} lineStyle={item.style ?? 'solid'} />
)}
{item.shape === 'outlined-square' && <OutlinedSquare color={item.color} />}
{item.shape === 'dotted-square' && <DottedSquare color={item.color} />}
{item.shape === 'line' && <Line color={item.color} lineStyle={item.style ?? 'solid'} />}
{item.shape === 'circle' && <Circle color={item.color} />}
</Item>
);
Expand Down Expand Up @@ -111,29 +100,12 @@ function DottedSquare({ color }: { color: string }) {
<Shape color="white" css={css({ top: '3px' })}>
<svg width={16} height={16} viewBox={`0 0 ${16} ${16}`}>
<defs>
<pattern
id="dotted_legend"
width="4"
height="4"
patternUnits="userSpaceOnUse"
>
<line
x1="0"
y1="4"
x2="0"
y2="0"
style={{ stroke: color, strokeWidth: 4, strokeDasharray: 2 }}
/>
<pattern id="dotted_legend" width="4" height="4" patternUnits="userSpaceOnUse">
<line x1="0" y1="4" x2="0" y2="0" style={{ stroke: color, strokeWidth: 4, strokeDasharray: 2 }} />
</pattern>
</defs>
<g>
<rect
x={0}
y={0}
fill={`url(#dotted_legend)`}
width={16}
height={16}
/>
<rect x={0} y={0} fill={`url(#dotted_legend)`} width={16} height={16} />
</g>
</svg>
</Shape>
Expand Down Expand Up @@ -170,18 +142,14 @@ const Circle = styled(Shape)(
})
);

const Line = styled.div<{ color: string; lineStyle: LegendLineStyle }>(
({ color, lineStyle }) =>
css({
display: 'block',
position: 'absolute',
borderTopColor: color as SystemStyleObject,
borderTopStyle: lineStyle,
borderTopWidth: '3px',
top: '10px',
width: '15px',
height: 0,
borderRadius: '2px',
left: 0,
})
const Line = styled(Shape)<{ color: string; lineStyle: LegendLineStyle }>(({ color, lineStyle }) =>
css({
borderTopColor: color as SystemStyleObject,
borderTopStyle: lineStyle,
borderTopWidth: '3px',
top: '10px',
width: '15px',
height: 0,
borderRadius: '2px',
})
);
52 changes: 10 additions & 42 deletions packages/app/src/components/time-series-chart/logic/legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,23 @@ import { useMemo } from 'react';
import { isDefined } from 'ts-is-present';
import { LegendItem } from '~/components/legend';
import { useIntl } from '~/intl';
import {
HatchedTimespanAnnotationIcon,
OutOfBoundsIcon,
SeriesIcon,
SolidTimespanAnnotationIcon,
} from '../components';
import { HatchedTimespanAnnotationIcon, OutOfBoundsIcon, SeriesIcon, SolidTimespanAnnotationIcon } from '../components';
import { TimelineMarker } from '../components/timeline';
import { isVisibleEvent } from '../components/timeline/logic';
import { DataOptions } from './common';
import { isVisible, SeriesConfig } from './series';

type SplitLegendGroup = { label: string; items: LegendItem[] };

export function useLegendItems<T extends TimestampedValue>(
domain: number[],
config: SeriesConfig<T>,
dataOptions?: DataOptions,
hasOutofBoudsValues = false,
forceLegend = false
) {
const { timelineEvents, timespanAnnotations, outOfBoundsConfig } =
dataOptions || {};
export function useLegendItems<T extends TimestampedValue>(domain: number[], config: SeriesConfig<T>, dataOptions?: DataOptions, hasOutofBoudsValues = false, forceLegend = false) {
const { timelineEvents, timespanAnnotations, outOfBoundsConfig } = dataOptions || {};
const { commonTexts } = useIntl();

return useMemo(() => {
const legendItems = config
.filter(isVisible)
.filter((configItem) => !configItem?.hideInLegend)

.map<LegendItem | undefined>((x) => {
switch (x.type) {
case 'split-area':
Expand Down Expand Up @@ -66,20 +55,13 @@ export function useLegendItems<T extends TimestampedValue>(
*/
if (timespanAnnotations) {
for (const annotation of timespanAnnotations) {
const isAnnotationVisible =
(first(domain) as number) <= annotation.end &&
annotation.start <= (last(domain) as number);
const isAnnotationVisible = (first(domain) as number) <= annotation.end && annotation.start <= (last(domain) as number);

if (isAnnotationVisible) {
legendItems.push({
label: annotation.label,
shape: 'custom',
shapeComponent:
annotation.fill === 'solid' || !isDefined(annotation.fill) ? (
<SolidTimespanAnnotationIcon />
) : (
<HatchedTimespanAnnotationIcon />
),
shapeComponent: annotation.fill === 'solid' || !isDefined(annotation.fill) ? <SolidTimespanAnnotationIcon /> : <HatchedTimespanAnnotationIcon />,
} as LegendItem);
}
}
Expand All @@ -89,9 +71,7 @@ export function useLegendItems<T extends TimestampedValue>(
* Add timeline events to the legend
*/
if (timelineEvents) {
const hasVisibleEvents = timelineEvents.some((x) =>
isVisibleEvent(x, domain)
);
const hasVisibleEvents = timelineEvents.some((x) => isVisibleEvent(x, domain));

if (hasVisibleEvents) {
legendItems.push({
Expand Down Expand Up @@ -125,22 +105,10 @@ export function useLegendItems<T extends TimestampedValue>(
* one) to determine if a legend is required. We only have to render a
* legend when there's at least two items.
*/
const isLegendRequired =
forceLegend || legendItems.length + splitLegendGroups.length > 1;

const isLegendRequired = forceLegend || legendItems.length + splitLegendGroups.length > 1;
return {
legendItems: isLegendRequired ? legendItems : undefined,
splitLegendGroups:
splitLegendGroups.length > 0 ? splitLegendGroups : undefined,
splitLegendGroups: splitLegendGroups.length > 0 ? splitLegendGroups : undefined,
};
}, [
config,
domain,
commonTexts,
timelineEvents,
timespanAnnotations,
outOfBoundsConfig,
hasOutofBoudsValues,
forceLegend,
]);
}, [config, domain, commonTexts, timelineEvents, timespanAnnotations, outOfBoundsConfig, hasOutofBoudsValues, forceLegend]);
}
Loading