bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/whiteboard/shapes/poll/component.jsx
2017-06-03 17:02:00 -07:00

526 lines
17 KiB
JavaScript
Executable File

import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import ShapeHelpers from '../helpers.js';
export default class PollDrawComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
//flag indicating whether we need to continue calculating the sizes or display the shape
prepareToDisplay: true,
//outer (white) rectangle's coordinates and sizes (calculated in componentWillMount)
outerRect: {
x: 0,
y: 0,
width: 0,
height: 0,
},
//inner rectangle's coordinates and sizes
innerRect: {
x: 0,
y: 0,
width: 0,
height: 0,
},
thickness: 0,
backgroundColor: '#ffffff',
//max line sizes
maxLineWidth: 0,
maxLineHeight: 0,
//max widths of the keys (left) and percent (right) strings
maxLeftWidth: 0,
maxRightWidth: 0,
//these parameters are used in calculations before and while displaying the final result
votesTotal: 0,
maxNumVotes: 0,
textArray: [],
maxDigitWidth: 0,
maxDigitHeight: 0,
//start value used for font-size calculations
calcFontSize: 50,
currentLine: 0,
lineToMeasure: [],
fontSizeDirection: 1,
};
}
componentWillMount() {
//in this part we retrieve the props and perform initial calculations for the state
//calculating only the parts which have to be done just once and don't require
//rendering / rerendering the text objects
//x1 and y1 - coordinates of the top left corner of the shape
//initial width and height are the width and height of the shape
//all the points are given as percentages of the slide
let x1 = this.props.shape.points[0];
let y1 = this.props.shape.points[1];
let initialWidth = this.props.shape.points[2];
let initialHeight = this.props.shape.points[3];
//calculating the data for the outer rectangle
//0.001 is needed to accomodate bottom and right borders of the shape
let x = x1 / 100 * this.props.slideWidth;
let y = y1 / 100 * this.props.slideHeight;
let width = (initialWidth - 0.001) / 100 * this.props.slideWidth;
let height = (initialHeight - 0.001) / 100 * this.props.slideHeight;
let votesTotal = 0;
let maxNumVotes = 0;
let textArray = [];
//counting the total number of votes, finding the biggest number of votes
this.props.shape.result.reduce(function (previousValue, currentValue, currentIndex, array) {
votesTotal = previousValue + currentValue.num_votes;
if (maxNumVotes < currentValue.num_votes) {
maxNumVotes = currentValue.num_votes;
}
return votesTotal;
}, 0);
//filling the textArray with data to display
//adding value of the iterator to each line needed to create unique
//keys while rendering at the end
let arrayLength = this.props.shape.result.length;
for (let i = 0; i < arrayLength; ++i) {
let _tempArray = [];
let _result = this.props.shape.result[i];
_tempArray.push(_result.key, _result.num_votes + '');
if (votesTotal === 0) {
_tempArray.push('0%');
_tempArray.push(i);
} else {
percResult = _result.num_votes / votesTotal * 100;
_tempArray.push(Math.round(percResult) + '%');
_tempArray.push(i);
}
textArray.push(_tempArray);
}
//calculating the data for the inner rectangle
let innerWidth = width * 0.95;
let innerHeight = height - width * 0.05;
let innerX = x + width * 0.025;
let innerY = y + width * 0.025;
let thickness = (width - innerWidth) / 10;
//calculating the maximum possible width and height of the each line
//25% of the height goes to the padding
let maxLineWidth = innerWidth / 3;
let maxLineHeight = innerHeight * 0.75 / textArray.length;
let lineToMeasure = textArray[0];
//saving all the initial calculations in the state
this.setState({
outerRect: {
x: x,
y: y,
width: width,
height: height,
},
innerRect: {
x: innerX,
y: innerY,
width: innerWidth,
height: innerHeight,
},
thickness: thickness,
votesTotal: votesTotal,
maxNumVotes: maxNumVotes,
textArray: textArray,
maxLineWidth: maxLineWidth,
maxLineHeight: maxLineHeight,
lineToMeasure: lineToMeasure,
});
}
componentDidMount() {
this.checkSizes();
}
componentDidUpdate() {
if (this.state.prepareToDisplay) {
this.checkSizes();
}
}
checkSizes() {
let maxLineWidth = this.state.maxLineWidth;
let maxLineHeight = this.state.maxLineHeight;
//calculating the font size in this if / else block
if (this.state.fontSizeDirection != 0) {
let key = `${this.props.shape.id}_key_${this.state.currentLine}`;
let votes = `${this.props.shape.id}_votes_${this.state.currentLine}`;
let percent = `${this.props.shape.id}_percent_${this.state.currentLine}`;
let keySizes = ReactDOM.findDOMNode(this[key]).getBBox();
let voteSizes = ReactDOM.findDOMNode(this[votes]).getBBox();
let percSizes = ReactDOM.findDOMNode(this[percent]).getBBox();
//first check if we can still increase the font-size
if (this.state.fontSizeDirection == 1) {
if (keySizes.width < maxLineWidth && keySizes.height < maxLineHeight &&
voteSizes.width < maxLineWidth && voteSizes.height < maxLineHeight &&
percSizes.width < maxLineWidth && percSizes.height < maxLineHeight) {
return this.setState({
calcFontSize: this.state.calcFontSize + 1,
});
//we can't increase font-size anymore, start decreasing
} else {
return this.setState({
fontSizeDirection: -1,
calcFontSize: this.state.calcFontSize - 1,
});
}
} else if (this.state.fontSizeDirection == -1) {
//check if the font-size is still bigger than allowed
if (keySizes.width > maxLineWidth || keySizes.height > maxLineHeight ||
voteSizes.width > maxLineWidth || voteSizes.height > maxLineHeight ||
percSizes.width > maxLineWidth || percSizes.height > maxLineHeight) {
return this.setState({
calcFontSize: this.state.calcFontSize - 1,
});
//font size is fine for the current line, switch to the next line
//or finish with the font-size calculations if this we are at the end of the array
} else {
if (this.state.currentLine < this.state.textArray.length - 1) {
return this.setState({
currentLine: this.state.currentLine + 1,
lineToMeasure: this.state.textArray[this.state.currentLine + 1],
});
} else {
return this.setState({
fontSizeDirection: 0,
currentLine: 0,
lineToMeasure: this.state.textArray[0],
});
}
}
}
}
//next block is executed when we finally found a proper font size
//finding the biggest width and height of the left and right strings,
//max real line height and max width value for 1 digit
let maxLeftWidth = 0;
let maxRightWidth = 0;
maxLineHeight = 0;
for (let i = 0; i < this.state.textArray.length; ++i) {
let key = `${this.props.shape.id}_key_${i}`;
let percent = `${this.props.shape.id}_percent_${i}`;
let keySizes = ReactDOM.findDOMNode(this[key]).getBBox();
let percSizes = ReactDOM.findDOMNode(this[percent]).getBBox();
if (keySizes.width > maxLeftWidth) {
maxLeftWidth = keySizes.width;
}
if (percSizes.width > maxRightWidth) {
maxRightWidth = percSizes.width;
}
if (keySizes.height > maxLineHeight) {
maxLineHeight = keySizes.height;
}
if (percSizes.height > maxLineHeight) {
maxLineHeight = percSizes.height;
}
}
let digitRef = `${this.props.shape.id}_digit`;
let maxDigitWidth = ReactDOM.findDOMNode(this[digitRef]).getBBox().width;
let maxDigitHeight = ReactDOM.findDOMNode(this[digitRef]).getBBox().height;
this.setState({
maxLeftWidth: maxLeftWidth,
maxRightWidth: maxRightWidth,
maxLineHeight: maxLineHeight,
maxDigitWidth: maxDigitWidth,
maxDigitHeight: maxDigitHeight,
prepareToDisplay: false,
});
}
renderPoll() {
//*********************************************************************************************
//******************************************MAGIC NUMBER***************************************
//There is no automatic vertical centering in SVG.
//To center the text element we have to move it down by the half of its height.
//But every text element has its own padding by default. The height we receive
//by calling getBBox() includes padding, but the anchor point doesn't consider it.
//This way the text element is moved down a little bit and we have to move it up a bit.
// 1/6 of the maximum height of the digit seems to work fine.
//Oleksandr Zhurbenko. June 22, 2016
let magicNumber = this.state.maxDigitHeight / 6;
//maximum height and width of the line bar
let maxBarWidth = this.state.innerRect.width * 0.9 -
this.state.maxLeftWidth -
this.state.maxRightWidth;
let barHeight = this.state.innerRect.height * 0.75 / this.state.textArray.length;
//Horizontal padding
let horizontalPadding = this.state.innerRect.width * 0.1 / 4;
//Vertical padding
let verticalPadding = this.state.innerRect.height * 0.25 / (this.state.textArray.length + 1);
//Initial coordinates of the key column
let yLeft = this.state.innerRect.y + verticalPadding + barHeight / 2 - magicNumber;
let xLeft = this.state.innerRect.x + horizontalPadding + 1;
//Initial coordinates of the line bar column
let xBar = this.state.innerRect.x + this.state.maxLeftWidth + horizontalPadding * 2;
let yBar = this.state.innerRect.y + verticalPadding;
//Initial coordinates of the percentage column
let yRight = this.state.innerRect.y + verticalPadding + barHeight / 2 - magicNumber;
let xRight = this.state.innerRect.x +
horizontalPadding * 3 +
this.state.maxLeftWidth +
this.state.maxRightWidth +
maxBarWidth + 1;
let yNumVotes = this.state.innerRect.y + verticalPadding - magicNumber;
let extendedTextArray = [];
for (let i = 0; i < this.state.textArray.length; i++) {
if (this.state.maxNumVotes == 0 || this.props.shape.result[i].num_votes === 0) {
barWidth = 1;
} else {
barWidth = this.props.shape.result[i].num_votes / this.state.maxNumVotes * maxBarWidth;
}
//coordinates and color of the text inside the line bar
//xNumVotesDefault and xNumVotesMovedRight are 2 different x coordinates for the text
//since if the line bar is too small then we place the number to the right of the bar
let xNumVotesDefault = this.state.innerRect.x +
this.state.maxLeftWidth +
horizontalPadding * 2;
let xNumVotesMovedRight = xNumVotesDefault +
barWidth / 2 +
this.state.maxDigitWidth / 2;
let xNumVotes;
let color;
if (barWidth < this.state.maxDigitWidth + 8) {
xNumVotes = xNumVotesMovedRight;
color = '#333333';
} else {
xNumVotes = xNumVotesDefault;
color = 'white';
}
extendedTextArray[i] =
{
key: `${this.props.shape.id}_${this.state.textArray[i][3]}`,
keyColumn: {
keyString: this.state.textArray[i][0],
xLeft: xLeft,
yLeft: yLeft,
},
barColumn: {
votesString: this.state.textArray[i][1],
xBar: xBar,
yBar: yBar,
barWidth: barWidth,
barHeight: barHeight,
yNumVotes: yNumVotes,
xNumVotes: xNumVotes,
color: color,
numVotes: this.props.shape.result[i].num_votes,
},
percentColumn: {
xRight: xRight,
yRight: yRight,
percentString: this.state.textArray[i][2],
},
};
//changing the Y coordinate for all the objects
yBar = yBar + barHeight + verticalPadding;
yLeft = yLeft + barHeight + verticalPadding;
yRight = yRight + barHeight + verticalPadding;
yNumVotes = yNumVotes + barHeight + verticalPadding;
}
return (
<g>
<rect
x={this.state.outerRect.x}
y={this.state.outerRect.y}
width={this.state.outerRect.width}
height={this.state.outerRect.height}
strokeWidth="0"
fill={this.state.backgroundColor}
/>
<rect
x={this.state.innerRect.x}
y={this.state.innerRect.y}
width={this.state.innerRect.width}
height={this.state.innerRect.height}
stroke="#333333"
fill={this.state.backgroundColor}
strokeWidth={this.state.thickness}
/>
<text
x={this.state.innerRect.x}
y={this.state.innerRect.y}
fill="#333333"
fontFamily="Arial"
fontSize={this.state.calcFontSize}
textAnchor="start"
>
{extendedTextArray.map((line) =>
<tspan
x={line.keyColumn.xLeft}
y={line.keyColumn.yLeft}
dy={this.state.maxLineHeight / 2}
key={`${line.key}_key`}
>
{line.keyColumn.keyString}
</tspan>
)}
</text>
{extendedTextArray.map((line) =>
<rect
key={`${line.key}_bar`}
x={line.barColumn.xBar}
y={line.barColumn.yBar}
width={line.barColumn.barWidth}
height={line.barColumn.barHeight}
stroke="#333333"
fill="#333333"
strokeWidth={this.state.thickness - 1}
>
</rect>
)}
<text
x={this.state.innerRect.x}
y={this.state.innerRect.y}
fill="#333333"
fontFamily="Arial"
fontSize={this.state.calcFontSize}
textAnchor="end"
>
{extendedTextArray.map((line) =>
<tspan
x={line.percentColumn.xRight}
y={line.percentColumn.yRight}
dy={this.state.maxLineHeight / 2}
key={`${line.key}_percent`}
>
{line.percentColumn.percentString}
</tspan>
)}
</text>
<text
x={this.state.innerRect.x}
y={this.state.innerRect.y}
fill="#333333"
fontFamily="Arial"
fontSize={this.state.calcFontSize}
>
{extendedTextArray.map((line) =>
<tspan
x={line.barColumn.xNumVotes + line.barColumn.barWidth / 2}
y={line.barColumn.yNumVotes + line.barColumn.barHeight / 2}
dy={this.state.maxLineHeight / 2}
key={`${line.key}_num_votes`}
fill={line.barColumn.color}
>
{line.barColumn.numVotes}
</tspan>
)}
</text>
</g>
);
}
renderLine(line) {
//this func just renders the strings for one line
return (
<g key={`${this.props.shape.id}_line_${line[3]}`}>
<text
fontFamily="Arial"
fontSize={this.state.calcFontSize}
ref={(ref) => { this[`${this.props.shape.id}_key_${line[3]}`] = ref; }}
>
<tspan>
{line[0]}
</tspan>
</text>
<text
fontFamily="Arial"
fontSize={this.state.calcFontSize}
ref={(ref) => { this[`${this.props.shape.id}_votes_${line[3]}`] = ref; }}
>
<tspan>
{line[1]}
</tspan>
</text>
<text
fontFamily="Arial"
fontSize={this.state.calcFontSize}
ref={(ref) => { this[`${this.props.shape.id}_percent_${line[3]}`] = ref; }}
>
<tspan>
{line[2]}
</tspan>
</text>
</g>
);
}
renderTestStrings() {
//check whether we need to render just one line (which means we still calculating the font-size)
//or if we finished with the font-size and we need to render all the strings in order to
//determine the maxHeight, maxWidth and maxDigitWidth
if (this.state.fontSizeDirection != 0) {
return this.renderLine(this.state.lineToMeasure);
} else {
return (
<g>
{this.state.textArray.map((line) =>
this.renderLine(line)
)}
<text
fontFamily="Arial"
fontSize={this.state.calcFontSize}
ref={(ref) => { this[`${this.props.shape.id}_digit`] = ref; }}
>
<tspan>
0
</tspan>
</text>
</g>
);
}
}
render() {
return (
<g>
{this.state.prepareToDisplay ?
this.renderTestStrings()
:
this.renderPoll()
}
</g>
);
}
}