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 ( {extendedTextArray.map((line) => {line.keyColumn.keyString} )} {extendedTextArray.map((line) => )} {extendedTextArray.map((line) => {line.percentColumn.percentString} )} {extendedTextArray.map((line) => {line.barColumn.numVotes} )} ); } renderLine(line) { //this func just renders the strings for one line return ( { this[`${this.props.shape.id}_key_${line[3]}`] = ref; }} > {line[0]} { this[`${this.props.shape.id}_votes_${line[3]}`] = ref; }} > {line[1]} { this[`${this.props.shape.id}_percent_${line[3]}`] = ref; }} > {line[2]} ); } 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 ( {this.state.textArray.map((line) => this.renderLine(line) )} { this[`${this.props.shape.id}_digit`] = ref; }} > 0 ); } } render() { return ( {this.state.prepareToDisplay ? this.renderTestStrings() : this.renderPoll() } ); } }