mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 17:34:10 +03:00
Database Visualizations (https://github.com/enso-org/ide/pull/1335)
Original commit: 132ae3e2fe
This commit is contained in:
parent
1205a5dcda
commit
6aeff8f8a4
@ -11,6 +11,11 @@
|
||||
will be lost. In this build we added notification in statusbar to signalize
|
||||
that the connection was lost and IDE must be restarted. In future IDE will try
|
||||
to automatically reconnect.
|
||||
- [Database Visualizations][1335]. Visualizations for the Database library have
|
||||
been added. The Table visualization now automatically executes the underlying
|
||||
query to display its results in a table. In addition, the SQL Query
|
||||
visualization allows the user to see the query that is going to be run against
|
||||
the database.
|
||||
|
||||
<br/>![Bug Fixes](/docs/assets/tags/bug_fixes.svg)
|
||||
|
||||
|
@ -20,6 +20,16 @@ pub fn table_visualization() -> visualization::java_script::FallibleDefinition {
|
||||
visualization::java_script::Definition::new_builtin(source)
|
||||
}
|
||||
|
||||
/// Return a `JavaScript` SQL visualization.
|
||||
pub fn sql_visualization() -> visualization::java_script::FallibleDefinition {
|
||||
let loading_scripts = include_str!("java_script/helpers/loading.js");
|
||||
let scrollable = include_str!("java_script/helpers/scrollable.js");
|
||||
let source = include_str!("java_script/sql.js");
|
||||
let source = format!("{}{}{}",loading_scripts,scrollable,source);
|
||||
|
||||
visualization::java_script::Definition::new_builtin(source)
|
||||
}
|
||||
|
||||
/// Return a `JavaScript` Scatter plot visualization.
|
||||
pub fn scatter_plot_visualization() -> visualization::java_script::FallibleDefinition {
|
||||
let loading_scripts = include_str!("java_script/helpers/loading.js");
|
||||
|
@ -0,0 +1,440 @@
|
||||
/** SQL visualization. */
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/** The qualified name of the Text type. */
|
||||
const textType = 'Builtins.Main.Text'
|
||||
|
||||
/** The module prefix added for unknown SQL types. */
|
||||
const customSqlTypePrefix = 'Standard.Database.Data.Sql.Sql_Type.'
|
||||
|
||||
/** Specifies opacity of interpolation background color. */
|
||||
const interpolationBacgroundOpacity = 0.3
|
||||
|
||||
/** The CSS styles for the visualization. */
|
||||
const visualizationStyle = `
|
||||
<style>
|
||||
.sql {
|
||||
font-family: DejaVuSansMonoBook, sans-serif;
|
||||
font-size: 12px;
|
||||
margin-left: 7px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.interpolation {
|
||||
border-radius: 6px;
|
||||
padding:1px 2px 1px 2px;
|
||||
display: inline;
|
||||
}
|
||||
.mismatch-parent {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.mismatch-mouse-area {
|
||||
display: inline;
|
||||
position: absolute;
|
||||
width: 150%;
|
||||
height: 150%;
|
||||
align-self: center;
|
||||
z-index: 0;
|
||||
}
|
||||
.mismatch {
|
||||
z-index: 1;
|
||||
}
|
||||
.modulepath {
|
||||
color: rgba(150, 150, 150, 0.9);
|
||||
}
|
||||
.tooltip {
|
||||
font-family: DejaVuSansMonoBook, sans-serif;
|
||||
font-size: 12px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
background-color: rgba(249, 249, 249, 1);
|
||||
box-shadow: 0 0 16px rgba(0, 0, 0, 0.16);
|
||||
text-align: left;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
z-index: 99999;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
|
||||
// =============================
|
||||
// === Script Initialisation ===
|
||||
// =============================
|
||||
|
||||
loadScript('https://cdnjs.cloudflare.com/ajax/libs/sql-formatter/4.0.2/sql-formatter.min.js')
|
||||
|
||||
// ===========================
|
||||
// === Table visualization ===
|
||||
// ===========================
|
||||
|
||||
/**
|
||||
* A visualization that pretty-prints generated SQL code and displays type hints related to
|
||||
* interpolated query parameters.
|
||||
*/
|
||||
class SqlVisualization extends Visualization {
|
||||
// TODO Change the type below once #837 is done:
|
||||
// 'Standard.Database.Data.Table.Table | Standard.Database.Data.Column.Column'
|
||||
static inputType = 'Any'
|
||||
static label = 'SQL Query'
|
||||
|
||||
constructor(api) {
|
||||
super(api)
|
||||
this.setPreprocessorModule('Standard.Visualization.Sql.Visualization')
|
||||
this.setPreprocessorCode(`x -> here.prepare_visualization x`)
|
||||
}
|
||||
|
||||
onDataReceived(data) {
|
||||
this.removeAllChildren()
|
||||
|
||||
let parsedData = data
|
||||
if (typeof data === 'string') {
|
||||
parsedData = JSON.parse(data)
|
||||
}
|
||||
|
||||
let visHtml = visualizationStyle
|
||||
if (parsedData.error !== undefined) {
|
||||
visHtml += parsedData.error
|
||||
} else {
|
||||
const params = parsedData.interpolations.map(param =>
|
||||
renderInterpolationParameter(this.theme, param)
|
||||
)
|
||||
|
||||
let language = 'sql'
|
||||
if (parsedData.dialect == 'postgresql') {
|
||||
language = 'postgresql'
|
||||
}
|
||||
|
||||
const formatted = sqlFormatter.format(parsedData.code, {
|
||||
params: params,
|
||||
language: language,
|
||||
})
|
||||
|
||||
const codeRepresentation = '<pre class="sql">' + formatted + '</pre>'
|
||||
visHtml += codeRepresentation
|
||||
}
|
||||
|
||||
const containers = this.createContainers()
|
||||
const parentContainer = containers.parent
|
||||
containers.scrollable.innerHTML = visHtml
|
||||
this.dom.appendChild(parentContainer)
|
||||
|
||||
const tooltip = new Tooltip(parentContainer)
|
||||
const baseMismatches = this.dom.getElementsByClassName('mismatch')
|
||||
const extendedMismatchAreas = this.dom.getElementsByClassName('mismatch-mouse-area')
|
||||
setupMouseInteractionForMismatches(tooltip, baseMismatches)
|
||||
setupMouseInteractionForMismatches(tooltip, extendedMismatchAreas)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all children of this visualization's DOM.
|
||||
*
|
||||
* May be used to reset the visualization's content.
|
||||
*/
|
||||
removeAllChildren() {
|
||||
while (this.dom.firstChild) {
|
||||
this.dom.removeChild(this.dom.lastChild)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates containers for the visualization.
|
||||
*/
|
||||
createContainers() {
|
||||
const parentContainer = document.createElement('div')
|
||||
parentContainer.setAttributeNS(null, 'style', 'position: relative;')
|
||||
const width = this.dom.getAttributeNS(null, 'width')
|
||||
const height = this.dom.getAttributeNS(null, 'height')
|
||||
const scrollable = document.createElement('div')
|
||||
scrollable.setAttributeNS(null, 'id', 'vis-sql-view')
|
||||
scrollable.setAttributeNS(null, 'class', 'scrollable')
|
||||
scrollable.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height)
|
||||
scrollable.setAttributeNS(null, 'width', '100%')
|
||||
scrollable.setAttributeNS(null, 'height', '100%')
|
||||
const viewStyle = `width: ${width - 5}px;
|
||||
height: ${height - 5}px;
|
||||
overflow: scroll;
|
||||
padding:2.5px;`
|
||||
scrollable.setAttributeNS(null, 'style', viewStyle)
|
||||
parentContainer.appendChild(scrollable)
|
||||
return {
|
||||
parent: parentContainer,
|
||||
scrollable: scrollable,
|
||||
}
|
||||
}
|
||||
|
||||
setSize(size) {
|
||||
this.dom.setAttributeNS(null, 'width', size[0])
|
||||
this.dom.setAttributeNS(null, 'height', size[1])
|
||||
}
|
||||
}
|
||||
|
||||
// === Handling Colors ===
|
||||
|
||||
/**
|
||||
* Renders a 4-element array representing a color into a CSS-compatible rgba string.
|
||||
*/
|
||||
function convertColorToRgba(color) {
|
||||
const r = 255 * color.red
|
||||
const g = 255 * color.green
|
||||
const b = 255 * color.blue
|
||||
const a = color.alpha
|
||||
return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'
|
||||
}
|
||||
|
||||
/** Replaces the alpha component of a color (represented as a 4-element array),
|
||||
* returning a new color.
|
||||
*/
|
||||
function replaceAlpha(color, newAlpha) {
|
||||
return {
|
||||
red: color.red,
|
||||
green: color.green,
|
||||
blue: color.blue,
|
||||
alpha: newAlpha,
|
||||
}
|
||||
}
|
||||
|
||||
// === HTML Rendering Helpers ===
|
||||
|
||||
/**
|
||||
* Renders a HTML representation of a message to be displayed in a tooltip,
|
||||
* which explains a type mismatch.
|
||||
*/
|
||||
function renderTypeHintMessage(
|
||||
receivedTypeName,
|
||||
expectedTypeName,
|
||||
receivedTypeColor,
|
||||
expectedTypeColor
|
||||
) {
|
||||
const received = new QualifiedTypeName(receivedTypeName)
|
||||
const expected = new QualifiedTypeName(expectedTypeName)
|
||||
|
||||
let receivedPrefix = ''
|
||||
if (received.moduleName !== null) {
|
||||
receivedPrefix = '<span class="modulepath">' + received.moduleName + '.</span>'
|
||||
}
|
||||
const receivedStyledSpan = '<span style="color: ' + convertColorToRgba(receivedTypeColor) + '">'
|
||||
const receivedSuffix = receivedStyledSpan + received.name + '</span>'
|
||||
|
||||
let expectedPrefix = ''
|
||||
if (expected.moduleName !== null) {
|
||||
expectedPrefix = '<span class="modulepath">' + expected.moduleName + '.</span>'
|
||||
}
|
||||
const expectedStyledSpan = '<span style="color: ' + convertColorToRgba(expectedTypeColor) + '">'
|
||||
const expectedSuffix = expectedStyledSpan + expected.name + '</span>'
|
||||
|
||||
let message = 'Received ' + receivedPrefix + receivedSuffix + '<br>'
|
||||
message += 'Expected ' + expectedPrefix + expectedSuffix + '<br>'
|
||||
message += 'The database may perform an auto conversion.'
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a qualified type name.
|
||||
*
|
||||
* The `moduleName` field is the name of the module the type is from. It may be null if the original
|
||||
* type name did not contain a module name.
|
||||
* The `name` field is the simple name of the type itself.
|
||||
*/
|
||||
class QualifiedTypeName {
|
||||
/** Creates a QualifiedTypeName instance from a string representation. */
|
||||
constructor(typeName) {
|
||||
let ix = typeName.lastIndexOf('.')
|
||||
if (ix < 0) {
|
||||
this.moduleName = null
|
||||
this.name = typeName
|
||||
} else {
|
||||
this.moduleName = typeName.substr(0, ix)
|
||||
this.name = typeName.substr(ix + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders HTML for displaying an Enso parameter that is interpolated into the SQL code.
|
||||
*/
|
||||
function renderInterpolationParameter(theme, param) {
|
||||
const actualType = param.actual_type
|
||||
let value = param.value
|
||||
|
||||
if (actualType == textType) {
|
||||
value = "'" + value.replaceAll("'", "''") + "'"
|
||||
}
|
||||
|
||||
const actualTypeColor = theme.getColorForType(actualType)
|
||||
const fgColor = actualTypeColor
|
||||
let bgColor = replaceAlpha(fgColor, interpolationBacgroundOpacity)
|
||||
const expectedEnsoType = param.expected_enso_type
|
||||
|
||||
if (actualType == expectedEnsoType) {
|
||||
return renderRegularInterpolation(value, fgColor, bgColor)
|
||||
} else {
|
||||
let expectedType = expectedEnsoType
|
||||
if (expectedType === null) {
|
||||
expectedType = customSqlTypePrefix + param.expected_sql_type
|
||||
}
|
||||
|
||||
const expectedTypeColor = theme.getColorForType(expectedType)
|
||||
const hoverBgColor = expectedTypeColor
|
||||
bgColor = replaceAlpha(hoverBgColor, interpolationBacgroundOpacity)
|
||||
const hoverFgColor = theme.getForegroundColorForType(expectedType)
|
||||
|
||||
const message = renderTypeHintMessage(
|
||||
actualType,
|
||||
expectedType,
|
||||
actualTypeColor,
|
||||
expectedTypeColor
|
||||
)
|
||||
|
||||
return renderMismatchedInterpolation(
|
||||
value,
|
||||
message,
|
||||
fgColor,
|
||||
bgColor,
|
||||
hoverFgColor,
|
||||
hoverBgColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper that renders the HTML representation of a regular SQL interpolation.
|
||||
*/
|
||||
function renderRegularInterpolation(value, fgColor, bgColor) {
|
||||
let html =
|
||||
'<div class="interpolation" style="color:' +
|
||||
convertColorToRgba(fgColor) +
|
||||
';background-color:' +
|
||||
convertColorToRgba(bgColor) +
|
||||
';">'
|
||||
html += value
|
||||
html += '</div>'
|
||||
return html
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper that renders the HTML representation of a type-mismatched SQL interpolation.
|
||||
*
|
||||
* This only prepares the HTML code, to setup the interactions, `setupMouseInteractionForMismatches`
|
||||
* must be called after these HTML elements are added to the DOM.
|
||||
*/
|
||||
function renderMismatchedInterpolation(
|
||||
value,
|
||||
message,
|
||||
fgColor,
|
||||
bgColor,
|
||||
hoverFgColor,
|
||||
hoverBgColor
|
||||
) {
|
||||
let html = '<div class="mismatch-parent">'
|
||||
html += '<div class="mismatch-mouse-area"></div>'
|
||||
html += '<div class="interpolation mismatch"'
|
||||
let style = 'color:' + convertColorToRgba(fgColor) + ';'
|
||||
style += 'background-color:' + convertColorToRgba(bgColor) + ';'
|
||||
html += ' style="' + style + '"'
|
||||
html += ' data-fgColor="' + convertColorToRgba(fgColor) + '"'
|
||||
html += ' data-bgColor="' + convertColorToRgba(bgColor) + '"'
|
||||
html += ' data-fgColorHover="' + convertColorToRgba(hoverFgColor) + '"'
|
||||
html += ' data-bgColorHover="' + convertColorToRgba(hoverBgColor) + '"'
|
||||
html += ' data-message="' + encodeURIComponent(message) + '"'
|
||||
html += '>'
|
||||
html += value
|
||||
html += '</div>'
|
||||
html += '</div>'
|
||||
return html
|
||||
}
|
||||
|
||||
// === Tooltip ===
|
||||
|
||||
/**
|
||||
* A hint tooltip that can be displayed above elements.
|
||||
*/
|
||||
class Tooltip {
|
||||
constructor(container) {
|
||||
this.tooltip = document.createElement('div')
|
||||
this.tooltip.setAttributeNS(null, 'class', 'tooltip')
|
||||
container.appendChild(this.tooltip)
|
||||
this.tooltipOwner = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the tooltip.
|
||||
*
|
||||
* The actor parameter specifies who is initiating the hiding.
|
||||
* If this method is called but the tooltip has got a new owner in the meantime, the call is
|
||||
* ignored.
|
||||
*/
|
||||
hide(actor) {
|
||||
if (this.tooltipOwner === null || this.tooltipOwner == actor) {
|
||||
this.tooltipOwner = null
|
||||
this.tooltip.style.opacity = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the tooltip above the element represented by `actor`.
|
||||
*
|
||||
* Tooltip content is specified by the `message` which can include arbitrary HTML.
|
||||
*/
|
||||
show(actor, message) {
|
||||
this.tooltipOwner = actor
|
||||
this.tooltip.innerHTML = message
|
||||
this.tooltip.style.opacity = 1
|
||||
|
||||
const interpolantContainer = actor.parentElement
|
||||
const codeContainer = interpolantContainer.parentElement
|
||||
const scrollElement = codeContainer.parentElement
|
||||
|
||||
const scrollOffsetX = scrollElement.scrollLeft
|
||||
const scrollOffsetY = scrollElement.scrollTop + scrollElement.offsetHeight
|
||||
|
||||
const interpolantOffsetX = interpolantContainer.offsetLeft
|
||||
const interpolantOffsetY = interpolantContainer.offsetTop
|
||||
|
||||
const centeringOffset = (interpolantContainer.offsetWidth - this.tooltip.offsetWidth) / 2
|
||||
const belowPadding = 3
|
||||
const belowOffset = interpolantContainer.offsetHeight + belowPadding
|
||||
|
||||
const x = interpolantOffsetX - scrollOffsetX + centeringOffset
|
||||
const y = interpolantOffsetY - scrollOffsetY + belowOffset
|
||||
|
||||
this.tooltip.style.transform = 'translate(' + x + 'px, ' + y + 'px)'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up mouse events for the interpolated parameters that have a type mismatch.
|
||||
*/
|
||||
function setupMouseInteractionForMismatches(tooltip, elements) {
|
||||
function interpolationMouseEnter(event) {
|
||||
const target = this.parentElement.getElementsByClassName('mismatch')[0]
|
||||
const fg = target.getAttribute('data-fgColorHover')
|
||||
const bg = target.getAttribute('data-bgColorHover')
|
||||
const message = decodeURIComponent(target.getAttribute('data-message'))
|
||||
tooltip.show(target, message)
|
||||
target.style.color = fg
|
||||
target.style.backgroundColor = bg
|
||||
}
|
||||
function interpolationMouseLeave(event) {
|
||||
const target = this.parentElement.getElementsByClassName('mismatch')[0]
|
||||
const fg = target.getAttribute('data-fgColor')
|
||||
const bg = target.getAttribute('data-bgColor')
|
||||
target.style.color = fg
|
||||
target.style.backgroundColor = bg
|
||||
tooltip.hide(target)
|
||||
}
|
||||
|
||||
for (let i = 0; i < elements.length; ++i) {
|
||||
elements[i].addEventListener('mouseenter', interpolationMouseEnter)
|
||||
elements[i].addEventListener('mouseleave', interpolationMouseLeave)
|
||||
}
|
||||
}
|
||||
|
||||
return SqlVisualization
|
@ -16,16 +16,8 @@ class TableVisualization extends Visualization {
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
this.setPreprocessorModule('Standard.Table')
|
||||
this.setPreprocessorCode(`
|
||||
x -> case x of
|
||||
Table.Table _ ->
|
||||
header = ["header", x.columns.map .name]
|
||||
data = ["data", x.columns.map .to_vector . map (x -> x.take_start 2000) ]
|
||||
pairs = [header,data]
|
||||
Json.from_pairs pairs . to_text
|
||||
_ -> x . to_json . to_text
|
||||
`)
|
||||
this.setPreprocessorModule('Standard.Visualization.Table.Visualization')
|
||||
this.setPreprocessorCode(`x -> here.prepare_visualization x 1000`)
|
||||
}
|
||||
|
||||
onDataReceived(data) {
|
||||
@ -132,9 +124,13 @@ class TableVisualization extends Visualization {
|
||||
|
||||
function genGenericTable(data, level) {
|
||||
let result = ''
|
||||
data.forEach((point, ix) => {
|
||||
result += '<tr><th>' + ix + '</th>' + toTableCell(point, level) + '</tr>'
|
||||
})
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach((point, ix) => {
|
||||
result += '<tr><th>' + ix + '</th>' + toTableCell(point, level) + '</tr>'
|
||||
})
|
||||
} else {
|
||||
result += '<tr>' + toTableCell(data, level) + '</tr>'
|
||||
}
|
||||
return tableOf(result, level)
|
||||
}
|
||||
|
||||
@ -174,17 +170,69 @@ class TableVisualization extends Visualization {
|
||||
}
|
||||
}
|
||||
|
||||
function genDataframe(parsedData) {
|
||||
let result = ''
|
||||
function addHeader(content) {
|
||||
result += '<th>' + content + '</th>'
|
||||
}
|
||||
function addCell(content) {
|
||||
result += '<td class="plaintext">' + content + '</td>'
|
||||
}
|
||||
result += '<tr>'
|
||||
parsedData.indices_header.forEach(addHeader)
|
||||
parsedData.header.forEach(addHeader)
|
||||
result += '</tr>'
|
||||
let rows = 0
|
||||
if (parsedData.data.length > 0) {
|
||||
rows = parsedData.data[0].length
|
||||
} else if (parsedData.indices.length > 0) {
|
||||
rows = parsedData.indices[0].length
|
||||
}
|
||||
for (let i = 0; i < rows; ++i) {
|
||||
result += '<tr>'
|
||||
parsedData.indices.forEach(ix => addHeader(ix[i]))
|
||||
parsedData.data.forEach(col => addCell(col[i]))
|
||||
result += '</tr>'
|
||||
}
|
||||
return tableOf(result, 0)
|
||||
}
|
||||
|
||||
while (this.dom.firstChild) {
|
||||
this.dom.removeChild(this.dom.lastChild)
|
||||
}
|
||||
|
||||
const style_dark = `
|
||||
<style>
|
||||
table {
|
||||
table, .hiddenrows {
|
||||
font-family: DejaVuSansMonoBook, sans-serif;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 1px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
table > tbody > tr:first-child > th:first-child,
|
||||
table > tbody > tr:first-child > td:first-child {
|
||||
border-top-left-radius: 9px;
|
||||
}
|
||||
|
||||
table > tbody > tr:first-child > th:last-child,
|
||||
table > tbody > tr:first-child > td:last-child {
|
||||
border-top-right-radius: 9px;
|
||||
}
|
||||
|
||||
table > tbody > tr:last-child > th:first-child,
|
||||
table > tbody > tr:last-child > td:first-child {
|
||||
border-bottom-left-radius: 9px;
|
||||
}
|
||||
|
||||
table > tbody > tr:last-child > th:last-child,
|
||||
table > tbody > tr:last-child > td:last-child {
|
||||
border-bottom-right-radius: 9px;
|
||||
}
|
||||
|
||||
td {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
padding: 0;
|
||||
@ -201,25 +249,58 @@ class TableVisualization extends Visualization {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
th {
|
||||
th, .hiddenrows {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
td {
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: rgba(255, 255, 200, 0.1);
|
||||
}
|
||||
|
||||
.hiddenrows {
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
|
||||
const style_light = `
|
||||
<style>
|
||||
table {
|
||||
table, .hiddenrows {
|
||||
font-family: DejaVuSansMonoBook, sans-serif;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 1px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
table > tbody > tr:first-child > th:first-child,
|
||||
table > tbody > tr:first-child > td:first-child {
|
||||
border-top-left-radius: 9px;
|
||||
}
|
||||
|
||||
table > tbody > tr:first-child > th:last-child,
|
||||
table > tbody > tr:first-child > td:last-child {
|
||||
border-top-right-radius: 9px;
|
||||
}
|
||||
|
||||
table > tbody > tr:last-child > th:first-child,
|
||||
table > tbody > tr:last-child > td:first-child {
|
||||
border-bottom-left-radius: 9px;
|
||||
}
|
||||
|
||||
table > tbody > tr:last-child > th:last-child,
|
||||
table > tbody > tr:last-child > td:last-child {
|
||||
border-bottom-right-radius: 9px;
|
||||
}
|
||||
|
||||
td {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
padding: 0;
|
||||
@ -236,15 +317,23 @@ class TableVisualization extends Visualization {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
th {
|
||||
th, .hiddenrows {
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
td {
|
||||
background-color: rgba(0, 0, 0, 0.025);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: rgba(30, 30, 20, 0.1);
|
||||
}
|
||||
|
||||
.hiddenrows {
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>`
|
||||
|
||||
const width = this.dom.getAttributeNS(null, 'width')
|
||||
@ -255,8 +344,8 @@ class TableVisualization extends Visualization {
|
||||
tabElem.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height)
|
||||
tabElem.setAttributeNS(null, 'width', '100%')
|
||||
tabElem.setAttributeNS(null, 'height', '100%')
|
||||
const tblViewStyle = `width: ${width - 10}px;
|
||||
height: ${height - 10}px;
|
||||
const tblViewStyle = `width: ${width - 5}px;
|
||||
height: ${height - 5}px;
|
||||
overflow: scroll;
|
||||
padding:2.5px;`
|
||||
tabElem.setAttributeNS(null, 'style', tblViewStyle)
|
||||
@ -271,8 +360,34 @@ class TableVisualization extends Visualization {
|
||||
if (document.getElementById('root').classList.contains('dark-theme')) {
|
||||
style = style_dark
|
||||
}
|
||||
const table = genTable(parsedData.data || parsedData, 0, parsedData.header)
|
||||
tabElem.innerHTML = style + table
|
||||
|
||||
if (parsedData.error !== undefined) {
|
||||
tabElem.innerHTML = 'Error: ' + parsedData.error
|
||||
} else if (parsedData.json !== undefined) {
|
||||
const table = genTable(parsedData.json, 0, undefined)
|
||||
tabElem.innerHTML = style + table
|
||||
} else {
|
||||
const table = genDataframe(parsedData)
|
||||
let suffix = ''
|
||||
const allRowsCount = parsedData.all_rows_count
|
||||
if (allRowsCount !== undefined) {
|
||||
const includedRowsCount = parsedData.data.length > 0 ? parsedData.data[0].length : 0
|
||||
const hiddenCount = allRowsCount - includedRowsCount
|
||||
if (hiddenCount > 0) {
|
||||
let rows = 'rows'
|
||||
if (hiddenCount == 1) {
|
||||
rows = 'row'
|
||||
}
|
||||
suffix =
|
||||
'<span class="hiddenrows">… and ' +
|
||||
hiddenCount +
|
||||
' more ' +
|
||||
rows +
|
||||
'.</span>'
|
||||
}
|
||||
}
|
||||
tabElem.innerHTML = style + table + suffix
|
||||
}
|
||||
}
|
||||
|
||||
setSize(size) {
|
||||
|
@ -86,6 +86,7 @@ impl Registry {
|
||||
self.try_add_java_script(builtin::visualization::java_script::scatter_plot_visualization());
|
||||
self.try_add_java_script(builtin::visualization::java_script::histogram_visualization());
|
||||
self.try_add_java_script(builtin::visualization::java_script::table_visualization());
|
||||
self.try_add_java_script(builtin::visualization::java_script::sql_visualization());
|
||||
self.try_add_java_script(builtin::visualization::java_script::geo_map_visualization());
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user