feat: full legend items name on hover

This commit is contained in:
Vyacheslav Matyukhin 2022-05-08 00:12:31 +04:00
parent 066b79fe12
commit a2f94efcc7
No known key found for this signature in database
GPG Key ID: 3D2A774C5489F96C
5 changed files with 155 additions and 25 deletions

68
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "2.0.0", "version": "2.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@floating-ui/react-dom": "^0.7.0",
"@graphql-yoga/node": "^2.1.0", "@graphql-yoga/node": "^2.1.0",
"@pothos/core": "^3.5.1", "@pothos/core": "^3.5.1",
"@pothos/plugin-prisma": "^3.4.0", "@pothos/plugin-prisma": "^3.4.0",
@ -1468,6 +1469,32 @@
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.0.tgz",
"integrity": "sha512-W7+i5Suhhvv97WDTW//KqUA43f/2a4abprM1rWqtLM9lIlJ29tbFI8h232SvqunXon0WmKNEKVjbOsgBhTnbLw=="
},
"node_modules/@floating-ui/dom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.0.tgz",
"integrity": "sha512-PS75dnMg4GdWjDFOiOs15cDzYJpukRNHqQn0ugrBlsrpk2n+y8bwZ24XrsdLSL7kxshmxxr2nTNycLnmRIvV7g==",
"dependencies": {
"@floating-ui/core": "^0.7.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.0.tgz",
"integrity": "sha512-mpYGykTqwtBYT+ZTQQ2OfZ6wXJNuUgmqqD9ooCgbMRgvul6InFOTtWYvtujps439hmOFiVPm4PoBkEEn5imidg==",
"dependencies": {
"@floating-ui/dom": "^0.5.0",
"use-isomorphic-layout-effect": "^1.1.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@glennsl/bs-json": { "node_modules/@glennsl/bs-json": {
"version": "5.0.4", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/@glennsl%2fbs-json/-/bs-json-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@glennsl%2fbs-json/-/bs-json-5.0.4.tgz",
@ -39293,6 +39320,19 @@
"node": ">= 10.x" "node": ">= 10.x"
} }
}, },
"node_modules/use-isomorphic-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-subscription": { "node_modules/use-subscription": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz", "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz",
@ -41182,6 +41222,28 @@
"tiny-lru": "7.0.6" "tiny-lru": "7.0.6"
} }
}, },
"@floating-ui/core": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.0.tgz",
"integrity": "sha512-W7+i5Suhhvv97WDTW//KqUA43f/2a4abprM1rWqtLM9lIlJ29tbFI8h232SvqunXon0WmKNEKVjbOsgBhTnbLw=="
},
"@floating-ui/dom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.0.tgz",
"integrity": "sha512-PS75dnMg4GdWjDFOiOs15cDzYJpukRNHqQn0ugrBlsrpk2n+y8bwZ24XrsdLSL7kxshmxxr2nTNycLnmRIvV7g==",
"requires": {
"@floating-ui/core": "^0.7.0"
}
},
"@floating-ui/react-dom": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.0.tgz",
"integrity": "sha512-mpYGykTqwtBYT+ZTQQ2OfZ6wXJNuUgmqqD9ooCgbMRgvul6InFOTtWYvtujps439hmOFiVPm4PoBkEEn5imidg==",
"requires": {
"@floating-ui/dom": "^0.5.0",
"use-isomorphic-layout-effect": "^1.1.1"
}
},
"@glennsl/bs-json": { "@glennsl/bs-json": {
"version": "5.0.4", "version": "5.0.4",
"resolved": "https://registry.npmjs.org/@glennsl%2fbs-json/-/bs-json-5.0.4.tgz", "resolved": "https://registry.npmjs.org/@glennsl%2fbs-json/-/bs-json-5.0.4.tgz",
@ -69331,6 +69393,12 @@
} }
} }
}, },
"use-isomorphic-layout-effect": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
"requires": {}
},
"use-subscription": { "use-subscription": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz", "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz",

View File

@ -27,6 +27,7 @@
"dbshell": ". .env && psql $DIGITALOCEAN_POSTGRES" "dbshell": ". .env && psql $DIGITALOCEAN_POSTGRES"
}, },
"dependencies": { "dependencies": {
"@floating-ui/react-dom": "^0.7.0",
"@graphql-yoga/node": "^2.1.0", "@graphql-yoga/node": "^2.1.0",
"@pothos/core": "^3.5.1", "@pothos/core": "^3.5.1",
"@pothos/plugin-prisma": "^3.4.0", "@pothos/plugin-prisma": "^3.4.0",

View File

@ -16,7 +16,6 @@ const getVictoryGroup = ({
i: number; i: number;
highlight?: boolean; highlight?: boolean;
}) => { }) => {
console.log(i, data, highlight, data.length);
return ( return (
<VictoryGroup color={chartColors[i] || "darkgray"} data={data} key={i}> <VictoryGroup color={chartColors[i] || "darkgray"} data={data} key={i}>
<VictoryLine <VictoryLine

View File

@ -0,0 +1,85 @@
import { useRef, useState } from "react";
import { shift, useFloating } from "@floating-ui/react-dom";
type Item = {
name: string;
color: string;
};
const LegendItem: React.FC<{ item: Item; onHighlight: () => void }> = ({
item,
onHighlight,
}) => {
const { x, y, reference, floating, strategy } = useFloating({
// placement: "right",
middleware: [shift()],
});
const [showTooltip, setShowTooltip] = useState(false);
const textRef = useRef<HTMLDivElement>();
const onHover = () => {
if (textRef.current.scrollWidth > textRef.current.clientWidth) {
setShowTooltip(true);
}
onHighlight();
};
return (
<>
<div
className="flex items-center cursor-pointer"
onMouseOver={onHover}
onMouseLeave={() => setShowTooltip(false)}
ref={reference}
>
<svg className="mt-1 shrink-0" height="10" width="16">
<circle cx="4" cy="4" r="4" fill={item.color} />
</svg>
<div
className="text-xs sm:text-sm sm:whitespace-nowrap sm:text-ellipsis sm:overflow-hidden sm:max-w-160"
ref={textRef}
>
{item.name}
</div>
</div>
{showTooltip
? (() => {
return (
<div
className={`absolute text-xs p-2 border border-gray-300 rounded bg-white ${
showTooltip ? "" : "hidden"
}`}
ref={floating}
style={{
position: strategy,
top: y ?? "",
left: x ?? "",
}}
>
{item.name}
</div>
);
})()
: null}
</>
);
};
export const Legend: React.FC<{
items: { name: string; color: string }[];
setHighlight: (i: number | undefined) => void;
}> = ({ items, setHighlight }) => {
return (
<div className="space-y-2" onMouseLeave={() => setHighlight(undefined)}>
{items.map((item, i) => (
<LegendItem
key={item.name}
item={item}
onHighlight={() => setHighlight(i)}
/>
))}
</div>
);
};

View File

@ -3,6 +3,7 @@ import React, { useMemo, useState } from "react";
import { QuestionWithHistoryFragment } from "../../../fragments.generated"; import { QuestionWithHistoryFragment } from "../../../fragments.generated";
import { InnerChartPlaceholder } from "./InnerChartPlaceholder"; import { InnerChartPlaceholder } from "./InnerChartPlaceholder";
import { Legend } from "./Legend";
import { buildChartData, chartColors } from "./utils"; import { buildChartData, chartColors } from "./utils";
const InnerChart = dynamic( const InnerChart = dynamic(
@ -14,30 +15,6 @@ interface Props {
question: QuestionWithHistoryFragment; question: QuestionWithHistoryFragment;
} }
const Legend: React.FC<{
items: { name: string; color: string }[];
setHighlight: (i: number | undefined) => void;
}> = ({ items, setHighlight }) => {
return (
<div className="space-y-2" onMouseLeave={() => setHighlight(undefined)}>
{items.map((item, i) => (
<div
className="flex items-center"
key={item.name}
onMouseOver={() => setHighlight(i)}
>
<svg className="mt-1 shrink-0" height="10" width="16">
<circle cx="4" cy="4" r="4" fill={item.color} />
</svg>
<span className="text-xs sm:text-sm sm:whitespace-nowrap sm:text-ellipsis sm:overflow-hidden sm:max-w-160">
{item.name}
</span>
</div>
))}
</div>
);
};
export const HistoryChart: React.FC<Props> = ({ question }) => { export const HistoryChart: React.FC<Props> = ({ question }) => {
// maybe use context instead? // maybe use context instead?
const [highlight, setHighlight] = useState<number | undefined>(undefined); const [highlight, setHighlight] = useState<number | undefined>(undefined);