mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 18:18:53 +08:00
Compare commits
4 Commits
218ab52ab3
...
graph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2af0bba3 | ||
|
|
e4b35bac0a | ||
|
|
3d1e822cdc | ||
|
|
3d6479be7a |
23766
package-lock.json
generated
23766
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
src/App.vue
42
src/App.vue
@@ -33,48 +33,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
src: url('@/assets/fonts/OpenSans-Regular.woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
src: url('@/assets/fonts/OpenSans-SemiBold.woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
src: url('@/assets/fonts/OpenSans-Bold.woff2');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
src: url('@/assets/fonts/OpenSans-Italic.woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
src: url('@/assets/fonts/OpenSans-SemiBoldItalic.woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Open Sans';
|
|
||||||
src: url('@/assets/fonts/OpenSans-BoldItalic.woff2');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app,
|
#app,
|
||||||
.dialog,
|
.dialog,
|
||||||
input,
|
input,
|
||||||
|
|||||||
@@ -4,3 +4,10 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-view-warning {
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|||||||
45
src/assets/styles/typography.css
Normal file
45
src/assets/styles/typography.css
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url('@/assets/fonts/OpenSans-Regular.woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url('@/assets/fonts/OpenSans-SemiBold.woff2');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url('@/assets/fonts/OpenSans-Bold.woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url('@/assets/fonts/OpenSans-Italic.woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url('@/assets/fonts/OpenSans-SemiBoldItalic.woff2');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Open Sans';
|
||||||
|
src: url('@/assets/fonts/OpenSans-BoldItalic.woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-accent-shade);
|
||||||
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<Field label="Adjust sizes">
|
<Field label="Scaling ratio">
|
||||||
|
<NumericInput
|
||||||
|
:value="modelValue.scalingRatio"
|
||||||
|
@update="update('scalingRatio', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label="Prevent overlapping">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="booleanOptions"
|
:options="booleanOptions"
|
||||||
:activeOption="modelValue.adjustSizes"
|
:activeOption="modelValue.adjustSizes"
|
||||||
@@ -22,13 +29,6 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Gravity">
|
|
||||||
<NumericInput
|
|
||||||
:value="modelValue.gravity"
|
|
||||||
@update="update('gravity', $event)"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Strong gravity mode">
|
<Field label="Strong gravity mode">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="booleanOptions"
|
:options="booleanOptions"
|
||||||
|
|||||||
@@ -39,7 +39,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-if="modelValue.type !== 'constant'" label="Color as">
|
<Field
|
||||||
|
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||||
|
label="Color as"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="сolorAsOptions"
|
:options="сolorAsOptions"
|
||||||
:activeOption="modelValue.mode"
|
:activeOption="modelValue.mode"
|
||||||
@@ -47,7 +50,10 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-if="modelValue.type !== 'constant'" label="Colorscale direction">
|
<Field
|
||||||
|
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||||
|
label="Colorscale direction"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="сolorscaleDirections"
|
:options="сolorscaleDirections"
|
||||||
:activeOption="modelValue.colorscaleDirection"
|
:activeOption="modelValue.colorscaleDirection"
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Scaling ratio">
|
<Field label="Gravity">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.scalingRatio"
|
:value="modelValue.gravity"
|
||||||
@update="update('scalingRatio', $event)"
|
@update="update('gravity', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field>
|
<Field v-if="modelValue.type === 'variable'">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="colorSourceUsageOptions"
|
:options="colorSourceUsageOptions"
|
||||||
:activeOption="modelValue.sourceUsage"
|
:activeOption="modelValue.sourceUsage"
|
||||||
@@ -45,7 +45,10 @@
|
|||||||
</template>
|
</template>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-if="modelValue.type !== 'constant'" label="Color as">
|
<Field
|
||||||
|
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||||
|
label="Color as"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="сolorAsOptions"
|
:options="сolorAsOptions"
|
||||||
:activeOption="modelValue.mode"
|
:activeOption="modelValue.mode"
|
||||||
@@ -53,7 +56,10 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-if="modelValue.type !== 'constant'" label="Colorscale direction">
|
<Field
|
||||||
|
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||||
|
label="Colorscale direction"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="сolorscaleDirections"
|
:options="сolorscaleDirections"
|
||||||
:activeOption="modelValue.colorscaleDirection"
|
:activeOption="modelValue.colorscaleDirection"
|
||||||
|
|||||||
31
src/components/svg/settings.vue
Normal file
31
src/components/svg/settings.vue
Normal file
File diff suppressed because one or more lines are too long
@@ -5,8 +5,39 @@ const TYPE_NODE = 0
|
|||||||
const TYPE_EDGE = 1
|
const TYPE_EDGE = 1
|
||||||
const DEFAULT_SCALE = COLOR_PICKER_CONSTANTS.DEFAULT_SCALE
|
const DEFAULT_SCALE = COLOR_PICKER_CONSTANTS.DEFAULT_SCALE
|
||||||
|
|
||||||
|
export function dataSourceIsValid(dataSources) {
|
||||||
|
const docColumn = Object.keys(dataSources)[0]
|
||||||
|
if (!docColumn) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const records = dataSources[docColumn].slice(0, 10)
|
||||||
|
records.forEach(record => {
|
||||||
|
const parsedRec = JSON.parse(record)
|
||||||
|
if (Object.keys(parsedRec).length < 2) {
|
||||||
|
throw new Error('The records must have at least 2 keys')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const firstRecord = JSON.parse(records[0])
|
||||||
|
if (
|
||||||
|
!Object.keys(firstRecord).some(key => {
|
||||||
|
return records
|
||||||
|
.map(record => JSON.parse(record)[key])
|
||||||
|
.every(value => value === 0 || value === 1)
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'There must be a common key used as object type: 0 - node, 1 - edge'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function buildNodes(graph, dataSources, options) {
|
export function buildNodes(graph, dataSources, options) {
|
||||||
const docColumn = Object.keys(dataSources)[0] || 'doc'
|
const docColumn = Object.keys(dataSources)[0]
|
||||||
const { objectType, nodeId } = options.structure
|
const { objectType, nodeId } = options.structure
|
||||||
|
|
||||||
if (objectType && nodeId) {
|
if (objectType && nodeId) {
|
||||||
@@ -14,10 +45,12 @@ export function buildNodes(graph, dataSources, options) {
|
|||||||
.map(json => JSON.parse(json))
|
.map(json => JSON.parse(json))
|
||||||
.filter(item => item[objectType] === TYPE_NODE)
|
.filter(item => item[objectType] === TYPE_NODE)
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
graph.addNode(node[nodeId], {
|
if (node[nodeId]) {
|
||||||
data: node,
|
graph.addNode(node[nodeId], {
|
||||||
labelColor: options.style.nodes.label.color
|
data: node,
|
||||||
})
|
labelColor: options.style.nodes.label.color
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,10 +143,23 @@ function getUpdateSizeMethod(graph, sizeSettings) {
|
|||||||
if (type === 'constant') {
|
if (type === 'constant') {
|
||||||
return attributes => (attributes.size = value)
|
return attributes => (attributes.size = value)
|
||||||
} else if (type === 'variable') {
|
} else if (type === 'variable') {
|
||||||
return getVariabledSizeMethod(mode, source, scale, min)
|
return attributes => {
|
||||||
|
attributes.size = getVariabledSize(
|
||||||
|
mode,
|
||||||
|
attributes.data[source],
|
||||||
|
scale,
|
||||||
|
min
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return (attributes, nodeId) =>
|
return (attributes, nodeId) => {
|
||||||
(attributes.size = Math.max(graph[method](nodeId) * scale, min))
|
attributes.size = getVariabledSize(
|
||||||
|
mode,
|
||||||
|
graph[method](nodeId),
|
||||||
|
scale,
|
||||||
|
min
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,22 +230,13 @@ function getUpdateEdgeColorMethod(graph, colorSettings) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVariabledSizeMethod(mode, source, scale, min) {
|
function getVariabledSize(mode, value, scale, min) {
|
||||||
if (mode === 'diameter') {
|
if (mode === 'diameter') {
|
||||||
return attributes =>
|
return Math.max((value / 2) * scale, min / 2)
|
||||||
(attributes.size = Math.max(
|
|
||||||
(attributes.data[source] / 2) * scale,
|
|
||||||
min / 2
|
|
||||||
))
|
|
||||||
} else if (mode === 'area') {
|
} else if (mode === 'area') {
|
||||||
return attributes =>
|
return Math.max(Math.sqrt((value / 2) * scale), min / 2)
|
||||||
(attributes.size = Math.max(
|
|
||||||
Math.sqrt((attributes.data[source] / 2) * scale),
|
|
||||||
min / 2
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
return attributes =>
|
return Math.max(value * scale, min)
|
||||||
(attributes.size = Math.max(attributes.data[source] * scale, min))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import store from '@/store'
|
|||||||
import { createVfm, VueFinalModal, useVfm } from 'vue-final-modal'
|
import { createVfm, VueFinalModal, useVfm } from 'vue-final-modal'
|
||||||
|
|
||||||
import '@/assets/styles/variables.css'
|
import '@/assets/styles/variables.css'
|
||||||
|
import '@/assets/styles/typography.css'
|
||||||
import '@/assets/styles/buttons.css'
|
import '@/assets/styles/buttons.css'
|
||||||
import '@/assets/styles/tables.css'
|
import '@/assets/styles/tables.css'
|
||||||
import '@/assets/styles/dialogs.css'
|
import '@/assets/styles/dialogs.css'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="chartContainer" class="chart-container">
|
<div ref="chartContainer" class="chart-container">
|
||||||
<div v-show="!dataSources" class="warning chart-warning">
|
<div v-show="!dataSources" class="warning data-view-warning">
|
||||||
There is no data to build a chart. Run your SQL query and make sure the
|
There is no data to build a chart. Run your SQL query and make sure the
|
||||||
result is not empty.
|
result is not empty.
|
||||||
</div>
|
</div>
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
:useResizeHandler="useResizeHandler"
|
:useResizeHandler="useResizeHandler"
|
||||||
:debug="true"
|
:debug="true"
|
||||||
:advancedTraceTypeSelector="true"
|
:advancedTraceTypeSelector="true"
|
||||||
|
:hideControls="!showViewSettings"
|
||||||
@update="update"
|
@update="update"
|
||||||
@render="onRender"
|
@render="onRender"
|
||||||
/>
|
/>
|
||||||
@@ -47,7 +48,8 @@ export default {
|
|||||||
initOptions: Object,
|
initOptions: Object,
|
||||||
exportToPngEnabled: Boolean,
|
exportToPngEnabled: Boolean,
|
||||||
exportToSvgEnabled: Boolean,
|
exportToSvgEnabled: Boolean,
|
||||||
forPivot: Boolean
|
forPivot: Boolean,
|
||||||
|
showViewSettings: Boolean
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'update:exportToSvgEnabled',
|
'update:exportToSvgEnabled',
|
||||||
@@ -85,6 +87,9 @@ export default {
|
|||||||
dereference.default(this.state.data, this.dataSources)
|
dereference.default(this.state.data, this.dataSources)
|
||||||
this.updatePlotly()
|
this.updatePlotly()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
showViewSettings() {
|
||||||
|
this.handleResize()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -179,13 +184,6 @@ export default {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-warning {
|
|
||||||
height: 40px;
|
|
||||||
line-height: 40px;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
.chart {
|
||||||
min-height: 242px;
|
min-height: 242px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="plotly_editor">
|
<div :class="['plotly_editor', { with_controls: showViewSettings }]">
|
||||||
<GraphEditorControls>
|
<GraphEditorControls v-show="showViewSettings">
|
||||||
<PanelMenuWrapper>
|
<PanelMenuWrapper>
|
||||||
<Panel group="Structure" name="Graph">
|
<Panel group="Structure" name="Graph">
|
||||||
<Fold name="Graph">
|
<Fold name="Graph">
|
||||||
<Field>Choose keys explanation...</Field>
|
<Field>
|
||||||
|
Map your result set records to node and edge properties required
|
||||||
|
to build a graph. Learn more about result set requirements in the
|
||||||
|
<a href="https://sqliteviz.com/docs/graph/" target="_blank">
|
||||||
|
documentation</a
|
||||||
|
>.
|
||||||
|
</Field>
|
||||||
<Field label="Object type">
|
<Field label="Object type">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
:options="keysOptions"
|
:options="keysOptions"
|
||||||
:value="settings.structure.objectType"
|
:value="settings.structure.objectType"
|
||||||
@change="updateStructure('objectType', $event)"
|
@change="updateStructure('objectType', $event)"
|
||||||
/>
|
/>
|
||||||
<Field>0 - node; 1 - edge</Field>
|
<Field>
|
||||||
|
A field indicating if the record is node (value 0) or edge
|
||||||
|
(value 1).
|
||||||
|
</Field>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Node Id">
|
<Field label="Node Id">
|
||||||
@@ -20,6 +29,7 @@
|
|||||||
:value="settings.structure.nodeId"
|
:value="settings.structure.nodeId"
|
||||||
@change="updateStructure('nodeId', $event)"
|
@change="updateStructure('nodeId', $event)"
|
||||||
/>
|
/>
|
||||||
|
<Field> A field keeping unique node identifier. </Field>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Edge source">
|
<Field label="Edge source">
|
||||||
@@ -28,6 +38,9 @@
|
|||||||
:value="settings.structure.edgeSource"
|
:value="settings.structure.edgeSource"
|
||||||
@change="updateStructure('edgeSource', $event)"
|
@change="updateStructure('edgeSource', $event)"
|
||||||
/>
|
/>
|
||||||
|
<Field>
|
||||||
|
A field keeping a node identifier where the edge starts.
|
||||||
|
</Field>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Edge target">
|
<Field label="Edge target">
|
||||||
@@ -36,6 +49,9 @@
|
|||||||
:value="settings.structure.edgeTarget"
|
:value="settings.structure.edgeTarget"
|
||||||
@change="updateStructure('edgeTarget', $event)"
|
@change="updateStructure('edgeTarget', $event)"
|
||||||
/>
|
/>
|
||||||
|
<Field>
|
||||||
|
A field keeping a node identifier where the edge ends.
|
||||||
|
</Field>
|
||||||
</Field>
|
</Field>
|
||||||
</Fold>
|
</Fold>
|
||||||
</Panel>
|
</Panel>
|
||||||
@@ -164,6 +180,7 @@
|
|||||||
</Panel>
|
</Panel>
|
||||||
</PanelMenuWrapper>
|
</PanelMenuWrapper>
|
||||||
</GraphEditorControls>
|
</GraphEditorControls>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref="graph"
|
ref="graph"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -209,6 +226,7 @@ import NodeColorSettings from '@/components/Graph/NodeColorSettings.vue'
|
|||||||
import NodeSizeSettings from '@/components/Graph/NodeSizeSettings.vue'
|
import NodeSizeSettings from '@/components/Graph/NodeSizeSettings.vue'
|
||||||
import EdgeSizeSettings from '@/components/Graph/EdgeSizeSettings.vue'
|
import EdgeSizeSettings from '@/components/Graph/EdgeSizeSettings.vue'
|
||||||
import EdgeColorSettings from '@/components/Graph/EdgeColorSettings.vue'
|
import EdgeColorSettings from '@/components/Graph/EdgeColorSettings.vue'
|
||||||
|
import events from '@/lib/utils/events'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -235,7 +253,8 @@ export default {
|
|||||||
inject: ['tabLayout'],
|
inject: ['tabLayout'],
|
||||||
props: {
|
props: {
|
||||||
dataSources: Object,
|
dataSources: Object,
|
||||||
initOptions: Object
|
initOptions: Object,
|
||||||
|
showViewSettings: Boolean
|
||||||
},
|
},
|
||||||
emits: ['update'],
|
emits: ['update'],
|
||||||
data() {
|
data() {
|
||||||
@@ -275,7 +294,7 @@ export default {
|
|||||||
nodes: {
|
nodes: {
|
||||||
size: {
|
size: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
value: 4
|
value: 10
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@@ -359,6 +378,14 @@ export default {
|
|||||||
this.buildGraph()
|
this.buildGraph()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'settings.layout.type': {
|
||||||
|
immediate: true,
|
||||||
|
handler() {
|
||||||
|
events.send('viz_graph.render', null, {
|
||||||
|
layout: this.settings.layout.type
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
tabLayout: {
|
tabLayout: {
|
||||||
deep: true,
|
deep: true,
|
||||||
handler() {
|
handler() {
|
||||||
@@ -611,7 +638,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.plotly_editor > div {
|
.plotly_editor.with_controls > div {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="graphContainer" class="chart-container">
|
<div ref="graphContainer" class="graph-container">
|
||||||
<div v-show="!dataSources" class="warning chart-warning">
|
<div v-show="!dataSources" class="warning data-view-warning">
|
||||||
There is no data to build a graph. Run your SQL query and make sure the
|
There is no data to build a graph. Run your SQL query and make sure the
|
||||||
result is not empty.
|
result is not empty.
|
||||||
</div>
|
</div>
|
||||||
|
<div v-show="!dataSourceIsValid" class="warning data-view-warning">
|
||||||
|
Result set is invalid for graph visualisation. Learn more in
|
||||||
|
<a href="https://sqliteviz.com/docs/graph/" target="_blank">
|
||||||
|
documentation</a
|
||||||
|
>.
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="graph"
|
class="graph"
|
||||||
:style="{
|
:style="{
|
||||||
height: !dataSources ? 'calc(100% - 40px)' : '100%'
|
height:
|
||||||
|
!dataSources || !dataSourceIsValid ? 'calc(100% - 40px)' : '100%'
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<GraphEditor
|
<GraphEditor
|
||||||
ref="graphEditor"
|
ref="graphEditor"
|
||||||
:dataSources="dataSources"
|
:dataSources="dataSources"
|
||||||
:initOptions="initOptions"
|
:initOptions="initOptions"
|
||||||
|
:showViewSettings="showViewSettings"
|
||||||
@update="$emit('update')"
|
@update="$emit('update')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,8 +30,8 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import 'react-chart-editor/lib/react-chart-editor.css'
|
import 'react-chart-editor/lib/react-chart-editor.css'
|
||||||
import events from '@/lib/utils/events'
|
|
||||||
import GraphEditor from './GraphEditor.vue'
|
import GraphEditor from './GraphEditor.vue'
|
||||||
|
import { dataSourceIsValid } from '@/lib/graphHelper'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Graph',
|
name: 'Graph',
|
||||||
@@ -33,7 +41,8 @@ export default {
|
|||||||
initOptions: Object,
|
initOptions: Object,
|
||||||
exportToPngEnabled: Boolean,
|
exportToPngEnabled: Boolean,
|
||||||
exportToSvgEnabled: Boolean,
|
exportToSvgEnabled: Boolean,
|
||||||
exportToHtmlEnabled: Boolean
|
exportToHtmlEnabled: Boolean,
|
||||||
|
showViewSettings: Boolean
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'update:exportToSvgEnabled',
|
'update:exportToSvgEnabled',
|
||||||
@@ -46,7 +55,6 @@ export default {
|
|||||||
resizeObserver: null
|
resizeObserver: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.$emit('update:exportToSvgEnabled', false)
|
this.$emit('update:exportToSvgEnabled', false)
|
||||||
this.$emit('update:exportToHtmlEnabled', false)
|
this.$emit('update:exportToHtmlEnabled', false)
|
||||||
@@ -58,6 +66,17 @@ export default {
|
|||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
this.resizeObserver.unobserve(this.$refs.graphContainer)
|
this.resizeObserver.unobserve(this.$refs.graphContainer)
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
async showViewSettings() {
|
||||||
|
await this.$nextTick()
|
||||||
|
this.handleResize()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dataSourceIsValid() {
|
||||||
|
return !this.dataSources || dataSourceIsValid(this.dataSources)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getOptionsForSave() {
|
getOptionsForSave() {
|
||||||
return this.$refs.graphEditor.settings
|
return this.$refs.graphEditor.settings
|
||||||
@@ -73,7 +92,7 @@ export default {
|
|||||||
const renderer = this.$refs.graphEditor.renderer
|
const renderer = this.$refs.graphEditor.renderer
|
||||||
if (renderer) {
|
if (renderer) {
|
||||||
renderer.refresh()
|
renderer.refresh()
|
||||||
renderer.getCamera().setState({ x: 0.5, y: 0.5 })
|
renderer.getCamera().animatedReset({ duration: 600 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,18 +100,11 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.chart-container {
|
.graph-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-warning {
|
.graph {
|
||||||
height: 40px;
|
|
||||||
line-height: 40px;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart {
|
|
||||||
min-height: 242px;
|
min-height: 242px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,137 +1,132 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pivot-ui">
|
<div class="pivot-ui">
|
||||||
<div :class="{ collapsed }">
|
<div class="row">
|
||||||
<div class="row">
|
<label>Columns</label>
|
||||||
<label>Columns</label>
|
<multiselect
|
||||||
<multiselect
|
v-model="cols"
|
||||||
v-model="cols"
|
class="sqliteviz-select cols"
|
||||||
class="sqliteviz-select cols"
|
:options="colsToSelect"
|
||||||
:options="colsToSelect"
|
:disabled="colsToSelect.length === 0"
|
||||||
:disabled="colsToSelect.length === 0"
|
:multiple="true"
|
||||||
:multiple="true"
|
:hideSelected="true"
|
||||||
:hideSelected="true"
|
:closeOnSelect="true"
|
||||||
:closeOnSelect="true"
|
:showLabels="false"
|
||||||
:showLabels="false"
|
:max="colsToSelect.length"
|
||||||
:max="colsToSelect.length"
|
openDirection="bottom"
|
||||||
openDirection="bottom"
|
placeholder=""
|
||||||
placeholder=""
|
>
|
||||||
>
|
<template #maxElements>
|
||||||
<template #maxElements>
|
<span class="no-results">No Results</span>
|
||||||
<span class="no-results">No Results</span>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #placeholder>Choose columns</template>
|
<template #placeholder>Choose columns</template>
|
||||||
|
|
||||||
<template #noResult>
|
<template #noResult>
|
||||||
<span class="no-results">No Results</span>
|
<span class="no-results">No Results</span>
|
||||||
</template>
|
</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
<pivot-sort-btn v-model="colOrder" class="sort-btn" direction="col" />
|
<pivot-sort-btn v-model="colOrder" class="sort-btn" direction="col" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>Rows</label>
|
<label>Rows</label>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="rows"
|
v-model="rows"
|
||||||
class="sqliteviz-select rows"
|
class="sqliteviz-select rows"
|
||||||
:options="rowsToSelect"
|
:options="rowsToSelect"
|
||||||
:disabled="rowsToSelect.length === 0"
|
:disabled="rowsToSelect.length === 0"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:hideSelected="true"
|
:hideSelected="true"
|
||||||
:closeOnSelect="true"
|
:closeOnSelect="true"
|
||||||
:showLabels="false"
|
:showLabels="false"
|
||||||
:max="rowsToSelect.length"
|
:max="rowsToSelect.length"
|
||||||
:optionHeight="29"
|
:optionHeight="29"
|
||||||
openDirection="bottom"
|
openDirection="bottom"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
>
|
>
|
||||||
<template #maxElements>
|
<template #maxElements>
|
||||||
<span class="no-results">No Results</span>
|
<span class="no-results">No Results</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #placeholder>Choose rows</template>
|
<template #placeholder>Choose rows</template>
|
||||||
|
|
||||||
<template #noResult>
|
<template #noResult>
|
||||||
<span class="no-results">No Results</span>
|
<span class="no-results">No Results</span>
|
||||||
</template>
|
</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
<pivot-sort-btn v-model="rowOrder" class="sort-btn" direction="row" />
|
<pivot-sort-btn v-model="rowOrder" class="sort-btn" direction="row" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row aggregator">
|
<div class="row aggregator">
|
||||||
<label>Aggregator</label>
|
<label>Aggregator</label>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="aggregator"
|
v-model="aggregator"
|
||||||
class="sqliteviz-select short aggregator"
|
class="sqliteviz-select short aggregator"
|
||||||
:options="aggregators"
|
:options="aggregators"
|
||||||
label="name"
|
label="name"
|
||||||
trackBy="name"
|
trackBy="name"
|
||||||
:closeOnSelect="true"
|
:closeOnSelect="true"
|
||||||
:showLabels="false"
|
:showLabels="false"
|
||||||
:hideSelected="true"
|
:hideSelected="true"
|
||||||
:optionHeight="29"
|
:optionHeight="29"
|
||||||
openDirection="bottom"
|
openDirection="bottom"
|
||||||
placeholder="Choose a function"
|
placeholder="Choose a function"
|
||||||
>
|
>
|
||||||
<template #noResult>
|
<template #noResult>
|
||||||
<span class="no-results">No Results</span>
|
<span class="no-results">No Results</span>
|
||||||
</template>
|
</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
|
|
||||||
<multiselect
|
<multiselect
|
||||||
v-show="valCount > 0"
|
v-show="valCount > 0"
|
||||||
v-model="val1"
|
v-model="val1"
|
||||||
class="sqliteviz-select aggr-arg"
|
class="sqliteviz-select aggr-arg"
|
||||||
:options="keyNames"
|
:options="keyNames"
|
||||||
:disabled="keyNames.length === 0"
|
:disabled="keyNames.length === 0"
|
||||||
:closeOnSelect="true"
|
:closeOnSelect="true"
|
||||||
:showLabels="false"
|
:showLabels="false"
|
||||||
:hideSelected="true"
|
:hideSelected="true"
|
||||||
:optionHeight="29"
|
:optionHeight="29"
|
||||||
openDirection="bottom"
|
openDirection="bottom"
|
||||||
placeholder="Choose an argument"
|
placeholder="Choose an argument"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<multiselect
|
<multiselect
|
||||||
v-show="valCount > 1"
|
v-show="valCount > 1"
|
||||||
v-model="val2"
|
v-model="val2"
|
||||||
class="sqliteviz-select aggr-arg"
|
class="sqliteviz-select aggr-arg"
|
||||||
:options="keyNames"
|
:options="keyNames"
|
||||||
:disabled="keyNames.length === 0"
|
:disabled="keyNames.length === 0"
|
||||||
:closeOnSelect="true"
|
:closeOnSelect="true"
|
||||||
:showLabels="false"
|
:showLabels="false"
|
||||||
:hideSelected="true"
|
:hideSelected="true"
|
||||||
:optionHeight="29"
|
:optionHeight="29"
|
||||||
openDirection="bottom"
|
openDirection="bottom"
|
||||||
placeholder="Choose a second argument"
|
placeholder="Choose a second argument"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>View</label>
|
<label>View</label>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="renderer"
|
v-model="renderer"
|
||||||
class="sqliteviz-select short renderer"
|
class="sqliteviz-select short renderer"
|
||||||
:options="renderers"
|
:options="renderers"
|
||||||
label="name"
|
label="name"
|
||||||
trackBy="name"
|
trackBy="name"
|
||||||
:closeOnSelect="true"
|
:closeOnSelect="true"
|
||||||
:allowEmpty="false"
|
:allowEmpty="false"
|
||||||
:showLabels="false"
|
:showLabels="false"
|
||||||
:hideSelected="true"
|
:hideSelected="true"
|
||||||
:optionHeight="29"
|
:optionHeight="29"
|
||||||
openDirection="bottom"
|
openDirection="bottom"
|
||||||
placeholder="Choose a view"
|
placeholder="Choose a view"
|
||||||
>
|
>
|
||||||
<template #noResult>
|
<template #noResult>
|
||||||
<span class="no-results">No Results</span>
|
<span class="no-results">No Results</span>
|
||||||
</template>
|
</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="switcher" @click="collapsed = !collapsed">
|
|
||||||
{{ collapsed ? 'Show pivot settings' : 'Hide pivot settings' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -163,7 +158,6 @@ export default {
|
|||||||
const rendererName =
|
const rendererName =
|
||||||
(this.modelValue && this.modelValue.rendererName) || 'Table'
|
(this.modelValue && this.modelValue.rendererName) || 'Table'
|
||||||
return {
|
return {
|
||||||
collapsed: false,
|
|
||||||
renderer: {
|
renderer: {
|
||||||
name: rendererName,
|
name: rendererName,
|
||||||
fun: $.pivotUtilities.renderers[rendererName]
|
fun: $.pivotUtilities.renderers[rendererName]
|
||||||
@@ -291,9 +285,6 @@ export default {
|
|||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.collapsed {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switcher {
|
.switcher {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
result is not empty.
|
result is not empty.
|
||||||
</div>
|
</div>
|
||||||
<pivot-ui
|
<pivot-ui
|
||||||
|
v-show="showViewSettings"
|
||||||
v-model="pivotOptions"
|
v-model="pivotOptions"
|
||||||
:keyNames="columns"
|
:keyNames="columns"
|
||||||
@update="$emit('update')"
|
@update="$emit('update')"
|
||||||
@@ -47,7 +48,8 @@ export default {
|
|||||||
dataSources: Object,
|
dataSources: Object,
|
||||||
initOptions: Object,
|
initOptions: Object,
|
||||||
exportToPngEnabled: Boolean,
|
exportToPngEnabled: Boolean,
|
||||||
exportToSvgEnabled: Boolean
|
exportToSvgEnabled: Boolean,
|
||||||
|
showViewSettings: Boolean
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'loadingImageCompleted',
|
'loadingImageCompleted',
|
||||||
@@ -125,6 +127,9 @@ export default {
|
|||||||
},
|
},
|
||||||
pivotOptions() {
|
pivotOptions() {
|
||||||
this.show()
|
this.show()
|
||||||
|
},
|
||||||
|
showViewSettings() {
|
||||||
|
this.handleResize()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -321,4 +326,8 @@ export default {
|
|||||||
.pivot-output:empty {
|
.pivot-output:empty {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.js-plotly-plot) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
v-model:exportToHtmlEnabled="exportToHtmlEnabled"
|
v-model:exportToHtmlEnabled="exportToHtmlEnabled"
|
||||||
:initOptions="initOptionsByMode[mode]"
|
:initOptions="initOptionsByMode[mode]"
|
||||||
:data-sources="dataSource"
|
:data-sources="dataSource"
|
||||||
|
:showViewSettings="showViewSettings"
|
||||||
@loading-image-completed="loadingImage = false"
|
@loading-image-completed="loadingImage = false"
|
||||||
@update="$emit('update')"
|
@update="$emit('update')"
|
||||||
/>
|
/>
|
||||||
@@ -42,6 +43,18 @@
|
|||||||
|
|
||||||
<div class="side-tool-bar-divider" />
|
<div class="side-tool-bar-divider" />
|
||||||
|
|
||||||
|
<icon-button
|
||||||
|
ref="settingsBtn"
|
||||||
|
:active="showViewSettings"
|
||||||
|
tooltip="Toggle visualisation settings visibility"
|
||||||
|
tooltipPosition="top-left"
|
||||||
|
@click="showViewSettings = !showViewSettings"
|
||||||
|
>
|
||||||
|
<settings-icon />
|
||||||
|
</icon-button>
|
||||||
|
|
||||||
|
<div class="side-tool-bar-divider" />
|
||||||
|
|
||||||
<icon-button
|
<icon-button
|
||||||
:disabled="!exportToPngEnabled || loadingImage"
|
:disabled="!exportToPngEnabled || loadingImage"
|
||||||
:loading="loadingImage"
|
:loading="loadingImage"
|
||||||
@@ -103,6 +116,7 @@ import IconButton from '@/components/IconButton'
|
|||||||
import ChartIcon from '@/components/svg/chart'
|
import ChartIcon from '@/components/svg/chart'
|
||||||
import PivotIcon from '@/components/svg/pivot'
|
import PivotIcon from '@/components/svg/pivot'
|
||||||
import GraphIcon from '@/components/svg/graph.vue'
|
import GraphIcon from '@/components/svg/graph.vue'
|
||||||
|
import SettingsIcon from '@/components/svg/settings.vue'
|
||||||
import HtmlIcon from '@/components/svg/html'
|
import HtmlIcon from '@/components/svg/html'
|
||||||
import ExportToSvgIcon from '@/components/svg/exportToSvg'
|
import ExportToSvgIcon from '@/components/svg/exportToSvg'
|
||||||
import PngIcon from '@/components/svg/png'
|
import PngIcon from '@/components/svg/png'
|
||||||
@@ -123,6 +137,7 @@ export default {
|
|||||||
ChartIcon,
|
ChartIcon,
|
||||||
PivotIcon,
|
PivotIcon,
|
||||||
GraphIcon,
|
GraphIcon,
|
||||||
|
SettingsIcon,
|
||||||
ExportToSvgIcon,
|
ExportToSvgIcon,
|
||||||
PngIcon,
|
PngIcon,
|
||||||
HtmlIcon,
|
HtmlIcon,
|
||||||
@@ -150,7 +165,8 @@ export default {
|
|||||||
pivot: this.initMode === 'pivot' ? this.initOptions : null,
|
pivot: this.initMode === 'pivot' ? this.initOptions : null,
|
||||||
graph: this.initMode === 'graph' ? this.initOptions : null
|
graph: this.initMode === 'graph' ? this.initOptions : null
|
||||||
},
|
},
|
||||||
showLoadingDialog: false
|
showLoadingDialog: false,
|
||||||
|
showViewSettings: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -238,7 +254,9 @@ export default {
|
|||||||
events.send(
|
events.send(
|
||||||
this.mode === 'chart' || this.plotlyInPivot
|
this.mode === 'chart' || this.plotlyInPivot
|
||||||
? 'viz_plotly.export'
|
? 'viz_plotly.export'
|
||||||
: 'viz_pivot.export',
|
: this.mode === 'graph'
|
||||||
|
? 'viz_graph.export'
|
||||||
|
: 'viz_pivot.export',
|
||||||
null,
|
null,
|
||||||
eventLabels
|
eventLabels
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ export default {
|
|||||||
border-left: 1px solid var(--color-border-light);
|
border-left: 1px solid var(--color-border-light);
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
.side-tool-bar-divider {
|
.side-tool-bar-divider {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ export default {
|
|||||||
) {
|
) {
|
||||||
const stmt = [
|
const stmt = [
|
||||||
'/*',
|
'/*',
|
||||||
' * Your database is empty. In order to start building charts',
|
' * Your database is empty. In order to start building data visualisations',
|
||||||
' * you should create a table and insert data into it.',
|
' * you should create tables and insert data into them.',
|
||||||
' */',
|
' */',
|
||||||
'CREATE TABLE house',
|
'CREATE TABLE house',
|
||||||
'(',
|
'(',
|
||||||
@@ -54,7 +54,20 @@ export default {
|
|||||||
"('Gryffindor', 100),",
|
"('Gryffindor', 100),",
|
||||||
"('Hufflepuff', 90),",
|
"('Hufflepuff', 90),",
|
||||||
"('Ravenclaw', 95),",
|
"('Ravenclaw', 95),",
|
||||||
"('Slytherin', 80);"
|
"('Slytherin', 80);",
|
||||||
|
'',
|
||||||
|
'CREATE TABLE student',
|
||||||
|
'(',
|
||||||
|
' id INTEGER,',
|
||||||
|
' name TEXT,',
|
||||||
|
' house TEXT',
|
||||||
|
');',
|
||||||
|
'INSERT INTO student VALUES',
|
||||||
|
"(1, 'Harry Potter', 'Gryffindor'),",
|
||||||
|
"(2, 'Ron Weasley', 'Gryffindor'),",
|
||||||
|
"(3, 'Draco Malfoy', 'Slytherin'),",
|
||||||
|
"(4, 'Luna Lovegood', 'Ravenclaw'),",
|
||||||
|
"(5, 'Cedric Diggory', 'Hufflepuff');"
|
||||||
].join('\n')
|
].join('\n')
|
||||||
|
|
||||||
const tabId = await this.$store.dispatch('addTab', { query: stmt })
|
const tabId = await this.$store.dispatch('addTab', { query: stmt })
|
||||||
|
|||||||
Reference in New Issue
Block a user