|
import React, { useMemo, useState } from "react"; |
|
import { Bar } from "@visx/shape"; |
|
import { Group } from "@visx/group"; |
|
import { LinearGradient } from "@visx/gradient"; |
|
import { AxisBottom, AxisLeft } from "@visx/axis"; |
|
import { scaleBand, scaleLinear } from "@visx/scale"; |
|
|
|
const verticalMargin = 20; |
|
const leftMargin = 50; |
|
const barPadding = 4; |
|
|
|
interface tripPurpose { |
|
trip_purpose_group: string; |
|
percentage: number; |
|
} |
|
export type BarsProps = { |
|
data: tripPurpose[]; |
|
width: number; |
|
height: number; |
|
events?: boolean; |
|
}; |
|
|
|
const getTripPurposeValue = (d: tripPurpose) => Number(d.percentage); |
|
|
|
export default function SimpleChartTripPurpose({ |
|
data, |
|
width, |
|
height, |
|
events = false, |
|
}: BarsProps) { |
|
const xMax = width - leftMargin; |
|
const yMax = height - verticalMargin - 20; |
|
|
|
const xScale = useMemo( |
|
() => |
|
scaleBand<number>({ |
|
range: [0, xMax], |
|
round: true, |
|
domain: data.map((d: any) => d.trip_purpose_group), |
|
paddingInner: 0.2, |
|
paddingOuter: 0.1, |
|
}), |
|
[xMax] |
|
); |
|
const yScale = useMemo( |
|
() => |
|
scaleLinear<number>({ |
|
range: [yMax, 0], |
|
round: true, |
|
domain: [0, Math.max(...data.map(getTripPurposeValue))], |
|
}), |
|
[yMax] |
|
); |
|
|
|
const [isHover, setIsHover] = useState(Array(data.length).fill(false)); |
|
|
|
return width < 10 ? null : ( |
|
<svg |
|
width={width} |
|
height={height} |
|
style={{ |
|
borderBottomLeftRadius: "14px", |
|
borderBottomRightRadius: "14px", |
|
boxShadow: "0 0 8px rgba(0, 0, 0, 0.2)", |
|
}} |
|
> |
|
<LinearGradient from="#351CAB" to="#621A61" id="teal" /> |
|
<rect width={width} height={height} fill="url(#teal)" /> |
|
<Group top={verticalMargin}> |
|
{data.map((d: any, index: any) => { |
|
const barWidth = xScale.bandwidth(); |
|
const barHeight = yMax - (yScale(d.percentage) ?? 0); |
|
const barX = xScale(d.trip_purpose_group) ?? 0; |
|
const barY = yMax - barHeight; |
|
return ( |
|
<Bar |
|
key={`bar-${d.trip_purpose_group}`} |
|
className={isHover[index] ? "hovered" : ""} |
|
style={{ |
|
cursor: "pointer", |
|
transition: "all 0.5s", |
|
opacity: "1", |
|
}} |
|
x={barX + 35} |
|
y={barY - 10} |
|
rx={5} |
|
ry={5} |
|
width={barWidth - barPadding} |
|
height={barHeight} |
|
fill={isHover[index] ? "#FFFFFF" : "#AAB1FF"} |
|
onClick={() => { |
|
if (events) alert(`clicked: ${JSON.stringify(d)}`); |
|
}} |
|
onMouseMove={() => { |
|
const updatedHover = [...isHover]; |
|
updatedHover[index] = true; |
|
setIsHover(updatedHover); |
|
}} |
|
onMouseLeave={() => { |
|
const updatedHover = [...isHover]; |
|
updatedHover[index] = false; |
|
setIsHover(updatedHover); |
|
}} |
|
/> |
|
); |
|
})} |
|
<AxisBottom |
|
top={height - verticalMargin - 30} |
|
left={leftMargin - 15} |
|
scale={xScale} |
|
tickStroke="white" |
|
stroke="white" |
|
strokeWidth={2} |
|
tickLabelProps={() => ({ |
|
fill: "white", |
|
fontSize: 10, |
|
textAnchor: "middle", |
|
fontWeight: "bold", |
|
})} |
|
/> |
|
<AxisLeft |
|
left={leftMargin - 15} |
|
top={verticalMargin - 30} |
|
scale={yScale} |
|
tickFormat={(value) => value.toString()} |
|
tickStroke="white" |
|
strokeWidth={2} |
|
stroke="white" |
|
tickLabelProps={() => ({ |
|
fill: "white", |
|
fontSize: 11, |
|
textAnchor: "end", |
|
dy: "0.3em", |
|
dx: "-0.25em", |
|
fontWeight: "bold", |
|
})} |
|
/> |
|
</Group> |
|
</svg> |
|
); |
|
} |
|
|