Skip to content

Commit

Permalink
Feature/keyboard (#389)
Browse files Browse the repository at this point in the history
* feat: exit by esc

* feat: turn page by keyboard

* fix: delete experiment

* restore create_experiment.py

* Update create_experiment.py

* Update create_experiment.py

* feat: click to switch images

* fix: same file name

* feat: turn page by arrow in audio chart modal

---------

Co-authored-by: KAAANG <[email protected]>
  • Loading branch information
Feudalman and SAKURA-CAT authored Mar 4, 2024
1 parent f0f52d8 commit 26ce19c
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 35 deletions.
9 changes: 8 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
"todo-tree.general.tags": [
"TODO", // 待办
"FIXME", // 待修复
"COMPAT" // 兼容性问题
"COMPAT", // 兼容性问题
"WARNING" // 警告
],
"todo-tree.highlights.customHighlight": {
"TODO": {
Expand All @@ -65,6 +66,12 @@
"foreground": "#ffff00",
"iconColour": "#ffff"
},
"WARNING": {
"icon": "alert",
"type": "tag",
"foreground": "#ff0000",
"iconColour": "#ff0000"
},
"FIXME": {
"icon": "flame",
"type": "tag",
Expand Down
7 changes: 5 additions & 2 deletions swanlab/server/controller/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,16 +560,19 @@ def delete_experiment(experiment_id: int):
tag_names = [tag["name"] for tag in __to_list(tags)]
# 检查多实验图表是否有需要删除的
project_id = Experiment.get_by_id(experiment_id).project_id.id
# 找到属于多实验且与该实验相关的图表
charts = Chart.filter(Chart.project_id == project_id, Chart.name.in_(tag_names))

db = connect()
with db.atomic():
# 必须先清除数据库中的实验数据
Experiment.delete().where(Experiment.id == experiment_id).execute()
# 图表无 source 的需要删除
del_list = []
for chart in charts:
if len(chart.sources) == 0:
chart.delete().execute()
if chart.sources.count() == 0:
del_list.append(chart.id)
Chart.delete().where(Chart.id.in_(del_list)).execute()
db.commit()

return SUCCESS_200({"experiment_id": experiment_id})
Expand Down
68 changes: 58 additions & 10 deletions test/create_experiment.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,67 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
r"""
@DATE: 2024-03-04 13:26:59
@File: test/create_experiment.py
@IDE: vscode
@Description:
创建一个文件,作为测试用例
WARNING 请勿随意修改此文件,以免影响测试效果
"""
import swanlab
import time
import random
import numpy as np

epochs = 50
lr = 0.01
offset = random.random() / 5

run = swanlab.init(
experiment_name="Example",
description="这是一个机器学习模拟实验",
# 初始化
swanlab.init(
log_level="debug",
config={
"learning_rate": 0.01,
"epochs": 20,
"epochs": epochs,
"learning_rate": lr,
"test": 1,
"debug": "这是一串" + "很长" * 100 + "的字符串",
"verbose": 1,
},
logggings=True,
)

# 模拟机器学习训练过程
for epoch in range(2, run.config.epochs):
# 模拟训练
for epoch in range(2, epochs):
acc = 1 - 2**-epoch - random.random() / epoch - offset
loss = 2**-epoch + random.random() / epoch + offset
swanlab.log({"loss": loss, "accuracy": acc})
loss2 = 3**-epoch + random.random() / epoch + offset * 3
print(f"epoch={epoch}, accuracy={acc}, loss={loss}")
if epoch % 10 == 0:
# 测试audio
sample_rate = 44100
test_audio_arr = np.random.randn(2, 100000)
swanlab.log(
{
"test/audio": [swanlab.Audio(test_audio_arr, sample_rate, caption="test")] * (epoch // 10),
},
step=epoch,
)
# 测试image
test_image = np.random.randint(0, 255, (100, 100, 3))
swanlab.log(
{
"test/image": [swanlab.Image(test_image, caption="test")] * (epoch // 10),
},
step=epoch,
)
# 测试text
swanlab.log(
{
"text": swanlab.Text("这是一段测试文本", caption="test"),
},
step=epoch,
)
# 测试折线图
swanlab.log({"t/accuracy": acc, "loss": loss, "loss2": loss2})
else:
# 测试折线图
swanlab.log({"t/accuracy": acc, "loss": loss, "loss2": loss2})
time.sleep(0.5)
28 changes: 27 additions & 1 deletion vue/src/charts/components/SlideBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
* @file: SlideBar.vue
* @since: 2024-01-30 16:18:31
**/
import { computed, ref } from 'vue'
import { computed, ref, onUnmounted } from 'vue'
const props = defineProps({
max: {
Expand All @@ -45,6 +45,11 @@ const props = defineProps({
reference: {
type: String,
default: 'Step'
},
// 通过方向键切换
turnByArrow: {
type: Boolean,
default: false
}
})
Expand Down Expand Up @@ -94,6 +99,27 @@ const handleChange = (e) => {
if (e.target.value == _modelValue.value) return
_modelValue.value = e.target.value
}
// ---------------------------------- 键盘左右切换 ----------------------------------
const handleKeydown = (e) => {
if (!props.turnByArrow) return
if (e.key == 'ArrowLeft') {
handleClickDown()
} else if (e.key == 'ArrowRight') {
handleClickUp()
}
}
if (props.turnByArrow) {
document.addEventListener('keydown', handleKeydown)
}
onUnmounted(() => {
if (props.turnByArrow) {
document.removeEventListener('keydown', handleKeydown)
}
})
</script>

<style lang="scss" scoped>
Expand Down
8 changes: 6 additions & 2 deletions vue/src/charts/modules/TextDetail.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="px-6 py-10 overflow-hidden bg-higher rounded-lg">
<div class="text-detail">
<!-- infos -->
<div v-for="item in info_list" :key="item" class="sm:flex pb-3">
<span class="block w-48 font-semibold shrink-0">{{ $t(`common.chart.charts.text.titles.${item.key}`) }}:</span>
Expand Down Expand Up @@ -55,4 +55,8 @@ const info_list = computed(() => {
})
</script>

<style lang="scss" scoped></style>
<style lang="scss" scoped>
.text-detail {
@apply px-6 py-10 overflow-hidden bg-higher rounded-lg;
}
</style>
17 changes: 14 additions & 3 deletions vue/src/charts/modules/TextModule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@
@turn="clickToTurn"
:key="pages.maxIndex"
v-if="data.list.length > 1"
:turn-by-arrow="modal && !isZoom"
/>
<!-- 数据详情 -->
<SLModal max-w="1200" v-model="isZoom">
<SLModal max-w="1200" v-model="isZoom" esc-exit>
<TextDetail :data="current" />
</SLModal>
</div>
Expand All @@ -58,7 +59,7 @@
* @file: TextModule.vue
* @since: 2024-02-20 20:06:45
**/
import { ref, inject, computed } from 'vue'
import { ref, inject, computed, watch } from 'vue'
import SLModal from '@swanlab-vue/components/SLModal.vue'
import TextDetail from './TextDetail.vue'
import SlideBar from '../components/SlideBar.vue'
Expand All @@ -78,10 +79,13 @@ const props = defineProps({
},
modal: {
type: Boolean
},
modelValue: {
type: Boolean
}
})
const emits = defineEmits(['getText'])
const emits = defineEmits(['getText', 'update:modelValue'])
const color = inject('colors')[0]
const skeleton = ref(false)
Expand Down Expand Up @@ -217,6 +221,13 @@ const zoom = (text, i) => {
}
isZoom.value = true
}
watch(
() => isZoom.value,
(v) => {
emits('update:modelValue', v)
}
)
</script>
<style lang="scss" scoped>
Expand Down
3 changes: 2 additions & 1 deletion vue/src/charts/package/AudioChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
/>
</div>
<!-- 放大效果弹窗 -->
<SLModal class="p-10 pt-0 overflow-hidden" max-w="-1" v-model="isZoom">
<SLModal class="p-10 pt-0 overflow-hidden" max-w="-1" v-model="isZoom" esc-exit>
<p class="text-center mt-4 mb-10 text-2xl font-semibold">{{ title }}</p>
<div class="audio-content" ref="audioContentRef">
<AudioModule :audios="audioData" :key="nowStep" v-if="audioData && !loading" />
Expand All @@ -49,6 +49,7 @@
:key="slideKey"
@turn="handelTurn"
v-if="maxIndex !== minIndex"
:turn-by-arrow="isZoom"
/>
</div>
</SLModal>
Expand Down
82 changes: 72 additions & 10 deletions vue/src/charts/package/ImageChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
:key="index"
>
<div class="image-container">
<img :src="imagesData[s.filename].url" @click="handelClickZoom(s.filename)" />
<img :src="imagesData[s.filename].url" @click="handelClickZoom(s.filename, index)" />
<DownloadButton class="download-button" @click.stop="download(s.filename)" />
</div>
<p class="text-xs">{{ s.caption }}</p>
Expand All @@ -44,7 +44,7 @@
/>
</div>
<!-- 放大效果弹窗 -->
<SLModal class="p-10 pt-0 overflow-hidden" max-w="-1" v-model="isZoom">
<SLModal class="p-10 pt-0 overflow-hidden" max-w="-1" v-model="isZoom" @onExit="exitByEsc">
<p class="text-center mt-4 mb-10 text-2xl font-semibold">{{ title }}</p>
<div class="image-content image-content-zoom">
<div class="flex flex-col justify-center items-center h-full" v-if="loading">
Expand All @@ -58,7 +58,7 @@
:key="index"
>
<div class="image-container">
<img :src="imagesData[s.filename].url" @click="handelClickZoom(s.filename)" />
<img :src="imagesData[s.filename].url" @click="handelClickZoom(s.filename, index)" />
<DownloadButton class="download-button" @click.stop="download(s.filename)" />
</div>
<p class="text-xs mt-2">{{ s.caption }}</p>
Expand All @@ -75,22 +75,37 @@
:key="slideKey"
@turn="handelTurn"
v-if="maxIndex !== minIndex"
:turn-by-arrow="isZoom && !isSingleZoom"
/>
</div>
</SLModal>
<!-- 额外的放大功能,点击某个图像,放大显示 -->
<SLModal
class="w-full flex justify-center min-h-[calc(100vh-8rem)] p-14"
class="w-full flex justify-center min-h-[calc(100vh-8rem)] p-14 relative"
max-w="-1"
v-model="isSingleZoom"
close-on-overlay-click
@onExit="exitByEsc"
>
<!-- 标题 -->
<p class="w-full overflow-hidden text-center text-lg font-semibold absolute top-5">
{{ signleZoomFilename }}
</p>
<!-- 图片 -->
<div class="image-single-zoom">
<div class="relative">
<img :src="imagesData[signleZoomFilename].url" class="object-contain" />
<!-- 上一张图片 -->
<SLIcon icon="down" class="icon rotate-90" @click="handleSingleChange({ key: 'ArrowLeft' })"></SLIcon>
<!-- 当前图片 -->
<div class="relative mx-5 select-none">
<img :src="imagesData[signleZoomFilename].url" class="object-contain w-full" />
<DownloadButton class="download-button" @click.stop="download(signleZoomFilename)" />
</div>
<!-- 下一张图片 -->
<SLIcon icon="down" class="icon -rotate-90" @click="handleSingleChange({ key: 'ArrowRight' })"></SLIcon>
</div>
<p class="w-full text-center absolute bottom-5 select-none">
{{ `${currentSingleImageIndex + 1} / ${stepsData[currentIndex][source[0]].length}` }}
</p>
</SLModal>
</template>
</template>
Expand Down Expand Up @@ -293,14 +308,53 @@ const zoom = () => {
// ---------------------------------- 点击某个图像,放大 ----------------------------------
const isSingleZoom = ref(false)
const signleZoomFilename = ref()
// 当前单个图像的索引
const currentSingleImageIndex = ref(0)
// 点击某个图像,放大
const handelClickZoom = (filename) => {
const handelClickZoom = (filename, index) => {
signleZoomFilename.value = filename
isSingleZoom.value = true
// console.log('filename', filename)
// console.log('image data', imagesData[filename])
currentSingleImageIndex.value = index
}
// ---------------------------------- ESC 退出弹窗 ----------------------------------
// 通过 esc 按键关闭弹窗
const exitByEsc = () => {
// 如果两个弹窗都有,只关闭单图弹窗
if (isZoom.value && isSingleZoom.value) return (isSingleZoom.value = false)
// 剩下的情况都是只有一个弹窗,全部关闭
isZoom.value = false
isSingleZoom.value = false
}
// ---------------------------------- 左右方向键翻页 ----------------------------------
// 方向键切换单图 - 回调
const handleSingleChange = ({ key }) => {
const images = stepsData[currentIndex.value][source.value[0]]
if (key === 'ArrowRight' && currentSingleImageIndex.value < images.length - 1) {
currentSingleImageIndex.value++
} else if (key === 'ArrowLeft' && currentSingleImageIndex.value > 0) {
currentSingleImageIndex.value--
}
signleZoomFilename.value = images[currentSingleImageIndex.value].filename
}
// 订阅通过左右方向键切换单图
watch(
() => isSingleZoom.value,
(newVal) => {
if (newVal) {
window.addEventListener('keyup', handleSingleChange)
} else {
window.removeEventListener('keyup', handleSingleChange)
}
}
)
// ---------------------------------- 点击下载按钮下载 ----------------------------------
const download = (filename) => {
Expand Down Expand Up @@ -359,9 +413,17 @@ defineExpose({
}
.image-single-zoom {
@apply flex items-center;
@apply flex items-center justify-between w-full;
&:hover .download-button {
@apply block;
}
.icon {
@apply w-10 h-10 cursor-pointer border rounded-full opacity-20 transition-all;
&:hover {
@apply opacity-100;
}
}
}
</style>
Loading

0 comments on commit 26ce19c

Please sign in to comment.