Skip to content

Commit ffcc636

Browse files
committed
fix(alignTicks): Change alignTick strategy: (1) Previously some series data may be out of the calculated extent and can not be displayed. (2) Previously the precision is incorrect for small float number (fixed at 10 rather than based on the magnitude of the value). (3) Make the tick precision more acceptable when min/max of axis is fixed, and remove console warning, because whey can be specified when dataZoom dragging. (4) Clarify the related code for LogScale.
1 parent d168bf2 commit ffcc636

24 files changed

Lines changed: 1459 additions & 382 deletions

‎src/coord/axisAlignTicks.ts‎

Lines changed: 240 additions & 94 deletions
Large diffs are not rendered by default.

‎src/coord/axisCommonTypes.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,10 @@ export interface ValueAxisBaseOption extends NumericAxisBaseOptionCommon {
210210

211211
/**
212212
* Optional value can be:
213-
* + `false`: always include value 0.
213+
* + `false`: always include value 0 if not conflict with `axis.min/max` setting.
214214
* + `true`: the axis may not contain zero position.
215215
*/
216-
scale?: boolean;
216+
scale?: boolean;
217217
}
218218
exportinterfaceLogAxisBaseOptionextendsNumericAxisBaseOptionCommon{
219219
type?: 'log';

‎src/coord/axisHelper.ts‎

Lines changed: 61 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,35 @@ import CartesianAxisModel from './cartesian/AxisModel';
4646
importSeriesDatafrom'../data/SeriesData';
4747
import{getStackedDimension}from'../data/helper/dataStackHelper';
4848
import{Dictionary,DimensionName,ScaleTick}from'../util/types';
49-
import{ensureScaleRawExtentInfo}from'./scaleRawExtentInfo';
49+
import{ensureScaleRawExtentInfo,ScaleRawExtentResult}from'./scaleRawExtentInfo';
5050
import{parseTimeAxisLabelFormatter}from'../util/time';
5151
import{getScaleBreakHelper}from'../scale/break';
5252
import{error}from'../util/log';
53+
import{isIntervalScale,isTimeScale}from'../scale/helper';
5354

5455

5556
typeBarWidthAndOffset=ReturnType<typeofmakeColumnLayout>;
5657

5758
/**
58-
* Get axis scale extent before niced.
59+
* Prepare axis scale extent before niced.
5960
* Item of returned array can only be number (including Infinity and NaN).
6061
*
61-
* Caution:
62-
* Precondition of calling this method:
63-
* The scale extent has been initialized using series data extent via
64-
* `scale.setExtent` or `scale.unionExtentFromData`;
62+
* CAVEAT:
63+
* This function has side-effect.
64+
*
65+
* FIXME:
66+
* Refector to decouple `unionExtentFromData` and irregular value handling from `scale`.
67+
* Merge `unionAxisExtentFromData` and `unionExtentFromData`.
68+
* Refector `ensureScaleRawExtentInfo`.
6569
*/
66-
exportfunctiongetScaleExtent(scale: Scale,model: AxisBaseModel){
67-
constscaleType=scale.type;
68-
constrawExtentResult=ensureScaleRawExtentInfo(scale,model,scale.getExtent()).calculate();
70+
exportfunctionadoptScaleExtentOptionAndPrepare(
71+
scale: Scale,
72+
model: AxisBaseModel,
73+
// Typically: data extent from all series on this axis.
74+
// Can be obtained by `scale.unionExtentFromData(); scale.getExtent()`;
75+
dataExtent: number[]
76+
): ScaleRawExtentResult{
77+
constrawExtentResult=ensureScaleRawExtentInfo(scale,model,dataExtent).calculate();
6978

7079
scale.setBlank(rawExtentResult.isBlank);
7180

@@ -82,7 +91,7 @@ export function getScaleExtent(scale: Scale, model: AxisBaseModel) {
8291
// (4) Consider other chart types using `barGrid`?
8392
// See #6728, #4862, `test/bar-overflow-time-plot.html`
8493
constecModel=model.ecModel;
85-
if(ecModel&&(scaleType==='time'/* || scaleType === 'interval' */)){
94+
if(ecModel&&(isTimeScale(scale)/* || scaleType === 'interval' */)){
8695
constbarSeriesModels=prepareLayoutBarSeries('bar',ecModel);
8796
letisBaseAxisAndHasBarSeries=false;
8897

@@ -102,13 +111,10 @@ export function getScaleExtent(scale: Scale, model: AxisBaseModel) {
102111
}
103112
}
104113

105-
return{
106-
extent: [min,max],
107-
// "fix" means "fixed", the value should not be
108-
// changed in the subsequent steps.
109-
fixMin: rawExtentResult.minFixed,
110-
fixMax: rawExtentResult.maxFixed
111-
};
114+
rawExtentResult.min=min;
115+
rawExtentResult.max=max;
116+
117+
returnrawExtentResult;
112118
}
113119

114120
functionadjustScaleForOverflow(
@@ -151,32 +157,25 @@ function adjustScaleForOverflow(
151157
return{min: min,max: max};
152158
}
153159

154-
// Precondition of calling this method:
155-
// The scale extent has been initialized using series data extent via
156-
// `scale.setExtent` or `scale.unionExtentFromData`;
157160
exportfunctionniceScaleExtent(
158161
scale: Scale,
159-
inModel: AxisBaseModel
160-
){
162+
inModel: AxisBaseModel,
163+
// Typically: data extent from all series on this axis, which can be obtained by
164+
// `scale.unionExtentFromData(...); scale.getExtent();`.
165+
dataExtent: number[],
166+
): void{
161167
constmodel=inModelasAxisBaseModel<LogAxisBaseOption>;
162-
constextentInfo=getScaleExtent(scale,model);
163-
constextent=extentInfo.extent;
164-
constsplitNumber=model.get('splitNumber');
165-
166-
if(scaleinstanceofLogScale){
167-
scale.base=model.get('logBase');
168-
}
168+
constextentInfo=adoptScaleExtentOptionAndPrepare(scale,model,dataExtent);
169169

170-
constscaleType=scale.type;
171-
constinterval=model.get('interval');
172-
constisIntervalOrTime=scaleType==='interval'||scaleType==='time';
170+
constisInterval=isIntervalScale(scale);
171+
constisIntervalOrTime=isInterval||isTimeScale(scale);
173172

174173
scale.setBreaksFromOption(retrieveAxisBreaksOption(model));
175-
scale.setExtent(extent[0],extent[1]);
174+
scale.setExtent(extentInfo.min,extentInfo.max);
176175
scale.calcNiceExtent({
177-
splitNumber: splitNumber,
178-
fixMin: extentInfo.fixMin,
179-
fixMax: extentInfo.fixMax,
176+
splitNumber: model.get('splitNumber'),
177+
fixMin: extentInfo.minFixed,
178+
fixMax: extentInfo.maxFixed,
180179
minInterval: isIntervalOrTime ? model.get('minInterval') : null,
181180
maxInterval: isIntervalOrTime ? model.get('maxInterval') : null
182181
});
@@ -185,36 +184,35 @@ export function niceScaleExtent(
185184
// is not good enough. He can specify the interval. It is often appeared
186185
// in angle axis with angle 0 - 360. Interval calculated in interval scale is hard
187186
// to be 60.
188-
// FIXME
189-
if(interval!=null){
190-
(scaleasIntervalScale).setInterval&&(scaleasIntervalScale).setInterval(interval);
187+
// In `xxxAxis.type: 'log'`, ec option `xxxAxis.interval` requires a logarithm-applied
188+
// value rather than a value in the raw scale.
189+
constinterval=model.get('interval');
190+
if(interval!=null&&(scaleasIntervalScale).setInterval){
191+
(scaleasIntervalScale).setInterval({interval});
191192
}
192193
}
193194

194-
/**
195-
* @param axisType Default retrieve from model.type
196-
*/
197-
exportfunctioncreateScaleByModel(model: AxisBaseModel,axisType?: string): Scale{
198-
axisType=axisType||model.get('type');
199-
if(axisType){
200-
switch(axisType){
201-
// Buildin scale
202-
case'category':
203-
returnnewOrdinalScale({
204-
ordinalMeta: model.getOrdinalMeta
205-
? model.getOrdinalMeta()
206-
: model.getCategories(),
207-
extent: [Infinity,-Infinity]
208-
});
209-
case'time':
210-
returnnewTimeScale({
211-
locale: model.ecModel.getLocaleModel(),
212-
useUTC: model.ecModel.get('useUTC'),
213-
});
214-
default:
215-
// case 'value'/'interval', 'log', or others.
216-
returnnew(Scale.getClass(axisType)||IntervalScale)();
217-
}
195+
exportfunctioncreateScaleByModel(model: AxisBaseModel): Scale{
196+
constaxisType=model.get('type');
197+
switch(axisType){
198+
case'category':
199+
returnnewOrdinalScale({
200+
ordinalMeta: model.getOrdinalMeta
201+
? model.getOrdinalMeta()
202+
: model.getCategories(),
203+
extent: [Infinity,-Infinity]
204+
});
205+
case'time':
206+
returnnewTimeScale({
207+
locale: model.ecModel.getLocaleModel(),
208+
useUTC: model.ecModel.get('useUTC'),
209+
});
210+
case'log':
211+
// See also #3749
212+
returnnewLogScale((modelasAxisBaseModel<LogAxisBaseOption>).get('logBase'));
213+
default:
214+
// case 'value'/'interval', or others.
215+
returnnew(Scale.getClass(axisType)||IntervalScale)();
218216
}
219217
}
220218

@@ -303,7 +301,6 @@ export function getAxisRawValue<TIsCategory extends boolean>(axis: Axis, tick: S
303301

304302
/**
305303
* @param model axisLabelModel or axisTickModel
306-
* @return {number|String} Can be null|'auto'|number|function
307304
*/
308305
exportfunctiongetOptionCategoryInterval(
309306
model: Model<AxisBaseOption['axisLabel']>

‎src/coord/axisModelCommonMixin.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ interface AxisModelCommonMixin<Opt extends AxisBaseOption> extends Pick<Model<Op
3131
classAxisModelCommonMixin<OptextendsAxisBaseOption>{
3232

3333
getNeedCrossZero(): boolean{
34-
constoption=this.optionasValueAxisBaseOption;
35-
return!option.scale;
34+
return!(this.optionasValueAxisBaseOption).scale;
3635
}
3736

3837
/**

‎src/coord/cartesian/Grid.ts‎

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
getDataDimensionsOnAxis,
3333
isNameLocationCenter,
3434
shouldAxisShow,
35+
retrieveAxisBreaksOption,
3536
}from'../../coord/axisHelper';
3637
importCartesian2D,{cartesian2DDimensions}from'./Cartesian2D';
3738
importAxis2Dfrom'./Axis2D';
@@ -124,50 +125,32 @@ class Grid implements CoordinateSystemMaster {
124125
this._updateScale(ecModel,this.model);
125126

126127
functionupdateAxisTicks(axes: Record<number,Axis2D>){
127-
letalignTo: Axis2D;
128128
// Axis is added in order of axisIndex.
129129
constaxesIndices=keys(axes);
130-
constlen=axesIndices.length;
131-
if(!len){
132-
return;
133-
}
134130
constaxisNeedsAlign: Axis2D[]=[];
135-
// Process once and calculate the ticks for those don't use alignTicks.
136-
for(leti=len-1;i>=0;i--){
137-
constidx=+axesIndices[i];// Convert to number.
138-
constaxis=axes[idx];
139-
constmodel=axis.modelasAxisBaseModel<NumericAxisBaseOptionCommon>;
140-
constscale=axis.scale;
141-
if(// Only value and log axis without interval support alignTicks.
142-
isIntervalOrLogScale(scale)
143-
&&model.get('alignTicks')
144-
&&model.get('interval')==null
145-
){
131+
132+
for(leti=axesIndices.length-1;i>=0;i--){// Reverse order
133+
constaxis=axes[+axesIndices[i]];
134+
if(axis.alignTo){
146135
axisNeedsAlign.push(axis);
147136
}
148137
else{
149-
niceScaleExtent(scale,model);
150-
if(isIntervalOrLogScale(scale)){// Can only align to interval or log axis.
151-
alignTo=axis;
152-
}
138+
niceScaleExtent(axis.scale,axis.model,axis.scale.getExtent());
153139
}
154140
};
155-
// All axes has set alignTicks. Pick the first one.
156-
// PENDING. Should we find the axis that both set interval, min, max and align to this one?
157-
if(axisNeedsAlign.length){
158-
if(!alignTo){
159-
alignTo=axisNeedsAlign.pop();
160-
niceScaleExtent(alignTo.scale,alignTo.model);
141+
each(axisNeedsAlign,axis=>{
142+
if(incapableOfAlignNeedFallback(axis,axis.alignToasAxis2D)){
143+
niceScaleExtent(axis.scale,axis.model,axis.scale.getExtent());
161144
}
162-
163-
each(axisNeedsAlign,axis=>{
145+
else{
164146
alignScaleTicks(
165147
axis.scaleasIntervalScale|LogScale,
148+
axis.scale.getExtent(),
166149
axis.model,
167-
alignTo.scaleasIntervalScale|LogScale
150+
axis.alignTo.scaleasIntervalScale|LogScale
168151
);
169-
});
170-
}
152+
}
153+
});
171154
}
172155

173156
updateAxisTicks(axesMap.x);
@@ -450,6 +433,9 @@ class Grid implements CoordinateSystemMaster {
450433
});
451434
});
452435

436+
prepareAlignToInCoordSysCreate(axesMap.x);
437+
prepareAlignToInCoordSysCreate(axesMap.y);
438+
453439
functioncreateAxisCreator(dimName: Cartesian2DDimensionName){
454440
returnfunction(axisModel: CartesianAxisModel,idx: number): void{
455441
if(!isAxisUsedInTheGrid(axisModel,gridModel)){
@@ -698,6 +684,66 @@ function canOnZeroToAxis(axis: Axis2D): boolean {
698684
returnaxis&&axis.type!=='category'&&axis.type!=='time'&&ifAxisCrossZero(axis);
699685
}
700686

687+
/**
688+
* [CAVEAT] This method is called before data processing stage.
689+
* Do not rely on any info that is determined afterward.
690+
*/
691+
functionprepareAlignToInCoordSysCreate(axes: Record<number,Axis2D>): void{
692+
// Axis is added in order of axisIndex.
693+
constaxesIndices=keys(axes);
694+
695+
letalignTo: Axis2D;
696+
constaxisNeedsAlign: Axis2D[]=[];
697+
698+
for(leti=axesIndices.length-1;i>=0;i--){// Reverse order
699+
constaxis=axes[+axesIndices[i]];
700+
if(
701+
isIntervalOrLogScale(axis.scale)
702+
// NOTE: `scale.hasBreaks()` is not available at this moment. Check it later.
703+
&&retrieveAxisBreaksOption(axis.model)==null
704+
// NOTE: `scale.getTicks()` is not available at this moment. Check it later.
705+
){
706+
// Request `alignTicks`.
707+
if((axis.modelasAxisBaseModel<NumericAxisBaseOptionCommon>).get('alignTicks')
708+
&&(axis.modelasAxisBaseModel<NumericAxisBaseOptionCommon>).get('interval')==null
709+
){
710+
axisNeedsAlign.push(axis);
711+
}
712+
else{
713+
// `alignTo` the last one that does not request `alignTicks`
714+
// (This rule is retained for backward compat).
715+
alignTo=axis;
716+
}
717+
}
718+
};
719+
// If all axes has set alignTicks, pick the first one as alignTo.
720+
// PENDING. Should we find the axis that both set interval, min, max and align to this one?
721+
// PENDING. Should we allow specifying alignTo via ec option?
722+
if(!alignTo){
723+
alignTo=axisNeedsAlign.pop();
724+
}
725+
if(alignTo){
726+
each(axisNeedsAlign,function(axis){
727+
axis.alignTo=alignTo;
728+
});
729+
}
730+
}
731+
732+
/**
733+
* This is just a defence code. They are unlikely to be actually `true`,
734+
* since these cases have been addressed in `prepareAlignToInCoordSysCreate`.
735+
*
736+
* Can not be called BEFORE "nice" performed.
737+
*/
738+
functionincapableOfAlignNeedFallback(targetAxis: Axis2D,alignTo: Axis2D): boolean{
739+
returntargetAxis.scale.hasBreaks()
740+
||alignTo.scale.hasBreaks()
741+
// Normally ticks length are more than 2 even when axis is blank.
742+
// But still guard for corner cases and possible changes.
743+
||alignTo.scale.getTicks().length<2;
744+
}
745+
746+
701747
functionupdateAxisTransform(axis: Axis2D,coordBase: number){
702748
constaxisExtent=axis.getExtent();
703749
constaxisExtentSum=axisExtent[0]+axisExtent[1];

‎src/coord/cartesian/defaultAxisExtentFromData.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ function shrinkAxisExtent(axisRecordMap: HashMap<AxisRecord>) {
241241
if(tarAxisExtent){
242242
constrawExtentResult=axisRecord.rawExtentResult;
243243
constrawExtentInfo=axisRecord.rawExtentInfo;
244-
// Shink the original extent.
244+
// Shrink the original extent.
245245
if(!rawExtentResult.minFixed&&tarAxisExtent[0]>rawExtentResult.min){
246246
rawExtentInfo.modifyDataMinMax('min',tarAxisExtent[0]);
247247
}

0 commit comments

Comments
 (0)