UNPKG

rn-wordcloud

Version:

A React Native library for creating beautiful and neat word (tag) cloud from any text with custom weight and color.

489 lines (443 loc) 13 kB
import React from "react"; import { View, Text, Touchable, TouchableOpacity } from "react-native"; import PropTypes from "prop-types"; import Word from "./word"; import Space from "./space"; import Util, { spaceTypes } from "./util"; class WordCloud extends React.Component { constructor(props) { super(props); this.state = { words: [], }; this.spaceDataObject = null; this.spaceIdArray = null; this.distanceCounter = 1; this.target = { width: props.options.width, height: props.options.height, xOffset: props.options.width / 2, yOffset: props.options.height / 2, }; this._placeFirstWord = this._placeFirstWord.bind(this); this._placeOtherWord = this._placeOtherWord.bind(this); } componentDidMount() { this._init(this.props.options, this.props.onWordPress); } _init(options, onWordPress = null) { this.spaceDataObject = {}; this.spaceIdArray = []; const { words: initialWords, fontOffset, minFont, maxFont, fontFamily, } = options; initialWords.sort(function (a, b) { if (a.value < b.value) { return 1; } else if (a.value > b.value) { return -1; } else { return 0; } }); const _maxValue = initialWords[0].value; const _minValue = initialWords[initialWords.length - 1].value; const fontFactor = (maxFont - minFont) / (_maxValue - _minValue); const wordsArray = initialWords.map((wordConfig, index) => { const word = new Word({ ...wordConfig, minValue: _minValue, maxValue: _maxValue, fontFactor, fontOffset: fontOffset + minFont, fontFamily, index, _placeFirstWord: this._placeFirstWord, _placeOtherWord: this._placeOtherWord, onWordPress, }); return word; }); this.setState({ words: wordsArray, }); } _draw() { const { words } = this.state; if (words.length === 0) return null; return words.map((word) => { return word.view; }); } _updateSpaceIdArray(distanceS, distance) { if (this.spaceIdArray.length !== 0) { for (let index = 0; index < this.spaceIdArray.length; index++) { if (distance < parseFloat(this.spaceIdArray[index].split("_")[0])) { this.spaceIdArray.splice(index, 0, distanceS); return; } } this.spaceIdArray.push(distanceS); } else { this.spaceIdArray.push(distanceS); } } _pushSpaceData(type, w, h, x, y) { // Calculating Distance between (x,y): Key point of Space and Center of Container (this.target.xOffset, this.target.yOffset) const distance = Math.sqrt( (this.target.xOffset - x) * (this.target.xOffset - x) + (this.target.yOffset - y) * (this.target.yOffset - y) ); const distanceS = `${distance}_${this.distanceCounter++}`; // Update Space Id Array this._updateSpaceIdArray(distanceS, distance); // Add Space into Space Data Object this.spaceDataObject[distanceS] = new Space(type, w, h, x, y); } _updateTextPosition(word, top, left, transform = false) { // Update the styles of the word view const buttonStyle = { position: "absolute", left, top, transform: [{ rotate: transform ? "270deg" : "0deg" }], }; const textStyle = { fontSize: word.font, lineHeight: word.font, }; if (word.color && word.color !== null && word.color !== "") { textStyle.color = word.color; } else { textStyle.color = Util.getRandomColor(); } if (word.fontFamily && word.fontFamily !== "") { textStyle.fontFamily = word.fontFamily; } this.setState((prevState) => ({ words: prevState.words.map((prevWord) => prevWord === word ? { ...prevWord, view: (prevWord.view && ( <TouchableOpacity key={word.text} style={buttonStyle} onPress={() => word._onWordPress && word._onWordPress({ text: word.text, value: word.value }) } > <Text style={textStyle}>{word.text}</Text> </TouchableOpacity> )) || null, } : prevWord ), })); } _placeFirstWord(word) { const w = word.width; const h = word.height; const xoff = this.target.xOffset - w / 2; const yoff = this.target.yOffset - h / 2; const tw = this.target.width; const th = this.target.height; // Update the styles of the word view this._updateTextPosition(word, yoff, xoff); // Call the pushSpaceData function with the appropriate parameters this._pushSpaceData( spaceTypes.LB, tw - xoff - w, h, xoff + w, yoff + h / 2 ); //M1 this._pushSpaceData( spaceTypes.LT, w, th - yoff - h, xoff + w / 2, yoff + h ); //M2 this._pushSpaceData(spaceTypes.RT, xoff, h, xoff, yoff + h / 2); //M3 this._pushSpaceData(spaceTypes.RB, w, yoff, xoff + w / 2, yoff); //M4 this._pushSpaceData(spaceTypes.LT, w / 2, h / 2, xoff + w, yoff + h / 2); //C1 this._pushSpaceData(spaceTypes.RT, w / 2, h / 2, xoff + w / 2, yoff + h); //C2 this._pushSpaceData(spaceTypes.RB, w / 2, h / 2, xoff, yoff + h / 2); //C3 this._pushSpaceData(spaceTypes.LB, w / 2, h / 2, xoff + w / 2, yoff); //C4 this._pushSpaceData( spaceTypes.LT, tw - xoff - w - w / 2, th - yoff - h / 2, xoff + w + w / 2, yoff + h / 2 ); //S1 this._pushSpaceData( spaceTypes.RT, xoff + w / 2, th - yoff - h - h / 2, xoff + w / 2, yoff + h + h / 2 ); //S2 this._pushSpaceData( spaceTypes.RB, xoff - w / 2, yoff + h / 2, xoff - w / 2, yoff + h / 2 ); //S3 this._pushSpaceData( spaceTypes.LB, xoff + w / 2, yoff - h / 2, xoff + w / 2, yoff - h / 2 ); //S4 } _placeOtherWord(word) { for (let index = 0; index < this.spaceIdArray.length; index++) { const spaceId = this.spaceIdArray[index]; const obj = this.spaceDataObject[spaceId]; let alignmentInd = 0; let alignmentIndCount = 0; if (word.width <= obj.width && word.height <= obj.height) { alignmentInd = spaceTypes.HR; alignmentIndCount++; } if (this.props.options.verticalEnabled) { if (word.height <= obj.width && word.width <= obj.height) { alignmentInd = spaceTypes.VR; alignmentIndCount++; } } if (alignmentIndCount > 0) { this.spaceDataObject[spaceId] = null; this.spaceIdArray.splice(index, 1); // For Word's Span Position let xMul = 1; let yMul = 1; // For new Child Spaces let xMulS = 1; let yMulS = 1; switch (obj.spaceType) { case spaceTypes.LB: xMul = 0; yMul = -1; xMulS = 1; yMulS = -1; break; case spaceTypes.LT: xMul = 0; yMul = 0; xMulS = 1; yMulS = 1; break; case spaceTypes.RT: xMul = -1; yMul = 0; xMulS = -1; yMulS = 1; break; case spaceTypes.RB: xMul = -1; yMul = -1; xMulS = -1; yMulS = -1; break; default: break; } if (alignmentIndCount > 1) { // Making Horizontal Word in Larger Number // Random number[0,5] is >0 and <3 --> HR // Random number[0,5] is >3 --> VR if (Math.random() * 5 > 3) alignmentInd = spaceTypes.VR; else alignmentInd = spaceTypes.HR; } const w = word.width; const h = word.height; switch (alignmentInd) { case spaceTypes.HR: // Update the styles of the word view this._updateTextPosition(word, obj.y + yMul * h, obj.x + xMul * w); if (Math.random() * 2 > 1) { /* * _________________________________ * | | * | T | * | | * |_______________________________| * | | | * | WORD | R | * | ******** | | * |_______________|_______________| * */ this._pushSpaceData( obj.spaceType, obj.width - w, h, obj.x + xMulS * w, obj.y ); //R this._pushSpaceData( obj.spaceType, obj.width, obj.height - h, obj.x, obj.y + yMulS * h ); //T } else { /* * _________________________________ * | | | * | T | | * | | | * |_______________| R | * | | | * | WORD | | * | ******** | | * |_______________|_______________| * */ this._pushSpaceData( obj.spaceType, obj.width - w, obj.height, obj.x + xMulS * w, obj.y ); //R this._pushSpaceData( obj.spaceType, w, obj.height - h, obj.x, obj.y + yMulS * h ); //T } break; case spaceTypes.VR: // Update the styles of the word view this._updateTextPosition( word, obj.y + yMul * w + (w - h) / 2, obj.x + xMul * h - (w - h) / 2, true ); if (Math.random() * 2 > 1) { /* * _________________________________ * | | * | T | * | | * |_______________________________| * | D | | * | R | R | * | O | | * |_______W_______|_______________| * */ this._pushSpaceData( obj.spaceType, obj.width - h, w, obj.x + xMulS * h, obj.y ); //R this._pushSpaceData( obj.spaceType, obj.width, obj.height - w, obj.x, obj.y + yMulS * w ); //T } else { /* * _________________________________ * | | | * | T | | * | | | * |_______________| R | * | D | | * | R | | * | O | | * |_______W_______|_______________| * */ this._pushSpaceData( obj.spaceType, obj.width - h, obj.height, obj.x + xMulS * h, obj.y ); //R this._pushSpaceData( obj.spaceType, h, obj.height - w, obj.x, obj.y + yMulS * w ); //T } break; default: break; } return; } } } render() { return ( <View style={{ width: this.props.options.width, height: this.props.options.height, position: "relative", }} > {this._draw()} </View> ); } } WordCloud.propTypes = { options: PropTypes.shape({ words: PropTypes.arrayOf( PropTypes.shape({ text: PropTypes.string.isRequired, value: PropTypes.number.isRequired, color: PropTypes.string, }) ).isRequired, verticalEnabled: PropTypes.bool, minFont: PropTypes.number, maxFont: PropTypes.number, fontOffset: PropTypes.number, width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, fontFamily: PropTypes.string, }).isRequired, onWordPress: PropTypes.func, }; WordCloud.defaultProps = { options: { words: [], verticalEnabled: true, minFont: 10, maxFont: 50, fontOffset: 1, width: 300, height: 200, fontFamily: "", }, onWordPress: null, }; export default WordCloud;