Skip to content

Commit 2aaddbc

Browse files
committed
Add direction markers
1 parent 5b6d31d commit 2aaddbc

File tree

4 files changed

+129
-22
lines changed

4 files changed

+129
-22
lines changed

src/drawable.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ import * as util from './util'
22

33
export enum System { None, Cartesian, Geographic, Complex };
44

5+
export class DirectionMarker {
6+
constructor(
7+
public x: number,
8+
public y: number,
9+
public angle: number)
10+
{}
11+
}
12+
513
export class PlotlyData {
614
constructor(
715
public traces: any[],
@@ -10,12 +18,14 @@ export class PlotlyData {
1018
public colorId: number)
1119
{}
1220

21+
public directions: DirectionMarker[] = [];
22+
1323
static empty(colorId: number) {
1424
return new PlotlyData([], System.None, new util.LonInterval(0, 0), colorId);
1525
}
1626
}
1727

18-
function createTrace(xs: number[] | undefined, ys: number[], system: System): any {
28+
function createTrace(xs: number[], ys: number[], system: System): any {
1929
return system === System.Geographic
2030
? {
2131
lon: xs === undefined ? Array.from(Array(ys.length).keys()) : xs,
@@ -26,6 +36,17 @@ function createTrace(xs: number[] | undefined, ys: number[], system: System): an
2636
};
2737
}
2838

39+
function direction(xs: number[], ys: number[], reversed: boolean, system: System) : DirectionMarker | undefined {
40+
const isGeographic = system === System.Geographic;
41+
const s = !reversed ? util.firstSegment(xs, ys, isGeographic) : util.rFirstSegment(xs, ys, isGeographic);
42+
if (s === undefined)
43+
return undefined;
44+
const a = util.azimuth(s.xs[0], s.ys[0], s.xs[1], s.ys[1], isGeographic);
45+
if (a === undefined)
46+
return undefined;
47+
return new DirectionMarker(s.xs[0], s.ys[0], a);
48+
}
49+
2950
export class Drawable {
3051
toPlotly(colorId: number): PlotlyData {
3152
return PlotlyData.empty(colorId);
@@ -36,7 +57,7 @@ export enum PlotStyle { LinesAndMarkers, Lines, Markers, Bars };
3657

3758
export class Plot extends Drawable {
3859
constructor(
39-
public readonly xs: number[] | undefined,
60+
public readonly xs: number[],
4061
public readonly ys: number[],
4162
public readonly system: System,
4263
public plotStyle: PlotStyle = PlotStyle.LinesAndMarkers) {
@@ -62,6 +83,12 @@ export class Plot extends Drawable {
6283
trace.mode = "lines+markers";
6384
}
6485
result.traces = [trace];
86+
if (this.plotStyle === PlotStyle.Lines || this.plotStyle === PlotStyle.LinesAndMarkers) {
87+
const dir = direction(this.xs, this.ys, false, this.system);
88+
if (dir !== undefined) {
89+
result.directions.push(dir);
90+
}
91+
}
6592
}
6693
if (this.system === System.Geographic)
6794
result.lonInterval = util.LonInterval.fromPoints(this.xs);
@@ -122,13 +149,21 @@ export class Ring extends Drawable {
122149
constructor(
123150
public readonly xs: number[],
124151
public readonly ys: number[],
152+
public readonly reversed: boolean,
125153
public readonly system: System,
126154
private readonly _isBox: boolean = false) {
127155
super();
128-
// naiively close the ring
129-
if (xs.length > 0 && ys.length > 0) {
130-
this.xs.push(xs[0]);
131-
this.ys.push(ys[0]);
156+
// close the ring
157+
if (ys.length >= 2) {
158+
// TODO: In geographic this may not detect the same points
159+
if (xs[0] != xs[xs.length - 1] || ys[0] != ys[ys.length - 1]) {
160+
this.xs.push(xs[0]);
161+
this.ys.push(ys[0]);
162+
}
163+
}
164+
if (reversed) {
165+
this.xs.reverse();
166+
this.ys.reverse();
132167
}
133168
}
134169
toPlotly(colorId: number): PlotlyData {
@@ -149,8 +184,15 @@ export class Ring extends Drawable {
149184
mode: this._isBox ? "lines" : "lines+markers",
150185
fill: 'toself'
151186
}];
152-
if (this.system === System.Geographic)
187+
if (this.system === System.Geographic) {
153188
result.lonInterval = util.LonInterval.fromPoints(this.xs);
189+
}
190+
if (this.ys.length > 0) {
191+
const dir = direction(this.xs, this.ys, this.reversed, this.system);
192+
if (dir !== undefined) {
193+
result.directions.push(dir);
194+
}
195+
}
154196
return result;
155197
}
156198
};
@@ -171,6 +213,7 @@ export class Polygon extends Drawable {
171213
result.traces[0].y.push(null);
172214
result.traces[0].x = result.traces[0].x.concat(d.traces[0].x);
173215
result.traces[0].y = result.traces[0].y.concat(d.traces[0].y);
216+
result.directions = result.directions.concat(d.directions);
174217
}
175218
} else {
176219
// geographic has to be treated separately because typical way of dealing with holes does not work
@@ -193,6 +236,7 @@ export class Polygon extends Drawable {
193236
result.traces[1].lon = result.traces[1].lon.concat(d.traces[0].lon);
194237
result.traces[1].lat = result.traces[1].lat.concat(d.traces[0].lat);
195238
closeHoles = true;
239+
result.directions = result.directions.concat(d.directions);
196240
}
197241
if (closeHoles) {
198242
result.traces[0].lon.push(result.traces[0].lon[0]);

src/extension.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { GraphicalWatch, GraphicalWatchEventData, GraphicalWatchEventType, GraphicalWatchVariable } from './graphicalwatch';
2-
import { Debugger, Endianness } from './debugger';
2+
import { Debugger, Endianness, Language } from './debugger';
33
import { Webview } from './webview';
44
import * as colors from './colors.json'
55
import * as draw from './drawable'
@@ -13,10 +13,11 @@ async function handleVariable(dbg: Debugger, gwVariable: GraphicalWatchVariable)
1313
if (type !== undefined) {
1414
const variable: load.Variable = new load.Variable(gwVariable.name, type);
1515
const loader = await load.getLoader(dbg, variable);
16-
if (loader instanceof load.Loader) {
16+
if (loader !== undefined) {
1717
const drawable = await loader.load(dbg, variable);
18-
if (drawable !== undefined)
18+
if (drawable !== undefined) {
1919
return [type, drawable.toPlotly(gwVariable.color)];
20+
}
2021
}
2122
return ['unknown (' + type + ')', draw.PlotlyData.empty(gwVariable.color)];
2223
}
@@ -80,6 +81,23 @@ function prepareMessage(potlyData: draw.PlotlyData[], colorTheme: vscode.ColorTh
8081
trace.fillcolor = colorStr + '55';
8182
trace.hoverinfo = d.system === draw.System.Geographic ? "lon+lat" : "x+y";
8283
message.plots[plotId].traces.push(trace);
84+
85+
for (let dir of d.directions) {
86+
let dirTrace : any = d.system === draw.System.Geographic ?
87+
{ lon: [dir.x], lat: [dir.y] } :
88+
{ x: [dir.x], y: [dir.y] };
89+
dirTrace.type = trace.type;
90+
dirTrace.mode = "markers";
91+
dirTrace.hoverinfo = "skip";
92+
dirTrace.marker = {
93+
size: 10,
94+
symbol: 'triangle-up',
95+
angleref: 'up',
96+
angle: dir.angle,
97+
color: colorStr + 'CC'
98+
};
99+
message.plots[plotId].traces.push(dirTrace);
100+
}
83101
}
84102
message.plots[plotId].lonintervals.push(d.lonInterval);
85103
}

src/loader.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ export class Numbers extends ContainerLoader {
384384
const el = parseFloat(elVal);
385385
ys.push(el);
386386
}
387-
return new draw.Plot(undefined, ys, draw.System.None);
387+
return new draw.Plot(util.indexesArray(ys), ys, draw.System.None);
388388
}
389389
}
390390

@@ -408,7 +408,7 @@ export class Values extends ContainerLoader {
408408
return undefined
409409
ys.push(n);
410410
}
411-
return new draw.Plot(undefined, ys, draw.System.None);
411+
return new draw.Plot(util.indexesArray(ys), ys, draw.System.None);
412412
}
413413
}
414414

@@ -618,7 +618,7 @@ export class Box2 extends Geometry {
618618
// TODO: This logic should rather be implemented in draw
619619
function loadBox(minx: number, miny: number, maxx: number, maxy: number, system: draw.System): draw.Ring {
620620
if (system !== draw.System.Geographic) {
621-
return new draw.Ring([minx, minx, maxx, maxx], [miny, maxy, maxy, miny], system, true);
621+
return new draw.Ring([minx, minx, maxx, maxx], [miny, maxy, maxy, miny], false, system, true);
622622
}
623623
else {
624624
miny = util.bounded(miny, -90, 90);
@@ -639,7 +639,7 @@ function loadBox(minx: number, miny: number, maxx: number, maxy: number, system:
639639
xs.push(x); ys.push(miny);
640640
}
641641
xs.push(minx); ys.push(miny);
642-
return new draw.Ring(xs, ys, system, true);
642+
return new draw.Ring(xs, ys, false, system, true);
643643
}
644644
}
645645

@@ -658,16 +658,12 @@ export class Ring extends PointsRange {
658658
async load(dbg: debug.Debugger, variable: Variable): Promise<draw.Drawable | undefined> {
659659
const plot = await super.load(dbg, variable);
660660
if (plot instanceof draw.Plot && plot.xs) {
661-
const cw = await this._isCw(dbg, variable);
662661
// TODO: This doesn't work well with classes like Shapely Polygon
663662
// where is_ccw member indicates the actual order of internal rings.
664663
// Such rings should be reversed based on the exterior ring.
665664
// Here rings are reversed locally.
666-
if (! cw) {
667-
plot.xs.reverse();
668-
plot.ys.reverse();
669-
}
670-
return new draw.Ring(plot.xs, plot.ys, plot.system);
665+
const isCw = await this._isCw(dbg, variable);
666+
return new draw.Ring(plot.xs, plot.ys, !isCw, plot.system);
671667
}
672668
else
673669
return undefined;

src/util.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1+
export const r2d = 180 / Math.PI;
2+
export const d2r = Math.PI / 180;
3+
14
export function bounded(v: number, min: number, max: number) {
25
return Math.min(Math.max(v, min), max);
36
}
47

5-
export function average(xs: number[] | undefined): number {
6-
return xs && xs.length > 0
8+
export function average(xs: number[]): number {
9+
return xs.length > 0
710
? xs.reduce((prev, curr) => prev + curr) / xs.length
811
: 0;
912
}
1013

14+
export function indexesArray(ys: number[]): number[] {
15+
return Array.from(Array(ys.length).keys());
16+
}
17+
1118
// (-180, 180]
1219
export function sLon(lon: number) : number {
1320
const n = lon > 180 ? ((lon + 180) % 360) - 180 :
@@ -110,3 +117,45 @@ export class LonInterval {
110117
return new LonInterval(min, max);
111118
}
112119
}
120+
121+
export function firstSegment(xs: number[], ys: number[], isGeographic: boolean): any {
122+
const n = Math.min(xs.length, ys.length);
123+
for (let i = 0 ; i < n - 1 ; ++i) {
124+
if (xs[i] !== xs[i + 1] || ys[i] !== ys[i + 1])
125+
return {
126+
xs: [xs[i], xs[i + 1]],
127+
ys: [ys[i], ys[i + 1]]
128+
};
129+
}
130+
return undefined;
131+
}
132+
133+
export function rFirstSegment(xs: number[], ys: number[], isGeographic: boolean): any {
134+
const n = Math.min(xs.length, ys.length);
135+
for (let i = n - 1 ; i > 0 ; --i) {
136+
if (xs[i] !== xs[i - 1] || ys[i] !== ys[i - 1])
137+
return {
138+
xs: [xs[i], xs[i - 1]],
139+
ys: [ys[i], ys[i - 1]]
140+
};
141+
}
142+
return undefined;
143+
}
144+
145+
export function azimuth(x0: number, y0: number, x1: number, y1: number, isGeographic: boolean): number | undefined {
146+
const dx = x1 - x0;
147+
const dy = y1 - y0;
148+
let num = 0;
149+
let den = 0;
150+
if (! isGeographic || dx * dx + dy * dy <= 1) {
151+
num = dx;
152+
den = dy;
153+
}
154+
else {
155+
num = Math.sin(dx * d2r);
156+
den = Math.tan(y1 * d2r) * Math.cos(y0 * d2r)
157+
- Math.sin(y0 * d2r) * Math.cos(dx * d2r);
158+
}
159+
const result = Math.atan2(num, den);
160+
return isNaN(result) ? undefined : result * r2d;
161+
}

0 commit comments

Comments
 (0)