import React, { Component } from 'react';
import PropTypes from "prop-types";
class TagCloud extends Component {
static propsTypes = {
style: PropTypes.object,
fontSize: PropTypes.number,
radius: PropTypes.number,
direction: PropTypes.number,
keep: PropTypes.bool,
mspeed: PropTypes.string,
ispeed: PropTypes.string,
}
componentDidMount() {
this.init();
}
componentWillUnmount() {
clearInterval(this.up);
}
update = () => {
let a, b;
if (!this.active && !this.keep) {
this.mouseX = Math.abs(this.mouseX - this.mouseX0) < 1 ? this.mouseX0 : (this.mouseX + this.mouseX0) / 2;
this.mouseY = Math.abs(this.mouseY - this.mouseY0) < 1 ? this.mouseY0 : (this.mouseY + this.mouseY0) / 2;
}
a = -(Math.min(Math.max(-this.mouseY, -this.size), this.size) / this.radius ) * this.mspeed;
b = (Math.min(Math.max(-this.mouseX, -this.size), this.size) / this.radius ) * this.mspeed;
if (Math.abs(a) <= 0.01 && Math.abs(b) <= 0.01) {
return;
}
this.lasta = a;
this.lastb = b;
var sc = this._getSc(a, b);
for (var j = 0, len = this.items.length; j < len; j++) {
var rx1 = this.items[j].x,
ry1 = this.items[j].y*sc[1] + this.items[j].z*(-sc[0]),
rz1 = this.items[j].y*sc[0] + this.items[j].z*sc[1];
var rx2 = rx1 * sc[3] + rz1 * sc[2],
ry2 = ry1,
rz2 = rz1 * sc[3] - rx1 * sc[2];
var per = this.depth / (this.depth + rz2);
this.items[j].x = rx2;
this.items[j].y = ry2;
this.items[j].z = rz2;
this.items[j].scale = per;
this.items[j].fontSize = Math.ceil(per * 3) + this.fontSize - 6;
this.items[j].alpha = 1.5 * per - 0.5;
this.items[j].element.style.left = this.items[j].x + (this.box.offsetWidth - this.items[j].offsetWidth) / 2 + "px";
this.items[j].element.style.top = this.items[j].y + (this.box.offsetHeight - this.items[j].offsetHeight) / 2 + "px";
this.items[j].element.style.fontSize = this.items[j].fontSize + "px";
this.items[j].element.style.filter = "alpha(opacity=" + 100 * this.items[j].alpha + ")";
this.items[j].element.style.opacity = this.items[j].alpha;
}
}
_getIsSpeed = (ispeed) => {
var speedMap = {
slow: 10,
normal: 25,
fast: 50
};
return speedMap[ispeed] || 25;
}
_getItems = () => {
let items = [];
let element = this.box.children;
let length = element.length;
let item;
for (var i = 0; i < length; i++) {
item = {};
item.angle = {};
item.angle.phi = Math.acos(-1 + (2 * i + 1) / length);
item.angle.theta = Math.sqrt((length + 1) * Math.PI) * item.angle.phi;
item.element = element[i];
item.offsetWidth = item.element.offsetWidth;
item.offsetHeight = item.element.offsetHeight;
item.x = this.radius * Math.cos(item.angle.theta) * Math.sin(item.angle.phi);
item.y = this.radius * Math.sin(item.angle.theta) * Math.sin(item.angle.phi);
item.z = this.radius * Math.cos(item.angle.phi);
item.element.style.left = item.x + (this.box.offsetWidth - item.offsetWidth) / 2 + "px";
item.element.style.top = item.y + (this.box.offsetHeight - item.offsetHeight) / 2 + "px";
items.push(item);
}
return items;
}
_getMsSpeed = (mspeed) => {
var speedMap = {
slow: 1.5,
normal: 3,
fast: 5
};
return speedMap[mspeed] || 3;
}
getConfig = () => {
return {
fontSize: 16,
radius: 60,
mspeed: "normal",
ispeed: "normal",
direction: 135,
keep: true,
...this.props,
element: this.wrap,
}
}
_getSc = (a, b) => {
var l = Math.PI / 180;
return [
Math.sin(a * l),
Math.cos(a * l),
Math.sin(b * l),
Math.cos(b * l)
];
}
init = () => {
const config = this.getConfig();
this.config = config;
this.fontSize = config.fontSize;
this.radius = config.radius;
this.depth = 1.5 * config.radius;
this.size = 1.5 * config.radius;
this.box = this.wrap;
this.mspeed = this._getMsSpeed(config.mspeed);
this.ispeed = this._getIsSpeed(config.ispeed);
this.items = this._getItems();
this.direction = config.direction;
this.keep = config.keep;
this.active = false;
this.lasta = 1;
this.lastb = 1;
this.mouseX0 = this.ispeed * Math.sin(this.direction * Math.PI / 180);
this.mouseY0 = -this.ispeed * Math.cos(this.direction * Math.PI / 180);
this.mouseX = this.mouseX0;
this.mouseY = this.mouseY0;
this.update();
this.box.style.visibility = "visible";
this.box.style.position = "relative";
this.box.style.minHeight = 2 * this.size + "px";
this.box.style.minWidth = 2 * this.size + "px";
for (var j = 0, len = this.items.length; j < len; j++) {
this.items[j].element.style.position = "absolute";
this.items[j].element.style.zIndex = j + 1;
}
this.up = setInterval(()=> {
this.update();
}, 30);
}
handleMouseOver = () => {
this.active = true;
}
handleMouseOut = () => {
this.active = false;
}
handleMouseMove = (ev) => {
let boxPosition = this.box.getBoundingClientRect();
this.mouseX = (ev.clientX - (boxPosition.left + this.box.offsetWidth / 2)) / 5;
this.mouseY = (ev.clientY - (boxPosition.top + this.box.offsetHeight / 2)) / 5;
}
render() {
return (
<div style={this.props.style} ref={ref=>this.wrap=ref}
className="tagcloud" onMouseOver={this.handleMouseOver}
onMouseMove={this.handleMouseMove}
onMouseOut={this.handleMouseOut}>
{this.props.children}
</div>
);
}
}
class App extends Component {
handleClick = (text) => {
alert(text);
}
render() {
return (
<TagCloud fontSize={30} radius={60} mspeed="fast" ispeed="fast" direction={200} keep={true}>
<a onClick={this.handleClick.bind(this, "text1")}>text1</a>
<a onClick={this.handleClick.bind(this, "text2")}>text2</a>
<a onClick={this.handleClick.bind(this, "text3")}>text3</a>
<a onClick={this.handleClick.bind(this, "text4")}>text4</a>
<a onClick={this.handleClick.bind(this, "text5")}>text5</a>
<a onClick={this.handleClick.bind(this, "text6")}>text6</a>
</TagCloud>
);
}
}
ReactDOM.render(
<App />
, mountNode)