feat: full legend items name on hover
This commit is contained in:
parent
066b79fe12
commit
a2f94efcc7
68
package-lock.json
generated
68
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
85
src/web/questions/components/HistoryChart/Legend.tsx
Normal file
85
src/web/questions/components/HistoryChart/Legend.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user