mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 10:08:52 +08:00
Compare commits
3 Commits
3d6479be7a
...
0a2af0bba3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2af0bba3 | ||
|
|
e4b35bac0a | ||
|
|
3d1e822cdc |
42
src/App.vue
42
src/App.vue
@@ -33,48 +33,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<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,
|
||||
.dialog,
|
||||
input,
|
||||
|
||||
@@ -4,3 +4,10 @@
|
||||
font-size: 13px;
|
||||
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>
|
||||
<Field label="Adjust sizes">
|
||||
<Field label="Scaling ratio">
|
||||
<NumericInput
|
||||
:value="modelValue.scalingRatio"
|
||||
@update="update('scalingRatio', $event)"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Prevent overlapping">
|
||||
<RadioBlocks
|
||||
:options="booleanOptions"
|
||||
:activeOption="modelValue.adjustSizes"
|
||||
@@ -22,13 +29,6 @@
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Gravity">
|
||||
<NumericInput
|
||||
:value="modelValue.gravity"
|
||||
@update="update('gravity', $event)"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Strong gravity mode">
|
||||
<RadioBlocks
|
||||
:options="booleanOptions"
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
</template>
|
||||
</Field>
|
||||
|
||||
<Field v-if="modelValue.type !== 'constant'" label="Color as">
|
||||
<Field
|
||||
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||
label="Color as"
|
||||
>
|
||||
<RadioBlocks
|
||||
:options="сolorAsOptions"
|
||||
:activeOption="modelValue.mode"
|
||||
@@ -47,7 +50,10 @@
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field v-if="modelValue.type !== 'constant'" label="Colorscale direction">
|
||||
<Field
|
||||
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||
label="Colorscale direction"
|
||||
>
|
||||
<RadioBlocks
|
||||
:options="сolorscaleDirections"
|
||||
:activeOption="modelValue.colorscaleDirection"
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Scaling ratio">
|
||||
<Field label="Gravity">
|
||||
<NumericInput
|
||||
:value="modelValue.scalingRatio"
|
||||
@update="update('scalingRatio', $event)"
|
||||
:value="modelValue.gravity"
|
||||
@update="update('gravity', $event)"
|
||||
/>
|
||||
</Field>
|
||||
</template>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field>
|
||||
<Field v-if="modelValue.type === 'variable'">
|
||||
<RadioBlocks
|
||||
:options="colorSourceUsageOptions"
|
||||
:activeOption="modelValue.sourceUsage"
|
||||
@@ -45,7 +45,10 @@
|
||||
</template>
|
||||
</Field>
|
||||
|
||||
<Field v-if="modelValue.type !== 'constant'" label="Color as">
|
||||
<Field
|
||||
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||
label="Color as"
|
||||
>
|
||||
<RadioBlocks
|
||||
:options="сolorAsOptions"
|
||||
:activeOption="modelValue.mode"
|
||||
@@ -53,7 +56,10 @@
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field v-if="modelValue.type !== 'constant'" label="Colorscale direction">
|
||||
<Field
|
||||
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||
label="Colorscale direction"
|
||||
>
|
||||
<RadioBlocks
|
||||
:options="сolorscaleDirections"
|
||||
:activeOption="modelValue.colorscaleDirection"
|
||||
|
||||
@@ -5,8 +5,39 @@ const TYPE_NODE = 0
|
||||
const TYPE_EDGE = 1
|
||||
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) {
|
||||
const docColumn = Object.keys(dataSources)[0] || 'doc'
|
||||
const docColumn = Object.keys(dataSources)[0]
|
||||
const { objectType, nodeId } = options.structure
|
||||
|
||||
if (objectType && nodeId) {
|
||||
@@ -14,10 +45,12 @@ export function buildNodes(graph, dataSources, options) {
|
||||
.map(json => JSON.parse(json))
|
||||
.filter(item => item[objectType] === TYPE_NODE)
|
||||
nodes.forEach(node => {
|
||||
if (node[nodeId]) {
|
||||
graph.addNode(node[nodeId], {
|
||||
data: node,
|
||||
labelColor: options.style.nodes.label.color
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -110,10 +143,23 @@ function getUpdateSizeMethod(graph, sizeSettings) {
|
||||
if (type === 'constant') {
|
||||
return attributes => (attributes.size = value)
|
||||
} else if (type === 'variable') {
|
||||
return getVariabledSizeMethod(mode, source, scale, min)
|
||||
return attributes => {
|
||||
attributes.size = getVariabledSize(
|
||||
mode,
|
||||
attributes.data[source],
|
||||
scale,
|
||||
min
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return (attributes, nodeId) =>
|
||||
(attributes.size = Math.max(graph[method](nodeId) * scale, min))
|
||||
return (attributes, nodeId) => {
|
||||
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') {
|
||||
return attributes =>
|
||||
(attributes.size = Math.max(
|
||||
(attributes.data[source] / 2) * scale,
|
||||
min / 2
|
||||
))
|
||||
return Math.max((value / 2) * scale, min / 2)
|
||||
} else if (mode === 'area') {
|
||||
return attributes =>
|
||||
(attributes.size = Math.max(
|
||||
Math.sqrt((attributes.data[source] / 2) * scale),
|
||||
min / 2
|
||||
))
|
||||
return Math.max(Math.sqrt((value / 2) * scale), min / 2)
|
||||
} else {
|
||||
return attributes =>
|
||||
(attributes.size = Math.max(attributes.data[source] * scale, min))
|
||||
return Math.max(value * scale, min)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import store from '@/store'
|
||||
import { createVfm, VueFinalModal, useVfm } from 'vue-final-modal'
|
||||
|
||||
import '@/assets/styles/variables.css'
|
||||
import '@/assets/styles/typography.css'
|
||||
import '@/assets/styles/buttons.css'
|
||||
import '@/assets/styles/tables.css'
|
||||
import '@/assets/styles/dialogs.css'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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
|
||||
result is not empty.
|
||||
</div>
|
||||
@@ -184,13 +184,6 @@ export default {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart-warning {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chart {
|
||||
min-height: 242px;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<Field>
|
||||
Map your result set records to node and edge properties required
|
||||
to build a graph. Learn more about result set requirements in the
|
||||
documentation.
|
||||
<a href="https://sqliteviz.com/docs/graph/" target="_blank">
|
||||
documentation</a
|
||||
>.
|
||||
</Field>
|
||||
<Field label="Object type">
|
||||
<Dropdown
|
||||
@@ -224,6 +226,7 @@ import NodeColorSettings from '@/components/Graph/NodeColorSettings.vue'
|
||||
import NodeSizeSettings from '@/components/Graph/NodeSizeSettings.vue'
|
||||
import EdgeSizeSettings from '@/components/Graph/EdgeSizeSettings.vue'
|
||||
import EdgeColorSettings from '@/components/Graph/EdgeColorSettings.vue'
|
||||
import events from '@/lib/utils/events'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -291,7 +294,7 @@ export default {
|
||||
nodes: {
|
||||
size: {
|
||||
type: 'constant',
|
||||
value: 4
|
||||
value: 10
|
||||
},
|
||||
color: {
|
||||
type: 'constant',
|
||||
@@ -375,6 +378,14 @@ export default {
|
||||
this.buildGraph()
|
||||
}
|
||||
},
|
||||
'settings.layout.type': {
|
||||
immediate: true,
|
||||
handler() {
|
||||
events.send('viz_graph.render', null, {
|
||||
layout: this.settings.layout.type
|
||||
})
|
||||
}
|
||||
},
|
||||
tabLayout: {
|
||||
deep: true,
|
||||
handler() {
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
<template>
|
||||
<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
|
||||
result is not empty.
|
||||
</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
|
||||
class="graph"
|
||||
:style="{
|
||||
height: !dataSources ? 'calc(100% - 40px)' : '100%'
|
||||
height:
|
||||
!dataSources || !dataSourceIsValid ? 'calc(100% - 40px)' : '100%'
|
||||
}"
|
||||
>
|
||||
<GraphEditor
|
||||
@@ -23,8 +30,8 @@
|
||||
|
||||
<script>
|
||||
import 'react-chart-editor/lib/react-chart-editor.css'
|
||||
import events from '@/lib/utils/events'
|
||||
import GraphEditor from './GraphEditor.vue'
|
||||
import { dataSourceIsValid } from '@/lib/graphHelper'
|
||||
|
||||
export default {
|
||||
name: 'Graph',
|
||||
@@ -48,7 +55,6 @@ export default {
|
||||
resizeObserver: null
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$emit('update:exportToSvgEnabled', false)
|
||||
this.$emit('update:exportToHtmlEnabled', false)
|
||||
@@ -66,6 +72,11 @@ export default {
|
||||
this.handleResize()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dataSourceIsValid() {
|
||||
return !this.dataSources || dataSourceIsValid(this.dataSources)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getOptionsForSave() {
|
||||
return this.$refs.graphEditor.settings
|
||||
@@ -93,13 +104,6 @@ export default {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart-warning {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.graph {
|
||||
min-height: 242px;
|
||||
}
|
||||
|
||||
@@ -326,4 +326,8 @@ export default {
|
||||
.pivot-output:empty {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
:deep(.js-plotly-plot) {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -254,6 +254,8 @@ export default {
|
||||
events.send(
|
||||
this.mode === 'chart' || this.plotlyInPivot
|
||||
? 'viz_plotly.export'
|
||||
: this.mode === 'graph'
|
||||
? 'viz_graph.export'
|
||||
: 'viz_pivot.export',
|
||||
null,
|
||||
eventLabels
|
||||
|
||||
@@ -42,8 +42,8 @@ export default {
|
||||
) {
|
||||
const stmt = [
|
||||
'/*',
|
||||
' * Your database is empty. In order to start building charts',
|
||||
' * you should create a table and insert data into it.',
|
||||
' * Your database is empty. In order to start building data visualisations',
|
||||
' * you should create tables and insert data into them.',
|
||||
' */',
|
||||
'CREATE TABLE house',
|
||||
'(',
|
||||
@@ -54,7 +54,20 @@ export default {
|
||||
"('Gryffindor', 100),",
|
||||
"('Hufflepuff', 90),",
|
||||
"('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')
|
||||
|
||||
const tabId = await this.$store.dispatch('addTab', { query: stmt })
|
||||
|
||||
Reference in New Issue
Block a user