UNPKG

@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
(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; }