diff options
| -rw-r--r-- | src/App.vue | 12 | ||||
| -rw-r--r-- | src/components/TimeLine.vue | 37 | ||||
| -rw-r--r-- | src/lib.ts | 44 |
3 files changed, 59 insertions, 34 deletions
diff --git a/src/App.vue b/src/App.vue index 9ac8e3f..abbe2b4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -19,7 +19,7 @@ <div class="grow min-h-0 flex" :class="{'flex-col': !vert}" :style="{backgroundColor: store.color.bg}" ref="contentAreaRef"> <time-line v-for="(state, idx) in timelines" :key="state.id" - :vert="vert" :initialState="state" :eventMap="eventMap" + :vert="vert" :initialState="state" :eventTree="eventTree" class="grow basis-full min-h-0 outline outline-1" @remove="onTimelineRemove(idx)" @state-chg="onTimelineChg($event, idx)" @event-req="onEventReq"/> <base-line :vert="vert" :timelines="timelines"/> @@ -38,8 +38,9 @@ import PlusIcon from './components/icon/PlusIcon.vue'; import SettingsIcon from './components/icon/SettingsIcon.vue'; import HelpIcon from './components/icon/HelpIcon.vue'; // Other -import {HistDate, TimelineState, HistEvent, queryServer, HistEventJson, jsonToHistEvent} from './lib'; +import {HistDate, TimelineState, HistEvent, queryServer, HistEventJson, jsonToHistEvent, cmpHistEvent} from './lib'; import {useStore} from './store'; +import {RBTree} from './rbtree'; // Refs const contentAreaRef = ref(null as HTMLElement | null); @@ -99,7 +100,7 @@ function onTimelineRemove(idx: number){ } // Event data -const eventMap: Ref<Map<number, HistEvent>> = ref(new Map()); // Maps event IDs to HistEvents +const eventTree: Ref<RBTree<HistEvent>> = ref(new RBTree(cmpHistEvent)); // Used to look up events by date range async function onEventReq(startDate: HistDate, endDate: HistDate){ // Get events from server let urlParams = new URLSearchParams({type: 'events', range: `${startDate}.${endDate}`, limit: '10'}); @@ -109,9 +110,8 @@ async function onEventReq(startDate: HistDate, endDate: HistDate){ } // Add to map for (let eventObj of responseObj){ - if (!eventMap.value.has(eventObj.id)){ - eventMap.value.set(eventObj.id, jsonToHistEvent(eventObj)); - } + let event = jsonToHistEvent(eventObj); + eventTree.value.insert(event); } } diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue index da2c571..3a5de5b 100644 --- a/src/components/TimeLine.vue +++ b/src/components/TimeLine.vue @@ -28,10 +28,10 @@ </text> </svg> <!-- Events --> - <div v-for="id in eventIdToPos.keys()" :key="id" + <div v-for="id in idToEvent.keys()" :key="id" class="absolute bg-black text-white border border-white rounded animate-fadein" :style="eventStyles(id)"> - {{eventMap.get(id)!.title}} + {{idToEvent.get(id)!.event.title}} </div> <!-- Buttons --> <icon-button :size="30" class="absolute top-2 right-2" @@ -53,6 +53,7 @@ import {WRITING_MODE_HORZ, MIN_DATE, MAX_DATE, MONTH_SCALE, DAY_SCALE, SCALES, M HistDate, stepDate, inDateScale, getScaleRatio, getUnitDiff, getDaysInMonth, moduloPositive, TimelineState, HistEvent, getImagePath} from '../lib'; import {useStore} from '../store'; +import {RBTree} from '../rbtree'; // Refs const rootRef: Ref<HTMLElement | null> = ref(null); @@ -64,7 +65,7 @@ const store = useStore(); const props = defineProps({ vert: {type: Boolean, required: true}, initialState: {type: Object as PropType<TimelineState>, required: true}, - eventMap: {type: Object as PropType<Map<number, HistEvent>>, required: true}, + eventTree: {type: Object as PropType<RBTree<HistEvent>>, required: true}, }); const emit = defineEmits(['remove', 'state-chg', 'event-req']); @@ -253,20 +254,23 @@ const ticks = computed((): Ticks => { }); // For displayed events -const eventIdToPos = computed(() => { // Maps visible event IDs to x-pos, y-pos, width, and height - let idToPos: Map<number, [number, number, number, number]> = new Map(); - // Find events to display, and do basic layouting +const idToEvent = computed(() => { // Maps visible event IDs to HistEvent, x-pos, y-pos, width, and height + let idToPos: Map<number, {event: HistEvent, x: number, y: number, w: number, h: number}> = new Map(); let numUnits = getNumVisibleUnits(); let minorAxisStep = 0; - for (let event of props.eventMap.values()){ - if (event.start.isEarlier(startDate.value) || endDate.value.isEarlier(event.start)){ - continue; + // Find events to display, and do basic layouting + let iter = props.eventTree.lowerBound(new HistEvent(0, '', startDate.value)); + while (iter.data() != null){ + let event = iter.data(); + iter.next(); + if (endDate.value.isEarlier(event.start)){ + break; } let unitOffset = getUnitDiff(event.start, startDate.value, scale.value) + startOffset.value; let posFrac = unitOffset / numUnits; let posX = props.vert ? minorAxisStep : availLen.value * posFrac; let posY = props.vert ? availLen.value * posFrac : minorAxisStep; - idToPos.set(event.id, [posX, posY, 100, 100]); + idToPos.set(event.id, {event, x: posX, y: posY, w: 100, h: 100}); minorAxisStep += 10; } // If more events could be displayed, notify parent @@ -725,14 +729,13 @@ function tickLabelStyles(idx: number){ } } function eventStyles(eventId: number){ - const [x, y, w, h] = eventIdToPos.value.get(eventId)!; - let event = props.eventMap.get(eventId)!; + const evt = idToEvent.value.get(eventId)!; return { - left: x + 'px', - top: y + 'px', - width: w + 'px', - height: h + 'px', - backgroundImage: `url(${getImagePath(event.imgId)})`, + left: evt.x + 'px', + top: evt.y + 'px', + width: evt.w + 'px', + height: evt.h + 'px', + backgroundImage: `url(${getImagePath(evt.event.imgId)})`, backgroundSize: 'cover', transitionProperty: skipTransition.value ? 'none' : 'all', transitionDuration, @@ -299,17 +299,39 @@ export class TimelineState { } } -export type HistEvent = { - id: number, - title: string, - start: HistDate, - startUpper: HistDate | null, - end: HistDate | null, - endUpper: HistDate | null, - ctg: string, - imgId: number, - pop: number, -}; +export class HistEvent { + id: number; + title: string; + start: HistDate; + startUpper: HistDate | null; + end: HistDate | null; + endUpper: HistDate | null; + ctg: string; + imgId: number; + pop: number; + constructor( + id: number, title: string, start: HistDate, startUpper: HistDate | null = null, + end: HistDate | null = null, endUpper: HistDate | null = null, ctg='', imgId=0, pop=0){ + this.id = id; + this.title = title; + this.start = start; + this.startUpper = startUpper; + this.end = end; + this.endUpper = endUpper; + this.ctg = ctg; + this.imgId = imgId; + this.pop = pop; + } +} +export function cmpHistEvent(event: HistEvent, event2: HistEvent){ + if (event.start.isEarlier(event2.start)){ + return -1; + } else if (!event.start.equals(event2.start)){ + return 1; + } else { + return event.id - event2.id; + } +} // For server requests const SERVER_DATA_URL = (new URL(window.location.href)).origin + '/data/' |
