[Plots] Allow user to change marker style (#3135)

* lineWidth is not supported in modern browsers

* allow marker shape selection in plot inspector

add shapes for canvas2d - point, cross, star

* change line style to line method in anticipation of adding true line style attribute

* add canvas2d circle marker shape

* allow point shapes for webGL plots

add circle shape

* refactor for clarity

* refactor shape to shape code

* add missing semi-colon

* helper function for marker options display in inspector

refactor for clarity

* access correct file

* add diamond marker shape

* add triangle shape to canvas

* add webgl draw triangle marker shape

* refactor fragment shader for clarity
This commit is contained in:
David Tsay 2020-07-09 13:14:32 -07:00 committed by GitHub
parent 0d9558b891
commit 80c20b3d05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 219 additions and 45 deletions

View File

@ -44,7 +44,7 @@
</li>
<li class="grid-row">
<div class="grid-cell label"
title="The line rendering style for this series.">Line Style</div>
title="The rendering method to join lines for this series.">Line Method</div>
<div class="grid-cell value">{{ {
'none': 'None',
'linear': 'Linear interpolation',
@ -56,7 +56,7 @@
<div class="grid-cell label"
title="Whether markers are displayed, and their size.">Markers</div>
<div class="grid-cell value">
{{series.get('markers') ? "Enabled: " + series.get('markerSize') + "px" : "Disabled"}}
{{ series.markerOptionsDisplayText() }}
</div>
</li>
<li class="grid-row">

View File

@ -52,7 +52,7 @@
</li>
<li class="grid-row">
<div class="grid-cell label"
title="The line rendering style for this series.">Line Style</div>
title="The rendering method to join lines for this series.">Line Method</div>
<div class="grid-cell value">
<select ng-model="form.interpolate">
<option value="none">None</option>
@ -64,12 +64,27 @@
<li class="grid-row">
<div class="grid-cell label"
title="Whether markers are displayed.">Markers</div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.markers"/></div>
<div class="grid-cell value">
<input type="checkbox" ng-model="form.markers"/>
<select
ng-show="form.markers"
ng-model="form.markerShape">
<option
ng-repeat="option in markerShapeOptions"
value="{{ option.value }}"
ng-selected="option.value == form.markerShape"
>
{{ option.name }}
</option>
</select>
</div>
</li>
<li class="grid-row">
<div class="grid-cell label"
title="Display markers visually denoting points in alarm.">Alarm Markers</div>
<div class="grid-cell value"><input type="checkbox" ng-model="form.alarmMarkers"/></div>
<div class="grid-cell value">
<input type="checkbox" ng-model="form.alarmMarkers"/>
</div>
</li>
<li class="grid-row" ng-show="form.markers || form.alarmMarkers">
<div class="grid-cell label"

View File

@ -371,7 +371,8 @@ function (
chartElement.getBuffer(),
chartElement.color().asRGBAArray(),
chartElement.count,
chartElement.series.get('markerSize')
chartElement.series.get('markerSize'),
chartElement.series.get('markerShape')
);
};
@ -395,9 +396,10 @@ function (
this.offset.yVal(highlight.point, highlight.series)
]),
color = highlight.series.get('color').asRGBAArray(),
pointCount = 1;
pointCount = 1,
shape = highlight.series.get('markerShape');
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE);
this.drawAPI.drawPoints(points, color, pointCount, HIGHLIGHT_SIZE, shape);
};
MCTChartController.prototype.drawRectangles = function () {

View File

@ -25,12 +25,14 @@ define([
'lodash',
'../configuration/Model',
'../lib/extend',
'EventEmitter'
'EventEmitter',
'../draw/MarkerShapes'
], function (
_,
Model,
extend,
EventEmitter
EventEmitter,
MARKER_SHAPES
) {
/**
@ -56,6 +58,7 @@ define([
* `linear` (points are connected via straight lines), or
* `stepAfter` (points are connected by steps).
* `markers`: boolean, whether or not this series should render with markers.
* `markerShape`: string, shape of markers.
* `markerSize`: number, size in pixels of markers for this series.
* `alarmMarkers`: whether or not to display alarm markers for this series.
* `stats`: An object that tracks the min and max y values observed in this
@ -101,6 +104,7 @@ define([
xKey: options.collection.plot.xAxis.get('key'),
yKey: range.key,
markers: true,
markerShape: 'point',
markerSize: 2.0,
alarmMarkers: true
};
@ -410,6 +414,18 @@ define([
} else {
this.filters = deepCopiedFilters;
}
},
markerOptionsDisplayText: function () {
const showMarkers = this.get('markers');
if (!showMarkers) {
return "Disabled";
}
const markerShapeKey = this.get('markerShape');
const markerShape = MARKER_SHAPES[markerShapeKey].label;
const markerSize = this.get('markerSize');
return `${markerShape}: ${markerSize}px`;
}
});

View File

@ -23,10 +23,12 @@
define([
'EventEmitter',
'../lib/eventHelpers'
'../lib/eventHelpers',
'./MarkerShapes'
], function (
EventEmitter,
eventHelpers
eventHelpers,
MARKER_SHAPES
) {
/**
@ -121,18 +123,17 @@ define([
buf,
color,
points,
pointSize
pointSize,
shape
) {
var i = 0,
offset = pointSize / 2;
const drawC2DShape = MARKER_SHAPES[shape].drawC2D.bind(this);
this.setColor(color);
for (; i < points; i++) {
this.c2d.fillRect(
this.x(buf[i * 2]) - offset,
this.y(buf[i * 2 + 1]) - offset,
pointSize,
for (let i = 0; i < points; i++) {
drawC2DShape(
this.x(buf[i * 2]),
this.y(buf[i * 2 + 1]),
pointSize
);
}

View File

@ -23,30 +23,64 @@
define([
'EventEmitter',
'../lib/eventHelpers'
'../lib/eventHelpers',
'./MarkerShapes'
], function (
EventEmitter,
eventHelpers
eventHelpers,
MARKER_SHAPES
) {
// WebGL shader sources (for drawing plain colors)
var FRAGMENT_SHADER = [
"precision mediump float;",
"uniform vec4 uColor;",
"void main(void) {",
"gl_FragColor = uColor;",
"}"
].join('\n'),
VERTEX_SHADER = [
"attribute vec2 aVertexPosition;",
"uniform vec2 uDimensions;",
"uniform vec2 uOrigin;",
"uniform float uPointSize;",
"void main(void) {",
"gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);",
"gl_PointSize = uPointSize;",
"}"
].join('\n');
const FRAGMENT_SHADER = `
precision mediump float;
uniform vec4 uColor;
uniform int uMarkerShape;
void main(void) {
gl_FragColor = uColor;
if (uMarkerShape > 1) {
vec2 clipSpacePointCoord = 2.0 * gl_PointCoord - 1.0;
if (uMarkerShape == 2) { // circle
float distance = length(clipSpacePointCoord);
if (distance > 1.0) {
discard;
}
} else if (uMarkerShape == 3) { // diamond
float distance = abs(clipSpacePointCoord.x) + abs(clipSpacePointCoord.y);
if (distance > 1.0) {
discard;
}
} else if (uMarkerShape == 4) { // triangle
float x = clipSpacePointCoord.x;
float y = clipSpacePointCoord.y;
float distance = 2.0 * x - 1.0;
float distance2 = -2.0 * x - 1.0;
if (distance > y || distance2 > y) {
discard;
}
}
}
}
`;
const VERTEX_SHADER = `
attribute vec2 aVertexPosition;
uniform vec2 uDimensions;
uniform vec2 uOrigin;
uniform float uPointSize;
void main(void) {
gl_Position = vec4(2.0 * ((aVertexPosition - uOrigin) / uDimensions) - vec2(1,1), 0, 1);
gl_PointSize = uPointSize;
}
`;
/**
* Create a draw api utilizing WebGL.
@ -90,6 +124,7 @@ define([
this.vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(this.vertexShader, VERTEX_SHADER);
this.gl.compileShader(this.vertexShader);
this.fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(this.fragmentShader, FRAGMENT_SHADER);
this.gl.compileShader(this.fragmentShader);
@ -105,6 +140,7 @@ define([
// shader programs (to pass values into shaders at draw-time)
this.aVertexPosition = this.gl.getAttribLocation(this.program, "aVertexPosition");
this.uColor = this.gl.getUniformLocation(this.program, "uColor");
this.uMarkerShape = this.gl.getUniformLocation(this.program, "uMarkerShape");
this.uDimensions = this.gl.getUniformLocation(this.program, "uDimensions");
this.uOrigin = this.gl.getUniformLocation(this.program, "uOrigin");
this.uPointSize = this.gl.getUniformLocation(this.program, "uPointSize");
@ -114,9 +150,6 @@ define([
// Create a buffer to holds points which will be drawn
this.buffer = this.gl.createBuffer();
// Use a line width of 2.0 for legibility
this.gl.lineWidth(2.0);
// Enable blending, for smoothness
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
@ -138,14 +171,18 @@ define([
((v - this.origin[1]) / this.dimensions[1]) * this.height;
};
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points) {
DrawWebGL.prototype.doDraw = function (drawType, buf, color, points, shape) {
if (this.isContextLost) {
return;
}
const shapeCode = MARKER_SHAPES[shape] ? MARKER_SHAPES[shape].drawWebGL : 0;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, buf, this.gl.DYNAMIC_DRAW);
this.gl.vertexAttribPointer(this.aVertexPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.uniform4fv(this.uColor, color);
this.gl.uniform1i(this.uMarkerShape, shapeCode)
this.gl.drawArrays(drawType, 0, points);
};
@ -210,12 +247,12 @@ define([
* Draw the buffer as points.
*
*/
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize) {
DrawWebGL.prototype.drawPoints = function (buf, color, points, pointSize, shape) {
if (this.isContextLost) {
return;
}
this.gl.uniform1f(this.uPointSize, pointSize);
this.doDraw(this.gl.POINTS, buf, color, points);
this.doDraw(this.gl.POINTS, buf, color, points, shape);
};
/**

View File

@ -0,0 +1,90 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([], function () {
/**
* @label string (required) display name of shape
* @drawWebGL integer (unique, required) index provided to WebGL Fragment Shader
* @drawC2D function (required) canvas2d draw function
*/
const MARKER_SHAPES = {
point: {
label: 'Point',
drawWebGL: 1,
drawC2D: function (x, y, size) {
const offset = size / 2;
this.c2d.fillRect(x - offset, y - offset, size, size);
}
},
circle: {
label: 'Circle',
drawWebGL: 2,
drawC2D: function (x, y, size) {
const radius = size / 2;
this.c2d.beginPath();
this.c2d.arc(x, y, radius, 0, 2 * Math.PI, false);
this.c2d.closePath();
this.c2d.fill();
}
},
diamond: {
label: 'Diamond',
drawWebGL: 3,
drawC2D: function (x, y, size) {
const offset = size / 2;
const top = [x, y + offset];
const right = [x + offset, y];
const bottom = [x, y - offset];
const left = [x - offset, y];
this.c2d.beginPath();
this.c2d.moveTo(...top);
this.c2d.lineTo(...right);
this.c2d.lineTo(...bottom);
this.c2d.lineTo(...left);
this.c2d.closePath();
this.c2d.fill();
}
},
triangle: {
label: 'Triangle',
drawWebGL: 4,
drawC2D: function (x, y, size) {
const offset = size / 2;
const v1 = [x, y - offset];
const v2 = [x - offset, y + offset];
const v3 = [x + offset, y + offset];
this.c2d.beginPath();
this.c2d.moveTo(...v1);
this.c2d.lineTo(...v2);
this.c2d.lineTo(...v3);
this.c2d.closePath();
this.c2d.fill();
}
}
};
return MARKER_SHAPES;
});

View File

@ -22,9 +22,11 @@
define([
'./PlotModelFormController',
'../draw/MarkerShapes',
'lodash'
], function (
PlotModelFormController,
MARKER_SHAPES,
_
) {
@ -93,6 +95,13 @@ define([
value: o.key
};
});
this.$scope.markerShapeOptions = Object.entries(MARKER_SHAPES)
.map(([key, obj]) => {
return {
name: obj.label,
value: key
};
});
},
fields: [
@ -108,6 +117,10 @@ define([
modelProp: 'markers',
objectPath: dynamicPathForKey('markers')
},
{
modelProp: 'markerShape',
objectPath: dynamicPathForKey('markerShape')
},
{
modelProp: 'markerSize',
coerce: Number,