diff --git a/web-server/img/favicon.ico b/web-server/img/favicon.ico
new file mode 100644
index 000000000..b37befd6b
Binary files /dev/null and b/web-server/img/favicon.ico differ
diff --git a/web-server/js/slycat-table-ingestion-react.js b/web-server/js/slycat-table-ingestion-react.js
index a2ba931eb..2fdc617d1 100644
--- a/web-server/js/slycat-table-ingestion-react.js
+++ b/web-server/js/slycat-table-ingestion-react.js
@@ -1,19 +1,18 @@
import React, { useState } from "react";
export default function SlycatTableIngestion(props) {
-
// Declare a new UI state variables...
// To track which variables are selected
// selected is an array, each element corresponds to a variable, with boolean values indicating whether it's selected.
- const [selected, setSelected] = useState(props.variables.map((variable, indexVars) => variable.selected));
+ const [selected, setSelected] = useState(
+ props.variables.map((variable, indexVars) => variable.selected),
+ );
// To track the last selected variable
const [lastSelected, setLastSelected] = useState(0);
function anyVariablesSelected() {
- for(var i = 0; i < selected.length; i++)
- {
- if( selected[i] )
- {
+ for (var i = 0; i < selected.length; i++) {
+ if (selected[i]) {
return true;
}
}
@@ -26,60 +25,50 @@ export default function SlycatTableIngestion(props) {
function selectAll(e) {
let property = e.target.dataset.property;
- for(let [index, variableSelected] of selected.entries())
- {
- if(variableSelected)
- {
+ for (let [index, variableSelected] of selected.entries()) {
+ if (variableSelected) {
// Find the radio button that needs to be selected based on its name and value attributes
- let radio = document.querySelector(`.${props.uniqueID} input[value='${property}'][name='${index}']`);
+ let radio = document.querySelector(
+ `.${props.uniqueID} input[value='${property}'][name='${index}']`,
+ );
// Fire the onChange handler only if radio button is not disabled
- if(!radio.disabled)
- {
- props.onChange({currentTarget: radio});
+ if (!radio.disabled) {
+ props.onChange({ currentTarget: radio });
}
}
}
}
function select(event, varIndex) {
- if(props.variables[varIndex].disabled)
- {
+ if (props.variables[varIndex].disabled) {
return;
- }
- else if(event.shiftKey)
- {
+ } else if (event.shiftKey) {
// Find start and end between lastSelected and currently shift clicked
let begin = Math.min(lastSelected, varIndex);
let end = Math.max(lastSelected, varIndex);
// Set everything in between to selected
let newSelected = selected.slice(0);
- for(var i = begin; i <= end; i++)
- {
- if(!props.variables[i].disabled)
- {
+ for (var i = begin; i <= end; i++) {
+ if (!props.variables[i].disabled) {
newSelected[i] = true;
}
}
setSelected(newSelected);
// Set current clicked to lastSelected
setLastSelected(varIndex);
- }
- else if(event.ctrlKey || event.metaKey)
- {
+ } else if (event.ctrlKey || event.metaKey) {
setLastSelected(varIndex);
let newSelected = selected.slice(0);
// Invert the selected state, so Ctrl + click will either selecte unselected, or unselect selected.
newSelected[varIndex] = !newSelected[varIndex];
setSelected(newSelected);
- }
- else
- {
+ } else {
// Set last selected to current variable
setLastSelected(varIndex);
// Set all selected to false
- let newSelected = selected.map(x => false);
+ let newSelected = selected.map((x) => false);
// Set selected to true on current variable
newSelected[varIndex] = true;
setSelected(newSelected);
@@ -87,109 +76,96 @@ export default function SlycatTableIngestion(props) {
}
const propertiesItems = props.properties.map((property, indexProps) => {
- if(property.type == 'bool')
- {
+ if (property.type == "bool") {
return (
-
+
{property.name}
-
);
+ } else if (property.type == "select") {
+ return property.values.map((value, indexVals, array) => (
+
+ {value}
+
+
+ ));
}
- else if(property.type == 'select')
- {
- return (
- property.values.map((value, indexVals, array) =>
- (
-
- {value}
-
-
- )
- )
- );
- }
-
});
// Figure out if radio button should be disabled
function disabledRadioButton(property, value, variable) {
// Check if we have a disabledValue defined for this radio button
- let disabledValue = property.disabledValues && property.disabledValues[value] && property.disabledValues[value].indexOf(variable.index) > -1;
+ let disabledValue =
+ property.disabledValues &&
+ property.disabledValues[value] &&
+ property.disabledValues[value].indexOf(variable.index) > -1;
// Also return disabled if the entire variable is disabled
- return variable.disabled || disabledValue ? 'disabled' : false;
+ return variable.disabled || disabledValue ? "disabled" : false;
}
const variablesItems = props.variables.map((variable, indexVars, arrayVars) => {
return (
-
- select(event, indexVars)}>
- {variable.name}
-
- {
- props.properties.map((property, indexProps, arrayProps) => {
- if(property.type == 'bool')
- {
+
+ select(event, indexVars)}>
+ {variable.name}
+
+ {props.properties.map((property, indexProps, arrayProps) => {
+ if (property.type == "bool") {
return (
-
-
);
+ } else if (property.type == "select") {
+ return property.values.map((value, indexVals, arrayVals) => (
+
+
+
+ ));
}
- else if(property.type == 'select')
- {
- return (
- property.values.map((value, indexVals, arrayVals) =>
- (
-
-
-
- )
- )
- );
- }
- })
- }
-
+ })}
+
);
});
@@ -202,10 +178,8 @@ export default function SlycatTableIngestion(props) {
{propertiesItems}
-
- {variablesItems}
-
+ {variablesItems}
);
-}
\ No newline at end of file
+}
diff --git a/web-server/plugins/slycat-parameter-image/js/Components/ControlsButtonVarOptions.js b/web-server/plugins/slycat-parameter-image/js/Components/ControlsButtonVarOptions.js
index f7f38c693..b3d8183e6 100644
--- a/web-server/plugins/slycat-parameter-image/js/Components/ControlsButtonVarOptions.js
+++ b/web-server/plugins/slycat-parameter-image/js/Components/ControlsButtonVarOptions.js
@@ -19,7 +19,6 @@ import VariableAliasLabels from "components/VariableAliasLabels";
import ScatterplotOptions from "components/ScatterplotOptions";
import VariableRanges from "components/VariableRanges";
import "js/slycat-table-ingestion";
-import ko from "knockout";
import "../../css/controls-button-var-options.css";
import $ from "jquery";
import client from "js/slycat-web-client";
@@ -133,7 +132,7 @@ class ControlsButtonVarOptions extends React.PureComponent {
// Maybe it was deleted.
// Let's save to the model's artifact instead.
console.log(
- "Oops, we have a pointer to project data but can't save to it. Let's save to the model's artifact instead."
+ "Oops, we have a pointer to project data but can't save to it. Let's save to the model's artifact instead.",
);
self.writeAliasesToModelArtifact();
},
@@ -157,11 +156,11 @@ class ControlsButtonVarOptions extends React.PureComponent {
},
error: function () {
console.log(
- "Oops, can't even write aliases to model artifact. Closing dialog and popping up error dialog."
+ "Oops, can't even write aliases to model artifact. Closing dialog and popping up error dialog.",
);
$("#" + self.modalId).modal("hide");
dialog.ajax_error(
- "There was an error saving the variable alias labels to the model's artifact."
+ "There was an error saving the variable alias labels to the model's artifact.",
)();
},
});
@@ -242,9 +241,12 @@ class ControlsButtonVarOptions extends React.PureComponent {
undefined;
// Check if variable is greater than zero
const isGreaterThanZero = this.props.table_statistics?.[index]?.min > 0;
- // If not numeric or not greater than zero, add to disabledLogVariables
+ // Check if custom axis min variable range is 0 or less
+ const isCustomAxisMinZeroOrLess = this.props.variableRanges?.[index]?.min <= 0;
+ // If not numeric or not greater than zero or custom min variablel range is 0 or less,
+ // add to disabledLogVariables
// so we don't allow them for log scales.
- if (!isNumericVariable || !isGreaterThanZero) {
+ if (!isNumericVariable || !isGreaterThanZero || isCustomAxisMinZeroOrLess) {
disabledLogVariables.push(index);
}
}
@@ -660,7 +662,7 @@ const mapStateToProps = (state, ownProps) => {
min: value.min,
max: value.max,
};
- }
+ },
);
return {
diff --git a/web-server/plugins/slycat-parameter-image/js/Components/ControlsSelection.js b/web-server/plugins/slycat-parameter-image/js/Components/ControlsSelection.js
index c61c526f4..a46d19c77 100644
--- a/web-server/plugins/slycat-parameter-image/js/Components/ControlsSelection.js
+++ b/web-server/plugins/slycat-parameter-image/js/Components/ControlsSelection.js
@@ -18,13 +18,17 @@ class ControlsSelection extends React.PureComponent {
{ className: "btn-primary", label: "Apply" },
],
callback: function (button, valueIn) {
- if (button.label === "Apply") {
- let userValue = valueIn().trim();
- let numeric = self.props.metadata["column-types"][variableIndex] !== "string";
- let valueValid = userValue.length > 0;
- if (valueValid && numeric && isNaN(Number(userValue))) {
- valueValid = false;
- }
+ if (button?.label === "Apply") {
+ const userValue = valueIn().trim();
+ const numeric = self.props.metadata["column-types"][variableIndex] !== "string";
+ const log_scale_type = self.props.axes_variables_scale?.[variableIndex] == "Log";
+
+ // Perform validity checks
+ const fails_length_check = userValue.length <= 0;
+ const fails_numeric_check = numeric && isNaN(Number(userValue));
+ const fails_log_check = log_scale_type && userValue <= 0;
+ const valueValid = !fails_length_check && !fails_numeric_check && !fails_log_check;
+
if (valueValid) {
self.props.element.trigger("set-value", {
selection: self.props.selection,
@@ -32,9 +36,13 @@ class ControlsSelection extends React.PureComponent {
value: numeric ? userValue : '"' + userValue + '"',
});
} else {
- let alertText = "Please enter a value.";
- if (numeric) {
+ let alertText;
+ if (fails_length_check) {
+ alertText = "Please enter a value.";
+ } else if (fails_numeric_check) {
alertText = "Please enter a numeric value.";
+ } else if (fails_log_check) {
+ alertText = "Please enter a value greater than 0. This variable is on a log scale.";
}
self.set_value(variable, variableIndex, userValue, alertText);
}
@@ -161,7 +169,7 @@ class ControlsSelection extends React.PureComponent {
// console.log(`current_selection_pinned is ${current_selection_pinned}`);
// Find the unpinned items in the current selection that are visible (i.e, not off axes).
const unpinned_selection_not_off_axes = unpinned_selection.filter(
- (unpinned_selected_item) => !this._is_off_axes(unpinned_selected_item)
+ (unpinned_selected_item) => !this._is_off_axes(unpinned_selected_item),
);
// console.log(`unpinned_selection_not_off_axes is ${unpinned_selection_not_off_axes}`);
// Check if entire unpinned selection is off axes, because we will be
@@ -356,6 +364,7 @@ const mapStateToProps = (state, ownProps) => {
media_index: state.media_index,
xValues: state.derived.xValues,
yValues: state.derived.yValues,
+ axes_variables_scale: state.axesVariables,
};
};
diff --git a/web-server/plugins/slycat-parameter-image/js/Components/Histogram.tsx b/web-server/plugins/slycat-parameter-image/js/Components/Histogram.tsx
index 6841ee3f5..74ffb85b9 100644
--- a/web-server/plugins/slycat-parameter-image/js/Components/Histogram.tsx
+++ b/web-server/plugins/slycat-parameter-image/js/Components/Histogram.tsx
@@ -35,7 +35,7 @@ type HistogramProps = {
},
) => void;
handleBackgroundClick: (event: React.MouseEvent) => void;
- tickFormatter: (value: number) => string;
+ tickFormatter: ((value: number, index?: number) => string) | null;
};
const Histogram: React.FC = (props) => {
@@ -207,13 +207,13 @@ const Histogram: React.FC = (props) => {
})
.append("title")
.text((bin) => {
- const x0 = tickFormatter ? tickFormatter(bin.x0) : bin.x0;
- const x1 = tickFormatter ? tickFormatter(bin.x1) : bin.x1;
+ const x0 = tickFormatter && bin.x0 !== undefined ? tickFormatter(bin.x0) : bin.x0;
+ const x1 = tickFormatter && bin.x1 !== undefined ? tickFormatter(bin.x1) : bin.x1;
return `Count: ${bin.length}\n\nRange: ${x0} (inclusive) to \n${x1} (exclusive, except for last bar)`;
});
let x_axis = d3.axisBottom(x_scale).tickSizeOuter(0);
-
+
// Create tick values by combining the x0 and x1 values of each bin.
// That way we get a tick at start and end of each bin.
let tickValues = [...bins.map((bin) => bin.x0), ...bins.map((bin) => bin.x1)];
diff --git a/web-server/plugins/slycat-parameter-image/js/Components/PSHistogram.tsx b/web-server/plugins/slycat-parameter-image/js/Components/PSHistogram.tsx
index 6065902b8..1e4226911 100644
--- a/web-server/plugins/slycat-parameter-image/js/Components/PSHistogram.tsx
+++ b/web-server/plugins/slycat-parameter-image/js/Components/PSHistogram.tsx
@@ -125,7 +125,7 @@ const PSHistogram: React.FC = (props) => {
const handleBinClick = (
event: React.MouseEvent,
bin: {
- range: number[];
+ range: (number | undefined)[];
count: number;
index: number;
bins_length: number;
diff --git a/web-server/plugins/slycat-parameter-image/js/ui.js b/web-server/plugins/slycat-parameter-image/js/ui.js
index 790891c6f..b01da6a3f 100644
--- a/web-server/plugins/slycat-parameter-image/js/ui.js
+++ b/web-server/plugins/slycat-parameter-image/js/ui.js
@@ -18,7 +18,6 @@ import bookmark_manager from "js/slycat-bookmark-manager";
import * as dialog from "js/slycat-dialog";
import NoteManager from "./note-manager";
import FilterManager from "./filter-manager";
-// import d3 from "d3";
import URI from "urijs";
import * as chunker from "js/chunker";
import "./parameter-image-scatterplot";
diff --git a/webpack.common.js b/webpack.common.js
index 2b19add79..b7d7ccb23 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -18,6 +18,8 @@ const branch = execSync('git rev-parse --abbrev-ref HEAD')
// importing vtk rules for for vtk.js package to work
var vtkRules = require('vtk.js/Utilities/config/dependency.js').webpack.core.rules;
+const favicon = 'web-server/img/favicon.ico';
+
module.exports = {
experiments: {
// Support the Top Level Await Stage 3 proposal, it makes the module an async module when await is used on the top-level.
@@ -85,36 +87,43 @@ module.exports = {
new HtmlWebpackPlugin({
template: 'web-server/plugins/slycat-run-command/ui.html',
filename: 'ui_run_command.html',
+ favicon: favicon,
chunks: ['ui_run_command'],
}),
new HtmlWebpackPlugin({
template: 'web-server/plugins/slycat-smb/ui.html',
filename: 'ui_smb.html',
+ favicon: favicon,
chunks: ['ui_smb'],
}),
new HtmlWebpackPlugin({
template: 'web-server/templates/slycat-projects.html',
filename: 'slycat_projects.html',
+ favicon: favicon,
chunks: ['slycat_projects'],
}),
new HtmlWebpackPlugin({
template: 'web-server/templates/slycat-project.html',
filename: 'slycat_project.html',
+ favicon: favicon,
chunks: ['slycat_project'],
}),
new HtmlWebpackPlugin({
template: 'web-server/templates/slycat-page.html',
filename: 'slycat_page.html',
+ favicon: favicon,
chunks: ['slycat_page'],
}),
new HtmlWebpackPlugin({
template: 'web-server/templates/slycat-model-page.html',
filename: 'slycat_model.html',
+ favicon: favicon,
chunks: ['slycat_model'],
}),
new HtmlWebpackPlugin({
template: 'web-server/slycat-login/index.html',
filename: 'slycat_login.html',
+ favicon: favicon,
chunks: ['slycat_login'],
}),
// Copying our documentation manual into the dist folder, from docs/manual/html to dist/docs