@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
370 lines (283 loc) • 12.4 kB
JavaScript
import { assert } from "../../../core/assert.js";
import { isDefined } from "../../../core/process/matcher/isDefined.js";
import LabelView from "../../common/LabelView.js";
import SVG from "../../SVG.js";
import View from "../../View.js";
import EmptyView from "../EmptyView.js";
import ImageView from "../image/ImageView.js";
export class WindRoseDiagram extends View {
/**
*
* @param {{name:string,icon?:(View|string),value?:number,tooltip?:function, tooltipContext?:*}[]} axes
* @param {number} value_mark_stride
* @param {number} angular_offset
* @param {number} radius
* @param {number} value_max
* @param {DomTooltipManager} [tooltips]
*/
constructor({
axes,
angular_offset = 0,
radius_inner = 200,
radius_outer = radius_inner,
value_max = 10,
value_mark_stride = value_max / 5,
icon_size = 32,
tooltips,
backplate_stroke_width = 2,
backplate_stroke_color = 'rgba(0,0,0,1)',
backplate_fill_color = 'rgba(99,99,99,1)',
display_backplate = true,
graph_stroke_width = 1,
graph_stroke_color = 'rgba(0,0,0,1)',
graph_fill_color = 'rgba(0,0,0,0.1)',
mark_stroke_width = 1,
mark_stroke_color = 'rgba(0,0,0,0.13)',
display_marks = true,
origin_radius = 8,
origin_stroke_width = 0,
origin_stroke_color = 'transparent',
origin_fill_color = 'red',
display_origin = true,
classList = []
}) {
super();
this.__axes = axes;
this.__settings_value_max = value_max;
this.__settings_value_mark_stride = value_mark_stride;
this.__settings_angular_offset = angular_offset;
this.__settings_icon_size = icon_size;
this.__settings_graph_stroke_width = graph_stroke_width;
this.__settings_graph_stroke_color = graph_stroke_color;
this.__settings_graph_fill_color = graph_fill_color;
this.__settings_backplate_stroke_width = backplate_stroke_width;
this.__settings_backplate_stroke_color = backplate_stroke_color;
this.__settings_backplate_fill_color = backplate_fill_color;
this.__settings_display_backplate = display_backplate;
this.__settings_mark_stroke_width = mark_stroke_width;
this.__settings_mark_stroke_color = mark_stroke_color;
this.__settings_display_marks = display_marks;
this.__settings_origin_radius = origin_radius;
this.__settings_origin_stroke_width = origin_stroke_width;
this.__settings_origin_stroke_color = origin_stroke_color;
this.__settings_origin_fill_color = origin_fill_color;
this.__settings_display_origin = display_origin;
this.__settings_radius_inner = radius_inner;
this.__settings_radius_outer = radius_outer;
this.__tooltips = tooltips;
this.__values = new Float32Array(axes.length);
this.el = document.createElement('div');
this.addClass('ui-wind-rose-diagram-view');
this.addClasses(classList);
const has_icons = axes.some(isDefined);
const size = radius_outer * 2 + (has_icons ? icon_size * (Math.SQRT1_2 + 0.5) * 2 : 0);
this.size.set(size, size);
this.__vCanvas = new EmptyView({
classList: ['canvas']
});
this.addChild(this.__vCanvas);
this.__vGraph = new EmptyView({
classList: ["graph"],
el: SVG.createElement('svg'),
attr: {
width: this.size.x * 2,
height: this.size.y * 2
},
css: {
position: 'absolute',
left: 0,
top: 0,
overflow: 'visible'
}
});
this.addChild(this.__vGraph);
this.__svg_graph_path = SVG.createElement('path');
this.__svg_graph_path.classList.add('graph-path-main');
this.__svg_graph_path.setAttribute('stroke-width', this.__settings_graph_stroke_width);
this.__svg_graph_path.setAttribute('stroke', this.__settings_graph_stroke_color);
this.__svg_graph_path.setAttribute('fill', this.__settings_graph_fill_color);
this.__vGraph.el.appendChild(this.__svg_graph_path);
this.on.linked.add(this.draw, this);
// process input axes
for (let i = 0; i < axes.length; i++) {
const axis = axes[i];
assert.defined(axis, 'axis');
assert.notNull(axis, 'axis');
if (axis.value !== undefined) {
this.set_axis_value(i, axis.value);
}
}
}
/**
*
* @param {number} index
* @param {number} value
*/
set_axis_value(index, value) {
assert.isNonNegativeInteger(index, 'index');
assert.isFiniteNumber(value, 'value');
assert.lessThan(index, this.__axes.length, 'index overflow');
this.__values[index] = value;
}
draw() {
this.draw_canvas();
this.draw_values();
}
__get_axis_count() {
return this.__axes.length;
}
__get_axis_angle(index) {
const axis_count = this.__get_axis_count();
return index * Math.PI * (2 / axis_count) + this.__settings_angular_offset - Math.PI * 0.5;
}
__create_loop_path(radius) {
const axis_count = this.__get_axis_count();
const offset_x = this.size.x * 0.5;
const offset_y = this.size.y * 0.5;
let path = 'M';
for (let i = 0; i < axis_count; i++) {
const angle = this.__get_axis_angle(i);
const cos = Math.cos(angle);
const sin = Math.sin(angle);
path += cos * radius + offset_x;
path += ','
path += sin * radius + offset_y;
if (i < axis_count - 1) {
path += 'L'
} else {
path += 'Z';
}
}
return path;
}
draw_canvas() {
const container = this.__vCanvas;
container.removeAllChildren();
const axis_count = this.__get_axis_count();
const offset_x = this.size.x * 0.5;
const offset_y = this.size.y * 0.5;
// draw background and marks
const svg_main = SVG.createElement('svg');
svg_main.setAttribute('width', String(this.size.x));
svg_main.setAttribute('height', String(this.size.y));
if (this.__settings_display_backplate) {
const svg_path = SVG.createElement('path');
svg_path.classList.add('backplate');
svg_path.setAttribute('d', this.__create_loop_path(this.__settings_radius_outer + this.__settings_backplate_stroke_width / 2));
svg_path.setAttribute('stroke-width', String(this.__settings_backplate_stroke_width));
svg_path.setAttribute('stroke', this.__settings_backplate_stroke_color);
svg_path.setAttribute('fill', this.__settings_backplate_fill_color);
svg_main.appendChild(svg_path);
}
if (this.__settings_display_marks) {
for (let i = this.__settings_value_mark_stride; i < this.__settings_value_max; i += this.__settings_value_mark_stride) {
const svg_path = SVG.createElement('path');
svg_path.classList.add('mark');
const f = i / this.__settings_value_max;
const radius = f * this.__settings_radius_inner;
svg_path.setAttribute('d', this.__create_loop_path(radius));
svg_path.setAttribute('fill', 'none');
svg_path.setAttribute('stroke-width', this.__settings_mark_stroke_width);
svg_path.setAttribute('stroke', this.__settings_mark_stroke_color);
svg_main.appendChild(svg_path);
}
}
if (this.__settings_display_origin) {
const svg_path = SVG.createElement('path');
svg_path.classList.add('origin');
svg_path.setAttribute('d', this.__create_loop_path(this.__settings_origin_radius));
svg_path.setAttribute('fill', this.__settings_origin_fill_color);
svg_path.setAttribute('stroke-width', this.__settings_origin_stroke_width);
svg_path.setAttribute('stroke', this.__settings_origin_stroke_color);
svg_main.appendChild(svg_path);
}
const vSvgMain = new EmptyView({
el: svg_main,
css: {
position: 'absolute',
top: 0,
left: 0
}
});
container.addChild(vSvgMain);
for (let i = 0; i < axis_count; i++) {
// draw axis name
const axis = this.__axes[i];
if (axis === undefined) {
continue;
}
const angle = this.__get_axis_angle(i);
const cos = Math.cos(angle);
const sin = Math.sin(angle);
if (axis.name !== undefined) {
const vName = new LabelView(axis.name, {
classList: ['axis-name'],
css: {
position: "absolute",
top: "0",
left: "0"
}
});
const label_offset = this.__settings_radius_inner;
vName.position.set(
cos * label_offset + offset_x,
sin * label_offset + offset_y
);
if (this.__tooltips !== undefined && axis.tooltip !== undefined) {
this.__tooltips.add(vName, axis.tooltip, axis.tooltipContext);
}
container.addChild(vName);
}
// process icon
if (axis.icon !== undefined) {
let vIcon;
if (typeof axis.icon === "string") {
vIcon = new ImageView(axis.icon);
} else {
vIcon = axis.icon;
}
const icon_size = this.__settings_icon_size;
vIcon.css({
position: 'absolute',
left: `-${icon_size * 0.5}px`,
top: `-${icon_size * 0.5}px`
});
const icon_offset = this.__settings_radius_outer + icon_size * Math.SQRT1_2;
vIcon.position.set(
cos * icon_offset + offset_x,
sin * icon_offset + offset_y
);
vIcon.size.set(
icon_size,
icon_size
);
if (this.__tooltips !== undefined && axis.tooltip !== undefined) {
this.__tooltips.add(vIcon, axis.tooltip, axis.tooltipContext);
}
container.addChild(vIcon);
}
}
}
draw_values() {
const axis_count = this.__get_axis_count();
const offset_x = this.size.x * 0.5;
const offset_y = this.size.y * 0.5;
let path = 'M';
for (let i = 0; i < axis_count; i++) {
const angle = this.__get_axis_angle(i);
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const f = this.__values[i] / this.__settings_value_max;
const radius = this.__settings_radius_inner * f;
path += cos * radius + offset_x;
path += ','
path += sin * radius + offset_y;
if (i < axis_count - 1) {
path += 'L'
} else {
path += 'Z';
}
}
this.__svg_graph_path.setAttribute('d', path);
}
}