안경잡이개발자

728x90
반응형

▶ word_cloud.py


  기본적으로 플라스크(Flask)의 소스코드에서 단어(Word)의 가중치를 실수형까지 포함할 수 있도록 처리합니다. 또한 기본적으로 최대 단어 개수와 최소 단어 길이는 모두 정수형으로 들어올 수 있도록 처리하겠습니다.


def process_from_text(text, max_count, min_length, words, file_name):
# 최대 max_count 개의 단어 및 등장 횟수를 추출합니다.
tags = get_tags(text, int(max_count), int(min_length))
# 단어 가중치를 적용합니다.
for n, c in words.items():
if n in tags:
tags[n] = tags[n] * float(words[n])
# 명사의 출현 빈도 정보를 통해 워드 클라우드 이미지를 생성합니다.
make_cloud_image(tags, file_name)


▶ Detail.js


  최소 단어 길이 및 최대 단어 개수 등을 설정하여 워드 클라우드를 만들 수 있도록 처리합니다.


import React from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import '../index.css';
import { withStyles } from '@material-ui/core/styles';
import Fab from '@material-ui/core/Fab';
import UpdateIcon from '@material-ui/icons/Update';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogTitle from '@material-ui/core/DialogTitle';
import Button from '@material-ui/core/Button';
import DialogContent from '@material-ui/core/DialogContent';
import TextField from '@material-ui/core/TextField';

const databaseURL = "https://react-example-55161.firebaseio.com/";
const apiURL = "http://localhost:5000";

const styles = theme => ({
fab: {
position: 'fixed',
bottom: '20px',
right: '20px'
},
});


class Detail extends React.Component {

constructor(props) {
super(props);
this.state = {
dialog: false,
textContent: '',
words: {},
imageUrl: null,
maxCount: 30,
minLength: 1
}
}

componentDidMount() {
this._getImage();
this._getText();
this._getWords();
}

_getText() {
fetch(`${databaseURL}/texts/${this.props.match.params.textID}.json`).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(text => this.setState({textContent: text['textContent']}));
}

_getWords() {
fetch(`${databaseURL}/words.json`).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(words => this.setState({words: (words == null) ? {} : words}));
}

_getImage() {
fetch(`${apiURL}/validate?textID=${this.props.match.params.textID}`).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(data => {
if(data['result'] == true) {
this.setState({imageUrl: apiURL + "/outputs?textID=" + this.props.match.params.textID})
} else {
this.setState({imageUrl: 'NONE'});
}
});
}

handleDialogToggle = () => this.setState({
dialog: !this.state.dialog
})

handleSubmit = () => {
this.setState({imageUrl: 'READY'});
const wordCloud = {
textID: this.props.match.params.textID,
text: this.state.textContent,
maxCount: this.state.maxCount,
minLength: this.state.minLength,
words: this.state.words
}
this.handleDialogToggle();
if (!wordCloud.textID ||
!wordCloud.text ||
!wordCloud.maxCount ||
!wordCloud.minLength ||
!wordCloud.words) {
return;
}
this._post(wordCloud);
}

_post = (wordCloud) => {
return fetch(`${apiURL}/process`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(wordCloud)
}).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(data => {
this.setState({imageUrl: apiURL + "/outputs?textID=" + this.props.match.params.textID})
});
}

handleValueChange = (e) => {
let nextState = {};
if(e.target.value % 1 === 0) {
if(e.target.value < 1) {
nextState[e.target.name] = 1;
} else {
nextState[e.target.name] = e.target.value;
}
}
this.setState(nextState);
}

render() {
const { classes } = this.props;
return (
<div>
<Card>
<CardContent>
{
(this.state.imageUrl)?
((this.state.imageUrl == 'READY')?
'워드 클라우드 이미지를 불러오고 있습니다.':
((this.state.imageUrl == 'NONE')?
'해당 텍스트에 대한 워드 클라우드를 만들어 주세요.':
<img key={Math.random()} src={this.state.imageUrl + '&random=' + Math.random()} style={{width: '100%'}}/>)):
''
}
</CardContent>
</Card>
<Fab color="primary" className={classes.fab} onClick={this.handleDialogToggle}>
<UpdateIcon />
</Fab>
<Dialog open={this.state.dialog} onClose={this.handleDialogToggle}>
<DialogTitle>워드 클라우드 생성</DialogTitle>
<DialogContent>
<TextField label="최대 단어 개수" type="number" name="maxCount" value={this.state.maxCount} onChange={this.handleValueChange}/><br/>
<TextField label="최소 단어 길이" type="number" name="minLength" value={this.state.minLength} onChange={this.handleValueChange}/><br/>
</DialogContent>
<DialogActions>
<Button variant="contained" color="primary" onClick={this.handleSubmit}>
{(this.state.imageUrl == 'NONE')? '만들기' : '다시 만들기'}
</Button>
<Button variant="outlined" color="primary" onClick={this.handleDialogToggle}>닫기</Button>
</DialogActions>
</Dialog>
</div>
);
}
}

export default withStyles(styles)(Detail);


▶ Words.js


  이후에 가중치(Weight) 값의 검증을 수행합니다.


import React from 'react';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import Fab from '@material-ui/core/Fab';
import AddIcon from '@material-ui/icons/Add';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import TextField from '@material-ui/core/TextField';
import '../index.css';

const styles = theme => ({
fab: {
position: 'fixed',
bottom: '20px',
right: '20px'
},
});

const databaseURL = "https://react-example-55161.firebaseio.com/";

class Words extends React.Component {
constructor() {
super();
this.state = {
words: {},
dialog: false,
word: '',
weight: ''
};
}

_get() {
fetch(`${databaseURL}/words.json`).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(words => this.setState({words: words}));
}

_post(word) {
return fetch(`${databaseURL}/words.json`, {
method: 'POST',
body: JSON.stringify(word)
}).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(data => {
let nextState = this.state.words;
nextState[data.name] = word;
this.setState({words: nextState});
});
}

_delete(id) {
return fetch(`${databaseURL}/words/${id}.json`, {
method: 'DELETE'
}).then(res => {
if(res.status != 200) {
throw new Error(res.statusText);
}
return res.json();
}).then(() => {
let nextState = this.state.words;
delete nextState[id];
this.setState({words: nextState});
});
}

componentDidMount() {
this._get();
}

handleDialogToggle = () => this.setState({
dialog: !this.state.dialog
})

handleValueChange = (e) => {
let nextState = {};
nextState[e.target.name] = e.target.value;
this.setState(nextState);
if(e.target.value < 1) {
this.setState({weight: 1});
}
else if(e.target.value > 9) {
this.setState({weight: 9});
}
}

handleSubmit = () => {
const word = {
word: this.state.word,
weight: this.state.weight
}
this.handleDialogToggle();
if (!word.word && !word.weight) {
return;
}
this._post(word);
}

handleDelete = (id) => {
this._delete(id);
}

render() {
const { classes } = this.props;
return (
<div>
{Object.keys(this.state.words).map(id => {
const word = this.state.words[id];
return (
<div key={id}>
<Card>
<CardContent>
<Typography color="textSecondary" gutterBottom>
가중치: {word.weight}
</Typography>
<Grid container>
<Grid item xs={6}>
<Typography variant="h5" component="h2">
{word.word}
</Typography>
</Grid>
<Grid item xs={6}>
<Button variant="contained" color="primary" onClick={() => this.handleDelete(id)}>삭제</Button>
</Grid>
</Grid>
</CardContent>
</Card>
<br />
</div>
);
})}
<Fab color="primary" className={classes.fab} onClick={this.handleDialogToggle}>
<AddIcon />
</Fab>
<Dialog open={this.state.dialog} onClose={this.handleDialogToggle}>
<DialogTitle>단어 추가</DialogTitle>
<DialogContent>
<TextField label="단어" type="text" name="word" value={this.state.word} onChange={this.handleValueChange}/><br/>
<TextField label="가중치(1부터 9까지)" type="number" name="weight" value={this.state.weight} onChange={this.handleValueChange}/><br/>
</DialogContent>
<DialogActions>
<Button variant="contained" color="primary" onClick={this.handleSubmit}>추가</Button>
<Button variant="outlined" color="primary" onClick={this.handleDialogToggle}>닫기</Button>
</DialogActions>
</Dialog>
</div>
);
}
}

export default withStyles(styles)(Words);


※ 실행 결과 ※




728x90
반응형