Skip to content

Commit

Permalink
Refactored tick marks weight
Browse files Browse the repository at this point in the history
Don't use numbers for weight, use const enum instead
  • Loading branch information
timocov committed Oct 27, 2021
1 parent 6262b19 commit 366bd76
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 75 deletions.
63 changes: 32 additions & 31 deletions src/api/time-scale-point-weight-generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TimeScalePoint, UTCTimestamp } from '../model/time-data';
import { TickMarkWeight, TimeScalePoint } from '../model/time-data';

function hours(count: number): number {
return count * 60 * 60 * 1000;
Expand All @@ -14,54 +14,55 @@ function seconds(count: number): number {

interface WeightDivisor {
divisor: number;
weight: number;
weight: TickMarkWeight;
}

const intradayWeightDivisors: WeightDivisor[] = [
// TODO: divisor=1 means 1ms and it's strange that weight for 1ms > weight for 1s
{ divisor: 1, weight: 20 },
{ divisor: seconds(1), weight: 19 },
{ divisor: minutes(1), weight: 20 },
{ divisor: minutes(5), weight: 21 },
{ divisor: minutes(30), weight: 22 },
{ divisor: hours(1), weight: 30 },
{ divisor: hours(3), weight: 31 },
{ divisor: hours(6), weight: 32 },
{ divisor: hours(12), weight: 33 },
{ divisor: seconds(1), weight: TickMarkWeight.Second },
{ divisor: minutes(1), weight: TickMarkWeight.Minute1 },
{ divisor: minutes(5), weight: TickMarkWeight.Minute5 },
{ divisor: minutes(30), weight: TickMarkWeight.Minute30 },
{ divisor: hours(1), weight: TickMarkWeight.Hour1 },
{ divisor: hours(3), weight: TickMarkWeight.Hour3 },
{ divisor: hours(6), weight: TickMarkWeight.Hour6 },
{ divisor: hours(12), weight: TickMarkWeight.Hour12 },
];

function weightByTime(currentDate: Date, prevDate: Date | null): number {
if (prevDate !== null) {
if (currentDate.getUTCFullYear() !== prevDate.getUTCFullYear()) {
return 70;
} else if (currentDate.getUTCMonth() !== prevDate.getUTCMonth()) {
return 60;
} else if (currentDate.getUTCDate() !== prevDate.getUTCDate()) {
return 50;
}
function weightByTime(currentDate: Date, prevDate: Date): TickMarkWeight {
if (currentDate.getUTCFullYear() !== prevDate.getUTCFullYear()) {
return TickMarkWeight.Year;
} else if (currentDate.getUTCMonth() !== prevDate.getUTCMonth()) {
return TickMarkWeight.Month;
} else if (currentDate.getUTCDate() !== prevDate.getUTCDate()) {
return TickMarkWeight.Day;
}

for (let i = intradayWeightDivisors.length - 1; i >= 0; --i) {
if (Math.floor(prevDate.getTime() / intradayWeightDivisors[i].divisor) !== Math.floor(currentDate.getTime() / intradayWeightDivisors[i].divisor)) {
return intradayWeightDivisors[i].weight;
}
for (let i = intradayWeightDivisors.length - 1; i >= 0; --i) {
if (Math.floor(prevDate.getTime() / intradayWeightDivisors[i].divisor) !== Math.floor(currentDate.getTime() / intradayWeightDivisors[i].divisor)) {
return intradayWeightDivisors[i].weight;
}
}

return 20;
return TickMarkWeight.LessThanSecond;
}

export function fillWeightsForPoints(sortedTimePoints: readonly Mutable<TimeScalePoint>[], startIndex: number = 0): void {
let prevTime: UTCTimestamp | null = (startIndex === 0 || sortedTimePoints.length === 0)
? null
: sortedTimePoints[startIndex - 1].time.timestamp;
let prevDate: Date | null = prevTime !== null ? new Date(prevTime * 1000) : null;
if (sortedTimePoints.length === 0) {
return;
}

let prevTime = startIndex === 0 ? null : sortedTimePoints[startIndex - 1].time.timestamp;
let prevDate = prevTime !== null ? new Date(prevTime * 1000) : null;

let totalTimeDiff = 0;

for (let index = startIndex; index < sortedTimePoints.length; ++index) {
const currentPoint = sortedTimePoints[index];
const currentDate = new Date(currentPoint.time.timestamp * 1000);
currentPoint.timeWeight = weightByTime(currentDate, prevDate);

if (prevDate !== null) {
currentPoint.timeWeight = weightByTime(currentDate, prevDate);
}

totalTimeDiff += currentPoint.time.timestamp - (prevTime || currentPoint.time.timestamp);

Expand Down
7 changes: 4 additions & 3 deletions src/gui/time-axis-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IDataSource } from '../model/idata-source';
import { InvalidationLevel } from '../model/invalidate-mask';
import { LayoutOptionsInternal } from '../model/layout-options';
import { TextWidthCache } from '../model/text-width-cache';
import { TickMarkWeight } from '../model/time-data';
import { TimeMark } from '../model/time-scale';
import { TimeAxisViewRendererOptions } from '../renderers/itime-axis-view-renderer';

Expand Down Expand Up @@ -303,9 +304,9 @@ export class TimeAxisWidget implements MouseEventHandlers, IDestroyable {
let maxWeight = tickMarks.reduce(markWithGreaterWeight, tickMarks[0]).weight;

// special case: it looks strange if 15:00 is bold but 14:00 is not
// so if maxWeight > 30 and < 40 reduce it to 30
if (maxWeight > 30 && maxWeight < 40) {
maxWeight = 30;
// so if maxWeight > TickMarkWeight.Hour1 and < TickMarkWeight.Day reduce it to TickMarkWeight.Hour1
if (maxWeight > TickMarkWeight.Hour1 && maxWeight < TickMarkWeight.Day) {
maxWeight = TickMarkWeight.Hour1;
}

ctx.save();
Expand Down
4 changes: 2 additions & 2 deletions src/model/localization-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BusinessDay, UTCTimestamp } from './time-data';
export type TimeFormatterFn = (time: BusinessDay | UTCTimestamp) => string;

/**
* Represents options for formattings dates, times, and prices according to a locale.
* Represents options for formatting dates, times, and prices according to a locale.
*/
export interface LocalizationOptions {
/**
Expand All @@ -19,7 +19,7 @@ export interface LocalizationOptions {
locale: string;

/**
* Override fomatting of the price scale crosshair label. Can be used for cases that can't be covered with built-in price formats.
* Override formatting of the price scale crosshair label. Can be used for cases that can't be covered with built-in price formats.
*
* See also {@link PriceFormatCustom}.
*/
Expand Down
8 changes: 4 additions & 4 deletions src/model/tick-marks.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { lowerbound } from '../helpers/algorithms';
import { ensureDefined } from '../helpers/assertions';

import { TimePoint, TimePointIndex, TimeScalePoint } from './time-data';
import { TickMarkWeight, TimePoint, TimePointIndex, TimeScalePoint } from './time-data';

export interface TickMark {
index: TimePointIndex;
time: TimePoint;
weight: number;
weight: TickMarkWeight;
}

interface MarksCache {
Expand All @@ -15,7 +15,7 @@ interface MarksCache {
}

export class TickMarks {
private _marksByWeight: Map<number, TickMark[]> = new Map();
private _marksByWeight: Map<TickMarkWeight, TickMark[]> = new Map();
private _cache: MarksCache | null = null;

public setTimeScalePoints(newPoints: readonly TimeScalePoint[], firstChangedPointIndex: number): void {
Expand Down Expand Up @@ -57,7 +57,7 @@ export class TickMarks {
return;
}

const weightsToClear: number[] = [];
const weightsToClear: TickMarkWeight[] = [];

this._marksByWeight.forEach((marks: TickMark[], timeWeight: number) => {
if (sinceIndex <= marks[0].index) {
Expand Down
24 changes: 23 additions & 1 deletion src/model/time-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,30 @@ export interface TimePoint {
businessDay?: BusinessDay;
}

/**
* Describes a weight of tick mark, i.e. a part of a time that changed since previous time.
* Note that you can use any timezone to calculate this value, it is unnecessary to use UTC.
*
* @example Between 2020-01-01 and 2020-01-02 there is a day of difference, i.e. for 2020-01-02 weight would be a day.
* @example Between 2020-01-01 and 2020-02-02 there is a month of difference, i.e. for 2020-02-02 weight would be a month.
*/
export const enum TickMarkWeight {
LessThanSecond = 0,
Second = 10,
Minute1 = 20,
Minute5 = 21,
Minute30 = 22,
Hour1 = 30,
Hour3 = 31,
Hour6 = 32,
Hour12 = 33,
Day = 50,
Month = 60,
Year = 70,
}

export interface TimeScalePoint {
readonly timeWeight: number;
readonly timeWeight: TickMarkWeight;
readonly time: TimePoint;
}

Expand Down
69 changes: 35 additions & 34 deletions src/model/time-scale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
Logical,
LogicalRange,
SeriesItemsIndexesRange,
TickMarkWeight,
TimedValue,
TimePoint,
TimePointIndex,
Expand All @@ -34,15 +35,6 @@ const enum Constants {
MinVisibleBarsCount = 2,
}

const enum MarkWeightBorder {
Minute = 20,
Hour = 30,
Day = 40,
Week = 50,
Month = 60,
Year = 70,
}

interface TransitionState {
barSpacing: number;
rightOffset: number;
Expand All @@ -52,7 +44,7 @@ export interface TimeMark {
needAlignCoordinate: boolean;
coord: number;
label: string;
weight: number;
weight: TickMarkWeight;
}

/**
Expand Down Expand Up @@ -456,22 +448,17 @@ export class TimeScale {
continue;
}

const time = this.indexToTime(tm.index);
if (time === null) {
continue;
}

let label: TimeMark;
if (targetIndex < this._labels.length) {
label = this._labels[targetIndex];
label.coord = this.indexToCoordinate(tm.index);
label.label = this._formatLabel(time, tm.weight);
label.label = this._formatLabel(tm.time, tm.weight);
label.weight = tm.weight;
} else {
label = {
needAlignCoordinate: false,
coord: this.indexToCoordinate(tm.index),
label: this._formatLabel(time, tm.weight),
label: this._formatLabel(tm.time, tm.weight),
weight: tm.weight,
};

Expand Down Expand Up @@ -847,23 +834,8 @@ export class TimeScale {
return formatter.format(time);
}

private _formatLabelImpl(timePoint: TimePoint, weight: number): string {
let tickMarkType: TickMarkType;

const timeVisible = this._options.timeVisible;
if (weight < MarkWeightBorder.Minute && timeVisible) {
tickMarkType = this._options.secondsVisible ? TickMarkType.TimeWithSeconds : TickMarkType.Time;
} else if (weight < MarkWeightBorder.Day && timeVisible) {
tickMarkType = TickMarkType.Time;
} else if (weight < MarkWeightBorder.Week) {
tickMarkType = TickMarkType.DayOfMonth;
} else if (weight < MarkWeightBorder.Month) {
tickMarkType = TickMarkType.DayOfMonth;
} else if (weight < MarkWeightBorder.Year) {
tickMarkType = TickMarkType.Month;
} else {
tickMarkType = TickMarkType.Year;
}
private _formatLabelImpl(timePoint: TimePoint, weight: TickMarkWeight): string {
const tickMarkType = weightToTickMarkType(weight, this._options.timeVisible, this._options.secondsVisible);

if (this._options.tickMarkFormatter !== undefined) {
// this is temporary solution to make more consistency API
Expand Down Expand Up @@ -949,3 +921,32 @@ export class TimeScale {
this._correctBarSpacing();
}
}

// eslint-disable-next-line complexity
function weightToTickMarkType(weight: TickMarkWeight, timeVisible: boolean, secondsVisible: boolean): TickMarkType {
switch (weight) {
case TickMarkWeight.LessThanSecond:
case TickMarkWeight.Second:
return timeVisible
? (secondsVisible ? TickMarkType.TimeWithSeconds : TickMarkType.Time)
: TickMarkType.DayOfMonth;

case TickMarkWeight.Minute1:
case TickMarkWeight.Minute5:
case TickMarkWeight.Minute30:
case TickMarkWeight.Hour1:
case TickMarkWeight.Hour3:
case TickMarkWeight.Hour6:
case TickMarkWeight.Hour12:
return timeVisible ? TickMarkType.Time : TickMarkType.DayOfMonth;

case TickMarkWeight.Day:
return TickMarkType.DayOfMonth;

case TickMarkWeight.Month:
return TickMarkType.Month;

case TickMarkWeight.Year:
return TickMarkType.Year;
}
}

0 comments on commit 366bd76

Please sign in to comment.