diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.vue | 2 | ||||
| -rw-r--r-- | src/components/TimeLine.vue | 92 |
2 files changed, 81 insertions, 13 deletions
diff --git a/src/App.vue b/src/App.vue index 17760b0..1a8608b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -191,7 +191,7 @@ function reduceEvents(){ exhaustedRanges.clear(); } // For getting events from server -const EVENT_REQ_LIMIT = 10; +const EVENT_REQ_LIMIT = 30; let pendingReq = false; // Used to serialise event-req handling async function onEventReq(startDate: HistDate, endDate: HistDate){ while (pendingReq){ diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue index 88ac62d..c581634 100644 --- a/src/components/TimeLine.vue +++ b/src/components/TimeLine.vue @@ -5,8 +5,6 @@ @wheel.exact.prevent="onWheel" @wheel.shift.exact.prevent="onShiftWheel" ref="rootRef"> <svg :viewBox="`0 0 ${width} ${height}`"> - <!-- Main line (unit horizontal line that gets transformed, with extra length to avoid gaps when panning) --> - <line :stroke="store.color.alt" stroke-width="2px" x1="-1" y1="0" x2="2" y2="0" :style="mainlineStyles"/> <!-- Tick markers --> <template v-for="date, idx in ticks.dates" :key="date.toInt()"> <line v-if="date.equals(MIN_DATE, scale) || date.equals(MAX_DATE, scale)" @@ -20,16 +18,22 @@ :stroke="store.color.alt" stroke-width="1px" :style="tickStyles(idx)" class="animate-fadein"/> </template> + <!-- Event lines --> + <line v-for="id in eventLines.keys()" :key="id" + x1="0" y1="0" x2="1" y2="0" :stroke="store.color.altDark2" stroke-width="1px" + :style="eventLineStyles(id)" class="animate-fadein"/> <!-- Tick labels --> <text v-for="date, idx in ticks.dates" :key="date.toInt()" x="0" y="0" :text-anchor="vert ? 'start' : 'middle'" dominant-baseline="middle" :fill="store.color.textDark" :style="tickLabelStyles(idx)" class="text-sm animate-fadein"> {{date}} </text> + <!-- Main line (unit horizontal line that gets transformed, with extra length to avoid gaps when panning) --> + <line :stroke="store.color.alt" stroke-width="2px" x1="-1" y1="0" x2="2" y2="0" :style="mainlineStyles"/> </svg> <!-- Events --> <div v-for="id in idToPos.keys()" :key="id" - class="absolute bg-black text-white border border-white rounded animate-fadein" + class="absolute bg-black text-white rounded animate-fadein text-sm" :style="eventStyles(id)"> {{idToEvent.get(id)!.title}} </div> @@ -55,6 +59,8 @@ import {WRITING_MODE_HORZ, MIN_DATE, MAX_DATE, MONTH_SCALE, DAY_SCALE, SCALES, M import {useStore} from '../store'; import {RBTree} from '../rbtree'; +const SCRIM_GRADIENT = 'linear-gradient(to bottom, rgba(0,0,0,0.4), #0000 40%)'; + // Refs const rootRef: Ref<HTMLElement | null> = ref(null); @@ -73,6 +79,7 @@ const emit = defineEmits(['remove', 'state-chg', 'event-req', 'event-display']); const width = ref(0); const height = ref(0); const availLen = computed(() => props.vert ? height.value : width.value); +const availBreadth = computed(() => props.vert ? width.value : height.value); const prevVert = ref(props.vert); // For skipping transitions on horz/vert swap const mounted = ref(false); onMounted(() => { @@ -273,15 +280,42 @@ const idToPos = computed(() => { return new Map(); } let map: Map<number, [number, number, number, number]> = new Map(); // Maps visible event IDs to x/y/w/h - let numUnits = getNumVisibleUnits(); - let minorAxisStep = 0; + // Do basic grid-like layout + let spacing = 10, sz = 80, midOffset = [10, 40]; + let posX = spacing, posY = spacing; for (let event of idToEvent.value.values()){ - 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; - map.set(event.id, [posX, posY, 100, 100]); - minorAxisStep += 10; + map.set(event.id, [posX, posY, sz, sz]); + if (props.vert){ + posY += sz + spacing; + if (posY + sz + spacing > availLen.value){ // If at end of column + posY = spacing; + posX += sz + spacing; + // Avoid collision with timeline + if (posX <= availBreadth.value / 2 + midOffset[1] && + posX + sz >= availBreadth.value / 2 - midOffset[0]){ + posX = availBreadth.value / 2 + midOffset[1]; + } + // If finished last row + if (posX + sz + spacing > availBreadth.value){ + break; + } + } + } else { + posX += sz + spacing; + if (posX + sz + spacing > availLen.value){ // If at end of row + posX = spacing; + posY += sz + spacing; + // Avoid collision with timeline + if (posY <= availBreadth.value / 2 + midOffset[1] && + posY + sz >= availBreadth.value / 2 - midOffset[0]){ + posY = availBreadth.value / 2 + midOffset[1]; + } + // If finished last column + if (posY + sz + spacing > availBreadth.value){ + break; + } + } + } } // If more events could be displayed, notify parent if (map.size < 3){ @@ -292,6 +326,31 @@ const idToPos = computed(() => { // return map; }); +const eventLines = computed(() => { + let lineCoords: Map<number, [number, number, number, number]> = new Map(); + // Maps event ID to x, y, length, and angle + let numUnits = getNumVisibleUnits(); + for (let [id, [eventX, eventY, eventW, eventH]] of idToPos.value){ + let x = eventX + eventW/2; + let y = eventY + eventH/2; + let x2: number; + let y2: number; + let event = idToEvent.value.get(id)!; + let unitOffset = getUnitDiff(event.start, startDate.value, scale.value) + startOffset.value; + let posFrac = unitOffset / numUnits; + if (props.vert){ + x2 = availBreadth.value / 2; + y2 = posFrac * availLen.value; + } else { + x2 = posFrac * availLen.value; + y2 = availBreadth.value / 2; + } + let l = Math.sqrt((x-x2)**2 + (y-y2)**2); + let a = Math.atan2(y2-y, x2-x) * 180 / Math.PI; + lineCoords.set(id, [x, y, l, a]); + } + return lineCoords; +}); // For panning/zooming function panTimeline(scrollRatio: number){ @@ -748,11 +807,20 @@ function eventStyles(eventId: number){ top: y + 'px', width: w + 'px', height: h + 'px', - backgroundImage: `url(${getImagePath(event.imgId)})`, + backgroundImage: `${SCRIM_GRADIENT},url(${getImagePath(event.imgId)})`, backgroundSize: 'cover', transitionProperty: skipTransition.value ? 'none' : 'all', transitionDuration, transitionTimingFunction, }; } +function eventLineStyles(eventId: number){ + const [x, y, l, a] = eventLines.value.get(eventId)!; + return { + transform: `translate(${x}px, ${y}px) rotate(${a}deg) scaleX(${l})`, + transitionProperty: skipTransition.value ? 'none' : 'transform', + transitionDuration, + transitionTimingFunction, + }; +} </script> |
