Skip to content

Commit

Permalink
Make pie charts render nicely on mobile and be responsive in general
Browse files Browse the repository at this point in the history
  • Loading branch information
holly-cummins committed Nov 13, 2024
1 parent c420009 commit 305e41a
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 46 deletions.
130 changes: 94 additions & 36 deletions src/components/charts/contributions-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,56 @@ import { getPalette } from "../util/styles/style"
import { LabelList, Legend, Pie, PieChart, ResponsiveContainer, Text, Tooltip } from "recharts"
import PropTypes from "prop-types"
import styled from "styled-components"
import { useMediaQuery } from "react-responsive"
import { device } from "../util/styles/breakpoints"

const RADIAN = Math.PI / 180

const bigHeight = "520px"
const mediumHeight = "340px"
const smallHeight = "260px"

// We need to set an explicit height for the charts, or the contents don't render at all
const ChartHolder = styled.div`
height: ${bigHeight};
// noinspection CssUnknownProperty
@media ${device.sm} {
height: ${mediumHeight};
}
// noinspection CssUnknownProperty
@media ${device.xs} {
height: ${smallHeight};
}
`

const LegendHolder = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`

const LegendSwatch = styled.div`
height: var(--font-size-10);
width: var(--font-size-10);
border-radius: 3px;
background-color: ${(props) => props.color};
border: 0.5px lightgray solid;
`

const ContributorList = styled.ul`
overflow: scroll;
height: 400px;
background-color: var(--main-background-color); // this very slightly reduces quite how awful it is if the content overflows to the right-hand side
padding-inline-start: 0;
height: calc(${bigHeight} - var(--a-generous-space));
// noinspection CssUnknownProperty
@media ${device.sm} {
height: auto;
}
`

const ContributorInformation = styled.li`
Expand All @@ -29,7 +64,7 @@ const ContributorInformation = styled.li`
column-gap: 0.75rem;
font-size: var(--font-size-10);
padding: 2px;
max-width: 200px;
`

const Contributor = styled.div`
Expand Down Expand Up @@ -59,6 +94,9 @@ const ContributionsChart = (props) => {
const uncolouredContributors = props.contributors
const uncolouredCompanies = props.companies

const isMobile = useMediaQuery({ query: device.xs })
const isSmallScreen = useMediaQuery({ query: device.sm })

if (uncolouredContributors?.length > 0) {
const palette = getPalette(uncolouredContributors.length, props.baseColour)

Expand All @@ -80,40 +118,60 @@ const ContributionsChart = (props) => {
const contributors = ungroupedContributors.sort((a, b) => a.company === b.company ? (b.contributions - a.contributions) : companyComparator(a.company, b.company))

const lotsOfContributors = contributors.length > 8
const shouldRenderLabels = !lotsOfContributors && !isSmallScreen
const shouldRenderExternalLegend = isSmallScreen

// These have to be convincingly smaller than the size of the chartholder, or a ring will go missing
const innerRadius = isMobile ? 40 : 70
const companyRingWidth = isMobile ? 15 : 25

const innerRadius = 70
const companyRingWidth = 25
const width = "100%"

// we set a blank label if there are a small number of contributors, so we get the line, but we define our own
// text so we can make it black. the offset in the label list is hand-tuned to put the text near the end of the line
return (
<ResponsiveContainer width={700} height="100%">
<PieChart title={"Committers"}
desc={`A pie chart showing that ${contributors[0].name} has made the most commits in the past six months.`}>
<Pie data={companies} dataKey="contributions" nameKey="name" innerRadius={innerRadius}
outerRadius={innerRadius + companyRingWidth}
>
</Pie>

<Pie data={contributors} dataKey="contributions" nameKey="name"
innerRadius={innerRadius + companyRingWidth + 5}
label={lotsOfContributors ? false : () => ""}
>

{lotsOfContributors ||
<LabelList position="outside" offset={21} stroke="none"
fill="var(--main-text-color)"
content={renderCustomizedLabel} valueAccessor={(p) => p} />}
}
</Pie>

{lotsOfContributors && <Legend layout="vertical" align="right" verticalAlign="top"
content={renderLegend} />}

<Tooltip formatter={((value, name) => [`${value} commits`, name])} />

</PieChart>
</ResponsiveContainer>)
<div width={width}>
<ChartHolder>

<ResponsiveContainer width={width} height="100%">
<PieChart title={"Committers"}
desc={`A pie chart showing that ${contributors[0].name} has made the most commits in the past six months.`}>
<Pie data={companies} dataKey="contributions" nameKey="name" innerRadius={innerRadius}
outerRadius={innerRadius + companyRingWidth}
>
</Pie>

<Pie data={contributors} dataKey="contributions" nameKey="name"
innerRadius={innerRadius + companyRingWidth + 5}
label={shouldRenderLabels ? () => "" : false}
>

{shouldRenderLabels &&
<LabelList position="outside" offset={21} stroke="none"
fill="var(--main-text-color)"
content={renderCustomizedLabel} valueAccessor={(p) => p} />}
}
</Pie>

{(shouldRenderExternalLegend) || <Legend layout="vertical" align="right" verticalAlign="top"
content={renderLegend} />}

<Tooltip formatter={((value, name) => [`${value} commits`, name])} />

</PieChart>
</ResponsiveContainer>
</ChartHolder>
{shouldRenderExternalLegend &&
renderLegend({
payload: contributors.map(entry => {
return { payload: { ...entry, value: entry.contributions } }
})
})

}
</div>)


}
}

Expand Down Expand Up @@ -150,32 +208,32 @@ const renderLegend = (props) => {
const { payload } = props

return (
<div>
<LegendHolder>
<h5>Commits</h5>
<ContributorList>
{
payload.filter(entry => entry.payload.login) // Filter out companies from the list, by assuming they won't have a login field
.sort((a, b) =>
b.payload.contributions - a.payload.contributions
).map((entry, index) => {
const { payload: { name, value }, color } = entry
const { payload: { name, value, fill } } = entry

return (
<ContributorInformation key={`item-${index}`}>
<Contributor>
<LegendSwatch color={color} />
<LegendSwatch color={fill} />
<a href={entry?.payload.url}>{name}</a>
</Contributor>
<span
style={{
"text-align": "right"
"textAlign": "right"
}}>{value}</span>
</ContributorInformation>
)
})
}
</ContributorList>
</div>
</LegendHolder>
)
}

Expand Down
14 changes: 4 additions & 10 deletions src/templates/extension-detail.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const Metadata = styled.div`

const MainContent = styled.div`
width: 70%;
min-width: 70%;
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -174,11 +175,6 @@ const DocumentationHeading = styled.h2`
padding-bottom: 10px;
`

// I wish this wasn't here, but we need to set an explicit height for the charts, or the contents don't render at all
const ChartHolder = styled.div`
height: 480px; // For now, an arbitrary height, but we should tune
`

const Logo = ({ extension, isMobile }) => {
// Size here doesn't matter that much because display size is set in css, but getting smaller makes page load quicker
return isMobile ?
Expand Down Expand Up @@ -368,11 +364,9 @@ const ExtensionDetailTemplate = ({
past {numMonthsWithUnit}{" "}
(excluding merge commits).</p>)}

<ChartHolder>
<ContributionsChart contributors={metadata.sourceControl.contributors}
companies={metadata.sourceControl.companies} baseColour={"#4695EB"}
companyColour={"#555"} />
</ChartHolder>
<ContributionsChart contributors={metadata.sourceControl.contributors}
companies={metadata.sourceControl.companies} baseColour={"#4695EB"}
companyColour={"#555"} />

{metadata?.sourceControl?.companies && (
<p><i>Company affiliations are derived from GitHub user profiles. Want your company's name to be
Expand Down

0 comments on commit 305e41a

Please sign in to comment.