mirror of
https://github.com/lana-k/sqliteviz.git
synced 2026-03-24 23:16:18 +08:00
Compare commits
27 Commits
218ab52ab3
...
0.28.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
859cd2ccfc | ||
|
|
a59946c09d | ||
|
|
7b06b3d9c8 | ||
|
|
ced933f497 | ||
|
|
cda368f109 | ||
|
|
df67466c2f | ||
|
|
528549ae5a | ||
|
|
20f4dcc645 | ||
|
|
b8353ef0ce | ||
|
|
7975f419c9 | ||
|
|
72aa0dd80b | ||
|
|
e000ee71fc | ||
|
|
b6a12668d3 | ||
|
|
713f5ac768 | ||
|
|
5492609c3a | ||
|
|
8bfd0f5944 | ||
|
|
a8006bcf52 | ||
|
|
1463f93bb0 | ||
|
|
5108495430 | ||
|
|
d28968e539 | ||
|
|
68221cba6d | ||
|
|
65c1c18fcb | ||
|
|
d7db6a0f5d | ||
|
|
0a2af0bba3 | ||
|
|
e4b35bac0a | ||
|
|
3d1e822cdc | ||
|
|
3d6479be7a |
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
@@ -23,7 +23,10 @@ jobs:
|
|||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
sudo add-apt-repository -y ppa:mozillateam/ppa
|
sudo add-apt-repository -y ppa:mozillateam/ppa
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y chromium-browser firefox-esr
|
sudo apt-get install -y \
|
||||||
|
chromium-browser \
|
||||||
|
firefox-esr \
|
||||||
|
xvfb
|
||||||
|
|
||||||
- name: Update npm
|
- name: Update npm
|
||||||
run: npm install -g npm@10
|
run: npm install -g npm@10
|
||||||
@@ -35,4 +38,4 @@ jobs:
|
|||||||
run: npm run lint -- --no-fix
|
run: npm run lint -- --no-fix
|
||||||
|
|
||||||
- name: Run karma tests
|
- name: Run karma tests
|
||||||
run: npm run test
|
run: xvfb-run -a npm test
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ of SQLite databases, CSV, JSON or NDJSON files.
|
|||||||
|
|
||||||
With sqliteviz you can:
|
With sqliteviz you can:
|
||||||
|
|
||||||
- run SQL queries against a SQLite database and create [Plotly][11] charts and pivot tables based on the result sets
|
- run SQL queries against a SQLite database and create [Plotly][11] charts, graphs and pivot tables based on the result sets
|
||||||
- import a CSV/JSON/NDJSON file into a SQLite database and visualize imported data
|
- import a CSV/JSON/NDJSON file into a SQLite database and visualize imported data
|
||||||
- export result set to CSV file
|
- export result set to CSV file
|
||||||
- manage inquiries and run them against different databases
|
- manage inquiries and run them against different databases
|
||||||
@@ -33,7 +33,9 @@ It's a kind of middleground between [Plotly Falcon][1] and [Redash][2].
|
|||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4] and [Vue-Codemirror][8] in [Vue.js][5]. CSV parsing is performed with [Papa Parse][9].
|
It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4]
|
||||||
|
and [Vue-Codemirror][8] in [Vue.js][5]. CSV parsing is performed with [Papa Parse][9].
|
||||||
|
Graphs are visualized with [Sigma.js][13] and [Graphology][14].
|
||||||
|
|
||||||
[1]: https://github.com/plotly/falcon
|
[1]: https://github.com/plotly/falcon
|
||||||
[2]: https://github.com/getredash/redash
|
[2]: https://github.com/getredash/redash
|
||||||
@@ -47,3 +49,5 @@ It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4]
|
|||||||
[10]: https://github.com/lana-k/sqliteviz/wiki/Predefined-queries
|
[10]: https://github.com/lana-k/sqliteviz/wiki/Predefined-queries
|
||||||
[11]: https://github.com/plotly/plotly.js
|
[11]: https://github.com/plotly/plotly.js
|
||||||
[12]: https://github.com/nicolaskruchten/pivottable
|
[12]: https://github.com/nicolaskruchten/pivottable
|
||||||
|
[13]: https://www.sigmajs.org/
|
||||||
|
[14]: https://graphology.github.io/
|
||||||
|
|||||||
23766
package-lock.json
generated
23766
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sqliteviz",
|
"name": "sqliteviz",
|
||||||
"version": "0.27.1",
|
"version": "0.28.1",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
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,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<Field label="Adjust sizes">
|
<Field label="Scaling ratio" fieldContainerClassName="test_fa2_scaling">
|
||||||
|
<NumericInput
|
||||||
|
:value="modelValue.scalingRatio"
|
||||||
|
@update="update('scalingRatio', $event)"
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
label="Prevent overlapping"
|
||||||
|
fieldContainerClassName="test_fa2_adjustSizes"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="booleanOptions"
|
:options="booleanOptions"
|
||||||
:activeOption="modelValue.adjustSizes"
|
:activeOption="modelValue.adjustSizes"
|
||||||
@@ -7,7 +17,10 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Barnes-Hut optimize">
|
<Field
|
||||||
|
label="Barnes-Hut optimize"
|
||||||
|
fieldContainerClassName="test_fa2_barnes_hut"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="booleanOptions"
|
:options="booleanOptions"
|
||||||
:activeOption="modelValue.barnesHutOptimize"
|
:activeOption="modelValue.barnesHutOptimize"
|
||||||
@@ -15,21 +28,21 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-show="modelValue.barnesHutOptimize" label="Barnes-Hut Theta">
|
<Field
|
||||||
|
v-show="modelValue.barnesHutOptimize"
|
||||||
|
label="Barnes-Hut Theta"
|
||||||
|
fieldContainerClassName="test_fa2_barnes_theta"
|
||||||
|
>
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.barnesHutTheta"
|
:value="modelValue.barnesHutTheta"
|
||||||
@update="update('barnesHutTheta', $event)"
|
@update="update('barnesHutTheta', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Gravity">
|
<Field
|
||||||
<NumericInput
|
label="Strong gravity mode"
|
||||||
:value="modelValue.gravity"
|
fieldContainerClassName="test_fa2_strong_gravity"
|
||||||
@update="update('gravity', $event)"
|
>
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Strong gravity mode">
|
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="booleanOptions"
|
:options="booleanOptions"
|
||||||
:activeOption="modelValue.strongGravityMode"
|
:activeOption="modelValue.strongGravityMode"
|
||||||
@@ -37,7 +50,10 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Noack's LinLog model">
|
<Field
|
||||||
|
label="Noack's LinLog model"
|
||||||
|
fieldContainerClassName="test_fa2_lin_log"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="booleanOptions"
|
:options="booleanOptions"
|
||||||
:activeOption="modelValue.linLogMode"
|
:activeOption="modelValue.linLogMode"
|
||||||
@@ -45,7 +61,10 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Out bound attraction distribution">
|
<Field
|
||||||
|
label="Outbound attraction distribution"
|
||||||
|
fieldContainerClassName="test_fa2_outbound_attraction"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="booleanOptions"
|
:options="booleanOptions"
|
||||||
:activeOption="modelValue.outboundAttractionDistribution"
|
:activeOption="modelValue.outboundAttractionDistribution"
|
||||||
@@ -53,7 +72,7 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Slow down">
|
<Field label="Slow down" fieldContainerClassName="test_fa2_slow_down">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.slowDown"
|
:value="modelValue.slowDown"
|
||||||
:min="0"
|
:min="0"
|
||||||
@@ -65,10 +84,15 @@
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
:options="keyOptions"
|
:options="keyOptions"
|
||||||
:value="modelValue.weightSource"
|
:value="modelValue.weightSource"
|
||||||
|
className="test_fa2_weight_source"
|
||||||
@change="update('weightSource', $event)"
|
@change="update('weightSource', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field v-show="modelValue.weightSource" label="Edge weight influence">
|
<Field
|
||||||
|
v-show="modelValue.weightSource"
|
||||||
|
label="Edge weight influence"
|
||||||
|
fieldContainerClassName="test_fa2_weight_influence"
|
||||||
|
>
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.edgeWeightInfluence"
|
:value="modelValue.edgeWeightInfluence"
|
||||||
@update="update('edgeWeightInfluence', $event)"
|
@update="update('edgeWeightInfluence', $event)"
|
||||||
|
|||||||
@@ -40,7 +40,6 @@
|
|||||||
import { applyPureReactInVue } from 'veaury'
|
import { applyPureReactInVue } from 'veaury'
|
||||||
import Field from 'react-chart-editor/lib/components/fields/Field'
|
import Field from 'react-chart-editor/lib/components/fields/Field'
|
||||||
import NumericInput from 'react-chart-editor/lib/components/widgets/NumericInput'
|
import NumericInput from 'react-chart-editor/lib/components/widgets/NumericInput'
|
||||||
import Dropdown from 'react-chart-editor/lib/components/widgets/Dropdown'
|
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from 'vue-multiselect'
|
||||||
import 'react-chart-editor/lib/react-chart-editor.css'
|
import 'react-chart-editor/lib/react-chart-editor.css'
|
||||||
|
|
||||||
@@ -48,7 +47,6 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Field: applyPureReactInVue(Field),
|
Field: applyPureReactInVue(Field),
|
||||||
NumericInput: applyPureReactInVue(NumericInput),
|
NumericInput: applyPureReactInVue(NumericInput),
|
||||||
Dropdown: applyPureReactInVue(Dropdown),
|
|
||||||
Multiselect
|
Multiselect
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<Field label="Color">
|
<Field label="Color" fieldContainerClassName="test_edge_color">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="edgeColorTypeOptions"
|
:options="edgeColorTypeOptions"
|
||||||
:activeOption="modelValue.type"
|
:activeOption="modelValue.type"
|
||||||
@option-change="updateColorType"
|
@option-change="updateColorType"
|
||||||
/>
|
/>
|
||||||
<Field v-if="modelValue.type === 'constant'">
|
<Field
|
||||||
|
v-if="modelValue.type === 'constant'"
|
||||||
|
fieldContainerClassName="test_edge_color_value"
|
||||||
|
>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
:selectedColor="modelValue.value"
|
:selectedColor="modelValue.value"
|
||||||
@color-change="updateSettings('value', $event)"
|
@color-change="updateSettings('value', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Field>
|
<Field fieldContainerClassName="test_edge_color_value">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
v-if="modelValue.type === 'variable'"
|
v-if="modelValue.type === 'variable'"
|
||||||
:options="keyOptions"
|
:options="keyOptions"
|
||||||
@@ -21,7 +24,7 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field>
|
<Field fieldContainerClassName="test_edge_color_mapping_mode">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="colorSourceUsageOptions"
|
:options="colorSourceUsageOptions"
|
||||||
:activeOption="modelValue.sourceUsage"
|
:activeOption="modelValue.sourceUsage"
|
||||||
@@ -39,7 +42,11 @@
|
|||||||
</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"
|
||||||
|
fieldContainerClassName="test_edge_color_as"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="сolorAsOptions"
|
:options="сolorAsOptions"
|
||||||
:activeOption="modelValue.mode"
|
:activeOption="modelValue.mode"
|
||||||
@@ -47,7 +54,11 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-if="modelValue.type !== 'constant'" label="Colorscale direction">
|
<Field
|
||||||
|
v-if="modelValue.type !== 'constant' && modelValue.sourceUsage === 'map_to'"
|
||||||
|
label="Colorscale direction"
|
||||||
|
fieldContainerClassName="test_edge_color_colorscale_direction"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="сolorscaleDirections"
|
:options="сolorscaleDirections"
|
||||||
:activeOption="modelValue.colorscaleDirection"
|
:activeOption="modelValue.colorscaleDirection"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<Field label="Size">
|
<Field label="Size" fieldContainerClassName="test_edge_size">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="edgeSizeTypeOptions"
|
:options="edgeSizeTypeOptions"
|
||||||
:activeOption="modelValue.type"
|
:activeOption="modelValue.type"
|
||||||
@option-change="updateSizeType"
|
@option-change="updateSizeType"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Field>
|
<Field fieldContainerClassName="test_edge_size_value">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
v-if="modelValue.type === 'constant'"
|
v-if="modelValue.type === 'constant'"
|
||||||
:value="modelValue.value"
|
:value="modelValue.value"
|
||||||
@@ -23,14 +23,14 @@
|
|||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<template v-if="modelValue.type !== 'constant'">
|
<template v-if="modelValue.type !== 'constant'">
|
||||||
<Field label="Size scale">
|
<Field label="Size scale" fieldContainerClassName="test_edge_size_scale">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.scale"
|
:value="modelValue.scale"
|
||||||
@update="updateSettings('scale', $event)"
|
@update="updateSettings('scale', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Minimum size">
|
<Field label="Minimum size" fieldContainerClassName="test_edge_size_min">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.min"
|
:value="modelValue.min"
|
||||||
@update="updateSettings('min', $event)"
|
@update="updateSettings('min', $event)"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<Field label="Initial iterations">
|
<Field
|
||||||
|
label="Initial iterations"
|
||||||
|
fieldContainerClassName="test_fa2_iteration_amount"
|
||||||
|
>
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.initialIterationsAmount"
|
:value="modelValue.initialIterationsAmount"
|
||||||
:min="1"
|
:min="1"
|
||||||
@@ -7,10 +10,10 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Scaling ratio">
|
<Field label="Gravity" fieldContainerClassName="test_fa2_gravity">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.scalingRatio"
|
:value="modelValue.gravity"
|
||||||
@update="update('scalingRatio', $event)"
|
@update="update('gravity', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,41 +1,61 @@
|
|||||||
<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>
|
||||||
<Field label="Object type">
|
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 ref="objectTypeField" label="Object type">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
:options="keysOptions"
|
:options="keysOptions"
|
||||||
:value="settings.structure.objectType"
|
:value="settings.structure.objectType"
|
||||||
|
className="test_object_type_select"
|
||||||
@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">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
:options="keysOptions"
|
:options="keysOptions"
|
||||||
:value="settings.structure.nodeId"
|
:value="settings.structure.nodeId"
|
||||||
|
className="test_node_id_select"
|
||||||
@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">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
:options="keysOptions"
|
:options="keysOptions"
|
||||||
:value="settings.structure.edgeSource"
|
:value="settings.structure.edgeSource"
|
||||||
|
className="test_edge_source_select"
|
||||||
@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">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
:options="keysOptions"
|
:options="keysOptions"
|
||||||
:value="settings.structure.edgeTarget"
|
:value="settings.structure.edgeTarget"
|
||||||
|
className="test_edge_target_select"
|
||||||
@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>
|
||||||
@@ -55,6 +75,7 @@
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
:options="keysOptions"
|
:options="keysOptions"
|
||||||
:value="settings.style.nodes.label.source"
|
:value="settings.style.nodes.label.source"
|
||||||
|
className="test_label_select"
|
||||||
@change="updateNodes('label.source', $event)"
|
@change="updateNodes('label.source', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
@@ -81,7 +102,10 @@
|
|||||||
|
|
||||||
<Panel group="Style" name="Edges">
|
<Panel group="Style" name="Edges">
|
||||||
<Fold name="Edges">
|
<Fold name="Edges">
|
||||||
<Field label="Direction">
|
<Field
|
||||||
|
label="Direction"
|
||||||
|
fieldContainerClassName="test_edge_direction"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="visibilityOptions"
|
:options="visibilityOptions"
|
||||||
:activeOption="settings.style.edges.showDirection"
|
:activeOption="settings.style.edges.showDirection"
|
||||||
@@ -93,6 +117,7 @@
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
:options="keysOptions"
|
:options="keysOptions"
|
||||||
:value="settings.style.edges.label.source"
|
:value="settings.style.edges.label.source"
|
||||||
|
className="test_edge_label_select"
|
||||||
@change="updateEdges('label.source', $event)"
|
@change="updateEdges('label.source', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
@@ -123,6 +148,8 @@
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
:options="layoutOptions"
|
:options="layoutOptions"
|
||||||
:value="settings.layout.type"
|
:value="settings.layout.type"
|
||||||
|
:clearable="false"
|
||||||
|
className="test_layout_algorithm_select"
|
||||||
@change="updateLayout($event)"
|
@change="updateLayout($event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
@@ -143,10 +170,18 @@
|
|||||||
/>
|
/>
|
||||||
</Fold>
|
</Fold>
|
||||||
<div class="force-atlas-buttons">
|
<div class="force-atlas-buttons">
|
||||||
<Button variant="secondary" @click="resetFA2LayoutSettings">
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
class="test_fa2_reset"
|
||||||
|
@click="resetFA2LayoutSettings"
|
||||||
|
>
|
||||||
Reset
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" @click="toggleFA2Layout">
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
class="test_fa2_toggle"
|
||||||
|
@click="toggleFA2Layout"
|
||||||
|
>
|
||||||
<template #node:icon>
|
<template #node:icon>
|
||||||
<div
|
<div
|
||||||
:style="{
|
:style="{
|
||||||
@@ -164,8 +199,10 @@
|
|||||||
</Panel>
|
</Panel>
|
||||||
</PanelMenuWrapper>
|
</PanelMenuWrapper>
|
||||||
</GraphEditorControls>
|
</GraphEditorControls>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref="graph"
|
ref="graph"
|
||||||
|
class="test_graph_output"
|
||||||
:style="{
|
:style="{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@@ -188,10 +225,11 @@ import Button from 'react-chart-editor/lib/components/widgets/Button'
|
|||||||
import Field from 'react-chart-editor/lib/components/fields/Field'
|
import Field from 'react-chart-editor/lib/components/fields/Field'
|
||||||
import RandomLayoutSettings from '@/components/Graph/RandomLayoutSettings.vue'
|
import RandomLayoutSettings from '@/components/Graph/RandomLayoutSettings.vue'
|
||||||
import ForceAtlasLayoutSettings from '@/components/Graph/ForceAtlasLayoutSettings.vue'
|
import ForceAtlasLayoutSettings from '@/components/Graph/ForceAtlasLayoutSettings.vue'
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
import AdvancedForceAtlasLayoutSettings from '@/components/Graph/AdvancedForceAtlasLayoutSettings.vue'
|
import AdvancedForceAtlasLayoutSettings from '@/components/Graph/AdvancedForceAtlasLayoutSettings.vue'
|
||||||
import CirclePackLayoutSettings from '@/components/Graph/CirclePackLayoutSettings.vue'
|
import CirclePackLayoutSettings from '@/components/Graph/CirclePackLayoutSettings.vue'
|
||||||
import FA2Layout from 'graphology-layout-forceatlas2/worker'
|
import FA2Layout from 'graphology-layout-forceatlas2/worker'
|
||||||
import forceAtlas2 from 'graphology-layout-forceatlas2'
|
import * as forceAtlas2 from 'graphology-layout-forceatlas2'
|
||||||
import RunIcon from '@/components/svg/run.vue'
|
import RunIcon from '@/components/svg/run.vue'
|
||||||
import StopIcon from '@/components/svg/stop.vue'
|
import StopIcon from '@/components/svg/stop.vue'
|
||||||
import { downloadAsPNG, drawOnCanvas } from '@sigma/export-image'
|
import { downloadAsPNG, drawOnCanvas } from '@sigma/export-image'
|
||||||
@@ -209,6 +247,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 +274,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 +315,7 @@ export default {
|
|||||||
nodes: {
|
nodes: {
|
||||||
size: {
|
size: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
value: 4
|
value: 10
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
@@ -319,11 +359,10 @@ export default {
|
|||||||
if (!this.dataSources) {
|
if (!this.dataSources) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
const firstColumnName = Object.keys(this.dataSources)[0]
|
||||||
try {
|
try {
|
||||||
return (
|
return (
|
||||||
this.dataSources[Object.keys(this.dataSources)[0] || 'doc'].map(
|
this.dataSources[firstColumnName].map(json => JSON.parse(json)) || []
|
||||||
json => JSON.parse(json)
|
|
||||||
) || []
|
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
return []
|
return []
|
||||||
@@ -359,6 +398,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() {
|
||||||
@@ -390,9 +437,7 @@ export default {
|
|||||||
this.updateLayout(this.settings.layout.type)
|
this.updateLayout(this.settings.layout.type)
|
||||||
this.renderer = new Sigma(this.graph, this.$refs.graph, {
|
this.renderer = new Sigma(this.graph, this.$refs.graph, {
|
||||||
renderEdgeLabels: true,
|
renderEdgeLabels: true,
|
||||||
allowInvalidContainer: true,
|
allowInvalidContainer: true
|
||||||
labelColor: { attribute: 'labelColor', color: '#444444' },
|
|
||||||
edgeLabelColor: { attribute: 'labelColor', color: '#a2b1c6' }
|
|
||||||
})
|
})
|
||||||
if (this.settings.layout.type === 'forceAtlas2') {
|
if (this.settings.layout.type === 'forceAtlas2') {
|
||||||
this.autoRunFA2Layout()
|
this.autoRunFA2Layout()
|
||||||
@@ -464,7 +509,7 @@ export default {
|
|||||||
|
|
||||||
if (layoutType === 'random') {
|
if (layoutType === 'random') {
|
||||||
random.assign(this.graph, {
|
random.assign(this.graph, {
|
||||||
rng: seedrandom(this.settings.layout.options.seedValue || 1)
|
rng: seedrandom(this.settings.layout.options.seedValue)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -497,7 +542,7 @@ export default {
|
|||||||
this.settings.layout.options.hierarchyAttributes?.map(
|
this.settings.layout.options.hierarchyAttributes?.map(
|
||||||
(_, index) => 'hierarchyAttribute' + index
|
(_, index) => 'hierarchyAttribute' + index
|
||||||
) || [],
|
) || [],
|
||||||
rng: seedrandom(this.settings.layout.options.seedValue || 1)
|
rng: seedrandom(this.settings.layout.options.seedValue)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -547,10 +592,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
autoRunFA2Layout() {
|
autoRunFA2Layout() {
|
||||||
if (this.fa2Layout.isRunning()) {
|
|
||||||
this.stopFA2Layout()
|
|
||||||
}
|
|
||||||
|
|
||||||
let iteration = 1
|
let iteration = 1
|
||||||
this.checkIteration = () => {
|
this.checkIteration = () => {
|
||||||
if (
|
if (
|
||||||
@@ -565,7 +606,7 @@ export default {
|
|||||||
this.fa2Layout.start()
|
this.fa2Layout.start()
|
||||||
},
|
},
|
||||||
setRecommendedFA2Settings() {
|
setRecommendedFA2Settings() {
|
||||||
const sensibleSettings = forceAtlas2.inferSettings(this.graph)
|
const sensibleSettings = forceAtlas2.default.inferSettings(this.graph)
|
||||||
this.settings.layout.options = {
|
this.settings.layout.options = {
|
||||||
initialIterationsAmount: 50,
|
initialIterationsAmount: 50,
|
||||||
adjustSizes: false,
|
adjustSizes: false,
|
||||||
@@ -611,7 +652,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.plotly_editor > div {
|
.plotly_editor.with_controls > div {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<Field label="Color">
|
<Field label="Color" fieldContainerClassName="test_node_color">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="nodeColorTypeOptions"
|
:options="nodeColorTypeOptions"
|
||||||
:activeOption="modelValue.type"
|
:activeOption="modelValue.type"
|
||||||
@option-change="updateColorType"
|
@option-change="updateColorType"
|
||||||
/>
|
/>
|
||||||
<Field v-if="modelValue.type === 'constant'">
|
<Field
|
||||||
|
v-if="modelValue.type === 'constant'"
|
||||||
|
fieldContainerClassName="test_node_color_value"
|
||||||
|
>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
:selectedColor="modelValue.value"
|
:selectedColor="modelValue.value"
|
||||||
@color-change="updateSettings('value', $event)"
|
@color-change="updateSettings('value', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<Field>
|
<Field fieldContainerClassName="test_node_color_value">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
v-if="modelValue.type === 'variable'"
|
v-if="modelValue.type === 'variable'"
|
||||||
:options="keyOptions"
|
:options="keyOptions"
|
||||||
@@ -23,11 +26,15 @@
|
|||||||
v-if="modelValue.type === 'calculated'"
|
v-if="modelValue.type === 'calculated'"
|
||||||
:options="nodeCalculatedColorMethodOptions"
|
:options="nodeCalculatedColorMethodOptions"
|
||||||
:value="modelValue.method"
|
:value="modelValue.method"
|
||||||
|
:clearable="false"
|
||||||
@change="updateSettings('method', $event)"
|
@change="updateSettings('method', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field>
|
<Field
|
||||||
|
v-if="modelValue.type === 'variable'"
|
||||||
|
fieldContainerClassName="test_node_color_mapping_mode"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="colorSourceUsageOptions"
|
:options="colorSourceUsageOptions"
|
||||||
:activeOption="modelValue.sourceUsage"
|
:activeOption="modelValue.sourceUsage"
|
||||||
@@ -35,7 +42,12 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-if="modelValue.sourceUsage === 'map_to'">
|
<Field
|
||||||
|
v-if="
|
||||||
|
modelValue.sourceUsage === 'map_to' ||
|
||||||
|
modelValue.type === 'calculated'
|
||||||
|
"
|
||||||
|
>
|
||||||
<ColorscalePicker
|
<ColorscalePicker
|
||||||
:selected="modelValue.colorscale"
|
:selected="modelValue.colorscale"
|
||||||
className="colorscale-picker"
|
className="colorscale-picker"
|
||||||
@@ -45,7 +57,13 @@
|
|||||||
</template>
|
</template>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-if="modelValue.type !== 'constant'" label="Color as">
|
<Field
|
||||||
|
v-if="
|
||||||
|
modelValue.sourceUsage === 'map_to' || modelValue.type === 'calculated'
|
||||||
|
"
|
||||||
|
label="Color as"
|
||||||
|
fieldContainerClassName="test_node_color_as"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="сolorAsOptions"
|
:options="сolorAsOptions"
|
||||||
:activeOption="modelValue.mode"
|
:activeOption="modelValue.mode"
|
||||||
@@ -53,7 +71,13 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field v-if="modelValue.type !== 'constant'" label="Colorscale direction">
|
<Field
|
||||||
|
v-if="
|
||||||
|
modelValue.sourceUsage === 'map_to' || modelValue.type === 'calculated'
|
||||||
|
"
|
||||||
|
label="Colorscale direction"
|
||||||
|
fieldContainerClassName="test_node_color_colorscale_direction"
|
||||||
|
>
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="сolorscaleDirections"
|
:options="сolorscaleDirections"
|
||||||
:activeOption="modelValue.colorscaleDirection"
|
:activeOption="modelValue.colorscaleDirection"
|
||||||
@@ -120,7 +144,6 @@ export default {
|
|||||||
},
|
},
|
||||||
calculated: {
|
calculated: {
|
||||||
method: 'degree',
|
method: 'degree',
|
||||||
sourceUsage: 'map_to',
|
|
||||||
colorscale: null,
|
colorscale: null,
|
||||||
mode: 'continious',
|
mode: 'continious',
|
||||||
colorscaleDirection: 'normal'
|
colorscaleDirection: 'normal'
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<Field label="Size">
|
<Field label="Size" fieldContainerClassName="test_node_size">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="nodeSizeTypeOptions"
|
:options="nodeSizeTypeOptions"
|
||||||
:activeOption="modelValue.type"
|
:activeOption="modelValue.type"
|
||||||
@option-change="updateSizeType"
|
@option-change="updateSizeType"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Field>
|
<Field fieldContainerClassName="test_node_size_value">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
v-if="modelValue.type === 'constant'"
|
v-if="modelValue.type === 'constant'"
|
||||||
:value="modelValue.value"
|
:value="modelValue.value"
|
||||||
:min="1"
|
:min="1"
|
||||||
|
class="test_node_size_value"
|
||||||
@update="updateSettings('value', $event)"
|
@update="updateSettings('value', $event)"
|
||||||
/>
|
/>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -23,20 +24,21 @@
|
|||||||
v-if="modelValue.type === 'calculated'"
|
v-if="modelValue.type === 'calculated'"
|
||||||
:options="nodeCalculatedSizeMethodOptions"
|
:options="nodeCalculatedSizeMethodOptions"
|
||||||
:value="modelValue.method"
|
:value="modelValue.method"
|
||||||
|
:clearable="false"
|
||||||
@change="updateSettings('method', $event)"
|
@change="updateSettings('method', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<template v-if="modelValue.type !== 'constant'">
|
<template v-if="modelValue.type !== 'constant'">
|
||||||
<Field label="Size scale">
|
<Field label="Size scale" fieldContainerClassName="test_node_size_scale">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.scale"
|
:value="modelValue.scale"
|
||||||
@update="updateSettings('scale', $event)"
|
@update="updateSettings('scale', $event)"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Size mode">
|
<Field label="Size mode" fieldContainerClassName="test_node_size_mode">
|
||||||
<RadioBlocks
|
<RadioBlocks
|
||||||
:options="nodeSizeModeOptions"
|
:options="nodeSizeModeOptions"
|
||||||
:activeOption="modelValue.mode"
|
:activeOption="modelValue.mode"
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<Field label="Minimum size">
|
<Field label="Minimum size" fieldContainerClassName="test_node_size_min">
|
||||||
<NumericInput
|
<NumericInput
|
||||||
:value="modelValue.min"
|
:value="modelValue.min"
|
||||||
@update="updateSettings('min', $event)"
|
@update="updateSettings('min', $event)"
|
||||||
|
|||||||
@@ -7,27 +7,33 @@
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M5 4C5 5.10457 4.10457 6 3 6C1.89543 6 1 5.10457 1 4C1 2.89543 1.89543 2 3 2C4.10457 2 5 2.89543 5 4Z"
|
d="M5 4C5 5.10457 4.10457 6 3 6C1.89543 6 1 5.10457 1 4C1 2.89543
|
||||||
|
1.89543 2 3 2C4.10457 2 5 2.89543 5 4Z"
|
||||||
fill="#A2B1C6"
|
fill="#A2B1C6"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M17 7.5C17 8.88071 15.8807 10 14.5 10C13.1193 10 12 8.88071 12 7.5C12 6.11929 13.1193 5 14.5 5C15.8807 5 17 6.11929 17 7.5Z"
|
d="M17 7.5C17 8.88071 15.8807 10 14.5 10C13.1193 10 12 8.88071 12
|
||||||
|
7.5C12 6.11929 13.1193 5 14.5 5C15.8807 5 17 6.11929 17 7.5Z"
|
||||||
fill="#A2B1C6"
|
fill="#A2B1C6"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M8 13.5C8 14.8807 6.88071 16 5.5 16C4.11929 16 3 14.8807 3 13.5C3 12.1193 4.11929 11 5.5 11C6.88071 11 8 12.1193 8 13.5Z"
|
d="M8 13.5C8 14.8807 6.88071 16 5.5 16C4.11929 16 3 14.8807 3 13.5C3
|
||||||
|
12.1193 4.11929 11 5.5 11C6.88071 11 8 12.1193 8 13.5Z"
|
||||||
fill="#A2B1C6"
|
fill="#A2B1C6"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M2.93128 5.31436L3.90527 5.08778L5.48693 11.8867L4.51294 12.1133L2.93128 5.31436Z"
|
d="M2.93128 5.31436L3.90527 5.08778L5.48693 11.8867L4.51294
|
||||||
|
12.1133L2.93128 5.31436Z"
|
||||||
fill="#A2B1C6"
|
fill="#A2B1C6"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M12.9447 7.79159L13.5548 8.58392L7.30516 13.3962L6.69507 12.6038L12.9447 7.79159Z"
|
d="M12.9447 7.79159L13.5548 8.58392L7.30516 13.3962L6.69507
|
||||||
|
12.6038L12.9447 7.79159Z"
|
||||||
fill="#A2B1C6"
|
fill="#A2B1C6"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M14.1316 6.51712L3.13166 3.51723L2.86844 4.48202L13.8684 7.48191L14.1316 6.51712Z"
|
d="M14.1316 6.51712L3.13166 3.51723L2.86844 4.48202L13.8684
|
||||||
|
7.48191L14.1316 6.51712Z"
|
||||||
fill="#A2B1C6"
|
fill="#A2B1C6"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
129
src/components/svg/settings.vue
Normal file
129
src/components/svg/settings.vue
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clip-path="url(#clip0_2236_2707)">
|
||||||
|
<path
|
||||||
|
d="M8.11441 0.0274963C7.73011 0.0274918 7.35916 0.168422 7.0718
|
||||||
|
0.423594C6.78445 0.678766 6.60065 1.03046 6.55522 1.41207L7.89167
|
||||||
|
1.57122C7.89815 1.51669 7.9244 1.46644 7.96546 1.42997C8.00651 1.39351
|
||||||
|
8.05951 1.37337 8.11441 1.37337V0.0274963ZM9.88559
|
||||||
|
0.0274963H8.11441V1.37337H9.88559V0.0274963ZM11.4448
|
||||||
|
1.41207C11.3994 1.03046 11.2156 0.678766 10.9282 0.423594C10.6408
|
||||||
|
0.168422 10.2699 0.0274918 9.88559 0.0274963V1.37337C9.94049 1.37337
|
||||||
|
9.99349 1.39351 10.0345 1.42997C10.0756 1.46644 10.1018 1.51669
|
||||||
|
10.1083 1.57122L11.4448 1.41207ZM11.5815 2.56055L11.4448
|
||||||
|
1.41207L10.1083 1.57122L10.2449 2.7197L11.5815 2.56055ZM14.349
|
||||||
|
3.0888L13.282 3.5464L13.8123 4.78326L14.8793 4.32566L14.349
|
||||||
|
3.0888ZM16.3276 3.74682C16.1355 3.41401 15.8279 3.16322 15.4633
|
||||||
|
3.04195C15.0986 2.92067 14.7021 2.93735 14.349 3.0888L14.8793
|
||||||
|
4.32566C14.9298 4.304 14.9865 4.30172 15.0386 4.31904C15.0907
|
||||||
|
4.33636 15.1346 4.3722 15.1621 4.41976L16.3276 3.74682ZM17.2132
|
||||||
|
5.28067L16.3276 3.74682L15.1621 4.41976L16.0477 5.95361L17.2132
|
||||||
|
5.28067ZM16.7937 7.32326C17.1015 7.09311 17.3142 6.75809 17.3915
|
||||||
|
6.38165C17.4688 6.00521 17.4054 5.61348 17.2132 5.28067L16.0477
|
||||||
|
5.95361C16.0751 6.00117 16.0842 6.05714 16.0731 6.11093C16.0621
|
||||||
|
6.16471 16.0317 6.21257 15.9877 6.24544L16.7937 7.32326ZM15.7861
|
||||||
|
8.07673L16.7937 7.32326L15.9877 6.24544L14.9801 6.99891L15.7861
|
||||||
|
8.07673ZM16.7937 10.6767L15.7861 9.92327L14.9802 11.0011L15.9877
|
||||||
|
11.7546L16.7937 10.6767ZM17.2132 12.7193C17.4054 12.3865 17.4688
|
||||||
|
11.9948 17.3915 11.6183C17.3142 11.2419 17.1015 10.9069 16.7937
|
||||||
|
10.6767L15.9877 11.7546C16.0317 11.7874 16.0621 11.8353 16.0731
|
||||||
|
11.8891C16.0842 11.9429 16.0751 11.9988 16.0477 12.0464L17.2132
|
||||||
|
12.7193ZM16.3276 14.2532L17.2132 12.7193L16.0477 12.0464L15.1621
|
||||||
|
13.5802L16.3276 14.2532ZM14.349 14.9112C14.7021 15.0626 15.0986
|
||||||
|
15.0793 15.4633 14.958C15.8279 14.8368 16.1355 14.586 16.3276
|
||||||
|
14.2532L15.1621 13.5802C15.1346 13.6278 15.0907 13.6636 15.0386
|
||||||
|
13.6809C14.9865 13.6982 14.9298 13.6959 14.8793 13.6742L14.349
|
||||||
|
14.9112ZM13.282 14.4536L14.349 14.9112L14.8793 13.6742L13.8125
|
||||||
|
13.2167L13.282 14.4536ZM11.4448 16.5879L11.5814 15.4394L10.245
|
||||||
|
15.2803L10.1083 16.4288L11.4448 16.5879ZM9.88559 17.9725C10.2699
|
||||||
|
17.9725 10.6408 17.8316 10.9282 17.5764C11.2156 17.3212 11.3994
|
||||||
|
16.9695 11.4448 16.5879L10.1083 16.4288C10.1018 16.4833 10.0756
|
||||||
|
16.5336 10.0345 16.57C9.99349 16.6065 9.94049 16.6266 9.88559
|
||||||
|
16.6266V17.9725ZM8.11441
|
||||||
|
17.9725H9.88559V16.6266H8.11441V17.9725ZM6.55522
|
||||||
|
16.5879C6.60065 16.9695 6.78445 17.3212 7.0718
|
||||||
|
17.5764C7.35916 17.8316 7.73011 17.9725 8.11441
|
||||||
|
17.9725V16.6266C8.05951 16.6266 8.00651 16.6065
|
||||||
|
7.96546 16.57C7.9244 16.5336 7.89815 16.4833 7.89167
|
||||||
|
16.4288L6.55522 16.5879ZM6.44172 15.6342L6.55522 16.5879L7.89167
|
||||||
|
16.4288L7.77817 15.475L6.44172 15.6342ZM4.00097 13.2967L3.12066
|
||||||
|
13.6742L3.65104 14.9112L4.53136 14.5337L4.00097 13.2967ZM3.12066
|
||||||
|
13.6742C3.07021 13.6959 3.01346 13.6982 2.96138 13.6809C2.90929
|
||||||
|
13.6636 2.86536 13.6278 2.83791 13.5802L1.67238 14.2532C1.86453
|
||||||
|
14.586 2.17205 14.8368 2.53672 14.958C2.90138 15.0793 3.29785
|
||||||
|
15.0626 3.65104 14.9112L3.12066 13.6742ZM2.83791 13.5802L1.95233
|
||||||
|
12.0464L0.786798 12.7193L1.67238 14.2532L2.83791 13.5802ZM1.95233
|
||||||
|
12.0464C1.92487 11.9988 1.91582 11.9429 1.92688 11.8891C1.93794
|
||||||
|
11.8353 1.96834 11.7874 2.01233 11.7546L1.20626 10.6767C0.898499
|
||||||
|
10.9069 0.685823 11.2419 0.608517 11.6183C0.531211 11.9948 0.594643
|
||||||
|
12.3865 0.786798 12.7193L1.95233 12.0464ZM2.01233 11.7546L2.70007
|
||||||
|
11.2403L1.89389 10.1625L1.20626 10.6767L2.01233 11.7546ZM1.20626
|
||||||
|
7.32326L1.894 7.83761L2.70007 6.75979L2.01233 6.24544L1.20626
|
||||||
|
7.32326ZM0.786798 5.28067C0.594643 5.61348 0.531211 6.00521
|
||||||
|
0.608517 6.38165C0.685823 6.75809 0.898499 7.09311 1.20626
|
||||||
|
7.32326L2.01233 6.24544C1.96834 6.21257 1.93794 6.16471 1.92688
|
||||||
|
6.11093C1.91582 6.05714 1.92487 6.00117 1.95233 5.95361L0.786798
|
||||||
|
5.28067ZM1.67238 3.74682L0.786798 5.28067L1.95233 5.95361L2.83791
|
||||||
|
4.41976L1.67238 3.74682ZM3.65104 3.0888C3.29785 2.93735 2.90138
|
||||||
|
2.92067 2.53672 3.04195C2.17205 3.16322 1.86453 3.41401 1.67238
|
||||||
|
3.74682L2.83791 4.41976C2.86536 4.37223 2.90929 4.33641 2.96138
|
||||||
|
4.31909C3.01346 4.30176 3.07021 4.30414 3.12066 4.32577L3.65104
|
||||||
|
3.0888ZM4.53136 3.46632L3.65104 3.0888L3.12066 4.32577L4.00097
|
||||||
|
4.70329L4.53136 3.46632ZM6.55522 1.41207L6.44172 2.36584L7.77817
|
||||||
|
2.52499L7.89167 1.57122L6.55522 1.41207ZM6.3585 4.5022C7.04018
|
||||||
|
4.11795 7.6696 3.4366 7.77817 2.52499L6.44172 2.36584C6.39887 2.72575
|
||||||
|
6.13116 3.08544 5.69756 3.32994L6.3585 4.5022ZM4.00097 4.70329C4.81792
|
||||||
|
5.05367 5.68948 4.87938 6.3585 4.5022L5.69756 3.32994C5.28213 3.56412
|
||||||
|
4.85852 3.60663 4.53136 3.46632L4.00097 4.70329ZM3.72866 9C3.72866
|
||||||
|
8.20369 3.44288 7.3153 2.70007 6.75979L1.894 7.83761C2.19884 8.06562
|
||||||
|
2.38278 8.48834 2.38278 9H3.72866ZM6.3585 13.4978C5.68948 13.1206
|
||||||
|
4.81792 12.9463 4.00097 13.2967L4.53136 14.5337C4.85852 14.3934
|
||||||
|
5.28213 14.436 5.69756 14.6703L6.3585 13.4978ZM2.70007
|
||||||
|
11.2403C3.44299 10.6848 3.72866 9.79631 3.72866 9H2.38278C2.38278
|
||||||
|
9.51177 2.19873 9.9346 1.89389 10.1625L2.70007 11.2403ZM11.5243
|
||||||
|
13.4358C10.9034 13.8057 10.3448 14.4432 10.245 15.2803L11.5814
|
||||||
|
15.4394C11.6183 15.1293 11.8415 14.8134 12.2132 14.5919L11.5243
|
||||||
|
13.4358ZM13.8125 13.2167C13.0169 12.8756 12.1673 13.0527 11.5243
|
||||||
|
13.4358L12.2132 14.5919C12.5925 14.3659 12.9839 14.3258 13.282
|
||||||
|
14.4536L13.8125 13.2167ZM7.77817 15.475C7.6696 14.5635 7.04018
|
||||||
|
13.8822 6.3585 13.4978L5.69756 14.6703C6.13116 14.9147 6.39887
|
||||||
|
15.2742 6.44172 15.6342L7.77817 15.475ZM14.047 9C14.047 9.71656
|
||||||
|
14.316 10.5045 14.9802 11.0011L15.7861 9.92327C15.547 9.74449 15.3929
|
||||||
|
9.41139 15.3929 9H14.047ZM14.9801 6.99891C14.3159 7.49553 14.047
|
||||||
|
8.28332 14.047 9H15.3929C15.3929 8.58861 15.547 8.2555 15.7861
|
||||||
|
8.07673L14.9801 6.99891ZM11.5243 4.56422C12.1673 4.94734 13.0168
|
||||||
|
5.12444 13.8123 4.78326L13.282 3.5464C12.9839 3.67426 12.5925
|
||||||
|
3.63399 12.2132 3.408L11.5243 4.56422ZM10.2449 2.7197C10.3446
|
||||||
|
3.55683 10.9034 4.19433 11.5243 4.56422L12.2132 3.408C11.8415
|
||||||
|
3.1866 11.6184 2.87077 11.5815 2.56055L10.2449 2.7197Z"
|
||||||
|
fill="#A2B1C6"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M11.0935 9C11.0935 7.84373 10.1562 6.90642 8.99988 6.90642C7.84361
|
||||||
|
6.90642 6.90629 7.84373 6.90629 9C6.90629 10.1563 7.84361 11.0936
|
||||||
|
8.99988 11.0936C10.1562 11.0936 11.0935 10.1563 11.0935 9ZM12.2898
|
||||||
|
9C12.2898 10.817 10.8169 12.2899 8.99988 12.2899C7.18289 12.2899
|
||||||
|
5.70996 10.817 5.70996 9C5.70996 7.18301 7.18289 5.71008 8.99988
|
||||||
|
5.71008C10.8169 5.71008 12.2898 7.18301 12.2898 9Z"
|
||||||
|
fill="#A2B1C6"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2236_2707">
|
||||||
|
<rect width="18" height="18" fill="white" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SettingsIcon'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M3 4C3 3.44772 3.44772 3 4 3H14C14.5523 3 15 3.44772 15 4V14C15 14.5523 14.5523 15 14 15H4C3.44772 15 3 14.5523 3 14V4Z"
|
d="M3 4C3 3.44772 3.44772 3 4 3H14C14.5523 3 15 3.44772 15 4V14C15
|
||||||
|
14.5523 14.5523 15 14 15H4C3.44772 15 3 14.5523 3 14V4Z"
|
||||||
fill="#A2B1C6"
|
fill="#A2B1C6"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -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,16 +45,17 @@ 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
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildEdges(graph, dataSources, options) {
|
export function buildEdges(graph, dataSources, options) {
|
||||||
const docColumn = Object.keys(dataSources)[0] || 'doc'
|
const docColumn = Object.keys(dataSources)[0]
|
||||||
const { objectType, edgeSource, edgeTarget } = options.structure
|
const { objectType, edgeSource, edgeTarget } = options.structure
|
||||||
|
|
||||||
if (objectType && edgeSource && edgeTarget) {
|
if (objectType && edgeSource && edgeTarget) {
|
||||||
@@ -36,8 +68,7 @@ export function buildEdges(graph, dataSources, options) {
|
|||||||
const target = edge[edgeTarget]
|
const target = edge[edgeTarget]
|
||||||
if (graph.hasNode(source) && graph.hasNode(target)) {
|
if (graph.hasNode(source) && graph.hasNode(target)) {
|
||||||
graph.addEdge(source, target, {
|
graph.addEdge(source, target, {
|
||||||
data: edge,
|
data: edge
|
||||||
labelColor: options.style.edges.label.color
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -110,10 +141,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 +228,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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +259,6 @@ function getColorMethod(
|
|||||||
colorscale[index % colorscale.length]
|
colorscale[index % colorscale.length]
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
return (attributes, nodeId) => {
|
return (attributes, nodeId) => {
|
||||||
const category = sourceGetter(nodeId, attributes)
|
const category = sourceGetter(nodeId, attributes)
|
||||||
attributes.color = colorMap[category]
|
attributes.color = colorMap[category]
|
||||||
@@ -240,6 +274,7 @@ function getColorMethod(
|
|||||||
const value = sourceGetter(nodeId, attributes)
|
const value = sourceGetter(nodeId, attributes)
|
||||||
const normalizedValue = (value - min) / (max - min)
|
const normalizedValue = (value - min) / (max - min)
|
||||||
if (isNaN(normalizedValue)) {
|
if (isNaN(normalizedValue)) {
|
||||||
|
attributes.color = '#000000'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const exactMatch = normalizedColorscale.find(
|
const exactMatch = normalizedColorscale.find(
|
||||||
@@ -251,9 +286,9 @@ function getColorMethod(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rightColorIndex = normalizedColorscale.findIndex(
|
const rightColorIndex = normalizedColorscale.findIndex(
|
||||||
([value]) => value >= normalizedValue
|
([value]) => value > normalizedValue
|
||||||
)
|
)
|
||||||
const leftColorIndex = (rightColorIndex || 1) - 1
|
const leftColorIndex = rightColorIndex - 1
|
||||||
const right = normalizedColorscale[rightColorIndex]
|
const right = normalizedColorscale[rightColorIndex]
|
||||||
const left = normalizedColorscale[leftColorIndex]
|
const left = normalizedColorscale[leftColorIndex]
|
||||||
const interpolationFactor =
|
const interpolationFactor =
|
||||||
@@ -290,18 +325,3 @@ function getEdgeValueScale(graph, sourceGetter) {
|
|||||||
}, new Set())
|
}, new Set())
|
||||||
return Array.from(scaleSet).sort((a, b) => a - b)
|
return Array.from(scaleSet).sort((a, b) => a - b)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOptionsFromDataSources(dataSources) {
|
|
||||||
if (!dataSources) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(dataSources).map(name => ({
|
|
||||||
value: name,
|
|
||||||
label: name
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getOptionsFromDataSources
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
@@ -115,8 +120,8 @@ export default {
|
|||||||
this.resizeObserver.observe(this.$refs.chartContainer)
|
this.resizeObserver.observe(this.$refs.chartContainer)
|
||||||
if (this.dataSources) {
|
if (this.dataSources) {
|
||||||
dereference.default(this.state.data, this.dataSources)
|
dereference.default(this.state.data, this.dataSources)
|
||||||
this.updatePlotly()
|
|
||||||
}
|
}
|
||||||
|
this.handleResize()
|
||||||
},
|
},
|
||||||
activated() {
|
activated() {
|
||||||
this.useResizeHandler = true
|
this.useResizeHandler = true
|
||||||
@@ -129,6 +134,10 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async handleResize() {
|
async handleResize() {
|
||||||
|
// Call updatePlotly twice because there is a small gap (for scrolling?)
|
||||||
|
// on right and bottom of the plot.
|
||||||
|
// After the second call it's good.
|
||||||
|
this.updatePlotly()
|
||||||
this.updatePlotly()
|
this.updatePlotly()
|
||||||
},
|
},
|
||||||
onRender() {
|
onRender() {
|
||||||
@@ -179,13 +188,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,19 +1,30 @@
|
|||||||
<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 no-data">
|
||||||
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 invalid-data"
|
||||||
|
>
|
||||||
|
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 +33,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 '@/components/Graph/GraphEditor.vue'
|
||||||
import GraphEditor from './GraphEditor.vue'
|
import { dataSourceIsValid } from '@/lib/graphHelper'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Graph',
|
name: 'Graph',
|
||||||
@@ -33,11 +44,14 @@ 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',
|
||||||
'update:exportToHtmlEnabled',
|
'update:exportToHtmlEnabled',
|
||||||
|
'update:exportToPngEnabled',
|
||||||
|
'update:exportToClipboardEnabled',
|
||||||
'update',
|
'update',
|
||||||
'loadingImageCompleted'
|
'loadingImageCompleted'
|
||||||
],
|
],
|
||||||
@@ -46,10 +60,26 @@ export default {
|
|||||||
resizeObserver: null
|
resizeObserver: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
dataSourceIsValid() {
|
||||||
|
return !this.dataSources || dataSourceIsValid(this.dataSources)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
async showViewSettings() {
|
||||||
|
await this.$nextTick()
|
||||||
|
this.handleResize()
|
||||||
|
},
|
||||||
|
dataSources() {
|
||||||
|
this.$emit('update:exportToPngEnabled', !!this.dataSources)
|
||||||
|
this.$emit('update:exportToClipboardEnabled', !!this.dataSources)
|
||||||
|
}
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$emit('update:exportToSvgEnabled', false)
|
this.$emit('update:exportToSvgEnabled', false)
|
||||||
this.$emit('update:exportToHtmlEnabled', false)
|
this.$emit('update:exportToHtmlEnabled', false)
|
||||||
|
this.$emit('update:exportToPngEnabled', !!this.dataSources)
|
||||||
|
this.$emit('update:exportToClipboardEnabled', !!this.dataSources)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.resizeObserver = new ResizeObserver(this.handleResize)
|
this.resizeObserver = new ResizeObserver(this.handleResize)
|
||||||
@@ -66,14 +96,14 @@ export default {
|
|||||||
await this.$refs.graphEditor.saveAsPng()
|
await this.$refs.graphEditor.saveAsPng()
|
||||||
this.$emit('loadingImageCompleted')
|
this.$emit('loadingImageCompleted')
|
||||||
},
|
},
|
||||||
async prepareCopy() {
|
prepareCopy() {
|
||||||
return await this.$refs.graphEditor.prepareCopy()
|
return this.$refs.graphEditor.prepareCopy()
|
||||||
},
|
},
|
||||||
async handleResize() {
|
async handleResize() {
|
||||||
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 +111,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')"
|
||||||
@@ -30,7 +31,7 @@ import fIo from '@/lib/utils/fileIo'
|
|||||||
import $ from 'jquery'
|
import $ from 'jquery'
|
||||||
import 'pivottable'
|
import 'pivottable'
|
||||||
import 'pivottable/dist/pivot.css'
|
import 'pivottable/dist/pivot.css'
|
||||||
import PivotUi from './PivotUi'
|
import PivotUi from './PivotUi/index.vue'
|
||||||
import pivotHelper from './pivotHelper'
|
import pivotHelper from './pivotHelper'
|
||||||
import Chart from '@/views/MainView/Workspace/Tabs/Tab/DataView/Chart'
|
import Chart from '@/views/MainView/Workspace/Tabs/Tab/DataView/Chart'
|
||||||
import chartHelper from '@/lib/chartHelper'
|
import chartHelper from '@/lib/chartHelper'
|
||||||
@@ -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() {
|
||||||
@@ -222,12 +227,12 @@ export default {
|
|||||||
|
|
||||||
async prepareCopy() {
|
async prepareCopy() {
|
||||||
if (this.viewCustomChart) {
|
if (this.viewCustomChart) {
|
||||||
return await this.$refs.customChart.prepareCopy()
|
return this.$refs.customChart.prepareCopy()
|
||||||
}
|
}
|
||||||
if (this.viewStandartChart) {
|
if (this.viewStandartChart) {
|
||||||
return await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'png')
|
return chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'png')
|
||||||
}
|
}
|
||||||
return await pivotHelper.getPivotCanvas(this.$refs.pivotOutput)
|
return pivotHelper.getPivotCanvas(this.$refs.pivotOutput)
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveAsSvg() {
|
async saveAsSvg() {
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -7,14 +7,17 @@
|
|||||||
v-model:exportToPngEnabled="exportToPngEnabled"
|
v-model:exportToPngEnabled="exportToPngEnabled"
|
||||||
v-model:exportToSvgEnabled="exportToSvgEnabled"
|
v-model:exportToSvgEnabled="exportToSvgEnabled"
|
||||||
v-model:exportToHtmlEnabled="exportToHtmlEnabled"
|
v-model:exportToHtmlEnabled="exportToHtmlEnabled"
|
||||||
|
v-model:exportToClipboardEnabled="exportToClipboardEnabled"
|
||||||
: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')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<side-tool-bar panel="dataView" @switch-to="$emit('switchTo', $event)">
|
<side-tool-bar panel="dataView" @switch-to="$emit('switchTo', $event)">
|
||||||
<icon-button
|
<icon-button
|
||||||
|
ref="chartBtn"
|
||||||
:active="mode === 'chart'"
|
:active="mode === 'chart'"
|
||||||
tooltip="Switch to chart"
|
tooltip="Switch to chart"
|
||||||
tooltipPosition="top-left"
|
tooltipPosition="top-left"
|
||||||
@@ -32,6 +35,7 @@
|
|||||||
<pivot-icon />
|
<pivot-icon />
|
||||||
</icon-button>
|
</icon-button>
|
||||||
<icon-button
|
<icon-button
|
||||||
|
ref="graphBtn"
|
||||||
:active="mode === 'graph'"
|
:active="mode === 'graph'"
|
||||||
tooltip="Switch to graph"
|
tooltip="Switch to graph"
|
||||||
tooltipPosition="top-left"
|
tooltipPosition="top-left"
|
||||||
@@ -43,6 +47,19 @@
|
|||||||
<div class="side-tool-bar-divider" />
|
<div class="side-tool-bar-divider" />
|
||||||
|
|
||||||
<icon-button
|
<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
|
||||||
|
ref="pngExportBtn"
|
||||||
:disabled="!exportToPngEnabled || loadingImage"
|
:disabled="!exportToPngEnabled || loadingImage"
|
||||||
:loading="loadingImage"
|
:loading="loadingImage"
|
||||||
tooltip="Save as PNG image"
|
tooltip="Save as PNG image"
|
||||||
@@ -72,6 +89,7 @@
|
|||||||
</icon-button>
|
</icon-button>
|
||||||
<icon-button
|
<icon-button
|
||||||
ref="copyToClipboardBtn"
|
ref="copyToClipboardBtn"
|
||||||
|
:disabled="!exportToClipboardEnabled"
|
||||||
:loading="copyingImage"
|
:loading="copyingImage"
|
||||||
tooltip="Copy visualisation to clipboard"
|
tooltip="Copy visualisation to clipboard"
|
||||||
tooltipPosition="top-left"
|
tooltipPosition="top-left"
|
||||||
@@ -103,6 +121,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 +142,7 @@ export default {
|
|||||||
ChartIcon,
|
ChartIcon,
|
||||||
PivotIcon,
|
PivotIcon,
|
||||||
GraphIcon,
|
GraphIcon,
|
||||||
|
SettingsIcon,
|
||||||
ExportToSvgIcon,
|
ExportToSvgIcon,
|
||||||
PngIcon,
|
PngIcon,
|
||||||
HtmlIcon,
|
HtmlIcon,
|
||||||
@@ -141,6 +161,7 @@ export default {
|
|||||||
exportToPngEnabled: true,
|
exportToPngEnabled: true,
|
||||||
exportToSvgEnabled: true,
|
exportToSvgEnabled: true,
|
||||||
exportToHtmlEnabled: true,
|
exportToHtmlEnabled: true,
|
||||||
|
exportToClipboardEnabled: true,
|
||||||
loadingImage: false,
|
loadingImage: false,
|
||||||
copyingImage: false,
|
copyingImage: false,
|
||||||
preparingCopy: false,
|
preparingCopy: false,
|
||||||
@@ -150,7 +171,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: {
|
||||||
@@ -162,6 +184,7 @@ export default {
|
|||||||
mode(newMode, oldMode) {
|
mode(newMode, oldMode) {
|
||||||
this.$emit('update')
|
this.$emit('update')
|
||||||
this.exportToPngEnabled = true
|
this.exportToPngEnabled = true
|
||||||
|
this.exportToClipboardEnabled = true
|
||||||
this.initOptionsByMode[oldMode] = this.getOptionsForSave()
|
this.initOptionsByMode[oldMode] = this.getOptionsForSave()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -238,7 +261,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 })
|
||||||
|
|||||||
1874
tests/components/Graph/GraphEditor.spec.js
Normal file
1874
tests/components/Graph/GraphEditor.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
1206
tests/lib/graphHelper.spec.js
Normal file
1206
tests/lib/graphHelper.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
20
tests/testUtils.js
Normal file
20
tests/testUtils.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export function waitCondition(condition, timeoutMs = 5000) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (condition()) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const start = new Date().getTime()
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (condition()) {
|
||||||
|
clearInterval(interval)
|
||||||
|
resolve()
|
||||||
|
} else {
|
||||||
|
if (new Date().getTime() - start > timeoutMs) {
|
||||||
|
clearInterval(interval)
|
||||||
|
reject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ describe('Chart.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('getOptionsForSave called with proper arguments', () => {
|
it('getOptionsForSave called with proper arguments', () => {
|
||||||
// mount the component
|
|
||||||
const wrapper = mount(Chart, {
|
const wrapper = mount(Chart, {
|
||||||
global: {
|
global: {
|
||||||
mocks: { $store }
|
mocks: { $store }
|
||||||
@@ -30,7 +29,6 @@ describe('Chart.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('emits update when plotly updates', async () => {
|
it('emits update when plotly updates', async () => {
|
||||||
// mount the component
|
|
||||||
const wrapper = mount(Chart, {
|
const wrapper = mount(Chart, {
|
||||||
global: {
|
global: {
|
||||||
mocks: { $store }
|
mocks: { $store }
|
||||||
@@ -48,7 +46,6 @@ describe('Chart.vue', () => {
|
|||||||
points: [80]
|
points: [80]
|
||||||
}
|
}
|
||||||
|
|
||||||
// mount the component
|
|
||||||
const wrapper = mount(Chart, {
|
const wrapper = mount(Chart, {
|
||||||
props: {
|
props: {
|
||||||
dataSources,
|
dataSources,
|
||||||
@@ -187,7 +184,8 @@ describe('Chart.vue', () => {
|
|||||||
const wrapper = mount(Chart, {
|
const wrapper = mount(Chart, {
|
||||||
attachTo: document.body,
|
attachTo: document.body,
|
||||||
props: {
|
props: {
|
||||||
dataSources
|
dataSources,
|
||||||
|
showViewSettings: true
|
||||||
},
|
},
|
||||||
global: {
|
global: {
|
||||||
mocks: { $store }
|
mocks: { $store }
|
||||||
@@ -207,4 +205,41 @@ describe('Chart.vue', () => {
|
|||||||
expect(wrapper.find('.Select__menu').text()).to.contain('name' + 'points')
|
expect(wrapper.find('.Select__menu').text()).to.contain('name' + 'points')
|
||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('hides and shows controls depending on showViewSettings and resizes the plot', async () => {
|
||||||
|
const wrapper = mount(Chart, {
|
||||||
|
attachTo: document.body,
|
||||||
|
props: {
|
||||||
|
dataSources: null,
|
||||||
|
showViewSettings: false
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
mocks: { $store }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// don't call flushPromises here, otherwize resize observer will be call to often
|
||||||
|
// which causes ResizeObserver loop completed with undelivered notifications.
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
const plot = wrapper.find('.svg-container').wrapperElement
|
||||||
|
await flushPromises()
|
||||||
|
const initialPlotWidth = plot.scrollWidth
|
||||||
|
const initialPlotHeight = plot.scrollHeight
|
||||||
|
|
||||||
|
expect(wrapper.find('.plotly_editor .editor_controls').exists()).to.equal(
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapper.setProps({ showViewSettings: true })
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(plot.scrollWidth).not.to.equal(initialPlotWidth)
|
||||||
|
expect(plot.scrollHeight).to.equal(initialPlotHeight)
|
||||||
|
expect(wrapper.find('.plotly_editor .editor_controls').exists()).to.equal(
|
||||||
|
true
|
||||||
|
)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import DataView from '@/views/MainView/Workspace/Tabs/Tab/DataView'
|
import DataView from '@/views/MainView/Workspace/Tabs/Tab/DataView/index.vue'
|
||||||
import sinon from 'sinon'
|
import sinon from 'sinon'
|
||||||
import { nextTick } from 'vue'
|
import { nextTick } from 'vue'
|
||||||
import cIo from '@/lib/utils/clipboardIo'
|
import cIo from '@/lib/utils/clipboardIo'
|
||||||
@@ -15,7 +15,7 @@ describe('DataView.vue', () => {
|
|||||||
it('emits update on mode changing', async () => {
|
it('emits update on mode changing', async () => {
|
||||||
const wrapper = mount(DataView, {
|
const wrapper = mount(DataView, {
|
||||||
global: {
|
global: {
|
||||||
stubs: { chart: true }
|
mocks: { $store }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -29,7 +29,10 @@ describe('DataView.vue', () => {
|
|||||||
it('method getOptionsForSave calls the same method of the current view component', async () => {
|
it('method getOptionsForSave calls the same method of the current view component', async () => {
|
||||||
const wrapper = mount(DataView, {
|
const wrapper = mount(DataView, {
|
||||||
global: {
|
global: {
|
||||||
mocks: { $store }
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -53,13 +56,28 @@ describe('DataView.vue', () => {
|
|||||||
expect(wrapper.vm.getOptionsForSave()).to.eql({
|
expect(wrapper.vm.getOptionsForSave()).to.eql({
|
||||||
here_are: 'pivot_settings'
|
here_are: 'pivot_settings'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const graphBtn = wrapper.findComponent({ ref: 'graphBtn' })
|
||||||
|
await graphBtn.trigger('click')
|
||||||
|
|
||||||
|
const graph = wrapper.findComponent({ name: 'graph' }).vm
|
||||||
|
sinon
|
||||||
|
.stub(graph, 'getOptionsForSave')
|
||||||
|
.returns({ here_are: 'graph_settings' })
|
||||||
|
|
||||||
|
expect(wrapper.vm.getOptionsForSave()).to.eql({
|
||||||
|
here_are: 'graph_settings'
|
||||||
|
})
|
||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('method saveAsSvg calls the same method of the current view component', async () => {
|
it('method saveAsSvg calls the same method of the current view component', async () => {
|
||||||
const wrapper = mount(DataView, {
|
const wrapper = mount(DataView, {
|
||||||
global: {
|
global: {
|
||||||
mocks: { $store }
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -87,13 +105,22 @@ describe('DataView.vue', () => {
|
|||||||
// Export to svg
|
// Export to svg
|
||||||
await svgBtn.trigger('click')
|
await svgBtn.trigger('click')
|
||||||
expect(pivot.saveAsSvg.calledOnce).to.equal(true)
|
expect(pivot.saveAsSvg.calledOnce).to.equal(true)
|
||||||
|
|
||||||
|
// Switch to graph - svg disabled
|
||||||
|
const graphBtn = wrapper.findComponent({ ref: 'graphBtn' })
|
||||||
|
await graphBtn.trigger('click')
|
||||||
|
expect(svgBtn.attributes('disabled')).to.not.equal(undefined)
|
||||||
|
|
||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('method saveAsHtml calls the same method of the current view component', async () => {
|
it('method saveAsHtml calls the same method of the current view component', async () => {
|
||||||
const wrapper = mount(DataView, {
|
const wrapper = mount(DataView, {
|
||||||
global: {
|
global: {
|
||||||
mocks: { $store }
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -114,9 +141,76 @@ describe('DataView.vue', () => {
|
|||||||
const pivot = wrapper.findComponent({ name: 'pivot' }).vm
|
const pivot = wrapper.findComponent({ name: 'pivot' }).vm
|
||||||
sinon.spy(pivot, 'saveAsHtml')
|
sinon.spy(pivot, 'saveAsHtml')
|
||||||
|
|
||||||
// Export to svg
|
// Export to html
|
||||||
await htmlBtn.trigger('click')
|
await htmlBtn.trigger('click')
|
||||||
expect(pivot.saveAsHtml.calledOnce).to.equal(true)
|
expect(pivot.saveAsHtml.calledOnce).to.equal(true)
|
||||||
|
|
||||||
|
// Switch to graph - htmlBtn disabled
|
||||||
|
const graphBtn = wrapper.findComponent({ ref: 'graphBtn' })
|
||||||
|
await graphBtn.trigger('click')
|
||||||
|
expect(htmlBtn.attributes('disabled')).to.not.equal(undefined)
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('method saveAsPng calls the same method of the current view component', async () => {
|
||||||
|
const clock = sinon.useFakeTimers()
|
||||||
|
const wrapper = mount(DataView, {
|
||||||
|
global: {
|
||||||
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Find chart and stub the method
|
||||||
|
const chart = wrapper.findComponent({ name: 'Chart' }).vm
|
||||||
|
sinon.stub(chart, 'saveAsPng').callsFake(() => {
|
||||||
|
chart.$emit('loadingImageCompleted')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Export to png
|
||||||
|
const pngBtn = wrapper.findComponent({ ref: 'pngExportBtn' })
|
||||||
|
await pngBtn.trigger('click')
|
||||||
|
await clock.tick(0)
|
||||||
|
expect(chart.saveAsPng.calledOnce).to.equal(true)
|
||||||
|
|
||||||
|
// Switch to pivot
|
||||||
|
const pivotBtn = wrapper.findComponent({ ref: 'pivotBtn' })
|
||||||
|
await pivotBtn.trigger('click')
|
||||||
|
|
||||||
|
// Find pivot and stub the method
|
||||||
|
const pivot = wrapper.findComponent({ name: 'pivot' }).vm
|
||||||
|
sinon.stub(pivot, 'saveAsPng').callsFake(() => {
|
||||||
|
pivot.$emit('loadingImageCompleted')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Export to png
|
||||||
|
await pngBtn.trigger('click')
|
||||||
|
await clock.tick(0)
|
||||||
|
expect(pivot.saveAsPng.calledOnce).to.equal(true)
|
||||||
|
|
||||||
|
// Switch to graph
|
||||||
|
const graphBtn = wrapper.findComponent({ ref: 'graphBtn' })
|
||||||
|
await graphBtn.trigger('click')
|
||||||
|
|
||||||
|
// Save as png is disabled because there is no data
|
||||||
|
expect(pngBtn.attributes('disabled')).to.not.equal(undefined)
|
||||||
|
|
||||||
|
await wrapper.setProps({ dataSource: { doc: [] } })
|
||||||
|
|
||||||
|
// Find graph and stub the method
|
||||||
|
const graph = wrapper.findComponent({ name: 'graph' }).vm
|
||||||
|
sinon.stub(graph, 'saveAsPng').callsFake(() => {
|
||||||
|
graph.$emit('loadingImageCompleted')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Export to png
|
||||||
|
await pngBtn.trigger('click')
|
||||||
|
await clock.tick(0)
|
||||||
|
expect(graph.saveAsPng.calledOnce).to.equal(true)
|
||||||
|
|
||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -276,4 +370,52 @@ describe('DataView.vue', () => {
|
|||||||
expect(wrapper.vm.copyToClipboard.calledOnce).to.equal(false)
|
expect(wrapper.vm.copyToClipboard.calledOnce).to.equal(false)
|
||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('saves visualisation options and restores them when switch between modes', async () => {
|
||||||
|
const wrapper = mount(DataView, {
|
||||||
|
props: {
|
||||||
|
initMode: 'graph',
|
||||||
|
initOptions: { test_options: 'graph_options_from_inquiery' }
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
mocks: { $store },
|
||||||
|
stubs: {
|
||||||
|
chart: true,
|
||||||
|
graph: true,
|
||||||
|
pivot: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const getOptionsForSaveStub = sinon.stub(wrapper.vm, 'getOptionsForSave')
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.findComponent({ name: 'graph' }).props('initOptions')
|
||||||
|
).to.eql({ test_options: 'graph_options_from_inquiery' })
|
||||||
|
|
||||||
|
getOptionsForSaveStub.returns({ test_options: 'latest_graph_options' })
|
||||||
|
const chartBtn = wrapper.findComponent({ ref: 'chartBtn' })
|
||||||
|
await chartBtn.trigger('click')
|
||||||
|
|
||||||
|
getOptionsForSaveStub.returns({ test_options: 'chart_settings' })
|
||||||
|
|
||||||
|
const pivotBtn = wrapper.findComponent({ ref: 'pivotBtn' })
|
||||||
|
await pivotBtn.trigger('click')
|
||||||
|
|
||||||
|
getOptionsForSaveStub.returns({ test_options: 'pivot_settings' })
|
||||||
|
await chartBtn.trigger('click')
|
||||||
|
expect(
|
||||||
|
wrapper.findComponent({ name: 'chart' }).props('initOptions')
|
||||||
|
).to.eql({ test_options: 'chart_settings' })
|
||||||
|
|
||||||
|
await pivotBtn.trigger('click')
|
||||||
|
expect(
|
||||||
|
wrapper.findComponent({ name: 'pivot' }).props('initOptions')
|
||||||
|
).to.eql({ test_options: 'pivot_settings' })
|
||||||
|
|
||||||
|
const graphBtn = wrapper.findComponent({ ref: 'graphBtn' })
|
||||||
|
await graphBtn.trigger('click')
|
||||||
|
expect(
|
||||||
|
wrapper.findComponent({ name: 'graph' }).props('initOptions')
|
||||||
|
).to.eql({ test_options: 'latest_graph_options' })
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,504 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import { mount, flushPromises } from '@vue/test-utils'
|
||||||
|
import Graph from '@/views/MainView/Workspace/Tabs/Tab/DataView/Graph/index.vue'
|
||||||
|
|
||||||
|
function getPixels(canvas) {
|
||||||
|
const context = canvas.getContext('webgl2')
|
||||||
|
const width = context.canvas.width
|
||||||
|
const height = context.canvas.height
|
||||||
|
|
||||||
|
// Create arrays to hold the pixel data
|
||||||
|
const pixels = new Uint8Array(width * height * 4)
|
||||||
|
|
||||||
|
// Read pixels from canvas
|
||||||
|
context.readPixels(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
context.RGBA,
|
||||||
|
context.UNSIGNED_BYTE,
|
||||||
|
pixels
|
||||||
|
)
|
||||||
|
return pixels.join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Graph.vue', () => {
|
||||||
|
const $store = { state: { isWorkspaceVisible: true } }
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows message when no data', () => {
|
||||||
|
const wrapper = mount(Graph, {
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
GraphEditor: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attachTo: document.body
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.no-data').isVisible()).to.equal(true)
|
||||||
|
expect(wrapper.find('.invalid-data').isVisible()).to.equal(false)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows message when data is invalid', () => {
|
||||||
|
const wrapper = mount(Graph, {
|
||||||
|
props: {
|
||||||
|
dataSources: {
|
||||||
|
column1: ['value1', 'value2']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
GraphEditor: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attachTo: document.body
|
||||||
|
})
|
||||||
|
expect(wrapper.find('.no-data').isVisible()).to.equal(false)
|
||||||
|
expect(wrapper.find('.invalid-data').isVisible()).to.equal(true)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('emits update when graph editor updates', async () => {
|
||||||
|
const wrapper = mount(Graph, {
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
GraphEditor: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wrapper.findComponent({ ref: 'graphEditor' }).vm.$emit('update')
|
||||||
|
expect(wrapper.emitted('update')).to.have.lengthOf(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('the graph resizes when the container resizes', async () => {
|
||||||
|
const wrapper = mount(Graph, {
|
||||||
|
attachTo: document.body,
|
||||||
|
props: {
|
||||||
|
dataSources: {
|
||||||
|
doc: [
|
||||||
|
'{"object_type":0,"node_id":"Gryffindor"}',
|
||||||
|
'{"object_type":0,"node_id":"Hufflepuff"}'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
initOptions: {
|
||||||
|
structure: {
|
||||||
|
nodeId: 'node_id',
|
||||||
|
objectType: 'object_type',
|
||||||
|
edgeSource: 'source',
|
||||||
|
edgeTarget: 'target'
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
nodes: {
|
||||||
|
size: {
|
||||||
|
type: 'constant',
|
||||||
|
value: 10
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: 'constant',
|
||||||
|
value: '#1F77B4'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
source: null,
|
||||||
|
color: '#444444'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edges: {
|
||||||
|
showDirection: true,
|
||||||
|
size: {
|
||||||
|
type: 'constant',
|
||||||
|
value: 2
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: 'constant',
|
||||||
|
value: '#a2b1c6'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
source: null,
|
||||||
|
color: '#a2b1c6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'circular',
|
||||||
|
options: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const container =
|
||||||
|
wrapper.find('.graph-container').wrapperElement.parentElement
|
||||||
|
const canvas = wrapper.find('canvas.sigma-nodes').wrapperElement
|
||||||
|
|
||||||
|
const initialContainerWidth = container.scrollWidth
|
||||||
|
const initialContainerHeight = container.scrollHeight
|
||||||
|
|
||||||
|
const initialCanvasWidth = canvas.scrollWidth
|
||||||
|
const initialCanvasHeight = canvas.scrollHeight
|
||||||
|
|
||||||
|
const newContainerWidth = initialContainerWidth * 2 || 1000
|
||||||
|
const newContainerHeight = initialContainerHeight * 2 || 2000
|
||||||
|
|
||||||
|
container.style.width = `${newContainerWidth}px`
|
||||||
|
container.style.height = `${newContainerHeight}px`
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(canvas.scrollWidth).not.to.equal(initialCanvasWidth)
|
||||||
|
expect(canvas.scrollHeight).not.to.equal(initialCanvasHeight)
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('nodes and edges are rendered', async () => {
|
||||||
|
const wrapper = mount(Graph, {
|
||||||
|
attachTo: document.body,
|
||||||
|
props: {
|
||||||
|
showViewSettings: true,
|
||||||
|
dataSources: {
|
||||||
|
doc: [
|
||||||
|
'{"object_type": 0, "node_id": 1}',
|
||||||
|
'{"object_type": 0, "node_id": 2}',
|
||||||
|
'{"object_type": 1, "source": 1, "target": 2}'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const container =
|
||||||
|
wrapper.find('.graph-container').wrapperElement.parentElement
|
||||||
|
container.style.height = '400px'
|
||||||
|
|
||||||
|
await wrapper
|
||||||
|
.find('.test_object_type_select.dropdown-container .Select__indicator')
|
||||||
|
.wrapperElement.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', { bubbles: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
let options = wrapper.findAll('.Select__menu .Select__option')
|
||||||
|
|
||||||
|
await options[0].trigger('click')
|
||||||
|
|
||||||
|
const nodeCanvasPixelsBefore = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
|
||||||
|
)
|
||||||
|
const edgeCanvasPixelsBefore = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
|
||||||
|
)
|
||||||
|
|
||||||
|
await wrapper
|
||||||
|
.find('.test_node_id_select.dropdown-container .Select__indicator')
|
||||||
|
.wrapperElement.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', { bubbles: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
options = wrapper.findAll('.Select__menu .Select__option')
|
||||||
|
await options[1].trigger('click')
|
||||||
|
|
||||||
|
await wrapper
|
||||||
|
.find('.test_edge_source_select.dropdown-container .Select__indicator')
|
||||||
|
.wrapperElement.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', { bubbles: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
options = wrapper.findAll('.Select__menu .Select__option')
|
||||||
|
await options[2].trigger('click')
|
||||||
|
|
||||||
|
await wrapper
|
||||||
|
.find('.test_edge_target_select.dropdown-container .Select__indicator')
|
||||||
|
.wrapperElement.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', { bubbles: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
options = wrapper.findAll('.Select__menu .Select__option')
|
||||||
|
await options[3].trigger('click')
|
||||||
|
|
||||||
|
const nodeCanvasPixelsAfter = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
|
||||||
|
)
|
||||||
|
const edgeCanvasPixelsAfter = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nodeCanvasPixelsBefore).not.equal(nodeCanvasPixelsAfter)
|
||||||
|
expect(edgeCanvasPixelsBefore).not.equal(edgeCanvasPixelsAfter)
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rerenders when dataSource changes but does not rerender if dataSources is empty', async () => {
|
||||||
|
const wrapper = mount(Graph, {
|
||||||
|
attachTo: document.body,
|
||||||
|
props: {
|
||||||
|
showViewSettings: true,
|
||||||
|
dataSources: {
|
||||||
|
doc: [
|
||||||
|
'{"object_type": 0, "node_id": 1}',
|
||||||
|
'{"object_type": 0, "node_id": 2}',
|
||||||
|
'{"object_type": 1, "source": 1, "target": 2}'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const container =
|
||||||
|
wrapper.find('.graph-container').wrapperElement.parentElement
|
||||||
|
container.style.height = '400px'
|
||||||
|
|
||||||
|
await wrapper
|
||||||
|
.find('.test_object_type_select.dropdown-container .Select__indicator')
|
||||||
|
.wrapperElement.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', { bubbles: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
let options = wrapper.findAll('.Select__menu .Select__option')
|
||||||
|
|
||||||
|
await options[0].trigger('click')
|
||||||
|
|
||||||
|
await wrapper
|
||||||
|
.find('.test_node_id_select.dropdown-container .Select__indicator')
|
||||||
|
.wrapperElement.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', { bubbles: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
options = wrapper.findAll('.Select__menu .Select__option')
|
||||||
|
await options[1].trigger('click')
|
||||||
|
|
||||||
|
await wrapper
|
||||||
|
.find('.test_edge_source_select.dropdown-container .Select__indicator')
|
||||||
|
.wrapperElement.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', { bubbles: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
options = wrapper.findAll('.Select__menu .Select__option')
|
||||||
|
await options[2].trigger('click')
|
||||||
|
|
||||||
|
await wrapper
|
||||||
|
.find('.test_edge_target_select.dropdown-container .Select__indicator')
|
||||||
|
.wrapperElement.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', { bubbles: true })
|
||||||
|
)
|
||||||
|
|
||||||
|
options = wrapper.findAll('.Select__menu .Select__option')
|
||||||
|
await options[3].trigger('click')
|
||||||
|
|
||||||
|
const nodeCanvasPixelsBefore = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
|
||||||
|
)
|
||||||
|
const edgeCanvasPixelsBefore = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
|
||||||
|
)
|
||||||
|
await wrapper.setProps({
|
||||||
|
dataSources: {
|
||||||
|
doc: [
|
||||||
|
'{"object_type": 0, "node_id": 1}',
|
||||||
|
'{"object_type": 0, "node_id": 2}',
|
||||||
|
'{"object_type": 0, "node_id": 3}',
|
||||||
|
'{"object_type": 1, "source": 1, "target": 2}',
|
||||||
|
'{"object_type": 1, "source": 1, "target": 3}'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodeCanvasPixelsAfter = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
|
||||||
|
)
|
||||||
|
const edgeCanvasPixelsAfter = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nodeCanvasPixelsBefore).not.equal(nodeCanvasPixelsAfter)
|
||||||
|
expect(edgeCanvasPixelsBefore).not.equal(edgeCanvasPixelsAfter)
|
||||||
|
|
||||||
|
await wrapper.setProps({
|
||||||
|
dataSources: null
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodeCanvasPixelsAfterEmtyData = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-nodes').wrapperElement
|
||||||
|
)
|
||||||
|
const edgeCanvasPixelsAfterEmtyData = getPixels(
|
||||||
|
wrapper.find('.test_graph_output canvas.sigma-edges').wrapperElement
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nodeCanvasPixelsAfterEmtyData).equal(nodeCanvasPixelsAfter)
|
||||||
|
expect(edgeCanvasPixelsAfterEmtyData).equal(edgeCanvasPixelsAfter)
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('saveAsPng', async () => {
|
||||||
|
const wrapper = mount(Graph, {
|
||||||
|
props: {
|
||||||
|
dataSources: {
|
||||||
|
doc: [
|
||||||
|
'{"object_type":0,"node_id":"Gryffindor"}',
|
||||||
|
'{"object_type":0,"node_id":"Hufflepuff"}'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
initOptions: {
|
||||||
|
structure: {
|
||||||
|
nodeId: 'node_id',
|
||||||
|
objectType: 'object_type',
|
||||||
|
edgeSource: 'source',
|
||||||
|
edgeTarget: 'target'
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
nodes: {
|
||||||
|
size: {
|
||||||
|
type: 'constant',
|
||||||
|
value: 10
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: 'constant',
|
||||||
|
value: '#1F77B4'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
source: null,
|
||||||
|
color: '#444444'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edges: {
|
||||||
|
showDirection: true,
|
||||||
|
size: {
|
||||||
|
type: 'constant',
|
||||||
|
value: 2
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: 'constant',
|
||||||
|
value: '#a2b1c6'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
source: null,
|
||||||
|
color: '#a2b1c6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'circular',
|
||||||
|
options: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
sinon.stub(wrapper.vm.$refs.graphEditor, 'saveAsPng')
|
||||||
|
await wrapper.vm.saveAsPng()
|
||||||
|
expect(wrapper.emitted().loadingImageCompleted.length).to.equal(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides and shows controls depending on showViewSettings and resizes the graph', async () => {
|
||||||
|
const wrapper = mount(Graph, {
|
||||||
|
attachTo: document.body,
|
||||||
|
props: {
|
||||||
|
dataSources: {
|
||||||
|
doc: [
|
||||||
|
'{"object_type":0,"node_id":"Gryffindor"}',
|
||||||
|
'{"object_type":0,"node_id":"Hufflepuff"}'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
initOptions: {
|
||||||
|
structure: {
|
||||||
|
nodeId: 'node_id',
|
||||||
|
objectType: 'object_type',
|
||||||
|
edgeSource: 'source',
|
||||||
|
edgeTarget: 'target'
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
nodes: {
|
||||||
|
size: {
|
||||||
|
type: 'constant',
|
||||||
|
value: 10
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: 'constant',
|
||||||
|
value: '#1F77B4'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
source: null,
|
||||||
|
color: '#444444'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edges: {
|
||||||
|
showDirection: true,
|
||||||
|
size: {
|
||||||
|
type: 'constant',
|
||||||
|
value: 2
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: 'constant',
|
||||||
|
value: '#a2b1c6'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
source: null,
|
||||||
|
color: '#a2b1c6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: 'circular',
|
||||||
|
options: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
global: {
|
||||||
|
mocks: { $store },
|
||||||
|
provide: {
|
||||||
|
tabLayout: { dataView: 'above' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const canvas = wrapper.find('canvas.sigma-nodes').wrapperElement
|
||||||
|
|
||||||
|
const initialPlotWidth = canvas.scrollWidth
|
||||||
|
const initialPlotHeight = canvas.scrollHeight
|
||||||
|
|
||||||
|
expect(
|
||||||
|
wrapper.find('.plotly_editor .editor_controls').isVisible()
|
||||||
|
).to.equal(false)
|
||||||
|
|
||||||
|
await wrapper.setProps({ showViewSettings: true })
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(canvas.scrollWidth).not.to.equal(initialPlotWidth)
|
||||||
|
expect(canvas.scrollHeight).to.equal(initialPlotHeight)
|
||||||
|
expect(
|
||||||
|
wrapper.find('.plotly_editor .editor_controls').isVisible()
|
||||||
|
).to.equal(true)
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { mount, flushPromises } from '@vue/test-utils'
|
import { mount, flushPromises } from '@vue/test-utils'
|
||||||
import Pivot from '@/views/MainView/Workspace/Tabs/Tab/DataView/Pivot'
|
import Pivot from '@/views/MainView/Workspace/Tabs/Tab/DataView/Pivot/index.vue'
|
||||||
import chartHelper from '@/lib/chartHelper'
|
import chartHelper from '@/lib/chartHelper'
|
||||||
import fIo from '@/lib/utils/fileIo'
|
import fIo from '@/lib/utils/fileIo'
|
||||||
import $ from 'jquery'
|
import $ from 'jquery'
|
||||||
@@ -586,4 +586,48 @@ describe('Pivot.vue', () => {
|
|||||||
|
|
||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('hides or shows controlls depending on showViewSettings and resizes standart chart', async () => {
|
||||||
|
const wrapper = mount(Pivot, {
|
||||||
|
global: {
|
||||||
|
mocks: { $store: { state: { isWorkspaceVisible: true } } }
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
dataSources: {
|
||||||
|
item: ['foo', 'bar', 'bar', 'bar'],
|
||||||
|
year: [2021, 2021, 2020, 2020]
|
||||||
|
},
|
||||||
|
initOptions: {
|
||||||
|
rows: ['item'],
|
||||||
|
cols: ['year'],
|
||||||
|
colOrder: 'key_a_to_z',
|
||||||
|
rowOrder: 'key_a_to_z',
|
||||||
|
aggregatorName: 'Count',
|
||||||
|
vals: [],
|
||||||
|
renderer: $.pivotUtilities.renderers['Bar Chart'],
|
||||||
|
rendererName: 'Bar Chart'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attachTo: container
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.find('.pivot-ui').isVisible()).to.equal(false)
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
const plot = wrapper.find('.svg-container').wrapperElement
|
||||||
|
const initialPlotHeight = plot.scrollHeight
|
||||||
|
|
||||||
|
await wrapper.setProps({ showViewSettings: true })
|
||||||
|
expect(wrapper.find('.pivot-ui').isVisible()).to.equal(true)
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
const plotAfterResize = wrapper.find('.svg-container').wrapperElement
|
||||||
|
|
||||||
|
expect(plotAfterResize.scrollWidth.scrollHeight).not.to.equal(
|
||||||
|
initialPlotHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user