diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/App.vue | 123 | ||||
| -rw-r--r-- | src/components/BaseLine.vue | 4 | ||||
| -rw-r--r-- | src/components/TimeLine.vue | 32 | ||||
| -rw-r--r-- | src/lib.ts | 321 | ||||
| -rw-r--r-- | src/rbtree.ts | 4 |
5 files changed, 247 insertions, 237 deletions
diff --git a/src/App.vue b/src/App.vue index c3cf6bf..d3fbd69 100644 --- a/src/App.vue +++ b/src/App.vue @@ -39,8 +39,8 @@ import PlusIcon from './components/icon/PlusIcon.vue'; import SettingsIcon from './components/icon/SettingsIcon.vue'; import HelpIcon from './components/icon/HelpIcon.vue'; // Other -import {HistDate, YearDate, TimelineState, HistEvent, queryServer, HistEventJson, jsonToHistEvent, cmpHistEvent, - timeout} from './lib'; +import {timeout, HistDate, YearDate, HistEvent, queryServer, HistEventJson, jsonToHistEvent, + TimelineState, cmpHistEvent, DateRangeTree} from './lib'; import {useStore} from './store'; import {RBTree, rbtree_shallow_copy} from './rbtree'; @@ -102,92 +102,7 @@ function onTimelineRemove(idx: number){ // For storing and looking up events const eventTree: ShallowRef<RBTree<HistEvent>> = shallowRef(new RBTree(cmpHistEvent)); let idToEvent: Map<number, HistEvent> = new Map(); -// For tracking ranges for which the server has no more events -let exhaustedRanges = new RBTree(cmpDatePairs); -function cmpDatePairs(datePair1: [HistDate, HistDate], datePair2: [HistDate, HistDate]){ - return datePair1[0].cmp(datePair2[0]); -} -function isExhaustedRange(startDate: HistDate, endDate: HistDate): boolean { - // Check if input range is contained in a stored exhausted range - let itr = exhaustedRanges.lowerBound([startDate, new YearDate()]); - let datePair = itr.data(); - if (datePair == null){ - datePair = itr.prev(); - if (datePair == null){ - return false; - } else { - return !datePair[1].isEarlier(endDate); - } - } else { - if (startDate.isEarlier(datePair[0])){ - return false; - } else { - return !datePair[1].isEarlier(endDate); - } - } -} -function addExhaustedRange(startDate: HistDate, endDate: HistDate){ - let rangesToRemove: HistDate[] = []; // Holds starts of ranges to remove - // Find ranges to remove - let itr = exhaustedRanges.lowerBound([startDate, new YearDate()]); - let prevRange = itr.prev(); - if (prevRange != null){ // Check for start-overlapping range - if (prevRange[1].isEarlier(startDate)){ - prevRange = null; - } else { - rangesToRemove.push(prevRange[0]); - } - } - let datePair = itr.next(); - while (datePair != null && !endDate.isEarlier(datePair[1])){ // Check for included ranges - rangesToRemove.push(datePair[0]); - datePair = itr.next(); - } - let nextRange = itr.data(); - if (nextRange != null){ // Check for end-overlapping range - if (endDate.isEarlier(nextRange[0])){ - nextRange = null; - } else { - rangesToRemove.push(nextRange[0]) - } - } - // Remove included/overlapping ranges - for (let start of rangesToRemove){ - exhaustedRanges.remove([start, new YearDate()]); - } - // Add possibly-merged range - if (prevRange != null){ - startDate = prevRange[0]; - } - if (nextRange != null){ - endDate = nextRange[1]; - } - exhaustedRanges.insert([startDate, endDate]); -} -// For keeping event data under a memory limit -const EXCESS_EVENTS_THRESHOLD = 10000; -let displayedEvents: Map<number, number[]> = new Map(); // Maps TimeLine IDs to IDs of displayed events -function onEventDisplay(eventIds: number[], timelineId: number){ - displayedEvents.set(timelineId, eventIds); -} -function reduceEvents(){ - // Get events to keep - let eventsToKeep: Map<number, HistEvent> = new Map(); - for (let [, ids] of displayedEvents){ - for (let id of ids){ - eventsToKeep.set(id, idToEvent.get(id)!); - } - } - // Create new event tree - let newTree = new RBTree(cmpHistEvent); - for (let [, event] of eventsToKeep){ - newTree.insert(event); - } - // Replace old data - eventTree.value = newTree; - idToEvent = eventsToKeep; - exhaustedRanges.clear(); -} +let exhaustedRanges = new DateRangeTree(); // Holds ranges for which the server has no more events // For getting events from server const EVENT_REQ_LIMIT = 30; const REQ_EXCLS_LIMIT = 100; @@ -197,8 +112,8 @@ async function onEventReq(startDate: HistDate, endDate: HistDate){ await timeout(100); } pendingReq = true; - // Exclude exhausted range - if (isExhaustedRange(startDate, endDate)){ + // Skip if exhausted range + if (exhaustedRanges.has([startDate, endDate])){ pendingReq = false; return; } @@ -244,7 +159,7 @@ async function onEventReq(startDate: HistDate, endDate: HistDate){ if (added){ eventTree.value = rbtree_shallow_copy(eventTree.value); // Note: triggerRef(eventTree) does not work here } else { - addExhaustedRange(startDate, endDate); // Mark as exhausted range + exhaustedRanges.add([startDate, endDate]); // Mark as exhausted range } // Check memory limit if (eventTree.value.size > EXCESS_EVENTS_THRESHOLD){ @@ -252,6 +167,30 @@ async function onEventReq(startDate: HistDate, endDate: HistDate){ } pendingReq = false; } +// For keeping event data under a memory limit +const EXCESS_EVENTS_THRESHOLD = 10000; +let displayedEvents: Map<number, number[]> = new Map(); // Maps TimeLine IDs to IDs of displayed events +function onEventDisplay(eventIds: number[], timelineId: number){ + displayedEvents.set(timelineId, eventIds); +} +function reduceEvents(){ + // Get events to keep + let eventsToKeep: Map<number, HistEvent> = new Map(); + for (let [, ids] of displayedEvents){ + for (let id of ids){ + eventsToKeep.set(id, idToEvent.get(id)!); + } + } + // Create new event tree + let newTree = new RBTree(cmpHistEvent); + for (let [, event] of eventsToKeep){ + newTree.insert(event); + } + // Replace old data + eventTree.value = newTree; + idToEvent = eventsToKeep; + exhaustedRanges.clear(); +} // For resize handling let lastResizeHdlrTime = 0; // Used to throttle resize handling @@ -269,7 +208,7 @@ async function onResize(){ } // Setup a handler to execute after ending a run of resize events clearTimeout(afterResizeHdlr); - afterResizeHdlr = setTimeout(async () => { + afterResizeHdlr = window.setTimeout(async () => { afterResizeHdlr = 0; await handleResize(); lastResizeHdlrTime = new Date().getTime(); diff --git a/src/components/BaseLine.vue b/src/components/BaseLine.vue index 33d8a88..0187b20 100644 --- a/src/components/BaseLine.vue +++ b/src/components/BaseLine.vue @@ -43,7 +43,7 @@ const periods: Ref<Period[]> = ref([ const skipTransition = ref(true); onMounted(() => setTimeout(() => {skipTransition.value = false}, 100)); -// For size tracking (used to prevent time spans shrinking below 1 pixel) +// For size and mount-status tracking const width = ref(0); const height = ref(0); const mounted = ref(false); @@ -87,7 +87,7 @@ function spanStyles(state: TimelineState){ let start = state.startDate.clone(); let end = state.endDate.clone(); let scale = SCALES[state.scaleIdx]; - if (scale != MONTH_SCALE && scale != DAY_SCALE){ // Possibly incorporate offsets + if (scale != MONTH_SCALE && scale != DAY_SCALE){ // Account for offsets stepDate(start, 1, {forward: false, count: Math.floor(state.startOffset * scale), inplace: true}); stepDate(end, 1, {count: Math.floor(state.endOffset * scale), inplace: true}); } diff --git a/src/components/TimeLine.vue b/src/components/TimeLine.vue index 8a6ddc4..fd119be 100644 --- a/src/components/TimeLine.vue +++ b/src/components/TimeLine.vue @@ -11,6 +11,8 @@ <stop offset="95%" stop-color="gold"/> </linearGradient> </defs> + <!-- 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)" @@ -24,19 +26,17 @@ :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.01" stroke="url('#eventLineGradient')" stroke-width="1px" - :style="eventLineStyles(id)" class="animate-fadein"/> - <!-- Note: With a fully vertical or horizontal line, nothing gets displayed --> <!-- 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.toDisplayString()}} </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"/> + <!-- Event lines --> + <line v-for="id in eventLines.keys()" :key="id" + x1="0" y1="0" x2="1" y2="0.01" stroke="url('#eventLineGradient')" stroke-width="1px" + :style="eventLineStyles(id)" class="animate-fadein"/> + <!-- Note: With a fully vertical or horizontal line, nothing gets displayed --> </svg> <!-- Events --> <div v-for="id in idToPos.keys()" :key="id" class="absolute animate-fadein" :style="eventStyles(id)"> @@ -88,7 +88,7 @@ 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 prevVert = ref(props.vert); // Previous 'vert' value, used for skipping transitions on horz/vert swap const mounted = ref(false); onMounted(() => { let rootEl = rootRef.value!; @@ -121,7 +121,7 @@ const eventMajorSz = computed(() => props.vert ? eventHeight.value : eventWidth. const eventMinorSz = computed(() => props.vert ? eventWidth.value : eventHeight.value) const sideMainline = computed( // True if unable to fit mainline in middle with events on both sides () => availBreadth.value < store.mainlineBreadth + (eventMinorSz.value + store.spacing * 2) * 2); -const mainlineOffset = computed(() => { // Distance mainline-area line to side of display area +const mainlineOffset = computed(() => { // Distance from mainline-area line to left/top of display area if (!sideMainline.value){ return availBreadth.value / 2 - store.mainlineBreadth /2 + store.largeTickLen / 2; } else { @@ -675,9 +675,9 @@ function zoomTimeline(zoomRatio: number){ } newNumUnits += newStartOffset + newEndOffset; } - // Possibly change the scale + // Possibly zoom in/out let tickDiff = availLen.value / newNumUnits; - if (tickDiff < store.minTickSep){ // Possibly zoom out + if (tickDiff < store.minTickSep){ // If trying to zoom out if (scaleIdx.value == 0){ console.log('Reached zoom out limit'); return; @@ -688,8 +688,8 @@ function zoomTimeline(zoomRatio: number){ newStartOffset /= oldUnitsPerNew; newEndOffset /= oldUnitsPerNew; // Shift starting and ending points to align with new scale - // Note: There is some distortion due to not accounting for no year 0 CE here - // But the result seems tolerable, and resolving it adds a fair bit of code complexity + // Note: There is some distortion due to not fully accounting for no year 0 CE here, + // but the result seems tolerable, and resolving it adds a fair bit of code complexity let newStartSubUnits = (scale.value == DAY_SCALE) ? getDaysInMonth(newStart.year, newStart.month) : (scale.value == MONTH_SCALE) ? 12 : @@ -742,7 +742,7 @@ function zoomTimeline(zoomRatio: number){ // scaleIdx.value -= 1; } - } else { // Possibly zoom in + } else { // If trying to zoom in if (scaleIdx.value == SCALES.length - 1){ if (newNumUnits < store.minLastTicks){ console.log('Reached zoom in limit'); @@ -824,7 +824,7 @@ function onPointerDown(evt: PointerEvent){ dragVelocity = 0; vUpdateTime = Date.now(); vPrevPointer = null; - vUpdater = setInterval(() => { + vUpdater = window.setInterval(() => { if (vPrevPointer != null){ let time = Date.now(); let ptrDiff = (props.vert ? pointerY! : pointerX!) - vPrevPointer; @@ -843,7 +843,7 @@ function onPointerMove(evt: PointerEvent){ // Handle pointer dragging dragDiff += props.vert ? evt.clientY - pointerY! : evt.clientX - pointerX!; if (dragHandler == 0){ - dragHandler = setTimeout(() => { + dragHandler = window.setTimeout(() => { if (Math.abs(dragDiff) > 2){ panTimeline(-dragDiff / availLen.value); dragDiff = 0; @@ -2,10 +2,14 @@ * Project-wide globals */ +import {RBTree} from './rbtree'; + export const DEBUG = true; -export const WRITING_MODE_HORZ = - window.getComputedStyle(document.body)['writing-mode' as any].startsWith('horizontal'); +export let WRITING_MODE_HORZ = true; // Used with ResizeObserver callbacks, to determine which resized dimensions are width and height +if ('writing-mode' in window.getComputedStyle(document.body)){ // Can be null when testing + WRITING_MODE_HORZ = window.getComputedStyle(document.body)['writing-mode' as any].startsWith('horizontal'); +} // Similar to %, but for negative LHS, return a positive offset from a lower RHS multiple export function moduloPositive(x: number, y: number){ @@ -16,7 +20,7 @@ export async function timeout(ms: number){ return new Promise(resolve => setTimeout(resolve, ms)) } -// For calendar conversion. Mostly copied from backend/hist_data/cal.py +// For calendar conversion (mostly copied from backend/hist_data/cal.py) export function gregorianToJdn(year: number, month: number, day: number): number { if (year < 0){ year += 1; @@ -75,7 +79,7 @@ export function getDaysInMonth(year: number, month: number){ } // For date representation -export const MIN_CAL_YEAR = -4713; // Year after which months/day scales are usable +export const MIN_CAL_YEAR = -4713; // Earliest year where months/day scales are usable export class HistDate { gcal: boolean | null; year: number; @@ -84,8 +88,8 @@ export class HistDate { constructor(gcal: boolean | null, year: number, month: number, day: number){ this.gcal = gcal; this.year = year; - this.month = month; - this.day = day; + this.month = gcal == null ? 1 : month; + this.day = gcal == null ? 1 : day; } equals(other: HistDate, scale=DAY_SCALE){ if (scale == DAY_SCALE){ @@ -96,6 +100,9 @@ export class HistDate { return Math.floor(this.year / scale) == Math.floor(other.year / scale); } } + clone(){ + return new HistDate(this.gcal, this.year, this.month, this.day); + } isEarlier(other: HistDate, scale=DAY_SCALE){ const yearlyScale = scale != DAY_SCALE && scale != MONTH_SCALE; const thisYear = yearlyScale ? Math.floor(this.year / scale) : this.year; @@ -119,8 +126,33 @@ export class HistDate { return 0; } } - toInt(){ - return this.day + this.month * 50 + this.year * 1000; + getDayDiff(other: HistDate){ // Assumes neither date has gcal=null + const jdn2 = gregorianToJdn(this.year, this.month, this.day); + const jdn1 = gregorianToJdn(other.year, other.month, other.day); + return Math.abs(jdn1 - jdn2); + } + getMonthDiff(other: HistDate){ + // Determine earlier date + let earlier = this as HistDate; + let later = other; + if (other.year < this.year || other.year == this.year && other.month < this.month){ + earlier = other; + later = this as HistDate; + } + // + const yearDiff = earlier.getYearDiff(later); + if (yearDiff == 0){ + return later.month - earlier.month; + } else { + return (13 - earlier.month) + (yearDiff - 1) * 12 + later.month - 1; + } + } + getYearDiff(other: HistDate){ + let yearDiff = Math.abs(this.year - other.year); + if (this.year * other.year < 0){ // Account for no 0 CE + yearDiff -= 1; + } + return yearDiff; } toString(){ if (this.gcal != null){ @@ -175,36 +207,8 @@ export class HistDate { } } } - getDayDiff(other: HistDate){ // Assumes neither date has gcal=null - const jdn2 = gregorianToJdn(this.year, this.month, this.day); - const jdn1 = gregorianToJdn(other.year, other.month, other.day); - return Math.abs(jdn1 - jdn2); - } - getMonthDiff(other: HistDate){ - // Determine earlier date - let earlier = this as HistDate; - let later = other; - if (other.year < this.year || other.year == this.year && other.month < this.month){ - earlier = other; - later = this as HistDate; - } - // - const yearDiff = earlier.getYearDiff(later); - if (yearDiff == 0){ - return later.month - earlier.month; - } else { - return (13 - earlier.month) + (yearDiff - 1) * 12 + later.month - 1; - } - } - getYearDiff(other: HistDate){ - let yearDiff = Math.abs(this.year - other.year); - if (this.year * other.year < 0){ // Account for no 0 CE - yearDiff -= 1; - } - return yearDiff; - } - clone(){ - return new HistDate(this.gcal, this.year, this.month, this.day); + toInt(){ // Used for v-for keys + return this.day + this.month * 50 + this.year * 1000; } } export class YearDate extends HistDate { @@ -232,7 +236,97 @@ export class CalDate extends HistDate { } } -// Timeline parameters +// For event representation +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){ + const cmp = event.start.cmp(event2.start); + return cmp != 0 ? cmp : event.id - event2.id; +} + +// For server requests +const SERVER_DATA_URL = (new URL(window.location.href)).origin + '/data/' +const SERVER_IMG_PATH = '/hist_data/img/' +export async function queryServer(params: URLSearchParams, serverDataUrl=SERVER_DATA_URL){ + // Construct URL + const url = new URL(serverDataUrl); + url.search = params.toString(); + // Query server + let responseObj; + try { + const response = await fetch(url.toString()); + responseObj = await response.json(); + } catch (error){ + console.log(`Error with querying ${url.toString()}: ${error}`); + return null; + } + return responseObj; +} +export function getImagePath(imgId: number): string { + return SERVER_IMG_PATH + String(imgId) + '.jpg'; +} +// For server responses +export type HistDateJson = { + gcal: boolean | null, + year: number, + month: number, + day: number, +} +export type HistEventJson = { + id: number, + title: string, + start: HistDateJson, + startUpper: HistDateJson | null, + end: HistDateJson | null, + endUpper: HistDateJson | null, + ctg: string, + imgId: number, + pop: number, +} +export function jsonToHistDate(json: HistDateJson){ + if (json.gcal == null){ + return new YearDate(json.year); + } else { + return new CalDate(json.year, json.month, json.day, json.gcal); + } +} +export function jsonToHistEvent(json: HistEventJson){ + return { + id: json.id, + title: json.title, + start: jsonToHistDate(json.start), + startUpper: json.startUpper == null ? null : jsonToHistDate(json.startUpper), + end: json.end == null ? null : jsonToHistDate(json.end), + endUpper: json.endUpper == null ? null : jsonToHistDate(json.endUpper), + ctg: json.ctg, + imgId: json.imgId, + pop: json.pop, + }; +} + +// For dates in a timeline const currentDate = new Date(); export const MIN_DATE = new YearDate(-13.8e9); export const MAX_DATE = new CalDate(currentDate.getFullYear(), currentDate.getMonth() + 1, currentDate.getDate()); @@ -259,7 +353,8 @@ if (DEBUG){ } } } -export function stepDate(date: HistDate, scale: number, {forward=true, count=1, inplace=false} = {}): HistDate { +export function stepDate( // If stepping by month or years, leaves day value unchanged + date: HistDate, scale: number, {forward=true, count=1, inplace=false} = {}): HistDate { const newDate = inplace ? date : date.clone(); if (count < 0){ count = -count; @@ -342,7 +437,7 @@ export function inDateScale(date: HistDate, scale: number): boolean { } } export function getScaleRatio(scale: number, scale2: number){ - // Returns upper number of units in 'scale' per unit in 'scale2' + // Returns number of units in 'scale' per unit in 'scale2' (provides an upper value for days-per-month/year) if (scale == DAY_SCALE){ scale = 1 / 12 / 31; } else if (scale == MONTH_SCALE){ @@ -384,91 +479,67 @@ export class TimelineState { } } -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; +// For managing sets of non-overlapping date ranges +export type DateRange = [HistDate, HistDate]; +export class DateRangeTree { + tree: RBTree<DateRange>; + constructor(){ + this.tree = new RBTree((r1: DateRange, r2: DateRange) => r1[0].cmp(r2[0])); } -} -export function cmpHistEvent(event: HistEvent, event2: HistEvent){ - const cmp = event.start.cmp(event2.start); - return cmp != 0 ? cmp : event.id - event2.id; -} - -// For server requests -const SERVER_DATA_URL = (new URL(window.location.href)).origin + '/data/' -const SERVER_IMG_PATH = '/hist_data/img/' -export async function queryServer(params: URLSearchParams){ - // Construct URL - const url = new URL(SERVER_DATA_URL); - url.search = params.toString(); - // Query server - let responseObj; - try { - const response = await fetch(url.toString()); - responseObj = await response.json(); - } catch (error){ - console.log(`Error with querying ${url.toString()}: ${error}`); - return null; + add(range: DateRange){ + let rangesToRemove: HistDate[] = []; // Holds starts of ranges to remove + let dummyDate = new YearDate(); + // Find ranges to remove + let itr = this.tree.lowerBound([range[0], dummyDate]); + let prevRange = itr.prev(); + if (prevRange != null){ // Check for start-overlapping range + if (prevRange[1].isEarlier(range[0])){ + prevRange = null; + } else { + rangesToRemove.push(prevRange[0]); + } + } + let r = itr.next(); + while (r != null && !range[1].isEarlier(r[1])){ // Check for included ranges + rangesToRemove.push(r[0]); + r = itr.next(); + } + let nextRange = itr.data(); + if (nextRange != null){ // Check for end-overlapping range + if (range[1].isEarlier(nextRange[0])){ + nextRange = null; + } else { + rangesToRemove.push(nextRange[0]) + } + } + // Remove included/overlapping ranges + for (let start of rangesToRemove){ + this.tree.remove([start, dummyDate]); + } + // Add possibly-merged range + let startDate = prevRange != null ? prevRange[0] : range[0]; + let endDate = nextRange != null ? nextRange[1] : range[1]; + this.tree.insert([startDate, endDate]); } - return responseObj; -} -export function getImagePath(imgId: number): string { - return SERVER_IMG_PATH + String(imgId) + '.jpg'; -} -// For server responses -export type HistDateJson = { - gcal: boolean | null, - year: number, - month: number, - day: number, -} -export type HistEventJson = { - id: number, - title: string, - start: HistDateJson, - startUpper: HistDateJson | null, - end: HistDateJson | null, - endUpper: HistDateJson | null, - ctg: string, - imgId: number, - pop: number, -} -export function jsonToHistDate(json: HistDateJson){ - if (json.gcal == null){ - return new YearDate(json.year); - } else { - return new CalDate(json.year, json.month, json.day, json.gcal); + has(range: DateRange): boolean { + let itr = this.tree.lowerBound([range[0], new YearDate()]); + let r = itr.data(); + if (r == null){ + r = itr.prev(); + if (r == null){ + return false; + } else { + return !r[1].isEarlier(range[1]); + } + } else { + if (range[0].isEarlier(r[0])){ + return false; + } else { + return !r[1].isEarlier(range[1]); + } + } + } + clear(){ + this.tree.clear(); } -} -export function jsonToHistEvent(json: HistEventJson){ - return { - id: json.id, - title: json.title, - start: jsonToHistDate(json.start), - startUpper: json.startUpper == null ? null : jsonToHistDate(json.startUpper), - end: json.end == null ? null : jsonToHistDate(json.end), - endUpper: json.endUpper == null ? null : jsonToHistDate(json.endUpper), - ctg: json.ctg, - imgId: json.imgId, - pop: json.pop, - }; } diff --git a/src/rbtree.ts b/src/rbtree.ts index 8422dec..b4ae540 100644 --- a/src/rbtree.ts +++ b/src/rbtree.ts @@ -1,6 +1,6 @@ // Copied from node_modules/bintrees/lib/, and adapted to use ES6, classes, and typescript -class Node<T> { +export class Node<T> { data: T; left: Node<T> | null; right: Node<T> | null; @@ -24,7 +24,7 @@ class Node<T> { } } -class Iterator<T> { +export class Iterator<T> { _tree: RBTree<T>; _ancestors: Node<T>[]; _cursor: Node<T> | null; |
