diff --git a/benchexec/tablegenerator/react-table/src/components/StatisticsTable.js b/benchexec/tablegenerator/react-table/src/components/StatisticsTable.js
index 2e86c524a..156c3f902 100644
--- a/benchexec/tablegenerator/react-table/src/components/StatisticsTable.js
+++ b/benchexec/tablegenerator/react-table/src/components/StatisticsTable.js
@@ -8,23 +8,23 @@
import React, { useState, useMemo, useEffect } from "react";
import {
- useTable,
- useFilters,
- useResizeColumns,
- useFlexLayout,
+ useTable,
+ useFilters,
+ useResizeColumns,
+ useFlexLayout,
} from "react-table";
import { useSticky } from "react-table-sticky";
import {
- createRunSetColumns,
- SelectColumnsButton,
- StandardColumnHeader,
+ createRunSetColumns,
+ SelectColumnsButton,
+ StandardColumnHeader,
} from "./TableComponents";
import { computeStats, statisticsRows } from "../utils/stats.js";
import {
- determineColumnWidth,
- isNumericColumn,
- isNil,
- getHiddenColIds,
+ determineColumnWidth,
+ isNumericColumn,
+ isNil,
+ getHiddenColIds,
} from "../utils/utils";
const isTestEnv = process.env.NODE_ENV === "test";
@@ -47,8 +47,8 @@ const StatisticsTable = ({
stats: defaultStats,
filtered = false,
}) => {
- // We want to skip stat calculation in a test environment if not
- // specifically wanted (signaled by a passed onStatsReady callback function)
+ // We want to skip stat calculation in a test environment if not
+ // specifically wanted (signaled by a passed onStatsReady callback function)
const skipStats = isTestEnv && !onStatsReady;
// When filtered, initialize with empty statistics until computed statistics
@@ -58,247 +58,256 @@ const StatisticsTable = ({
// we want to trigger a re-calculation of our stats whenever data changes.
useEffect(() => {
- const updateStats = async () => {
- if (filtered) {
- const newStats = await computeStats({
- tools,
- tableData,
- stats: defaultStats,
- });
- setStats(newStats);
- } else {
- setStats(defaultStats);
- }
- if (onStatsReady) {
- onStatsReady();
- }
+ const updateStats = async() => {
+ if (filtered) {
+ const newStats = await computeStats({
+ tools,
+ tableData,
+ stats: defaultStats,
+ });
+ setStats(newStats);
+ } else {
+ setStats(defaultStats);
+ }
+ if (onStatsReady) {
+ onStatsReady();
+ }
};
if (!skipStats) {
- updateStats(); // necessary such that hook is not async
+ updateStats(); // necessary such that hook is not async
}
- }, [tools, tableData, onStatsReady, skipStats, defaultStats, filtered]);
+}, [tools, tableData, onStatsReady, skipStats, defaultStats, filtered]);
- const renderTableHeaders = (headerGroups) => (
-
- {headerGroups.map((headerGroup) => (
-
- {headerGroup.headers.map((header) => (
-
( <
+ div className = "table-header" > {
+ headerGroups.map((headerGroup) => ( <
+ div className = "tr headergroup" {...headerGroup.getHeaderGroupProps() } > {
+ headerGroup.headers.map((header) => ( <
+ div {...header.getHeaderProps({
+ className: `th header ${header.headers ? "outer " : ""}${
header.className || ""
}`,
- })}
- >
- {header.render("Header")}
+ })
+ } > { header.render("Header") }
- {(!header.className ||
- !header.className.includes("separator")) && (
-
- )}
-
- ))}
+ {
+ (!header.className ||
+ !header.className.includes("separator")) && ( <
+ div {...header.getResizerProps() }
+ className = { `resizer ${header.isResizing ? "isResizing" : ""}` }
+ />
+ )
+ }
+
+ ))
+ }
+
+ ))
+ }
- ))}
-
- );
-
- const renderTableData = (rows) => (
-
- {rows.map((row) => {
- prepareRow(row);
- return (
-
- {row.cells.map((cell) => (
-
- {cell.render("Cell")}
-
- ))}
-
- );
- })}
-
- );
+ );
- const renderTable = (headerGroups, rows) => {
- if (filtered && stats.length === 0) {
- return (
-
- Please wait while the statistics are being calculated.
-
- );
- }
- return (
-
-
-
-
- {renderTableHeaders(headerGroups)}
- {renderTableData(rows)}
-
-
+ const renderTableData = (rows) => ( <
+ div {...getTableBodyProps() }
+ className = "table-body body" > {
+ rows.map((row) => {
+ prepareRow(row);
+ return ( <
+ div {...row.getRowProps() }
+ className = "tr" > {
+ row.cells.map((cell) => ( <
+ div {...cell.getCellProps({
+ className: "td " + (cell.column.className || ""),
+ })
+ } > { cell.render("Cell") }
+
+ ))
+ }
+
+ );
+ })
+ }
-
);
- };
- const columns = useMemo(() => {
- const createColumnBuilder =
- ({ switchToQuantile, hiddenCols }) =>
- (runSetIdx, column, columnIdx) => ({
- id: `${runSetIdx}_${column.display_title}_${columnIdx}`,
- Header: (
- switchToQuantile(column)}
- />
- ),
- hidden:
- hiddenCols[runSetIdx].includes(column.colIdx) ||
- !(isNumericColumn(column) || column.type === "status"),
- width: determineColumnWidth(
- column,
- null,
- column.type === "status" ? 6 : null,
- ),
- minWidth: 30,
- accessor: (row) => row.content[runSetIdx][columnIdx],
- Cell: (cell) => {
- let valueToRender = cell.value?.sum;
- // We handle status differently as the main aggregation (denoted "sum")
- // is of type "count" for this column type.
- // This means that the default value if no data is available is 0
- if (column.type === "status") {
- if (cell.value === undefined) {
- // No data is available, default to 0
- valueToRender = 0;
- } else if (cell.value === null) {
- // We receive a null value directly from the stats object of the dataset.
- // Will be rendered as "-"
- // This edge case only applies to the local summary as it contains static values
- // that we can not calculate and therefore directly take them from the stats object.
+ const renderTable = (headerGroups, rows) => {
+ if (filtered && stats.length === 0) {
+ return (
+
+ Please wait
+ while the statistics are being calculated.
+
+ );
+ }
+ return (
+
+
+
+
{ renderTableHeaders(headerGroups) } { renderTableData(rows) }
+
+
+
+
+ );
+ };
- valueToRender = null;
- } else {
- valueToRender = Number.isInteger(Number(cell.value.sum))
- ? Number(cell.value.sum)
- : cell.value.sum;
- }
- }
- return !isNil(valueToRender) ? (
-
- ) : (
- -
- );
- },
- });
+ // Stutus Categories
+ const statusCategories = ["unknown", "TIMEOUT", "OUT OF MEMORY"]; //Add more statuses
+ const columns = useMemo(() => {
+ const createColumnBuilder =
+ ({ switchToQuantile, hiddenCols }) =>
+ (runSetIdx, column, columnIdx) => ({
+ id: `${runSetIdx}_${column.display_title}_${columnIdx}`,
+ Header: ( <
+ StandardColumnHeader column = { column }
+ className = "header-data clickable"
+ title = "Show Quantile Plot of this column"
+ onClick = {
+ (e) => switchToQuantile(column)
+ }
+ />
+ ),
+ hidden: hiddenCols[runSetIdx].includes(column.colIdx) ||
+ !(isNumericColumn(column) || column.type === "status"),
+ width: determineColumnWidth(
+ column,
+ null,
+ column.type === "status" ? 6 : null,
+ ),
+ minWidth: 30,
+ accessor: (row) => row.content[runSetIdx][columnIdx],
+ Cell: (cell) => {
+ let valueToRender = cell.value ? .sum ;
+ // We handle status differently as the main aggregation (denoted "sum")
+ // is of type "count" for this column type.
+ // This means that the default value if no data is available is 0
+ if (column.type === "status") {
+ if (cell.value === undefined) {
+ // No data is available, default to 0
+ valueToRender = 0;
+ } else if (cell.value === null) {
+ // We receive a null value directly from the stats object of the dataset.
+ // Will be rendered as "-"
+ // This edge case only applies to the local summary as it contains static values
+ // that we can not calculate and therefore directly take them from the stats object.
- const createRowTitleColumn = () => ({
- Header: () => (
-
- ),
- id: "row-title",
- sticky: isTitleColSticky ? "left" : "",
- width: titleColWidth,
- minWidth: 100,
- columns: [
- {
- id: "summary",
- width: titleColWidth,
- minWidth: 100,
- Header: ,
- Cell: (cell) => (
-
- ),
- },
- ],
- });
+ valueToRender = null;
+ } else {
+ valueToRender = Number.isInteger(Number(cell.value.sum)) ?
+ Number(cell.value.sum) :
+ cell.value.sum;
+ }
+ }
+ return !isNil(valueToRender) ? ( <
+ div dangerouslySetInnerHTML = {
+ {
+ __html: valueToRender,
+ }
+ }
+ className = "cell"
+ title = {
+ column.type !== "status" ? renderTooltip(cell.value) : undefined
+ } >
+
+ ) : (
+ -
+ );
+ },
+ });
+
+ const createRowTitleColumn = () => ({
+ Header: () => (
+
+ ),
+ id: "row-title",
+ sticky: isTitleColSticky ? "left" : "",
+ width: titleColWidth,
+ minWidth: 100,
+ columns: [{
+ id: "summary",
+ width: titleColWidth,
+ minWidth: 100,
+ Header: < SelectColumnsButton handler = { selectColumn }
+ />,
+ Cell: (cell) => ( <
+ div dangerouslySetInnerHTML = {
+ {
+ __html:
+ (cell.row.original.title ||
+ " ".repeat(
+ 4 * statisticsRows[cell.row.original.id].indent,
+ ) + statisticsRows[cell.row.original.id].title) +
+ (filtered ? " of selected rows" : ""),
+ }
+ }
+ title = {
+ cell.row.original.description ||
+ statisticsRows[cell.row.original.id].description
+ }
+ className = "row-title" /
+ >
+ ),
+ }, ],
+ });
- const statColumns = tools
- .map((runSet, runSetIdx) =>
- createRunSetColumns(
- runSet,
- runSetIdx,
- createColumnBuilder({ switchToQuantile, hiddenCols }),
- ),
- )
- .flat();
+ const statusColumns = statusCategories.map((status) => ({
+ id: `status_${status}`,
+ Header: status.toUpperCase(),
+ accessor: (row) => row.satusCounts ? .[status] || 0,
+ cell: (cell) => < div > { cell.value }
+ }));
+ const statColumns = tools
+ .map((runSet, runSetIdx) =>
+ createRunSetColumns(
+ runSet,
+ runSetIdx,
+ createColumnBuilder({ switchToQuantile, hiddenCols }),
+ ),
+ )
+ .flat();
- return [createRowTitleColumn()].concat(statColumns);
- }, [
- filtered,
- isTitleColSticky,
- switchToQuantile,
- hiddenCols,
- selectColumn,
- tools,
- ]);
+ return [createRowTitleColumn()].concat(statColumns, statColumns);
+ }, [
+ filtered,
+ isTitleColSticky,
+ switchToQuantile,
+ hiddenCols,
+ selectColumn,
+ tools,
+ ]);
- const data = useMemo(() => stats, [stats]);
+ const data = useMemo(() => stats, [stats]);
- const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
- useTable(
- {
- columns,
- data,
- initialState: {
- hiddenColumns: getHiddenColIds(columns),
+ const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
+ useTable({
+ columns,
+ data,
+ initialState: {
+ hiddenColumns: getHiddenColIds(columns),
+ },
},
- },
- useFilters,
- useResizeColumns,
- useFlexLayout,
- useSticky,
+ useFilters,
+ useResizeColumns,
+ useFlexLayout,
+ useSticky,
);
- return (
-
-
Statistics
- {renderTable(headerGroups, rows)}
-
- );
+ return (
+
+
Statistics
{ renderTable(headerGroups, rows) }
+
+ );
};
-export default StatisticsTable;
+export default StatisticsTable;
\ No newline at end of file
diff --git a/benchexec/tablegenerator/react-table/src/utils/stats.js b/benchexec/tablegenerator/react-table/src/utils/stats.js
index e6bf407cd..6ab72ad95 100644
--- a/benchexec/tablegenerator/react-table/src/utils/stats.js
+++ b/benchexec/tablegenerator/react-table/src/utils/stats.js
@@ -11,55 +11,77 @@ import { enqueue } from "../workers/workerDirector";
const keysToIgnore = ["meta"];
export const statisticsRows = {
- total: { title: "all results" },
- correct: {
- indent: 1,
- title: "correct results",
- description:
- "(property holds + result is true) OR (property does not hold + result is false)",
- },
- correct_true: {
- indent: 2,
- title: "correct true",
- description: "property holds + result is true",
- },
- correct_false: {
- indent: 2,
- title: "correct false",
- description: "property does not hold + result is false",
- },
- correct_unconfirmed: {
- indent: 1,
- title: "correct-unconfirmed results",
- description:
- "(property holds + result is true) OR (property does not hold + result is false), but unconfirmed",
- },
- correct_unconfirmed_true: {
- indent: 2,
- title: "correct-unconfirmed true",
- description: "property holds + result is true, but unconfirmed",
- },
- correct_unconfirmed_false: {
- indent: 2,
- title: "correct-unconfirmed false",
- description: "property does not hold + result is false, but unconfirmed",
- },
- wrong: {
- indent: 1,
- title: "incorrect results",
- description:
- "(property holds + result is false) OR (property does not hold + result is true)",
- },
- wrong_true: {
- indent: 2,
- title: "incorrect true",
- description: "property does not hold + result is true",
- },
- wrong_false: {
- indent: 2,
- title: "incorrect false",
- description: "property holds + result is false",
- },
+ total: { title: "all results" },
+ correct: {
+ indent: 1,
+ title: "correct results",
+ description: "(property holds + result is true) OR (property does not hold + result is false)",
+ },
+ correct_true: {
+ indent: 2,
+ title: "correct true",
+ description: "property holds + result is true",
+ },
+ correct_false: {
+ indent: 2,
+ title: "correct false",
+ description: "property does not hold + result is false",
+ },
+ correct_unconfirmed: {
+ indent: 1,
+ title: "correct-unconfirmed results",
+ description: "(property holds + result is true) OR (property does not hold + result is false), but unconfirmed",
+ },
+ correct_unconfirmed_true: {
+ indent: 2,
+ title: "correct-unconfirmed true",
+ description: "property holds + result is true, but unconfirmed",
+ },
+ correct_unconfirmed_false: {
+ indent: 2,
+ title: "correct-unconfirmed false",
+ description: "property does not hold + result is false, but unconfirmed",
+ },
+ wrong: {
+ indent: 1,
+ title: "incorrect results",
+ description: "(property holds + result is false) OR (property does not hold + result is true)",
+ },
+ wrong_true: {
+ indent: 2,
+ title: "incorrect true",
+ description: "property does not hold + result is true",
+ },
+ wrong_false: {
+ indent: 2,
+ title: "incorrect false",
+ description: "property holds + result is false",
+ },
+ unknown: {
+ indent: 1,
+ title: "unknown results",
+ description: "Result with unknown status",
+ },
+ unknown_true: {
+ indent: 2,
+ title: "unknown true",
+ description: "Result with unknown status",
+ },
+ unknown_false: {
+ indent: 2,
+ title: "unknown false",
+ description: "Result with unknown status",
+ },
+ timeout: {
+ indent: 1,
+ title: "TIMEOUT results",
+ description: "Tasks that timed out",
+ },
+ out_of_memory: {
+ indent: 1,
+ title: "OUT OF MEMORY results",
+ description: "Tasks that run out of memory",
+ },
};
/**
@@ -67,7 +89,7 @@ export const statisticsRows = {
* compute values (e.g., local summary, score).
*/
export const filterComputableStatistics = (stats) =>
- stats.filter((row) => statisticsRows[row.id]);
+ stats.filter((row) => statisticsRows[row.id]);
/**
* This method gets called on the initial render or whenever there is a
@@ -78,57 +100,57 @@ export const filterComputableStatistics = (stats) =>
* necessary transformation to bring the calculation results into the
* required format.
*/
-export const computeStats = async ({ tools, tableData, stats }) => {
- const formatter = buildFormatter(tools);
- let res = await processData({ tools, tableData, formatter });
-
- const availableStats = stats
- .map((row) => row.id)
- .filter((id) => statisticsRows[id]);
- const cleaned = cleanupStats(res, formatter, availableStats);
-
- // fill up stat array to match column mapping
-
- // The result of our stat calculation only contains relevant columns.
- // The stat table however requires a strict ordering of columns that also
- // includes columns that are not even rendered.
- //
- // In order to ensure a consistent layout we iterate through all columns
- // of the runset and append dummy objects until we reach a column that we
- // have calculated data for
- res = cleaned.map((tool, toolIdx) => {
- const out = [];
- const toolColumns = tools[toolIdx].columns;
- let pointer = 0;
- let curr = toolColumns[pointer];
-
- for (const col of tool) {
- const { title } = col;
- while (pointer < toolColumns.length && title !== curr.title) {
- // irrelevant column
- out.push({});
- pointer++;
- curr = toolColumns[pointer];
- }
- if (pointer >= toolColumns.length) {
- break;
- }
- // relevant column
- out.push(col);
- pointer++;
- curr = toolColumns[pointer];
- }
+export const computeStats = async({ tools, tableData, stats }) => {
+ const formatter = buildFormatter(tools);
+ let res = await processData({ tools, tableData, formatter });
+
+ const availableStats = stats
+ .map((row) => row.id)
+ .filter((id) => statisticsRows[id]);
+ const cleaned = cleanupStats(res, formatter, availableStats);
+
+ // fill up stat array to match column mapping
+
+ // The result of our stat calculation only contains relevant columns.
+ // The stat table however requires a strict ordering of columns that also
+ // includes columns that are not even rendered.
+
+ // In order to ensure a consistent layout we iterate through all columns
+ // of the runset and append dummy objects until we reach a column that we
+ // have calculated data for
+ res = cleaned.map((tool, toolIdx) => {
+ const out = [];
+ const toolColumns = tools[toolIdx].columns;
+ let pointer = 0;
+ let curr = toolColumns[pointer];
+
+ for (const col of tool) {
+ const { title } = col;
+ while (pointer < toolColumns.length && title !== curr.title) {
+ // irrelevant column
+ out.push({});
+ pointer++;
+ curr = toolColumns[pointer];
+ }
+ if (pointer >= toolColumns.length) {
+ break;
+ }
+ // relevant column
+ out.push(col);
+ pointer++;
+ curr = toolColumns[pointer];
+ }
- return out;
- });
+ return out;
+ });
- // Put new statistics in same "shape" as old ones.
- return filterComputableStatistics(stats).map((row) => {
- const content = row.content.map((tool, toolIdx) => {
- return res[toolIdx].map((col) => col[row.id]);
+ // Put new statistics in same "shape" as old ones.
+ return filterComputableStatistics(stats).map((row) => {
+ const content = row.content.map((tool, toolIdx) => {
+ return res[toolIdx].map((col) => col[row.id]);
+ });
+ return {...row, content };
});
- return { ...row, content };
- });
};
/**
@@ -138,71 +160,71 @@ export const computeStats = async ({ tools, tableData, stats }) => {
* @param {object[]} tools
*/
const buildFormatter = (tools) =>
- tools.map((tool, tIdx) =>
- tool.columns.map((column, cIdx) => {
- const { number_of_significant_digits: sigDigits } = column;
- return new NumberFormatterBuilder(sigDigits, `${tIdx}-${cIdx}`);
- }),
- );
+ tools.map((tool, tIdx) =>
+ tool.columns.map((column, cIdx) => {
+ const { number_of_significant_digits: sigDigits } = column;
+ return new NumberFormatterBuilder(sigDigits, `${tIdx}-${cIdx}`);
+ }),
+ );
const maybeRound =
- (key, maxDecimalInputLength, columnIdx) =>
- (number, { significantDigits }) => {
- const asNumber = Number(number);
- const [integer, decimal] = number.split(".");
-
- if (["sum", "avg", "stdev"].includes(key)) {
- // for cases when we have no significant digits defined,
- // we want to pad avg and stdev to two digits
- if (isNil(significantDigits) && key !== "sum") {
- return asNumber.toFixed(2);
- }
- // integer value without leading 0
- const cleanedInt = integer.replace(/^0+/, "");
- // decimal value without leading 0, if cleanedInt is empty (evaluates to zero)
- let cleanedDec = decimal || "";
- if (cleanedInt === "") {
- cleanedDec = cleanedDec.replace(/^0+/, "");
- }
-
- // differences in length between input value with maximal length and current value
- const deltaInputLength = maxDecimalInputLength - (decimal?.length ?? 0);
-
- // differences in length between num of significant digits and current value
- const deltaSigDigLength =
- significantDigits - (cleanedInt.length + cleanedDec.length);
-
- // if we have not yet filled the number of significant digits, we could decide to pad
- const paddingPossible = deltaSigDigLength > 0;
-
- const missingDigits = (decimal?.length ?? 0) + deltaSigDigLength;
-
- if (deltaInputLength > 0 && paddingPossible && key !== "stdev") {
- if (deltaInputLength > deltaSigDigLength) {
- // we want to pad to the smaller value (sigDigits vs maxDecimal)
- return asNumber.toFixed(missingDigits);
+ (key, maxDecimalInputLength, columnIdx) =>
+ (number, { significantDigits }) => {
+ const asNumber = Number(number);
+ const [integer, decimal] = number.split(".");
+
+ if (["sum", "avg", "stdev"].includes(key)) {
+ // for cases when we have no significant digits defined,
+ // we want to pad avg and stdev to two digits
+ if (isNil(significantDigits) && key !== "sum") {
+ return asNumber.toFixed(2);
+ }
+ // integer value without leading 0
+ const cleanedInt = integer.replace(/^0+/, "");
+ // decimal value without leading 0, if cleanedInt is empty (evaluates to zero)
+ let cleanedDec = decimal || "";
+ if (cleanedInt === "") {
+ cleanedDec = cleanedDec.replace(/^0+/, "");
+ }
+
+ // differences in length between input value with maximal length and current value
+ const deltaInputLength = maxDecimalInputLength - (decimal ? .length ? ? 0);
+
+ // differences in length between num of significant digits and current value
+ const deltaSigDigLength =
+ significantDigits - (cleanedInt.length + cleanedDec.length);
+
+ // if we have not yet filled the number of significant digits, we could decide to pad
+ const paddingPossible = deltaSigDigLength > 0;
+
+ const missingDigits = (decimal ? .length ? ? 0) + deltaSigDigLength;
+
+ if (deltaInputLength > 0 && paddingPossible && key !== "stdev") {
+ if (deltaInputLength > deltaSigDigLength) {
+ // we want to pad to the smaller value (sigDigits vs maxDecimal)
+ return asNumber.toFixed(missingDigits);
+ }
+ return asNumber.toFixed(maxDecimalInputLength);
+ }
+
+ // if avg was previously padded to fill the number of significant digits,
+ // we want to make sure, that we don't go over the maximumDecimalDigits
+ if (
+ key === "avg" &&
+ !paddingPossible &&
+ deltaInputLength < 0 &&
+ number[number.length - 1] === "0"
+ ) {
+ return asNumber.toFixed(maxDecimalInputLength);
+ }
+
+ if (key === "stdev" && paddingPossible) {
+ return asNumber.toFixed(missingDigits);
+ }
}
- return asNumber.toFixed(maxDecimalInputLength);
- }
-
- // if avg was previously padded to fill the number of significant digits,
- // we want to make sure, that we don't go over the maximumDecimalDigits
- if (
- key === "avg" &&
- !paddingPossible &&
- deltaInputLength < 0 &&
- number[number.length - 1] === "0"
- ) {
- return asNumber.toFixed(maxDecimalInputLength);
- }
-
- if (key === "stdev" && paddingPossible) {
- return asNumber.toFixed(missingDigits);
- }
- }
- return number;
- };
+ return number;
+ };
/**
* Used to apply formatting to calculated stats and to remove
@@ -212,106 +234,105 @@ const maybeRound =
* @param {Function[][]} formatter
*/
const cleanupStats = (unfilteredStats, formatter, availableStats) => {
- const stats = unfilteredStats.map((tool, toolIdx) =>
- tool.map((col, colIdx) => {
- const { columnType } = col;
- const out = { columnType };
-
- for (const visibleStats of availableStats) {
- const currentCol = col[visibleStats];
- if (!currentCol) {
- continue;
- }
- out[visibleStats] = currentCol;
- if (currentCol?.sum ?? false) {
- formatter[toolIdx][colIdx].addDataItem(currentCol.sum);
- }
- }
- return out;
- }),
- );
-
- for (const to in formatter) {
- for (const co in formatter[to]) {
- // we build all formatters which makes them ready to use
- formatter[to][co] = formatter[to][co].build();
- }
- }
-
- const cleaned = stats.map((tool, toolIdx) =>
- tool
- .map(({ columnType, ...column }, columnIdx) => {
- const out = {};
- // if no total is calculated, then no values suitable for calculation were found
- if (column.total === undefined) {
- return undefined;
- }
- for (const [resultKey, result] of Object.entries(column)) {
- const rowRes = {};
- const meta = result?.meta;
- for (let [key, value] of Object.entries(result)) {
- // we ignore any of these defined keys
- if (keysToIgnore.includes(key)) {
- continue;
+ const stats = unfilteredStats.map((tool, toolIdx) =>
+ tool.map((col, colIdx) => {
+ const { columnType } = col;
+ const out = { columnType };
+
+ for (const visibleStats of availableStats) {
+ const currentCol = col[visibleStats];
+ if (!currentCol) {
+ continue;
+ }
+ out[visibleStats] = currentCol;
+ if (currentCol ? .sum ? ? false) {
+ formatter[toolIdx][colIdx].addDataItem(currentCol.sum);
+ }
}
+ return out;
+ }),
+ );
- const maxDecimalInputLength = meta?.maxDecimals ?? 0;
+ for (const to in formatter) {
+ for (const co in formatter[to]) {
+ // we build all formatters which makes them ready to use
+ formatter[to][co] = formatter[to][co].build();
+ }
+ }
- // attach the title to the stat item
- // this will later be used to ensure correct ordering of columns
- if (key === "title") {
- out.title = value;
- continue;
+ const cleaned = stats.map((tool, toolIdx) =>
+ tool
+ .map(({ columnType, ...column }, columnIdx) => {
+ const out = {};
+ // if no total is calculated, then no values suitable for calculation were found
+ if (column.total === undefined) {
+ return undefined;
}
- // if we have numeric values or 'NaN' we want to apply formatting
- if (
- !isNil(value) &&
- (!isNaN(value) || value === "NaN") &&
- formatter[toolIdx][columnIdx]
- ) {
- try {
- if (key === "sum") {
- rowRes[key] = formatter[toolIdx][columnIdx](value, {
- leadingZero: false,
- whitespaceFormat: true,
- html: true,
- additionalFormatting: maybeRound(
- key,
- maxDecimalInputLength,
- columnIdx,
- ),
- });
- } else {
- rowRes[key] = formatter[toolIdx][columnIdx](value, {
- leadingZero: true,
- whitespaceFormat: false,
- html: false,
- additionalFormatting: maybeRound(
- key,
- maxDecimalInputLength,
- columnIdx,
- ),
- });
+ for (const [resultKey, result] of Object.entries(column)) {
+ const rowRes = {};
+ const meta = result ? .meta;
+ for (let [key, value] of Object.entries(result)) {
+ // we ignore any of these defined keys
+ if (keysToIgnore.includes(key)) {
+ continue;
+ }
+
+ const maxDecimalInputLength = meta ? .maxDecimals ? ? 0;
+
+ // attach the title to the stat item
+ // this will later be used to ensure correct ordering of columns
+ if (key === "title") {
+ out.title = value;
+ continue;
+ }
+ // if we have numeric values or 'NaN' we want to apply formatting
+ if (!isNil(value) &&
+ (!isNaN(value) || value === "NaN") &&
+ formatter[toolIdx][columnIdx]
+ ) {
+ try {
+ if (key === "sum") {
+ rowRes[key] = formatter[toolIdx][columnIdx](value, {
+ leadingZero: false,
+ whitespaceFormat: true,
+ html: true,
+ additionalFormatting: maybeRound(
+ key,
+ maxDecimalInputLength,
+ columnIdx,
+ ),
+ });
+ } else {
+ rowRes[key] = formatter[toolIdx][columnIdx](value, {
+ leadingZero: true,
+ whitespaceFormat: false,
+ html: false,
+ additionalFormatting: maybeRound(
+ key,
+ maxDecimalInputLength,
+ columnIdx,
+ ),
+ });
+ }
+ } catch (e) {
+ console.error({
+ key,
+ value,
+ formatter: formatter[toolIdx][columnIdx],
+ e,
+ });
+ }
+ }
}
- } catch (e) {
- console.error({
- key,
- value,
- formatter: formatter[toolIdx][columnIdx],
- e,
- });
- }
- }
- }
- out[resultKey] = rowRes;
- }
+ out[resultKey] = rowRes;
+ }
- return out;
- })
- .filter((i) => !isNil(i)),
- );
- return cleaned;
+ return out;
+ })
+ .filter((i) => !isNil(i)),
+ );
+ return cleaned;
};
const RESULT_TRUE_PROP = "true";
@@ -327,42 +348,42 @@ const RESULT_CLASS_OTHER = "other";
* @see result.py
*/
const classifyResult = (result) => {
- if (isNil(result)) {
+ if (isNil(result)) {
+ return RESULT_CLASS_OTHER;
+ }
+ if (result === RESULT_TRUE_PROP) {
+ return RESULT_CLASS_TRUE;
+ }
+ if (result === RESULT_FALSE_PROP) {
+ return RESULT_CLASS_FALSE;
+ }
+ if (result.startsWith(`${RESULT_FALSE_PROP}(`) && result.endsWith(")")) {
+ return RESULT_CLASS_FALSE;
+ }
+
return RESULT_CLASS_OTHER;
- }
- if (result === RESULT_TRUE_PROP) {
- return RESULT_CLASS_TRUE;
- }
- if (result === RESULT_FALSE_PROP) {
- return RESULT_CLASS_FALSE;
- }
- if (result.startsWith(`${RESULT_FALSE_PROP}(`) && result.endsWith(")")) {
- return RESULT_CLASS_FALSE;
- }
-
- return RESULT_CLASS_OTHER;
};
const prepareRows = (
- rows,
- toolIdx,
- categoryAccessor,
- statusAccessor,
- formatter,
+ rows,
+ toolIdx,
+ categoryAccessor,
+ statusAccessor,
+ formatter,
) => {
- return rows.map((row) => {
- const cat = categoryAccessor(toolIdx, row);
- const stat = statusAccessor(toolIdx, row);
-
- const mappedCat = cat.replace(/-/g, "_");
- const mappedRes = classifyResult(stat);
-
- return {
- categoryType: mappedCat,
- resultType: mappedRes,
- row: row.results[toolIdx].values,
- };
- });
+ return rows.map((row) => {
+ const cat = categoryAccessor(toolIdx, row);
+ const stat = statusAccessor(toolIdx, row);
+
+ const mappedCat = cat.replace(/-/g, "_");
+ const mappedRes = classifyResult(stat);
+
+ return {
+ categoryType: mappedCat,
+ resultType: mappedRes,
+ row: row.results[toolIdx].values,
+ };
+ });
};
/**
@@ -372,21 +393,22 @@ const prepareRows = (
* @param {object[]} tools
*/
const splitColumnsWithMeta = (tools) => (preppedRows, toolIdx) => {
- const out = [];
- for (const { row, categoryType, resultType } of preppedRows) {
- for (const columnIdx in row) {
- const column = row[columnIdx].raw;
- const curr = out[columnIdx] || [];
- // we attach extra meta information for later use in calculation and mapping
- // of results
- const { type: columnType, title: columnTitle } =
- tools[toolIdx].columns[columnIdx];
-
- curr.push({ categoryType, resultType, column, columnType, columnTitle });
- out[columnIdx] = curr;
+ const out = [];
+ for (const { row, categoryType, resultType }
+ of preppedRows) {
+ for (const columnIdx in row) {
+ const column = row[columnIdx].raw;
+ const curr = out[columnIdx] || [];
+ // we attach extra meta information for later use in calculation and mapping
+ // of results
+ const { type: columnType, title: columnTitle } =
+ tools[toolIdx].columns[columnIdx];
+
+ curr.push({ categoryType, resultType, column, columnType, columnTitle });
+ out[columnIdx] = curr;
+ }
}
- }
- return out;
+ return out;
};
/**
@@ -395,37 +417,37 @@ const splitColumnsWithMeta = (tools) => (preppedRows, toolIdx) => {
*
* @param {object} options
*/
-const processData = async ({ tools, tableData, formatter }) => {
- const catAccessor = (toolIdx, row) => row.results[toolIdx].category;
- const statAccessor = (toolIdx, row) => row.results[toolIdx].values[0].raw;
- const promises = [];
-
- const splitRows = [];
- for (const toolIdx in tools) {
- splitRows.push(
- prepareRows(tableData, toolIdx, catAccessor, statAccessor, formatter),
- );
- }
- const columnSplitter = splitColumnsWithMeta(tools);
-
- const preparedData = splitRows.map(columnSplitter);
- // filter out non-relevant rows
- for (const toolIdx in preparedData) {
- preparedData[toolIdx] = preparedData[toolIdx].filter((i) => !isNil(i));
- }
-
- for (const toolDataIdx in preparedData) {
- const toolData = preparedData[toolDataIdx];
- const subPromises = [];
- for (const columnIdx in toolData) {
- const columns = toolData[columnIdx];
- subPromises.push(enqueue({ name: "stats", data: columns }));
+const processData = async({ tools, tableData, formatter }) => {
+ const catAccessor = (toolIdx, row) => row.results[toolIdx].category;
+ const statAccessor = (toolIdx, row) => row.results[toolIdx].values[0].raw;
+ const promises = [];
+
+ const splitRows = [];
+ for (const toolIdx in tools) {
+ splitRows.push(
+ prepareRows(tableData, toolIdx, catAccessor, statAccessor, formatter),
+ );
}
- promises[toolDataIdx] = subPromises;
- }
+ const columnSplitter = splitColumnsWithMeta(tools);
- const allPromises = promises.map((p) => Promise.all(p));
- const res = await Promise.all(allPromises);
+ const preparedData = splitRows.map(columnSplitter);
+ // filter out non-relevant rows
+ for (const toolIdx in preparedData) {
+ preparedData[toolIdx] = preparedData[toolIdx].filter((i) => !isNil(i));
+ }
- return res;
-};
+ for (const toolDataIdx in preparedData) {
+ const toolData = preparedData[toolDataIdx];
+ const subPromises = [];
+ for (const columnIdx in toolData) {
+ const columns = toolData[columnIdx];
+ subPromises.push(enqueue({ name: "stats", data: columns }));
+ }
+ promises[toolDataIdx] = subPromises;
+ }
+
+ const allPromises = promises.map((p) => Promise.all(p));
+ const res = await Promise.all(allPromises);
+
+ return res;
+};
\ No newline at end of file
diff --git a/benchexec/tablegenerator/react-table/src/workers/scripts/stats.worker.js b/benchexec/tablegenerator/react-table/src/workers/scripts/stats.worker.js
index f0a9a8d64..0f79b0443 100644
--- a/benchexec/tablegenerator/react-table/src/workers/scripts/stats.worker.js
+++ b/benchexec/tablegenerator/react-table/src/workers/scripts/stats.worker.js
@@ -15,42 +15,42 @@
* @returns {Number} The result of the addition
*/
const safeAdd = (a, b) => {
- let aNum = a;
- let bNum = b;
-
- if (typeof a === "string") {
- aNum = Number(a);
- }
- if (typeof b === "string") {
- bNum = Number(b);
- }
-
- if (Number.isInteger(aNum) || Number.isInteger(bNum)) {
- return aNum + bNum;
- }
-
- const aString = a.toString();
- const aLength = aString.length;
- const aDecimalPoint = aString.indexOf(".");
- const bString = b.toString();
- const bLength = bString.length;
- const bDecimalPoint = bString.indexOf(".");
-
- const length = Math.max(aLength - aDecimalPoint, bLength - bDecimalPoint) - 1;
-
- return Number((aNum + bNum).toFixed(length));
+ let aNum = a;
+ let bNum = b;
+
+ if (typeof a === "string") {
+ aNum = Number(a);
+ }
+ if (typeof b === "string") {
+ bNum = Number(b);
+ }
+
+ if (Number.isInteger(aNum) || Number.isInteger(bNum)) {
+ return aNum + bNum;
+ }
+
+ const aString = a.toString();
+ const aLength = aString.length;
+ const aDecimalPoint = aString.indexOf(".");
+ const bString = b.toString();
+ const bLength = bString.length;
+ const bDecimalPoint = bString.indexOf(".");
+
+ const length = Math.max(aLength - aDecimalPoint, bLength - bDecimalPoint) - 1;
+
+ return Number((aNum + bNum).toFixed(length));
};
const mathStringMax = (a, b) => {
- const numA = Number(a);
- const numB = Number(b);
- return numA > numB ? a : b;
+ const numA = Number(a);
+ const numB = Number(b);
+ return numA > numB ? a : b;
};
const mathStringMin = (a, b) => {
- const numA = Number(a);
- const numB = Number(b);
- return numA < numB ? a : b;
+ const numA = Number(a);
+ const numB = Number(b);
+ return numA < numB ? a : b;
};
/**
@@ -64,72 +64,72 @@ const mathStringMin = (a, b) => {
* @param {String} type
*/
const maybeAdd = (a, b, type) => {
- if (Number(b)) {
- return safeAdd(a, b);
- }
- if (type === "status") {
- return a + 1;
- }
- return a;
+ if (Number(b)) {
+ return safeAdd(a, b);
+ }
+ if (type === "status") {
+ return a + 1;
+ }
+ return a;
};
const removeRoundOff = (num) => {
- const str = num.toString();
- if (str.match(/\..+?0{2,}\d$/)) {
- return Number(str.substr(0, str.length - 1));
- }
- return num;
+ const str = num.toString();
+ if (str.match(/\..+?0{2,}\d$/)) {
+ return Number(str.substr(0, str.length - 1));
+ }
+ return num;
};
const calculateMean = (values, allItems) => {
- const numMin = Number(values.min);
- const numMax = Number(values.max);
- if (numMin === -Infinity && numMax === Infinity) {
- values.avg = "NaN";
- } else if (numMin === -Infinity) {
- values.avg = "-Infinity";
- } else if (numMax === Infinity) {
- values.avg = "Infinity";
- } else {
- values.avg = removeRoundOff(values.sum / allItems.length);
- }
+ const numMin = Number(values.min);
+ const numMax = Number(values.max);
+ if (numMin === -Infinity && numMax === Infinity) {
+ values.avg = "NaN";
+ } else if (numMin === -Infinity) {
+ values.avg = "-Infinity";
+ } else if (numMax === Infinity) {
+ values.avg = "Infinity";
+ } else {
+ values.avg = removeRoundOff(values.sum / allItems.length);
+ }
};
const calculateMedian = (values, allItems) => {
- if (allItems.length % 2 === 0) {
- const idx = allItems.length / 2;
- values.median =
- (Number(allItems[idx - 1].column) + Number(allItems[idx].column)) / 2.0;
- } else {
- values.median = allItems[Math.floor(allItems.length / 2.0)].column;
- }
+ if (allItems.length % 2 === 0) {
+ const idx = allItems.length / 2;
+ values.median =
+ (Number(allItems[idx - 1].column) + Number(allItems[idx].column)) / 2.0;
+ } else {
+ values.median = allItems[Math.floor(allItems.length / 2.0)].column;
+ }
};
const calculateStdev = (hasNegInf, hasPosInf, variance, size) => {
- if (hasNegInf && hasPosInf) {
- return "NaN";
- }
- if (hasNegInf || hasPosInf) {
- return Infinity;
- }
- return Math.sqrt(variance / size);
+ if (hasNegInf && hasPosInf) {
+ return "NaN";
+ }
+ if (hasNegInf || hasPosInf) {
+ return Infinity;
+ }
+ return Math.sqrt(variance / size);
};
const parsePythonInfinityValues = (data) =>
- data.map((item) => {
- if (item.columnType === "status" || !item.column.endsWith("Inf")) {
- return item;
- }
- // We have a python Infinity value that we want to transfer to a string
- // that can be interpreted as a JavaScript Infinity value
- item.column = item.column.replace("Inf", "Infinity");
- return item;
- });
+ data.map((item) => {
+ if (item.columnType === "status" || !item.column.endsWith("Inf")) {
+ return item;
+ }
+ // We have a python Infinity value that we want to transfer to a string
+ // that can be interpreted as a JavaScript Infinity value
+ item.column = item.column.replace("Inf", "Infinity");
+ return item;
+ });
// If a bucket contains a NaN value, we can not perform any stat calculation
const shouldSkipBucket = (bucketMeta, key) => {
- if (bucketMeta[key] && bucketMeta[key].hasNaN) {
- return true;
- }
- return false;
+ if (bucketMeta[key] && bucketMeta[key].hasNaN) {
+ return true;
+ }
+ return false;
};
/**
@@ -145,13 +145,13 @@ const shouldSkipBucket = (bucketMeta, key) => {
* @param {UpdateMaxDecimalMetaInfoParam} param
*/
const updateMaxDecimalMetaInfo = ({ columnType, column, bucket }) => {
- if (columnType !== "status") {
- const [, decimal] = column.split(".");
- bucket.meta.maxDecimals = Math.max(
- bucket.meta.maxDecimals,
- decimal?.length ?? 0,
- );
- }
+ if (columnType !== "status") {
+ const [, decimal] = column.split(".");
+ bucket.meta.maxDecimals = Math.max(
+ bucket.meta.maxDecimals,
+ decimal ? .length ? ? 0,
+ );
+ }
};
/**
@@ -178,243 +178,259 @@ const updateMaxDecimalMetaInfo = ({ columnType, column, bucket }) => {
* @prop {MetaInfo} [meta] - Meta information of the bucket
*/
-onmessage = function (e) {
- const { data, transaction } = e.data;
-
- // template
- /** @const { Bucket } */
- const defaultObj = {
- sum: 0,
- avg: 0,
- max: "-Infinity",
- median: 0,
- min: "Infinity",
- stdev: 0,
- variance: 0,
- };
-
- /** @const {MetaInfo} */
- const metaTemplate = {
- type: null,
- maxDecimals: 0,
- };
-
- // Copy of the template with all values replaced with NaN
- const nanObj = { ...defaultObj };
- for (const objKey of Object.keys(nanObj)) {
- nanObj[objKey] = "NaN";
- }
-
- let copy = [...data].filter(
- (i) => i && i.column !== undefined && i.column !== null,
- );
- copy = parsePythonInfinityValues(copy);
-
- if (copy.length === 0) {
- // No data to perform calculations with
- postResult({ total: undefined }, transaction);
- return;
- }
-
- const { columnType } = copy[0];
- metaTemplate.type = columnType;
-
- copy.sort((a, b) => a.column - b.column);
-
- /** @type {Object.} */
- const buckets = {};
- const bucketNaNInfo = {}; // used to store NaN info of buckets
-
- /** @type {Bucket} */
- let total = { ...defaultObj, items: [], meta: { ...metaTemplate } };
-
- total.max = copy[copy.length - 1].column;
- total.min = copy[0].column;
-
- const totalNaNInfo = {
- hasNaN: copy.some((item) => {
- if (item.columnType !== "status" && isNaN(item.column)) {
- return true;
- }
- return false;
- }),
- };
-
- // Bucket setup with sum and min/max
- for (const item of copy) {
- const key = `${item.categoryType}_${item.resultType}`;
- const totalKey = `${item.categoryType}`;
- const { columnType: type, column, columnTitle: title } = item;
- if (!total.title) {
- total.title = title;
- }
- const bucket = buckets[key] || {
- ...defaultObj,
- title,
- items: [],
- meta: { ...metaTemplate },
+onmessage = function(e) {
+ const { data, transaction } = e.data;
+
+ // template
+ /** @const { Bucket } */
+ const defaultObj = {
+ sum: 0,
+ avg: 0,
+ max: "-Infinity",
+ median: 0,
+ min: "Infinity",
+ stdev: 0,
+ variance: 0,
};
- const subTotalBucket = buckets[totalKey] || {
- ...defaultObj,
- title,
- items: [],
- meta: { ...metaTemplate },
+ /** @const {MetaInfo} */
+ const metaTemplate = {
+ type: null,
+ maxDecimals: 0,
};
- const itemIsNaN = type !== "status" && isNaN(column);
-
- // if one item is NaN we store that info so we can default all
- // calculated values for this bucket to NaN
- if (itemIsNaN) {
- bucketNaNInfo[key] = { hasNaN: true };
- bucketNaNInfo[totalKey] = { hasNaN: true };
+ const customStatusCounts = {};
+ const bucket = buckets[key] || {...defaultObj, items: [], meta: {...metaTemplate } };
- // set all values for this bucket to NaN
- buckets[key] = { ...nanObj, title };
- buckets[totalKey] = { ...nanObj, title };
- continue;
+ // Copy of the template with all values replaced with NaN
+ const nanObj = {...defaultObj };
+ for (const objKey of Object.keys(nanObj)) {
+ nanObj[objKey] = "NaN";
}
- // we check if we should skip calculation for these buckets
- const skipBucket = shouldSkipBucket(bucketNaNInfo, key);
- const skipSubTotal = shouldSkipBucket(bucketNaNInfo, totalKey);
-
- if (!skipBucket) {
- bucket.sum = maybeAdd(bucket.sum, column, type);
- updateMaxDecimalMetaInfo({ columnType, column, bucket });
- }
- if (!skipSubTotal) {
- subTotalBucket.sum = maybeAdd(subTotalBucket.sum, column, type);
- updateMaxDecimalMetaInfo({ columnType, column, bucket: subTotalBucket });
+ let copy = [...data].filter(
+ (i) => i && i.column !== undefined && i.column !== null,
+ );
+ copy = parsePythonInfinityValues(copy);
+
+ for (const item of copy) {
+ const key = `${item.categoryType}_${item.resultType}`;
+ const status = item.resultType;
+ const bucket = buckets[key] || {...defaultObj, items: [], meta: {...metaTemplate } };
+
+ if (!customStatusCounts[status]) {
+ customStatusCounts[status] = 0;
+ }
+ customStatusCounts[status] += 1;
+ buckets[key] = bucket;
}
- if (!totalNaNInfo.hasNaN) {
- total.sum = maybeAdd(total.sum, column, type);
- updateMaxDecimalMetaInfo({ columnType, column, bucket: total });
+ if (copy.length === 0) {
+ // No data to perform calculations with
+ const result = { columnType, total, ...buckets };
+ result.customStatusCounts = customStatusCounts;
+ postResult({ total: undefined }, transaction);
+ return;
}
- if (!isNaN(Number(column))) {
- if (!skipBucket) {
- bucket.max = mathStringMax(bucket.max, column);
- bucket.min = mathStringMin(bucket.min, column);
- }
- if (!skipSubTotal) {
- subTotalBucket.max = mathStringMax(subTotalBucket.max, column);
- subTotalBucket.min = mathStringMin(subTotalBucket.min, column);
- }
- }
- if (!skipBucket) {
- try {
- bucket.items.push(item);
- } catch (e) {
- console.e({ bucket, bucketMeta: bucketNaNInfo, key });
- }
- }
- if (!skipSubTotal) {
- try {
- subTotalBucket.items.push(item);
- } catch (e) {
- console.e({ subTotalBucket, bucketMeta: bucketNaNInfo, totalKey });
- }
+ const { columnType } = copy[0];
+ metaTemplate.type = columnType;
+
+ copy.sort((a, b) => a.column - b.column);
+
+ /** @type {Object.} */
+ const buckets = {};
+ const bucketNaNInfo = {}; // used to store NaN info of buckets
+
+ /** @type {Bucket} */
+ let total = {...defaultObj, items: [], meta: {...metaTemplate } };
+
+ total.max = copy[copy.length - 1].column;
+ total.min = copy[0].column;
+
+ const totalNaNInfo = {
+ hasNaN: copy.some((item) => {
+ if (item.columnType !== "status" && isNaN(item.column)) {
+ return true;
+ }
+ return false;
+ }),
+ };
+
+ // Bucket setup with sum and min/max
+ for (const item of copy) {
+ const key = `${item.categoryType}_${item.resultType}`;
+ const totalKey = `${item.categoryType}`;
+ const { columnType: type, column, columnTitle: title } = item;
+ if (!total.title) {
+ total.title = title;
+ }
+ const bucket = buckets[key] || {
+ ...defaultObj,
+ title,
+ items: [],
+ meta: {...metaTemplate },
+ };
+
+ const subTotalBucket = buckets[totalKey] || {
+ ...defaultObj,
+ title,
+ items: [],
+ meta: {...metaTemplate },
+ };
+
+ const itemIsNaN = type !== "status" && isNaN(column);
+
+ // if one item is NaN we store that info so we can default all
+ // calculated values for this bucket to NaN
+ if (itemIsNaN) {
+ bucketNaNInfo[key] = { hasNaN: true };
+ bucketNaNInfo[totalKey] = { hasNaN: true };
+
+ // set all values for this bucket to NaN
+ buckets[key] = {...nanObj, title };
+ buckets[totalKey] = {...nanObj, title };
+ continue;
+ }
+
+ // we check if we should skip calculation for these buckets
+ const skipBucket = shouldSkipBucket(bucketNaNInfo, key);
+ const skipSubTotal = shouldSkipBucket(bucketNaNInfo, totalKey);
+
+ if (!skipBucket) {
+ bucket.sum = maybeAdd(bucket.sum, column, type);
+ updateMaxDecimalMetaInfo({ columnType, column, bucket });
+ }
+ if (!skipSubTotal) {
+ subTotalBucket.sum = maybeAdd(subTotalBucket.sum, column, type);
+ updateMaxDecimalMetaInfo({ columnType, column, bucket: subTotalBucket });
+ }
+ if (!totalNaNInfo.hasNaN) {
+ total.sum = maybeAdd(total.sum, column, type);
+ updateMaxDecimalMetaInfo({ columnType, column, bucket: total });
+ }
+
+ if (!isNaN(Number(column))) {
+ if (!skipBucket) {
+ bucket.max = mathStringMax(bucket.max, column);
+ bucket.min = mathStringMin(bucket.min, column);
+ }
+ if (!skipSubTotal) {
+ subTotalBucket.max = mathStringMax(subTotalBucket.max, column);
+ subTotalBucket.min = mathStringMin(subTotalBucket.min, column);
+ }
+ }
+ if (!skipBucket) {
+ try {
+ bucket.items.push(item);
+ } catch (e) {
+ console.e({ bucket, bucketMeta: bucketNaNInfo, key });
+ }
+ }
+ if (!skipSubTotal) {
+ try {
+ subTotalBucket.items.push(item);
+ } catch (e) {
+ console.e({ subTotalBucket, bucketMeta: bucketNaNInfo, totalKey });
+ }
+ }
+
+ buckets[key] = bucket;
+ buckets[totalKey] = subTotalBucket;
}
- buckets[key] = bucket;
- buckets[totalKey] = subTotalBucket;
- }
+ for (const [bucket, values] of Object.entries(buckets)) {
+ if (shouldSkipBucket(bucketNaNInfo, bucket)) {
+ continue;
+ }
+ calculateMean(values, values.items);
- for (const [bucket, values] of Object.entries(buckets)) {
- if (shouldSkipBucket(bucketNaNInfo, bucket)) {
- continue;
+ calculateMedian(values, values.items);
+ buckets[bucket] = values;
}
- calculateMean(values, values.items);
-
- calculateMedian(values, values.items);
- buckets[bucket] = values;
- }
- const totalHasNaN = totalNaNInfo.hasNaN;
-
- if (totalHasNaN) {
- total = { ...total, ...nanObj };
- } else {
- calculateMean(total, copy);
- calculateMedian(total, copy);
- }
-
- for (const item of copy) {
- const { column } = item;
- if (isNaN(Number(column))) {
- continue;
+ const totalHasNaN = totalNaNInfo.hasNaN;
+
+ if (totalHasNaN) {
+ total = {...total, ...nanObj };
+ } else {
+ calculateMean(total, copy);
+ calculateMedian(total, copy);
}
- const numCol = Number(column);
- const key = `${item.categoryType}_${item.resultType}`;
- const totalKey = `${item.categoryType}`;
- const bucket = buckets[key];
- const subTotalBucket = buckets[totalKey];
- const diffBucket = numCol - bucket.avg;
- const diffSubTotal = numCol - subTotalBucket.avg;
- const diffTotal = numCol - total.avg;
- total.variance += Math.pow(diffTotal, 2);
- bucket.variance += Math.pow(diffBucket, 2);
- subTotalBucket.variance += Math.pow(diffSubTotal, 2);
- }
-
- const totalHasNegInf = Number(total.min) === -Infinity;
- const totalHasPosInf = Number(total.max) === Infinity;
- total.stdev = calculateStdev(
- totalHasNegInf,
- totalHasPosInf,
- total.variance,
- copy.length,
- );
-
- for (const [bucket, values] of Object.entries(buckets)) {
- if (shouldSkipBucket(bucketNaNInfo, bucket)) {
- for (const [key, val] of Object.entries(values)) {
- values[key] = val.toString();
- }
- buckets[bucket] = values;
- continue;
+
+ for (const item of copy) {
+ const { column } = item;
+ if (isNaN(Number(column))) {
+ continue;
+ }
+ const numCol = Number(column);
+ const key = `${item.categoryType}_${item.resultType}`;
+ const totalKey = `${item.categoryType}`;
+ const bucket = buckets[key];
+ const subTotalBucket = buckets[totalKey];
+ const diffBucket = numCol - bucket.avg;
+ const diffSubTotal = numCol - subTotalBucket.avg;
+ const diffTotal = numCol - total.avg;
+ total.variance += Math.pow(diffTotal, 2);
+ bucket.variance += Math.pow(diffBucket, 2);
+ subTotalBucket.variance += Math.pow(diffSubTotal, 2);
}
- const valuesHaveNegInf = Number(values.min) === -Infinity;
- const valuesHavePosInf = Number(total.max) === Infinity;
- values.stdev = calculateStdev(
- valuesHaveNegInf,
- valuesHavePosInf,
- values.variance,
- values.items.length,
+
+ const totalHasNegInf = Number(total.min) === -Infinity;
+ const totalHasPosInf = Number(total.max) === Infinity;
+ total.stdev = calculateStdev(
+ totalHasNegInf,
+ totalHasPosInf,
+ total.variance,
+ copy.length,
);
- for (const [key, val] of Object.entries(values)) {
- if (key === "meta") {
- continue;
- }
- values[key] = val.toString();
+ for (const [bucket, values] of Object.entries(buckets)) {
+ if (shouldSkipBucket(bucketNaNInfo, bucket)) {
+ for (const [key, val] of Object.entries(values)) {
+ values[key] = val.toString();
+ }
+ buckets[bucket] = values;
+ continue;
+ }
+ const valuesHaveNegInf = Number(values.min) === -Infinity;
+ const valuesHavePosInf = Number(total.max) === Infinity;
+ values.stdev = calculateStdev(
+ valuesHaveNegInf,
+ valuesHavePosInf,
+ values.variance,
+ values.items.length,
+ );
+
+ for (const [key, val] of Object.entries(values)) {
+ if (key === "meta") {
+ continue;
+ }
+ values[key] = val.toString();
+ }
+ // clearing memory
+ delete values.items;
+ delete values.variance;
+ buckets[bucket] = values;
}
- // clearing memory
- delete values.items;
- delete values.variance;
- buckets[bucket] = values;
- }
-
- for (const [key, value] of Object.entries(total)) {
- if (key === "meta") {
- continue;
+
+ for (const [key, value] of Object.entries(total)) {
+ if (key === "meta") {
+ continue;
+ }
+ total[key] = value.toString();
}
- total[key] = value.toString();
- }
- delete total.items;
- delete total.variance;
+ delete total.items;
+ delete total.variance;
- const result = { columnType, total, ...buckets };
- postResult(result, transaction);
+ const result = { columnType, total, ...buckets };
+ postResult(result, transaction);
};
const postResult = (result, transaction) => {
- // handling in tests
- if (this.mockedPostMessage) {
- this.mockedPostMessage({ result, transaction });
- return;
- }
- postMessage({ result, transaction });
-};
+ // handling in tests
+ if (this.mockedPostMessage) {
+ this.mockedPostMessage({ result, transaction });
+ return;
+ }
+ postMessage({ result, transaction });
+};
\ No newline at end of file