diff --git a/app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js b/app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js
index 49d5fa889792..0abc083096e1 100644
--- a/app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js
+++ b/app/gui/view/graph-editor/src/builtin/visualization/java_script/table.js
@@ -28,7 +28,19 @@ class TableVisualization extends Visualization {
constructor(data) {
super(data)
- this.setPreprocessor('Standard.Visualization.Table.Visualization', 'prepare_visualization')
+ this.setRowLimitAndPage(1000, 0)
+ }
+
+ setRowLimitAndPage(row_limit, page) {
+ if (this.row_limit !== row_limit || this.page !== page) {
+ this.row_limit = row_limit
+ this.page = page
+ this.setPreprocessor(
+ 'Standard.Visualization.Table.Visualization',
+ 'prepare_visualization',
+ this.row_limit.toString()
+ )
+ }
}
onDataReceived(data) {
@@ -79,21 +91,46 @@ class TableVisualization extends Visualization {
return content
}
+ function cellRenderer(params) {
+ if (params.value === null) {
+ return 'Nothing'
+ } else if (params.value === undefined) {
+ return ''
+ } else if (params.value === '') {
+ return 'Empty'
+ }
+ return params.value.toString()
+ }
+
if (!this.tabElem) {
while (this.dom.firstChild) {
this.dom.removeChild(this.dom.lastChild)
}
const style =
- '.ag-theme-alpine { --ag-grid-size: 3px; --ag-list-item-height: 20px; display: inline; }'
+ '.ag-theme-alpine { --ag-grid-size: 3px; --ag-list-item-height: 20px; display: inline; }\n' +
+ '.vis-status-bar { height: 20x; background-color: white; font-size:14px; white-space:nowrap; padding: 0 5px; overflow:hidden; border-radius: 16px }\n' +
+ '.vis-status-bar > button { width: 12px; margin: 0 2px; display: none }\n' +
+ '.vis-tbl-grid { height: calc(100% - 20px); width: 100%; }\n'
const styleElem = document.createElement('style')
styleElem.innerHTML = style
this.dom.appendChild(styleElem)
+ const statusElem = document.createElement('div')
+ statusElem.setAttributeNS(null, 'id', 'vis-tbl-status')
+ statusElem.setAttributeNS(null, 'class', 'vis-status-bar')
+ this.dom.appendChild(statusElem)
+ this.statusElem = statusElem
+
+ const gridElem = document.createElement('div')
+ gridElem.setAttributeNS(null, 'id', 'vis-tbl-grid')
+ gridElem.className = 'vis-tbl-grid'
+ this.dom.appendChild(gridElem)
+
const tabElem = document.createElement('div')
tabElem.setAttributeNS(null, 'id', 'vis-tbl-view')
tabElem.setAttributeNS(null, 'class', 'scrollable ag-theme-alpine')
- this.dom.appendChild(tabElem)
+ gridElem.appendChild(tabElem)
this.tabElem = tabElem
this.agGridOptions = {
@@ -106,6 +143,7 @@ class TableVisualization extends Visualization {
resizable: true,
minWidth: 25,
headerValueGetter: params => params.colDef.field,
+ cellRenderer: cellRenderer,
},
onColumnResized: e => this.lockColumnSize(e),
}
@@ -198,28 +236,106 @@ class TableVisualization extends Visualization {
dataTruncated = parsedData.all_rows_count !== rowData.length
}
- // If the table contains more rows than an upper limit, the engine will send only some of all rows.
+ // Update Status Bar
+ this.updateStatusBarControls(
+ parsedData.all_rows_count === undefined ? 1 : parsedData.all_rows_count,
+ dataTruncated
+ )
+
// If data is truncated, we cannot rely on sorting/filtering so will disable.
- // A pinned row is added to tell the user the row count and that filter/sort is disabled.
- const col_span = '__COL_SPAN__'
- if (dataTruncated) {
- columnDefs[0].colSpan = p => p.data[col_span] || 1
- }
this.agGridOptions.defaultColDef.filter = !dataTruncated
this.agGridOptions.defaultColDef.sortable = !dataTruncated
this.agGridOptions.api.setColumnDefs(columnDefs)
- if (dataTruncated) {
- const field = columnDefs[0].field
- const extraRow = {
- [field]: `Showing ${rowData.length} of ${parsedData.all_rows_count} rows. Sorting and filtering disabled.`,
- [col_span]: columnDefs.length,
- }
- this.agGridOptions.api.setPinnedTopRowData([extraRow])
- }
this.agGridOptions.api.setRowData(rowData)
this.updateTableSize(this.dom.getAttributeNS(null, 'width'))
}
+ makeOption(value, label) {
+ const optionElem = document.createElement('option')
+ optionElem.value = value
+ optionElem.appendChild(document.createTextNode(label))
+ return optionElem
+ }
+
+ makeButton(label, onclick) {
+ const buttonElem = document.createElement('button')
+ buttonElem.name = label
+ buttonElem.appendChild(document.createTextNode(label))
+ buttonElem.addEventListener('click', onclick)
+ return buttonElem
+ }
+
+ // Updates the status bar to reflect the current row limit and page, shown at top of the visualization.
+ // - Creates the row dropdown and page buttons.
+ // - Updated the row counts and filter available options.
+ updateStatusBarControls(all_rows_count, dataTruncated) {
+ const pageLimit = Math.ceil(all_rows_count / this.row_limit)
+ if (this.page > pageLimit) {
+ this.page = pageLimit
+ }
+
+ if (this.statusElem.childElementCount === 0) {
+ this.statusElem.appendChild(
+ this.makeButton('«', () => this.setRowLimitAndPage(this.row_limit, 0))
+ )
+ this.statusElem.appendChild(
+ this.makeButton('‹', () => this.setRowLimitAndPage(this.row_limit, this.page - 1))
+ )
+
+ const selectElem = document.createElement('select')
+ selectElem.name = 'row-limit'
+ selectElem.addEventListener('change', e => {
+ this.setRowLimitAndPage(e.target.value, this.page)
+ })
+ this.statusElem.appendChild(selectElem)
+
+ const rowCountSpanElem = document.createElement('span')
+ this.statusElem.appendChild(rowCountSpanElem)
+
+ this.statusElem.appendChild(
+ this.makeButton('›', () => this.setRowLimitAndPage(this.row_limit, this.page + 1))
+ )
+ this.statusElem.appendChild(
+ this.makeButton('»', () => this.setRowLimitAndPage(this.row_limit, pageLimit - 1))
+ )
+ }
+
+ // Enable/Disable Page buttons
+ this.statusElem.children.namedItem('«').disabled = this.page === 0
+ this.statusElem.children.namedItem('‹').disabled = this.page === 0
+ this.statusElem.children.namedItem('›').disabled = this.page === pageLimit - 1
+ this.statusElem.children.namedItem('»').disabled = this.page === pageLimit - 1
+
+ // Update row limit dropdown and row count
+ const rowCountElem = this.statusElem.getElementsByTagName('span')[0]
+ const rowLimitElem = this.statusElem.children.namedItem('row-limit')
+ if (all_rows_count > 1000) {
+ rowLimitElem.style.display = 'inline-block'
+ const rowCounts = [1000, 2500, 5000, 10000, 25000, 50000, 100000].filter(
+ r => r <= all_rows_count
+ )
+ if (
+ all_rows_count < rowCounts[rowCounts.length - 1] &&
+ rowCounts.indexOf(all_rows_count) === -1
+ ) {
+ rowCounts.push(all_rows_count)
+ }
+ rowLimitElem.innerHTML = ''
+ rowCounts.forEach(r => {
+ const option = this.makeOption(r, r.toString())
+ rowLimitElem.appendChild(option)
+ })
+ rowLimitElem.value = this.row_limit
+
+ rowCountElem.innerHTML = dataTruncated
+ ? ` of ${all_rows_count} rows (Sorting/Filtering disabled).`
+ : ` rows.`
+ } else {
+ rowLimitElem.style.display = 'none'
+ rowCountElem.innerHTML = all_rows_count === 1 ? '1 row.' : `${all_rows_count} rows.`
+ }
+ }
+
updateTableSize(clientWidth) {
// Check the grid has been initialised and return if not.
if (!this.agGridOptions) {
diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso
index 39a4794cd60e..e97d9bec3014 100644
--- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso
+++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Text/Extensions.enso
@@ -40,6 +40,8 @@ from project.Data.Json import Json, Invalid_JSON, JS_Object
from project.Data.Numbers import Decimal, Integer, Number, Number_Parse_Error
from project.Data.Text.Text_Sub_Range import Codepoint_Ranges, Text_Sub_Range
+from project.Metadata import make_single_choice
+
import project.Data.Index_Sub_Range as Index_Sub_Range_Module
polyglot java import com.ibm.icu.lang.UCharacter
@@ -333,7 +335,8 @@ Text.match self pattern=".*" case_sensitivity=Case_Sensitivity.Sensitive =
Split with a vector of strings.
'azbzczdzezfzg'.split ['b', 'zez'] == ['az', 'zczd', 'fzg']
-Text.split : Text | Vector Text -> Case_Sensitivity -> Boolean -> Vector Text | Illegal_Argument
+@delimiter (make_single_choice [',', ';', '|', ['{tab}', "'\t'"], ['{space}', "' '"], ['{newline}', "['\n', '\r\n', '\r']"], ['Custom', ""]])
+Text.split : Text | Vector Text -> Case_Sensitivity -> Boolean -> Vector Text | Illegal_Argument
Text.split self delimiter="," case_sensitivity=Case_Sensitivity.Sensitive use_regex=False =
delimiter_is_empty = case delimiter of
_ : Text -> delimiter.is_empty
diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Metadata.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Metadata.enso
index 6f669108d347..c985a248e65e 100644
--- a/distribution/lib/Standard/Base/0.0.0-dev/src/Metadata.enso
+++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Metadata.enso
@@ -77,3 +77,11 @@ type Widget
## Describes a file chooser.
File_Browse label:(Nothing | Text)=Nothing display:Display=Display.When_Modified action:File_Action=File_Action.Open file_types:(Vector Pair)=[Pair.new "All Files" "*.*"]
+
+## PRIVATE
+make_single_choice : Vector -> Display -> Widget
+make_single_choice values display=Display.Always =
+ make_option value = case value of
+ _ : Vector -> Choice.Option value.first value.second
+ _ : Text -> Choice.Option value value.pretty
+ Widget.Single_Choice (values.map make_option) Nothing display
diff --git a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Table/Visualization.enso b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Table/Visualization.enso
index 16d12e2819fa..8b4be931d5f0 100644
--- a/distribution/lib/Standard/Visualization/0.0.0-dev/src/Table/Visualization.enso
+++ b/distribution/lib/Standard/Visualization/0.0.0-dev/src/Table/Visualization.enso
@@ -39,6 +39,10 @@ prepare_visualization y max_rows=1000 =
dataframe = x.read max_rows
all_rows_count = x.row_count
make_json_for_table dataframe [] all_rows_count
+ _ : Function ->
+ pairs = [['_display_text_', '[Function '+x.to_text+']']]
+ value = JS_Object.from_pairs pairs
+ JS_Object.from_pairs [["json", value]]
_ ->
js_value = x.to_js_object
value = if js_value.is_a JS_Object . not then js_value else
@@ -96,7 +100,7 @@ make_json_for_object_matrix current vector idx=0 = if idx == vector.length then
_ ->
js_object = row.to_js_object
if js_object.is_a JS_Object . not then False else
- if js_object.field_names.sort == ["type" , "constructor"] then False else
+ if js_object.field_names.sort == ["constructor", "type"] then False else
pairs = js_object.field_names.map f-> [f, make_json_for_value (js_object.get f)]
JS_Object.from_pairs pairs
if to_append == False then Nothing else
@@ -184,4 +188,5 @@ make_json_for_value val level=0 = case val of
truncated = val.columns.take 5 . map _.name
prepared = if val.column_count > 5 then truncated + ["… " + (val.column_count - 5).to_text+ " more"] else truncated
"Table{" + val.row_count.to_text + " rows x [" + (prepared.join ", ") + "]}"
+ _ : Function -> "[Function "+val.to_text+"]"
_ -> val.to_display_text