Browse Source

Retrieval

- Added Babel for ES2020
- Added Retrieval Viz to example
- Worked on Retrieval components
master
Pierre Vanhulst 2 years ago
parent
commit
99baa184f3
  1. 5
      babel.config.js
  2. 1198
      dist/colvis-client.common.js
  3. 2
      dist/colvis-client.common.js.map
  4. 2
      dist/colvis-client.css
  5. 1198
      dist/colvis-client.umd.js
  6. 2
      dist/colvis-client.umd.js.map
  7. 2
      dist/colvis-client.umd.min.js
  8. 2
      dist/colvis-client.umd.min.js.map
  9. 51
      example/src/App.vue
  10. 6
      example/src/assets/prior-annotations.json
  11. 12301
      package-lock.json
  12. 2
      package.json
  13. 29
      src/assets/styles/generic/_animations.sass
  14. 5
      src/assets/styles/generic/_variables.sass
  15. 2
      src/components/Input/Box.vue
  16. 2
      src/components/Input/Free.vue
  17. 7
      src/components/Input/Main.vue
  18. 2
      src/components/Input/Overlay.vue
  19. 2
      src/components/Input/Selector.vue
  20. 47
      src/components/Retrieval/CCard.vue
  21. 103
      src/components/Retrieval/CText.vue
  22. 103
      src/components/Retrieval/List.vue
  23. 209
      src/components/Retrieval/Viewer.vue
  24. 105
      src/components/Retrieval/Viz.vue
  25. 19
      src/components/_generic/CAnnotation.vue
  26. 17
      src/components/_generic/CChip.vue
  27. 16
      src/components/_generic/CLoading.vue
  28. 32
      src/lib.js
  29. 2
      src/models/Annotation/DataUnit.js
  30. 6
      src/models/Annotation/index.js
  31. 36
      src/models/RawAnnotation.js

5
babel.config.js

@ -1,5 +1,4 @@
module.exports = {
presets: [
'@vue/app'
]
presets: [ '@vue/app' ],
plugins: [ '@babel/plugin-proposal-optional-chaining', '@babel/plugin-syntax-nullish-coalescing-operator' ]
}

1198
dist/colvis-client.common.js vendored

File diff suppressed because one or more lines are too long

2
dist/colvis-client.common.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/colvis-client.css vendored

File diff suppressed because one or more lines are too long

1198
dist/colvis-client.umd.js vendored

File diff suppressed because one or more lines are too long

2
dist/colvis-client.umd.js.map vendored

File diff suppressed because one or more lines are too long

2
dist/colvis-client.umd.min.js vendored

File diff suppressed because one or more lines are too long

2
dist/colvis-client.umd.min.js.map vendored

File diff suppressed because one or more lines are too long

51
example/src/App.vue

@ -7,6 +7,7 @@
</v-toolbar-title>
<v-spacer/>
<v-btn @click="annotating = !annotating">Annotate</v-btn>
<v-btn @click="viewing = !viewing">Viewing</v-btn>
<v-switch v-model="loading" label="loading" />
<v-spacer/>
</v-app-bar>
@ -16,10 +17,25 @@
<v-flex md6>
<div id="vis"></div>
<ColInputMain
:annotating="annotating" :previousTags="['bidule', 'truc']" :previousUnits="previousUnits" :loading="loading"
:annotating="annotating" :previousTags="['bidule', 'truc']" :state="state" :previousUnits="previousUnits" :loading="loading"
@close="annotating = false" @submittingAnnotation="alertAnnotation"
/>
<ColRetrievalViz :annotations="annotations" />
<ColRetrievalViz :viewing="viewing" :annotations="annotations" @updateState="updateState">
<template v-slot:card>
<v-toolbar dense flat>
<v-avatar class="primary">U</v-avatar>
<v-spacer/>
<v-btn icon>
<v-icon>mdi-thumb-up</v-icon>
</v-btn>
</v-toolbar>
</template>
</ColRetrievalViz>
</v-flex>
<v-flex md6>
<v-switch v-model="dummyState1" label="Dummy state 1"/>
<v-switch v-model="dummyState2" label="Dummy state 2"/>
<v-switch v-model="dummyState3" label="Dummy state 3"/>
</v-flex>
</v-layout>
</v-content>
@ -36,15 +52,30 @@ export default {
return {
init: false,
annotating: false,
viewing: false,
annotations: annotations,
loading: false,
previousUnits: [{"role":"subject", "title": "two random cars", "id":-229606214,"natureId":"@/n/car","aggregated":true,"units":[{"role":"subject","id":"buick skylark 320-15-1970-01-01","natureId":"@/n/car","aggregated":false,"units":[],"title":null},{"role":"subject","id":"ford galaxie 500-15-1970-01-01","natureId":"@/n/car","aggregated":false,"units":[]}]}]
previousUnits: [{"role":"subject", "title": "two random cars", "id":-229606214,"natureId":"@/n/car","aggregated":true,"units":[{"role":"subject","id":"buick skylark 320-15-1970-01-01","natureId":"@/n/car","aggregated":false,"units":[],"title":null},{"role":"subject","id":"ford galaxie 500-15-1970-01-01","natureId":"@/n/car","aggregated":false,"units":[]}]}],
dummyState1: false,
dummyState2: false,
dummyState3: true
}
},
methods: {
alertAnnotation(annotation) {
this.annotations.push(annotation);
},
updateState(state) {
console.log('updating state')
Object.assign(this, state)
}
},
computed: {
state () {
const { dummyState1, dummyState2, dummyState3 } = this;
return { dummyState1, dummyState2, dummyState3 };
}
},
@ -60,11 +91,15 @@ export default {
const characters = Array.from(new Set(data.map(d => d.character)))
const sessions = Array.from(new Set(data.map(d => d.id)))
const scenarios = Array.from(new Set(data.map(d => d.scenario)))
this.$colvis.initialize({ specs: colvisSpecs, staticData: {
'@/n/character': characters,
'@/n/session': sessions,
'@/n/scenario': scenarios
} });
this.$colvis.initialize({
specs: colvisSpecs,
staticData: {
'@/n/character': characters,
'@/n/session': sessions,
'@/n/scenario': scenarios
},
vizState: this.state
});
console.log(this.$colvis.getEntities())
});

6
example/src/assets/prior-annotations.json

File diff suppressed because one or more lines are too long

12301
package-lock.json generated

File diff suppressed because it is too large Load Diff

2
package.json

@ -17,6 +17,8 @@
"test:unit": "vue-cli-service test:unit"
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@types/chai": "^4.1.0",
"@types/mocha": "^5.2.4",
"@vue/cli-plugin-babel": "^3.9.0",

29
src/assets/styles/generic/_animations.sass

@ -1,7 +1,36 @@
@import './_variables'
@keyframes col-rotate
0%
stroke-dasharray: 5
stroke-width: 2
50%
stroke-dasharray: 15
stroke-width: 6
100%
stroke-dasharray: 5
stroke-width: 2
@keyframes col-giggle
0%
box-shadow: $elevation
transform: translateX(-.5rem)
50%
box-shadow: $elevation2
transform: translateX(.5rem)
100%
box-shadow: $elevation
transform: translateX(-.5rem)
@keyframes col-loading
0%
width: 10%
left: 0
20%
width: 70%
left: 20%
100%
width: 10%
left: 100%

5
src/assets/styles/generic/_variables.sass

@ -1,9 +1,9 @@
@use "sass:map"
$colors: ( "blue": #009bf9, "red": #ff009b, "red--error": #ff5252, "yellow": #ffb300, "green": #74b153 )
$colors: ( "blue": #009bf9, "red": #ff009b, "red--error": #ff5252, "yellow": #ffb300, "green": #74b153, "grey": #CFCFCF )
$variations: ("light": (function: lighten, parameters: 15%), "dark": (function: darken, parameters: 15%), "darker": (function: darken, parameters: 30%))
$selectors: ( "blue": #009bf9, "red": #ff009b )
$selectors: ( "blue": #009bf9, "red": #ff009b, "yellow": #ffb300 )
$texts: ( "dark": rgb(255,255,255), "light": rgba(0,0,0) )
$backgrounds: ( "dark": rgb(0,0,0), "light": rgb(225,225,225), "dark--toolbar": rgb(50,50,50), "light--toolbar": rgb(200, 200, 200) )
@ -54,6 +54,7 @@ $backgrounds: ( "dark": rgb(0,0,0), "light": rgb(225,225,225), "dark--toolbar":
@return colvisColor($color, $variation)
$elevation: 1px 2px 2px -1px rgba(0, 0, 0, .2), -1px 2px 2px -1px rgba(0, 0, 0, .2)
$elevation2: 2px 3px 3px -1px rgba(0, 0, 0, .2), -2px 3px 3px -1px rgba(0, 0, 0, .2)
$background--dark: rgb(50,50,50)

2
src/components/Input/Box.vue

@ -120,7 +120,7 @@ export default {
previousUnits: { type: Array, default: () => []},
/** The list of existing tags in the backend's database */
previousTags: { type: Array, default: () => [] },
selectionMethods: { type: Array, default: () => [] }
selectionMethods: { type: Array, default: () => [] },
},
data () {
return initialState();

2
src/components/Input/Free.vue

@ -27,7 +27,7 @@ export default {
components: { CMessage, CCombobox },
props: {
/** The list of existing tags in the backend's database */
previousTags: { type: Array, default: () => [] }
previousTags: { type: Array, default: () => [] },
},
data () {
return new initialState();

7
src/components/Input/Main.vue

@ -54,6 +54,8 @@ export default {
previousTags: { type: Array, default: () => [] },
/** Whether the parent is loading data */
loading: { type: Boolean, default: false },
/** The state of the visualization */
state: { type: Object },
},
data () {
return initialState();
@ -110,6 +112,11 @@ export default {
this.$emit('submittingAnnotation', a);
Object.assign(this.$data, initialState());
}
},
watch: {
state () {
this.$colvis.setVizState(this.state);
}
}
}
</script>

2
src/components/Input/Overlay.vue

@ -23,7 +23,7 @@
v-if="!free"
:previousTags="previousTags" :previousUnits="previousUnits"
:selectedComplements="selectedComplements" :selectedSubjects="selectedSubjects"
:selectionMethods="selectionMethods"
:selectionMethods="selectionMethods"
@stepChanged="$emit('stepChanged', $event)" @boxSubjectSelection="$emit('boxSubjectSelection', $event)"
@boxComplementSelection="$emit('boxComplementSelection', $event)"
@submittingAnnotation="$emit('submittingAnnotation', $event)"

2
src/components/Input/Selector.vue

@ -275,7 +275,7 @@ export default {
},
mounted () {
this.getPosition();
addEventListener('resize', this.getPosition);
this.eventListeners.push({ resize: this.getPosition });
addEventListener('keydown', this.changeMode);

47
src/components/Retrieval/CCard.vue

@ -0,0 +1,47 @@
<template lang="pug">
div.card(:class="{ active: annotation === activeAnnotation }")
div.annotation(@click="$emit('selectAnnotation', annotation)"): c-text(:annotation="annotation" @highlight="$emit('highlight', $event)")
small.date {{ (new Date(annotation.meta.timestamp)).toLocaleString() }}
slot(name="extension")
</template>
<script>
import CText from '@/components/Retrieval/CText.vue';
export default {
components: { CText },
props: {
annotation: {
type: Object,
required: true
},
activeAnnotation: {
type: Object
}
}
}
</script>
<style lang="sass" scoped>
@import '../../assets/styles/generic/_animations'
@import '../../assets/styles/generic/_variables'
.card
width: 20em
background-color: rgba(255, 255, 255, .5)
backdrop-filter: blur(2px)
box-shadow: $elevation
&:hover
box-shadow: $elevation2
&.active
animation: col-giggle ease-out 1.5s infinite
.date
text-align: center
.annotation
&:hover
cursor: pointer
</style>

103
src/components/Retrieval/CText.vue

@ -0,0 +1,103 @@
<template lang="pug">
div.text
span.text__unit(@click.stop="subjectVisible = !subjectVisible")
c-chip(:text="subject.title" :inactive="!subject.units.length" @mouseover="$emit('highlight', subject)" @mouseout="$emit('highlight')")
div.text__unit__ag(v-if="subject.units.length" :class="{ active: subjectVisible }")
c-chip(v-for="unit in subject.units" :text="unit.title" inactive @mouseover="$emit('highlight', unit)" @mouseout="$emit('highlight')")
span.text__verb {{ verb }}
span.text__unit(v-if="complement" @click.stop="complementVisible = !complementVisible")
c-chip(:text="complement.title" :inactive="!complement.units.length" @mouseover="$emit('highlight', complement)" @mouseout="$emit('highlight')")
div.text__unit__ag(v-if="complement.units.length" :class="{ active: complementVisible }")
c-chip(v-for="unit in complement.units" :text="unit.title" inactive @mouseover="$emit('highlight', unit)" @mouseout="$emit('highlight')")
span.text__details(v-if="details") {{ details }}
span.text__meaning(v-if="meaning") {{ meaning }}
</template>
<script>
import CChip from '@/components/_generic/CChip.vue';
export default {
components: { CChip },
props: {
annotation: { type: Object, required: true }
},
data () {
return {
subjectVisible: false,
complementVisible: false
}
},
computed: {
subject () {
return this.annotation.dataUnits.filter(u => u.role === 'subject')[0];
},
verb () {
const n = this.subject.units.length > 1 ? 'plural' : 'singular';
let verb = this.annotation.rawAnnotation.reason.verb[n];
if (this.complement) verb += ` ${this.annotation.rawAnnotation.reason.verb.comparison}`;
else verb += `.`;
return ` ${verb}`;
},
complement () {
return this.annotation.dataUnits.filter(u => u.role === 'complement')[0];
},
details () {
let d = this.annotation.rawAnnotation.reason.details;
if (!d) return;
d = d.substr(-1).match(/^[.,:!? ]/) ? d : `${d}.`;
return ` ${d}`;
},
meaning () {
let m = this.annotation.rawAnnotation.meaning;
if (!m) return;
m = m.substr(-1).match(/^[.,:!? ]/) ? m : `${m}.`;
return ` ${m}`;
}
},
watch: {
subjectVisible () {
if (this.subjectVisible) this.complementVisible = false;
},
complementVisible () {
if (this.complementVisible) this.subjectVisible = false;
}
}
}
</script>
<style lang="sass" scoped>
@import '../../assets/styles/generic/_variables'
.text
padding: .5rem .5rem .5rem 2rem
position: relative
&::before
position: absolute
content: "”"
font-size: 4em
opacity: .5
font-family: serif
top: 0
left: 0
width: 100%
height: 100%
&__unit
position: relative
&__ag
position: absolute
left: 50%
bottom: 0
transform: translate(-50%, 100%)
display: none
width: 20em
background: rgba(255, 255, 255, .4)
backdrop-filter: blur(3px)
padding: .5rem
border-radius: 3px
box-shadow: $elevation
z-index: 150
&.active
display: initial
</style>

103
src/components/Retrieval/List.vue

@ -0,0 +1,103 @@
<template lang="pug">
aside.col-retrieval--viz
transition-group(name="flip-list" tag="ul" class="annotations-list")
li.annotations-list__item(v-for="annotation in sortedAnnotations" :key="annotation.meta.timestamp")
c-card(:annotation="annotation" :activeAnnotation="activeAnnotation" @highlight="$emit('highlight', $event)" @selectAnnotation="$emit('selectAnnotation', annotation)")
template(v-slot:extension)
slot(name="card")
</template>
<script>
import CCard from '@/components/Retrieval/CCard.vue';
import CLogo from '../_generic/CLogo.vue';
export default {
components: { CCard, CLogo },
props: {
/**
* The current state of the application's visualization.
* Used to display relevant annotations
*/
state: {
type: Object,
default: () => {}
},
/**
* The list of prior annotations
*/
annotations: {
type: Array,
default: () => []
},
/**
* Whether the list should be visible
*/
viewing: {
type: Boolean,
default: false
},
activeAnnotation: {
type: Object
}
},
data () {
return { open: false }
},
computed: {
selectedSubjects () {
if (!this.activeAnnotation) return [];
else {
return this.$colvis.getAnnotationDataBinders(this.activeAnnotation);
}
},
sortedAnnotations () {
const clone = JSON.parse(JSON.stringify(this.annotations));
const state = this.$colvis.getVizState();
clone.forEach(a => {
const s = a.meta.state;
let sim = 0;
for(const prop in s) {
console.log(prop, s[prop], state[prop]);
if (s[prop] === state[prop]) sim += 1;
}
a.sim = sim;
})
clone.sort((a, b) => new Date(a.meta.timestamp > new Date(b.meta.timestamp) ? 1 : -1));
clone.sort((a, b) => a.sim > b.sim ? -1 : 1);
return clone
}
},
}
</script>
<style lang="sass" scoped>
@import '../../assets/styles/generic/_variables'
.col-retrieval--viz
position: fixed
right: 0
top: 0
height: 100%
min-width: 25rem
transition: transform .2s ease-in-out
background: rgba(255, 255, 255, .3)
backdrop-filter: blur(2px)
z-index: 100
overflow-x: hidden
overflow-y: auto
+container("light")
display: flex
justify-content: center
.annotations-list
display: flex
flex-direction: column
list-style: none
padding: 0
&__item
margin-top: 1rem
.flip-list-move
transition: transform 1s
</style>

209
src/components/Retrieval/Viewer.vue

@ -0,0 +1,209 @@
<template lang="pug">
svg.col-retrieval-viewer(
v-if="$colvis.hasViewer()"
:class="{ active: activeUnits.length }"
ref="selection"
:style="{ top: `${compTop}px`, left: `${compLeft}px`, width: `${width}px`, height: `${height}px` }"
)
path.col-retrieval-viewer__selected(
v-for="(el, i) in pathPool"
:key="i"
:class="{ subject: el.role === 'subject', complement: el.role === 'complement' }"
:transform="getTransform(el)"
:d="el.domElement.attributes.d.value"
)
circle.col-retrieval-viewer__selected(
v-for="(el, i) in circlePool"
:key="i"
:class="{ subject: el.role === 'subject', complement: el.role === 'complement' }"
:transform="getTransform(el)"
:cx="el.domElement.cx.animVal.value"
:cy="el.domElement.cy.animVal.value"
:r="el.domElement.r.animVal.value"
)
rect.col-retrieval-viewer__selected(
v-for="(el, i) in rectPool"
:key="i"
:class="{ subject: el.role === 'subject', complement: el.role === 'complement' }"
:transform="getTransform(el)"
:x="el.domElement.attributes.x.value"
:y="el.domElement.attributes.y.value"
:width="el.domElement.attributes.width.value"
:height="el.domElement.attributes.height.value"
)
</template>
<script>
/**
* Create a new Selector.
* Selectors are the visual interface that allows analysts to select data units from their visualizations.
* @extends Vue
*/
export default {
props: {
/** Whether the visualization has been initialized */
init: { type: Boolean, required: true },
/** The list of selected units */
activeUnits: { type: Array, default: () => [] },
},
data () {
/** The top position of the Selector, in pixels */
const top = 0;
/** The left position of the Selector, in pixels */
const left = 0;
/** The width of the Selector, in pixels */
const width = 0;
/** The height of the Selector, in pixels */
const height = 0;
/** Some parent elements of the Selector might be in Position relative */
const offsetTop = 0;
const offsetLeft = 0;
/** List of event listeners to remove once the component is destroyed */
const eventListeners = []
/** ResizeObserver to make sure that the selector matches the SVG it overlays */
const resizeObserver = null;
return { top, left, width, height, offsetTop, offsetLeft, eventListeners, resizeObserver };
},
computed: {
/** @returns the list of selected DataBinders, either as subjects or complements. */
pool() {
return this.$colvis.getDataBinders(this.activeUnits);
},
/** @returns the list of paths within the selected DataBinders. */
pathPool() {
return this.pool.filter((binder) => binder.domElement.tagName === 'path');
},
/** @returns the list of circles within the selected DataBinders. */
circlePool() {
return this.pool.filter((binder) => binder.domElement.tagName === 'circle');
},
/** @returns the list of rectangles within the selected DataBinders. */
rectPool() {
return this.pool.filter((binder) => binder.domElement.tagName === 'rect');
},
compLeft() {
return this.left - this.offsetLeft;
},
compTop() {
return this.top - this.offsetTop;
}
},
methods: {
/**
* Return the translation value of a given element, based on its transformation matrix.
* It allows to restore the element's precise position despite potential transformations
* implied by parents.
* @param {DataBinder} element the element whose translate value is required
*/
getTransform(element) {
if (!(element.domElement instanceof SVGGraphicsElement))
throw new Error('The selected element is not a SVG element');
const matrix = element.domElement.getCTM();
return matrix ? `translate(${matrix.e}, ${matrix.f})` : ``;
},
/**
* Get the position of the container of the visualization.
* Setting { top, left, width, height } properties of the instance.
*/
getPosition() {
function position () {
const target = document.querySelector(this.$colvis.getSpecs().visualization.container);
if (!target) {
throw new Error('Could not locate a target container for the Selector. Please make sure the specifications is set correctly.');
}
let offsetTop = 0;
let offsetLeft = 0;
let offsetter = this.$refs.selection.parentElement;
while (offsetter.offsetParent) {
offsetTop += offsetter.offsetParent.offsetTop;
offsetLeft += offsetter.offsetParent.offsetLeft;
offsetter = offsetter.offsetParent;
}
const { top, left, width, height } = target.getBoundingClientRect();
Object.assign(this.$data, { top, left, width, height, offsetTop, offsetLeft });
}
position.call(this);
if (this.height <= 0) {
console.warn('Could not attach Selector. Will try again in 1 second.');
const interval = window.setInterval(e => {
position.call(this);
if (this.height > 0) {
console.log('Selector attached');
clearInterval(interval);
} else console.warn('Could not attach Selector. Will try again in 1 second.');
}, 1000)
}
},
},
watch: {
init () {
if (this.$colvis.isInit()) this.getPosition();
}
},
mounted () {
this.getPosition();
addEventListener('resize', this.getPosition);
this.eventListeners.push({ resize: this.getPosition });
/**
* We resize the selector when the selector's parent's element is resized. We cannot observe
* SVGs, since there are multiple issues with SVG and bounding boxes.
*/
this.resizeObserver = new ResizeObserver(e => { console.log('triggering resize'); this.getPosition(); });
this.resizeObserver.observe(this.$refs.selection.parentElement);
},
destroyed () {
this.eventListeners.forEach(e => {
Object.keys(e).forEach(key => {
removeEventListener(key, e[key]);
});
});
this.resizeObserver.disconnect();
}
}
</script>
<style lang="sass">
@import "../../assets/styles/generic/_animations"
@import "../../assets/styles/generic/_variables"
.col-retrieval-viewer
position: absolute
pointer-events: none
z-index: 4
&.active
background-color: rgba(sc("red"), .2)
pointer-events: auto
.col-input-selector__selection, .col-input-selector__selected
opacity: 1
&__selection, &__selected
opacity: .9
fill: rgba(100, 100, 100, .3)
stroke: rgb(100, 100, 100)
mix-blend-mode: multiply
&.subject
fill: rgba(sc("blue"), .3)
stroke: sc("blue")
stroke-dasharray: 5
animation: col-rotate 2s infinite
&.complement
fill: rgba(sc("yellow"), .3)
stroke: sc("yellow")
stroke-dasharray: 5
animation: col-rotate 2s infinite
</style>

105
src/components/Retrieval/Viz.vue

@ -1,20 +1,20 @@
<template lang="pug">
section.col-retrieval--viz(:class="{ active: open }")
header.col-retrieval--viz__header(@click="open = !open" :class="{ active: open }")
c-logo.col-retrieval--viz__header__logo(color="white")
h3.col-retrieval--viz__header__extra There are {{ annotations.length }} annotations for this visualization.
main.col-retrieval--viz__body
ul.annotations-list
li.annotations-list__item(v-for="annotation in annotations" :key="annotation.meta.time" @mousedown="selectAnnotation(annotation)")
c-annotation(:annotation="annotation")
section.col-retrieval--viz(v-if="$colvis.isInit()")
col-retrieval-viewer(v-show="activeUnits.length" :activeUnits="activeUnits" :init="$colvis.isInit()" :offsetTop="offsetTop")
col-retrieval-list(v-show="viewing" :annotations="annotations" :activeAnnotation="activeAnnotation" :state="state" @highlight="selectUnit" @selectAnnotation="selectAnnotation")
template(v-slot:card)
slot(name="card")
</template>
<script>
import CAnnotation from '../_generic/CAnnotation.vue';
import ColRetrievalViewer from '@/components/Retrieval/Viewer.vue'
import ColRetrievalList from '@/components/Retrieval/List.vue'
import CCard from '@/components/Retrieval/CCard.vue';
import CLogo from '../_generic/CLogo.vue';
import { strfy, hashCode } from '../../assets/utils';
export default {
components: { CAnnotation, CLogo },
components: { ColRetrievalViewer, ColRetrievalList },
props: {
/**
* The current state of the application's visualization.
@ -30,74 +30,41 @@ export default {
annotations: {
type: Array,
default: () => []
}
},
/**
* Whether the list should be visible
*/
viewing: {
type: Boolean,
default: false
},
},
data () {
return {
open: false,
activeAnnotation: null
activeAnnotation: null,
activeUnits: []
}
},
computed: {
selectedSubjects () {
if (!this.activeAnnotation) return [];
else {
return this.$colvis.getAnnotationDataBinders(this.activeAnnotation);
}
}
},
methods: {
methods: {
selectUnit (unit) {
if (!unit) this.activeUnits = [];
else this.activeUnits.push(unit);
},
selectAnnotation (annotation) {
if (this.activeAnnotation === annotation) this.activeAnnotation = null;
else this.activeAnnotation = annotation;
}
},
watch: {
activeAnnotation () {
this.activeUnits = [];
if (this.activeAnnotation) {
this.activeAnnotation.dataUnits.forEach(d => {
this.selectUnit(d);
});
this.$emit('updateState', this.activeAnnotation.meta.state)
}
}
}
}
</script>
<style lang="sass">
@import '../../assets/styles/generic/_variables'
.col-retrieval--viz
position: fixed
bottom: 0
left: 0
width: 100%
transform: translateY(100%)
transition: transform .2s ease-in-out
&.active
transform: none
&__header
+toolbar("light")
border-radius: 0 5px 0 0
max-width: 64px
transform: translateY(-100%)
backdrop-filter: blur(10px)
transition: all .2s ease-in-out
overflow: hidden
position: absolute
&__logo
flex-shrink: 0
margin-right: 1rem
&__extra
min-width: max-content
&:hover, &.active
cursor: pointer
filter: brightness(1.1)
max-width: 40rem
width: max-content
&__body
+container("light")
.annotations-list
display: flex
&__item
display: inline-block
</style>

19
src/components/_generic/CAnnotation.vue

@ -1,19 +0,0 @@
<template lang="pug">
div.annotation {{ annotation.rawAnnotation.meaning }}
</template>
<script>
export default {
props: {
annotation: {
type: Object,
required: true
}
}
}
</script>
<style lang="sass" scoped>
.annotation
width: 200px
</style>

17
src/components/_generic/CChip.vue

@ -1,5 +1,5 @@
<template lang="pug">
span.chip {{ text }}
span.chip(:class="{ inactive }" @mouseover="$emit('mouseover')" @mouseout="$emit('mouseout')") {{ text }}
small.chip__button(v-if="closeable" @click="$emit('close')") x
</template>
@ -12,6 +12,10 @@ export default {
closeable: {
type: Boolean,
default: false
},
inactive: {
type: Boolean,
default: false
}
}
}
@ -28,6 +32,17 @@ export default {
display: inline-block
margin: 0 .2rem .2rem 0
&:hover
background: cc("blue", "light")
cursor: pointer
&.inactive
background: cc("grey", "dark")
&:hover
background: cc("grey")
cursor: initial
&__button
display: inline-block
width: 1rem

16
src/components/_generic/CLoading.vue

@ -4,6 +4,7 @@
</template>
<style lang="sass" scoped>
@import '../../assets/styles/generic/_animations'
@import '../../assets/styles/generic/_variables'
.loading
@ -17,18 +18,5 @@
position: absolute
left: 0
top: 0
animation: loading-animation ease-out 1.5s infinite
@keyframes loading-animation
0%
width: 10%
left: 0
20%
width: 70%
left: 20%
100%
width: 10%
left: 100%
animation: col-loading ease-out 1.5s infinite
</style>

32
src/lib.js

@ -1,5 +1,7 @@
import ColInputMain from './components/Input/Main.vue';
import ColRetrievalViz from './components/Retrieval/Viz.vue';
import CText from './components/Retrieval/CText.vue';
import CCard from './components/Retrieval/CCard.vue';
import { getDataFromContainer, hashCode, strfy } from './assets/utils';
import defaultOptions from './assets/utils/defaultOptions.json'
import { DataBinder } from './models';
@ -13,6 +15,8 @@ const ColvisPlugin = {
/** Registering the main components */
Vue.component('ColInputMain', ColInputMain);
Vue.component('ColRetrievalViz', ColRetrievalViz);
Vue.component('CText', CText);
Vue.component('CCard', CCard);
/** Installing the colvis plugin under $colvis namespace */
Vue.prototype.$colvis = {
@ -113,7 +117,10 @@ const ColvisPlugin = {
return hashCode(this._vm.$data.rawSpecs);
},
getVizState () {
return this._vm.$data.state;
return this._vm.$data.vizState;
},
setVizState (vizState) {
this._vm.$data.vizState = vizState;
},
getOptions () {
return this._vm.$data.specs.options;
@ -126,6 +133,10 @@ const ColvisPlugin = {
if (this._vm.$data.specs.options.selector === false) return false;
else return true;
},
hasViewer () {
if (this._vm.$data.specs.options.viewer === false) return false;
else return true;
},
/**
*
* @param {*} items
@ -175,7 +186,24 @@ const ColvisPlugin = {
return this.getEntities()[index]
}
})
.filter((binder) => binder) // remove potential non-existant inputs
.filter((binder) => binder) // remove potential non-existant binders
},
getDataBinders (units) {
const singleUnits = units.reduce(
(prev, unit) => unit.units.length ? prev.concat(unit.units) : prev.concat([unit]),
[]
);
return singleUnits
.map(unit => { // get the corresponding DataBinder
const ids = this.getEntities().map(e => e.id);
const index = ids.indexOf(unit.id);
if (index !== -1) {
const entity = this.getEntities()[index];
entity.role = unit.role;
return entity;
}
})
.filter((binder) => binder) // remove potential non-existant binders
},
getBinderNature (binder) {
let n;

2
src/models/Annotation/DataUnit.js

@ -43,7 +43,7 @@ export default class DataUnit {
/**
* The title of the Data Unit, if aggregated.
* The title of the Data Unit.
* @type {string|null}
* @public
*/

6
src/models/Annotation/index.js

@ -75,12 +75,11 @@ export default class Annotation {
constructor({ rawAnnotation, specs, ci }) {
// Process all subject data units
rawAnnotation.subjects.forEach(subject => {
this.dataUnits.push(new DataUnit({ role: 'subject', id: subject.id, natureId: subject.natureId}))
this.dataUnits.push(new DataUnit({ role: 'subject', id: subject.id, natureId: subject.natureId, title: subject.title }))
});
rawAnnotation.complements.forEach(complement => {
this.dataUnits.push(new DataUnit({ role: 'complement', id: complement.id, natureId: complement.natureId }))
this.dataUnits.push(new DataUnit({ role: 'complement', id: complement.id, natureId: complement.natureId, title: complement.title }))
});
const subjects = this.dataUnits.filter(d => d.role === 'subject');
@ -89,6 +88,7 @@ export default class Annotation {
let aggregatedComplementDataUnit;
if (subjects.length > 1) {
console.log('this shoudl be good', rawAnnotation)
aggregatedSubjectDataUnit = new DataUnit({
role: 'subject',
id: hashCode(strfy(subjects.map(s => s.id))+rawAnnotation.subjectName),

36
src/models/RawAnnotation.js

@ -76,6 +76,11 @@ export default class RawAnnotation {
*/
text;
/**
* The state of the visualization
*/
state;
/**
* Constructor method of the RawAnnotation class
* @param {Object} raw - The raw values of the form
@ -83,45 +88,40 @@ export default class RawAnnotation {
* @param {DataBinder[]} raw.complements - The complements of the annotation
* @param {Object} raw.reason - The reason
* @param {string} raw.meaning - The meaning
* @param {string[]} raw.subjects - The tags of the annotation
* @param {string[]} raw.tags - The tags of the annotation
* @param {Object} raw.state - The state of the visualization
* @param {('combobox'|'selector')[]} raw.selectionMethods - the methods used
*/
constructor({ subjects, subjectName, complements, complementName, reason, meaning, tags, selectionMethods, specs, ci }) {
constructor({ subjects, subjectName, complements, complementName, reason, meaning, tags, selectionMethods, specs, ci, state }) {
this.subjects = subjects;
this.subjectName = subjectName;
this.subjectName = subjectName || (this.subjects.length === 1 ? this.subjects[0].title : null);
this.complements = complements;
this.complementName = complementName;
this.complementName = complementName || (this.complements.length === 1 ? this.complements[0].title : null);
this.reason = reason;
this.meaning = meaning;
this.tags = tags;
this.selectionMethods = selectionMethods;
this.state = state;
const subList = ci.getFilteredList(subjects, specs);
const comList = ci.getFilteredList(complements, specs);
let tsubjects, tcomplements
if (subjectName) {
tsubjects = subjectName;
} else {
if (!this.subjectName && this.subjects.length > 1) {
let tSubList = subList.displayed.map(s => ci.getEntityTitle(s, specs));
if (subList.hidden.length) tSubList = tSubList.concat([`and ${subList.hidden.length} others`])
tsubjects = tSubList.join(', ');
this.subjectName = tSubList.join(', ');
}
if (complementName) {
tcomplements = complementName;
} else {
if (!this.complementName && this.complements.length > 1) {
let tComList = comList.displayed.map(c => ci.getEntityTitle(c, specs));
if (comList.hidden.length) tComList = tComList.concat([`and ${comList.hidden.length} others`])
tcomplements = tComList.join(', ');
this.complementName = tComList.join(', ');
}
if (tsubjects && (reason.verb || reason.details)) {
this.text = tsubjects;
if (this.subjectName && (reason.verb || reason.details)) {
this.text = this.subjectName;
this.text += ` ${reason.verb.text}`;
if (tcomplements) this.text += ` ${tcomplements}`;
if (this.complementName) this.text += ` ${this.complementName}`;
if (reason.details) this.text += ` ${reason.details}`;
this.text += '.';
if (meaning) this.text += ` ${meaning}.`;

Loading…
Cancel
Save