Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] [5.0] time axis label formatter #12859

Merged
merged 13 commits into from
Jul 29, 2020
10 changes: 6 additions & 4 deletions src/component/axisPointer/viewHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,17 +163,19 @@ export function getValueLabel(
): string {
value = axis.scale.parse(value);
let text = (axis.scale as IntervalScale).getLabel(
// If `precision` is set, width can be fixed (like '12.00500'), which
// helps to debounce when when moving label.
value, {
{
value
}, {
// If `precision` is set, width can be fixed (like '12.00500'), which
// helps to debounce when when moving label.
precision: opt.precision
}
);
const formatter = opt.formatter;

if (formatter) {
const params = {
value: axisHelper.getAxisRawValue(axis, value),
value: axisHelper.getAxisRawValue(axis, {value}),
axisDimension: axis.dim,
axisIndex: (axis as Axis2D).index, // Only Carteian Axis has index
seriesData: [] as CallbackDataParams[]
Expand Down
4 changes: 3 additions & 1 deletion src/component/dataZoom/SliderZoomView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,9 @@ class SliderZoomView extends DataZoomView {
? ''
// FIXME Glue code
: (axis.type === 'category' || axis.type === 'time')
? axis.scale.getLabel(Math.round(value as number))
? axis.scale.getLabel({
value: Math.round(value as number)
})
// param of toFixed should less then 20.
: (value as number).toFixed(Math.min(labelPrecision as number, 20));

Expand Down
16 changes: 8 additions & 8 deletions src/component/timeline/SliderTimelineView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import ExtensionAPI from '../../ExtensionAPI';
import { merge, each, extend, clone, isString, bind, defaults, retrieve2 } from 'zrender/src/core/util';
import SliderTimelineModel from './SliderTimelineModel';
import ComponentView from '../../view/Component';
import { LayoutOrient, ZRTextAlign, ZRTextVerticalAlign, ZRElementEvent } from '../../util/types';
import { LayoutOrient, ZRTextAlign, ZRTextVerticalAlign, ZRElementEvent, ScaleTick } from '../../util/types';
import TimelineModel, { TimelineDataItemOption, TimelineCheckpointStyle } from './TimelineModel';
import { TimelineChangePayload, TimelinePlayChangePayload } from './timelineAction';
import Model from '../../model/Model';
Expand Down Expand Up @@ -129,7 +129,7 @@ class SliderTimelineView extends TimelineView {
const axis = this._axis = this._createAxis(layoutInfo, timelineModel);

timelineModel.formatTooltip = function (dataIndex: number) {
return encodeHTML(axis.scale.getLabel(dataIndex));
return encodeHTML(axis.scale.getLabel({value: dataIndex}));
};

each(
Expand Down Expand Up @@ -344,7 +344,7 @@ class SliderTimelineView extends TimelineView {
// Customize scale. The `tickValue` is `dataIndex`.
scale.getTicks = function () {
return data.mapArray(['value'], function (value: number) {
return value;
return {value};
});
};

Expand Down Expand Up @@ -420,16 +420,16 @@ class SliderTimelineView extends TimelineView {
this._tickSymbols = [];

// The value is dataIndex, see the costomized scale.
each(ticks, (value) => {
const tickCoord = axis.dataToCoord(value);
const itemModel = data.getItemModel<TimelineDataItemOption>(value);
each(ticks, (tick: ScaleTick) => {
const tickCoord = axis.dataToCoord(tick.value);
const itemModel = data.getItemModel<TimelineDataItemOption>(tick.value);
const itemStyleModel = itemModel.getModel('itemStyle');
const hoverStyleModel = itemModel.getModel(['emphasis', 'itemStyle']);
const progressStyleModel = itemModel.getModel(['progress', 'itemStyle']);

const symbolOpt = {
position: [tickCoord, 0],
onclick: bind(this._changeTimeline, this, value)
onclick: bind(this._changeTimeline, this, tick.value)
};
const el = giveSymbol(itemModel, itemStyleModel, group, symbolOpt);
el.ensureState('emphasis').style = hoverStyleModel.getItemStyle();
Expand All @@ -439,7 +439,7 @@ class SliderTimelineView extends TimelineView {

const ecData = getECData(el);
if (itemModel.get('tooltip')) {
ecData.dataIndex = value;
ecData.dataIndex = tick.value;
ecData.dataModel = timelineModel;
}
else {
Expand Down
8 changes: 5 additions & 3 deletions src/coord/Axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,12 @@ class Axis {
const result = createAxisTicks(this, tickModel);
const ticks = result.ticks;

const ticksCoords = map(ticks, function (tickValue) {
const ticksCoords = map(ticks, function (tick) {
return {
coord: this.dataToCoord(tickValue),
tickValue: this.scale instanceof OrdinalScale ? this.scale.getCategoryIndex(tickValue) : tickValue
coord: this.dataToCoord(tick.value),
tickValue: this.scale instanceof OrdinalScale
? this.scale.getCategoryIndex(tick.value)
: tick.value
};
}, this);

Expand Down
22 changes: 20 additions & 2 deletions src/coord/axisCommonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import {
TextCommonOption, LineStyleOption, OrdinalRawValue, ZRColor,
AreaStyleOption, ComponentOption, ColorString,
AnimationOptionMixin, Dictionary, ScaleDataValue
AnimationOptionMixin, Dictionary, ScaleDataValue, TimeScaleTick
} from '../util/types';


Expand Down Expand Up @@ -153,6 +153,24 @@ interface AxisTickOption {
interval?: 'auto' | number | ((index: number, value: string) => boolean)
}

export type AxisLabelFormatterOption = string | ((value: OrdinalRawValue | number, index: number) => string);

type TimeAxisLabelUnitFormatter = AxisLabelFormatterOption | string[];

export type TimeAxisLabelFormatterOption = string
| ((value: TimeScaleTick, index: number) => string)
| {
year?: TimeAxisLabelUnitFormatter,
month?: TimeAxisLabelUnitFormatter,
week?: TimeAxisLabelUnitFormatter,
day?: TimeAxisLabelUnitFormatter,
hour?: TimeAxisLabelUnitFormatter,
minute?: TimeAxisLabelUnitFormatter,
second?: TimeAxisLabelUnitFormatter,
millisecond?: TimeAxisLabelUnitFormatter,
inherit?: boolean
};

interface AxisLabelOption extends Omit<TextCommonOption, 'color'> {
show?: boolean,
// Whether axisLabel is inside the grid or outside the grid.
Expand All @@ -164,7 +182,7 @@ interface AxisLabelOption extends Omit<TextCommonOption, 'color'> {
showMaxLabel?: boolean,
margin?: number,
// value is supposed to be OptionDataPrimitive but for time axis, it is time stamp.
formatter?: string | ((value: OrdinalRawValue | number, index: number) => string),
formatter?: AxisLabelFormatterOption | TimeAxisLabelFormatterOption,

// --------------------------------------------
// [Properties below only for 'category' axis]:
Expand Down
18 changes: 15 additions & 3 deletions src/coord/axisDefault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,22 @@ const valueAxis: AxisBaseOption = zrUtil.merge({
}
}, defaultOption);

const timeAxis: AxisBaseOption = zrUtil.defaults({
const timeAxis: AxisBaseOption = zrUtil.merge({
scale: true,
min: 'dataMin',
max: 'dataMax'
axisLabel: {
// To eliminate labels that are not nice
showMinLabel: false,
showMaxLabel: false,
rich: {
primary: {
color: '#000',
fontWeight: 'bold'
}
}
},
splitLine: {
show: false
}
}, valueAxis);

const logAxis: AxisBaseOption = zrUtil.defaults({
Expand Down
48 changes: 32 additions & 16 deletions src/coord/axisHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ import Model from '../model/Model';
import { AxisBaseModel } from './AxisBaseModel';
import LogScale from '../scale/Log';
import Axis from './Axis';
import { AxisBaseOption } from './axisCommonTypes';
import { AxisBaseOption, TimeAxisLabelFormatterOption } from './axisCommonTypes';
import type CartesianAxisModel from './cartesian/AxisModel';
import List from '../data/List';
import { getStackedDimension } from '../data/helper/dataStackHelper';
import { Dictionary, ScaleDataValue, DimensionName } from '../util/types';
import { Dictionary, DimensionName, ScaleTick, TimeScaleTick } from '../util/types';
import { ensureScaleRawExtentInfo } from './scaleRawExtentInfo';


Expand Down Expand Up @@ -219,48 +219,60 @@ export function ifAxisCrossZero(axis: Axis) {
* If category axis, this param is not requied.
* return: {string} label string.
*/
export function makeLabelFormatter(axis: Axis) {
export function makeLabelFormatter(axis: Axis): (tick: ScaleTick, idx: number) => string {
const labelFormatter = axis.getLabelModel().get('formatter');
const categoryTickStart = axis.type === 'category' ? axis.scale.getExtent()[0] : null;

if (typeof labelFormatter === 'string') {
if (axis.scale.type === 'time') {
return (function (tpl) {
return function (val: number | string) {
return function (tick: ScaleTick, idx: number) {
return (axis.scale as TimeScale).getFormattedLabel(tick, idx, tpl);
}
})(labelFormatter as TimeAxisLabelFormatterOption);
}
else if (typeof labelFormatter === 'string') {
return (function (tpl) {
return function (tick: ScaleTick) {
// For category axis, get raw value; for numeric axis,
// get foramtted label like '1,333,444'.
val = axis.scale.getLabel(val);
return tpl.replace('{value}', val != null ? val : '');
const label = axis.scale.getLabel(tick);
const text = tpl.replace('{value}', label != null ? label : '');

return text;
};
})(labelFormatter);
}
else if (typeof labelFormatter === 'function') {
return (function (cb) {
return function (tickValue: number, idx: number) {
return function (tick: ScaleTick, idx: number) {
// The original intention of `idx` is "the index of the tick in all ticks".
// But the previous implementation of category axis do not consider the
// `axisLabel.interval`, which cause that, for example, the `interval` is
// `1`, then the ticks "name5", "name7", "name9" are displayed, where the
// corresponding `idx` are `0`, `2`, `4`, but not `0`, `1`, `2`. So we keep
// the definition here for back compatibility.
if (categoryTickStart != null) {
idx = tickValue - categoryTickStart;
idx = tick.value - categoryTickStart;
}
return cb(getAxisRawValue(axis, tickValue), idx);
return cb(
getAxisRawValue(axis, tick) as (TimeScaleTick & string) | (TimeScaleTick & number),
idx
);
};
})(labelFormatter);
}
else {
return function (tick: number) {
return function (tick: ScaleTick) {
return axis.scale.getLabel(tick);
};
}
}

export function getAxisRawValue(axis: Axis, value: number | string): number | string {
export function getAxisRawValue(axis: Axis, tick: ScaleTick): number | string {
// In category axis with data zoom, tick is not the original
// index of axis.data. So tick should not be exposed to user
// in category axis.
return axis.type === 'category' ? axis.scale.getLabel(value) : value;
return axis.type === 'category' ? axis.scale.getLabel(tick) : tick.value;
}

/**
Expand All @@ -275,7 +287,7 @@ export function estimateLabelUnionRect(axis: Axis) {
return;
}

let realNumberScaleTicks;
let realNumberScaleTicks: ScaleTick[];
let tickCount;
const categoryScaleExtent = scale.getExtent();

Expand All @@ -298,8 +310,12 @@ export function estimateLabelUnionRect(axis: Axis) {
step = Math.ceil(tickCount / 40);
}
for (let i = 0; i < tickCount; i += step) {
const tickValue = realNumberScaleTicks ? realNumberScaleTicks[i] : categoryScaleExtent[0] + i;
const label = labelFormatter(tickValue, i);
const tick = realNumberScaleTicks
? realNumberScaleTicks[i]
: {
value: categoryScaleExtent[0] + i
};
const label = labelFormatter(tick, i);
const unrotatedSingleRect = axisLabelModel.getTextRect(label);
const singleRect = rotateTextRect(unrotatedSingleRect, axisLabelModel.get('rotate') || 0);

Expand Down
13 changes: 7 additions & 6 deletions src/coord/axisTickLabelBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
} from './axisHelper';
import Axis from './Axis';
import Model from '../model/Model';
import {ScaleTick} from '../util/types';

const inner = makeInner();

Expand All @@ -55,7 +56,7 @@ export function createAxisLabels(axis: Axis): {
* }
*/
export function createAxisTicks(axis: Axis, tickModel: Model): {
ticks: number[],
ticks: ScaleTick[],
tickCategoryInterval?: number
} {
// Only ordinal scale support tick interval
Expand Down Expand Up @@ -142,15 +143,15 @@ function makeCategoryTicks(axis, tickModel) {
});
}

function makeRealNumberLabels(axis) {
function makeRealNumberLabels(axis: Axis) {
const ticks = axis.scale.getTicks();
const labelFormatter = makeLabelFormatter(axis);
return {
labels: zrUtil.map(ticks, function (tickValue, idx) {
labels: zrUtil.map(ticks, function (tick, idx) {
return {
formattedLabel: labelFormatter(tickValue, idx),
rawLabel: axis.scale.getLabel(tickValue),
tickValue: tickValue
formattedLabel: labelFormatter(tick, idx),
rawLabel: axis.scale.getLabel(tick),
tickValue: tick.value
};
})
};
Expand Down
2 changes: 2 additions & 0 deletions src/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import * as colorTool from 'zrender/src/tool/color';
import * as graphicUtil from './util/graphic';
import * as numberUtil from './util/number';
import * as formatUtil from './util/format';
import * as timeUtil from './util/time';
import {throttle} from './util/throttle';
import * as ecHelper from './helper';
import parseGeoJSON from './coord/geo/parseGeoJson';
Expand All @@ -40,6 +41,7 @@ export {default as Model} from './model/Model';
export {default as Axis} from './coord/Axis';
export {numberUtil as number};
export {formatUtil as format};
export {timeUtil as time};
export {throttle};
export {ecHelper as helper};
export {matrix};
Expand Down
16 changes: 16 additions & 0 deletions src/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@
*/

export default {
time: {
month: [
'一月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '十一月', '十二月'
],
monthAbbr: [
'一', '二', '三', '四', '五', '六',
'七', '八', '九', '十', '十一', '十二'
],
dayOfWeek: [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be Chinese month and monthAbbr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

'星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'
],
dayOfWeekAbbr: [
'日', '一', '二', '三', '四', '五', '六'
]
},
legend: {
selector: {
all: '全选',
Expand Down
16 changes: 16 additions & 0 deletions src/langEN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@
*/

export default {
time: {
month: [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
],
monthAbbr: [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
],
dayOfWeek: [
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'
],
dayOfWeekAbbr: [
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
]
},
legend: {
selector: {
all: 'All',
Expand Down
Loading