Skip to content

Commit

Permalink
feat(Poll): Add main site poll component (#159)
Browse files Browse the repository at this point in the history
* Add files via upload

* Add files via upload

* Delete index.html

* Delete style.css

* Delete Graph.tsx

* Delete index.tsx

* Create poll component (#84)

* Add files via upload

* Delete Choice.tsx

* Delete Graph.tsx

* Delete Question.tsx

* Delete index.mdx

* Delete index.tsx

* Delete index.html

* Delete style.css

* Add files via upload

* Delete Choice.tsx

* Delete Graph.tsx

* Delete Question.tsx

* Delete index.mdx

* Delete index.tsx

* Delete Graph.js

* Delete Graph.tsx

* Delete index.js

* Delete index.tsx

* Add files via upload

* Update index.mdx

* Create poll component #84

* Update index.mdx

* fix d3 dependency
  • Loading branch information
sarahc7 authored and zeehang committed Feb 13, 2019
1 parent 9b0de0f commit 227848d
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@fortawesome/fontawesome-svg-core": "^1.2.8",
"@fortawesome/free-solid-svg-icons": "^5.5.0",
"@fortawesome/react-fontawesome": "^0.1.3",
"d3": "^5.9.1",
"emotion": "^9.2.12",
"emotion-server": "^9.2.12",
"gatsby-plugin-emotion": "^2.0.6",
Expand Down
4 changes: 2 additions & 2 deletions src/components/Article/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ Note that `Article` expects an array of `Content`, which
},
{
type: 'image',
value: `{
value: {
"caption": "Freshman attacker Ashworth Molthen has registered 21 goals on the season, finding the back of the net in 13 of the 16 games so far this year for UCLA men’s water polo. UCLA is one of two teams remaining in the MPSF conference that remains undefeated.", "credit": "Amy Dixon/Photo Editor", "type": "image", "url": "https://dailybruin.com/images/2018/10/web.sp_.mwp_.nbk_.ADX_-640x461.jpg",
"alt": "Ashworth Molthen playing water polo."
}`,
},
},
]}
/>
Expand Down
64 changes: 64 additions & 0 deletions src/components/Poll/Choice.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import * as React from 'react'
import { css } from 'react-emotion'
import * as MainSiteStyles from '../../globals/mainsiteGlobalStyles'

interface ChoiceProps {
choice: string
votes: number
}

class Choice extends React.Component<ChoiceProps> {
public state = {
votes: this.props.votes,
}

constructor(props) {
super(props);
}

public handleClick = () => {
this.setState({ votes: this.state.votes+1 });
this.props.handler();
}

public render() {
return (
<div
className={css`
padding: 5px 0px 5px;
&:first-child {
padding-top: 0px;
}
&:last-child {
border-bottom: none;
padding-bottom: 0px;
}
`}
>
<p
className={css`
font-family: ${MainSiteStyles.storyListFont}, serif;
font-size: 0.775rem;
font-weight: 700;
line-height: 1rem;
margin: 0px 0px 3px;
`}
>
<a onClick = {this.handleClick}>
<FontAwesomeIcon
icon={faCheckCircle}
className={css`
margin-right: 10px;
`}
/>
</a>
{this.props.choice}
</p>
</div>
)
}
}

export default Choice
145 changes: 145 additions & 0 deletions src/components/Poll/Graph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React from "react"
import { css } from 'react-emotion'
import * as MainSiteStyles from '../../globals/mainsiteGlobalStyles'
import * as d3 from "d3"
import Choice from './Choice'

class Graph extends React.Component {
constructor(props) {
super(props);
this.state = { h: 200 };
this.wrap = this.wrap.bind(this);
}

public wrap(text, width) {
let maxLines = 1;
text.each(function() {
const t = d3.select(this);
const words = t.text().split(/\s+/).reverse();
let word;
let line = [];
let lineNumber = 1;
const y = t.attr("y");
const dy = 10;
let tspan = t.text(null).append("tspan").attr("x", 0).attr("y", y);
word = words.pop();
while (word) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = t.append("tspan").attr("x", 0).attr("dy", dy + "px").text(word);
lineNumber++;
}
if(lineNumber > maxLines) {
maxLines = lineNumber;
}
word = words.pop();
}
});
const newHeightValue = (maxLines-2) * 35;
if(this.state.h !== 200 + newHeightValue) {
this.setState({h: this.state.h+newHeightValue});
}
}

public componentDidMount() {
this.draw();
}

public componentDidUpdate() {
this.draw();
}

public draw = () => {
const data = [...this.props.data];
data.sort((a, b) => a.votes - b.votes);

const svg = d3.select(this.svg);
const margin = { top: 15, right: 120, bottom: 0, left: 20 };
const width = 225;
const height = this.state.h;

svg.selectAll("g").remove();

const g = svg
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");

const x = d3
.scaleLinear()
.domain([0, d3.max(data, d => d.votes)])
.rangeRound([0, width]);

const y = d3
.scaleBand()
.rangeRound([height, 0])
.padding(5)
.domain(data.map(d => d.choice));

g
.append("g")
.call(d3.axisRight(y).tickSize(0))
.attr("font-family", "PT Serif")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("transform", "translate(0, -15)")
.selectAll("path")
.attr("stroke", "transparent")

g
.append("g")
.call(d3.axisTop(x).tickSize(0))
.attr("font-family", "PT Serif")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("transform", "translate(0, "+((this.state.h-200)/10)+")")
.selectAll("path")

g
.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("transform", "translate(0, -25)")
.attr("class", "bar")
.attr("y", d => y(d.choice))
.attr("height", "3px")
.style("fill", "#0080C6")
.transition()
.delay(250)
.attr("width", d => x(d.votes))

g
.selectAll(".tick text")
.call(this.wrap, width)
}


public render() {
return (
<div>
<p
className={css`
font-family: ${MainSiteStyles.storyListFont}, serif;
font-size: 10px;
margin: -20px 0px 10px 155px;
`}
>
*{this.props.legend}
</p>
<svg
className={css`
display: inline-block;
height: ${this.state.h}px;
`}
ref={e => (this.svg = e)}
/>
</div>
)
}
}

export default Graph;
30 changes: 30 additions & 0 deletions src/components/Poll/Question.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from 'react'
import { css } from 'react-emotion'
import * as MainSiteStyles from '../../globals/mainsiteGlobalStyles'

interface QuestionProps {
text: string
}

export default function Question(props: QuestionProps) {
return (
<div
className={css`
padding: ${MainSiteStyles.cardInnerPadding};
padding-bottom: 0px;
`}
>
<h3
className={css`
font-family: ${MainSiteStyles.storyListFont}, serif;
font-size: 0.875rem;
font-weight: 700;
line-height: 1.125rem;
margin: 0px 0px 3px;
`}
>
{props.text}
</h3>
</div>
)
}
39 changes: 39 additions & 0 deletions src/components/Poll/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: Poll
route: /poll
---

import { Playground, PropsTable } from 'docz'
import Poll from '.'

# Poll

A poll with a graph of the results.

<PropsTable of={Poll} />

<Playground>
<Poll
poll={[
{
choice: 'De Neve',
votes: 10,
},
{
choice: 'Covel',
votes: 90,
},
{
choice: 'Feast',
votes: 30,
},
{
choice: 'Bruin Plate',
votes: 40,
}
]}
question={"There's a lot going on at UCLA. Tuition hikes, protests, and more fun things. That's why we're asking you this question. What's your favorite dining hall?"}
hasVoted={false}
legend={"Number of Students"}
/>
</Playground>
95 changes: 95 additions & 0 deletions src/components/Poll/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import * as React from 'react'
import { css } from 'react-emotion'
import * as MainSiteStyles from '../../globals/mainsiteGlobalStyles'
import Choice from './Choice'
import Question from './Question'
import Graph from './Graph'

/**
* Poll Properties
*/

interface PollProps {
/** A list of the choices. */
poll: ChoiceProps[]
/** Poll question. */
question: string
/** Graph legend. */
legend: string
}

class Poll extends React.Component<PollProps> {
public state = {
hasVoted: false,
}

constructor(props) {
super(props);
this.handler = this.handler.bind(this);
}

public handler() {
this.setState({
hasVoted: true
});
}

public render() {
const renderedChoices = this.props.poll.map((poll, index) => (
<Choice
choice={poll.choice}
votes={poll.votes}
key={index}
handler={this.handler}
/>
))

return (
<div
className={css`
background-color: ${MainSiteStyles.white};
box-shadow: ${MainSiteStyles.cardShadow};
justify-content: center;
margin: 16px auto;
max-width: 292px;
`}
>
<div
className={css`
background-color: ${MainSiteStyles.black};
padding: 2px 0px 4px 10px;
`}
>
<h2
className={css`
color: ${MainSiteStyles.white};
font-family: ${MainSiteStyles.topBarFont}, sans-serif;
font-size: 1.125rem;
font-weight: 900;
line-height: 1.4375rem;
margin: 0px;
overflow-wrap: break-word;
`}
>
{'POLL'}
</h2>
</div>
<Question
text={this.props.question}
/>
<div
className={css`
padding: ${MainSiteStyles.cardInnerPadding};
`}
>
{!this.state.hasVoted && renderedChoices}
{this.state.hasVoted &&
<Graph data={this.props.poll} legend={this.props.legend} />
}
</div>
</div>
)
}
}

export default Poll

0 comments on commit 227848d

Please sign in to comment.