Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/histogram slider #127

Merged
merged 7 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ RUN echo -e " server {\n" \
" root /app;\n" \
" index index.html;\n" \
" try_files \$uri \$uri/ /index.html;\n" \
" add_header 'Cross-Origin-Embedder-Policy' 'require-corp';\n" \
" add_header 'Cross-Origin-Opener-Policy' 'same-origin';\n" \
" }\n" \
" }\n" \
> /etc/nginx/conf.d/datalab.conf
Expand Down
12 changes: 4 additions & 8 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"vue": "^3.5.9",
"vue-router": "^4.2.5",
"vue3-carousel": "^0.3.1",
"vuetify": "^3.6.0",
"vuetify": "^3.7.4",
"webfontloader": "^1.0.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/components/DataSession/OperationWizard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ref, onMounted, computed, defineEmits, defineProps} from 'vue'
import { fetchApiCall, handleError } from '@/utils/api'
import { calculateColumnSpan } from '@/utils/common'
import ImageGrid from '../Global/ImageGrid'
import ImageScalingGroup from '../Global/ImageScalingGroup'
import ImageScalingGroup from '../Global/Scaling/ImageScalingGroup'
import { useConfigurationStore } from '@/stores/configuration'
import { useAlertsStore } from '@/stores/alerts'

Expand Down
229 changes: 229 additions & 0 deletions src/components/Global/Scaling/HistogramSlider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<script setup>
import { ref, defineProps, defineEmits, computed, watch } from 'vue'

// This component draws a sparkline histogram with range slider controls on top of it.
// It has two coordinate systems - sliderRange which is a linear system of bins for the
// histogram passed in, with bin middles defined by a same-sized array bins, and scaleRange
// which is the actual lower/upper point values in pixel space. You can get better granularity
// on changing scaleRange with the number fields, since that can change to any value in the range
// vs using the histogram slider which has steps according to the highly non-linear bins used
// to generate the histogram.

const props = defineProps({
histogram: {
type: Array,
required: true
},
bins: {
type: Array,
required: true
},
maxValue: {
type: Number,
required: false,
default: 65500
},
zMin: {
type: Number,
required: false,
default: 0
},
zMax: {
type: Number,
required: false,
default: 65500
}
})

const emit = defineEmits(['updateScaling'])

const scaleRange = ref([props.bins[0], props.bins[props.bins.length-1]])
const sliderRange = ref([0, props.bins.length-1])
const selectedColor = 'rgb(20,182,246)'
const backgroundColor = 'rgb(204,208,211)'

const gradient = computed(() => {
// This function computes a "gradient" for the histogram sparkline, showing the selected region as highlighted
let gradientArray = []
for (let i = 0; i < props.bins.length; i++) {
if (i > (sliderRange.value[0]+1) && i < (sliderRange.value[1]-1)) {
gradientArray.push(selectedColor)
}
else {
gradientArray.push(backgroundColor)
}
}
return gradientArray
})

function sliderToBinValue(sliderValue) {
// This converts from sliderRange coordinates to scaleRange coordinates
if (props.bins.length >= sliderValue) {
return props.bins[sliderValue]
}
}

function labelSliderToScaleValue(sliderValue) {
// This returns the current scaleRange value given a sliderValue. It doesn't convert and show the sliderValue
// so that we can correctly show what the scaleRange is if it is modified in the number fields.
if (sliderValue == sliderRange.value[0]) {
return scaleRange.value[0]
}
return scaleRange.value[1]
}

function scaleToSliderValue(scaleValue) {
// This converts from scaleRange to the nearest sliderRange value, which allows us to map from scale space to
// slider space when the user makes a change to the scaleRange directly with the number fields
if (Math.abs(scaleValue - props.zMin) > Math.abs(scaleValue - props.zMax)) {
// Closer to the zMax, so iterate in reverse to find a match sooner
let previousDistance = Math.abs(scaleValue - props.bins[props.bins.length-1])
for (let i = props.bins.length-2; i >= 0; i--) {
let distance = Math.abs(scaleValue - props.bins[i])
if (distance > previousDistance) {
return i + 1
}
else {
previousDistance = distance
}
}
return 0
}
else {
// Closer to zMin, so iterate in the natural order
let previousDistance = Math.abs(scaleValue - props.bins[0])
for (let i = 1; i < props.bins.length; i++) {
let distance = Math.abs(scaleValue - props.bins[i])
if (distance > previousDistance) {
return i - 1
}
else {
previousDistance = distance
}
}
return props.bins.length - 1
}
}

function updateScaleRange() {
// This is called when the range slider control is moved
scaleRange.value[0] = sliderToBinValue(sliderRange.value[0])
scaleRange.value[1] = sliderToBinValue(sliderRange.value[1])
emit('updateScaling', scaleRange.value[0], scaleRange.value[1])
}

function updateLowerScale(value) {
// This is called when a change is made on the lower point number control
scaleRange.value = [Number(value), scaleRange.value[1]]
sliderRange.value[0] = scaleToSliderValue(Number(value))
emit('updateScaling', scaleRange.value[0], scaleRange.value[1])
}

function updateUpperScale(value) {
// This is called when a change is made on the upper point number control
scaleRange.value = [scaleRange.value[0], Number(value)]
sliderRange.value[1] = scaleToSliderValue(Number(value))
emit('updateScaling', scaleRange.value[0], scaleRange.value[1])
}

function zScaleImage() {
// This is called to reset the ranges to the zMin/zMax
scaleRange.value = [props.zMin, props.zMax]
sliderRange.value = [scaleToSliderValue(props.zMin), scaleToSliderValue(props.zMax)]
emit('updateScaling', scaleRange.value[0], scaleRange.value[1])
}

watch(
() => props.zMax, () => {
zScaleImage()
}
)

</script>
<template>
<v-row class="ml-4">
<v-col cols="4">
<v-text-field
v-model="scaleRange[0]"
:min="0"
label="lower point"
density="compact"
type="number"
variant="outlined"
step="10"
hide-details
@update:modelValue="updateLowerScale">
</v-text-field>
</v-col>
<v-col cols="4">
<v-text-field
v-model="scaleRange[1]"
:max="props.maxValue"
label="upper point"
density="compact"
type="number"
variant="outlined"
step="10"
hide-details
@update:modelValue="updateUpperScale">
</v-text-field>
</v-col>
<v-col cols="4">
<v-btn
class="ml-2"
variant="outlined"
@click="zScaleImage"
>Z-Scale</v-btn>
</v-col>
</v-row>
<v-row class="ma-0">
<v-sheet class="stackSheet">
<v-sparkline
smooth=true
fill=true
line-width="0.1"
height="50"
:gradient="gradient"
gradient-direction='left'
:model-value="props.histogram"
auto-draw
>
</v-sparkline>
<v-range-slider
class="stackControls"
v-model="sliderRange"
step="1"
track-size="0"
:track-color="backgroundColor"
:track-fill-color="selectedColor"
:thumb-color="selectedColor"
thumb-size="16"
:max="props.bins.length-1"
thumb-label="always"
strict
hide-details
@update:modelValue="updateScaleRange"
>
<template v-slot:thumb-label="{ modelValue }">
{{ labelSliderToScaleValue(modelValue) }}
</template>
</v-range-slider>
</v-sheet>
</v-row>
</template>
<style scoped>

.stackSheet {
position: relative;
width: 500px;
margin-left: 10px;
}

.stackControls {
position: absolute;
bottom: -6px;
left: 5px;
width: 473px;
}

</style>
Loading