Back to Gallery
Histogram with Slider
const LIGHT_GREY = "hsl(355, 20%, 90%)"; const PRIMARY_COLOR = "hsl(355, 92%, 67%)"; const Container = styled.div``; const Card = styled.div` background-color: #2b2a31; padding: 40px 36px 30px; border-radius: 5px; // when rendered in the gallery preview a & { padding: 24px 20px 10px; } `; const yearToSeason = year => `${year}-${(year + 1 + "").slice(2, 4)}`; const YEARS = Object.keys(basketballData).map(year => parseInt(year, 10)); const FIRST_YEAR = YEARS[0]; const LAST_YEAR = YEARS[YEARS.length - 1]; const TOTAL_YEARS = LAST_YEAR - FIRST_YEAR; const getTooltipText = ({ datum }) => { const { binnedData, x0, x1 } = datum; const playerCount = binnedData.length; if (!playerCount) { return null; } const playerNames = binnedData .slice(0, 2) .map(({ player }) => { const [firstName, lastName] = player.split(" "); return lastName ? `${firstName.slice(0, 1)}. ${lastName}` : firstName; }) .join(", "); const playerNamesList = `\n (${playerNames}${ playerCount > 2 ? `, and ${playerCount - 2} more players` : "" })`; return `${playerCount} player${ playerCount === 1 ? "" : "s" } averaged between ${x0}-${x1} 3PT attempts ${playerNamesList}`; }; const sharedAxisStyles = { axis: { stroke: "transparent" }, tickLabels: { fill: LIGHT_GREY, fontSize: 14 }, axisLabel: { fill: LIGHT_GREY, padding: 36, fontSize: 15, fontStyle: "italic" } }; const GradientSvg = styled.svg` position: fixed; opacity: 0; `; const App = () => { const [year, setYear] = React.useState(FIRST_YEAR); return ( <Container> <GradientSvg> <defs> <linearGradient id="gradient1" x1="0%" y1="0%" x2="50%" y2="100%"> <stop offset="0%" stopColor="#FFE29F" /> <stop offset="40%" stopColor="#FFA99F" /> <stop offset="100%" stopColor={PRIMARY_COLOR} /> </linearGradient> </defs> </GradientSvg> <Card> <VictoryChart containerComponent={ <VictoryVoronoiContainer labels={getTooltipText} voronoiDimension="x" labelComponent={ <VictoryTooltip constrainToVisibleArea style={{ fill: LIGHT_GREY, fontSize: 11 }} flyoutStyle={{ fill: "#24232a", stroke: PRIMARY_COLOR, strokeWidth: 0.5 }} /> } /> } height={280} > <VictoryLabel text={`3pt Attempts Per Game Averages (${yearToSeason(year)})`} x={225} y={18} textAnchor="middle" style={{ fill: LIGHT_GREY, fontSize: 16 }} /> <VictoryAxis style={{ ...sharedAxisStyles, grid: { fill: LIGHT_GREY, stroke: LIGHT_GREY, pointerEvents: "painted", strokeWidth: 0.5 } }} label="# of players" dependentAxis /> <VictoryAxis style={{ ...sharedAxisStyles, axisLabel: { ...sharedAxisStyles.axisLabel, padding: 35 } }} label="3pt attempts per game" /> <VictoryHistogram cornerRadius={2} domain={{ y: [0, 125] }} animate={{ duration: 300 }} data={basketballData[year]} bins={_.range(0, 16, 2)} style={{ data: { stroke: "transparent", fill: "url(#gradient1)", strokeWidth: 1, }, labels: { fill: "red" } }} x="3pa" /> </VictoryChart> <YearSlider year={year} setYear={setYear} /> </Card> </Container> ); }; const SliderContainer = styled.div` padding: 64px 25px 10px; // when rendered in the gallery preview a & { padding: 24px 36px 0px; } `; const getYear = percent => Math.round(FIRST_YEAR + TOTAL_YEARS * (percent / 100)); const SEASONS = YEARS.map(year => yearToSeason(year)); const YearSlider = ({ year, setYear }) => { const [value, setValue] = React.useState(0); return ( <SliderContainer> <Slider onChange={newValue => { setValue(newValue); const calculatedYear = getYear(newValue); if (year !== calculatedYear) { setYear(calculatedYear); } }} color={PRIMARY_COLOR} value={value} maxValue={100} tooltipValues={SEASONS} /> </SliderContainer> ); }; render(<App/>);