bubble-chart-js 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/README.md +35 -20
  3. package/assets/bubble-chart.png +0 -0
  4. package/commands.jsonc +13 -0
  5. package/dist/bubbleChart.cjs.js +1 -0
  6. package/dist/bubbleChart.esm.js +1 -0
  7. package/dist/bubbleChart.umd.js +1 -0
  8. package/dist/canvas.d.ts +2 -1
  9. package/dist/constants/app-constants.d.ts +3 -0
  10. package/dist/constants/physics.d.ts +3 -3
  11. package/dist/features/text-wrapper.d.ts +1 -0
  12. package/dist/features/tooltip.d.ts +4 -3
  13. package/dist/index.d.ts +7 -0
  14. package/dist/models/internal/{dataItemInfo.d.ts → data-item-info.d.ts} +1 -1
  15. package/dist/models/public/config/bubble-appearance.d.ts +32 -0
  16. package/dist/models/public/config/font-options.d.ts +46 -0
  17. package/dist/models/public/config/interaction-options.d.ts +5 -0
  18. package/dist/models/public/config/tooltip-config.d.ts +4 -0
  19. package/dist/models/public/config/tooltip-options.d.ts +170 -0
  20. package/dist/models/public/configuration.d.ts +25 -10
  21. package/dist/models/public/data-item.d.ts +8 -0
  22. package/dist/services/{renderService.d.ts → render-service.d.ts} +1 -1
  23. package/dist/utils/helper.d.ts +1 -1
  24. package/index.html +77 -0
  25. package/package.json +5 -3
  26. package/tsconfig.json +3 -1
  27. package/webpack.config.js +49 -13
  28. package/bubble-chart-js-1.0.0.tgz +0 -0
  29. package/dist/bundle.js +0 -186
  30. package/dist/features/textWrapper.d.ts +0 -1
  31. package/dist/main.d.ts +0 -1
  32. package/dist/models/public/dataItem.d.ts +0 -4
  33. package/src/canvas.ts +0 -17
  34. package/src/constants/physics.ts +0 -10
  35. package/src/core/renderer.ts +0 -110
  36. package/src/features/textWrapper.ts +0 -168
  37. package/src/features/tooltip.ts +0 -69
  38. package/src/main.ts +0 -5
  39. package/src/models/internal/dataItemInfo.ts +0 -8
  40. package/src/models/public/configuration.ts +0 -16
  41. package/src/models/public/dataItem.ts +0 -4
  42. package/src/services/chartService.ts +0 -24
  43. package/src/services/renderService.ts +0 -262
  44. package/src/utils/config.ts +0 -33
  45. package/src/utils/helper.ts +0 -3
  46. package/src/utils/validation.ts +0 -18
  47. /package/dist/services/{chartService.d.ts → chart-service.d.ts} +0 -0
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "bubble-chart-js",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "bubbleChartJs is a lightweight, customizable JavaScript library for creating stacked bubble charts. It arranges bubbles based on their values, with the largest bubble positioned at the top and surrounding bubbles decreasing in size accordingly.",
5
- "main": "dist/index.js",
5
+ "main": "dist/bubbleChart.cjs.js",
6
+ "module": "dist/bubbleChart.esm.js",
7
+ "browser": "dist/bubbleChart.umd.js",
6
8
  "types": "dist/index.d.ts",
7
9
  "scripts": {
8
10
  "clean": "rimraf dist",
9
- "build": "npm run clean && webpack --mode development",
11
+ "build": "npm run clean && webpack --mode production",
10
12
  "watch": "webpack --watch",
11
13
  "start": "webpack serve --open",
12
14
  "lint": "eslint src/**/*.ts",
package/tsconfig.json CHANGED
@@ -3,12 +3,14 @@
3
3
  "target": "ES6",
4
4
  "module": "ESNext",
5
5
  "declaration": true,
6
+ "declarationDir": "./dist",
6
7
  "outDir": "./dist",
7
8
  "rootDir": "./src",
8
9
  "strict": true,
9
10
  "esModuleInterop": true,
10
11
  "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true
12
+ "forceConsistentCasingInFileNames": true,
13
+ "allowSyntheticDefaultImports": true
12
14
  },
13
15
  "include": ["src/**/*"],
14
16
  "exclude": ["node_modules", "**/*.test.ts"]
package/webpack.config.js CHANGED
@@ -1,28 +1,64 @@
1
1
  const path = require("path");
2
2
 
3
- module.exports = {
4
- entry: "./src/main.ts", // Entry point for your application
5
- output: {
6
- filename: "bundle.js", // Output bundle file
7
- path: path.resolve(__dirname, "dist"), // Output directory
8
- },
3
+ /** @type {import('webpack').Configuration} */
4
+ const commonConfig = {
5
+ entry: "./src/index.ts",
6
+ target: "web",
9
7
  resolve: {
10
- extensions: [".ts", ".js"], // Resolve TypeScript and JavaScript files
8
+ extensions: [".ts", ".js"],
11
9
  },
12
10
  module: {
13
11
  rules: [
14
12
  {
15
- test: /\.ts$/, // Apply the loader to TypeScript files
13
+ test: /\.ts$/,
16
14
  use: "ts-loader",
17
15
  exclude: /node_modules/,
18
16
  },
19
17
  ],
20
18
  },
21
- devServer: {
22
- static: {
23
- directory: path.join(__dirname, "dist"), // Serve files from the dist directory
19
+ optimization: {
20
+ minimize: true,
21
+ },
22
+ };
23
+
24
+ /** UMD Build (Browser + Node.js) */
25
+ const umdConfig = {
26
+ ...commonConfig,
27
+ output: {
28
+ path: path.resolve(__dirname, "dist"),
29
+ filename: "bubbleChart.umd.js",
30
+ library: "BubbleChart",
31
+ libraryTarget: "umd",
32
+ globalObject: "this",
33
+ umdNamedDefine: true,
34
+ },
35
+ };
36
+
37
+ /** ESM Build (Modern JavaScript) */
38
+ const esmConfig = {
39
+ ...commonConfig,
40
+ output: {
41
+ path: path.resolve(__dirname, "dist"),
42
+ filename: "bubbleChart.esm.js",
43
+ library: {
44
+ type: "module",
45
+ },
46
+ },
47
+ experiments: {
48
+ outputModule: true, // Required for ESM builds
49
+ },
50
+ };
51
+
52
+ /** CommonJS Build (Node.js) */
53
+ const cjsConfig = {
54
+ ...commonConfig,
55
+ output: {
56
+ path: path.resolve(__dirname, "dist"),
57
+ filename: "bubbleChart.cjs.js",
58
+ library: {
59
+ type: "commonjs2",
24
60
  },
25
- compress: true,
26
- port: 9000, // Port for the dev server
27
61
  },
28
62
  };
63
+
64
+ module.exports = [umdConfig, esmConfig, cjsConfig];
Binary file
package/dist/bundle.js DELETED
@@ -1,186 +0,0 @@
1
- /*
2
- * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
3
- * This devtool is neither made for production nor for readable output files.
4
- * It uses "eval()" calls to create a separate source file in the browser devtools.
5
- * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
6
- * or disable the default devtool with "devtool: false".
7
- * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
8
- */
9
- /******/ (() => { // webpackBootstrap
10
- /******/ "use strict";
11
- /******/ var __webpack_modules__ = ({
12
-
13
- /***/ "./src/canvas.ts":
14
- /*!***********************!*\
15
- !*** ./src/canvas.ts ***!
16
- \***********************/
17
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
18
-
19
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ createCanvas: () => (/* binding */ createCanvas)\n/* harmony export */ });\n/**\n * Creates and initializes the canvas inside the specified container.\n */\nfunction createCanvas(containerId) {\n const canvasContainer = document.getElementById(containerId);\n if (!canvasContainer) {\n console.error(`Canvas container with ID '${containerId}' not found.`);\n return null;\n }\n const canvas = document.createElement(\"canvas\");\n canvas.width = canvasContainer.offsetWidth;\n canvas.height = canvasContainer.offsetHeight;\n canvasContainer.appendChild(canvas);\n return canvas;\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/canvas.ts?");
20
-
21
- /***/ }),
22
-
23
- /***/ "./src/constants/physics.ts":
24
- /*!**********************************!*\
25
- !*** ./src/constants/physics.ts ***!
26
- \**********************************/
27
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
28
-
29
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ PHYSICS: () => (/* binding */ PHYSICS)\n/* harmony export */ });\nconst PHYSICS = {\n forceStrength: 0.015,\n iterations: 1000,\n damping: 0.55,\n boundaryForce: 0.02,\n centerForce: 0.12,\n centerAttraction: 0.6,\n centerDamping: 0.3,\n centerRadiusBuffer: 35,\n};\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/constants/physics.ts?");
30
-
31
- /***/ }),
32
-
33
- /***/ "./src/core/renderer.ts":
34
- /*!******************************!*\
35
- !*** ./src/core/renderer.ts ***!
36
- \******************************/
37
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
38
-
39
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ renderChart: () => (/* binding */ renderChart)\n/* harmony export */ });\n/* harmony import */ var _features_textWrapper__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../features/textWrapper */ \"./src/features/textWrapper.ts\");\n/* harmony import */ var _features_tooltip__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../features/tooltip */ \"./src/features/tooltip.ts\");\n/* harmony import */ var _utils_validation__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../utils/validation */ \"./src/utils/validation.ts\");\n/* harmony import */ var _canvas__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../canvas */ \"./src/canvas.ts\");\n/* harmony import */ var _services_renderService__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../services/renderService */ \"./src/services/renderService.ts\");\n/* harmony import */ var _utils_helper__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../utils/helper */ \"./src/utils/helper.ts\");\n\n\n\n\n\n\nfunction renderChart(config) {\n if (!(0,_utils_validation__WEBPACK_IMPORTED_MODULE_2__.validateConfig)(config))\n return;\n // Create & setup canvas\n let canvas = (0,_canvas__WEBPACK_IMPORTED_MODULE_3__.createCanvas)(config.canvasContainerId);\n if (!canvas)\n return;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n console.error(\"Invalid context\");\n return;\n }\n const sortedData = (0,_services_renderService__WEBPACK_IMPORTED_MODULE_4__.getChartData)(config, canvas, ctx);\n function draw() {\n if (!canvas || !ctx) {\n console.warn(\"canvas or ctx is not valid\");\n return;\n }\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n sortedData.forEach((item) => {\n const color = config.colorMap[item.label] || config.defaultBubbleColor;\n // Ensure radius is at least minRadius\n const radius = Math.max(item.radius, config.minRadius);\n ctx.beginPath();\n ctx.arc(item.x, item.y, radius, 0, Math.PI * 2);\n ctx.fillStyle = color;\n ctx.fill();\n ctx.strokeStyle = \"black\"; // Border color\n ctx.lineWidth = 0.25; // Border thickness\n ctx.stroke(); // Apply border\n // Text styling\n ctx.fillStyle = config.fontColor;\n const fontSize = (0,_utils_helper__WEBPACK_IMPORTED_MODULE_5__.getFontSize)(radius, config.fontSize);\n ctx.font = `${fontSize}px ${config.fontFamily}`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n const padding = 5; // Padding around text\n const maxWidth = radius * 1.5 - padding * 2; // Adjusted for padding\n if (config.textWrap) {\n // Calculate vertical position for lines\n const lineHeight = fontSize * 1.2;\n // Dynamically determine lines if maxLines is not set\n const lines = (0,_features_textWrapper__WEBPACK_IMPORTED_MODULE_0__.getWrappedLines)(ctx, item.label, maxWidth, config.maxLines, radius);\n const startY = item.y - ((lines.length - 1) * lineHeight) / 2;\n lines.forEach((line, index) => {\n ctx.fillText(line, item.x, startY + index * lineHeight);\n });\n }\n else {\n ctx.fillText(item.label, item.x, item.y);\n }\n });\n }\n // Robust approach that handles resizing:\n function resizeCanvas() {\n const canvasContainer = document.getElementById(config.canvasContainerId);\n if (canvasContainer && canvas) {\n canvas.width = canvasContainer.offsetWidth;\n canvas.height = canvasContainer.offsetHeight;\n draw(); // Call your drawing function\n }\n }\n if (config.isResizeCanvasOnWindowSizeChange) {\n resizeCanvas(); // Initial resize\n window.addEventListener(\"resize\", resizeCanvas); // Resize on window resize\n }\n // Initial draw\n draw();\n if (config.showToolTip) {\n const tooltip = (0,_features_tooltip__WEBPACK_IMPORTED_MODULE_1__.createTooltipElement)();\n let animationFrameId = null;\n canvas.addEventListener(\"mousemove\", (event) => {\n if (animationFrameId)\n return; // Prevent excessive calls\n animationFrameId = requestAnimationFrame(() => {\n (0,_features_tooltip__WEBPACK_IMPORTED_MODULE_1__.handleMouseMove)(event, sortedData, canvas, tooltip);\n animationFrameId = null; // Reset after execution\n });\n });\n }\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/core/renderer.ts?");
40
-
41
- /***/ }),
42
-
43
- /***/ "./src/features/textWrapper.ts":
44
- /*!*************************************!*\
45
- !*** ./src/features/textWrapper.ts ***!
46
- \*************************************/
47
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
48
-
49
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ getWrappedLines: () => (/* binding */ getWrappedLines)\n/* harmony export */ });\nfunction getWrappedLines(ctx, text, maxLineWidth, maxAllowedLines, circleRadius, maxCharsPerWord = undefined) {\n if (!text || maxLineWidth <= 0)\n return [];\n let words = text.split(/\\s+/);\n // Set default for maxAllowedLines based on available space\n maxAllowedLines = determineMaxLines(ctx, maxAllowedLines, circleRadius, maxLineWidth);\n // Handle single-word case separately\n if (words.length === 1) {\n return [truncateTextToFit(ctx, words[0], maxLineWidth)];\n }\n if (maxCharsPerWord) {\n // For Now dont allow default word truncation\n // Set default for maxCharsPerWord if not provided\n maxCharsPerWord = determineMaxCharsPerWord(ctx, maxCharsPerWord, maxLineWidth);\n // Apply maxCharsPerWord truncation if needed\n words = words.map((word) => truncateWord(word, maxCharsPerWord));\n }\n return wrapTextIntoLines(ctx, words, maxLineWidth, maxAllowedLines);\n}\n/**\n * Determines maxAllowedLines based on available space.\n */\nfunction determineMaxLines(ctx, maxAllowedLines, circleRadius, // TODO later account circleRadius to handleLabeltext-NoOfLines\nmaxLineWidth) {\n if (maxAllowedLines && maxAllowedLines > 0)\n return maxAllowedLines;\n const fontSize = parseInt(ctx.font, 10) || 16;\n const lineHeight = fontSize * 1.2;\n return Math.floor(maxLineWidth / lineHeight) || 1; // Default: Fit within maxLineWidth\n}\n/**\n * Determines maxCharsPerWord based on maxLineWidth.\n */\nfunction determineMaxCharsPerWord(ctx, maxCharsPerWord, maxLineWidth) {\n if (maxCharsPerWord && maxCharsPerWord > 0)\n return maxCharsPerWord;\n const avgCharWidth = ctx.measureText(\"W\").width || 8; // Approximate avg char width\n return Math.floor(maxLineWidth / avgCharWidth); // Default: Fit within maxLineWidth\n}\n/**\n * Wraps text into multiple lines within maxLineWidth.\n */\nfunction wrapTextIntoLines(ctx, words, maxLineWidth, maxAllowedLines) {\n const wrappedLines = [];\n let currentLine = \"\";\n for (const word of words) {\n const testLine = currentLine ? `${currentLine} ${word}` : word;\n const testWidth = ctx.measureText(testLine).width;\n if (testWidth <= maxLineWidth) {\n currentLine = testLine;\n }\n else {\n if (currentLine)\n wrappedLines.push(currentLine);\n currentLine = word;\n if (wrappedLines.length >= maxAllowedLines - 1)\n break;\n }\n }\n if (currentLine)\n wrappedLines.push(currentLine);\n return finalizeWrappedLines(ctx, wrappedLines, maxLineWidth, maxAllowedLines);\n}\n/**\n * Ensures the final wrapped lines do not exceed maxAllowedLines.\n */\nfunction finalizeWrappedLines(ctx, wrappedLines, maxLineWidth, maxAllowedLines) {\n if (wrappedLines.length > maxAllowedLines) {\n wrappedLines.length = maxAllowedLines;\n }\n // Truncate the last line if needed\n if (wrappedLines.length === maxAllowedLines) {\n wrappedLines[maxAllowedLines - 1] = truncateTextToFit(ctx, wrappedLines[maxAllowedLines - 1], maxLineWidth);\n }\n return wrappedLines.map((line) => ctx.measureText(line).width > maxLineWidth\n ? truncateTextToFit(ctx, line, maxLineWidth)\n : line);\n}\n/**\n * Truncates text with an ellipsis if it exceeds maxLineWidth.\n */\nfunction truncateTextToFit(ctx, text, maxLineWidth) {\n text = text.trim();\n if (!text)\n return \"\"; // Handle empty or whitespace-only input\n if (ctx.measureText(text).width <= maxLineWidth)\n return text; // Return early if text fits\n let left = 0, right = text.length;\n // binary search\n while (left < right) {\n const mid = Math.ceil((left + right) / 2);\n const truncated = text.slice(0, mid) + \"…\";\n if (ctx.measureText(truncated).width > maxLineWidth) {\n right = mid - 1; // Reduce size\n }\n else {\n left = mid; // Expand size\n }\n }\n return text.slice(0, left) + \"…\";\n}\n/**\n * Truncates a word to maxCharsPerWord with an ellipsis.\n */\nfunction truncateWord(word, maxCharsPerWord) {\n return word.length > maxCharsPerWord\n ? word.slice(0, maxCharsPerWord) + \"…\"\n : word;\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/features/textWrapper.ts?");
50
-
51
- /***/ }),
52
-
53
- /***/ "./src/features/tooltip.ts":
54
- /*!*********************************!*\
55
- !*** ./src/features/tooltip.ts ***!
56
- \*********************************/
57
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
58
-
59
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ createTooltipElement: () => (/* binding */ createTooltipElement),\n/* harmony export */ handleMouseMove: () => (/* binding */ handleMouseMove)\n/* harmony export */ });\n// let hoveredItem: DataItemInfo | null = null;\nfunction createTooltipElement() {\n const tooltip = document.createElement(\"div\");\n tooltip.id = \"tooltip\";\n tooltip.style.display = \"none\";\n document.body.appendChild(tooltip);\n return tooltip;\n}\nfunction handleMouseMove(event, data, canvas, tooltip) {\n const { mouseX, mouseY } = getMousePosition(event, canvas);\n const hoveredItem = findHoveredItem(mouseX, mouseY, data);\n if (hoveredItem) {\n updateTooltip(event, hoveredItem, canvas, tooltip);\n }\n else {\n canvas.style.cursor = \"default\";\n tooltip.style.display = \"none\";\n }\n}\n/**\n * Gets the mouse position relative to the canvas.\n */\nfunction getMousePosition(event, canvas) {\n const rect = canvas.getBoundingClientRect();\n return {\n mouseX: event.clientX - rect.left,\n mouseY: event.clientY - rect.top,\n };\n}\n/**\n * Finds the hovered item based on proximity to circles.\n */\nfunction findHoveredItem(mouseX, mouseY, data) {\n return (data.find((item) => Math.hypot(mouseX - item.x, mouseY - item.y) < item.radius) || null);\n}\n/**\n * Updates the tooltip and cursor based on the hovered item.\n */\nfunction updateTooltip(event, hovered, canvas, tooltip) {\n if ((hovered === null || hovered === void 0 ? void 0 : hovered.label) && (hovered === null || hovered === void 0 ? void 0 : hovered.value) && canvas && tooltip) {\n canvas.style.cursor = \"pointer\";\n tooltip.style.display = \"block\";\n tooltip.innerHTML = `<div>${hovered.label}<br>Value: ${hovered.value}</div>`;\n tooltip.style.left = `${event.pageX + 15}px`;\n tooltip.style.top = `${event.pageY - 30}px`;\n tooltip.style.zIndex = \"9999\";\n }\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/features/tooltip.ts?");
60
-
61
- /***/ }),
62
-
63
- /***/ "./src/main.ts":
64
- /*!*********************!*\
65
- !*** ./src/main.ts ***!
66
- \*********************/
67
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
68
-
69
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _services_chartService__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./services/chartService */ \"./src/services/chartService.ts\");\n\n// export { initializeChart };\n// @ts-ignore (Ignore TypeScript error for missing window prop)\nwindow.initializeChart = _services_chartService__WEBPACK_IMPORTED_MODULE_0__.initializeChart;\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/main.ts?");
70
-
71
- /***/ }),
72
-
73
- /***/ "./src/services/chartService.ts":
74
- /*!**************************************!*\
75
- !*** ./src/services/chartService.ts ***!
76
- \**************************************/
77
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
78
-
79
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ initializeChart: () => (/* binding */ initializeChart)\n/* harmony export */ });\n/* harmony import */ var _core_renderer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../core/renderer */ \"./src/core/renderer.ts\");\n/* harmony import */ var _utils_config__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../utils/config */ \"./src/utils/config.ts\");\n\n\n/**\n * Initializes the chart, but stops execution if no valid data is provided.\n */\nfunction initializeChart(config = {}) {\n var _a, _b;\n if (!config.data || config.data.length === 0) {\n console.warn(\"initializeChart: No valid data provided. Chart initialization aborted.\");\n return;\n }\n const safeConfig = Object.assign({ canvasContainerId: (_a = config.canvasContainerId) !== null && _a !== void 0 ? _a : \"chart-container\", data: (_b = config.data) !== null && _b !== void 0 ? _b : [] }, config);\n const finalConfig = (0,_utils_config__WEBPACK_IMPORTED_MODULE_1__.mergeConfig)(safeConfig);\n (0,_core_renderer__WEBPACK_IMPORTED_MODULE_0__.renderChart)(finalConfig);\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/services/chartService.ts?");
80
-
81
- /***/ }),
82
-
83
- /***/ "./src/services/renderService.ts":
84
- /*!***************************************!*\
85
- !*** ./src/services/renderService.ts ***!
86
- \***************************************/
87
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
88
-
89
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ getChartData: () => (/* binding */ getChartData)\n/* harmony export */ });\n/* harmony import */ var _constants_physics__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../constants/physics */ \"./src/constants/physics.ts\");\n\nfunction getChartData(config, canvas, ctx) {\n // Add padding constant at the top\n const CANVAS_PADDING = 5; // pixels of padding around all edges\n // Calculate available space considering padding\n const maxPossibleRadius = Math.min((canvas.width - CANVAS_PADDING * 2) / 2, (canvas.height - CANVAS_PADDING * 2) / 2);\n // Calculate radii based on available space\n // const availableWidth = canvas.width - CANVAS_PADDING * 2;\n // const availableHeight = canvas.height - CANVAS_PADDING * 2;\n // const canvasMinDimension = Math.min(availableWidth, availableHeight);\n // Add this code for crisp rendering\n const devicePixelRatio = window.devicePixelRatio || 1;\n const rect = canvas.getBoundingClientRect();\n // reduce width & height\n // const rectWidth = rect.width - (rect.width / 100) * 10;\n // const rectHeight = rect.height - (rect.height / 100) * 10;\n canvas.width = rect.width * devicePixelRatio;\n canvas.height = rect.height * devicePixelRatio;\n canvas.style.width = rect.width + \"px\";\n canvas.style.height = rect.height + \"px\";\n ctx.scale(devicePixelRatio, devicePixelRatio);\n // Modify center calculations\n const centerX = rect.width / 2;\n const centerY = rect.height / 2;\n const sortedData = [...config.data]\n .sort((a, b) => b.value - a.value)\n .map((item) => (Object.assign(Object.assign({}, item), { radius: 0, x: 0, y: 0, fixed: false })));\n const maxValue = sortedData[0].value;\n // Define radius range dynamically\n // const internalMinRadius = canvasMinDimension * 0.25; // 5% of smallest dimension\n // const internalMaxRadius = canvasMinDimension * 0.65; // 15% of smallest dimension\n const internalMaxRadius = Math.min(maxPossibleRadius * 1.0, // Use 80% of maximum possible space\n 100 // Absolute maximum\n );\n const internalMinRadius = Math.max(internalMaxRadius * 0.3, // Minimum 30% of max radius\n 30 // Absolute minimum\n );\n // Value-based radius calculation with padding consideration\n sortedData.forEach((item) => {\n // Calculate radius proportional to value and canvas size\n const valueRatio = item.value / maxValue;\n item.radius =\n internalMinRadius + valueRatio * (internalMaxRadius - internalMinRadius);\n // Ensure radius respects padding\n item.radius = Math.min(item.radius, (canvas.width - CANVAS_PADDING * 2) / 2, (canvas.height - CANVAS_PADDING * 2) / 2);\n });\n // Add aspect ratio preservation in bubble positioning\n sortedData.forEach((item, index) => {\n if (index === 0) {\n item.x = centerX;\n item.y = centerY;\n item.fixed = true;\n }\n else {\n const baseDist = sortedData[0].radius + item.radius + 3;\n // Replace with deterministic positioning using golden angle\n const goldenAngle = Math.PI * (3 - Math.sqrt(5)); // ~137.5 degrees\n // Calculate position with padding constraints\n const maxX = canvas.width - CANVAS_PADDING - item.radius;\n const maxY = canvas.height - CANVAS_PADDING - item.radius;\n item.x = Math.min(maxX, Math.max(CANVAS_PADDING + item.radius, centerX + Math.cos(goldenAngle * index) * baseDist));\n item.y = Math.min(maxY, Math.max(CANVAS_PADDING + item.radius, centerY + Math.sin(goldenAngle * index) * baseDist));\n item.fixed = false;\n }\n });\n // Physics simulation\n // Adjust physics parameters for tighter packing\n // Unified physics simulation and collision resolution\n for (let i = 0; i < _constants_physics__WEBPACK_IMPORTED_MODULE_0__.PHYSICS.iterations; i++) {\n // Main physics simulation\n sortedData.forEach((current, index) => {\n // Apply special handling for center bubble\n if (index === 0) {\n // Soft center positioning with spring-like behavior\n const dx = centerX - current.x;\n const dy = centerY - current.y;\n const dist = Math.hypot(dx, dy);\n // Only apply correction if significantly off-center\n if (dist > 2) {\n current.x += dx * _constants_physics__WEBPACK_IMPORTED_MODULE_0__.PHYSICS.centerDamping;\n current.y += dy * _constants_physics__WEBPACK_IMPORTED_MODULE_0__.PHYSICS.centerDamping;\n }\n return;\n }\n let dxTotal = 0;\n let dyTotal = 0;\n // 1. Boundary constraints\n const boundaryPadding = current.radius + CANVAS_PADDING;\n if (current.x < boundaryPadding) {\n dxTotal += (boundaryPadding - current.x) * _constants_physics__WEBPACK_IMPORTED_MODULE_0__.PHYSICS.boundaryForce;\n }\n else if (current.x > canvas.width - boundaryPadding) {\n dxTotal +=\n (canvas.width - boundaryPadding - current.x) * _constants_physics__WEBPACK_IMPORTED_MODULE_0__.PHYSICS.boundaryForce;\n }\n // 2. Bubble repulsion with tight spacing\n sortedData.forEach((other) => {\n if (current === other)\n return;\n // Add additional center attraction\n const dx = centerX - current.x;\n const dy = centerY - current.y;\n const distance = Math.hypot(dx, dy);\n // Value-based attraction strength (weaker for smaller values)\n const attractionStrength = 0.02 * (current.value / maxValue);\n current.x += (dx / distance) * attractionStrength;\n current.y += (dy / distance) * attractionStrength;\n });\n // 3. Strong center attraction with value-based weighting\n const dxCenter = centerX - current.x;\n const dyCenter = centerY - current.y;\n const centerDist = Math.hypot(dxCenter, dyCenter);\n const minCenterDist = sortedData[0].radius + current.radius + _constants_physics__WEBPACK_IMPORTED_MODULE_0__.PHYSICS.centerRadiusBuffer;\n const attraction = _constants_physics__WEBPACK_IMPORTED_MODULE_0__.PHYSICS.centerForce * (1 - Math.pow(current.value / maxValue, 0.3));\n // Value-based attraction strength\n const attractionStrength = _constants_physics__WEBPACK_IMPORTED_MODULE_0__.PHYSICS.centerAttraction *\n (1 - current.value / maxValue) *\n (1 - Math.min(1, centerDist / minCenterDist));\n current.x += dxCenter * attractionStrength;\n current.y += dyCenter * attractionStrength;\n });\n // Combined collision resolution\n sortedData.forEach((current, i) => {\n sortedData.forEach((other, j) => {\n if (i >= j)\n return;\n // Special handling for center bubble collisions\n if (i === 0 || j === 0) {\n const centerBubble = i === 0 ? current : other;\n const normalBubble = i === 0 ? other : current;\n const dx = normalBubble.x - centerBubble.x;\n const dy = normalBubble.y - centerBubble.y;\n const distance = Math.hypot(dx, dy);\n const minDistance = centerBubble.radius + normalBubble.radius + 2;\n if (distance < minDistance) {\n const overlap = minDistance - distance;\n const angle = Math.atan2(dy, dx);\n // Only move the normal bubble\n normalBubble.x += Math.cos(angle) * overlap * 0.7;\n normalBubble.y += Math.sin(angle) * overlap * 0.7;\n }\n return;\n }\n const dx = current.x - other.x;\n const dy = current.y - other.y;\n const distance = Math.hypot(dx, dy);\n const minDistance = current.radius + other.radius - 5; // Allow 2px overlap\n if (distance < minDistance) {\n const overlap = (minDistance - distance) * 0.3; // Gentle correction\n const angle = Math.atan2(dy, dx);\n // Mass-weighted adjustment\n const massRatio = other.radius / (current.radius + other.radius);\n if (!current.fixed) {\n current.x += Math.cos(angle) * overlap * massRatio;\n current.y += Math.sin(angle) * overlap * massRatio;\n }\n if (!other.fixed) {\n other.x -= Math.cos(angle) * overlap * (1 - massRatio);\n other.y -= Math.sin(angle) * overlap * (1 - massRatio);\n }\n }\n });\n });\n }\n // Modify boundary clamping to include center bubble\n sortedData.forEach((bubble) => {\n const clampedX = Math.max(CANVAS_PADDING + bubble.radius, Math.min(canvas.width - CANVAS_PADDING - bubble.radius, bubble.x));\n const clampedY = Math.max(CANVAS_PADDING + bubble.radius, Math.min(canvas.height - CANVAS_PADDING - bubble.radius, bubble.y));\n // Only update position if not fixed or moved significantly\n if (!bubble.fixed ||\n Math.hypot(bubble.x - clampedX, bubble.y - clampedY) > 2) {\n bubble.x = clampedX;\n bubble.y = clampedY;\n }\n });\n return sortedData;\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/services/renderService.ts?");
90
-
91
- /***/ }),
92
-
93
- /***/ "./src/utils/config.ts":
94
- /*!*****************************!*\
95
- !*** ./src/utils/config.ts ***!
96
- \*****************************/
97
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
98
-
99
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ DEFAULT_CONFIG: () => (/* binding */ DEFAULT_CONFIG),\n/* harmony export */ mergeConfig: () => (/* binding */ mergeConfig)\n/* harmony export */ });\n/**\n * Default configuration object.\n */\nconst DEFAULT_CONFIG = {\n colorMap: {},\n defaultBubbleColor: \"#3498db\",\n fontColor: \"#ffffff\",\n minRadius: 10,\n maxLines: 3,\n textWrap: true,\n isResizeCanvasOnWindowSizeChange: true,\n fontSize: 14,\n fontFamily: \"Arial\",\n showToolTip: true,\n};\n/**\n * Merges user config with defaults, ensuring `canvasContainerId` and `data` are required.\n */\nfunction mergeConfig(customConfig) {\n return Object.assign(Object.assign({}, DEFAULT_CONFIG), customConfig);\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/utils/config.ts?");
100
-
101
- /***/ }),
102
-
103
- /***/ "./src/utils/helper.ts":
104
- /*!*****************************!*\
105
- !*** ./src/utils/helper.ts ***!
106
- \*****************************/
107
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
108
-
109
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ getFontSize: () => (/* binding */ getFontSize)\n/* harmony export */ });\nfunction getFontSize(radius, defaultFontSize = 14) {\n return Math.min(defaultFontSize, radius / 2);\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/utils/helper.ts?");
110
-
111
- /***/ }),
112
-
113
- /***/ "./src/utils/validation.ts":
114
- /*!*********************************!*\
115
- !*** ./src/utils/validation.ts ***!
116
- \*********************************/
117
- /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
118
-
119
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ validateConfig: () => (/* binding */ validateConfig)\n/* harmony export */ });\n/**\n * Validates configuration and ensures required properties exist.\n */\nfunction validateConfig(config) {\n if (!config) {\n console.error(\"Invalid config object\");\n return false;\n }\n if (!Array.isArray(config.data) || config.data.length === 0) {\n console.error(\"Invalid or empty data array\");\n return false;\n }\n return true;\n}\n\n\n//# sourceURL=webpack://bubble-chart-js/./src/utils/validation.ts?");
120
-
121
- /***/ })
122
-
123
- /******/ });
124
- /************************************************************************/
125
- /******/ // The module cache
126
- /******/ var __webpack_module_cache__ = {};
127
- /******/
128
- /******/ // The require function
129
- /******/ function __webpack_require__(moduleId) {
130
- /******/ // Check if module is in cache
131
- /******/ var cachedModule = __webpack_module_cache__[moduleId];
132
- /******/ if (cachedModule !== undefined) {
133
- /******/ return cachedModule.exports;
134
- /******/ }
135
- /******/ // Create a new module (and put it into the cache)
136
- /******/ var module = __webpack_module_cache__[moduleId] = {
137
- /******/ // no module.id needed
138
- /******/ // no module.loaded needed
139
- /******/ exports: {}
140
- /******/ };
141
- /******/
142
- /******/ // Execute the module function
143
- /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
144
- /******/
145
- /******/ // Return the exports of the module
146
- /******/ return module.exports;
147
- /******/ }
148
- /******/
149
- /************************************************************************/
150
- /******/ /* webpack/runtime/define property getters */
151
- /******/ (() => {
152
- /******/ // define getter functions for harmony exports
153
- /******/ __webpack_require__.d = (exports, definition) => {
154
- /******/ for(var key in definition) {
155
- /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
156
- /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
157
- /******/ }
158
- /******/ }
159
- /******/ };
160
- /******/ })();
161
- /******/
162
- /******/ /* webpack/runtime/hasOwnProperty shorthand */
163
- /******/ (() => {
164
- /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
165
- /******/ })();
166
- /******/
167
- /******/ /* webpack/runtime/make namespace object */
168
- /******/ (() => {
169
- /******/ // define __esModule on exports
170
- /******/ __webpack_require__.r = (exports) => {
171
- /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
172
- /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
173
- /******/ }
174
- /******/ Object.defineProperty(exports, '__esModule', { value: true });
175
- /******/ };
176
- /******/ })();
177
- /******/
178
- /************************************************************************/
179
- /******/
180
- /******/ // startup
181
- /******/ // Load entry module and return exports
182
- /******/ // This entry module can't be inlined because the eval devtool is used.
183
- /******/ var __webpack_exports__ = __webpack_require__("./src/main.ts");
184
- /******/
185
- /******/ })()
186
- ;
@@ -1 +0,0 @@
1
- export declare function getWrappedLines(ctx: CanvasRenderingContext2D, text: string, maxLineWidth: number, maxAllowedLines: number | undefined, circleRadius: number, maxCharsPerWord?: number | undefined): string[];
package/dist/main.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,4 +0,0 @@
1
- export interface DataItem {
2
- label: string;
3
- value: number;
4
- }
package/src/canvas.ts DELETED
@@ -1,17 +0,0 @@
1
- /**
2
- * Creates and initializes the canvas inside the specified container.
3
- */
4
- export function createCanvas(containerId: string): HTMLCanvasElement | null {
5
- const canvasContainer = document.getElementById(containerId);
6
- if (!canvasContainer) {
7
- console.error(`Canvas container with ID '${containerId}' not found.`);
8
- return null;
9
- }
10
-
11
- const canvas = document.createElement("canvas") as HTMLCanvasElement;
12
- canvas.width = canvasContainer.offsetWidth;
13
- canvas.height = canvasContainer.offsetHeight;
14
- canvasContainer.appendChild(canvas);
15
-
16
- return canvas;
17
- }
@@ -1,10 +0,0 @@
1
- export const PHYSICS = {
2
- forceStrength: 0.015,
3
- iterations: 1000,
4
- damping: 0.55,
5
- boundaryForce: 0.02,
6
- centerForce: 0.12,
7
- centerAttraction: 0.6,
8
- centerDamping: 0.3,
9
- centerRadiusBuffer: 35,
10
- } as const;
@@ -1,110 +0,0 @@
1
- import { Configuration } from "../models/public/configuration";
2
- import { getWrappedLines } from "../features/textWrapper";
3
- import { createTooltipElement, handleMouseMove } from "../features/tooltip";
4
- import { validateConfig } from "../utils/validation";
5
- import { createCanvas } from "../canvas";
6
- import { getChartData } from "../services/renderService";
7
- import { getFontSize } from "../utils/helper";
8
-
9
- export function renderChart(config: Configuration) {
10
- if (!validateConfig(config)) return;
11
-
12
- // Create & setup canvas
13
- let canvas = createCanvas(config.canvasContainerId);
14
- if (!canvas) return;
15
-
16
- const ctx = canvas.getContext("2d");
17
-
18
- if (!ctx) {
19
- console.error("Invalid context");
20
- return;
21
- }
22
-
23
- const sortedData = getChartData(config, canvas, ctx);
24
-
25
- function draw() {
26
- if (!canvas || !ctx) {
27
- console.warn("canvas or ctx is not valid");
28
- return;
29
- }
30
-
31
- ctx.clearRect(0, 0, canvas.width, canvas.height);
32
-
33
- sortedData.forEach((item) => {
34
- const color = config.colorMap[item.label] || config.defaultBubbleColor;
35
-
36
- // Ensure radius is at least minRadius
37
- const radius = Math.max(item.radius, config.minRadius);
38
-
39
- ctx.beginPath();
40
- ctx.arc(item.x, item.y, radius, 0, Math.PI * 2);
41
- ctx.fillStyle = color;
42
- ctx.fill();
43
-
44
- ctx.strokeStyle = "black"; // Border color
45
- ctx.lineWidth = 0.25; // Border thickness
46
- ctx.stroke(); // Apply border
47
-
48
- // Text styling
49
- ctx.fillStyle = config.fontColor;
50
- const fontSize = getFontSize(radius, config.fontSize);
51
- ctx.font = `${fontSize}px ${config.fontFamily}`;
52
- ctx.textAlign = "center";
53
- ctx.textBaseline = "middle";
54
-
55
- const padding = 5; // Padding around text
56
- const maxWidth = radius * 1.5 - padding * 2; // Adjusted for padding
57
-
58
- if (config.textWrap) {
59
- // Calculate vertical position for lines
60
- const lineHeight = fontSize * 1.2;
61
-
62
- // Dynamically determine lines if maxLines is not set
63
- const lines = getWrappedLines(
64
- ctx,
65
- item.label,
66
- maxWidth,
67
- config.maxLines,
68
- radius
69
- );
70
- const startY = item.y - ((lines.length - 1) * lineHeight) / 2;
71
-
72
- lines.forEach((line, index) => {
73
- ctx.fillText(line, item.x, startY + index * lineHeight);
74
- });
75
- } else {
76
- ctx.fillText(item.label, item.x, item.y);
77
- }
78
- });
79
- }
80
-
81
- // Robust approach that handles resizing:
82
- function resizeCanvas() {
83
- const canvasContainer = document.getElementById(config.canvasContainerId);
84
- if (canvasContainer && canvas) {
85
- canvas.width = canvasContainer.offsetWidth;
86
- canvas.height = canvasContainer.offsetHeight;
87
- draw(); // Call your drawing function
88
- }
89
- }
90
-
91
- if (config.isResizeCanvasOnWindowSizeChange) {
92
- resizeCanvas(); // Initial resize
93
- window.addEventListener("resize", resizeCanvas); // Resize on window resize
94
- }
95
-
96
- // Initial draw
97
- draw();
98
-
99
- if (config.showToolTip) {
100
- const tooltip = createTooltipElement();
101
- let animationFrameId: number | null = null;
102
- canvas.addEventListener("mousemove", (event) => {
103
- if (animationFrameId) return; // Prevent excessive calls
104
- animationFrameId = requestAnimationFrame(() => {
105
- handleMouseMove(event, sortedData, canvas, tooltip);
106
- animationFrameId = null; // Reset after execution
107
- });
108
- });
109
- }
110
- }