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 { Color } from "utils/Color";
import { TooltipWrapper } from "./TooltipWrapper";
import { getTooltipPosition, TooltipWrapper } from "./TooltipWrapper";
import styles from "./Boxplot.module.css";
@ -96,8 +96,8 @@ export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
valueAxisLabelTopOffset = 5,
valueAxisLabelLeftOffset = 10,
categoryAxisLabelLeftOffset = 30,
toolTipTopOffset = 20,
toolTipLeftOffset = 5,
toolTipTopOffset = 0,
toolTipLeftOffset = 0,
categoryAxisLabelSize = DEFAULT_LABEL_SIZE,
valueAxisLabelSize = DEFAULT_LABEL_SIZE,
boxPlotWidthFactor = 0.4,
@ -149,6 +149,21 @@ export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
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 : (
<div>
<svg width={width} height={height}>
@ -254,58 +269,19 @@ export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
strokeWidth={strokeWidth}
valueScale={yScale}
minProps={{
onMouseOver: () => {
showTooltip({
tooltipTop:
(yScale(getMin(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseMove: mouseOverEventHandler(d),
onMouseLeave: () => {
hideTooltip();
},
}}
maxProps={{
onMouseOver: () => {
showTooltip({
tooltipTop:
(yScale(getMax(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseMove: mouseOverEventHandler(d),
onMouseLeave: () => {
hideTooltip();
},
}}
boxProps={{
onMouseOver: () => {
showTooltip({
tooltipTop:
(yScale(getMedian(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseMove: mouseOverEventHandler(d),
strokeWidth: 0,
onMouseLeave: () => {
hideTooltip();
@ -315,20 +291,7 @@ export const BoxPlot = withTooltip<StatsPlotProps, TooltipData>(
style: {
stroke: Color.label,
},
onMouseOver: () => {
showTooltip({
tooltipTop:
(yScale(getMedian(d)) ?? 0) + toolTipTopOffset,
tooltipLeft:
xScale(getX(d))! +
constrainedWidth +
toolTipLeftOffset,
tooltipData: {
...d.boxPlot,
category: getX(d),
},
});
},
onMouseMove: mouseOverEventHandler(d),
onMouseLeave: () => {
hideTooltip();
},

View File

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

View File

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

View File

@ -10,6 +10,7 @@
display: flex;
font-size: calc(16rem / 16);
top: 0;
justify-content: center;
}
.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 React from "react";
@ -11,6 +13,23 @@ type TooltipWrapperProps = {
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 = ({
top,
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 { Text } from "@visx/text";
import { useTooltip, withTooltip } from "@visx/tooltip";
@ -9,7 +7,7 @@ import { Color } from "utils/Color";
import { inDevEnvironment } from "utils/inDevEnviroment";
import { useIsMobile } from "utils/isMobile";
import { TooltipWrapper } from "./TooltipWrapper";
import { getTooltipPosition, TooltipWrapper } from "./TooltipWrapper";
import styles from "./WordCloud.module.css";
@ -197,33 +195,22 @@ const WordCloudWords: React.FC<WordCloudWordsProps> = ({
className={styles.word}
textAnchor="middle"
onMouseMove={
((e: React.MouseEvent<SVGTextElement, 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 rootSVGLeft =
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;
((
e: React.MouseEvent<
SVGTextElement | SVGLineElement,
MouseEvent
>
) => {
const tooltipPos = getTooltipPosition(e);
if (word.text) {
showTooltip(
{
text: word.text,
value: (cloudWords[index] as WordData).value,
},
eventSvgCoords.x -
word.text.length * TOOLTIP_HORIZONTAL_SHIFT_SCALER +
alignmentOffset,
eventSvgCoords.y
tooltipPos.x -
word.text.length * TOOLTIP_HORIZONTAL_SHIFT_SCALER,
tooltipPos.y
);
}
}) as React.MouseEventHandler<SVGTextElement>

View File

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

View File

@ -1,7 +1,11 @@
import {
mockBoxPlotData,
mockCategoricalData,
mockGroupedBarGraphData,
mockLineData,
mockPieData,
mockStackedBarGraphData,
mockStackedBarKeys,
moreMockCategoricalData,
} from "data/mocks";
import { pageRoutes } from "data/routes";
@ -14,9 +18,19 @@ import { BarGraphVertical, BarGraphHorizontal } from "@/components/BarGraph";
import { BottomNav } from "@/components/BottomNav";
import { BoxPlot } from "@/components/Boxplot";
import { ComponentWrapper } from "@/components/ComponentWrapper";
import {
GroupedBarGraphHorizontal,
GroupedBarGraphVertical,
} from "@/components/GroupedBarGraph";
import { Header } from "@/components/Header";
import { LineGraph } from "@/components/LineGraph";
import { PieChart } from "@/components/PieChart";
import { SectionHeader } from "@/components/SectionHeader";
import { SectionWrapper } from "@/components/SectionWrapper";
import {
StackedBarGraphVertical,
StackedBarGraphHorizontal,
} from "@/components/StackedBarGraph";
import { WordCloud } from "@/components/WordCloud";
import styles from "./samplePage.module.css";
@ -46,6 +60,128 @@ export default function SamplePage() {
return (
<div className={styles.page}>
<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
title="Demographics"
subtitle="An insight into the demographics of UWs CS programs"