UNPKG

survey-analytics

Version:

SurveyJS Dashboard is a UI component for visualizing and analyzing survey data. It interprets the form JSON schema to identify question types and renders collected responses using interactive charts and tables.

1,043 lines (1,025 loc) 695 kB
/*! surveyjs - SurveyJS Dashboard library v2.5.35 Copyright (c) 2015-2026 Devsoft Baltic OÜ - http://surveyjs.io/ License: SEE LICENSE IN LICENSE */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('survey-core'), require('plotly.js-dist-min')) : typeof define === 'function' && define.amd ? define(['exports', 'survey-core', 'plotly.js-dist-min'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SurveyAnalytics = {}, global.Survey, global.Plotly)); })(this, (function (exports, surveyCore, Plotly) { 'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; // Uncomment the line below if you create a custom dictionary // import { setupLocale } from "../localizationManager"; var englishStrings = { groupButton: "Group By Me", ungroupButton: "Ungroup By Me", selectButton: "Select Me", hideColumn: "Hide column", showColumn: "Show column", makePrivateColumn: "Make column private", makePublicColumn: "Make column public", moveToDetail: "Move to Detail", showAsColumn: "Show as Column", filterPlaceholder: "Search...", removeRows: "Remove rows", showLabel: "Show", entriesLabel: "entries", visualizer_text: "Texts in table", visualizer_wordcloud: "Wordcloud", visualizer_histogram: "Histogram", visualizer_number: "Average", visualizer_choices: "Table", visualizer_selectBase: "Chart", visualizer_matrix: "Chart", chartType_bar: "Bar", chartType_vbar: "Vertical Bar", chartType_stackedbar: "Stacked Bar", chartType_doughnut: "Doughnut", chartType_pie: "Pie", chartType_scatter: "Scatter", chartType_gauge: "Gauge", chartType_bullet: "Bullet", chartType_line: "Line", chartType_radar: "Radar", hideButton: "Hide", makePrivateButton: "Make private", makePublicButton: "Make public", showButton: "Show", filter: "Filter", resetFilter: "Reset Filter", changeLocale: "Change Locale", clearButton: "Clear", addElement: "Choose question to show...", defaultOrder: "Default Order", ascOrder: "Ascending", descOrder: "Descending", showMinorColumns: "Show minor columns", actionsColumn: "Actions", otherCommentTitle: "Other items and comments", showPercentages: "Show percentages", hidePercentages: "Hide percentages", pdfDownloadCaption: "PDF", xlsxDownloadCaption: "Excel", csvDownloadCaption: "CSV", saveDiagramAsPNG: "Download plot as a PNG file", hideEmptyAnswers: "Hide empty answers", showEmptyAnswers: "Show empty answers", "topNValueText-1": "All answers", "topNValueText5": "Top 5 answers", "topNValueText10": "Top 10 answers", "topNValueText20": "Top 20 answers", hideMissingAnswers: "Hide missing answers", showMissingAnswers: "Show missing answers", missingAnswersLabel: "Missing answers", noVisualizerForQuestion: "This question type is not visualized yet", noResults: "There are no results yet", showPerValues: "Per Values", showPerColumns: "Per Columns", answer: "Answer", correctAnswer: "Correct answer: ", percent: "Percent", percentage: "Percentage", statistics_chart: "Chart", responses: "Responses", visualizer_nps: "NPS", visualizer_boolean: "Chart", visualizer_options: "Table", npsScore: "NPS", npsPromoters: "Promoters", npsPassives: "Passives", npsDetractors: "Detractors", axisXSelectorTitle: "Category (X Axis):", axisYSelectorTitle: "Legend (Series):", axisXAlternativeSelectorTitle: "Segments:", axisYAlternativeSelectorTitle: "Groups:", notSelected: "Not selected", intervalMode_default: "Default", intervalMode_decades: "Decades", intervalMode_years: "Years", intervalMode_quarters: "Quarters", intervalMode_months: "Months", intervalMode_days: "Days", intervalMode_custom: "Custom", intervalMode_auto: "Auto", intervalModeTitle: "Intervals:", noRunningTotals: "Show individual values", runningTotals: "Show running totals", groupDateSeries: "Compare periods", ungroupDateSeries: "View timeline", noneAggregateText: "None", selectAggregateText: "Aggregate:", groupedYearsAxisTitle: "Year ends with", license: "To use the Dashboard library in your application, a <a href='https://surveyjs.io/licensing' target='_blank'>developer license</a> is required. If you have an active license, please <a href='https://surveyjs.io/remove-alert-banner' target='_blank'>set up your license key</a> and ensure you're using the <a href='https://surveyjs.io/stay-updated/release-notes' target='_blank'>latest version</a>.", license2: "This banner appears because your maintenance subscription for the Dashboard library expired on {date}. You may continue using <a href='https://surveyjs.io/stay-updated/release-notes' target='_blank'>all versions released up to that date</a>. To remove this banner in the latest version, please <a href='https://surveyjs.io/manage#license-manager' target='_blank'>renew your subscription</a> and <a href='https://surveyjs.io/remove-alert-banner' target='_blank'>set up a new license key</a>." }; // Uncomment the line below if you create a custom dictionary. // Replace "en" with a custom locale code (for example, "fr" or "de"), // Replace `englishStrings` with the name of the variable that contains the custom dictionary. // setupLocale({ localeCode: "en", strings: englishStrings }); var localization = { currentLocaleValue: "", defaultLocaleValue: "en", locales: {}, localeNames: {}, supportedLocales: [], get currentLocale() { return this.currentLocaleValue === this.defaultLocaleValue ? "" : this.currentLocaleValue; }, set currentLocale(val) { this.currentLocaleValue = val; }, get defaultLocale() { return this.defaultLocaleValue; }, set defaultLocale(val) { this.defaultLocaleValue = val; }, setupLocale: function (loc, strings) { this.locales[loc] = strings; }, getLocaleName: function (loc, inEnglish) { if (inEnglish === void 0) { inEnglish = false; } var res = !inEnglish ? this.localeNames[loc] : ""; return res || surveyCore.surveyLocalization.getLocaleName(loc, inEnglish); }, getString: function (strName) { var loc = this.currentLocale ? this.locales[this.currentLocale] : this.locales[this.defaultLocale]; if (!loc || !loc[strName]) loc = this.locales[this.defaultLocale]; var result = loc[strName]; if (result === undefined) { result = this.locales["en"][strName] || strName; } return result; }, getLocales: function () { var res = []; res.push(""); if (this.supportedLocales && this.supportedLocales.length > 0) { for (var i = 0; i < this.supportedLocales.length; i++) { res.push(this.supportedLocales[i]); } } else { for (var key in this.locales) { res.push(key); } } res.sort(); return res; } }; var surveyStrings = englishStrings; function setupLocale(localeConfig) { var loc = localeConfig.localeCode; localization.setupLocale(loc, localeConfig.strings); if (localeConfig.nativeName) { localization.localeNames[loc] = localeConfig.nativeName; } } setupLocale({ localeCode: "en", strings: englishStrings, nativeName: "English" }); var DocumentHelper = /** @class */ (function () { function DocumentHelper() { } DocumentHelper.createSelector = function (options, isSelected, handler, title) { var selectWrapper = document.createElement("div"); selectWrapper.className = "sa-question__select-wrapper"; var titleElement = DocumentHelper.createElement("span", "sa-question__select-title"); var select = document.createElement("select"); select.className = "sa-question__select"; var updateTitle = function () { var titleText = !!title && (typeof title == "string" ? title : title()); titleElement.innerText = titleText; if (!!titleText) { selectWrapper.insertBefore(titleElement, select); } else if (titleElement.parentElement === selectWrapper) { selectWrapper.removeChild(titleElement); } }; select.onchange = handler; selectWrapper.appendChild(select); var updateOptions = function () { select.innerHTML = ""; var optionsSource = options || []; var optionItems = Array.isArray(optionsSource) ? optionsSource : optionsSource(); optionItems.forEach(function (option) { var optionElement = DocumentHelper.createElement("option", "", { value: option.value, text: option.text, selected: isSelected(option), }); select.appendChild(optionElement); }); }; selectWrapper["__updateSelect"] = function () { updateTitle(); updateOptions(); }; selectWrapper["__updateSelect"](); return selectWrapper; }; DocumentHelper.createButton = function (handler, text, className) { if (text === void 0) { text = ""; } if (className === void 0) { className = "sa-toolbar__button"; } var button = DocumentHelper.createElement("span", className, { innerText: text, onclick: handler, }); return button; }; DocumentHelper.createElement = function (tagName, className, attrs) { if (className === void 0) { className = ""; } var el = document.createElement(tagName); if (!!className) { el.className = className; } if (!!attrs) { Object.keys(attrs).forEach(function (key) { el[key] = attrs[key]; }); } return el; }; DocumentHelper.createSvgElement = function (path) { var svgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg"); var useElem = document.createElementNS("http://www.w3.org/2000/svg", "use"); useElem.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#sa-svg-" + path); svgElem.appendChild(useElem); return svgElem; }; DocumentHelper.createSvgButton = function (path) { var btn = (DocumentHelper.createElement("button", "sa-table__svg-button")); btn.appendChild(DocumentHelper.createSvgElement(path)); return btn; }; DocumentHelper.createSvgToggleButton = function (svgPath1, svPpath2, text1, text2, handler1, handler2, state, className) { if (state === void 0) { state = "first"; } if (className === void 0) { className = "sa-toolbar__button sa-toolbar__svg-button"; } var svg1 = DocumentHelper.createSvgElement(svgPath1); var svg2 = DocumentHelper.createSvgElement(svPpath2); var button = DocumentHelper.createElement("button", className); var toggle = function (e) { if (state === "first") { state = "second"; button.title = text2; button.removeChild(svg1); button.appendChild(svg2); handler2(e); } else if (state === "second") { state = "first"; button.title = text1; button.removeChild(svg2); button.appendChild(svg1); handler1(e); } }; if (state === "first") { button.title = text1; button.appendChild(svg1); } else if ((state = "second")) { button.title = text2; button.appendChild(svg2); } button.onclick = toggle; return button; }; DocumentHelper.createInput = function (className, placeholder, defaultValue) { if (placeholder === void 0) { placeholder = ""; } if (defaultValue === void 0) { defaultValue = ""; } var el = DocumentHelper.createElement("input", className, { placeholder: placeholder, defaultValue: defaultValue, }); return el; }; return DocumentHelper; }()); function getLicenseText() { var d = !!surveyCore.glc ? surveyCore.glc(1) : false; if (!!d && d.toLocaleDateString) return localization.getString("license2").replace("{date}", d.toLocaleDateString()); return localization.getString("license"); } function createCommercialLicenseLink() { var container = DocumentHelper.createElement("div", "sa-commercial"); var link = DocumentHelper.createElement("div", "sa-commercial__text"); var containerSpan = DocumentHelper.createElement("span", ""); var textSpan = DocumentHelper.createElement("span", "sa-commercial__product", { innerHTML: getLicenseText() }); container.appendChild(link).appendChild(containerSpan); containerSpan.appendChild(textSpan); return container; } function createLoadingIndicator() { var container = DocumentHelper.createElement("div", "sa-data-loading-indicator-panel"); var loadingIndicator = DocumentHelper.createElement("div", "sa-data-loading-indicator"); loadingIndicator.innerHTML = "\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <g clip-path=\"url(#clip0_17928_11482)\">\n <path d=\"M32 64C14.36 64 0 49.65 0 32C0 14.35 14.36 0 32 0C49.64 0 64 14.35 64 32C64 49.65 49.64 64 32 64ZM32 4C16.56 4 4 16.56 4 32C4 47.44 16.56 60 32 60C47.44 60 60 47.44 60 32C60 16.56 47.44 4 32 4Z\" fill=\"#E5E5E5\"></path>\n <path d=\"M53.2101 55.2104C52.7001 55.2104 52.1901 55.0104 51.8001 54.6204C51.0201 53.8404 51.0201 52.5704 51.8001 51.7904C57.0901 46.5004 60.0001 39.4704 60.0001 31.9904C60.0001 24.5104 57.0901 17.4804 51.8001 12.1904C51.0201 11.4104 51.0201 10.1404 51.8001 9.36039C52.5801 8.58039 53.8501 8.58039 54.6301 9.36039C60.6701 15.4004 64.0001 23.4404 64.0001 31.9904C64.0001 40.5404 60.6701 48.5704 54.6301 54.6204C54.2401 55.0104 53.7301 55.2104 53.2201 55.2104H53.2101Z\" fill=\"#19B394\"></path>\n </g>\n <defs>\n <clipPath id=\"clip0_17928_11482\">\n <rect width=\"64\" height=\"64\" fill=\"white\"></rect>\n </clipPath>\n </defs>\n </svg>\n "; container.appendChild(loadingIndicator); return container; } var DataHelper = /** @class */ (function () { function DataHelper() { } DataHelper.zipArrays = function () { var arrays = []; for (var _i = 0; _i < arguments.length; _i++) { arrays[_i] = arguments[_i]; } var zipArray = []; var _loop_1 = function (i) { zipArray[i] = []; arrays.forEach(function (arr) { zipArray[i].push(arr[i]); }); }; for (var i = 0; i < arrays[0].length; i++) { _loop_1(i); } return zipArray; }; DataHelper.unzipArrays = function (zipArray) { var arrays = []; zipArray.forEach(function (value, i) { value.forEach(function (val, j) { if (!arrays[j]) arrays[j] = []; arrays[j][i] = val; }); }); return arrays; }; DataHelper.sortDictionary = function (keys, values, desc) { var dictionary = this.zipArrays(keys, values); var comparator = function (a, b, asc) { if (asc === void 0) { asc = true; } var result = a[1] < b[1] ? 1 : a[1] == b[1] ? 0 : -1; return asc ? result : result * -1; }; dictionary.sort(function (a, b) { return desc ? comparator(a, b, false) : comparator(a, b); }); var keysAndValues = this.unzipArrays(dictionary); return { keys: keysAndValues[0], values: keysAndValues[1] }; }; DataHelper.toPercentage = function (value, maxValue) { return (value / maxValue) * 100; }; return DataHelper; }()); function toPrecision(value, precision) { if (precision === void 0) { precision = 2; } var base = Math.pow(10, precision); return Math.round(base * value) / base; } var DataProvider = /** @class */ (function () { function DataProvider(_data) { if (_data === void 0) { _data = []; } this._data = _data; this.filterValues = {}; /** * Fires when data has been changed. */ this.onFilterChanged = new surveyCore.Event(); /** * Fires when data has been changed. */ this.onDataChanged = new surveyCore.Event(); } Object.defineProperty(DataProvider.prototype, "data", { get: function () { if (Array.isArray(this._data)) { return this._data; } return undefined; }, set: function (data) { if (Array.isArray(data)) { this._data = [].concat(data); } else { this._data = data; } this.raiseDataChanged(); }, enumerable: false, configurable: true }); Object.defineProperty(DataProvider.prototype, "dataFn", { get: function () { if (typeof this._data === "function") { return this._data; } return undefined; }, enumerable: false, configurable: true }); Object.defineProperty(DataProvider.prototype, "filteredData", { get: function () { var _this = this; if (this._filteredData === undefined) { var filterKeys = Object.keys(this.filterValues); if (filterKeys.length > 0) { this._filteredData = this.data.filter(function (item) { return !Object.keys(_this.filterValues).some(function (key) { var filterValue = _this.filterValues[key]; var filterValueType = typeof filterValue; var questionValue = item[key]; if (Array.isArray(questionValue)) { if (filterValueType === "object") { return !questionArrayValueContainsValue(questionValue, filterValue); } else { return questionValue.indexOf(filterValue) == -1; } } if (typeof questionValue === "object") { if (filterValueType !== "object") return true; return !questionValueContainsValue(questionValue, filterValue); } var seriesValue = item[DataProvider.seriesMarkerKey]; if (!!seriesValue && filterValueType === "object") { return questionValue !== filterValue[seriesValue]; } if (filterValueType === "object" && filterValue.start !== undefined && filterValue.end !== undefined) { var continioiusValue = typeof questionValue === "number" ? questionValue : Date.parse(questionValue); if (isNaN(continioiusValue)) { continioiusValue = parseFloat(questionValue); if (isNaN(continioiusValue)) { return true; } } return continioiusValue < filterValue.start || continioiusValue >= filterValue.end; } return item[key] !== _this.filterValues[key]; }); }); } else { this._filteredData = this.data; } } return this._filteredData; }, enumerable: false, configurable: true }); /** * Sets filter by question name and value. */ DataProvider.prototype.setFilter = function (questionName, selectedValue) { var filterChanged = true; if (selectedValue !== undefined) { filterChanged = this.filterValues[questionName] !== selectedValue; if (filterChanged) { this.filterValues[questionName] = selectedValue; } } else { filterChanged = this.filterValues[questionName] !== undefined; if (filterChanged) { delete this.filterValues[questionName]; } } if (filterChanged) { this.raiseFilterChanged(questionName, selectedValue); this.raiseDataChanged(); } }; /** * Resets filter. */ DataProvider.prototype.resetFilter = function () { var _this = this; if (Object.keys(this.filterValues).length === 0) { return; } Object.keys(this.filterValues).forEach(function (key) { return delete _this.filterValues[key]; }); this.raiseFilterChanged(); this.raiseDataChanged(); }; DataProvider.prototype.raiseFilterChanged = function (questionName, selectedValue) { if (!this.onFilterChanged.isEmpty) { this.onFilterChanged.fire(this, { questionName: questionName, selectedValue: selectedValue }); } }; DataProvider.prototype.raiseDataChanged = function (questionName) { this._filteredData = undefined; if (!this.onDataChanged.isEmpty) { this.onDataChanged.fire(this, { questionName: questionName }); } }; DataProvider.prototype.getFilters = function () { var _this = this; return Object.keys(this.filterValues).map(function (key) { return ({ field: key, type: "=", value: _this.filterValues[key] }); }); }; DataProvider.prototype.fixDropdownData = function (dataNames) { (this.data || []).forEach(function (dataItem) { var rawDataItem = dataItem[dataNames[0]]; if (!!rawDataItem && typeof rawDataItem === "object" && !Array.isArray(rawDataItem)) { var arrayData_1 = []; Object.keys(rawDataItem).forEach(function (key) { var nestedDataItem = Object.assign({}, rawDataItem[key]); nestedDataItem[DataProvider.seriesMarkerKey] = key; arrayData_1.push(nestedDataItem); }); dataItem[dataNames[0]] = arrayData_1; } }); }; DataProvider.seriesMarkerKey = "__sa_series_name"; return DataProvider; }()); function questionArrayValueContainsValue(questionValues, filterValue) { for (var i = 0; i < questionValues.length; i++) { if (questionValueContainsValue(questionValues[i], filterValue)) { return true; } } return false; } function questionValueContainsValue(questionValue, filterValue) { var questionValueKeys = Object.keys(questionValue); var filterValueKeys = Object.keys(filterValue); if (filterValueKeys.length > questionValueKeys.length) return false; for (var _i = 0, filterValueKeys_1 = filterValueKeys; _i < filterValueKeys_1.length; _i++) { var key = filterValueKeys_1[_i]; if (filterValue[key] != questionValue[key]) return false; } return true; } /** * An object with methods used to register and unregister visualizers for individual question types. * * [View Demo](https://surveyjs.io/dashboard/examples/custom-survey-data-visualizer/ (linkStyle)) */ var VisualizationManager = /** @class */ (function () { function VisualizationManager() { } /** * Registers a visualizer for a specified question type. * * [View Demo](https://surveyjs.io/dashboard/examples/custom-survey-data-visualizer/ (linkStyle)) * @param questionType A question [type](https://surveyjs.io/form-library/documentation/api-reference/question#getType). * @param constructor A function that returns a visualizer constructor to register. * @param index A zero-based index that specifies the visualizer's position in the visualizer list for the specified question type. Pass `0` to insert the visualizer at the beginning of the list and use it by default. If `index` is not specified, the visualizer is added to the end of the list. */ VisualizationManager.registerVisualizer = function (questionType, constructor, index) { if (index === void 0) { index = Number.MAX_VALUE; } var visualizers = VisualizationManager.vizualizers[questionType]; if (!visualizers) { visualizers = []; VisualizationManager.vizualizers[questionType] = visualizers; } visualizers.push({ ctor: constructor, index: index }); }; /** * Unregisters a visualizer for a specified question type. * @param questionType A question [type](https://surveyjs.io/form-library/documentation/api-reference/question#getType). * @param constructor A function that returns a visualizer constructor to unregister. */ VisualizationManager.unregisterVisualizer = function (questionType, constructor) { var questionTypes = [questionType]; if (!questionType) { questionTypes = Object.keys(VisualizationManager.vizualizers); } questionTypes.forEach(function (qType) { if (constructor) { var visualizers = VisualizationManager.vizualizers[qType]; if (!!visualizers) { var vDescr = visualizers.filter(function (v) { return v.ctor === constructor; })[0]; if (!!vDescr) { var index = visualizers.indexOf(vDescr); if (index !== -1) { visualizers.splice(index, 1); } } } } else { VisualizationManager.vizualizers[qType] = []; } }); }; /** * @deprecated Call the [`unregisterVisualizer()`](https://surveyjs.io/dashboard/documentation/api-reference/visualizationmanager#unregisterVisualizer) method instead. * @param constructor A function that returns a visualizer constructor to unregister. */ VisualizationManager.unregisterVisualizerForAll = function (constructor) { VisualizationManager.unregisterVisualizer(undefined, constructor); }; /** * Returns all visualizer constructors for a specified question type. * @param visualizerType A question [type](https://surveyjs.io/form-library/documentation/api-reference/question#getType). */ VisualizationManager.getVisualizersByType = function (visualizerType, fallbackVisualizerType) { var vDescrs = VisualizationManager.vizualizers[visualizerType]; if (!!fallbackVisualizerType && (!vDescrs || vDescrs.length == 0)) { vDescrs = VisualizationManager.vizualizers[fallbackVisualizerType]; } if (!vDescrs) { if (VisualizationManager.defaultVisualizer.suppressVisualizerStubRendering) { return []; } return [VisualizationManager.defaultVisualizer]; } vDescrs = [].concat(vDescrs); vDescrs.sort(function (v1, v2) { return v1.index - v2.index; }); return vDescrs.map(function (v) { return v.ctor; }); }; /** * Returns a constructor for an alternative visualizer selector. * @see registerAltVisualizerSelector */ VisualizationManager.getAltVisualizerSelector = function () { return VisualizationManager.alternativesVisualizer || VisualizationManager.defaultVisualizer; }; /** * Registers an alternative visualizer selector. * @param constructor A function that returns a constructor for an alternative visualizer selector. */ VisualizationManager.registerAltVisualizerSelector = function (constructor) { VisualizationManager.alternativesVisualizer = constructor; }; VisualizationManager.getPivotVisualizerConstructor = function () { return VisualizationManager.pivotVisualizer || VisualizationManager.defaultVisualizer; }; VisualizationManager.registerPivotVisualizer = function (constructor) { VisualizationManager.pivotVisualizer = constructor; }; VisualizationManager.defaultVisualizer = undefined; VisualizationManager.alternativesVisualizer = undefined; VisualizationManager.pivotVisualizer = undefined; VisualizationManager.vizualizers = {}; return VisualizationManager; }()); /** * An object that allows you to create individual visualizers without creating a [visualization panel](https://surveyjs.io/dashboard/documentation/api-reference/visualizationpanel). */ var VisualizerFactory = /** @class */ (function () { function VisualizerFactory() { } /** * Creates a visualizer for a single question. * * ```js * import { VisualizerFactory } from "survey-analytics"; * * const visualizer = new VisualizerFactory.createVisualizer( * question, * data, * options * ); * * visualizer.render("containerId") * ``` * * If a question has more than one [registered](https://surveyjs.io/dashboard/documentation/api-reference/visualizationmanager#registerVisualizer) visualizer, users can switch between them using a drop-down menu. * @param question A question for which to create a visualizer. * @param data A data array with survey results to be visualized. * @param options An object with any custom properties you need within the visualizer. */ VisualizerFactory.createVisualizer = function (question, data, options) { var type = question.getType(); var creators = []; var questionForCreator = question; var optionsForCreator = Object.assign({}, options); if (type === "text" && question.inputType) { creators = VisualizationManager.getVisualizersByType(question.inputType, type); } else { var fallbackType = undefined; if (question instanceof surveyCore.QuestionCustomModel) { fallbackType = question.getDynamicType(); // questionForCreator = question.contentQuestion; } else if (question instanceof surveyCore.QuestionCompositeModel) { fallbackType = "composite"; } creators = VisualizationManager.getVisualizersByType(type, fallbackType); } var visualizers = creators.map(function (creator) { return new creator(questionForCreator, data, optionsForCreator); }); if (visualizers.length > 1) { var alternativesVisualizerConstructor = VisualizationManager.getAltVisualizerSelector(); var visualizer = new alternativesVisualizerConstructor(visualizers, questionForCreator, data, optionsForCreator); return visualizer; } return visualizers[0]; }; return VisualizerFactory; }()); function defaultStatisticsCalculator(data, dataInfo) { var dataNames = dataInfo.dataNames; var statistics = []; var values = dataInfo.getValues(); var valuesIndex = {}; values.forEach(function (val, index) { valuesIndex[val] = index; }); var processMissingAnswers = values.indexOf(undefined) !== -1; var series = dataInfo.getSeriesValues(); var seriesIndex = {}; series.forEach(function (val, index) { seriesIndex[val] = index; }); var seriesLength = series.length || 1; for (var i = 0; i < dataNames.length; ++i) { var dataNameStatistics = new Array(); for (var j = 0; j < seriesLength; ++j) { dataNameStatistics.push(new Array(values.length).fill(0)); } statistics.push(dataNameStatistics); } var getValueIndex = function (val) { if (val !== null && typeof val === "object") return valuesIndex[val.value]; return valuesIndex[val]; }; var processDataRow = function (dataRow, dataName, index) { var rowValue = dataRow[dataName]; if (rowValue !== undefined || processMissingAnswers) { var rowValues = Array.isArray(rowValue) ? rowValue : [rowValue]; if (series.length > 0) { var rowName = dataRow[DataProvider.seriesMarkerKey]; if (rowName !== undefined) { // Series are labelled by seriesMarkerKey in row data var seriesNo_1 = seriesIndex[rowName] || 0; rowValues.forEach(function (val) { var valIndex = getValueIndex(val); statistics[index][seriesNo_1][valIndex]++; }); } else { // Series are the keys in question value (matrix question) // TODO: think about the de-normalization and combine with the previous case rowValues.forEach(function (val) { series.forEach(function (seriesName) { if (val[seriesName] !== undefined) { var seriesNo_2 = seriesIndex[seriesName] || 0; var values_1 = Array.isArray(val[seriesName]) ? val[seriesName] : [val[seriesName]]; values_1.forEach(function (value) { var valIndex = getValueIndex(value); statistics[index][seriesNo_2][valIndex]++; }); } }); }); } } else { // No series rowValues.forEach(function (val) { var valIndex = getValueIndex(val); statistics[0][0][valIndex]++; }); } } }; data.forEach(function (dataRow) { var nestedDataRows = getNestedDataRows(dataRow, dataInfo); nestedDataRows.forEach(function (nestedDataRow) { dataNames.forEach(function (dataName, index) { processDataRow(nestedDataRow, dataName, index); }); }); }); return dataInfo.dataNames.length > 1 ? statistics : statistics[0]; } function histogramStatisticsCalculator(data, intervals, dataInfo, aggregateDataNames) { if (aggregateDataNames === void 0) { aggregateDataNames = []; } var seriesValues = dataInfo.getSeriesValues(); var statistics = []; if (seriesValues.length === 0) { seriesValues.push(""); } for (var i = 0; i < seriesValues.length; ++i) { statistics.push(intervals.map(function (i) { return 0; })); data[seriesValues[i]].forEach(function (dataValue) { var _loop_1 = function (j) { if (intervals[j].start <= dataValue.continuous && (dataValue.continuous < intervals[j].end || j == intervals.length - 1)) { if (aggregateDataNames.length > 0) { aggregateDataNames.forEach(function (aggregateDataName) { var aggregateDataValue = dataValue.row[aggregateDataName]; var numberValue = parseFloat(aggregateDataValue); if (aggregateDataValue !== undefined && !isNaN(numberValue)) { statistics[i][j] += numberValue; } }); } else { statistics[i][j]++; } return "break"; } }; for (var j = 0; j < intervals.length; ++j) { var state_1 = _loop_1(j); if (state_1 === "break") break; } }); } return statistics; } function mathStatisticsCalculator(data, dataInfo) { var resultMin = Number.MAX_VALUE, resultMax = -Number.MAX_VALUE, resultAverage = 0; var actualAnswerCount = 0; data.forEach(function (dataRow) { var nestedDataRows = getNestedDataRows(dataRow, dataInfo); nestedDataRows.forEach(function (nestedDataRow) { var answerData = nestedDataRow[dataInfo.dataNames[0]]; if (answerData !== undefined) { var questionValue = +answerData; actualAnswerCount++; resultAverage += questionValue; if (resultMin > questionValue) { resultMin = questionValue; } if (resultMax < questionValue) { resultMax = questionValue; } } }); }); if (actualAnswerCount > 0) { resultAverage = resultAverage / actualAnswerCount; } resultAverage = Math.ceil(resultAverage * 100) / 100; return [resultAverage, resultMin, resultMax]; } function getNestedDataRows(dataRow, dataInfo) { var nestedDataRows = []; if (!dataInfo.dataPath) { nestedDataRows = [dataRow]; } else { if (dataRow[dataInfo.dataPath] === undefined) return []; if (typeof dataRow[dataInfo.dataPath] !== "object") return []; if (Array.isArray(dataRow[dataInfo.dataPath])) { nestedDataRows = dataRow[dataInfo.dataPath]; } else { nestedDataRows = [dataRow[dataInfo.dataPath]]; } } return nestedDataRows; } var PostponeHelper = /** @class */ (function () { function PostponeHelper() { } PostponeHelper.postpone = function (fn, timeout) { if (PostponeHelper.postponeFunction) { return PostponeHelper.postponeFunction(fn, timeout); } else { return setTimeout(fn, timeout); } }; return PostponeHelper; }()); /** * A base object for all visualizers. Use it to implement a custom visualizer. * * Constructor parameters: * * - `question`: [`Question`](https://surveyjs.io/form-library/documentation/api-reference/question)\ * A survey question to visualize. * - `data`: `Array<any>`\ * Survey results. * - `options`\ * An object with the following properties: * - `dataProvider`: `DataProvider`\ * A data provider for this visualizer. * - `renderContent`: `(contentContainer: HTMLElement, visualizer: VisualizerBase) => void`\ * A function that renders the visualizer's HTML markup. Append the markup to `contentContainer`. * - `survey`: [`SurveyModel`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model)\ * Pass a `SurveyModel` instance if you want to use locales from the survey JSON schema. * - `seriesValues`: `Array<string>`\ * Series values used to group data. * - `seriesLabels`: `Array<string>`\ * Series labels to display. If this property is not set, `seriesValues` are used as labels. * - `type`: `string`\ * *(Optional)* The visualizer's type. * * [View Demo](https://surveyjs.io/dashboard/examples/how-to-plot-survey-data-in-custom-bar-chart/ (linkStyle)) */ var VisualizerBase = /** @class */ (function () { function VisualizerBase(question, data, options, _type) { if (options === void 0) { options = {}; } var _this = this; var _a; this.question = question; this.options = options; this._type = _type; this._showToolbar = true; this._footerVisualizer = undefined; this._dataProvider = undefined; this._getDataCore = undefined; this.labelTruncateLength = 27; this.haveCommercialLicense = false; this.renderResult = undefined; this.toolbarContainer = undefined; this.headerContainer = undefined; this.contentContainer = undefined; this.footerContainer = undefined; this._supportSelection = false; this._chartAdapter = undefined; this._footerIsCollapsed = undefined; /** * An event that is raised after the visualizer's content is rendered. * * Parameters: * * - `sender`: `VisualizerBase`\ * A `VisualizerBase` instance that raised the event. * * - `options.htmlElement`: `HTMLElement`\ * A page element with the visualizer's content. * @see render