Skip to content

Commit

Permalink
fix(charts): Fix chart URI sharable link bug issue
Browse files Browse the repository at this point in the history
  • Loading branch information
tholulomo committed Mar 29, 2023
1 parent 614f362 commit fe9e91a
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 10 deletions.
2 changes: 1 addition & 1 deletion app/src/components/nanomine/ImageUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export default {
} else if (this.inputtedDimensions.units === 'millimeters') {
ratio = ratio / 1000
}
// console.log(this.displayedFiles[0].size.width, this.displayedFiles[0].pixelSize.width)
this.selectedOptions.dimensions = { units: this.inputtedDimensions.units, width: this.displayedFiles[0].size.width, height: this.displayedFiles[0].size.height, ratio: ratio }
} else {
this.selectedOptions.dimensions = { units: this.inputtedDimensions.units, width: parseInt(this.inputtedDimensions.width), height: parseInt(this.inputtedDimensions.height), ratio: null }
Expand Down
76 changes: 70 additions & 6 deletions app/src/modules/vega-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,49 @@ const defaultChart = {
depiction: null
}

const chartType = 'http://semanticscience.org/resource/Chart'
// const lodPrefix = window.location.origin
const chartUriPrefix = 'http://nanomine.org/viz/'
const foafDepictionUri = 'http://xmlns.com/foaf/0.1/depiction'
const hasContentUri = 'http://vocab.rpi.edu/whyis/hasContent'

const chartFieldPredicates = {
baseSpec: 'http://semanticscience.org/resource/hasValue',
query: 'http://schema.org/query',
title: 'http://purl.org/dc/terms/title',
description: 'http://purl.org/dc/terms/description',
dataset: 'http://www.w3.org/ns/prov#used'
}

const chartIdLen = 16

function generateChartId () {
const intArr = new Uint8Array(chartIdLen / 2)
window.crypto.getRandomValues(intArr)
const chartId = Array.from(intArr, (dec) => ('0' + dec.toString(16)).substr(-2)).join('')

return `${chartUriPrefix}${chartId}`
}

function buildChartLd (chart) {
chart = Object.assign({}, chart)
chart.baseSpec = JSON.stringify(chart.baseSpec)
const chartLd = {
'@id': chart.uri,
'@type': [chartType],
[foafDepictionUri]: {
'@id': `${chart.uri}_depiction`,
[hasContentUri]: chart.depiction
}
}
Object.entries(chart)
.filter(([field, value]) => chartFieldPredicates[field])
.forEach(([field, value]) => {
chartLd[chartFieldPredicates[field]] = [{ '@value': value }]
})
return chartLd
}

function getDefaultChart () {
return Object.assign({}, defaultChart)
}
Expand All @@ -62,7 +105,17 @@ const chartQuery = `
`

async function loadChart (chartUri) {
const singleChartQuery = chartQuery + `\n VALUES (?uri) { (<${chartUri}>) }`
// Check the chart uri before running query
let chartUrl = chartUri
if (chartUrl.includes('view/')) {
// We should not get in this if logic. The issue with chart URI using a different
// lodPrefix is now fixed (e.g. http://nanomine.org/viz/chartId instead of http://purl.org/chart/view/chartId)
// Todo (ticket-xx): Remove this if logic if response is consistent
chartUrl = decodeURIComponent(chartUri.split('view/')[1])
}

const valuesBlock = `\n VALUES (?uri) { (<${chartUri}>) }`
const singleChartQuery = chartQuery.replace(/(where\s*{)/i, '$1' + valuesBlock)
const { results } = await querySparql(singleChartQuery)
const rows = results.bindings
if (rows.length < 1) {
Expand Down Expand Up @@ -102,17 +155,28 @@ function buildCsvSpec (baseSpec, csvResults) {
return spec
}

const chartUriPrefix = 'http://nanomine.org/viz/'

function toChartId (chartUri) {
if (!chartUri.startsWith(chartUriPrefix)) {
throw new Error(`Unexpected chart uri "${chartUri}". Was expecting prefix "${chartUriPrefix}"`)
if (chartUri && !chartUri.startsWith(chartUriPrefix)) {
// We should not get in this if logic. The issue with chart URI using a different
// lodPrefix is now fixed (e.g. http://nanomine.org/viz/chartId instead of http://purl.org/chart/view/chartId)
// Todo (ticket-xx): Remove this if logic if response is consistent
return chartUri
}
if (!chartUri) return

return chartUri.substring(chartUriPrefix.length)
}

function toChartUri (chartId) {
if (chartId.includes('viz')) {
return `${window.location.origin}/explorer/chart/view/${encodeURIComponent(chartId)}`
}

return chartUriPrefix + chartId
}

export { getDefaultChart, loadChart, buildSparqlSpec, buildCsvSpec, toChartId, toChartUri, chartUriPrefix }
function shareChartUri (chartId) {
return `${window.location.origin}/explorer/chart/view/${chartId}`
}

export { getDefaultChart, saveChart, loadChart, copyChart, buildSparqlSpec, buildCsvSpec, toChartId, toChartUri, shareChartUri, chartUriPrefix }
209 changes: 209 additions & 0 deletions app/src/pages/explorer/chart/editor/Chart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
<template>
<div class="chart_editor">
<div class="u--layout-width viz-sample u--layout-flex utility_flex_mobile" >
<div v-if="loading" class="editImage_modal" style="z-index:4">
<spinner :loading="loading"/>
</div>
<!-- Left element -->
<div class="chart_editor__left-view" style="flex: 1 1 50%;">
<div class="u--layout-width u--layout-flex u--layout-flex-justify-fs u_margin-bottom-small">
<a @click.prevent="leftTab = 1" :class="[leftTab === 1 ? 'active u--color-primary' : '']" class="viz-tab__button">Sparql</a>
<a @click.prevent="leftTab = 2" :class="[leftTab === 2 ? 'active u--color-primary' : '']" class="viz-tab__button">Vega</a>
<a @click.prevent="leftTab = 3" :class="[leftTab === 3 ? 'active u--color-primary' : '']" class="viz-tab__button">Save Chart</a>
</div>

<div class="chart_editor__left-content">
<yasqe
v-if="chart.query"
v-model="chart.query"
v-on:query-success="onQuerySuccess"
v-show="leftTab === 1" :showBtns='true'>
</yasqe>
<v-jsoneditor v-show="leftTab === 2"
v-model="chart.baseSpec"
:options="specJsonEditorOpts"
v-if="chart.baseSpec"
> </v-jsoneditor>
<form v-show="leftTab === 3">
<md-field>
<label>Title</label>
<md-input v-model="chart.title"></md-input>
</md-field>

<md-field>
<label>Description</label>
<md-textarea v-model="chart.description"></md-textarea>
</md-field>

<button class="btn btn--primary" @click.prevent="saveChart"> {{ actionType }} <md-icon class="u--color-success">check</md-icon> </button>
</form>
</div>

</div>

<!-- The resizer -->
<div class="u_height--max u--bg-grey u_pointer--ew md-xsmall-hide chart_editor-divider" id="dragMe"></div>

<!-- Right element -->
<div class="chart_editor__right-view" style="flex: 1 1 50%;">
<h4 class="u--margin-pos">Preview</h4>
<div class="u--layout-width u--layout-flex u--layout-flex-justify-end u_margin-bottom-small">
<a @click.prevent="rightTab = 1" :class="[rightTab === 1 ? 'active u--color-primary' : '']" class="viz-tab__button">Chart</a>
<a @click.prevent="rightTab = 2" :class="[rightTab === 2 ? 'active u--color-primary' : '']" class="viz-tab__button">Table</a>
</div>

<div class="chart_editor__right-content">
<div v-show="rightTab === 1" class="loading-dialog" style="margin: auto">
<vega-lite :spec="spec" @new-vega-view="onNewVegaView" class="btn--animated vega-embed-chartview"/>
</div>

<div v-show="rightTab === 2" class="viz-intro-query" style="min-height: 40rem !important">
<yasr :results="results"></yasr>
</div>
</div>
</div>

</div>
<md-button @click="goToExplorer" class="md-fab md-fixed md-fab-bottom-right md-primary btn--primary">
<md-icon>arrow_back</md-icon>
</md-button>
</div>
</template>

<script>
import { querySparql } from '@/modules/sparql'
import { saveChart, getDefaultChart, buildSparqlSpec } from '@/modules/vega-chart'
import VJsoneditor from 'v-jsoneditor'
import VegaLite from '@/components/explorer/VegaLiteWrapper.vue'
import yasqe from '@/components/explorer/yasqe'
import yasr from '@/components/explorer/yasr'
import spinner from '@/components/Spinner'
export default {
name: 'ChartCreate',
components: {
VJsoneditor,
VegaLite,
yasqe,
yasr,
spinner
},
data () {
return {
loading: true,
mouseIsDown: false,
x: 0,
y: 0,
leftTab: 1,
rightTab: 1,
results: null,
specJsonEditorOpts: {
mode: 'code',
mainMenuBar: false
},
chart: {
baseSpec: null,
query: null,
title: null,
description: null
},
actionType: 'Save Chart',
sumbmittedIdentifier: undefined
}
},
computed: {
spec () {
const spec = buildSparqlSpec(this.chart.baseSpec, this.results) ?? {}
return spec
}
},
methods: {
getSparqlData () {
const vm = this
querySparql(vm.chart.query)
.then(this.onQuerySuccess)
.then(this.loading = false)
},
onQuerySuccess (results) {
this.results = results
},
onSpecJsonError () {
console.log('bad', arguments)
},
async onNewVegaView (view) {
const blob = await view.toImageURL('png')
.then(url => fetch(url))
.then(resp => resp.blob())
const fr = new FileReader()
fr.addEventListener('load', () => {
this.chart.depiction = fr.result
})
fr.readAsDataURL(blob)
},
async loadChart () {
// this.types = 'new, edit, restore & delete'
let getChartPromise
if (this.$route.params.type === 'new') {
this.actionType = 'Save Chart'
getChartPromise = Promise.resolve(getDefaultChart())
} else if (this.$route.params.type === 'edit') {
// fetch chart from knowledge graph
this.actionType = 'Edit Chart'
} else {
// Get chart from mongo backup
this.actionType = 'Restore Chart'
this.reloadRestored()
}
getChartPromise
.then(chart => {
this.chart = chart
return this.getSparqlData()
})
},
async reloadRestored () {
// 1. Fetch backup from mongo
// 2. Post each chart (schema + sparql) to knowledge graph
// 3. Toggle restore flag in mongo
},
async saveChart () {
// Todo (ticket xx): Move this into vuex
try {
const chartNanopub = await saveChart(this.chart)
const resp = await this.$store.dispatch('explorer/curation/cacheNewChartResponse', {
identifier: this.sumbmittedIdentifier,
chartNanopub
})
if (resp.identifier) {
this.sumbmittedIdentifier = resp.identifier
}
if (this.$route.params.type === 'new') {
// Save chart to MongoDB - async operation
} else {
// Find in mongo and update - async operation
}
this.$store.commit('explorer/curation/setNewChartExist', true)
// Change button name after submission
this.actionType = 'Edit Chart'
this.$store.commit('setSnackbar', {
message: 'Chart saved successfully!',
duration: 5000
})
} catch (err) {
// TODO (Ticket xxx): USE THE APP DIALOGUE BOX INSTEAD OF ALERT BOX
return alert(err)
}
},
goToExplorer () {
this.$router.push('/explorer/chart')
}
},
created () {
this.loading = true
this.loadChart()
}
}
</script>
5 changes: 4 additions & 1 deletion app/src/pages/explorer/chart/view/vega-view-script.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import VJsoneditor from 'v-jsoneditor'
import Dialog from '@/components/Dialog.vue'
import { loadChart, buildSparqlSpec, toChartUri } from '@/modules/vega-chart'
import { loadChart, buildSparqlSpec, toChartUri, shareChartUri } from '@/modules/vega-chart'
import VegaLite from '@/components/explorer/VegaLiteWrapper.vue'
import yasqe from '@/components/explorer/yasqe'
import yasr from '@/components/explorer/yasr'
Expand Down Expand Up @@ -56,6 +56,9 @@ export default {
},
fullChartUri () {
return toChartUri(this.chartId)
},
shareChartUri () {
return shareChartUri(this.chartId)
}
},
methods: {
Expand Down
2 changes: 1 addition & 1 deletion app/src/pages/explorer/chart/view/vega-view.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
<template v-slot:content>
<div v-if="dialog.type=='share'">
<md-field> <label>Chart Link</label>
<md-textarea disabled v-model="fullChartUri"></md-textarea>
<md-textarea disabled v-model="shareChartUri"></md-textarea>
</md-field>
<div>Copy the link above to share this chart</div>
</div>
Expand Down
Loading

0 comments on commit fe9e91a

Please sign in to comment.