Merge branch 'main' of https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile into boxplot-component
commit
f25839702b
@ -0,0 +1,36 @@ |
||||
.barBackground { |
||||
fill: var(--card-background); |
||||
} |
||||
|
||||
.bar { |
||||
fill: var(--primary-accent-light); |
||||
} |
||||
|
||||
.barText { |
||||
visibility: hidden; |
||||
|
||||
font-family: "Inconsolata", monospace; |
||||
font-weight: 800; |
||||
fill: var(--label); |
||||
} |
||||
|
||||
.barGroup:hover .bar { |
||||
fill: var(--primary-accent); |
||||
filter: drop-shadow(0 0 calc(4rem / 16) var(--primary-accent)); |
||||
} |
||||
|
||||
.barGroup:hover .barText { |
||||
visibility: visible; |
||||
} |
||||
|
||||
.tickLabel { |
||||
font-family: "Inconsolata", monospace; |
||||
font-weight: 800; |
||||
fill: var(--label); |
||||
} |
||||
|
||||
.axisLabel { |
||||
font-family: "Inconsolata", monospace; |
||||
font-weight: 800; |
||||
fill: var(--label); |
||||
} |
@ -0,0 +1,353 @@ |
||||
import { AxisBottom, AxisLeft } from "@visx/axis"; |
||||
import { bottomTickLabelProps } from "@visx/axis/lib/axis/AxisBottom"; |
||||
import { leftTickLabelProps } from "@visx/axis/lib/axis/AxisLeft"; |
||||
import { GridColumns, GridRows } from "@visx/grid"; |
||||
import { Group } from "@visx/group"; |
||||
import { scaleBand, scaleLinear } from "@visx/scale"; |
||||
import { Bar } from "@visx/shape"; |
||||
import { Text } from "@visx/text"; |
||||
import React from "react"; |
||||
import { Color } from "utils/Color"; |
||||
|
||||
import styles from "./BarGraph.module.css"; |
||||
|
||||
interface BarGraphProps { |
||||
data: BarGraphData[]; |
||||
/** Width of the entire graph, in pixels. */ |
||||
width: number; |
||||
/** Height of the entire graph, in pixels. */ |
||||
height: number; |
||||
/** Distance between the edge of the graph and the area where the bars are drawn, in pixels. */ |
||||
margin: { |
||||
top: number; |
||||
bottom: number; |
||||
left: number; |
||||
right: number; |
||||
}; |
||||
className?: string; |
||||
/** Font size of the category tick labels, in pixels. Default is 16px. */ |
||||
categoryTickLabelSize?: number; |
||||
/** Font size of the value tick labels, in pixels. Default is 16px. */ |
||||
valueTickLabelSize?: number; |
||||
/** Font size of the value that appears when hovering over a bar, in pixels. */ |
||||
hoverLabelSize?: number; |
||||
/** Label text for the category axis. */ |
||||
categoryAxisLabel?: string; |
||||
/** Font size of the label for the cateogry axis, in pixels. */ |
||||
categoryAxisLabelSize?: number; |
||||
/** Controls the distance between the category axis label and the category axis. */ |
||||
categoryAxisLabelOffset?: number; |
||||
/** Label text for the value axis. */ |
||||
valueAxisLabel?: string; |
||||
/** Font size of the label for the value axis, in pixels. */ |
||||
valueAxisLabelSize?: number; |
||||
/** Controls the distance between the value axis label and the value axis. */ |
||||
valueAxisLabelOffset?: number; |
||||
} |
||||
|
||||
interface BarGraphData { |
||||
category: string; |
||||
value: number; |
||||
} |
||||
|
||||
const DEFAULT_LABEL_SIZE = 16; |
||||
|
||||
export function BarGraphHorizontal(props: BarGraphProps) { |
||||
const { |
||||
width, |
||||
height, |
||||
margin, |
||||
data, |
||||
className, |
||||
categoryTickLabelSize = DEFAULT_LABEL_SIZE, |
||||
valueTickLabelSize = DEFAULT_LABEL_SIZE, |
||||
hoverLabelSize, |
||||
categoryAxisLabel, |
||||
categoryAxisLabelSize = DEFAULT_LABEL_SIZE, |
||||
categoryAxisLabelOffset = 0, |
||||
valueAxisLabel, |
||||
valueAxisLabelSize = DEFAULT_LABEL_SIZE, |
||||
valueAxisLabelOffset = 0, |
||||
} = props; |
||||
|
||||
const barPadding = 0.4; |
||||
|
||||
const categoryMax = height - margin.top - margin.bottom; |
||||
const valueMax = width - margin.left - margin.right; |
||||
|
||||
const getCategory = (d: BarGraphData) => d.category; |
||||
const getValue = (d: BarGraphData) => d.value; |
||||
|
||||
const categoryScale = scaleBand({ |
||||
range: [0, categoryMax], |
||||
domain: data.map(getCategory), |
||||
padding: barPadding, |
||||
}); |
||||
|
||||
const valueScale = scaleLinear({ |
||||
range: [0, valueMax], |
||||
nice: true, |
||||
domain: [0, Math.max(...data.map(getValue))], |
||||
}); |
||||
|
||||
const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d)); |
||||
const valuePoint = (d: BarGraphData) => valueScale(getValue(d)); |
||||
|
||||
return ( |
||||
<svg className={className} width={width} height={height}> |
||||
<Group top={margin.top} left={margin.left}> |
||||
<Group> |
||||
{data.map((d, idx) => { |
||||
const barName = `${getCategory(d)}-${idx}`; |
||||
const barWidth = categoryScale.bandwidth(); |
||||
const backgroundBarWidth = barWidth / (1 - barPadding); |
||||
return idx % 2 === 0 ? ( |
||||
<Bar |
||||
className={styles.barBackground} |
||||
key={`bar-${barName}-background`} |
||||
x={0} |
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
y={categoryPoint(d)! - (backgroundBarWidth - barWidth) / 2} |
||||
width={valueMax} |
||||
height={backgroundBarWidth} |
||||
/> |
||||
) : null; |
||||
})} |
||||
</Group> |
||||
<GridColumns |
||||
scale={valueScale} |
||||
height={categoryMax} |
||||
numTicks={5} |
||||
stroke={Color.label} |
||||
strokeWidth={4} |
||||
strokeDasharray="10" |
||||
strokeLinecap="round" |
||||
/> |
||||
<Group> |
||||
{data.map((d, idx) => { |
||||
const barName = `${getCategory(d)}-${idx}`; |
||||
const barLength = valuePoint(d); |
||||
const barWidth = categoryScale.bandwidth(); |
||||
return ( |
||||
<Group className={styles.barGroup} key={`bar-${barName}`}> |
||||
<Bar |
||||
className={styles.bar} |
||||
x={0} |
||||
y={categoryPoint(d)} |
||||
width={barLength} |
||||
height={barWidth} |
||||
/> |
||||
<Text |
||||
className={styles.barText} |
||||
x={valuePoint(d) - 12} |
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
y={categoryPoint(d)! + barWidth / 2} |
||||
fontSize={hoverLabelSize ?? barWidth * 0.75} |
||||
textAnchor="end" |
||||
verticalAnchor="middle" |
||||
> |
||||
{getValue(d)} |
||||
</Text> |
||||
</Group> |
||||
); |
||||
})} |
||||
</Group> |
||||
</Group> |
||||
<AxisLeft |
||||
scale={categoryScale} |
||||
top={margin.top} |
||||
left={margin.left} |
||||
hideAxisLine |
||||
hideTicks |
||||
tickLabelProps={() => { |
||||
return { |
||||
...leftTickLabelProps(), |
||||
className: styles.tickLabel, |
||||
dx: "-0.5rem", |
||||
dy: "0.25rem", |
||||
fontSize: `${categoryTickLabelSize / 16}rem`, |
||||
}; |
||||
}} |
||||
label={categoryAxisLabel} |
||||
labelClassName={styles.axisLabel} |
||||
labelOffset={categoryAxisLabelOffset} |
||||
labelProps={{ |
||||
fontSize: `${categoryAxisLabelSize / 16}rem`, |
||||
}} |
||||
/> |
||||
<AxisBottom |
||||
scale={valueScale} |
||||
top={margin.top + categoryMax} |
||||
left={margin.left} |
||||
hideAxisLine |
||||
hideTicks |
||||
numTicks={5} |
||||
tickLabelProps={() => { |
||||
return { |
||||
...bottomTickLabelProps(), |
||||
className: styles.tickLabel, |
||||
dy: "0.25rem", |
||||
fontSize: `${valueTickLabelSize / 16}rem`, |
||||
}; |
||||
}} |
||||
label={valueAxisLabel} |
||||
labelClassName={styles.axisLabel} |
||||
labelOffset={valueAxisLabelOffset} |
||||
labelProps={{ |
||||
fontSize: `${valueAxisLabelSize / 16}rem`, |
||||
}} |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
||||
|
||||
export function BarGraphVertical(props: BarGraphProps) { |
||||
const { |
||||
width, |
||||
height, |
||||
margin, |
||||
data, |
||||
className, |
||||
categoryTickLabelSize = DEFAULT_LABEL_SIZE, |
||||
valueTickLabelSize = DEFAULT_LABEL_SIZE, |
||||
hoverLabelSize, |
||||
categoryAxisLabel, |
||||
categoryAxisLabelSize = DEFAULT_LABEL_SIZE, |
||||
categoryAxisLabelOffset = 0, |
||||
valueAxisLabel, |
||||
valueAxisLabelSize = DEFAULT_LABEL_SIZE, |
||||
valueAxisLabelOffset = 0, |
||||
} = props; |
||||
|
||||
const barPadding = 0.4; |
||||
|
||||
const categoryMax = width - margin.left - margin.right; |
||||
const valueMax = height - margin.top - margin.bottom; |
||||
|
||||
const getCategory = (d: BarGraphData) => d.category; |
||||
const getValue = (d: BarGraphData) => d.value; |
||||
|
||||
const categoryScale = scaleBand({ |
||||
range: [0, categoryMax], |
||||
domain: data.map(getCategory), |
||||
padding: barPadding, |
||||
}); |
||||
|
||||
const valueScale = scaleLinear({ |
||||
range: [valueMax, 0], |
||||
nice: true, |
||||
domain: [0, Math.max(...data.map(getValue))], |
||||
}); |
||||
|
||||
const categoryPoint = (d: BarGraphData) => categoryScale(getCategory(d)); |
||||
const valuePoint = (d: BarGraphData) => valueScale(getValue(d)); |
||||
|
||||
return ( |
||||
<svg className={className} width={width} height={height}> |
||||
<Group top={margin.top} left={margin.left}> |
||||
<Group> |
||||
{data.map((d, idx) => { |
||||
const barName = `${getCategory(d)}-${idx}`; |
||||
const barWidth = categoryScale.bandwidth(); |
||||
const backgroundBarWidth = barWidth / (1 - barPadding); |
||||
return idx % 2 === 0 ? ( |
||||
<Bar |
||||
className={styles.barBackground} |
||||
key={`bar-${barName}-background`} |
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
x={categoryPoint(d)! - (backgroundBarWidth - barWidth) / 2} |
||||
y={0} |
||||
width={backgroundBarWidth} |
||||
height={valueMax} |
||||
/> |
||||
) : null; |
||||
})} |
||||
</Group> |
||||
<GridRows |
||||
scale={valueScale} |
||||
width={categoryMax} |
||||
numTicks={5} |
||||
stroke={Color.label} |
||||
strokeWidth={4} |
||||
strokeDasharray="10" |
||||
strokeLinecap="round" |
||||
/> |
||||
<Group> |
||||
{data.map((d, idx) => { |
||||
const barName = `${getCategory(d)}-${idx}`; |
||||
const barHeight = valueMax - valuePoint(d); |
||||
const barWidth = categoryScale.bandwidth(); |
||||
return ( |
||||
<Group className={styles.barGroup} key={`bar-${barName}`}> |
||||
<Bar |
||||
className={styles.bar} |
||||
x={categoryPoint(d)} |
||||
y={valueMax - barHeight} |
||||
width={barWidth} |
||||
height={barHeight} |
||||
/> |
||||
<Text |
||||
className={styles.barText} |
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
x={categoryPoint(d)! + barWidth / 2} |
||||
y={valueMax - barHeight + 12} |
||||
fontSize={hoverLabelSize ?? barWidth * 0.5} |
||||
textAnchor="middle" |
||||
verticalAnchor="start" |
||||
> |
||||
{getValue(d)} |
||||
</Text> |
||||
</Group> |
||||
); |
||||
})} |
||||
</Group> |
||||
</Group> |
||||
<AxisBottom |
||||
scale={categoryScale} |
||||
top={valueMax + margin.top} |
||||
left={margin.left} |
||||
hideAxisLine |
||||
hideTicks |
||||
tickLabelProps={() => { |
||||
return { |
||||
...bottomTickLabelProps(), |
||||
className: styles.tickLabel, |
||||
dy: "-0.25rem", |
||||
fontSize: `${categoryTickLabelSize / 16}rem`, |
||||
width: categoryScale.bandwidth(), |
||||
verticalAnchor: "start", |
||||
}; |
||||
}} |
||||
label={categoryAxisLabel} |
||||
labelClassName={styles.axisLabel} |
||||
labelOffset={categoryAxisLabelOffset} |
||||
labelProps={{ |
||||
fontSize: `${categoryAxisLabelSize / 16}rem`, |
||||
}} |
||||
/> |
||||
<AxisLeft |
||||
scale={valueScale} |
||||
top={margin.top} |
||||
left={margin.left} |
||||
hideAxisLine |
||||
hideTicks |
||||
numTicks={5} |
||||
tickLabelProps={() => { |
||||
return { |
||||
...leftTickLabelProps(), |
||||
className: styles.tickLabel, |
||||
dx: "-0.5rem", |
||||
dy: "0.25rem", |
||||
fontSize: `${valueTickLabelSize / 16}rem`, |
||||
}; |
||||
}} |
||||
label={valueAxisLabel} |
||||
labelClassName={styles.axisLabel} |
||||
labelOffset={valueAxisLabelOffset} |
||||
labelProps={{ |
||||
fontSize: `${valueAxisLabelSize / 16}rem`, |
||||
}} |
||||
/> |
||||
</svg> |
||||
); |
||||
} |
@ -0,0 +1,20 @@ |
||||
.word:hover { |
||||
text-shadow: var(--primary-accent) 0 0 calc(20rem / 16); |
||||
text-anchor: "middle"; |
||||
cursor: default; |
||||
} |
||||
|
||||
.tooltip { |
||||
font-family: "Inconsolata", monospace; |
||||
font-weight: bold; |
||||
top: 0; |
||||
left: 0; |
||||
position: absolute; |
||||
background-color: var(--label); |
||||
color: var(--primary-background); |
||||
box-shadow: 0px calc(1rem / 16) calc(2rem / 16) var(--card-background); |
||||
pointer-events: none; |
||||
padding: calc(10rem / 16); |
||||
font-size: calc(18rem / 16); |
||||
border-radius: calc(10rem / 16); |
||||
} |
@ -0,0 +1,210 @@ |
||||
import { localPoint } from "@visx/event"; |
||||
import { Point } from "@visx/point"; |
||||
import { scaleLog } from "@visx/scale"; |
||||
import { Text } from "@visx/text"; |
||||
import { TooltipWithBounds, useTooltip, withTooltip } from "@visx/tooltip"; |
||||
import { Wordcloud as VisxWordcloud } from "@visx/wordcloud"; |
||||
import React from "react"; |
||||
import { Color } from "utils/Color"; |
||||
|
||||
import styles from "./WordCloud.module.css"; |
||||
|
||||
interface WordCloudProps { |
||||
data: Array<WordData>; |
||||
/** Width of the graph, in px */ |
||||
width?: number; |
||||
/** Height of the graph, in px */ |
||||
height?: number; |
||||
/** Minimum padding between words, in px */ |
||||
wordPadding?: number; |
||||
/** Weight of the font of the words */ |
||||
fontWeight?: number; |
||||
/** The desired font size of the smallest word, in px.*/ |
||||
minFontSize?: number; |
||||
/** The desired font size of the largest word, in px. */ |
||||
maxFontSize?: number; |
||||
/** A random seed in the range [0, 1) used for placing the words, change this value to get an alternate placement of words */ |
||||
randomSeed?: number; |
||||
/** Type of spiral used for rendering the words, either rectangular or archimedean */ |
||||
spiral?: "rectangular" | "archimedean"; |
||||
/** ClassName of the wrapper of the wordcloud */ |
||||
className?: string; |
||||
} |
||||
|
||||
interface WordData { |
||||
text: string; |
||||
value: number; |
||||
} |
||||
|
||||
const wordColors = [Color.primaryAccent, Color.primaryAccentLight]; |
||||
const TOOLTIP_HORIZONTAL_SHIFT_SCALER = 12.0; |
||||
|
||||
export const WordCloud = withTooltip( |
||||
({ |
||||
data, |
||||
width, |
||||
height, |
||||
wordPadding, |
||||
fontWeight, |
||||
minFontSize, |
||||
maxFontSize, |
||||
randomSeed, |
||||
spiral, |
||||
className, |
||||
}: WordCloudProps) => { |
||||
const { |
||||
tooltipData, |
||||
tooltipLeft, |
||||
tooltipTop, |
||||
tooltipOpen, |
||||
showTooltip, |
||||
hideTooltip, |
||||
} = useTooltip<WordData>(); |
||||
|
||||
return ( |
||||
<div className={className}> |
||||
<WordCloudWordsMemoized |
||||
width={width} |
||||
height={height} |
||||
data={data} |
||||
wordPadding={wordPadding} |
||||
fontWeight={fontWeight} |
||||
minFontSize={minFontSize} |
||||
maxFontSize={maxFontSize} |
||||
showTooltip={(data, left, top) => { |
||||
showTooltip({ |
||||
tooltipData: data, |
||||
tooltipLeft: left, |
||||
tooltipTop: top, |
||||
}); |
||||
}} |
||||
hideTooltip={hideTooltip} |
||||
tooltipLeft={tooltipLeft} |
||||
tooltipTop={tooltipTop} |
||||
randomSeed={randomSeed} |
||||
spiral={spiral} |
||||
/> |
||||
|
||||
{tooltipOpen && tooltipData ? ( |
||||
<TooltipWithBounds |
||||
// set this to random so it correctly updates with parent bounds
|
||||
key={Math.random()} |
||||
top={tooltipTop} |
||||
left={tooltipLeft} |
||||
className={styles.tooltip} |
||||
unstyled |
||||
applyPositionStyle |
||||
> |
||||
{tooltipData.text} ({tooltipData.value}) |
||||
</TooltipWithBounds> |
||||
) : null} |
||||
</div> |
||||
); |
||||
} |
||||
); |
||||
|
||||
/** The internal wordcloud component that actually lays out the word needs to be separate from the tooltip to prevent extra rerendering. */ |
||||
type WordCloudWordsProps = Omit<WordCloudProps, "className"> & { |
||||
showTooltip: ( |
||||
data: WordData, |
||||
tooltipLeft: number, |
||||
tooltipTop: number |
||||
) => void; |
||||
hideTooltip: () => void; |
||||
// tooltipLeft and tooltipTop are used for preventing unnessary renders
|
||||
tooltipLeft?: number; |
||||
tooltipTop?: number; |
||||
}; |
||||
const WordCloudWords: React.FC<WordCloudWordsProps> = ({ |
||||
data, |
||||
width = 1000, |
||||
height = 500, |
||||
wordPadding = 30, |
||||
fontWeight = 500, |
||||
minFontSize = 20, |
||||
maxFontSize = 150, |
||||
randomSeed = 0.5, |
||||
spiral = "rectangular", |
||||
showTooltip, |
||||
hideTooltip, |
||||
}) => { |
||||
const fontScale = scaleLog({ |
||||
domain: [ |
||||
Math.min(...data.map((w) => w.value)), |
||||
Math.max(...data.map((w) => w.value)), |
||||
], |
||||
range: [minFontSize, maxFontSize], |
||||
}); |
||||
const fontSizeSetter = (datum: WordData) => fontScale(datum.value); |
||||
const fixedValueGenerator = () => randomSeed; |
||||
return ( |
||||
<VisxWordcloud |
||||
words={data} |
||||
width={width} |
||||
height={height} |
||||
fontSize={fontSizeSetter} |
||||
font="Inconsolata, monospace" |
||||
padding={wordPadding} |
||||
spiral={spiral} |
||||
rotate={0} |
||||
random={fixedValueGenerator} |
||||
> |
||||
{(cloudWords) => |
||||
cloudWords.map((word, index) => { |
||||
return ( |
||||
<Text |
||||
key={`wordcloud-word-${word.text ?? ""}-${index}`} |
||||
fill={wordColors[index % wordColors.length]} |
||||
transform={`translate(${word.x ?? 0}, ${word.y ?? 0})`} |
||||
fontSize={word.size} |
||||
fontFamily={word.font} |
||||
fontWeight={fontWeight} |
||||
className={styles.word} |
||||
textAnchor="middle" |
||||
onMouseMove={ |
||||
((e: React.MouseEvent<SVGTextElement, MouseEvent>) => { |
||||
const eventSvgCoords = localPoint( |
||||
// ownerSVGElement is given by visx docs but not recognized by typescript
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
e.target.ownerSVGElement as Element, |
||||
e |
||||
) as Point; |
||||
|
||||
if (word.text) { |
||||
showTooltip( |
||||
{ text: word.text, value: data[index].value }, |
||||
eventSvgCoords.x - |
||||
word.text.length * TOOLTIP_HORIZONTAL_SHIFT_SCALER, |
||||
eventSvgCoords.y |
||||
); |
||||
} |
||||
}) as React.MouseEventHandler<SVGTextElement> |
||||
} |
||||
onMouseLeave={(_) => hideTooltip()} |
||||
> |
||||
{word.text} |
||||
</Text> |
||||
); |
||||
}) |
||||
} |
||||
</VisxWordcloud> |
||||
); |
||||
}; |
||||
|
||||
const shouldNotRerender = ( |
||||
prevProps: WordCloudWordsProps, |
||||
nextProps: WordCloudWordsProps |
||||
) => { |
||||
if ( |
||||
prevProps.tooltipLeft !== nextProps.tooltipLeft || |
||||
prevProps.tooltipTop !== nextProps.tooltipTop || |
||||
nextProps.tooltipLeft === undefined || |
||||
nextProps.tooltipTop === undefined |
||||
) { |
||||
return true; // do not re-render
|
||||
} |
||||
return false; // will re-render
|
||||
}; |
||||
|
||||
const WordCloudWordsMemoized = React.memo(WordCloudWords, shouldNotRerender); |
@ -0,0 +1,7 @@ |
||||
.page { |
||||
padding: calc(8rem / 16); |
||||
} |
||||
|
||||
.barGraphDemo { |
||||
border: calc(1rem / 16) solid black; |
||||
} |
@ -1,13 +1,64 @@ |
||||
import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph"; |
||||
import { mockCategoricalData, moreMockCategoricalData } from "data/mocks"; |
||||
import React from "react"; |
||||
|
||||
import { ColorPalette } from "../components/ColorPalette"; |
||||
import { WordCloud } from "../components/WordCloud"; |
||||
|
||||
import styles from "./playground.module.css"; |
||||
|
||||
export default function Home() { |
||||
return ( |
||||
<> |
||||
<div className={styles.page}> |
||||
<h1>Playground</h1> |
||||
<p>Show off your components here!</p> |
||||
<ColorPalette /> |
||||
</> |
||||
|
||||
<h2> |
||||
<code>{"<BarGraphHorizontal />"}</code> |
||||
</h2> |
||||
<BarGraphHorizontal |
||||
className={styles.barGraphDemo} |
||||
data={mockCategoricalData} |
||||
width={800} |
||||
height={500} |
||||
margin={{ |
||||
top: 20, |
||||
bottom: 40, |
||||
left: 150, |
||||
right: 20, |
||||
}} |
||||
/> |
||||
|
||||
<h2> |
||||
<code>{"<BarGraphVertical />"}</code> |
||||
</h2> |
||||
<p> |
||||
<code>{"<BarGraphVertical />"}</code> takes the same props as{" "} |
||||
<code>{"<BarGraphHorizontal />"}</code>. |
||||
</p> |
||||
<BarGraphVertical |
||||
className={styles.barGraphDemo} |
||||
data={mockCategoricalData} |
||||
width={800} |
||||
height={500} |
||||
margin={{ |
||||
top: 20, |
||||
bottom: 80, |
||||
left: 60, |
||||
right: 20, |
||||
}} |
||||
/> |
||||
|
||||
<h2> |
||||
<code>{"<WordCloud />"}</code> |
||||
</h2> |
||||
<WordCloud |
||||
data={moreMockCategoricalData.map((word) => ({ |
||||
text: word.key, |
||||
value: word.value, |
||||
}))} |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
|
Loading…
Reference in new issue