Add BoxPlot component (Closes #6) #34
Loading…
Reference in New Issue
No description provided.
Delete Branch "boxplot-component"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Done:
playground.tsx
My version:
Note:
No percentage displayed next to value axis labels.
Hovering over the individual boxplot does not darken the colour of the boxplot itself.
We can add outlier points although it could complicate the graph.
Still displays the following error:
Staging: https://boxplot-component-csc-class-profile-staging-snedadah.k8s.csclub.cloud/
Please delete
CandlestickChart.tsx
since it's empty, and please fix thepackage-lock.json
(it should be sufficient to runnpm i
and commit the generatedpackage-lock.json
).Otherwise, looks great overall - left a few comments about nits, styling consistency, and a small bug in an edge case 🙂
@ -0,0 +20,4 @@
const DEFAULT_LABEL_SIZE = 16;
interface BoxPlotData {
x: string;
NIT: maybe name this
label
orname
or something, just to be a bit more descriptive?The name was left as
x
since theboxPlot
property in theStats
object takes in a field namedx
. With the changes (of changingx
to a more descriptive name), inserting the boxplot data would look like this:since the
BoxPlotData
would have a different label name forx
. If we don't implement the changes, inserting boxplot data would look like this:I was wondering whether the name change was really necessary. We could add a comment next to the field
x
in theinterface BoxPlotData
to explain its purpose.We should be able to do
which is fairly concise, in my opinion. But ultimately it's up to you - a descriptive JSDoc comment in the interface would be a good idea either way. 🙂
@ -0,0 +26,4 @@
max: number;
firstQuartile: number;
thirdQuartile: number;
outliers: number[];
NIT: could we make
outliers
optional?@ -0,0 +29,4 @@
outliers: number[];
}
interface TooltipData {
Consider making the
TooltipData
type based off ofBoxPlotData
usingOmit
andPartial
?https://www.typescriptlang.org/docs/handbook/utility-types.html
@ -0,0 +39,4 @@
}
export type StatsPlotProps = {
width: number;
NIT: add a jsdoc (ie.
/** this */
) for each prop so people know what each prop does when they're using the component. See the bar graph or word cloud for an example.@ -0,0 +47,4 @@
left: number;
};
strokeWidth: number;
strokeDashArray: string;
NIT: I would suggest making
strokeWidth
andstrokeDashArray
optional props as well (ie. they already have default values), so people don't need to worry about them unless they want to customize the styling.strokeDashArray
in particular needs to be in a specific format, so it'd be great if people didn't have to worry about that when using the component. :)@ -0,0 +58,4 @@
toolTipFontSize?: number;
};
export default withTooltip<StatsPlotProps, TooltipData>(
NIT: we usually use non-default exports for components
@ -0,0 +86,4 @@
const yMax = height - 120;
// formatting data
const d: Stats[] = [];
for (let i = 0; i < data.length; i++) {
Two nits:
d
. (plotData
orformattedData
, perhaps?)My personal philosophy (feel free to disagree) when working with React stuff is to prefer functional logic (eg.
Array.map
) over imperative logic (eg. traditional for-loops) where it makes reasonable sense, since React itself follows more of a functional paradigm.@ -0,0 +94,4 @@
}
// accessors
const x = (d: Stats) => d.boxPlot.x;
NIT: name accessors as
getX
,getMin
, etc.@ -0,0 +103,4 @@
// scales
const xScale = scaleBand<string>({
range: [18, xMax - 80], // scaling is needed due to the left offset
Do these hardcoded numbers still work well for different graph widths, margins, datasets, etc.?
These hardcoded numbers work well if you change the width, height, margins, and datasets. I haven't tested for whether these hardcoded numbers work when you change the offset values though.
@ -0,0 +123,4 @@
});
const boxWidth = xScale.bandwidth();
const constrainedWidth = Math.min(200, boxWidth);
NIT: since it doesn't look like
boxWidth
is being used elsewhere, we could do this on one line, ie.@ -0,0 +125,4 @@
const boxWidth = xScale.bandwidth();
const constrainedWidth = Math.min(200, boxWidth);
return width < 10 ? null : (
NIT: restrictions like these on props are good things to include in the jsdoc descriptions in the props interface. 🙂
Are you suggesting that I mention this constraint in the
StatsPlotProps
interface (width >= 10) and I remove this conditional rendering or should I keep this conditional rendering?You can keep the conditional rendering, and then additionally mention it in the interface.
@ -0,0 +126,4 @@
const constrainedWidth = Math.min(200, boxWidth);
return width < 10 ? null : (
<div style={{ position: "relative" }}>
Is this (both the
div
and theposition: relative
) necessary?EDIT: My guess is that the relative positioning is necessary for the tooltip, if so then I would suggest using a className instead of inline styling like this.
@ -0,0 +130,4 @@
<svg width={width} height={height}>
<Group top={margin.top} left={margin.left}>
<GridRows
top={10}
NIT: probably good to pull this out into a constant, seeing as it's (presumably) important for the +10 in the
Line
components down below to match this value here?@ -0,0 +143,4 @@
<GridColumns
scale={xScale}
width={xMax}
height={yMax + 32}
I'm curious what the +32 is for? Does this number depend on any other hard-coded values and/or props?
@ -0,0 +162,4 @@
fill={Color.tertiaryBackground}
to={
new Point({
x: xMax - 20 - strokeWidth,
Similar to my comment above - does the -20 depend on other hard-coded values and/or user-provided props?
@ -0,0 +172,4 @@
strokeDasharray={strokeDashArray}
/>
<AxisBottom
top={yMax + 30}
Same as above - does the +30 depend on other hard-coded values and/or user-provided props?
@ -0,0 +203,4 @@
};
}}
/>
<Group top={6}>
Same as above - does the 6 depend on other hard-coded values and/or user-provided props?
@ -0,0 +205,4 @@
/>
<Group top={6}>
{d.map((d: Stats, i) => (
<g key={i}>
NIT: any reason why we're not using the visx
Group
component here?@ -0,0 +220,4 @@
median={median(d)}
boxWidth={constrainedWidth * 0.4}
fill={Color.primaryAccentLight}
fillOpacity={0.8}
For consistency with the other graphs, can we set
fillOpacity={1}
? (or just omit it, since1
should be the default value)Also while we're on the topic of styling, we can specify
rx={0}
andry={0}
to get rid of the rounded corners on the box, which would be consistent with the bar graphs. (Apparently the default value is 2, not 0, which is why we're getting those rounded corners.) 🙂@ -0,0 +227,4 @@
minProps={{
onMouseOver: () => {
showTooltip({
tooltipTop: yScale(min(d)) ?? 0 + 40,
NIT: either
yScale(min(d))!
if we can do a non-null assertion, or simplify0 + 40
to40
oh wait I just realized you probably meant
(yScale(min(d)) ?? 0) + 40
, I think the brackets might be necessary since ?? has a very low precedence when it comes to order of operations.but comment about ! still applies, if applicable :))
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
@ -0,0 +242,4 @@
maxProps={{
onMouseOver: () => {
showTooltip({
tooltipTop: yScale(max(d)) ?? 0 + 40,
same as above
@ -0,0 +254,4 @@
hideTooltip();
},
}}
boxProps={{
can add
stroke: "none"
to theboxProps
object to get rid of the outline around the box@ -0,0 +257,4 @@
boxProps={{
onMouseOver: () => {
showTooltip({
tooltipTop: yScale(median(d)) ?? 0 + 20,
same as above
@ -0,0 +271,4 @@
}}
medianProps={{
style: {
stroke: "white",
NIT:
stroke: Color.label
if it works@ -0,0 +275,4 @@
},
onMouseOver: () => {
showTooltip({
tooltipTop: yScale(median(d)) ?? 0 + 40,
same as above lol
@ -0,0 +302,4 @@
...defaultTooltipStyles,
backgroundColor: Color.secondaryBackground,
color: Color.primaryText,
padding: "10px",
NIT: does
calc(10rem / 16)
work here?@ -0,0 +306,4 @@
}}
>
<div>
<strong>{tooltipData.name}</strong>
Can we try to use a more meaningful element here rather than just a
div
? Maybe a<p>
?Also a NIT: use CSS instead of
<strong>
if we can@ -0,0 +310,4 @@
</div>
<div
style={{
marginTop: "5px",
NIT:
calc(5rem / 16)
if it worksAlso just a note that we should generally prefer styling via CSS whenever possible, instead of inline styles like this. 🙂
@ -0,0 +322,4 @@
{tooltipData.firstQuartile && (
<div>first quartile: {tooltipData.firstQuartile}</div>
)}
{tooltipData.min && <div>min: {tooltipData.min}</div>}
What happens here if
tooltipData.min
etc. is0
or some other falsy (but non-null) value? 😉This is why we should always prefer the ternary operator for conditional rendering, instead of using the && "hack".
or in this case, since the value could potentially be
0
,Also, NIT: using a more meaningful html tag like
p
would be preferred over adiv
.@ -10,3 +15,3 @@
export default function Home() {
return (
<div className={styles.page}>
<div className={styles.page} suppressHydrationWarning>
Does this actually do anything? 🤔
Regardless, if you can fix the lint errors and get CI to pass, everything will probably be fine on staging since it's a static site. You should double check which DOM elements are causing the hydration warnings, but my guess is that (a) it's visx's fault and (b) it's only causing wooziness because the dev env tries to use some SSR.
Oops wait I didn't mean to approve the PR yet - wanted to hear back about some of those comments first
Oh yeah @e26chiu another thing I forgot to mention - would it be possible to give the tooltips a light background with dark text, just to be consistent with the tooltips on other components (eg. word cloud)? Thanks 🙂
Thanks for iterating on the feedback, just a few more nits and a small bug!
Also, please fix the
package-lock.json
by runningnpm install
and committing the generatedpackage-lock.json
. (This is blocking approval right now.) Thanks!@ -0,0 +10,4 @@
.tooltip {
background-color: var(--label);
color: var(--primary-background);
padding: calc(10rem / 16);
NIT: add
border-radius: calc(10rem / 16);
to the tooltip to match the tooltip styles on the word cloud more closely. (we can go through the site and fix font size/weight later)@ -0,0 +21,4 @@
.tooltip .toolTipData {
margin-top: 5px;
margin-bottom: 10px;
font-size: 16px;
NIT: use
calc(16rem / 16)
etc. for all thepx
values in this file@ -0,0 +274,4 @@
toolTipLeftOffset,
tooltipData: {
min: getMin(d),
max: getMax(d),
For the
boxProps
, you didWould that work for the
minProps
andmaxProps
andmedianProps
as well?@ -0,0 +309,4 @@
hideTooltip();
},
}}
boxProps={{
NIT: add
stroke: none
(or something like that) to the boxProps object to get rid of the outline around the box while keeping the whisker lines.@ -0,0 +378,4 @@
{tooltipData.firstQuartile ? (
<p>first quartile: {tooltipData.firstQuartile}</p>
) : null}
{tooltipData.min ? <p>min: {tooltipData.min}</p> : null}
When the minimum (or another one of the stats) is
0
, the "min: 0" won't render at all. Should usetooltipData.min != null ? ...
for these.Though now that everything in the
TooltipData
type is required, we can probably get rid of all these conditional statements.🚢
@ -0,0 +15,4 @@
}
.tooltip .category {
margin: 10px 0 0 0;
NIT:
calc(10rem / 16)