diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/TimeLine.vue | 189 |
1 files changed, 107 insertions, 82 deletions
diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue index 23f0c4c..7b89194 100644 --- a/src/components/TimeLine.vue +++ b/src/components/TimeLine.vue @@ -70,8 +70,8 @@ import IconButton from './IconButton.vue'; // Icons import CloseIcon from './icon/CloseIcon.vue'; // Other -import {WRITING_MODE_HORZ, MIN_DATE, MAX_DATE, MONTH_SCALE, DAY_SCALE, SCALES, MIN_CAL_YEAR, - getDaysInMonth, HistDate, CalDate, stepDate, getScaleRatio, getNumSubUnits, getUnitDiff, +import {WRITING_MODE_HORZ, MIN_DATE, MAX_DATE, MONTH_SCALE, DAY_SCALE, SCALES, MIN_CAL_DATE, + getDaysInMonth, HistDate, stepDate, getScaleRatio, getNumSubUnits, getUnitDiff, getEventPrecision, dateToUnit, moduloPositive, TimelineState, HistEvent, getImagePath} from '../lib'; import {useStore} from '../store'; @@ -142,7 +142,6 @@ const mainlineOffset = computed(() => { // Distance from mainline-area line to l // Timeline data const ID = props.initialState.id as number; -const MIN_CAL_DATE = new CalDate(MIN_CAL_YEAR, 1, 1); const startDate = ref(props.initialState.startDate); // Earliest date in scale to display const endDate = ref(props.initialState.endDate); // Latest date in scale to display (may equal startDate) const startOffset = ref(store.defaultEndTickOffset); // Fraction of a scale unit before startDate to show @@ -355,35 +354,65 @@ const ticks = computed((): Tick[] => { ticks = [...ticksBefore, ...ticks, ...ticksAfter]; return ticks; }); -const firstIdx = computed((): number => { // Index of first visible tick - return ticks.value.findIndex((tick: Tick) => tick.offset >= 0); +const firstIdx = computed((): number => { // Index of first major tick after which events are visible (-1 if none) + // Looks for a first visible major tick, and uses a preceding tick if present + let idx = -1; + for (let i = 0; i < ticks.value.length; i++){ + let tick = ticks.value[i]; + if (tick.major){ + if (tick.offset >= 0){ + return (idx < 0) ? i : idx; + } else { + idx = i; + } + } + } + return idx; }); -const firstDate = computed(() => // Date of first visible tick - firstIdx.value < 0 ? startDate.value : ticks.value[firstIdx.value]!.date); -const firstOffset = computed(() => // Offset of first visible tick - firstIdx.value < 0 ? startOffset.value : ticks.value[firstIdx.value]!.offset); -const lastIdx = computed((): number => { +const firstDate = computed(() => firstIdx.value < 0 ? startDate.value : ticks.value[firstIdx.value]!.date); +const lastIdx = computed((): number => { // Index of last major tick before which events are visible (-1 if none) + // Looks for a last visible major tick, and uses a following tick if present let numUnits = getNumDisplayUnits(); + let idx = -1; for (let i = ticks.value.length - 1; i >= 0; i--){ let tick = ticks.value[i]; - if (tick.offset <= numUnits){ - return i; + if (tick.major){ + if (tick.offset <= numUnits){ + return (idx < 0) ? i : idx; + } else { + idx = i; + } } } - return -1; + return idx; }); const lastDate = computed(() => lastIdx.value < 0 ? endDate.value : ticks.value[lastIdx.value]!.date); // For displayed events -function dateToOffset(date: HistDate){ - let offset = 0; - if (firstDate.value == startDate.value){ - offset += getUnitDiff(date, firstDate.value, scale.value); +function dateToOffset(date: HistDate){ // Assumes 'date' is >=firstDate and <=lastDate + // Find containing major tick + let tickIdx = firstIdx.value; + for (let i = tickIdx + 1; i < lastIdx.value; i++){ + if (ticks.value[i].major){ + if (!date.isEarlier(ticks.value[i].date)){ + tickIdx = i; + } else { + break; + } + } + } + // Get offset within unit + const tick = ticks.value[tickIdx]; + if (!hasMinorScale.value){ + if (scale.value == DAY_SCALE){ + return tick.offset; + } else { + const nextScale = SCALES[scaleIdx.value + 1]; + return tick.offset + getUnitDiff(tick.date, date, nextScale) / getNumSubUnits(tick.date, scaleIdx.value); + } } else { - offset += getUnitDiff(date, firstDate.value, minorScale.value) - / getScaleRatio(minorScale.value, scale.value); + return tick.offset + getUnitDiff(tick.date, date, minorScale.value) / getNumSubUnits(tick.date, scaleIdx.value); } - return offset + firstOffset.value; } const idToEvent = computed(() => { // Maps visible event IDs to HistEvents let map: Map<number, HistEvent> = new Map(); @@ -445,10 +474,12 @@ const idToPos = computed(() => { let orderedEvents = [...idToEvent.value.values()]; orderedEvents.sort((x, y) => y.pop - x.pop); let numUnits = getNumDisplayUnits(); + const minOffset = store.spacing; + const maxOffset = availLen.value - eventMajorSz.value - store.spacing; for (let event of orderedEvents){ - // Get preferred pixel offset in column - let unitOffset = dateToOffset(event.start); - let targetOffset = unitOffset / numUnits * availLen.value - eventMajorSz.value / 2; + // Get preferred offset in column + let pxOffset = dateToOffset(event.start) / numUnits * availLen.value - eventMajorSz.value / 2; + let targetOffset = Math.max(Math.min(pxOffset, maxOffset), minOffset); // Find potential positions let positions: [number, number, number][] = []; // For each position, holds a column index, a within-column index to insert at, and an offset value @@ -459,69 +490,67 @@ const idToPos = computed(() => { let bestOffset: number | null = null; // Best offset found so far let bestIdx: number | null = null; // Index of insertion for bestOffset let colMainlineDist = Math.abs(colOffsets[colIdx] - mainlineOffset.value); - // Check for empty column - if (cols[colIdx].length == 0){ - let offset = Math.max(store.spacing, targetOffset) - offset = Math.min(offset, availLen.value - eventMajorSz.value - store.spacing); - positions.push([colIdx, 0, offset]); + if (Math.atan2(Math.abs(pxOffset - targetOffset), colMainlineDist) > MAX_ANGLE){ + // Invalid angle, skip column + } else if (cols[colIdx].length == 0){ // Check for empty column + positions.push([colIdx, 0, targetOffset]); break; - } - // Check placement before first event in column - let offset = cols[colIdx][0][1] - eventMajorSz.value - store.spacing; - if (offset >= store.spacing){ - if (offset >= targetOffset){ - positions.push([colIdx, 0, Math.max(store.spacing, targetOffset)]); - break; - } else { - if (Math.atan2(Math.abs(targetOffset - offset), colMainlineDist) <= MAX_ANGLE){ - bestOffset = offset; - bestIdx = 0; + } else { + // Check placement before first event in column + let offset = cols[colIdx][0][1] - eventMajorSz.value - store.spacing; + if (offset >= minOffset){ + if (offset >= targetOffset){ + positions.push([colIdx, 0, targetOffset]); + break; + } else { + if (Math.atan2(Math.abs(pxOffset - offset), colMainlineDist) <= MAX_ANGLE){ + bestOffset = offset; + bestIdx = 0; + } } } - } - // Check placement after each event element in column - for (let elIdx = 0; elIdx < cols[colIdx].length; elIdx++){ - offset = cols[colIdx][elIdx][1] + eventMajorSz.value + store.spacing; - if (elIdx == cols[colIdx].length - 1){ // If last element in column - if (offset < availLen.value - eventMajorSz.value - store.spacing){ + // Check placement after each event element in column + for (let elIdx = 0; elIdx < cols[colIdx].length; elIdx++){ + offset = cols[colIdx][elIdx][1] + eventMajorSz.value + store.spacing; + if (elIdx == cols[colIdx].length - 1){ // If last element in column + if (offset < maxOffset){ + // Check for better offset + if (bestOffset == null || Math.abs(pxOffset - offset) < Math.abs(pxOffset - bestOffset)){ + if (offset <= targetOffset){ + positions.push([colIdx, elIdx + 1, targetOffset]); + break columnLoop; + } else { + if (Math.atan2(Math.abs(pxOffset - offset), colMainlineDist) <= MAX_ANGLE){ + bestOffset = offset; + bestIdx = elIdx + 1; + } + } + } + } + } else { // If not last event in column + // Check for space between this and next element + let nextOffset = cols[colIdx][elIdx + 1][1]; + if (nextOffset - offset < eventMajorSz.value + store.spacing){ + continue; + } // Check for better offset - if (bestOffset == null - || Math.abs(targetOffset - offset) < Math.abs(targetOffset - bestOffset)){ - if (offset <= targetOffset){ - offset = Math.min(targetOffset, availLen.value - eventMajorSz.value - store.spacing); - positions.push([colIdx, elIdx + 1, offset]); + if (bestOffset == null || Math.abs(pxOffset - offset) < Math.abs(pxOffset - bestOffset)){ + if (offset <= targetOffset + && targetOffset <= nextOffset - eventMajorSz.value - store.spacing){ + positions.push([colIdx, elIdx + 1, targetOffset]); break columnLoop; } else { - if (Math.atan2(Math.abs(targetOffset - offset), colMainlineDist) <= MAX_ANGLE){ + if (offset <= targetOffset){ + offset = nextOffset - eventMajorSz.value - store.spacing; + } + if (Math.atan2(Math.abs(pxOffset - offset), colMainlineDist) <= MAX_ANGLE){ bestOffset = offset; bestIdx = elIdx + 1; } } - } - } - } else { // If not last event in column - // Check for space between this and next element - let nextOffset = cols[colIdx][elIdx + 1][1]; - if (nextOffset - offset < eventMajorSz.value + store.spacing){ - continue; - } - // Check for better offset - if (bestOffset == null || Math.abs(targetOffset - offset) < Math.abs(targetOffset - bestOffset)){ - if (offset <= targetOffset && targetOffset <= nextOffset - eventMajorSz.value - store.spacing){ - positions.push([colIdx, elIdx + 1, targetOffset]); - break columnLoop; } else { - if (Math.atan2(Math.abs(targetOffset - offset), colMainlineDist) <= MAX_ANGLE){ - if (offset > targetOffset){ - bestOffset = offset; - } else { - bestOffset = nextOffset - eventMajorSz.value - store.spacing; - } - bestIdx = elIdx + 1; - } + break; } - } else { - break; } } } @@ -563,9 +592,7 @@ const idToPos = computed(() => { } } // Notify parent - const rangeStart = stepDate(startDate.value, scale.value, {forward: false}); - const rangeEnd = stepDate(endDate.value, scale.value); - emit('event-display', ID, [...map.keys()], rangeStart, rangeEnd, minorScaleIdx.value); + emit('event-display', ID, [...map.keys()], firstDate.value, lastDate.value, minorScaleIdx.value); return map; }); @@ -623,16 +650,14 @@ const tickToCount = computed((): Map<number, number> => { return tickToCount; } let unitToTickIdx: [number, number][] = []; // Holds tick units with their tick indexes in tickToCount - const tempFirstIdx = Math.max(firstIdx.value - 1, 0); - const tempLastIdx = Math.min(lastIdx.value + 1, ticks.value.length - 1); - for (let tickIdx = tempFirstIdx; tickIdx < tempLastIdx; tickIdx++){ + for (let tickIdx = firstIdx.value; tickIdx < lastIdx.value; tickIdx++){ tickToCount.set(tickIdx, 0); let unit = dateToUnit(ticks.value[tickIdx].date, minorScale.value); unitToTickIdx.push([unit, tickIdx]); } // Accumulate counts for ticks - const firstUnit = dateToUnit(ticks.value[tempFirstIdx].date, minorScale.value); - const lastUnit = dateToUnit(ticks.value[tempLastIdx].date, minorScale.value); + const firstUnit = dateToUnit(ticks.value[firstIdx.value].date, minorScale.value); + const lastUnit = dateToUnit(ticks.value[lastIdx.value].date, minorScale.value); for (let [unit, count] of props.unitCountMaps[minorScaleIdx.value].entries()){ if (unit >= firstUnit && unit < lastUnit){ let i = 0; |
