mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
Web [Charts]: Add experimental auto axis components (#39805)
* Polish SVGRoot components API * Adjust truncation logic (change core components API) * Fix line chart according to the new core components API * Migrate BarChart component to the new core components abstractions * Fix ts problems with scales and elements props * Fix PR review comments (legacy styles, comment out lines, storybook stories code style) * Turn on snapshots for axis demo
This commit is contained in:
parent
6e1937d673
commit
df62e03eb3
@ -1,18 +1,14 @@
|
||||
import { ReactElement, SVGProps, useMemo, useRef, useState } from 'react'
|
||||
import { ReactElement, SVGProps, useMemo } from 'react'
|
||||
|
||||
import { scaleBand, scaleLinear } from '@visx/scale'
|
||||
import classNames from 'classnames'
|
||||
import { ScaleBand } from 'd3-scale'
|
||||
import { noop } from 'lodash'
|
||||
|
||||
import { AxisBottom, AxisLeft, getChartContentSizes, Tooltip } from '../../core'
|
||||
import { SvgAxisBottom, SvgAxisLeft, SvgContent, SvgRoot } from '../../core/components/SvgRoot'
|
||||
import { CategoricalLikeChart } from '../../types'
|
||||
|
||||
import { GroupedBars } from './components/GroupedBars'
|
||||
import { StackedBars } from './components/StackedBars'
|
||||
import { BarTooltipContent } from './components/TooltipContent'
|
||||
import { Category, getGroupedCategories } from './utils/get-grouped-categories'
|
||||
|
||||
import styles from './BarChart.module.scss'
|
||||
import { BarChartContent } from './BarChartContent'
|
||||
import { getGroupedCategories } from './utils/get-grouped-categories'
|
||||
|
||||
const DEFAULT_LINK_GETTER = (): null => null
|
||||
|
||||
@ -23,18 +19,12 @@ interface BarChartProps<Datum> extends CategoricalLikeChart<Datum>, SVGProps<SVG
|
||||
getCategory?: (datum: Datum) => string | undefined
|
||||
}
|
||||
|
||||
interface ActiveSegment<Datum> {
|
||||
category: Category<Datum>
|
||||
datum: Datum
|
||||
}
|
||||
|
||||
export function BarChart<Datum>(props: BarChartProps<Datum>): ReactElement {
|
||||
const {
|
||||
width: outerWidth,
|
||||
height: outerHeight,
|
||||
data,
|
||||
stacked = false,
|
||||
className,
|
||||
getDatumName,
|
||||
getDatumValue,
|
||||
getDatumColor,
|
||||
@ -44,26 +34,6 @@ export function BarChart<Datum>(props: BarChartProps<Datum>): ReactElement {
|
||||
...attributes
|
||||
} = props
|
||||
|
||||
const rootRef = useRef(null)
|
||||
const [yAxisElement, setYAxisElement] = useState<SVGGElement | null>(null)
|
||||
const [xAxisReference, setXAxisElement] = useState<SVGGElement | null>(null)
|
||||
const [activeSegment, setActiveSegment] = useState<ActiveSegment<Datum> | null>(null)
|
||||
|
||||
const content = useMemo(
|
||||
() =>
|
||||
getChartContentSizes({
|
||||
width: outerWidth,
|
||||
height: outerHeight,
|
||||
margin: {
|
||||
top: 16,
|
||||
right: 16,
|
||||
left: yAxisElement?.getBBox().width,
|
||||
bottom: xAxisReference?.getBBox().height,
|
||||
},
|
||||
}),
|
||||
[yAxisElement, xAxisReference, outerWidth, outerHeight]
|
||||
)
|
||||
|
||||
const categories = useMemo(
|
||||
() => getGroupedCategories({ data, stacked, getCategory, getDatumName, getDatumValue }),
|
||||
[data, stacked, getCategory, getDatumName, getDatumValue]
|
||||
@ -73,19 +43,17 @@ export function BarChart<Datum>(props: BarChartProps<Datum>): ReactElement {
|
||||
() =>
|
||||
scaleBand<string>({
|
||||
domain: categories.map(category => category.id),
|
||||
range: [0, content.width],
|
||||
padding: 0.2,
|
||||
}),
|
||||
[content, categories]
|
||||
[categories]
|
||||
)
|
||||
|
||||
const yScale = useMemo(
|
||||
() =>
|
||||
scaleLinear<number>({
|
||||
domain: [0, Math.max(...categories.map(category => category.maxValue))],
|
||||
range: [content.height, 0],
|
||||
}),
|
||||
[content, categories]
|
||||
[categories]
|
||||
)
|
||||
|
||||
const handleBarClick = (datum: Datum): void => {
|
||||
@ -98,78 +66,32 @@ export function BarChart<Datum>(props: BarChartProps<Datum>): ReactElement {
|
||||
onDatumLinkClick(datum)
|
||||
}
|
||||
|
||||
const withActiveLink = activeSegment?.datum ? getDatumLink(activeSegment?.datum) : null
|
||||
|
||||
return (
|
||||
<svg
|
||||
ref={rootRef}
|
||||
width={outerWidth}
|
||||
height={outerHeight}
|
||||
{...attributes}
|
||||
className={classNames(className, styles.root, { [styles.rootWithHoveredLinkPoint]: withActiveLink })}
|
||||
>
|
||||
<AxisLeft
|
||||
ref={setYAxisElement}
|
||||
scale={yScale}
|
||||
width={content.width}
|
||||
height={content.height}
|
||||
top={content.top}
|
||||
left={content.left}
|
||||
/>
|
||||
<SvgRoot {...attributes} width={outerWidth} height={outerHeight} xScale={xScale} yScale={yScale}>
|
||||
<SvgAxisLeft />
|
||||
<SvgAxisBottom />
|
||||
|
||||
<AxisBottom
|
||||
ref={setXAxisElement}
|
||||
scale={xScale}
|
||||
width={content.width}
|
||||
top={content.bottom}
|
||||
left={content.left}
|
||||
/>
|
||||
|
||||
{stacked ? (
|
||||
<StackedBars
|
||||
categories={categories}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
getDatumName={getDatumName}
|
||||
getDatumValue={getDatumValue}
|
||||
getDatumColor={getDatumColor}
|
||||
left={content.left}
|
||||
top={content.top}
|
||||
height={content.height}
|
||||
onBarHover={(datum, category) => setActiveSegment({ datum, category })}
|
||||
onBarLeave={() => setActiveSegment(null)}
|
||||
onBarClick={handleBarClick}
|
||||
/>
|
||||
) : (
|
||||
<GroupedBars
|
||||
categories={categories}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
getDatumName={getDatumName}
|
||||
getDatumValue={getDatumValue}
|
||||
getDatumColor={getDatumColor}
|
||||
getDatumLink={getDatumLink}
|
||||
left={content.left}
|
||||
top={content.top}
|
||||
height={content.height}
|
||||
width={content.width}
|
||||
onBarHover={(datum, category) => setActiveSegment({ datum, category })}
|
||||
onBarLeave={() => setActiveSegment(null)}
|
||||
onBarClick={handleBarClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeSegment && rootRef.current && (
|
||||
<Tooltip containerElement={rootRef.current}>
|
||||
<BarTooltipContent
|
||||
category={activeSegment.category}
|
||||
activeBar={activeSegment.datum}
|
||||
getDatumColor={getDatumColor}
|
||||
getDatumValue={getDatumValue}
|
||||
<SvgContent<ScaleBand<string>, any>>
|
||||
{({ yScale, xScale, content }) => (
|
||||
<BarChartContent<Datum>
|
||||
// Visx axis interfaces doesn't support scaleLiner scale in
|
||||
// axisScale interface
|
||||
yScale={yScale}
|
||||
xScale={xScale}
|
||||
width={content.width}
|
||||
height={content.height}
|
||||
top={content.top}
|
||||
left={content.left}
|
||||
stacked={stacked}
|
||||
categories={categories}
|
||||
getDatumName={getDatumName}
|
||||
getDatumValue={getDatumValue}
|
||||
getDatumColor={getDatumColor}
|
||||
getDatumLink={getDatumLink}
|
||||
onBarClick={handleBarClick}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</svg>
|
||||
)}
|
||||
</SvgContent>
|
||||
</SvgRoot>
|
||||
)
|
||||
}
|
||||
|
||||
110
client/web/src/charts/components/bar-chart/BarChartContent.tsx
Normal file
110
client/web/src/charts/components/bar-chart/BarChartContent.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import { ReactElement, SVGProps, useRef, useState } from 'react'
|
||||
|
||||
import { Group } from '@visx/group'
|
||||
import classNames from 'classnames'
|
||||
import { ScaleBand, ScaleLinear } from 'd3-scale'
|
||||
|
||||
import { Tooltip } from '../../core'
|
||||
|
||||
import { GroupedBars } from './components/GroupedBars'
|
||||
import { StackedBars } from './components/StackedBars'
|
||||
import { BarTooltipContent } from './components/TooltipContent'
|
||||
import { Category } from './utils/get-grouped-categories'
|
||||
|
||||
import styles from './BarChartContent.module.scss'
|
||||
|
||||
interface ActiveSegment<Datum> {
|
||||
category: Category<Datum>
|
||||
datum: Datum
|
||||
}
|
||||
|
||||
interface BarChartContentProps<Datum> extends SVGProps<SVGGElement> {
|
||||
stacked: boolean
|
||||
|
||||
top: number
|
||||
left: number
|
||||
|
||||
xScale: ScaleBand<string>
|
||||
yScale: ScaleLinear<number, number>
|
||||
categories: Category<Datum>[]
|
||||
|
||||
getDatumName: (datum: Datum) => string
|
||||
getDatumValue: (datum: Datum) => number
|
||||
getDatumColor: (datum: Datum) => string | undefined
|
||||
getDatumLink: (datum: Datum) => string | undefined | null
|
||||
onBarClick: (datum: Datum) => void
|
||||
}
|
||||
|
||||
export function BarChartContent<Datum>(props: BarChartContentProps<Datum>): ReactElement {
|
||||
const {
|
||||
xScale,
|
||||
yScale,
|
||||
categories,
|
||||
stacked,
|
||||
top,
|
||||
left,
|
||||
width = 0,
|
||||
height = 0,
|
||||
getDatumName,
|
||||
getDatumValue,
|
||||
getDatumColor,
|
||||
getDatumLink,
|
||||
onBarClick,
|
||||
...attributes
|
||||
} = props
|
||||
|
||||
const rootRef = useRef<SVGGElement>(null)
|
||||
const [activeSegment, setActiveSegment] = useState<ActiveSegment<Datum> | null>(null)
|
||||
|
||||
const withActiveLink = activeSegment?.datum ? getDatumLink(activeSegment?.datum) : null
|
||||
|
||||
return (
|
||||
<Group
|
||||
{...attributes}
|
||||
innerRef={rootRef}
|
||||
className={classNames(styles.root, { [styles.rootWithHoveredLinkPoint]: withActiveLink })}
|
||||
>
|
||||
{stacked ? (
|
||||
<StackedBars
|
||||
categories={categories}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
getDatumName={getDatumName}
|
||||
getDatumValue={getDatumValue}
|
||||
getDatumColor={getDatumColor}
|
||||
height={+height}
|
||||
onBarHover={(datum, category) => setActiveSegment({ datum, category })}
|
||||
onBarLeave={() => setActiveSegment(null)}
|
||||
onBarClick={onBarClick}
|
||||
/>
|
||||
) : (
|
||||
<GroupedBars
|
||||
categories={categories}
|
||||
xScale={xScale}
|
||||
yScale={yScale}
|
||||
getDatumName={getDatumName}
|
||||
getDatumValue={getDatumValue}
|
||||
getDatumColor={getDatumColor}
|
||||
getDatumLink={getDatumLink}
|
||||
height={+height}
|
||||
width={+width}
|
||||
onBarHover={(datum, category) => setActiveSegment({ datum, category })}
|
||||
onBarLeave={() => setActiveSegment(null)}
|
||||
onBarClick={onBarClick}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeSegment && rootRef.current && (
|
||||
<Tooltip containerElement={rootRef.current}>
|
||||
<BarTooltipContent
|
||||
category={activeSegment.category}
|
||||
activeBar={activeSegment.datum}
|
||||
getDatumColor={getDatumColor}
|
||||
getDatumValue={getDatumValue}
|
||||
getDatumName={getDatumName}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
@ -1,14 +1,15 @@
|
||||
import { ReactElement, useMemo, useState, SVGProps, CSSProperties, useRef } from 'react'
|
||||
|
||||
import { AxisScale, TickFormatter } from '@visx/axis/lib/types'
|
||||
import { Group } from '@visx/group'
|
||||
import { scaleTime, scaleLinear } from '@visx/scale'
|
||||
import { scaleTime, scaleLinear, getTicks } from '@visx/scale'
|
||||
import { AnyD3Scale } from '@visx/scale/lib/types/Scale'
|
||||
import { LinePath } from '@visx/shape'
|
||||
import { voronoi } from '@visx/voronoi'
|
||||
import classNames from 'classnames'
|
||||
import { noop } from 'lodash'
|
||||
|
||||
import { AxisLeft, AxisBottom } from '../../core'
|
||||
import { formatDateTick } from '../../core/components/axis/tick-formatters'
|
||||
import { SeriesLikeChart } from '../../types'
|
||||
|
||||
import { Tooltip, TooltipContent, PointGlyph } from './components'
|
||||
@ -24,7 +25,6 @@ import {
|
||||
getChartContentSizes,
|
||||
getMinMaxBoundaries,
|
||||
SeriesWithData,
|
||||
formatXTick,
|
||||
} from './utils'
|
||||
|
||||
import styles from './LineChart.module.scss'
|
||||
@ -67,6 +67,21 @@ const sortByDataKey = (dataKey: string | number | symbol, activeDataKey: string)
|
||||
|
||||
const identity = <T,>(argument: T): T => argument
|
||||
|
||||
interface GetScaleTicksInput {
|
||||
scale: AnyD3Scale
|
||||
space: number
|
||||
pixelsPerTick?: number
|
||||
}
|
||||
|
||||
export function getXScaleTicks<T>(input: GetScaleTicksInput): T[] {
|
||||
const { scale, space, pixelsPerTick = 80 } = input
|
||||
|
||||
// Calculate desirable number of ticks
|
||||
const numberTicks = Math.max(2, Math.floor(space / pixelsPerTick))
|
||||
|
||||
return getTicks(scale, numberTicks) as T[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual component that renders svg line chart with pre-defined sizes, tooltip,
|
||||
* voronoi area distribution.
|
||||
@ -211,7 +226,8 @@ export function LineChart<D>(props: LineChartProps<D>): ReactElement | null {
|
||||
width={content.width}
|
||||
top={content.bottom}
|
||||
left={content.left}
|
||||
tickFormat={(formatXTick as unknown) as TickFormatter<AxisScale>}
|
||||
tickValues={getXScaleTicks({ scale: xScale, space: content.width })}
|
||||
tickFormat={formatDateTick}
|
||||
/>
|
||||
|
||||
<Group top={content.top} left={content.left}>
|
||||
|
||||
@ -4,8 +4,9 @@ import { isDefined } from '@sourcegraph/common'
|
||||
import { H3 } from '@sourcegraph/wildcard'
|
||||
|
||||
import { TooltipList, TooltipListBlankItem, TooltipListItem } from '../../../../core'
|
||||
import { formatDateTick } from '../../../../core/components/axis/tick-formatters'
|
||||
import { Point } from '../../types'
|
||||
import { isValidNumber, formatYTick, SeriesWithData, SeriesDatum, getDatumValue, getLineColor } from '../../utils'
|
||||
import { isValidNumber, SeriesWithData, SeriesDatum, getDatumValue, getLineColor } from '../../utils'
|
||||
|
||||
import { getListWindow } from './utils/get-list-window'
|
||||
|
||||
@ -66,9 +67,10 @@ export function TooltipContent<Datum>(props: TooltipContentProps<Datum>): ReactE
|
||||
<TooltipListBlankItem>... and {lines.leftRemaining} more</TooltipListBlankItem>
|
||||
)}
|
||||
{lines.window.map(line => {
|
||||
const value = formatYTick(line.value)
|
||||
const value = formatDateTick((line.value as unknown) as Date)
|
||||
const isActiveLine = activePoint.seriesId === line.id
|
||||
const stackedValue = isActiveLine && stacked ? formatYTick(activePoint.value) : null
|
||||
const stackedValue =
|
||||
isActiveLine && stacked ? formatDateTick((activePoint.value as unknown) as Date) : null
|
||||
|
||||
return (
|
||||
<TooltipListItem
|
||||
|
||||
@ -3,5 +3,5 @@ export { isValidNumber } from './data-guards'
|
||||
export { generatePointsField } from './generate-points-field'
|
||||
export { getChartContentSizes } from '../../../core/utils/get-chart-content-sizes'
|
||||
export { getMinMaxBoundaries } from './get-min-max-boundary'
|
||||
export { formatYTick, formatXTick, formatXLabel, getYScaleTicks, getXScaleTicks } from './ticks'
|
||||
|
||||
export * from './data-series-processing'
|
||||
|
||||
74
client/web/src/charts/core/components/SvgRoot.story.tsx
Normal file
74
client/web/src/charts/core/components/SvgRoot.story.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import { boolean } from '@storybook/addon-knobs'
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
import { AxisScale } from '@visx/axis/lib/types'
|
||||
import { ParentSize } from '@visx/responsive'
|
||||
import { scaleBand, scaleLinear, scaleTime } from '@visx/scale'
|
||||
|
||||
import { WebStory } from '../../../components/WebStory'
|
||||
|
||||
import { formatDateTick } from './axis/tick-formatters'
|
||||
import { SvgRoot, SvgAxisLeft, SvgAxisBottom, SvgContent } from './SvgRoot'
|
||||
|
||||
const StoryConfig: Meta = {
|
||||
title: 'web/charts/core/axis',
|
||||
decorators: [story => <WebStory>{() => story()}</WebStory>],
|
||||
parameters: { chromatic: { disableSnapshots: false } },
|
||||
}
|
||||
|
||||
export default StoryConfig
|
||||
|
||||
interface TemplateProps {
|
||||
xScale: AxisScale
|
||||
yScale: AxisScale
|
||||
pixelsPerXTick?: number
|
||||
formatXLabel?: (value: any) => string
|
||||
color?: string
|
||||
}
|
||||
|
||||
const SimpleChartTemplate: Story<TemplateProps> = args => (
|
||||
<ParentSize style={{ width: 400, height: 400 }} debounceTime={0} className="flex-shrink-0">
|
||||
{parent => (
|
||||
<SvgRoot width={parent.width} height={parent.height} xScale={args.xScale} yScale={args.yScale}>
|
||||
<SvgAxisLeft />
|
||||
<SvgAxisBottom tickFormat={args.formatXLabel} pixelsPerTick={args.pixelsPerXTick} />
|
||||
|
||||
<SvgContent>
|
||||
{({ content }) => <rect fill={args.color} width={content.width} height={content.height} />}
|
||||
</SvgContent>
|
||||
</SvgRoot>
|
||||
)}
|
||||
</ParentSize>
|
||||
)
|
||||
|
||||
export const MainAxisDemo: Story = () => (
|
||||
<section style={{ display: 'flex', flexWrap: 'wrap', gap: 20 }}>
|
||||
<SimpleChartTemplate
|
||||
xScale={scaleTime<number>({
|
||||
domain: [new Date(2022, 8, 22), new Date(2022, 10, 22)],
|
||||
nice: true,
|
||||
clamp: true,
|
||||
})}
|
||||
yScale={scaleLinear({
|
||||
domain: [0, boolean('useMaxValuesForYScale', false) ? 1000000000000000000000000000000000000 : 10000],
|
||||
nice: true,
|
||||
clamp: true,
|
||||
})}
|
||||
formatXLabel={formatDateTick}
|
||||
pixelsPerXTick={20}
|
||||
color="darkslateblue"
|
||||
/>
|
||||
|
||||
<SimpleChartTemplate
|
||||
xScale={scaleBand<string>({
|
||||
domain: ['hello', 'worlddddddddddd', 'this', 'is', 'rotation', 'speaking'],
|
||||
padding: 0.2,
|
||||
})}
|
||||
yScale={scaleLinear({
|
||||
domain: [0, boolean('useMaxValuesForYScale', false) ? 1000000000000000000000000000000000000 : 10000],
|
||||
nice: true,
|
||||
clamp: true,
|
||||
})}
|
||||
color="pink"
|
||||
/>
|
||||
</section>
|
||||
)
|
||||
240
client/web/src/charts/core/components/SvgRoot.tsx
Normal file
240
client/web/src/charts/core/components/SvgRoot.tsx
Normal file
@ -0,0 +1,240 @@
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
FC,
|
||||
PropsWithChildren,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
SVGProps,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
import { AxisScale, TickRendererProps } from '@visx/axis'
|
||||
import { Group } from '@visx/group'
|
||||
import { scaleLinear } from '@visx/scale'
|
||||
import { noop } from 'lodash'
|
||||
import { useMergeRefs } from 'use-callback-ref'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
|
||||
import { createRectangle, EMPTY_RECTANGLE, Rectangle } from '@sourcegraph/wildcard'
|
||||
|
||||
import { AxisBottom, AxisLeft } from './axis/Axis'
|
||||
import { getMaxTickWidth, Tick, TickProps } from './axis/Tick'
|
||||
import { getXScaleTicks } from './axis/tick-formatters'
|
||||
|
||||
const DEFAULT_PADDING = { top: 16, right: 36, bottom: 0, left: 0 }
|
||||
|
||||
interface Padding {
|
||||
top: number
|
||||
right: number
|
||||
bottom: number
|
||||
left: number
|
||||
}
|
||||
|
||||
interface SVGRootLayout {
|
||||
width: number
|
||||
height: number
|
||||
yScale: AxisScale
|
||||
xScale: AxisScale
|
||||
content: Rectangle
|
||||
setPadding: Dispatch<SetStateAction<Padding>>
|
||||
}
|
||||
|
||||
const SVGRootContext = createContext<SVGRootLayout>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
xScale: scaleLinear(),
|
||||
yScale: scaleLinear(),
|
||||
content: EMPTY_RECTANGLE,
|
||||
setPadding: noop,
|
||||
})
|
||||
|
||||
interface SvgRootProps extends SVGProps<SVGSVGElement> {
|
||||
width: number
|
||||
height: number
|
||||
yScale: AxisScale
|
||||
xScale: AxisScale
|
||||
}
|
||||
|
||||
/**
|
||||
* SVG canvas root element. This component renders SVG element and
|
||||
* calculates and prepares all important canvas measurements for x/y-axis,
|
||||
* content and other chart elements.
|
||||
*/
|
||||
export const SvgRoot: FC<PropsWithChildren<SvgRootProps>> = props => {
|
||||
const { width, height, yScale: yOriginalScale, xScale: xOriginalScale, children, ...attributes } = props
|
||||
|
||||
const [padding, setPadding] = useState<Padding>(DEFAULT_PADDING)
|
||||
|
||||
const contentRectangle = useMemo(
|
||||
() =>
|
||||
createRectangle(
|
||||
padding.left,
|
||||
padding.top,
|
||||
width - padding.left - padding.right,
|
||||
height - padding.top - padding.bottom
|
||||
),
|
||||
[width, height, padding]
|
||||
)
|
||||
|
||||
const yScale = useMemo(() => yOriginalScale.copy().range([contentRectangle.height, 0]) as AxisScale, [
|
||||
yOriginalScale,
|
||||
contentRectangle,
|
||||
])
|
||||
|
||||
const xScale = useMemo(() => xOriginalScale.copy().range([0, contentRectangle.width]) as AxisScale, [
|
||||
xOriginalScale,
|
||||
contentRectangle,
|
||||
])
|
||||
|
||||
const context = useMemo<SVGRootLayout>(
|
||||
() => ({
|
||||
width,
|
||||
height,
|
||||
xScale,
|
||||
yScale,
|
||||
content: contentRectangle,
|
||||
setPadding,
|
||||
}),
|
||||
[width, height, contentRectangle, xScale, yScale]
|
||||
)
|
||||
|
||||
return (
|
||||
<SVGRootContext.Provider value={context}>
|
||||
<svg {...attributes} width={width} height={height}>
|
||||
{children}
|
||||
</svg>
|
||||
</SVGRootContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
interface SvgAxisLeftProps {}
|
||||
|
||||
export const SvgAxisLeft: FC<SvgAxisLeftProps> = props => {
|
||||
const { content, yScale, setPadding } = useContext(SVGRootContext)
|
||||
|
||||
const handleResize = ({ width = 0 }): void => {
|
||||
setPadding(padding => ({ ...padding, left: width }))
|
||||
}
|
||||
|
||||
const { ref } = useResizeObserver({ onResize: handleResize })
|
||||
|
||||
return (
|
||||
<AxisLeft
|
||||
{...props}
|
||||
ref={ref}
|
||||
width={content.width}
|
||||
height={content.height}
|
||||
top={content.top}
|
||||
left={content.left}
|
||||
scale={yScale}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const defaultToString = <T,>(tick: T): string => `${tick}`
|
||||
const defaultTruncatedTick = (tick: string): string => (tick.length >= 15 ? `${tick.slice(0, 15)}...` : tick)
|
||||
|
||||
// TODO: Support reverse truncation for some charts https://github.com/sourcegraph/sourcegraph/issues/39879
|
||||
export const reverseTruncatedTick = (tick: string): string => (tick.length >= 15 ? `...${tick.slice(-15)}` : tick)
|
||||
|
||||
interface SvgAxisBottomProps<Tick> {
|
||||
tickFormat?: (tick: Tick) => string
|
||||
pixelsPerTick?: number
|
||||
maxRotateAngle?: number
|
||||
getTruncatedTick?: (formattedTick: string) => string
|
||||
}
|
||||
|
||||
export function SvgAxisBottom<Tick = string>(props: SvgAxisBottomProps<Tick>): ReactElement {
|
||||
const {
|
||||
pixelsPerTick = 0,
|
||||
maxRotateAngle = 90,
|
||||
tickFormat = defaultToString,
|
||||
getTruncatedTick = defaultTruncatedTick,
|
||||
} = props
|
||||
const { content, xScale, setPadding } = useContext(SVGRootContext)
|
||||
|
||||
const axisGroupRef = useRef<SVGGElement>(null)
|
||||
const { ref } = useResizeObserver<SVGGElement>({
|
||||
// TODO: Fix corner cases with axis sizes see https://github.com/sourcegraph/sourcegraph/issues/39876
|
||||
onResize: ({ height = 0 }) => setPadding(padding => ({ ...padding, bottom: height })),
|
||||
})
|
||||
|
||||
const [, upperRangeBound] = xScale.range() as [number, number]
|
||||
const ticks = getXScaleTicks<Tick>({ scale: xScale, space: content.width, pixelsPerTick })
|
||||
|
||||
const maxWidth = useMemo(() => {
|
||||
const axisGroup = axisGroupRef.current
|
||||
|
||||
if (!axisGroup) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return getMaxTickWidth(axisGroup, ticks.map(tickFormat))
|
||||
}, [ticks, tickFormat])
|
||||
|
||||
const getXTickProps = (props: TickRendererProps): TickProps => {
|
||||
const measuredSize = ticks.length * maxWidth
|
||||
const rotate =
|
||||
upperRangeBound < measuredSize
|
||||
? maxRotateAngle * Math.min(1, (measuredSize / upperRangeBound - 0.8) / 2)
|
||||
: 0
|
||||
|
||||
if (rotate) {
|
||||
return {
|
||||
...props,
|
||||
// Truncate ticks only if we rotate them, this means truncate labels only
|
||||
// when they overlap
|
||||
getTruncatedTick,
|
||||
transform: `rotate(${rotate}, ${props.x} ${props.y})`,
|
||||
textAnchor: 'start',
|
||||
}
|
||||
}
|
||||
|
||||
return { ...props, textAnchor: 'middle' }
|
||||
}
|
||||
|
||||
return (
|
||||
<AxisBottom
|
||||
ref={useMergeRefs([axisGroupRef, ref])}
|
||||
scale={xScale}
|
||||
width={content.width}
|
||||
top={content.bottom}
|
||||
left={content.left}
|
||||
tickValues={ticks}
|
||||
tickComponent={props => <Tick {...getXTickProps(props)} />}
|
||||
tickFormat={tickFormat}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface SvgContentProps<XScale extends AxisScale, YScale extends AxisScale> {
|
||||
children: (input: { xScale: XScale; yScale: YScale; content: Rectangle }) => ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Compound svg canvas component, to render actual chart content on
|
||||
* SVG canvas with pre-calculated axes and paddings
|
||||
*/
|
||||
export function SvgContent<XScale extends AxisScale = AxisScale, YScale extends AxisScale = AxisScale>(
|
||||
props: SvgContentProps<XScale, YScale>
|
||||
): ReactElement {
|
||||
const { children } = props
|
||||
const { content, xScale, yScale } = useContext(SVGRootContext)
|
||||
|
||||
return (
|
||||
<Group top={content.top} left={content.left} width={content.width} height={content.height}>
|
||||
{children({
|
||||
// We need to cast scales here because there is no other way to type context
|
||||
// shared data in TS, React interfaces.
|
||||
xScale: xScale as XScale,
|
||||
yScale: yScale as YScale,
|
||||
content,
|
||||
})}
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
@ -27,13 +27,6 @@
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
// tick label
|
||||
text {
|
||||
fill: var(--text-muted);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&--vertical {
|
||||
line {
|
||||
// Hide line ticks visually and hide them from voice over
|
||||
|
||||
@ -1,29 +1,49 @@
|
||||
import { forwardRef, memo } from 'react'
|
||||
|
||||
import { AxisLeft as VisxAxisLeft, AxisBottom as VisxAsixBottom } from '@visx/axis'
|
||||
import { AxisScale, TickFormatter } from '@visx/axis/lib/types'
|
||||
import {
|
||||
AxisLeft as VisxAxisLeft,
|
||||
AxisBottom as VisxAsixBottom,
|
||||
TickLabelProps,
|
||||
SharedAxisProps,
|
||||
AxisScale,
|
||||
} from '@visx/axis'
|
||||
import { GridRows } from '@visx/grid'
|
||||
import { Group } from '@visx/group'
|
||||
import { TextProps } from '@visx/text'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { formatYTick, getXScaleTicks, getYScaleTicks } from '../../../components/line-chart/utils'
|
||||
|
||||
import { getTickXProps, getTickYProps, Tick } from './Tick'
|
||||
import { Tick } from './Tick'
|
||||
import { formatYTick, getXScaleTicks, getYScaleTicks } from './tick-formatters'
|
||||
|
||||
import styles from './Axis.module.scss'
|
||||
|
||||
interface AxisLeftProps {
|
||||
top: number
|
||||
left: number
|
||||
// TODO: Remove this prop generation, see https://github.com/sourcegraph/sourcegraph/issues/39874
|
||||
const getTickYLabelProps: TickLabelProps<number> = (value, index, values): Partial<TextProps> => ({
|
||||
dy: '0.25em',
|
||||
textAnchor: 'end',
|
||||
'aria-label': `Tick axis ${index + 1} of ${values.length}. Value: ${value}`,
|
||||
})
|
||||
|
||||
type OwnSharedAxisProps = Omit<SharedAxisProps<AxisScale>, 'tickLabelProps'>
|
||||
|
||||
export interface AxisLeftProps extends OwnSharedAxisProps {
|
||||
width: number
|
||||
height: number
|
||||
scale: AxisScale
|
||||
}
|
||||
|
||||
export const AxisLeft = memo(
|
||||
forwardRef<SVGGElement, AxisLeftProps>((props, reference) => {
|
||||
const { scale, left, top, width, height } = props
|
||||
const ticksValues = getYScaleTicks({ scale, space: height })
|
||||
const {
|
||||
scale,
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height,
|
||||
tickComponent = Tick,
|
||||
tickFormat = formatYTick,
|
||||
tickValues = getYScaleTicks({ scale, space: height }),
|
||||
...attributes
|
||||
} = props
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -33,22 +53,18 @@ export const AxisLeft = memo(
|
||||
width={width}
|
||||
height={height}
|
||||
scale={scale}
|
||||
tickValues={ticksValues}
|
||||
tickValues={tickValues}
|
||||
className={styles.gridLine}
|
||||
/>
|
||||
|
||||
<Group
|
||||
key={ticksValues.reduce((store, tick) => `${store}-${tick}`, '')}
|
||||
innerRef={reference}
|
||||
top={top}
|
||||
left={left}
|
||||
>
|
||||
<Group innerRef={reference} top={top} left={left}>
|
||||
<VisxAxisLeft
|
||||
{...attributes}
|
||||
scale={scale}
|
||||
tickValues={ticksValues}
|
||||
tickFormat={formatYTick}
|
||||
tickLabelProps={getTickYProps}
|
||||
tickComponent={Tick}
|
||||
tickValues={tickValues}
|
||||
tickFormat={tickFormat}
|
||||
tickLabelProps={getTickYLabelProps}
|
||||
tickComponent={tickComponent}
|
||||
axisLineClassName={classNames(styles.axisLine, styles.axisLineVertical)}
|
||||
tickClassName={classNames(styles.axisTick, styles.axisTickVertical)}
|
||||
/>
|
||||
@ -58,26 +74,30 @@ export const AxisLeft = memo(
|
||||
})
|
||||
)
|
||||
|
||||
interface AxisBottomProps {
|
||||
top: number
|
||||
left: number
|
||||
AxisLeft.displayName = 'AxisLeft'
|
||||
|
||||
// TODO: Remove this prop generation, see https://github.com/sourcegraph/sourcegraph/issues/39874
|
||||
const getTickXLabelProps: TickLabelProps<Date> = (value, index, values): Partial<TextProps> => ({
|
||||
'aria-label': `Tick axis ${index + 1} of ${values.length}. Value: ${value}`,
|
||||
textAnchor: 'middle',
|
||||
})
|
||||
|
||||
interface AxisBottomProps extends OwnSharedAxisProps {
|
||||
width: number
|
||||
scale: AxisScale
|
||||
tickFormat?: TickFormatter<AxisScale>
|
||||
}
|
||||
|
||||
export const AxisBottom = memo(
|
||||
forwardRef<SVGGElement, AxisBottomProps>((props, reference) => {
|
||||
const { scale, top, left, width, tickFormat } = props
|
||||
const { scale, top, left, width, tickValues, tickComponent = Tick, ...attributes } = props
|
||||
|
||||
return (
|
||||
<Group innerRef={reference} top={top} left={left}>
|
||||
<Group innerRef={reference} top={top} left={left} width={width}>
|
||||
<VisxAsixBottom
|
||||
{...attributes}
|
||||
scale={scale}
|
||||
tickValues={getXScaleTicks({ scale, space: width })}
|
||||
tickFormat={tickFormat}
|
||||
tickLabelProps={getTickXProps}
|
||||
tickComponent={Tick}
|
||||
tickComponent={tickComponent}
|
||||
tickValues={tickValues ?? getXScaleTicks({ scale, space: width })}
|
||||
tickLabelProps={getTickXLabelProps}
|
||||
axisLineClassName={styles.axisLine}
|
||||
tickClassName={styles.axisTick}
|
||||
/>
|
||||
@ -85,3 +105,5 @@ export const AxisBottom = memo(
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
AxisBottom.displayName = 'AxisBottom'
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
.tick {
|
||||
fill: var(--text-muted);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
@ -1,35 +1,19 @@
|
||||
import React from 'react'
|
||||
import { FC } from 'react'
|
||||
|
||||
import { TickLabelProps, TickRendererProps } from '@visx/axis'
|
||||
import { TickRendererProps } from '@visx/axis'
|
||||
import { Group } from '@visx/group'
|
||||
import { Text, TextProps } from '@visx/text'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { formatXLabel } from '../../../components/line-chart/utils'
|
||||
import styles from './Tick.module.scss'
|
||||
|
||||
export const getTickYProps: TickLabelProps<number> = (value, index, values): Partial<TextProps> => ({
|
||||
dx: '-0.25em',
|
||||
dy: '0.25em',
|
||||
fill: '#222',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 10,
|
||||
textAnchor: 'end',
|
||||
'aria-label': `Tick axis ${index + 1} of ${values.length}. Value: ${value}`,
|
||||
})
|
||||
export interface TickProps extends TickRendererProps {
|
||||
getTruncatedTick?: (lable: string) => string
|
||||
}
|
||||
|
||||
export const getTickXProps: TickLabelProps<Date> = (value, index, values): Partial<TextProps> => ({
|
||||
dy: '0.25em',
|
||||
fill: '#222',
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 10,
|
||||
textAnchor: 'middle',
|
||||
'aria-label': `Tick axis ${index + 1} of ${values.length}. Value: ${formatXLabel(value)}`,
|
||||
})
|
||||
|
||||
/**
|
||||
* Tick component displays tick label for each axis line of chart.
|
||||
*/
|
||||
export const Tick: React.FunctionComponent<React.PropsWithChildren<TickRendererProps>> = props => {
|
||||
const { formattedValue, ...tickLabelProps } = props
|
||||
/** Tick component displays tick label for each axis line of chart. */
|
||||
export const Tick: FC<TickProps> = props => {
|
||||
const { formattedValue = '', 'aria-label': ariaLabel, className, getTruncatedTick, ...tickLabelProps } = props
|
||||
|
||||
// Hack with Group + Text (aria hidden)
|
||||
// Because the Text component renders text inside svg element and text element with tspan
|
||||
@ -38,10 +22,37 @@ export const Tick: React.FunctionComponent<React.PropsWithChildren<TickRendererP
|
||||
// on the parent Group element with role text
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/aria-role
|
||||
<Group role="text" aria-label={tickLabelProps['aria-label']}>
|
||||
<Text aria-hidden={true} {...(tickLabelProps as TextProps)}>
|
||||
{formattedValue}
|
||||
<Group role="text" aria-label={ariaLabel}>
|
||||
<Text aria-hidden={true} className={classNames(styles.tick, className)} {...(tickLabelProps as TextProps)}>
|
||||
{getTruncatedTick ? getTruncatedTick(formattedValue) : formattedValue}
|
||||
</Text>
|
||||
</Group>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Text (labels) ticks measure helper. Since there is no way to measure text
|
||||
* before rendering inside React tree we have to conduct pre-rendering measurements
|
||||
* for ticks labels.
|
||||
*
|
||||
* It renders each labels (text tick) inside selection element with SVG text element
|
||||
* and measures its sizes.
|
||||
*/
|
||||
export const getMaxTickWidth = (selection: Element, labels: string[]): number => {
|
||||
const tester = document.createElementNS('http://www.w3.org/2000/svg', 'text')
|
||||
|
||||
// In order to sync Tick component and pre-rendering text styles which is vital for
|
||||
// text measurements
|
||||
tester.classList.add(styles.tick)
|
||||
selection.append(tester)
|
||||
|
||||
const boundingBoxes = labels.map(label => {
|
||||
tester.textContent = label
|
||||
|
||||
return tester.getBBox()
|
||||
})
|
||||
|
||||
tester.remove()
|
||||
|
||||
return Math.max(...boundingBoxes.map(b => b.width))
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ export function formatYTick(number: number): string {
|
||||
*
|
||||
* Example: 01 Jan, 12 Feb, ...
|
||||
*/
|
||||
export const formatXTick = timeFormat('%d %b')
|
||||
export const formatDateTick = timeFormat('%d %b')
|
||||
|
||||
/**
|
||||
* Returns a formatted date text for points aria labels.
|
||||
@ -37,11 +37,19 @@ interface GetScaleTicksInput {
|
||||
pixelsPerTick?: number
|
||||
}
|
||||
|
||||
export function getXScaleTicks(input: GetScaleTicksInput): number[] {
|
||||
export function getXScaleTicks<T>(input: GetScaleTicksInput): T[] {
|
||||
const { scale, space, pixelsPerTick = 80 } = input
|
||||
const maxTicks = Math.max(Math.floor(space / pixelsPerTick), MINIMUM_NUMBER_OF_TICKS)
|
||||
|
||||
return getTicks(scale, maxTicks) as number[]
|
||||
// Calculate desirable number of ticks
|
||||
const numberTicks = Math.max(MINIMUM_NUMBER_OF_TICKS, Math.floor(space / pixelsPerTick))
|
||||
|
||||
let filteredTicks = getTicks(scale)
|
||||
|
||||
while (filteredTicks.length > numberTicks) {
|
||||
filteredTicks = getHalvedTicks(filteredTicks)
|
||||
}
|
||||
|
||||
return filteredTicks
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,7 +91,7 @@ export function getYScaleTicks(input: GetScaleTicksInput): number[] {
|
||||
* removes all even index ticks with even number removes all
|
||||
* odd index ticks.
|
||||
*/
|
||||
function getHalvedTicks(ticks: number[]): number[] {
|
||||
function getHalvedTicks<T>(ticks: T[]): T[] {
|
||||
const isOriginTickLengthOdd = !(ticks.length % 2)
|
||||
const filteredTicks = []
|
||||
|
||||
@ -9,7 +9,7 @@ import styles from './Tooltip.module.scss'
|
||||
const TOOLTIP_PADDING = createRectangle(0, 0, 10, 10)
|
||||
|
||||
interface TooltipProps {
|
||||
containerElement: SVGSVGElement
|
||||
containerElement: Element
|
||||
activeElement?: HTMLElement
|
||||
}
|
||||
|
||||
@ -35,6 +35,10 @@ export const Tooltip: React.FunctionComponent<React.PropsWithChildren<TooltipPro
|
||||
}, [activeElement])
|
||||
|
||||
useEffect(() => {
|
||||
// We need this casting because Element type doesn't support
|
||||
// pointer or mouse events in pointermove handlers
|
||||
const element = containerElement as HTMLElement
|
||||
|
||||
function handleMove(event: PointerEvent): void {
|
||||
setVirtualElement({
|
||||
target: null,
|
||||
@ -43,10 +47,10 @@ export const Tooltip: React.FunctionComponent<React.PropsWithChildren<TooltipPro
|
||||
})
|
||||
}
|
||||
|
||||
containerElement.addEventListener('pointermove', handleMove)
|
||||
element.addEventListener('pointermove', handleMove)
|
||||
|
||||
return () => {
|
||||
containerElement.removeEventListener('pointermove', handleMove)
|
||||
element.removeEventListener('pointermove', handleMove)
|
||||
}
|
||||
}, [containerElement])
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import { CodeInsightsBackendContext, SeriesChartContent, CodeInsightsGqlBackend
|
||||
import { CaptureGroupCreationPage as CaptureGroupCreationPageComponent } from './CaptureGroupCreationPage'
|
||||
|
||||
export default {
|
||||
title: 'web/insights/creation-ui/CaptureGroupCreationPage',
|
||||
title: 'web/insights/creation-ui/capture-group/CaptureGroupCreationPage',
|
||||
decorators: [story => <WebStory>{() => <div className="p-3 container web-content">{story()}</div>}</WebStory>],
|
||||
parameters: {
|
||||
chromatic: {
|
||||
|
||||
@ -11,7 +11,7 @@ import { SERIES_MOCK_CHART } from '../../../../components'
|
||||
import { ComputeInsightCreationPage as ComputeInsightCreationPageComponent } from './ComputeInsightCreationPage'
|
||||
|
||||
const defaultStory: Meta = {
|
||||
title: 'web/insights/creation-ui/ComputeInsightCreationPage',
|
||||
title: 'web/insights/creation-ui/compute/ComputeInsightCreationPage',
|
||||
decorators: [story => <WebStory>{() => story()}</WebStory>],
|
||||
parameters: {
|
||||
chromatic: {
|
||||
|
||||
@ -22,10 +22,10 @@ import {
|
||||
useForm,
|
||||
} from '../../../../../components'
|
||||
import { useUiFeatures } from '../../../../../hooks'
|
||||
import { ComputeLivePreview } from '../../ComputeLivePreview'
|
||||
import { CreateComputeInsightFormFields } from '../types'
|
||||
|
||||
import { ComputeInsightMapPicker } from './ComputeInsightMapPicker'
|
||||
import { ComputeLivePreview } from './ComputeLivePreview'
|
||||
|
||||
const INITIAL_INSIGHT_VALUES: CreateComputeInsightFormFields = {
|
||||
series: [createDefaultEditSeries({ edit: true })],
|
||||
|
||||
@ -2,14 +2,14 @@ import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { GroupByField } from '@sourcegraph/shared/src/graphql-operations'
|
||||
|
||||
import { WebStory } from '../../../../../components/WebStory'
|
||||
import { CodeInsightsBackendStoryMock } from '../../../CodeInsightsBackendStoryMock'
|
||||
import { BackendInsightDatum, SeriesChartContent } from '../../../core'
|
||||
import { WebStory } from '../../../../../../../components/WebStory'
|
||||
import { CodeInsightsBackendStoryMock } from '../../../../../CodeInsightsBackendStoryMock'
|
||||
import { BackendInsightDatum, SeriesChartContent } from '../../../../../core'
|
||||
|
||||
import { ComputeLivePreview as ComputeLivePreviewComponent } from './ComputeLivePreview'
|
||||
|
||||
const defaultStory: Meta = {
|
||||
title: 'web/insights/creation-ui/ComputeLivePreview',
|
||||
title: 'web/insights/creation-ui/compute/ComputeLivePreview',
|
||||
decorators: [story => <WebStory>{() => story()}</WebStory>],
|
||||
}
|
||||
|
||||
@ -5,9 +5,9 @@ import { groupBy } from 'lodash'
|
||||
import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
|
||||
import { useDeepMemo } from '@sourcegraph/wildcard'
|
||||
|
||||
import { LegendItem, LegendList, Series } from '../../../../../charts'
|
||||
import { BarChart } from '../../../../../charts/components/bar-chart/BarChart'
|
||||
import { GroupByField } from '../../../../../graphql-operations'
|
||||
import { LegendItem, LegendList, Series } from '../../../../../../../charts'
|
||||
import { BarChart } from '../../../../../../../charts/components/bar-chart/BarChart'
|
||||
import { GroupByField } from '../../../../../../../graphql-operations'
|
||||
import {
|
||||
LivePreviewUpdateButton,
|
||||
LivePreviewCard,
|
||||
@ -20,13 +20,13 @@ import {
|
||||
StateStatus,
|
||||
COMPUTE_MOCK_CHART,
|
||||
EditableDataSeries,
|
||||
} from '../../../components'
|
||||
} from '../../../../../components'
|
||||
import {
|
||||
BackendInsightDatum,
|
||||
CategoricalChartContent,
|
||||
CodeInsightsBackendContext,
|
||||
SeriesPreviewSettings,
|
||||
} from '../../../core'
|
||||
} from '../../../../../core'
|
||||
|
||||
interface LanguageUsageDatum {
|
||||
name: string
|
||||
@ -11,7 +11,7 @@ import { getRandomLangStatsMock } from './components/live-preview-chart/constant
|
||||
import { LangStatsInsightCreationPage as LangStatsInsightCreationPageComponent } from './LangStatsInsightCreationPage'
|
||||
|
||||
const defaultStory: Meta = {
|
||||
title: 'web/insights/creation-ui/LangStatsInsightCreationPage',
|
||||
title: 'web/insights/creation-ui/lang-stats/LangStatsInsightCreationPage',
|
||||
decorators: [story => <WebStory>{() => story()}</WebStory>],
|
||||
parameters: {
|
||||
chromatic: {
|
||||
|
||||
@ -11,7 +11,7 @@ import { SERIES_MOCK_CHART } from '../../../../components'
|
||||
import { SearchInsightCreationPage as SearchInsightCreationPageComponent } from './SearchInsightCreationPage'
|
||||
|
||||
const defaultStory: Meta = {
|
||||
title: 'web/insights/creation-ui/SearchInsightCreationPage',
|
||||
title: 'web/insights/creation-ui/search/SearchInsightCreationPage',
|
||||
decorators: [story => <WebStory>{() => story()}</WebStory>],
|
||||
parameters: {
|
||||
chromatic: {
|
||||
|
||||
@ -396,7 +396,7 @@
|
||||
"@stripe/react-stripe-js": "^1.8.0-0",
|
||||
"@stripe/stripe-js": "^1.29.0",
|
||||
"@visx/annotation": "^2.10.0",
|
||||
"@visx/axis": "^2.10.0",
|
||||
"@visx/axis": "^2.11.1",
|
||||
"@visx/glyph": "^2.10.0",
|
||||
"@visx/grid": "^2.10.0",
|
||||
"@visx/group": "^2.10.0",
|
||||
@ -486,7 +486,7 @@
|
||||
"use-callback-ref": "^1.2.5",
|
||||
"use-debounce": "^8.0.1",
|
||||
"use-deep-compare-effect": "^1.6.1",
|
||||
"use-resize-observer": "^7.0.0",
|
||||
"use-resize-observer": "^9.0.2",
|
||||
"utility-types": "^3.10.0",
|
||||
"uuid": "^8.3.0",
|
||||
"webext-domain-permission-toggle": "^1.0.1",
|
||||
|
||||
38
yarn.lock
38
yarn.lock
@ -6710,16 +6710,16 @@
|
||||
prop-types "^15.5.10"
|
||||
react-use-measure "^2.0.4"
|
||||
|
||||
"@visx/axis@^2.10.0":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.npmjs.org/@visx/axis/-/axis-2.10.0.tgz#613fde76653edbefb795cafe2e11fc4c2294db46"
|
||||
integrity sha512-myEcXPzD7ZmKiXuhue2lpiuTDgl3Glhe1LB+xoUDS8ZAW76Asd6PwurjoxSnq3tHCz0EDBh7YlgApeFy3Bw38A==
|
||||
"@visx/axis@^2.11.1":
|
||||
version "2.11.1"
|
||||
resolved "https://registry.npmjs.org/@visx/axis/-/axis-2.11.1.tgz#e1cc978ede9cce197cd7712183301e9e89c519e2"
|
||||
integrity sha512-RZdT+yhAEOXtcLc3PgD14Xh5bJh5B64IG+zvHe3YVMkiFWGT1phy0sGTRRxritHke16ErB9vndx+pwVIGSkxAw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
"@visx/group" "2.10.0"
|
||||
"@visx/point" "2.6.0"
|
||||
"@visx/scale" "2.2.2"
|
||||
"@visx/shape" "2.10.0"
|
||||
"@visx/shape" "2.11.1"
|
||||
"@visx/text" "2.10.0"
|
||||
classnames "^2.3.1"
|
||||
prop-types "^15.6.0"
|
||||
@ -6831,6 +6831,24 @@
|
||||
lodash "^4.17.21"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
"@visx/shape@2.11.1":
|
||||
version "2.11.1"
|
||||
resolved "https://registry.npmjs.org/@visx/shape/-/shape-2.11.1.tgz#ff41d839ad689fea53504597bd4bfbc8b7c20bbb"
|
||||
integrity sha512-0ak3wTkXjExH7kzU62yXPu+RtuG35G1sNL58Ax4NBr4yKh2nTHcLRsMZ7k6yUG+wSjb8DgG/ywZ0bBGWXJYGbg==
|
||||
dependencies:
|
||||
"@types/d3-path" "^1.0.8"
|
||||
"@types/d3-shape" "^1.3.1"
|
||||
"@types/lodash" "^4.14.172"
|
||||
"@types/react" "*"
|
||||
"@visx/curve" "2.1.0"
|
||||
"@visx/group" "2.10.0"
|
||||
"@visx/scale" "2.2.2"
|
||||
classnames "^2.3.1"
|
||||
d3-path "^1.0.5"
|
||||
d3-shape "^1.2.0"
|
||||
lodash "^4.17.21"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
"@visx/text@2.10.0":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.npmjs.org/@visx/text/-/text-2.10.0.tgz#2ff413f4dd35617e45b24d6082d7b856358afe4c"
|
||||
@ -24952,12 +24970,12 @@ use-deep-compare-effect@^1.6.1:
|
||||
"@types/react" "^17.0.0"
|
||||
dequal "^2.0.2"
|
||||
|
||||
use-resize-observer@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-7.0.0.tgz#15f0efbd5a4e08a8cc51901f21a89ba836f2116e"
|
||||
integrity sha512-+RjrQsk/mL8aKy4TGBDiPkUv6whyeoGDMIZYk0gOGHOlnrsjImC+jG6lfAFcBCKAG9epGRL419adhDNdkDCQkA==
|
||||
use-resize-observer@^9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.0.2.tgz#25830221933d9b6e931850023305eb9d24379a6b"
|
||||
integrity sha512-JOzsmF3/IDmtjG7OE5qXOP69LEpBpwhpLSiT1XgSr+uFRX0ftJHQnDaP7Xq+uhbljLYkJt67sqsbnyXBjiY8ig==
|
||||
dependencies:
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
"@juggle/resize-observer" "^3.3.1"
|
||||
|
||||
use-sidecar@^1.0.1, use-sidecar@^1.0.5:
|
||||
version "1.0.5"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user