Skip to content

Commit a4e6cb3

Browse files
authored
Merge pull request #328 from Dessia-tech/feat/logscale
[6] Feat/logscale
2 parents 2e5af3c + 047763a commit a4e6cb3

File tree

11 files changed

+82
-24
lines changed

11 files changed

+82
-24
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Add
1010
- Integer axes only show integer ticks
1111
- Handle date as continuous value on axes
12+
- Allow to log scale axes
1213

1314
### Fix
1415
- Add reference_path to all Primitive / Elementary drawing Objects

cypress/e2e/axes.cy.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ describe('Axis', function() {
8787
expect(axis.maxValue, "maxValue").to.be.closeTo(8.7, 0.05);
8888
});
8989

90+
it("should scale axis in a logarithmic way", function() {
91+
const axis = new Axis(vector, boundingBox, origin, end, name, initScale, nTicks);
92+
expect(vector.map(element => Math.floor(axis.relativeToAbsolute(element))), "projected values").to.deep.equal([4, 27, 50, 72, 95]);
93+
axis.switchLogScale(vector);
94+
expect(vector.map(element => Math.floor(axis.relativeToAbsolute(element))), "projected log values").to.deep.equal([-19, -12, -8, -5, -3]);
95+
});
96+
9097
it('should update axis with translation and style', function() {
9198
const axis = new Axis(vector, boundingBox, origin, end, name, initScale, nTicks);
9299
const viewPoint = new Vertex(0, 0);
Lines changed: 2 additions & 2 deletions
Loading

plot_data/templates.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
onclick="plot_data.switchOrientation()"> Change Disposition </button>
5252
<button name="toogleAxes" value="OK" type="button"
5353
onclick="plot_data.htmlToggleAxes()"> Show / Hide Axes </button>
54+
<button name="logScale" value="OK" type="button"
55+
onclick="plot_data.switchLogScale()"> Log Scale</button>
5456
$specific_buttons
5557
<hr style="border-top: 2px;">
5658
</div>

script/parallel_plot.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727
elements[-1]['length'] = None
2828

2929
plot_data_object = plot_data.ParallelPlot(elements=elements,
30-
axes=['long middle_attribute name ' * 15, 'mass' * 30, 'color', 'length', 'shape',
31-
'long middle_attribute name ' * 10, 'long right attribute name ' * 7, "date"])
30+
axes=['long middle_attribute name ' * 15, 'mass' * 30, 'color', 'length',
31+
'shape', 'long middle_attribute name ' * 10, 'long right attribute name ' * 7,
32+
"date"])
3233

3334
# The line above shows the minimum requirements for creating a
3435
# parallel plot. However, many options are available for further customization.

script/plot_scatter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
elements = []
1111
SHAPES = ['1-2-3-4-5-6-7-8-9round', 'square', 'triangle', 'ellipse']
1212
COLORS = [RED, BLUE, GREEN, YELLOW, ORANGE, VIOLET]
13-
for i in range(1000):
13+
for i in range(500):
1414
random_shape = SHAPES[random.randint(0, len(SHAPES) - 1)]
1515
random_color = COLORS[random.randint(0, len(SHAPES) - 1)]
1616
elements.append({'mass': random.uniform(0, 50),

src/axes.ts

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class Axis extends Shape {
2424
public ticksFontsize: number = 12;
2525
public isDiscrete: boolean = true;
2626
public isInteger: boolean = false;
27+
public logScale: boolean = false;
2728
public isDate: boolean = false;
2829

2930
public drawPath: Path2D;
@@ -152,6 +153,30 @@ export class Axis extends Shape {
152153

153154
public toggleView(): void { this.visible = !this.visible }
154155

156+
private getLogBoundaries(vector: number[]): [number, number] {
157+
const positiveVector = vector.filter(value => value > 0);
158+
const min = Math.min(...positiveVector);
159+
const max = Math.max(...positiveVector);
160+
if (max <= 0) {
161+
this.logScale = false;
162+
return [this.initMinValue, this.initMaxValue]
163+
}
164+
if (this.logScale) {
165+
return [
166+
this.initMinValue > 0 ? Math.log10(this.initMinValue) : Math.log10(min),
167+
this.initMaxValue > 0 ? Math.log10(this.initMaxValue) : Math.log10(max)
168+
]
169+
} else return [10 ** this.initMinValue, 10 ** this.initMaxValue]
170+
}
171+
172+
public switchLogScale(vector: number[]): void {
173+
if (!this.isDiscrete) {
174+
this.logScale = !this.logScale;
175+
[this.initMinValue, this.initMaxValue] = this.getLogBoundaries(vector);
176+
this.updateTicks();
177+
}
178+
}
179+
155180
private discretePropertiesFromVector(vector: any[]): void {
156181
if (vector) {
157182
if (vector.length != 0) {
@@ -186,8 +211,8 @@ export class Axis extends Shape {
186211
public resetScale(): void {
187212
this.minValue = this.initMinValue;
188213
this.maxValue = this.initMaxValue;
189-
this._previousMin = this.initMinValue;
190-
this._previousMax = this.initMaxValue;
214+
this._previousMin = this.minValue;
215+
this._previousMax = this.maxValue;
191216
this.updateTicks();
192217
}
193218

@@ -251,13 +276,19 @@ export class Axis extends Shape {
251276
}
252277

253278
public absoluteToRelative(value: string | number): number {
254-
const numberedValue = this.stringToValue(value);
255-
return this.isVertical ? (numberedValue - this.transformMatrix.f) / this.transformMatrix.d : (numberedValue - this.transformMatrix.e) / this.transformMatrix.a
279+
let numberedValue = this.stringToValue(value);
280+
const projectedValue = this.isVertical
281+
? (numberedValue - this.transformMatrix.f) / this.transformMatrix.a
282+
: (numberedValue - this.transformMatrix.e) / this.transformMatrix.a;
283+
return this.logScale ? 10 ** projectedValue : projectedValue;
256284
}
257285

258286
public relativeToAbsolute(value: string | number): number {
259-
const numberedValue = this.stringToValue(value);
260-
return this.isVertical ? numberedValue * this.transformMatrix.d + this.transformMatrix.f : numberedValue * this.transformMatrix.a + this.transformMatrix.e
287+
let numberedValue = this.stringToValue(value);
288+
if (this.logScale) numberedValue = Math.log10(numberedValue);
289+
return this.isVertical
290+
? numberedValue * this.transformMatrix.d + this.transformMatrix.f
291+
: numberedValue * this.transformMatrix.a + this.transformMatrix.e;
261292
}
262293

263294
public normedValue(value: number): number { return value / this.interval }
@@ -311,7 +342,7 @@ export class Axis extends Shape {
311342
private getTickIncrement(): number {
312343
const rawIncrement = this.isDiscrete ? 1 : Axis.nearestFive((this.maxValue - this.minValue) / this.nTicks);
313344
const logExponent = Math.floor(Math.log10(rawIncrement));
314-
if (this.isInteger) return this.integerTickIncrement(rawIncrement, logExponent);
345+
if (this.isInteger && !this.logScale) return this.integerTickIncrement(rawIncrement, logExponent);
315346
return this.floatTickIncrement(rawIncrement, logExponent);
316347
}
317348

@@ -351,7 +382,7 @@ export class Axis extends Shape {
351382
if (ticks.slice(0)[0] < this.minValue) ticks.splice(0, 1);
352383
if (ticks.slice(-1)[0] >= this.maxValue) ticks.splice(-1, 1);
353384
this.updateTickPrecision(increment, ticks);
354-
return ticks
385+
return this.logScale ? ticks.map(tick => 10 ** tick) : ticks
355386
}
356387

357388
public drawWhenIsVisible(context: CanvasRenderingContext2D): void {
@@ -458,8 +489,7 @@ export class Axis extends Shape {
458489
if (count == tick && this.labels[count]) {
459490
text = this.labels[count];
460491
count++;
461-
}
462-
else text = '';
492+
} else text = '';
463493
}
464494
ticksText.push(this.computeTickText(context, text, tickTextParams, point, pointHTMatrix));
465495
})
@@ -497,7 +527,9 @@ export class Axis extends Shape {
497527
}
498528

499529
protected drawTickPoint(context: CanvasRenderingContext2D, tick: number, vertical: boolean, HTMatrix: DOMMatrix, color: string): Point {
500-
const center = new Vertex(tick * Number(!vertical), tick * Number(vertical)).transform(HTMatrix);
530+
const center = this.logScale ?
531+
new Vertex(Math.log10(tick) * Number(!vertical), Math.log10(tick) * Number(vertical)).transform(HTMatrix) :
532+
new Vertex(tick * Number(!vertical), tick * Number(vertical)).transform(HTMatrix);
501533
const point = new Point(center.x, center.y, SIZE_AXIS_END, this.tickMarker, this.tickOrientation, color);
502534
point.draw(context);
503535
return point
@@ -514,7 +546,7 @@ export class Axis extends Shape {
514546
}
515547

516548
private getValueToDrawMatrix(): DOMMatrix {
517-
const scale = this.drawLength / this.interval;
549+
let scale = this.drawLength / this.interval;
518550
if (this.isInverted) {
519551
return new DOMMatrix([
520552
-scale, 0, 0, -scale,
@@ -561,13 +593,18 @@ export class Axis extends Shape {
561593
}
562594
}
563595

596+
private restrictRubberBandTranslation(downValue: number, currentValue: number): [number, number] {
597+
if (!this.logScale || this.rubberBand.lastValues.x + currentValue - downValue > 0 || !this.rubberBand.isTranslating) return [downValue, currentValue]
598+
return [downValue, downValue - this.rubberBand.lastValues.x]
599+
}
600+
564601
public mouseMoveClickedArrow(mouseCoords: Vertex): void {
565602
const downValue = this.absoluteToRelative(this.isVertical ? this.mouseClick.y : this.mouseClick.x);
566603
const currentValue = this.absoluteToRelative(this.isVertical ? mouseCoords.y : mouseCoords.x);
567604
if (!this.rubberBand.isClicked) {
568605
this.rubberBand.minValue = Math.min(downValue, currentValue);
569606
this.rubberBand.maxValue = Math.max(downValue, currentValue);
570-
} else this.rubberBand.mouseMove(downValue, currentValue);
607+
} else this.rubberBand.mouseMove(...this.restrictRubberBandTranslation(downValue, currentValue));
571608
}
572609

573610
public mouseMoveClickedTitle(mouseCoords: Vertex): void { }
@@ -643,13 +680,13 @@ export class Axis extends Shape {
643680

644681
public updateScale(viewPoint: Vertex, scaling: Vertex, translation: Vertex): void {
645682
const HTMatrix = this.transformMatrix;
646-
let center = (viewPoint.x - HTMatrix.e) / HTMatrix.a;
683+
let center = (viewPoint.x - HTMatrix.e) / HTMatrix.a;;
647684
let offset = translation.x;
648-
let scale = scaling.x;
685+
let scale = this.logScale ? 10 ** (scaling.x - 1) : scaling.x;
649686
if (this.isVertical) {
650687
center = (viewPoint.y - HTMatrix.f) / HTMatrix.d;
651688
offset = translation.y;
652-
scale = scaling.y;
689+
scale = this.logScale ? 10 ** (scaling.y - 1) : scaling.y;
653690
}
654691
this.minValue = (this._previousMin - center) / scale + center - offset / HTMatrix.a;
655692
this.maxValue = (this._previousMax - center) / scale + center - offset / HTMatrix.a;

src/figures.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,10 @@ export class Scatter extends Frame {
595595
}
596596

597597
private projectPoint(xCoord: number, yCoord: number): [number, number] {
598-
return [xCoord * this.relativeMatrix.a + this.relativeMatrix.e, yCoord * this.relativeMatrix.d + this.relativeMatrix.f]
598+
return [
599+
this.axes[0].relativeToAbsolute(xCoord) * this.canvasMatrix.a + this.canvasMatrix.e,
600+
this.axes[1].relativeToAbsolute(yCoord) * this.canvasMatrix.d + this.canvasMatrix.f
601+
]
599602
}
600603

601604
private distanceMatrix(xCoords: number[], yCoords: number[]): number[][] {

src/functions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ export function isInteger(value: number): boolean {
4242
}
4343

4444
export function isIntegerArray(array: number[]): boolean {
45-
for (let value of array) { if (!isInteger(value) && value != null) return false };
46-
return true
45+
return !array.some(value => !isInteger(value) && value != null)
4746
}
4847

4948
export function getTenPower(value: number): number {

src/remoteFigure.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,12 @@ export class RemoteFigure {
424424

425425
public switchOrientation(): void {}
426426

427+
public switchLogScale(): void {
428+
this.axes.forEach(axis => axis.switchLogScale(this.features.get(axis.name) as number[]));
429+
this.resetScales();
430+
this.draw();
431+
}
432+
427433
public togglePoints(): void {}
428434

429435
public toggleAxes(): void {

0 commit comments

Comments
 (0)