Skip to content

Commit

Permalink
Merge pull request #233 from eee555/dev
Browse files Browse the repository at this point in the history
add BBBvSummary (#232)
  • Loading branch information
putianyi889 authored Feb 21, 2025
2 parents 0249d63 + f382948 commit 571ac95
Show file tree
Hide file tree
Showing 16 changed files with 370 additions and 19 deletions.
1 change: 1 addition & 0 deletions front_end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"primeicons": "^7.0.0",
"tinycolor2": "^1.6.0",
"uuid": "^9.0.0",
"validator": "^13.12.0",
"vue": "^3.5.12",
Expand Down
8 changes: 8 additions & 0 deletions front_end/src/components/common/BaseIconSetting.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<i class="pi pi-cog align-middle" style="justify-content: center; display: inline-flex; position: relative;" />
</template>

<script setup lang="ts">
import 'primeicons/primeicons.css'
</script>

Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,21 @@ import { ElText } from 'element-plus';
import BaseCardSmall from '@/components/common/BaseCardSmall.vue';
const prop = defineProps({
date: { type: Date, required: true },
videos: { type: Array<VideoAbstract>, default: [] },
date: { type: Date, required: true },
videos: { type: Array<VideoAbstract>, default: [] }, // 该日期的录像
bmax: { type: Number, default: 5, },
imax: { type: Number, default: 5, },
emax: { type: Number, default: 5, },
xOffset: { type: Number, default: 0 },
yOffset: { type: Number, default: 0 },
emax: { type: Number, default: 5, }, // 三个最大值,用于计算颜色
xOffset: { type: Number, default: 0 }, // 横坐标,单位为格
yOffset: { type: Number, default: 0 }, // 纵坐标,单位为格
})
const count = ref({ b: 0, i: 0, e: 0, });
const red = ref(0);
const green = ref(0);
const blue = ref(0);
watch(() => prop.videos, () => {
function refresh() {
count.value.b = 0;
count.value.i = 0;
count.value.e = 0;
Expand All @@ -61,7 +61,9 @@ watch(() => prop.videos, () => {
red.value = 255 * count.value.b / prop.bmax;
green.value = 255 * count.value.i / prop.imax;
blue.value = 255 * count.value.e / prop.emax;
}, { immediate: true });
}
watch(() => prop.videos, refresh, { immediate: true });
const size = computed(() => activityCalendarConfig.value.cellSize + 'px');
const borderRadius = computed(() => activityCalendarConfig.value.cornerRadius + '%');
Expand Down
43 changes: 43 additions & 0 deletions front_end/src/components/visualization/BBBvSummary/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<Header v-if="header" />
<el-row>
<YLabel :minBv="minBv" :maxBv="maxBv" />
<span :style="{ position: 'relative', height: (maxBv - minBv + 1) / 10 * BBBvSummaryConfig.cellHeight + 'px', flex: '1' }">
<Cell v-for="bv in range(minBv, maxBv)" :bv="bv" :level="level" :videos="groupedVideoAbstract.get(bv)"
:x-offset="getLastDigit(bv)" :y-offset="Math.floor((bv - minBv) / 10)" :color-theme="theme" />
</span>
</el-row>
</template>

<script setup lang="ts">
import BaseCardNormal from '@/components/common/BaseCardNormal.vue';
import { BBBvSummaryConfig, colorTheme, store } from '@/store';
import { ElRow } from 'element-plus';
import { maximum, minimum, range } from '@/utils/arrays';
import { getLastDigit, setLastDigit } from '@/utils/math';
import { MS_Level } from '@/utils/ms_const';
import { groupVideosByBBBv } from '@/utils/videoabstract';
import { computed, PropType } from 'vue';
import Header from './Header.vue';
import Cell from './Cell.vue';
import YLabel from './YLabel.vue';
import { PiecewiseColorScheme } from '@/utils/colors';
const prop = defineProps({
header: { type: Boolean, default: false },
level: { type: String as PropType<MS_Level>, required: true },
})
const groupedVideoAbstract = computed(() => groupVideosByBBBv(store.player.videos, prop.level));
const maxBv = computed(() => setLastDigit(maximum(groupedVideoAbstract.value.keys()), 9));
const minBv = computed(() => setLastDigit(minimum(groupedVideoAbstract.value.keys()), 0));
const theme = computed(() => {
if (prop.level == 'b') return new PiecewiseColorScheme(colorTheme.value.btime.colors, colorTheme.value.btime.thresholds);
else if (prop.level == 'i') return new PiecewiseColorScheme(colorTheme.value.itime.colors, colorTheme.value.itime.thresholds);
else if (prop.level == 'e') return new PiecewiseColorScheme(colorTheme.value.etime.colors, colorTheme.value.etime.thresholds);
else return new PiecewiseColorScheme([], []);
})
</script>
92 changes: 92 additions & 0 deletions front_end/src/components/visualization/BBBvSummary/Cell.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<template>
<tippy class="cell" :duration="0" sticky>
<span v-if="bestIndex == -1">&nbsp;</span>
<el-link v-else :underline="false" @click="preview(videos[bestIndex].id)">
{{ videos[bestIndex].getStat(displayBy) }}
</el-link>
<template #content>
test
</template>
</tippy>
</template>

<script setup lang="ts">
import { BBBvSummaryConfig, colorTheme } from '@/store';
import { VideoAbstract, getStat_stat } from '@/utils/videoabstract';
import { computed, PropType, ref, watch } from 'vue';
import { Tippy } from 'vue-tippy';
import { ElLink } from 'element-plus';
import { MS_Level } from '@/utils/ms_const';
import { PiecewiseColorScheme } from '@/utils/colors';
import tinycolor from 'tinycolor2';
import { preview } from '@/utils/common/PlayerDialog';
const bestValue = ref<number|null>(null);
const bestIndex = ref(-1);
const prop = defineProps({
level: { type: String as PropType<MS_Level>, required: true },
bv: { type: Number, required: true },
videos: { type: Array<VideoAbstract>, default: [] },
xOffset: { type: Number, default: 0 },
yOffset: { type: Number, default: 0 },
sortBy: { type: String as PropType<getStat_stat>, default: 'timems' },
sortDesc: { type: Boolean, default: false },
displayBy: { type: String as PropType<getStat_stat>, default: 'time' },
colorTheme: { type: Object as PropType<PiecewiseColorScheme>, default: new PiecewiseColorScheme([],[])}
})
function refresh() {
bestValue.value = null;
bestIndex.value = -1;
prop.videos.forEach((video, index) => {
const thisValue = video.getStat(prop.sortBy);
if (
bestValue.value === null ||
thisValue > bestValue.value && prop.sortDesc ||
thisValue < bestValue.value && !prop.sortDesc
) {
bestValue.value = thisValue;
bestIndex.value = index;
}
});
}
watch(() => prop.videos, refresh, { immediate: true });
const height = computed(() => BBBvSummaryConfig.value.cellHeight + 'px');
const borderRadius = computed(() => BBBvSummaryConfig.value.cornerRadius + '%');
const top = computed(() => prop.yOffset*BBBvSummaryConfig.value.cellHeight + 'px');
const left = computed(() => prop.xOffset * 10 + '%');
const color = computed(() => {
if (bestIndex.value === -1) return 'rgba(0,0,0,0)';
return prop.colorTheme.getColor(prop.videos[bestIndex.value].getStat(prop.displayBy))
})
const fontColor = computed(() => tinycolor(color.value).isDark() ? 'white' : 'black');
</script>

<style lang="less" scoped>
.cell {
position: absolute;
top: v-bind(top);
left: v-bind(left);
width: 10%;
height: v-bind(height);
border-radius: 1px;
background-color: v-bind(color);
border-style: solid;
border-width: 1px;
text-align: center;
align-items: center;
}
.el-link {
--el-link-text-color: v-bind(fontColor);
--el-link-font-size: 16px;
}
</style>
19 changes: 19 additions & 0 deletions front_end/src/components/visualization/BBBvSummary/Header.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<el-row :style="{textAlign: 'center', height: BBBvSummaryConfig.cellHeight + 'px'}">
<IconSetting style="width: 10%; min-width: 75px"><Setting/></IconSetting>
<span v-for="i in 10" style="flex: 1; min-width: 3em">{{ i-1 }}</span>
</el-row>
</template>

<script setup lang="ts">
import { ElRow } from 'element-plus';
import IconSetting from '@/components/widgets/IconSetting.vue';
import Setting from './Setting.vue';
import { BBBvSummaryConfig } from '@/store';
const prop = defineProps({
})
</script>
10 changes: 10 additions & 0 deletions front_end/src/components/visualization/BBBvSummary/Setting.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<el-text>行高</el-text>
<el-input-number v-model="BBBvSummaryConfig.cellHeight" :min="10" :max="30" size="small" controls-position="right" />
</template>

<script setup lang="ts">
import { BBBvSummaryConfig } from '@/store';
import { ElText, ElInputNumber } from 'element-plus';
</script>
17 changes: 17 additions & 0 deletions front_end/src/components/visualization/BBBvSummary/YLabel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<div style="width: 10%; min-width: 75px; text-align: center">
<div v-for="i of range(minBv, maxBv, 10)" :style="{width: '100%', height:BBBvSummaryConfig.cellHeight+'px'}">{{ i }}-{{ i+9 }}</div>
</div>
</template>

<script setup lang="ts">
import { BBBvSummaryConfig } from '@/store';
import { range } from '@/utils/arrays';
const prop = defineProps({
minBv: { type: Number, required: true },
maxBv: { type: Number, required: true },
})
</script>
11 changes: 6 additions & 5 deletions front_end/src/components/widgets/IconSetting.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<template>
<tippy interactive animate-fill arrow trigger="click">
<tippy interactive animate-fill arrow trigger="click" :append-to="body">
<el-link :underline="false">
<el-icon>
<Setting />
</el-icon>
<base-icon-setting />
</el-link>
<template #content>
<base-card-normal style="width: auto;">
Expand All @@ -15,6 +13,9 @@

<script setup lang="ts">
import { Tippy } from 'vue-tippy';
import { ElLink, ElIcon } from 'element-plus';
import { ElLink } from 'element-plus';
import BaseCardNormal from '../common/BaseCardNormal.vue';
import BaseIconSetting from '../common/BaseIconSetting.vue';
const body = document.body
</script>
4 changes: 4 additions & 0 deletions front_end/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ export const activityCalendarConfig = useLocalStorage('activity-calendar-config'
cellMargin: 3,
cornerRadius: 20,
showDate: false,
})

export const BBBvSummaryConfig = useLocalStorage('bbbv-summary-config', {
cellHeight: 20,
})
101 changes: 101 additions & 0 deletions front_end/src/utils/arrays.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
// Credit: ChatGPT
/**
* 生成一个 range
* @param start
* @param end
* @param step
* @returns start:step:end
*/
export function range(start: number, end: number, step: number = 1): number[] {
if (step <= 0) {
throw new Error("Step must be greater than 0.");
Expand All @@ -8,4 +15,98 @@ export function range(start: number, end: number, step: number = 1): number[] {
result.push(i);
}
return result;
}

// Credit: DeepSeek
/**
* 接收一个数组或者迭代器,返回最大值
* @param iter
* @returns
*/
export function maximum(iter: number[] | MapIterator<number>): number {
const arr = Array.isArray(iter) ? iter : Array.from(iter);

if (arr.length === 0) {
throw new Error("Array is empty");
}

let max = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}

return max;
}

/**
* 接收一个数组或者迭代器,返回最小值
* @param iter
* @returns
*/
export function minimum(iter: number[] | MapIterator<number>): number {
const arr = Array.isArray(iter) ? iter : Array.from(iter);

if (arr.length === 0) {
throw new Error("Array is empty");
}

let min = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
}

return min;
}

// Credit: ChatGPT
export function getInsertIndex(sortedArray: number[], value: number, isAscending: boolean): number {
// Determine the insertion point using binary search
let low = 0;
let high = sortedArray.length;

while (low < high) {
const mid = Math.floor((low + high) / 2);

if (isAscending) {
if (sortedArray[mid] < value) low = mid + 1;
else high = mid;
} else {
if (sortedArray[mid] > value) low = mid + 1;
else high = mid;
}
}

return low;
}

/**
* 判断数组是否是升序的
* @param arr
* @returns
*/
export function isAscending(arr: number[]): boolean {
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) {
return false;
}
}
return true;
}

/**
* 判断数组是否是降序的
* @param arr
* @returns
*/
export function isDescending(arr: number[]): boolean {
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] < arr[i + 1]) {
return false;
}
}
return true;
}
Loading

0 comments on commit 571ac95

Please sign in to comment.