Open jasonamyers opened 1 year ago
Hi @jasonamyers, that's an issue users reported many times 😓, SVG or canvas don't provide much flexibility to build this kind of layout, and I don't want to reimplement this in the lib.
I've started to implement a forwardLegendData
property in @nivo/waffle
, which will allow users to create their own legends outside of the chart, you can see how it can be used here, but unfortunately, it has not been implemented yet in @nivo/line
, and I cannot tell you when it will as I currently have other priorities.
Hey @plouc thank you so much for the response. Completely understand and I really do like what you're doing with the forwardLegendData. Thank you for Nivo and I appreciate all your work and support efforts!
@plouc Hey, I was wondering if we could have something similar for the heatmap chart as well. or is thre any way I can add custom legends which could be a react component outside the chart and have access to the main data passed through the heatmap graph? we wanted something similar for our project.
@yubi00, yes, technically, it is feasible.
hey @jasonamyers not sure if this helps but I had the same problem so I create my own custom legends component inspired on how the Pie custom component was made here.
Here you can see the code that I created:
import {
TooltipProvider,
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@radix-ui/react-tooltip";
import { ResponsiveLine } from '@nivo/line'
interface LineDto {
id: string;
data: Daum[];
}
interface Daum {
x: string;
y: number;
}
function CustomTooltip({
data,
}: {
data: LineDto[];
}) {
// my own custom colors so its matchs the chart
const colors = [
"rgb(31, 119, 180)",
"rgb(255, 127, 14)",
"rgb(44, 160, 44)",
"rgb(214, 39, 40)",
"rgb(148, 103, 189)",
"rgb(140, 86, 75)",
"rgb(227, 119, 194)",
"rgb(127, 127, 127)",
"rgb(188, 189, 34)",
"rgb(23, 190, 207)",
];
return (
<div
className={`flex justify-center items-center overflow-auto min-h-[40px] gap-2 px-2 w-full`}
>
{data.map((d, i) => {
const colorIndex = i % colors.length;
const color = colors[colorIndex];
// I generate the final string here, you can just use the d.id but in my case the id has a lot of information so i need to format it and thats why i use the generateFinalString function
console.log(d, 'd')
const final = generateFinalString(d);
// I only want to show 6 items in the tooltip, so i check if the index is greater than 6 and if it is i return null
if (i > 6) {
return null;
}
return <TooltipItem
key={d.id}
color={color} final={final} id={d.id} />;
})}
{data.length > 6 && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-neutral-600 font-normal text-sm min-w-[90px] ">
Next {data.length - 6}
</TooltipTrigger>
<TooltipContent
className={`cursor-pointer p-4 bg-white text-neutral-900 rounded-md shadow-md flex flex-col gap-1`}
>
{data.map((d, i) => {
const colorIndex = i % colors.length;
const color = colors[colorIndex];
const final = generateFinalString(d);
if (i < 6) {
return null;
}
return <TooltipItem
key={d.id}
color={color} final={final} id={d.id} />;
})}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
);
}
export const TooltipItem = ({ color, final, id }: { color: string; final: string; id: string }) => (
<TooltipProvider key={id}>
<Tooltip>
<TooltipTrigger className="flex items-center gap-2 overflow-hidden overflow-ellipsis whitespace-nowrap ">
<div
className="w-3 h-3 min-w-[12px]"
/>
<p className="text-xs overflow-hidden overflow-ellipsis whitespace-nowrap">
{final}
</p>
</TooltipTrigger>
<TooltipContent
className={`cursor-pointer p-4 bg-white text-neutral-900 rounded-md shadow-md flex flex-col gap-1`}
>
<p>{final}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
const MyResponsiveLine = ({ data }: {
data: LineDto[]
}) => (
<>
<CustomTooltip
data={data}
/>
<ResponsiveLine
data={data}
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
xScale={{ type: 'point' }}
yScale={{
type: 'linear',
min: 'auto',
max: 'auto',
stacked: true,
reverse: false
}}
yFormat=" >-.2f"
axisTop={null}
axisRight={null}
axisBottom={{
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: 'transportation',
legendOffset: 36,
legendPosition: 'middle'
}}
axisLeft={{
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: 'count',
legendOffset: -40,
legendPosition: 'middle'
}}
pointSize={10}
pointColor={{ theme: 'background' }}
pointBorderWidth={2}
pointBorderColor={{ from: 'serieColor' }}
pointLabelYOffset={-12}
useMesh={true}
/>
</>
)
I use tailwind and radixui for my tooltip component, you could use shadcn or you own custom component, keep in mind the code is for my own requirements but its a blueprint on how you could do your own and with the help of flex you can do exactly the layout you are looking for, hope this helps you.
this is the final result, I needed to add a next tooltip button but you could render all of the content and have it break down in rows with flex-wrap or grid.
pd: @plouc thanks for this awesome library I really like it :D
Is your feature request related to a problem? Please describe. I'm struggling to get all the legends in a line chart within the width we want. Is there a way to implement a custom layer to arrange the legend as I'd like?
Describe the solution you'd like I'd love to have the ability to make a way to split the legend into multiple rows etc. I'm not looking for auto layout, auto width, or anything. Or if there is a way to get the legend data/colors so I can use it in an HTML component, that would be awesome as well.
Describe alternatives you've considered I've tried using columns, and I dug into the BoxLegendSvg, but doesn't seem to be anything else available for Line charts
Additional context