Worked on tooltip centering

This commit is contained in:
Shahan Nedadahandeh 2022-12-26 23:04:32 -05:00
parent 519f925155
commit bd757f56e9
8 changed files with 240 additions and 108 deletions

View File

@ -11,7 +11,7 @@ import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withToolti
import React from "react"; import React from "react";
import { Color } from "utils/Color"; import { Color } from "utils/Color";
import { TooltipWrapper } from "./TooltipWrapper"; import { getTooltipPosition, TooltipWrapper } from "./TooltipWrapper";
import styles from "./Boxplot.module.css"; import styles from "./Boxplot.module.css";
@ -96,8 +96,8 @@ export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
valueAxisLabelTopOffset = 5, valueAxisLabelTopOffset = 5,
valueAxisLabelLeftOffset = 10, valueAxisLabelLeftOffset = 10,
categoryAxisLabelLeftOffset = 30, categoryAxisLabelLeftOffset = 30,
toolTipTopOffset = 20, toolTipTopOffset = 0,
toolTipLeftOffset = 5, toolTipLeftOffset = 0,
categoryAxisLabelSize = DEFAULT_LABEL_SIZE, categoryAxisLabelSize = DEFAULT_LABEL_SIZE,
valueAxisLabelSize = DEFAULT_LABEL_SIZE, valueAxisLabelSize = DEFAULT_LABEL_SIZE,
boxPlotWidthFactor = 0.4, boxPlotWidthFactor = 0.4,
@ -149,6 +149,21 @@ export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
const constrainedWidth = Math.min(200, xScale.bandwidth()); const constrainedWidth = Math.min(200, xScale.bandwidth());
const mouseOverEventHandler =
(d: Stats) =>
(e: React.MouseEvent<SVGLineElement | SVGRectElement, MouseEvent>) => {
const pos = getTooltipPosition(e);
showTooltip({
tooltipLeft: pos.x + toolTipLeftOffset,
tooltipTop: pos.y + toolTipTopOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
};
return width < 10 ? null : ( return width < 10 ? null : (
<div> <div>
<svg width={width} height={height}> <svg width={width} height={height}>
@ -254,58 +269,19 @@ export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
strokeWidth={strokeWidth} strokeWidth={strokeWidth}
valueScale={yScale} valueScale={yScale}
minProps={{ minProps={{
onMouseOver: () => { onMouseMove: mouseOverEventHandler(d),
showTooltip({
tooltipTop:
(yScale(getMin(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseLeave: () => { onMouseLeave: () => {
hideTooltip(); hideTooltip();
}, },
}} }}
maxProps={{ maxProps={{
onMouseOver: () => { onMouseMove: mouseOverEventHandler(d),
showTooltip({
tooltipTop:
(yScale(getMax(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseLeave: () => { onMouseLeave: () => {
hideTooltip(); hideTooltip();
}, },
}} }}
boxProps={{ boxProps={{
onMouseOver: () => { onMouseMove: mouseOverEventHandler(d),
showTooltip({
tooltipTop:
(yScale(getMedian(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
strokeWidth: 0, strokeWidth: 0,
onMouseLeave: () => { onMouseLeave: () => {
hideTooltip(); hideTooltip();
@ -315,20 +291,7 @@ export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
style: { style: {
stroke: Color.label, stroke: Color.label,
}, },
onMouseOver: () => { onMouseMove: mouseOverEventHandler(d),
showTooltip({
tooltipTop:
(yScale(getMedian(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseLeave: () => { onMouseLeave: () => {
hideTooltip(); hideTooltip();
}, },

View File

@ -2,7 +2,6 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: min-content;
} }
.barBackground { .barBackground {
@ -25,4 +24,4 @@
display: flex; display: flex;
margin: calc(16rem / 16); margin: calc(16rem / 16);
justify-content: center; justify-content: center;
} }

View File

@ -13,7 +13,7 @@ import { withTooltip } from "@visx/tooltip";
import React, { useState } from "react"; import React, { useState } from "react";
import { Color } from "utils/Color"; import { Color } from "utils/Color";
import { TooltipWrapper } from "./TooltipWrapper"; import { getTooltipPosition, TooltipWrapper } from "./TooltipWrapper";
import styles from "./GroupedBarGraph.module.css"; import styles from "./GroupedBarGraph.module.css";
@ -257,17 +257,12 @@ export const GroupedBarGraphVertical = withTooltip<
{barGroup.bars.map((bar) => ( {barGroup.bars.map((bar) => (
<HoverableBar <HoverableBar
onMouseMove={(e) => { onMouseMove={(e) => {
const eventSvgCoords = localPoint( const tooltipPos = getTooltipPosition(e);
// 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;
showTooltip({ showTooltip({
tooltipData: bar.value.toString(), tooltipData: bar.value.toString(),
tooltipTop: eventSvgCoords.y, tooltipTop: tooltipPos.y,
tooltipLeft: eventSvgCoords.x, tooltipLeft: tooltipPos.x,
}); });
}} }}
onMouseOut={hideTooltip} onMouseOut={hideTooltip}

View File

@ -10,6 +10,7 @@
display: flex; display: flex;
font-size: calc(16rem / 16); font-size: calc(16rem / 16);
top: 0; top: 0;
justify-content: center;
} }
.key { .key {

View File

@ -1,3 +1,5 @@
import localPoint from "@visx/event/lib/localPoint";
import { Point } from "@visx/point";
import { Tooltip } from "@visx/tooltip"; import { Tooltip } from "@visx/tooltip";
import React from "react"; import React from "react";
@ -11,6 +13,23 @@ type TooltipWrapperProps = {
children?: React.ReactNode; children?: React.ReactNode;
}; };
// Finds the SVG Element which is the outmost from element (highest parent of element which is svg)
function getOutmostSVG(element: Element): SVGElement | undefined {
let rootSVG: HTMLElement | Element | null = element ?? null;
let current: HTMLElement | Element | null = element;
while (current) {
console.log(current);
if (current.tagName == "svg") {
rootSVG = current;
}
current = current.parentElement;
}
return rootSVG as SVGElement;
}
const TooltipWrapper = ({ const TooltipWrapper = ({
top, top,
left, left,
@ -32,4 +51,45 @@ const TooltipWrapper = ({
); );
}; };
export { TooltipWrapper }; function getTooltipPosition(
e: React.MouseEvent<
SVGTextElement | SVGPathElement | SVGLineElement,
MouseEvent
>
) {
// ownerSVGElement is given by visx docs but not recognized by typescript
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const eventElement = e.target.ownerSVGElement as Element;
const eventSvgCoords = localPoint(eventElement, e) as Point;
const rootSVG: SVGElement | undefined = getOutmostSVG(eventElement);
if (!rootSVG) {
console.error("Failed to find parent SVG for tooltip!");
return { x: 0, y: 0 };
}
console.log(eventElement);
console.log(rootSVG);
console.log("parent is ", rootSVG.parentElement);
const rootSVGLeft = rootSVG.getBoundingClientRect().left ?? 0;
console.log(rootSVGLeft);
const parentDivLeft =
rootSVG.parentElement?.getBoundingClientRect().left ?? 0;
console.log(parentDivLeft);
// const parentParentDivLeft =
// eventElement.parentElement?.parentElement?.parentElement?.parentElement?.getBoundingClientRect()
// .left ?? 0;
// console.log(parentParentDivLeft);
// visx localPoint does not account for the horizontal shift due to centering of the parent element,
// so manually add any shift from that
const alignmentOffset = rootSVGLeft - parentDivLeft;
console.log(alignmentOffset);
return {
x: eventSvgCoords.x + alignmentOffset,
y: eventSvgCoords.y,
};
}
export { TooltipWrapper, getTooltipPosition };

View File

@ -1,5 +1,3 @@
import { localPoint } from "@visx/event";
import { Point } from "@visx/point";
import { scaleLog } from "@visx/scale"; import { scaleLog } from "@visx/scale";
import { Text } from "@visx/text"; import { Text } from "@visx/text";
import { useTooltip, withTooltip } from "@visx/tooltip"; import { useTooltip, withTooltip } from "@visx/tooltip";
@ -9,7 +7,7 @@ import { Color } from "utils/Color";
import { inDevEnvironment } from "utils/inDevEnviroment"; import { inDevEnvironment } from "utils/inDevEnviroment";
import { useIsMobile } from "utils/isMobile"; import { useIsMobile } from "utils/isMobile";
import { TooltipWrapper } from "./TooltipWrapper"; import { getTooltipPosition, TooltipWrapper } from "./TooltipWrapper";
import styles from "./WordCloud.module.css"; import styles from "./WordCloud.module.css";
@ -197,33 +195,22 @@ const WordCloudWords: React.FC<WordCloudWordsProps> = ({
className={styles.word} className={styles.word}
textAnchor="middle" textAnchor="middle"
onMouseMove={ onMouseMove={
((e: React.MouseEvent<SVGTextElement, MouseEvent>) => { ((
// ownerSVGElement is given by visx docs but not recognized by typescript e: React.MouseEvent<
// eslint-disable-next-line @typescript-eslint/ban-ts-comment SVGTextElement | SVGLineElement,
// @ts-ignore MouseEvent
const eventElement = e.target.ownerSVGElement as Element; >
const eventSvgCoords = localPoint(eventElement, e) as Point; ) => {
const rootSVGLeft = const tooltipPos = getTooltipPosition(e);
eventElement.parentElement?.parentElement?.getBoundingClientRect()
.left ?? 0;
const parentDivLeft =
eventElement.parentElement?.parentElement?.parentElement?.getBoundingClientRect()
.left ?? 0;
// visx localPoint does not account for the horizontal shift due to centering of the parent element,
// so manually add any shift from that
const alignmentOffset = rootSVGLeft - parentDivLeft;
if (word.text) { if (word.text) {
showTooltip( showTooltip(
{ {
text: word.text, text: word.text,
value: (cloudWords[index] as WordData).value, value: (cloudWords[index] as WordData).value,
}, },
eventSvgCoords.x - tooltipPos.x -
word.text.length * TOOLTIP_HORIZONTAL_SHIFT_SCALER + word.text.length * TOOLTIP_HORIZONTAL_SHIFT_SCALER,
alignmentOffset, tooltipPos.y
eventSvgCoords.y
); );
} }
}) as React.MouseEventHandler<SVGTextElement> }) as React.MouseEventHandler<SVGTextElement>

View File

@ -18,6 +18,7 @@ import React from "react";
import { Color } from "utils/Color"; import { Color } from "utils/Color";
import { About } from "@/components/About"; import { About } from "@/components/About";
import { ComponentWrapper } from "@/components/ComponentWrapper";
import { import {
GroupedBarGraphHorizontal, GroupedBarGraphHorizontal,
GroupedBarGraphVertical, GroupedBarGraphVertical,
@ -26,6 +27,7 @@ import { LineGraph } from "@/components/LineGraph";
import { PieChart } from "@/components/PieChart"; import { PieChart } from "@/components/PieChart";
import { QuotationCarousel } from "@/components/QuotationCarousel"; import { QuotationCarousel } from "@/components/QuotationCarousel";
import { Sections } from "@/components/Sections"; import { Sections } from "@/components/Sections";
import { SectionWrapper } from "@/components/SectionWrapper";
import { import {
StackedBarGraphVertical, StackedBarGraphVertical,
StackedBarGraphHorizontal, StackedBarGraphHorizontal,
@ -67,18 +69,7 @@ export default function Home() {
<h2> <h2>
<code>{"<BarGraphHorizontal />"}</code> <code>{"<BarGraphHorizontal />"}</code>
</h2> </h2>
<BarGraphHorizontal
className={styles.barGraphDemo}
data={mockCategoricalData}
width={800}
height={500}
margin={{
top: 25,
bottom: 40,
left: 150,
right: 20,
}}
/>
<h2> <h2>
<code>{"<BarGraphVertical />"}</code> <code>{"<BarGraphVertical />"}</code>

View File

@ -1,7 +1,11 @@
import { import {
mockBoxPlotData, mockBoxPlotData,
mockCategoricalData, mockCategoricalData,
mockGroupedBarGraphData,
mockLineData, mockLineData,
mockPieData,
mockStackedBarGraphData,
mockStackedBarKeys,
moreMockCategoricalData, moreMockCategoricalData,
} from "data/mocks"; } from "data/mocks";
import { pageRoutes } from "data/routes"; import { pageRoutes } from "data/routes";
@ -14,9 +18,19 @@ import { BarGraphVertical, BarGraphHorizontal } from "@/components/BarGraph";
import { BottomNav } from "@/components/BottomNav"; import { BottomNav } from "@/components/BottomNav";
import { BoxPlot } from "@/components/Boxplot"; import { BoxPlot } from "@/components/Boxplot";
import { ComponentWrapper } from "@/components/ComponentWrapper"; import { ComponentWrapper } from "@/components/ComponentWrapper";
import {
GroupedBarGraphHorizontal,
GroupedBarGraphVertical,
} from "@/components/GroupedBarGraph";
import { Header } from "@/components/Header"; import { Header } from "@/components/Header";
import { LineGraph } from "@/components/LineGraph"; import { LineGraph } from "@/components/LineGraph";
import { PieChart } from "@/components/PieChart";
import { SectionHeader } from "@/components/SectionHeader"; import { SectionHeader } from "@/components/SectionHeader";
import { SectionWrapper } from "@/components/SectionWrapper";
import {
StackedBarGraphVertical,
StackedBarGraphHorizontal,
} from "@/components/StackedBarGraph";
import { WordCloud } from "@/components/WordCloud"; import { WordCloud } from "@/components/WordCloud";
import styles from "./samplePage.module.css"; import styles from "./samplePage.module.css";
@ -46,6 +60,128 @@ export default function SamplePage() {
return ( return (
<div className={styles.page}> <div className={styles.page}>
<Header /> <Header />
<SectionWrapper title="Transfer" />
<ComponentWrapper align="right" heading={""}>
<PieChart data={mockPieData} width={800} labelWidth={215} />
<BarGraphHorizontal
className={styles.barGraphDemo}
data={mockCategoricalData}
width={800}
height={500}
margin={{
top: 25,
bottom: 40,
left: 150,
right: 20,
}}
/>
<BarGraphVertical
className={styles.barGraphDemo}
data={mockCategoricalData}
width={800}
height={500}
margin={{
top: 20,
bottom: 80,
left: 60,
right: 20,
}}
/>
<StackedBarGraphVertical
width={600}
height={400}
keys={mockStackedBarKeys}
colorRange={[
Color.primaryAccent,
Color.secondaryAccentLight,
Color.primaryAccentLighter,
]}
data={mockStackedBarGraphData}
margin={{
top: 20,
left: 20,
}}
/>
<StackedBarGraphHorizontal
width={600}
height={400}
keys={mockStackedBarKeys}
colorRange={[
Color.primaryAccent,
Color.secondaryAccentLight,
Color.primaryAccentLighter,
]}
data={mockStackedBarGraphData}
margin={{
top: 20,
left: 20,
}}
/>
<LineGraph
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data={mockLineData}
width={600}
height={400}
margin={{
top: 20,
bottom: 80,
left: 30,
right: 20,
}}
colorRange={[Color.primaryAccent, Color.secondaryAccentLight]}
/>
<GroupedBarGraphHorizontal
className={styles.barGraphDemo}
data={mockGroupedBarGraphData}
barColors={[Color.primaryAccentLight, Color.secondaryAccentLight]}
barHoverColorsMap={{
[Color.primaryAccentLight]: Color.primaryAccent,
[Color.secondaryAccentLight]: Color.secondaryAccent,
}}
width={600}
height={400}
margin={{
top: 20,
bottom: 40,
left: 60,
right: 20,
}}
/>
<GroupedBarGraphVertical
className={styles.barGraphDemo}
data={mockGroupedBarGraphData}
barColors={[Color.primaryAccentLight, Color.secondaryAccentLight]}
barHoverColorsMap={{
[Color.primaryAccentLight]: Color.primaryAccent,
[Color.secondaryAccentLight]: Color.secondaryAccent,
}}
width={500}
height={400}
margin={{
top: 20,
bottom: 40,
left: 50,
right: 20,
}}
/>
<BoxPlot
width={600}
height={400}
data={mockBoxPlotData}
margin={{
top: 20,
left: 20,
}}
/>
<WordCloud
data={moreMockCategoricalData.map((word) => ({
text: word.key,
value: word.value,
}))}
/>
</ComponentWrapper>
<SectionHeader <SectionHeader
title="Demographics" title="Demographics"
subtitle="An insight into the demographics of UWs CS programs" subtitle="An insight into the demographics of UWs CS programs"