Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 22x 22x 22x 22x 22x | import * as React from 'react'; import { ScaleTime } from 'd3-scale'; import { findIndex } from '@fluentui/react/lib/Utilities'; import { ILineDef, LabelLink, ILabelDef } from './LabelLink'; import { IEventsAnnotationProps } from '../LineChart.types'; import { ITheme } from '@fluentui/react/lib/Styling'; import { getColorFromToken } from '../../../utilities/colors'; interface IEventsAnnotationExtendProps extends IEventsAnnotationProps { scale: ScaleTime<number, number>; chartYBottom: number; chartYTop: number; theme: ITheme | undefined; } export const EventsAnnotation: React.FunctionComponent<IEventsAnnotationExtendProps> = props => { const textWidth = props.labelWidth ? props.labelWidth : 105; const textY = props.chartYTop - 20; const lineTopY = textY + 7; const textPadding = 5; const lineHeight = 18; const fontSize = '10pt'; const axisRange = props.scale.range(); const lineDefs: ILineDef[] = props.events.map(e => ({ ...e, x: props.scale(e.date) })); lineDefs.sort((e1, e2) => +e1.date - +e2.date); const fill: string | undefined = props.strokeColor ? getColorFromToken(props.strokeColor, props.theme?.isInverted) : props.theme?.palette.black; const lines = uniqBy(lineDefs, x => x.date.toString()).map((x, i) => ( <line key={i} x1={x.x} x2={x.x} y1={lineTopY} y2={props.chartYBottom} stroke={fill} strokeDasharray="8" /> )); const labelLinks = calculateLabels(lineDefs, textWidth + textPadding, axisRange[1], axisRange[0]).map((x, i) => ( <LabelLink key={i} theme={props.theme} {...{ lineDefs, labelDef: x, textY, textWidth, textLineHeight: lineHeight, textFontSize: fontSize, textColor: props.labelColor, mergedLabel: props.mergedLabel, }} /> )); return ( <> {lines} {labelLinks} </> ); }; function calculateLabels(lineDefs: ILineDef[], textWidth: number, maxX: number, minX: number): ILabelDef[] { const calculateLabel = (lastX: number, currentIdx: number): ILabelDef[] => { // base case 1 Iif (currentIdx === lineDefs.length) { return []; } const { x } = lineDefs[currentIdx]; const leftXBoundary = x - textWidth; // cannot render on top of other text Iif (x < lastX) { return []; } // base case 2 Iif (currentIdx === lineDefs.length - 1) { if (lastX < leftXBoundary) { return [{ x: x, anchor: 'end', aggregatedIdx: [currentIdx] }]; } else Iif (x + textWidth < maxX) { return [{ x: x, anchor: 'start', aggregatedIdx: [currentIdx] }]; } return []; } if (lastX < leftXBoundary) { // label on left side return backtrack(currentIdx, 'end'); } else { // label on right side return backtrack(currentIdx, 'start'); } }; const backtrack = (currentIdx: number, anchor: 'start' | 'end'): ILabelDef[] => { const bd = anchor === 'end' ? lineDefs[currentIdx].x : lineDefs[currentIdx].x + textWidth; let idx = findIndex( lineDefs, ds => ds.x > bd && (ds.x - textWidth >= bd || ds.x + textWidth < maxX), currentIdx + 1, ); Iif (idx === -1) { idx = lineDefs.length; } const aggregatedIdx: number[] = []; for (let i = currentIdx; i < idx; i++) { aggregatedIdx.push(i); } const next = calculateLabel(bd, idx); next.unshift({ x: lineDefs[currentIdx].x, anchor, aggregatedIdx }); return next; }; return calculateLabel(minX, 0); } /** Get unique items of `arr`, comparing based on the result of calling `iteratee` on each item. */ function uniqBy<T>(arr: T[], iteratee: (x: T) => string): T[] { const seen: string[] = []; const result: T[] = []; for (const x of arr) { const comp = iteratee(x); Iif (seen.indexOf(comp) === -1) { result.push(x); seen.push(comp); } } return result; } |