UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

370 lines (283 loc) • 12.4 kB
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); } }