Commit 9f2ba86e authored by Sascha Herzinger's avatar Sascha Herzinger
Browse files

Fixed many issues around the life cycle of the vue components and introduced a new tooltip library.

parent f04ae42b
......@@ -1486,11 +1486,6 @@
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.5.tgz",
"integrity": "sha1-smbUdscbDSaeesXzUrQQo7b+bvA="
},
"d3-tip": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/d3-tip/-/d3-tip-0.7.1.tgz",
"integrity": "sha1-eMv1VPZ7cgpw47DxkeFM/+aM3Xk="
},
"d3-transition": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.1.0.tgz",
......@@ -3040,9 +3035,9 @@
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
},
"gsap": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-1.20.1.tgz",
"integrity": "sha512-G8o785BTz+T+ZuWETM0kz2ZfPjBa47y+VkpZxk1vZU1mjruh4Za7jL4ypXxTKObGd7Q+/s2dbA8e/pe7tSmOIw=="
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/gsap/-/gsap-1.20.2.tgz",
"integrity": "sha512-W9xoPNkFIFmpeg6d0G6vt+ph9Mog7m6mRsUSqTHQqcuc/ujEreomH6gEeMnoM8du8Y1bqwCWgPbgKX+yXvFuDg=="
},
"handle-thing": {
"version": "1.2.5",
......@@ -3586,12 +3581,6 @@
"integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=",
"dev": true
},
"js2xmlparser": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-1.0.0.tgz",
"integrity": "sha1-WhcPLo1kds5FQF4EgjJCUTeC/jA=",
"dev": true
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
......@@ -3600,21 +3589,33 @@
"optional": true
},
"jsdoc": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.4.3.tgz",
"integrity": "sha1-5XQNYUXGgfZnnmwXeDqI292XzNM=",
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.0.tgz",
"integrity": "sha512-AkXuBHsvZ7D81lV2BRiZhvcWPAaNNfWssEoDqKte07mhE+/6FUgeTQxMhbQPx1gaeWNtd4K60fo/ecD9FPIpjA==",
"dev": true,
"dependencies": {
"acorn": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
"integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=",
"babylon": {
"version": "7.0.0-beta.16",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.16.tgz",
"integrity": "sha1-RIzu3uwKXvVrYoEuNVa/NsW7l4E=",
"dev": true
},
"espree": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/espree/-/espree-3.1.7.tgz",
"integrity": "sha1-/V3ux2qXpRIKnNOnyxF3oJI7EdI=",
"bluebird": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz",
"integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=",
"dev": true
},
"js2xmlparser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz",
"integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=",
"dev": true
},
"klaw": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz",
"integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=",
"dev": true
}
}
......@@ -3732,9 +3733,9 @@
"dev": true
},
"karma-webpack": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-2.0.3.tgz",
"integrity": "sha1-Oc6/XKJYATmyf5rmm3iBa5yC+uY=",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-2.0.4.tgz",
"integrity": "sha1-Pi1PSLqUqHjhxmu44a5hKJh6F1s=",
"dev": true,
"dependencies": {
"loader-utils": {
......@@ -4486,6 +4487,11 @@
"integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=",
"dev": true
},
"popper.js": {
"version": "1.10.8",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.10.8.tgz",
"integrity": "sha512-2LbS9yubeRwcRhThjGGcuKUHS5oGDxDRdYa+tBVjvwKBh7BBaNyALysYIJogCqTfQzOVtng8Z2TvaaSuYZtW8w=="
},
"portfinder": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz",
......@@ -5803,6 +5809,11 @@
"integrity": "sha1-q0iDz1l9zVCvIRNJoA+8pWrIa4Y=",
"dev": true
},
"tippy.js": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-1.1.0.tgz",
"integrity": "sha512-uVW+WCxWlwc7uqykreNBpFyVOPDOnk3bVR36+sjThK20aq2LQLPJ1PYA5MBWIoFFy5CNcVVwgV+Hh0wrgoILyg=="
},
"tmp": {
"version": "0.0.31",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz",
......@@ -6233,9 +6244,9 @@
"dev": true
},
"webpack-dev-server": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.5.0.tgz",
"integrity": "sha1-TTanKLA7iyr6SO0wJCiEfOooQK0=",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.5.1.tgz",
"integrity": "sha1-oC5yaoe7YD211xq7fW0mSb8Qx2k=",
"dev": true,
"dependencies": {
"camelcase": {
......@@ -6382,6 +6393,12 @@
"integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=",
"dev": true
},
"xmlcreate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz",
"integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=",
"dev": true
},
"xmlhttprequest": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
......
@import '~tippy.js/dist/tippy.css'
@font-face
font-family: 'Roboto'
font-style: normal
font-weight: 400
font-size: 14px
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/oMMgfZMQthOryQo9n22dcuvvDin1pK8aKteLpeZ5c0A.woff2) format('woff2')
\ No newline at end of file
src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v15/oMMgfZMQthOryQo9n22dcuvvDin1pK8aKteLpeZ5c0A.woff2) format('woff2')
.fjs-d3-tip
z-index: 9999
line-height: 1
padding: 12px
background: rgba(0, 0, 0, 0.8)
color: #fff
border-radius: 2px
pointer-events: none
font-size: 14px
font-family: Roboto, sans-serif
/* Creates a small triangle extender for the tooltip */
.fjs-d3-tip:after
box-sizing: border-box
display: inline
font-size: 16px
width: 100%
line-height: 0.8
color: rgba(0, 0, 0, 0.8)
position: absolute
pointer-events: none
/* Northward tooltips */
.fjs-d3-tip.n:after
content: "\25BC"
margin: -1px 0 0 0
top: 100%
left: 0
text-align: center
/* Eastward tooltips */
.fjs-d3-tip.e:after
content: "\25C0"
margin: -4px 0 0 0
top: 50%
left: -12px
/* Southward tooltips */
.fjs-d3-tip.s:after
content: "\25B2"
margin: 0 0 1px 0
top: -8px
left: 0
text-align: center
/* Westward tooltips */
.fjs-d3-tip.w:after
content: "\25B6"
margin: -4px 0 0 -1px
top: 50%
left: 100%
\ No newline at end of file
......@@ -17,7 +17,7 @@ export default class {
this._axios = axios.create({
baseURL: fractalisBaseURL,
timeout: 10000,
timeout: 30000,
withCredentials: true
})
}
......
......@@ -20,22 +20,48 @@
<svg :width="width"
:height="height">
<g :transform="`translate(${margin.left}, ${margin.top})`">
<g class="fjs-bplt-axis fjs-x-axis" :transform="`translate(0, ${padded.height})`"></g>
<g class="fjs-bplt-axis fjs-y-axis"></g>
<g v-for="label in Object.keys(results.statistics)" :transform="`translate(${scales.x(label)}, 0)`">
<line class="fjs-whisker"
<g class="fjs-boxplot-axis fjs-x-axis" :transform="`translate(0, ${padded.height})`"></g>
<g class="fjs-boxplot-axis fjs-y-axis"></g>
<g class="fjs-box"
:transform="`translate(${scales.x(label)}, 0)`"
:data-label="label"
@mouseenter="showTooltip(label)"
@mouseleave="hideTooltip(label)"
v-for="label in Object.keys(results.statistics)" >
<line class="fjs-upper-whisker"
:title="results.statistics[label].u_wsk"
:x1="- boxplotWidth / 6"
:y1="scales.y(stat)"
:y1="scales.y(results.statistics[label].u_wsk)"
:x2="boxplotWidth / 6"
:y2="scales.y(stat)"
v-for="stat in [results.statistics[label].l_wsk, results.statistics[label].u_wsk]">
:y2="scales.y(results.statistics[label].u_wsk)">
</line>
<line class="fjs-lower-whisker"
:title="results.statistics[label].l_wsk"
:x1="- boxplotWidth / 6"
:y1="scales.y(results.statistics[label].l_wsk)"
:x2="boxplotWidth / 6"
:y2="scales.y(results.statistics[label].l_wsk)">
</line>
<line class="fjs-quartile"
<line class="fjs-upper-quartile"
:title="results.statistics[label].u_qrt"
:x1="- boxplotWidth / 2"
:y1="scales.y(stat)"
:y1="scales.y(results.statistics[label].u_qrt)"
:x2="boxplotWidth / 2"
:y2="scales.y(stat)"
v-for="stat in [results.statistics[label].l_qrt, results.statistics[label].u_qrt]">
:y2="scales.y(results.statistics[label].u_qrt)">
</line>
<line class="fjs-lower-quartile"
:title="results.statistics[label].l_qrt"
:x1="- boxplotWidth / 2"
:y1="scales.y(results.statistics[label].l_qrt)"
:x2="boxplotWidth / 2"
:y2="scales.y(results.statistics[label].l_qrt)">
</line>
<line class="fjs-median"
:title="results.statistics[label].median"
:x1="- boxplotWidth / 2"
:y1="scales.y(results.statistics[label].median)"
:x2="boxplotWidth / 2"
:y2="scales.y(results.statistics[label].median)">
</line>
<line class="fjs-antenna"
:x1="0"
......@@ -55,13 +81,6 @@
:width="boxplotWidth"
:height="scales.y(results.statistics[label].l_qrt) - scales.y(results.statistics[label].median) - 1">
</rect>
<!--<circle class="fjs-point"-->
<!--:cx="scales.x(label)"-->
<!--:cy="scales.y(row[label])"-->
<!--r="4"-->
<!--v-if="row[label] !== null"-->
<!--v-for="row in results.data">-->
<!--</circle>-->
</g>
</g>
</svg>
......@@ -74,11 +93,10 @@
import store from '../../store/store'
import requestHandling from '../methods/run-analysis'
import * as d3 from 'd3'
import { TweenLite } from 'gsap'
import svgtooltip from '../directives/v-svgtooltip'
import TaskView from '../components/TaskView.vue'
import deepFreeze from 'deep-freeze-strict'
import utils from '../../services/utils'
import tippy from 'tippy.js'
export default {
name: 'boxplot',
data () {
......@@ -87,6 +105,7 @@
height: 0,
numData: [],
catData: [],
tooltips: {},
results: {
data: [],
statistics: {}
......@@ -96,8 +115,8 @@
computed: {
args () {
return {
variables: this.numData.map(d => `$${d}$`),
categories: this.catData.map(d => `$${d}$`),
variables: this.numData,
categories: this.catData,
id_filter: [],
subsets: store.getters.subsets
}
......@@ -146,6 +165,9 @@
return { x, y }
}
},
// IMPORTANT: If the code within the watchers does interact with the DOM the code should be wrapped into a $nextTick
// statement. This helps with the integration into the Vue component lifecycle. E.g.: an animation can't be
// applied to an element that does not exist yet.
watch: {
'args': {
handler: function (newArgs, oldArgs) {
......@@ -159,16 +181,56 @@
},
'axis': {
handler: function (newAxis) {
d3.select(`.fjs-vm-uid-${this._uid} .fjs-x-axis`)
.call(newAxis.x)
.selectAll('text')
.attr('transform', 'rotate(20)')
d3.select(`.fjs-vm-uid-${this._uid} .fjs-y-axis`)
.call(newAxis.y)
this.$nextTick(() => {
d3.select(`.fjs-vm-uid-${this._uid} .fjs-x-axis`)
.call(newAxis.x)
.selectAll('text')
.attr('transform', 'rotate(20)')
d3.select(`.fjs-vm-uid-${this._uid} .fjs-y-axis`)
.call(newAxis.y)
})
}
},
'results': {
handler: function () {
// Vue reuses elements, so when additional boxplots are added the tooltips might reference the
// the wrong boxes. This resets all tooltips when new back end data come in.
Object.keys(this.tooltips).forEach(label => this.tooltips[label].forEach(d => d.tip.destroyAll()))
this.tooltips = {}
}
}
},
methods: {
showTooltip (label) {
if (typeof this.tooltips[label] !== 'undefined') {
this.tooltips[label].forEach(d => d.tip.show(d.tip.getPopperElement(d.el)))
return
}
const defaultOptions = {
performance: true,
theme: 'light',
arrow: true,
trigger: 'manual'
}
const upperWhisker = document.querySelector(`.fjs-vm-uid-${this._uid} .fjs-box[data-label="${label}"] .fjs-upper-whisker`)
const lowerWhisker = document.querySelector(`.fjs-vm-uid-${this._uid} .fjs-box[data-label="${label}"] .fjs-lower-whisker`)
const upperQuartile = document.querySelector(`.fjs-vm-uid-${this._uid} .fjs-box[data-label="${label}"] .fjs-upper-quartile`)
const lowerQuartile = document.querySelector(`.fjs-vm-uid-${this._uid} .fjs-box[data-label="${label}"] .fjs-lower-quartile`)
const median = document.querySelector(`.fjs-vm-uid-${this._uid} .fjs-box[data-label="${label}"] .fjs-median`)
this.tooltips[label] = [
{ tip: tippy(upperWhisker, Object.assign({position: 'right'}, defaultOptions)), el: upperWhisker },
{ tip: tippy(lowerWhisker, Object.assign({position: 'right'}, defaultOptions)), el: lowerWhisker },
{ tip: tippy(upperQuartile, Object.assign({position: 'left'}, defaultOptions)), el: upperQuartile },
{ tip: tippy(lowerQuartile, Object.assign({position: 'left'}, defaultOptions)), el: lowerQuartile },
{ tip: tippy(median, Object.assign({position: 'right'}, defaultOptions)), el: median }
]
this.tooltips[label].forEach(d => {
d.tip.show(d.tip.getPopperElement(d.el))
})
},
hideTooltip (label) {
this.tooltips[label].forEach(d => d.tip.hide(d.tip.getPopperElement(d.el)))
},
update_numData (ids) {
this.numData = ids
},
......@@ -190,6 +252,7 @@
results.data = Object.keys(data).map(key => data[key])
deepFreeze(results) // massively improve performance by telling Vue that the objects properties won't change
this.results = results
this.handleResize()
})
.catch(error => console.error(error))
}
......@@ -199,8 +262,7 @@
TaskView
},
mixins: [
requestHandling,
svgtooltip
requestHandling
],
mounted () {
window.addEventListener('resize', this.handleResize)
......@@ -232,28 +294,31 @@
.fjs-vis-container
flex: 1
display: flex
.fjs-tooltip
position: absolute
svg
flex: 1
.fjs-whisker, .fjs-quartile, .fjs-antenna
shape-rendering: crispEdges
stroke: black
stroke-width: 2px
.fjs-below-median-box
stroke: none
fill: rgb(205, 232, 254)
shape-rendering: crispEdges
.fjs-above-median-box
stroke: none
fill: rgb(180, 221, 253)
shape-rendering: crispEdges
.fjs-box
.fjs-median, .fjs-lower-quartile, .fjs-upper-quartile
opacity: 1
.fjs-lower-whisker, .fjs-upper-whisker, .fjs-antenna
shape-rendering: crispEdges
stroke: black
stroke-width: 2px
.fjs-below-median-box
stroke: none
fill: rgb(205, 232, 254)
shape-rendering: crispEdges
.fjs-above-median-box
stroke: none
fill: rgb(180, 221, 253)
shape-rendering: crispEdges
</style>
<!--CSS for dynamically created components-->
<style lang="sass">
@import './src/assets/svgtooltip.sass'
.fjs-bplt-axis
.fjs-boxplot-axis
shape-rendering: crispEdges
stroke-width: 2px
.tick
......
......@@ -27,8 +27,7 @@
</div>
<div class="fjs-vis-container">
<svg :width="width"
:height="height">
<svg :height="height" :width="width">
<g :transform="`translate(${margin.left}, ${margin.top})`">
<g class="fjs-corr-axis fjs-x-axis-1" :transform="`translate(0, ${padded.height})`"></g>
<g class="fjs-corr-axis fjs-x-axis-2"></g>
......@@ -52,17 +51,17 @@
:cx="scales.x(point.x)"
:cy="scales.y(point.y)"
r="4"
v-svgtooltip="point.tooltip"
:fill="annotationColors[annotations.indexOf(point.annotation) % annotationColors.length]"
:stroke="subsetColors[point.subset]"
:title="point.tooltip"
v-for="point in shownPoints.all">
</circle>
<line class="fjs-lin-reg-line"
:title="regLine.tooltip"
:x1="tweened.regLine.x1"
:x2="tweened.regLine.x2"
:y1="tweened.regLine.y1"
:y2="tweened.regLine.y2"
v-svgtooltip="regLine.tooltip">
:y2="tweened.regLine.y2">
</line>
<polyline class="fjs-histogram-polyline fjs-bottom" points=""></polyline>
<polyline class="fjs-histogram-polyline fjs-left" points=""></polyline>
......@@ -114,16 +113,15 @@
</div>
</template>
<script>
import DataBox from '../components/DataBox.vue'
import store from '../../store/store'
import requestHandling from '../methods/run-analysis'
import * as d3 from 'd3'
import { TweenLite } from 'gsap'
import svgtooltip from '../directives/v-svgtooltip'
import TaskView from '../components/TaskView.vue'
import deepFreeze from 'deep-freeze-strict'
import tippy from 'tippy.js'
export default {
name: 'correlation-analysis',
data () {
......@@ -139,7 +137,6 @@
method: 'pearson'
},
shownResults: { // initially computed
init: true, // will disappear after being initially set
coef: 0,
p_value: 0,
slope: 0,
......@@ -147,16 +144,9 @@
method: '',
x_label: '',
y_label: '',
get data () {
return {
id: [],
[this.x_label]: [],
[this.y_label]: []
}
}
data: []
},
tmpResults: { // on-the-fly computed
init: true, // will disappear after being initially set
coef: 0,
p_value: 0,
slope: 0,
......@@ -164,13 +154,7 @@
method: '',
x_label: '',
y_label: '',
get data () {
return {
id: [],
[this.x_label]: [],
[this.y_label]: []
}
}
data: []
},
selectedPoints: [],
tweened: {
......@@ -187,12 +171,12 @@
},
args () {
return {
x: `$${this.xyData[0]}$`,
y: `$${this.xyData[1]}$`,
x: this.xyData[0],
y: this.xyData[1],
id_filter: this.selectedPoints.map(d => d.id),
method: this.params.method,
subsets: store.getters.subsets,
annotations: this.annotationData.map(d => `$${d}$`)
annotations: this.annotationData
}
},
margin () {
......@@ -216,30 +200,30 @@
const ids = []
const subsets = []
const annotations = []
let all = []
if (!this.shownResults.init) {
all = this.shownResults.data.map(d => {
const x = d[this.shownResults.x_label]
const y = d[this.shownResults.y_label]
const id = d.id
const subset = d.subset
const annotation = d.annotation
const tooltip = {
[this.shownResults.x_label]: x,
[this.shownResults.y_label]: y,
subset
}