@isoterik/react-word-cloud
Version:
A lightweight, interactable and customizable React word cloud library powered by d3-cloud, featuring built-in animations, tooltips, and flexible word rendering.
1,447 lines • 72.3 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("react/jsx-runtime"), require("react"), require("react-dom")) : typeof define === "function" && define.amd ? define(["exports", "react/jsx-runtime", "react", "react-dom"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["react-word-cloud"] = {}, global.ReactJsxRuntime, global.React, global.ReactDOM));
})(this, function(exports2, jsxRuntime, React, ReactDOM) {
"use strict";
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
if (e) {
for (const k in e) {
if (k !== "default") {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
const ReactDOM__namespace = /* @__PURE__ */ _interopNamespaceDefault(ReactDOM);
function getDefaultExportFromCjs(x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
}
function getAugmentedNamespace(n) {
if (n.__esModule) return n;
var f = n.default;
if (typeof f == "function") {
var a = function a2() {
if (this instanceof a2) {
return Reflect.construct(f, arguments, this.constructor);
}
return f.apply(this, arguments);
};
a.prototype = f.prototype;
} else a = {};
Object.defineProperty(a, "__esModule", { value: true });
Object.keys(n).forEach(function(k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function() {
return n[k];
}
});
});
return a;
}
var noop = { value: function() {
} };
function dispatch() {
for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
if (!(t = arguments[i] + "") || t in _ || /[\s.]/.test(t)) throw new Error("illegal type: " + t);
_[t] = [];
}
return new Dispatch(_);
}
function Dispatch(_) {
this._ = _;
}
function parseTypenames(typenames, types) {
return typenames.trim().split(/^|\s+/).map(function(t) {
var name = "", i = t.indexOf(".");
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
return { type: t, name };
});
}
Dispatch.prototype = dispatch.prototype = {
constructor: Dispatch,
on: function(typename, callback) {
var _ = this._, T = parseTypenames(typename + "", _), t, i = -1, n = T.length;
if (arguments.length < 2) {
while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
return;
}
if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
while (++i < n) {
if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
}
return this;
},
copy: function() {
var copy = {}, _ = this._;
for (var t in _) copy[t] = _[t].slice();
return new Dispatch(copy);
},
call: function(type, that) {
if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
},
apply: function(type, that, args) {
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
}
};
function get(type, name) {
for (var i = 0, n = type.length, c; i < n; ++i) {
if ((c = type[i]).name === name) {
return c.value;
}
}
}
function set(type, name, callback) {
for (var i = 0, n = type.length; i < n; ++i) {
if (type[i].name === name) {
type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
break;
}
}
if (callback != null) type.push({ name, value: callback });
return type;
}
const src = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
dispatch
}, Symbol.toStringTag, { value: "Module" }));
const require$$0 = /* @__PURE__ */ getAugmentedNamespace(src);
var d3Cloud;
var hasRequiredD3Cloud;
function requireD3Cloud() {
if (hasRequiredD3Cloud) return d3Cloud;
hasRequiredD3Cloud = 1;
const dispatch2 = require$$0.dispatch;
const RADIANS = Math.PI / 180;
const SPIRALS = {
archimedean: archimedeanSpiral,
rectangular: rectangularSpiral
};
const cw = 1 << 11 >> 5;
const ch = 1 << 11;
d3Cloud = function() {
var size = [256, 256], text = cloudText, font = cloudFont, fontSize = cloudFontSize, fontStyle = cloudFontNormal, fontWeight = cloudFontNormal, rotate = cloudRotate, padding = cloudPadding, spiral = archimedeanSpiral, words = [], timeInterval = Infinity, event = dispatch2("word", "end"), timer = null, random2 = Math.random, cloud2 = {}, canvas = cloudCanvas;
cloud2.canvas = function(_) {
return arguments.length ? (canvas = functor(_), cloud2) : canvas;
};
cloud2.start = function() {
var contextAndRatio = getContext(canvas()), board = zeroArray((size[0] >> 5) * size[1]), bounds = null, n = words.length, i = -1, tags = [], data = words.map(function(d, i2) {
d.text = text.call(this, d, i2);
d.font = font.call(this, d, i2);
d.style = fontStyle.call(this, d, i2);
d.weight = fontWeight.call(this, d, i2);
d.rotate = rotate.call(this, d, i2);
d.size = ~~fontSize.call(this, d, i2);
d.padding = padding.call(this, d, i2);
return d;
}).sort(function(a, b) {
return b.size - a.size;
});
if (timer) clearInterval(timer);
timer = setInterval(step, 0);
step();
return cloud2;
function step() {
var start = Date.now();
while (Date.now() - start < timeInterval && ++i < n && timer) {
var d = data[i];
d.x = size[0] * (random2() + 0.5) >> 1;
d.y = size[1] * (random2() + 0.5) >> 1;
cloudSprite(contextAndRatio, d, data, i);
if (d.hasText && place(board, d, bounds)) {
tags.push(d);
event.call("word", cloud2, d);
if (bounds) cloudBounds(bounds, d);
else bounds = [{ x: d.x + d.x0, y: d.y + d.y0 }, { x: d.x + d.x1, y: d.y + d.y1 }];
d.x -= size[0] >> 1;
d.y -= size[1] >> 1;
}
}
if (i >= n) {
cloud2.stop();
event.call("end", cloud2, tags, bounds);
}
}
};
cloud2.stop = function() {
if (timer) {
clearInterval(timer);
timer = null;
}
for (const d of words) {
delete d.sprite;
}
return cloud2;
};
function getContext(canvas2) {
const context = canvas2.getContext("2d", { willReadFrequently: true });
canvas2.width = canvas2.height = 1;
const ratio = Math.sqrt(context.getImageData(0, 0, 1, 1).data.length >> 2);
canvas2.width = (cw << 5) / ratio;
canvas2.height = ch / ratio;
context.fillStyle = context.strokeStyle = "red";
return { context, ratio };
}
function place(board, tag, bounds) {
[{ x: 0, y: 0 }, { x: size[0], y: size[1] }];
var startX = tag.x, startY = tag.y, maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]), s = spiral(size), dt = random2() < 0.5 ? 1 : -1, t = -dt, dxdy, dx, dy;
while (dxdy = s(t += dt)) {
dx = ~~dxdy[0];
dy = ~~dxdy[1];
if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break;
tag.x = startX + dx;
tag.y = startY + dy;
if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 || tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
if (!bounds || collideRects(tag, bounds)) {
if (!cloudCollide(tag, board, size[0])) {
var sprite = tag.sprite, w = tag.width >> 5, sw = size[0] >> 5, lx = tag.x - (w << 4), sx = lx & 127, msx = 32 - sx, h = tag.y1 - tag.y0, x = (tag.y + tag.y0) * sw + (lx >> 5), last;
for (var j = 0; j < h; j++) {
last = 0;
for (var i = 0; i <= w; i++) {
board[x + i] |= last << msx | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
}
x += sw;
}
return true;
}
}
}
return false;
}
cloud2.timeInterval = function(_) {
return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud2) : timeInterval;
};
cloud2.words = function(_) {
return arguments.length ? (words = _, cloud2) : words;
};
cloud2.size = function(_) {
return arguments.length ? (size = [+_[0], +_[1]], cloud2) : size;
};
cloud2.font = function(_) {
return arguments.length ? (font = functor(_), cloud2) : font;
};
cloud2.fontStyle = function(_) {
return arguments.length ? (fontStyle = functor(_), cloud2) : fontStyle;
};
cloud2.fontWeight = function(_) {
return arguments.length ? (fontWeight = functor(_), cloud2) : fontWeight;
};
cloud2.rotate = function(_) {
return arguments.length ? (rotate = functor(_), cloud2) : rotate;
};
cloud2.text = function(_) {
return arguments.length ? (text = functor(_), cloud2) : text;
};
cloud2.spiral = function(_) {
return arguments.length ? (spiral = SPIRALS[_] || _, cloud2) : spiral;
};
cloud2.fontSize = function(_) {
return arguments.length ? (fontSize = functor(_), cloud2) : fontSize;
};
cloud2.padding = function(_) {
return arguments.length ? (padding = functor(_), cloud2) : padding;
};
cloud2.random = function(_) {
return arguments.length ? (random2 = _, cloud2) : random2;
};
cloud2.on = function() {
var value = event.on.apply(event, arguments);
return value === event ? cloud2 : value;
};
return cloud2;
};
function cloudText(d) {
return d.text;
}
function cloudFont() {
return "serif";
}
function cloudFontNormal() {
return "normal";
}
function cloudFontSize(d) {
return Math.sqrt(d.value);
}
function cloudRotate() {
return (~~(random() * 6) - 3) * 30;
}
function cloudPadding() {
return 1;
}
function cloudSprite(contextAndRatio, d, data, di) {
if (d.sprite) return;
var c = contextAndRatio.context, ratio = contextAndRatio.ratio;
c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
var x = 0, y = 0, maxh = 0, n = data.length;
--di;
while (++di < n) {
d = data[di];
c.save();
c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
const metrics = c.measureText(d.text);
const anchor = -Math.floor(metrics.width / 2);
let w2 = (metrics.width + 1) * ratio;
let h2 = d.size << 1;
if (d.rotate) {
var sr = Math.sin(d.rotate * RADIANS), cr = Math.cos(d.rotate * RADIANS), wcr = w2 * cr, wsr = w2 * sr, hcr = h2 * cr, hsr = h2 * sr;
w2 = Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 31 >> 5 << 5;
h2 = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
} else {
w2 = w2 + 31 >> 5 << 5;
}
if (h2 > maxh) maxh = h2;
if (x + w2 >= cw << 5) {
x = 0;
y += maxh;
maxh = 0;
}
if (y + h2 >= ch) break;
c.translate((x + (w2 >> 1)) / ratio, (y + (h2 >> 1)) / ratio);
if (d.rotate) c.rotate(d.rotate * RADIANS);
c.fillText(d.text, anchor, 0);
if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, anchor, 0);
c.restore();
d.width = w2;
d.height = h2;
d.xoff = x;
d.yoff = y;
d.x1 = w2 >> 1;
d.y1 = h2 >> 1;
d.x0 = -d.x1;
d.y0 = -d.y1;
d.hasText = true;
x += w2;
}
var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data, sprite = [];
while (--di >= 0) {
d = data[di];
if (!d.hasText) continue;
var w = d.width, w32 = w >> 5, h = d.y1 - d.y0;
for (var i = 0; i < h * w32; i++) sprite[i] = 0;
x = d.xoff;
if (x == null) return;
y = d.yoff;
var seen = 0, seenRow = -1;
for (var j = 0; j < h; j++) {
for (var i = 0; i < w; i++) {
var k = w32 * j + (i >> 5), m = pixels[(y + j) * (cw << 5) + (x + i) << 2] ? 1 << 31 - i % 32 : 0;
sprite[k] |= m;
seen |= m;
}
if (seen) seenRow = j;
else {
d.y0++;
h--;
j--;
y++;
}
}
d.y1 = d.y0 + seenRow;
d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
}
}
function cloudCollide(tag, board, sw) {
sw >>= 5;
var sprite = tag.sprite, w = tag.width >> 5, lx = tag.x - (w << 4), sx = lx & 127, msx = 32 - sx, h = tag.y1 - tag.y0, x = (tag.y + tag.y0) * sw + (lx >> 5), last;
for (var j = 0; j < h; j++) {
last = 0;
for (var i = 0; i <= w; i++) {
if ((last << msx | (i < w ? (last = sprite[j * w + i]) >>> sx : 0)) & board[x + i]) return true;
}
x += sw;
}
return false;
}
function cloudBounds(bounds, d) {
var b0 = bounds[0], b1 = bounds[1];
if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
}
function collideRects(a, b) {
return a.x + a.x1 > b[0].x && a.x + a.x0 < b[1].x && a.y + a.y1 > b[0].y && a.y + a.y0 < b[1].y;
}
function archimedeanSpiral(size) {
var e = size[0] / size[1];
return function(t) {
return [e * (t *= 0.1) * Math.cos(t), t * Math.sin(t)];
};
}
function rectangularSpiral(size) {
var dy = 4, dx = dy * size[0] / size[1], x = 0, y = 0;
return function(t) {
var sign = t < 0 ? -1 : 1;
switch (Math.sqrt(1 + 4 * sign * t) - sign & 3) {
case 0:
x += dx;
break;
case 1:
y += dy;
break;
case 2:
x -= dx;
break;
default:
y -= dy;
break;
}
return [x, y];
};
}
function zeroArray(n) {
var a = [], i = -1;
while (++i < n) a[i] = 0;
return a;
}
function cloudCanvas() {
return document.createElement("canvas");
}
function functor(d) {
return typeof d === "function" ? d : function() {
return d;
};
}
return d3Cloud;
}
var d3CloudExports = requireD3Cloud();
const cloud = /* @__PURE__ */ getDefaultExportFromCjs(d3CloudExports);
const computeWords = (config, onComputeWord) => {
const {
words,
width,
height,
timeInterval,
spiral,
padding,
font,
fontStyle,
fontWeight,
fontSize,
rotate
} = config;
return new Promise((resolve) => {
const layout = cloud().size([width, height]).words(words);
layout.canvas(() => new OffscreenCanvas(1, 1));
if (timeInterval) {
layout.timeInterval(timeInterval);
}
if (spiral) {
layout.spiral(spiral);
}
if (padding) {
if (typeof padding === "function") {
layout.padding((datum, index2) => padding(datum, index2));
} else {
layout.padding(padding);
}
}
if (font) {
if (typeof font === "function") {
layout.font((datum, index2) => font(datum, index2));
} else {
layout.font(font);
}
}
if (fontStyle) {
if (typeof fontStyle === "function") {
layout.fontStyle((datum, index2) => fontStyle(datum, index2));
} else {
layout.fontStyle(fontStyle);
}
}
if (fontWeight) {
if (typeof fontWeight === "function") {
layout.fontWeight((datum, index2) => fontWeight(datum, index2));
} else {
layout.fontWeight(fontWeight);
}
}
if (fontSize) {
if (typeof fontSize === "function") {
layout.fontSize((datum, index2) => fontSize(datum, index2));
} else {
layout.fontSize(fontSize);
}
}
if (rotate) {
layout.rotate((datum, index2) => rotate(datum, index2));
}
layout.on("word", (word) => {
onComputeWord(word);
});
layout.on("end", (computedWords) => {
resolve(computedWords);
});
layout.start();
});
};
const computeLinearGradientCoords = (angle) => {
const rad = angle * Math.PI / 180;
const x1 = (Math.cos(rad + Math.PI) + 1) / 2 * 100;
const y1 = (Math.sin(rad + Math.PI) + 1) / 2 * 100;
const x2 = (Math.cos(rad) + 1) / 2 * 100;
const y2 = (Math.sin(rad) + 1) / 2 * 100;
return { x1, y1, x2, y2 };
};
class InternMap extends Map {
constructor(entries, key = keyof) {
super();
Object.defineProperties(this, { _intern: { value: /* @__PURE__ */ new Map() }, _key: { value: key } });
if (entries != null) for (const [key2, value] of entries) this.set(key2, value);
}
get(key) {
return super.get(intern_get(this, key));
}
has(key) {
return super.has(intern_get(this, key));
}
set(key, value) {
return super.set(intern_set(this, key), value);
}
delete(key) {
return super.delete(intern_delete(this, key));
}
}
function intern_get({ _intern, _key }, value) {
const key = _key(value);
return _intern.has(key) ? _intern.get(key) : value;
}
function intern_set({ _intern, _key }, value) {
const key = _key(value);
if (_intern.has(key)) return _intern.get(key);
_intern.set(key, value);
return value;
}
function intern_delete({ _intern, _key }, value) {
const key = _key(value);
if (_intern.has(key)) {
value = _intern.get(key);
_intern.delete(key);
}
return value;
}
function keyof(value) {
return value !== null && typeof value === "object" ? value.valueOf() : value;
}
function initRange(domain, range) {
switch (arguments.length) {
case 0:
break;
case 1:
this.range(domain);
break;
default:
this.range(range).domain(domain);
break;
}
return this;
}
const implicit = Symbol("implicit");
function ordinal() {
var index2 = new InternMap(), domain = [], range = [], unknown = implicit;
function scale(d) {
let i = index2.get(d);
if (i === void 0) {
if (unknown !== implicit) return unknown;
index2.set(d, i = domain.push(d) - 1);
}
return range[i % range.length];
}
scale.domain = function(_) {
if (!arguments.length) return domain.slice();
domain = [], index2 = new InternMap();
for (const value of _) {
if (index2.has(value)) continue;
index2.set(value, domain.push(value) - 1);
}
return scale;
};
scale.range = function(_) {
return arguments.length ? (range = Array.from(_), scale) : range.slice();
};
scale.unknown = function(_) {
return arguments.length ? (unknown = _, scale) : unknown;
};
scale.copy = function() {
return ordinal(domain, range).unknown(unknown);
};
initRange.apply(scale, arguments);
return scale;
}
function colors(specifier) {
var n = specifier.length / 6 | 0, colors2 = new Array(n), i = 0;
while (i < n) colors2[i] = "#" + specifier.slice(i * 6, ++i * 6);
return colors2;
}
const schemeCategory10 = colors("1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf");
const GradientDefs = ({ gradients }) => {
if (!gradients) {
return null;
}
return /* @__PURE__ */ jsxRuntime.jsx("defs", { children: gradients.map((gradient) => {
if (gradient.type === "linear") {
const angle = gradient.angle ?? 0;
const { x1, y1, x2, y2 } = computeLinearGradientCoords(angle);
return /* @__PURE__ */ jsxRuntime.jsx(
"linearGradient",
{
id: gradient.id,
gradientUnits: "userSpaceOnUse",
x1: `${x1}%`,
y1: `${y1}%`,
x2: `${x2}%`,
y2: `${y2}%`,
children: gradient.stops.map((stop) => /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: stop.offset, stopColor: stop.color }, stop.offset))
},
gradient.id
);
} else if (gradient.type === "radial") {
return /* @__PURE__ */ jsxRuntime.jsx("radialGradient", { id: gradient.id, children: gradient.stops.map((stop) => /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: stop.offset, stopColor: stop.color }, stop.offset)) }, gradient.id);
}
return null;
}) });
};
var reactFastCompare;
var hasRequiredReactFastCompare;
function requireReactFastCompare() {
if (hasRequiredReactFastCompare) return reactFastCompare;
hasRequiredReactFastCompare = 1;
var hasElementType = typeof Element !== "undefined";
var hasMap = typeof Map === "function";
var hasSet = typeof Set === "function";
var hasArrayBuffer = typeof ArrayBuffer === "function" && !!ArrayBuffer.isView;
function equal(a, b) {
if (a === b) return true;
if (a && b && typeof a == "object" && typeof b == "object") {
if (a.constructor !== b.constructor) return false;
var length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0; )
if (!equal(a[i], b[i])) return false;
return true;
}
var it;
if (hasMap && a instanceof Map && b instanceof Map) {
if (a.size !== b.size) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!b.has(i.value[0])) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!equal(i.value[1], b.get(i.value[0]))) return false;
return true;
}
if (hasSet && a instanceof Set && b instanceof Set) {
if (a.size !== b.size) return false;
it = a.entries();
while (!(i = it.next()).done)
if (!b.has(i.value[0])) return false;
return true;
}
if (hasArrayBuffer && ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
length = a.length;
if (length != b.length) return false;
for (i = length; i-- !== 0; )
if (a[i] !== b[i]) return false;
return true;
}
if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
if (a.valueOf !== Object.prototype.valueOf && typeof a.valueOf === "function" && typeof b.valueOf === "function") return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString && typeof a.toString === "function" && typeof b.toString === "function") return a.toString() === b.toString();
keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length) return false;
for (i = length; i-- !== 0; )
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
if (hasElementType && a instanceof Element) return false;
for (i = length; i-- !== 0; ) {
if ((keys[i] === "_owner" || keys[i] === "__v" || keys[i] === "__o") && a.$$typeof) {
continue;
}
if (!equal(a[keys[i]], b[keys[i]])) return false;
}
return true;
}
return a !== a && b !== b;
}
reactFastCompare = function isEqual(a, b) {
try {
return equal(a, b);
} catch (error) {
if ((error.message || "").match(/stack|recursion/i)) {
console.warn("react-fast-compare cannot handle circular refs");
return false;
}
throw error;
}
};
return reactFastCompare;
}
var reactFastCompareExports = requireReactFastCompare();
const isDeepEqual = /* @__PURE__ */ getDefaultExportFromCjs(reactFastCompareExports);
const generateTestId = (...components) => {
const name = components.join("-");
return `RWC__${name}`;
};
const svgTestId = generateTestId("WordCloud", "svg");
const Cloud = React.forwardRef(
({
fill = defaultFill,
transition = "all .5s ease",
renderWord = defaultWordRenderer,
renderTooltip = defaultTooltipRenderer,
width,
height,
gradients,
onWordClick,
onWordMouseOver,
onWordMouseOut,
enableTooltip,
svgProps,
...useWordCloudArgs
}, ref) => {
const { computedWords } = useWordCloud({ width, height, ...useWordCloudArgs });
const [hoveredWord, setHoveredWord] = React.useState({});
const [fillCacheVersion, setFillCacheVersion] = React.useState(0);
const [transitionCacheVersion, setTransitionCacheVersion] = React.useState(0);
const svgRef = React.useRef(null);
const wordRefs = React.useRef({});
const fillCacheRef = React.useRef({});
const transitionCacheRef = React.useRef({});
const mergedSvgRef = useMergedRefs(ref, svgRef);
React.useEffect(() => {
fillCacheRef.current = {};
setFillCacheVersion((v) => v + 1);
}, [fill]);
React.useEffect(() => {
transitionCacheRef.current = {};
setTransitionCacheVersion((v) => v + 1);
}, [transition]);
const handleWordMouseOver = React.useCallback(
(word, index2, event) => {
setHoveredWord({
word,
event,
wordElement: wordRefs.current[index2]
});
onWordMouseOver == null ? void 0 : onWordMouseOver(word, index2, event);
},
[onWordMouseOver]
);
const handleWordMouseOut = React.useCallback(
(word, index2, event) => {
setHoveredWord((prev) => ({
...prev,
event,
word: void 0
}));
onWordMouseOut == null ? void 0 : onWordMouseOut(word, index2, event);
},
[onWordMouseOut]
);
const tooltip = React.useMemo(
() => enableTooltip && renderTooltip && renderTooltip({
...hoveredWord,
svgElement: svgRef.current || void 0,
layoutWidth: width,
layoutHeight: height
}),
[enableTooltip, hoveredWord, renderTooltip, width, height]
);
const words = React.useMemo(() => {
return computedWords.map((word, index2) => {
let computedFill = fillCacheRef.current[index2];
if (!computedFill) {
computedFill = typeof fill === "function" ? fill(word, index2) : fill;
fillCacheRef.current[index2] = computedFill;
}
let computedTransition = transitionCacheRef.current[index2];
if (!computedTransition) {
computedTransition = typeof transition === "function" ? transition(word, index2) : transition;
transitionCacheRef.current[index2] = computedTransition;
}
const data = {
index: index2,
onWordClick,
onWordMouseOver: handleWordMouseOver,
onWordMouseOut: handleWordMouseOut,
fill: computedFill,
transition: computedTransition,
...word
};
const renderedWord = renderWord(data, (ref2) => {
wordRefs.current[index2] = ref2;
});
return /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: renderedWord }, index2);
});
}, [
computedWords,
fill,
handleWordMouseOut,
handleWordMouseOver,
onWordClick,
renderWord,
transition,
fillCacheVersion,
transitionCacheVersion
]);
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
/* @__PURE__ */ jsxRuntime.jsxs(
"svg",
{
ref: mergedSvgRef,
viewBox: `0 0 ${width} ${height}`,
"data-testid": svgTestId,
...svgProps,
children: [
/* @__PURE__ */ jsxRuntime.jsx(GradientDefs, { gradients }),
/* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${width / 2},${height / 2})`, children: words })
]
}
),
tooltip
] });
}
);
const WordCloud = React.memo(Cloud, isDeepEqual);
const testId$1 = generateTestId("DefaultWordRenderer", "text");
const WordRenderer$1 = React.forwardRef(
({ data, textStyle }, ref) => {
const { index: index2, onWordClick, onWordMouseOver, onWordMouseOut, ...word } = data;
React.useEffect(() => {
if (!ref) {
console.warn(
"DefaultWordRenderer: ref is not provided! This may cause issues with tooltip rendering."
);
}
}, [ref]);
return /* @__PURE__ */ jsxRuntime.jsx(
"text",
{
ref,
"data-testid": testId$1,
textAnchor: "middle",
transform: `translate(${word.x}, ${word.y}) rotate(${word.rotate})`,
style: {
fontFamily: word.font,
fontStyle: word.style,
fontWeight: word.weight,
fontSize: `${word.size}px`,
fill: word.fill,
transition: word.transition,
cursor: onWordClick ? "pointer" : "text",
...textStyle
},
onClick: (event) => onWordClick == null ? void 0 : onWordClick(word, index2, event),
onMouseOver: (event) => onWordMouseOver == null ? void 0 : onWordMouseOver(word, index2, event),
onMouseOut: (event) => onWordMouseOut == null ? void 0 : onWordMouseOut(word, index2, event),
children: word.text
},
index2
);
}
);
const DefaultWordRenderer = React.memo(WordRenderer$1, isDeepEqual);
const testId = generateTestId("AnimatedWordRenderer", "text");
const defaultAnimationDelay = (_, index2) => index2 * 10;
const WordRenderer = React.forwardRef(
({ data, animationDelay = defaultAnimationDelay, textStyle }, ref) => {
const { index: index2, onWordClick, onWordMouseOver, onWordMouseOut, ...word } = data;
const [visible, setVisible] = React.useState(false);
React.useEffect(() => {
if (!ref) {
console.warn(
"AnimatedWordRenderer: ref is not provided! This may cause issues with tooltip rendering."
);
}
}, [ref]);
React.useEffect(() => {
const delay = typeof animationDelay === "function" ? animationDelay(word, index2) : animationDelay;
const timeout = setTimeout(() => {
setVisible(true);
}, delay);
return () => clearTimeout(timeout);
}, [animationDelay, index2, word]);
return /* @__PURE__ */ jsxRuntime.jsx(
"text",
{
ref,
"data-testid": testId,
textAnchor: "middle",
transform: `translate(${word.x}, ${word.y}) rotate(${word.rotate}) scale(${visible ? 1 : 0})`,
style: {
fontFamily: word.font,
fontStyle: word.style,
fontWeight: word.weight,
fontSize: `${word.size}px`,
fill: word.fill,
transition: word.transition,
opacity: visible ? 1 : 0,
cursor: onWordClick ? "pointer" : "text",
...textStyle
},
onClick: (event) => onWordClick == null ? void 0 : onWordClick(word, index2, event),
onMouseOver: (event) => onWordMouseOver == null ? void 0 : onWordMouseOver(word, index2, event),
onMouseOut: (event) => onWordMouseOut == null ? void 0 : onWordMouseOut(word, index2, event),
children: word.text
},
index2
);
}
);
const AnimatedWordRenderer = React.memo(WordRenderer, isDeepEqual);
const containerTestId = generateTestId("DefaultTooltipRenderer", "container");
const textTestId = generateTestId("DefaultTooltipRenderer", "text");
const valueTestId = generateTestId("DefaultTooltipRenderer", "value");
const TooltipRenderer = ({
data,
transitionDuration = 300,
containerStyle,
textStyle,
valueStyle,
...useFloatingOptions
}) => {
const [visible, setVisible] = React.useState(false);
const [currentWord, setCurrentWord] = React.useState();
const { refs, floatingStyles } = useTooltip({ data, ...useFloatingOptions });
React.useEffect(() => {
if (data.word) {
setCurrentWord(data.word);
setVisible(true);
} else {
setVisible(false);
}
}, [data]);
const mergedContainerStyle = {
opacity: visible ? 1 : 0,
transition: `all ${transitionDuration}ms ease`,
pointerEvents: "none",
background: "rgba(0, 0, 0, 0.75)",
color: "#fff",
padding: "8px 12px",
borderRadius: "4px",
transform: "translate(10px, 10px)",
whiteSpace: "nowrap",
display: "flex",
alignItems: "center",
gap: "10px",
...containerStyle,
...floatingStyles
};
const mergedTextStyle = {
fontWeight: "bold",
fontSize: "14px",
fontFamily: "Arial",
...textStyle
};
const mergedValueStyle = {
fontSize: "12px",
fontFamily: "monospace",
...valueStyle
};
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: refs.setFloating, style: mergedContainerStyle, "data-testid": containerTestId, children: [
/* @__PURE__ */ jsxRuntime.jsx("span", { style: mergedTextStyle, "data-testid": textTestId, children: currentWord == null ? void 0 : currentWord.text }),
/* @__PURE__ */ jsxRuntime.jsx("span", { style: mergedValueStyle, "data-testid": valueTestId, children: currentWord == null ? void 0 : currentWord.value })
] });
};
const DefaultTooltipRenderer = React.memo(TooltipRenderer, isDeepEqual);
const defaultScaleOrdinal = ordinal(schemeCategory10);
const defaultFill = (_, index2) => defaultScaleOrdinal(String(index2));
const defaultRotate = () => (~~(Math.random() * 6) - 3) * 30;
const defaultFontSize = (word) => Math.sqrt(word.value);
const defaultWordRenderer = (data, ref) => /* @__PURE__ */ jsxRuntime.jsx(DefaultWordRenderer, { ref, data });
const animatedWordRenderer = (data, ref) => /* @__PURE__ */ jsxRuntime.jsx(AnimatedWordRenderer, { ref, data });
const defaultTooltipRenderer = (data) => /* @__PURE__ */ jsxRuntime.jsx(DefaultTooltipRenderer, { data });
const useWordCloud = ({
font = "Impact",
fontStyle = "normal",
fontWeight = "normal",
fontSize = defaultFontSize,
rotate = defaultRotate,
spiral = "archimedean",
padding = 1,
width,
height,
timeInterval = 1,
words,
onStartComputation,
onWordComputed,
onCompleteComputation
}) => {
const [computedWords, setComputedWords] = React.useState([]);
const [isLoading, setIsLoading] = React.useState(false);
const [pendingComputation, setPendingComputation] = React.useState(false);
const computationId = React.useRef(0);
const lastProcessedWordIndex = React.useRef(0);
const handleStartComputation = () => {
lastProcessedWordIndex.current = 0;
setIsLoading(true);
setComputedWords([]);
onStartComputation == null ? void 0 : onStartComputation();
};
const handleCompleteComputation = (words2) => {
setIsLoading(false);
onCompleteComputation == null ? void 0 : onCompleteComputation(words2);
};
const handleWorWordComputed = (word, index2) => {
onWordComputed == null ? void 0 : onWordComputed(word, index2);
};
React.useEffect(() => {
if (isLoading) return;
setPendingComputation(true);
computationId.current += 1;
handleStartComputation();
}, [
height,
padding,
width,
words,
timeInterval,
spiral,
font,
fontStyle,
fontWeight,
fontSize,
rotate
]);
React.useEffect(() => {
if (pendingComputation && computedWords.length === 0) {
const finalConfig = {
font,
fontStyle,
fontWeight,
fontSize,
rotate,
spiral,
padding,
width,
height,
timeInterval,
words
};
const currentComputationId = computationId.current;
computeWords(finalConfig, (computedWord) => {
if (currentComputationId === computationId.current) {
handleWorWordComputed(computedWord, lastProcessedWordIndex.current);
lastProcessedWordIndex.current += 1;
setComputedWords((prevWords) => {
return [...prevWords, computedWord];
});
}
}).then((words2) => {
if (currentComputationId === computationId.current) {
setPendingComputation(false);
handleCompleteComputation(words2);
}
});
}
}, [pendingComputation]);
return { computedWords, isLoading };
};
const min = Math.min;
const max = Math.max;
const round = Math.round;
const createCoords = (v) => ({
x: v,
y: v
});
function getSide(placement) {
return placement.split("-")[0];
}
function getAlignment(placement) {
return placement.split("-")[1];
}
function getOppositeAxis(axis) {
return axis === "x" ? "y" : "x";
}
function getAxisLength(axis) {
return axis === "y" ? "height" : "width";
}
function getSideAxis(placement) {
return ["top", "bottom"].includes(getSide(placement)) ? "y" : "x";
}
function getAlignmentAxis(placement) {
return getOppositeAxis(getSideAxis(placement));
}
function rectToClientRect(rect) {
const {
x,
y,
width,
height
} = rect;
return {
width,
height,
top: y,
left: x,
right: x + width,
bottom: y + height,
x,
y
};
}
function computeCoordsFromPlacement(_ref, placement, rtl) {
let {
reference,
floating
} = _ref;
const sideAxis = getSideAxis(placement);
const alignmentAxis = getAlignmentAxis(placement);
const alignLength = getAxisLength(alignmentAxis);
const side = getSide(placement);
const isVertical = sideAxis === "y";
const commonX = reference.x + reference.width / 2 - floating.width / 2;
const commonY = reference.y + reference.height / 2 - floating.height / 2;
const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2;
let coords;
switch (side) {
case "top":
coords = {
x: commonX,
y: reference.y - floating.height
};
break;
case "bottom":
coords = {
x: commonX,
y: reference.y + reference.height
};
break;
case "right":
coords = {
x: reference.x + reference.width,
y: commonY
};
break;
case "left":
coords = {
x: reference.x - floating.width,
y: commonY
};
break;
default:
coords = {
x: reference.x,
y: reference.y
};
}
switch (getAlignment(placement)) {
case "start":
coords[alignmentAxis] -= commonAlign * (rtl && isVertical ? -1 : 1);
break;
case "end":
coords[alignmentAxis] += commonAlign * (rtl && isVertical ? -1 : 1);
break;
}
return coords;
}
const computePosition$1 = async (reference, floating, config) => {
const {
placement = "bottom",
strategy = "absolute",
middleware = [],
platform: platform2
} = config;
const validMiddleware = middleware.filter(Boolean);
const rtl = await (platform2.isRTL == null ? void 0 : platform2.isRTL(floating));
let rects = await platform2.getElementRects({
reference,
floating,
strategy
});
let {
x,
y
} = computeCoordsFromPlacement(rects, placement, rtl);
let statefulPlacement = placement;
let middlewareData = {};
let resetCount = 0;
for (let i = 0; i < validMiddleware.length; i++) {
const {
name,
fn
} = validMiddleware[i];
const {
x: nextX,
y: nextY,
data,
reset
} = await fn({
x,
y,
initialPlacement: placement,
placement: statefulPlacement,
strategy,
middlewareData,
rects,
platform: platform2,
elements: {
reference,
floating
}
});
x = nextX != null ? nextX : x;
y = nextY != null ? nextY : y;
middlewareData = {
...middlewareData,
[name]: {
...middlewareData[name],
...data
}
};
if (reset && resetCount <= 50) {
resetCount++;
if (typeof reset === "object") {
if (reset.placement) {
statefulPlacement = reset.placement;
}
if (reset.rects) {
rects = reset.rects === true ? await platform2.getElementRects({
reference,
floating,
strategy
}) : reset.rects;
}
({
x,
y
} = computeCoordsFromPlacement(rects, statefulPlacement, rtl));
}
i = -1;
}
}
return {
x,
y,
placement: statefulPlacement,
strategy,
middlewareData
};
};
function hasWindow() {
return typeof window !== "undefined";
}
function getNodeName(node) {
if (isNode(node)) {
return (node.nodeName || "").toLowerCase();
}
return "#document";
}
function getWindow(node) {
var _node$ownerDocument;
return (node == null || (_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.defaultView) || window;
}
function getDocumentElement(node) {
var _ref;
return (_ref = (isNode(node) ? node.ownerDocument : node.document) || window.document) == null ? void 0 : _ref.documentElement;
}
function isNode(value) {
if (!hasWindow()) {
return false;
}
return value instanceof Node || value instanceof getWindow(value).Node;
}
function isElement(value) {
if (!hasWindow()) {
return false;
}
return value instanceof Element || value instanceof getWindow(value).Element;
}
function isHTMLElement(value) {
if (!hasWindow()) {
return false;
}
return value instanceof HTMLElement || value instanceof getWindow(value).HTMLElement;
}
function isShadowRoot(value) {
if (!hasWindow() || typeof ShadowRoot === "undefined") {
return false;
}
return value instanceof ShadowRoot || value instanceof getWindow(value).ShadowRoot;
}
function isOverflowElement(element) {
const {
overflow,
overflowX,
overflowY,
display
} = getComputedStyle(element);
return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !["inline", "contents"].includes(display);
}
function isTableElement(element) {
return ["table", "td", "th"].includes(getNodeName(element));
}
function isTopLayer(element) {
return [":popover-open", ":modal"].some((selector) => {
try {
return element.matches(selector);
} catch (e) {
return false;
}
});
}
function isContainingBlock(elementOrCss) {
const webkit = isWebKit();
const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss;
return ["transform", "translate", "scale", "rotate", "perspective"].some((value) => css[value] ? css[value] !== "none" : false) || (css.containerType ? css.containerType !== "normal" : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== "none" : false) || !webkit && (css.filter ? css.filter !== "none" : false) || ["transform", "translate", "scale", "rotate", "perspective", "filter"].some((value) => (css.willChange || "").includes(value)) || ["paint", "layout", "strict", "content"].some((value) => (css.contain || "").includes(value));
}
function getContainingBlock(element) {
let currentNode = getParentNode(element);
while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
if (isContainingBlock(currentNode)) {
return currentNode;
} else if (isTopLayer(currentNode)) {
return null;
}
currentNode = getParentNode(currentNode);
}
return null;
}
function isWebKit() {
if (typeof CSS === "undefined" || !CSS.supports) return false;
return CSS.supports("-webkit-backdrop-filter", "none");
}
function isLastTraversableNode(node) {
return ["html", "body", "#document"].includes(getNodeName(node));
}
function getComputedStyle(element) {
return getWindow(element).getComputedStyle(element);
}
function getNodeScroll(element) {
if (isElement(element)) {
return {
scrollLeft: element.scrollLeft,
scrollTop: element.scrollTop
};
}
return {
scrollLeft: element.scrollX,
scrollTop: element.scrollY
};
}
function getParentNode(node) {
if (getNodeName(node) === "html") {
return node;
}
const result = (
// Step into the shadow DOM of the parent of a slotted node.
node.assignedSlot || // DOM Element detected.
node.parentNode || // ShadowRoot detected.
isShadowRoot(node) && node.host || // Fallback.
getDocumentElement(node)
);
return isShadowRoot(result) ? result.host : result;
}
function getNearestOverflowAncestor(node) {
const parentNode = getParentNode(node);
if (isLastTraversableNode(parentNode)) {
return node.ownerDocument ? node.ownerDocument.body : node.body;
}
if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) {
return parentNode;
}
return getNearestOverflowAncestor(parentNode);
}
function getOverflowAncestors(node, list, traverseIframes) {
var _node$ownerDocument2;
if (list === void 0) {
list = [];
}
const scrollableAncestor = getNearestOverflowAncestor(node);
const isBody = scrollableAncestor === ((_node$ownerDocument2 = node.ownerDocument) == null ? void 0 : _node$ownerDocument2.body);
const win = getWindow(scrollableAncestor);
if (isBody) {
getFrameElement(win);
return list.concat(win, win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [], []);
}
return list.concat(scrollableAncestor, getOverflowAncestors(scrollableAncestor, []));
}
function getFrameElement(win) {
return win.parent && Object.getPrototypeOf(win.parent) ? win.frameElement : null;
}
function getCssDimensions(element) {
const css = getComputedStyle(element);
let width = parseFloat(css.width) || 0;
let height = parseFloat(css.height) || 0;
const hasOffset = isHTMLElement(element);
const offsetWidth = hasOffset ? element.offsetWidth : width;
const offsetHeight = hasOffset ? element.offsetHeight : height;
const shouldFallback = round(width) !== offsetWidth || round(height) !== offsetHeight;
if (shouldFallback) {
width = offsetWidth;
height = offsetHeight;
}
return {
width,
height,
$: shouldFallback
};
}
function unwrapElement(element) {
return !isElement(element) ? element.contextElement : element;
}
function getScale(element) {
const domElement = unwrapElement(element);
if (!isHTMLElement(domElement)) {
return createCoords(1);
}
const rect = domElement.getBoundingClientRect();
const {
width,
height,
$
} = getCssDimensions(domElement);
let x = ($ ? round(rect.width) : rect.width) / width;
let y = ($ ? round(rect.height) : rect.height) / height;
if (!x || !Number.isFinite(x)) {
x = 1;
}
if (!y || !Number.isFinite(y)) {
y = 1;
}
return {
x,
y
};
}
const noOffsets = /* @__PURE__ */ createCoords(0);
function getVisualOffsets(element) {
const win = getWindow(element);
if (!isWebKit() || !win.visualViewport) {
return noOffsets;
}
return {
x: win.visualViewport.offsetLeft,
y: win.visualViewport.offsetTop
};
}
function shouldAddVisualOffsets(element, isFixed, floatingOffsetParent) {
if (isFixed === void 0) {
isFixed = false;
}
if (!floatingOffsetParent || isFixed && floatingOffsetParent !== getWindow(element)) {
return false;
}
return isFixed;
}
function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetParent) {
if (includeScale === void 0) {
includeScale = false;
}