Skip to content

Commit

Permalink
Merge branch 'feature/sunburst'
Browse files Browse the repository at this point in the history
  • Loading branch information
eboileau committed Aug 12, 2024
2 parents 00cea3d + da8c856 commit d0add71
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 1 deletion.
7 changes: 7 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"axios": "^1.7.2",
"jwt-decode": "^4.0.0",
"pinia": "^2.1.7",
"plotly.js-dist": "^2.34.0",
"primeicons": "^7.0.0",
"primevue": "^3.52.0",
"vee-validate": "^4.13.1",
Expand Down
176 changes: 176 additions & 0 deletions client/src/components/chart/SunburstChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<script>
import Plotly from 'plotly.js-dist'
import { HTTP } from '@/services/API.js'
export default {
name: 'SunburstChart',
props: ['chart'],
setup(props) {
const chart = props.chart
},
data() {
return {
data: null
}
},
mounted() {
this.fetchData()
},
methods: {
async fetchData() {
try {
const response = await HTTP.get(`/sunburst/${this.chart}`)
const data = await response.data
this.calculateCumulativeSize(data[0])
this.data = this.processData(data[0]) // Assuming data is an array and you need the first element
this.createChart()
} catch (error) {
console.error('Error fetching data:', error)
}
},
calculateCumulativeSize(node) {
if (!node.children) {
return node.size || 0
}
let totalSize = 0
for (let child of node.children) {
totalSize += this.calculateCumulativeSize(child)
}
node.cumulativeSize = totalSize
return totalSize
},
getColors() {
return [
'#00b052',
'#02aeed',
'#6ac886',
'#7acff4',
'#c0e7ca',
'#e0f4fd',
'#00b052',
'#02aeed',
'#6ac886',
'#7acff4',
'#c0e7ca',
'#e0f4fd',
'#00b052',
'#02aeed',
'#6ac886',
'#7acff4',
'#c0e7ca',
'#e0f4fd',
'#00b052',
'#02aeed',
'#6ac886',
'#7acff4',
'#c0e7ca',
'#e0f4fd'
]
},
// Function to lighten a color by a certain percentage.
// This is used to create variations in color for child nodes.
lightenColor(color, depth) {
// Convert the hex color to an integer.
const num = parseInt(color.slice(1), 16)
// Adjust the amount based on the depth. The deeper the node, the less lightening is applied.
const amt = Math.round(2.55 * (depth * 5)) // Adjust the multiplier for finer control.
// Extract the red, green, and blue components and apply the adjustment.
const R = (num >> 16) - amt
const G = ((num >> 8) & 0x00ff) - amt
const B = (num & 0x0000ff) - amt
// Reassemble the color, ensuring each component is within valid range.
return `#${(0x1000000 + (R > 0 ? R : 0) * 0x10000 + (G > 0 ? G : 0) * 0x100 + (B > 0 ? B : 0))
.toString(16)
.slice(1)}`
},
processData(data) {
const ids = []
const labels = []
const parents = []
const values = []
const colors = []
const textColors = [] // Array to hold the text colors for labels.
const customdata = []
const colorPalette = this.getColors()
// Object to store the base color for each first-level node.
const baseColors = {}
function traverse(node, parent, depth, parentColor) {
const id = parent ? `${parent}-${node.name}` : node.name
ids.push(id)
labels.push(node.name)
parents.push(parent)
values.push(node.size || 0)
let color
let textColor = 'white' // Default text color is white.
if (depth === 0) {
color = '#F4F1F5FF'
textColor = 'black' // Keep root level label in black.
} else if (depth === 1) {
// Check if the node is a first-level node.
// Assign a color from the palette to first-level nodes.
color = colorPalette[ids.length % colorPalette.length]
baseColors[node.name] = color // Store the base color for later use.
} else {
// For subsequent levels, lighten the parent's color.
color = this.lightenColor(parentColor, depth) // Adjust lightness by depth level.
}
colors.push(color) // Add the calculated color to the colors array.
textColors.push(textColor) // Assign the text color to the textColors array.
// colors.push(colorPalette[depth % colorPalette.length]);
customdata.push(node.cumulativeSize || node.size || 0)
if (node.children) {
node.children.forEach((child) => traverse.call(this, child, id, depth + 1, color))
}
}
// Start the traversal from the root node, passing an empty string for the parent and depth 0.
traverse.call(this, data, '', 0, '')
return [
{
type: 'sunburst',
ids: ids,
labels: labels,
parents: parents,
values: values,
marker: { colors: colors, line: { width: 1 } },
textfont: { color: textColors }, // Set the text color for each label.
customdata: customdata,
hovertemplate: '<b>%{label}</b> : %{customdata}<extra></extra>',
textinfo: 'label',
insidetextorientation: 'horizontal'
}
]
},
createChart() {
const layout = {
margin: { t: 0, l: 0, r: 0, b: 0 },
sunburstcolorway: this.getColors(),
extendsunburstcolorway: true
}
Plotly.newPlot(this.$refs.chart, this.data, layout)
this.$refs.chart.on('plotly_click', (event) => {
const point = event.points[0]
if (point) {
Plotly.restyle(this.$refs.chart, 'root', [point.id])
}
})
}
}
}
</script>

<template>
<div ref="chart"></div>
</template>
44 changes: 44 additions & 0 deletions client/src/components/home/HomeRelease.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script setup>
import { ref, onMounted } from 'vue'
import SunburstChart from '@/components/chart/SunburstChart.vue'
import { HTTP } from '@/services/API.js'
const sites = ref()
const datasets = ref()
// clean this, and fetch annotation (DB) release info
onMounted(() => {
HTTP.get('/release')
.then(function (response) {
sites.value = response.data.sites
datasets.value = response.data.datasets
})
.catch((error) => {
console.log(error)
})
})
</script>

<template>
<SectionLayout :secondary="false">
<div>
<div class="px-4 py-8 md:px-6 lg:px-8 text-center">
<h1 class="font-ham text-4xl font-semibold m-auto p-4 dark:text-white/80">
<span>Release</span>
</h1>
<p class="text-xl font-medium text-gray-600 pt-4 pb-2 dark:text-surface-400">
Sci-ModoM contains {{ sites }} reported sites with stoichiometric information across
{{ datasets }} datasets, annotated using Ensembl release 110.
</p>
<div class="flex flex-row pt-12">
<div class="basis-1/2">
<SunburstChart chart="search" />
</div>
<div class="basis-1/2">
<SunburstChart chart="browse" />
</div>
</div>
</div>
</div>
</SectionLayout>
</template>
2 changes: 2 additions & 0 deletions client/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import HomeViews from '@/components/home/HomeViews.vue'
import HomeIntro from '@/components/home/HomeIntro.vue'
import HomeFeatures from '@/components/home/HomeFeatures.vue'
import HomeRoadmap from '@/components/home/HomeRoadmap.vue'
import HomeRelease from '@/components/home/HomeRelease.vue'
</script>

<template>
Expand All @@ -12,6 +13,7 @@ import HomeRoadmap from '@/components/home/HomeRoadmap.vue'
<HomeViews />
<HomeIntro />
<HomeFeatures />
<HomeRelease />
<HomeRoadmap />
</DefaultLayout>
</template>
16 changes: 16 additions & 0 deletions server/src/scimodom/api/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,19 @@ def get_logo_file(motif):
return {"image": get_valid_logo(motif).as_posix()}
except ClientResponseException as e:
return e.response_tupel


@api.route("/sunburst/<chart>", methods=["GET"])
@cross_origin(supports_credentials=True)
def get_sunburst_chart(chart):
if chart not in ["search", "browse"]:
raise ClientResponseException(404, "Unrecognized chart type.")
utitlies_service = get_utilities_service()
return utitlies_service.get_chart_data(chart)


@api.route("/release", methods=["GET"])
@cross_origin(supports_credentials=True)
def get_release():
utitlies_service = get_utilities_service()
return utitlies_service.get_release_info()
Loading

0 comments on commit d0add71

Please sign in to comment.