|
@@ -1,255 +1,288 @@
|
|
|
-import { draggable } from 'element-plus/es/components/color-picker/src/utils/draggable.mjs';
|
|
|
+// ColorBar.js
|
|
|
import * as THREE from 'three';
|
|
|
-import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
|
|
|
+import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
|
|
|
|
|
|
export class ColorBar {
|
|
|
- constructor(scene, camera, containerElement, options = {}) {
|
|
|
- this.scene = scene;
|
|
|
- this.camera = camera;
|
|
|
- this.containerElement = containerElement;
|
|
|
- this.options = {
|
|
|
- width: 10,
|
|
|
- height: 300,
|
|
|
- position: 'right',
|
|
|
- title: 'Value',
|
|
|
- colorMap: 'rainbow',
|
|
|
- min: 0,
|
|
|
- max: 1,
|
|
|
- draggable: true,
|
|
|
- ...options
|
|
|
- };
|
|
|
-
|
|
|
- this.init();
|
|
|
- if (this.options.draggable) {
|
|
|
- this.dragCleanup = this.enableDrag();
|
|
|
- }
|
|
|
- }
|
|
|
+ constructor(scene, camera, containerElement, options = {}) {
|
|
|
+ this.scene = scene;
|
|
|
+ this.camera = camera;
|
|
|
+ this.containerElement = containerElement;
|
|
|
+ this.options = {
|
|
|
+ width: 10,
|
|
|
+ height: 300,
|
|
|
+ position: 'right',
|
|
|
+ X: 0.85,
|
|
|
+ Y: 0.85,
|
|
|
+ title: 'Value', // 默认标题
|
|
|
+ colorMap: 'rainbow',
|
|
|
+ min: 0,
|
|
|
+ max: 1,
|
|
|
+ gradientStart: [0, 0, 1],
|
|
|
+ gradientEnd: [1, 0, 0],
|
|
|
+ font: 'Arial',
|
|
|
+ fontSize: 12,
|
|
|
+ bold: false,
|
|
|
+ italic: false,
|
|
|
+ titleFont: 'Arial',
|
|
|
+ titleFontSize: 16,
|
|
|
+ titleBold: false,
|
|
|
+ titleItalic: false,
|
|
|
+ draggable: true,
|
|
|
+ check1: true,
|
|
|
+ showTitle: true,
|
|
|
+ dataFormat: 'scientific',
|
|
|
+ ...options,
|
|
|
+ };
|
|
|
|
|
|
- init() {
|
|
|
- // 创建主容器
|
|
|
- this.container = document.createElement('div');
|
|
|
- this.container.style.position = 'absolute';
|
|
|
- this.container.style.pointerEvents = 'none';
|
|
|
- this.container.style.display = 'flex';
|
|
|
- this.container.style.flexDirection = 'column'; // 垂直堆叠,标题在上
|
|
|
-
|
|
|
- // 标题元素(顶部)
|
|
|
- this.titleElement = document.createElement('div');
|
|
|
- this.titleElement.style.textAlign = 'center';
|
|
|
- this.titleElement.style.fontWeight = 'bold';
|
|
|
- this.titleElement.style.fontSize = '11px';
|
|
|
- this.titleElement.style.marginBottom = '5px'; // 与颜色条间隔
|
|
|
- this.titleElement.textContent = this.options.title;
|
|
|
-
|
|
|
- // 子容器(颜色条和标签水平排列)
|
|
|
- this.subContainer = document.createElement('div');
|
|
|
- this.subContainer.style.display = 'flex';
|
|
|
- this.subContainer.style.alignItems = 'flex-start';
|
|
|
-
|
|
|
- // 颜色条元素
|
|
|
- this.barElement = document.createElement('div');
|
|
|
- this.barElement.className = 'color-bar';
|
|
|
- this.barElement.style.width = `${this.options.width}px`;
|
|
|
- this.barElement.style.height = `${this.options.height}px`;
|
|
|
- this.barElement.style.background = this.createGradient();
|
|
|
- this.barElement.style.borderRadius = '2px';
|
|
|
- this.barElement.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
|
|
|
-
|
|
|
- // 标签容器(右侧垂直排列)
|
|
|
- this.labelsElement = document.createElement('div');
|
|
|
- this.labelsElement.style.display = 'flex';
|
|
|
- this.labelsElement.style.flexDirection = 'column';
|
|
|
- this.labelsElement.style.justifyContent = 'space-between';
|
|
|
- this.labelsElement.style.height = `${this.options.height}px`;
|
|
|
- this.labelsElement.style.marginLeft = '8px';
|
|
|
- this.labelsElement.style.fontSize = '11px';
|
|
|
- this.labelsElement.innerHTML = `
|
|
|
- <span>${this.options.max.toFixed(2)}</span>
|
|
|
- <span>${((this.options.min + this.options.max) / 2).toFixed(2)}</span>
|
|
|
- <span>${this.options.min.toFixed(2)}</span>
|
|
|
- `;
|
|
|
-
|
|
|
- // 组装 DOM
|
|
|
- this.subContainer.appendChild(this.barElement);
|
|
|
- this.subContainer.appendChild(this.labelsElement);
|
|
|
- this.container.appendChild(this.titleElement);
|
|
|
- this.container.appendChild(this.subContainer);
|
|
|
- this.containerElement.appendChild(this.container);
|
|
|
-
|
|
|
- // 设置位置
|
|
|
- this.updatePosition();
|
|
|
- window.addEventListener('resize', () => this.updatePosition());
|
|
|
-
|
|
|
- if (this.options.draggable) {
|
|
|
- this.container.style.cursor = 'move';
|
|
|
- this.container.style.userSelect = 'none';
|
|
|
- this.container.style.pointerEvents = 'auto';
|
|
|
- this.container.title = '拖拽移动位置';
|
|
|
- }
|
|
|
+ this.init();
|
|
|
+ if (this.options.draggable) {
|
|
|
+ this.dragCleanup = this.enableDrag();
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // 修改渐变生成以考虑颜色范围
|
|
|
- createGradient() {
|
|
|
- const stops = []
|
|
|
- const rangeMin = this.options.min
|
|
|
- const rangeMax = this.options.max
|
|
|
- const dataMin = this.options.dataMin || rangeMin
|
|
|
- const dataMax = this.options.dataMax || rangeMax
|
|
|
-
|
|
|
- for (let i = 0; i <= 100; i += 10) {
|
|
|
- // 计算在数据范围内的相对位置
|
|
|
- const t = i / 100
|
|
|
- const dataValue = dataMin + t * (dataMax - dataMin)
|
|
|
- // 计算在颜色范围内的相对位置
|
|
|
- const colorT = (dataValue - rangeMin) / (rangeMax - rangeMin)
|
|
|
- const color = this.getColor(Math.max(0, Math.min(1, colorT)))
|
|
|
- stops.push(`${color} ${i}%`)
|
|
|
- }
|
|
|
- return `linear-gradient(to top, ${stops.join(', ')})`
|
|
|
- }
|
|
|
+ init() {
|
|
|
+ this.container = document.createElement('div');
|
|
|
+ this.container.style.position = 'absolute';
|
|
|
+ this.container.style.pointerEvents = 'none';
|
|
|
+ this.container.style.display = this.options.check1 ? 'flex' : 'none';
|
|
|
+ this.container.style.flexDirection = this.options.position === 'horizontal' ? 'row' : 'column';
|
|
|
|
|
|
- getColor(t) {
|
|
|
- const [r, g, b] = this.options.colorMap === 'rainbow'
|
|
|
- ? this.rainbowColorMap(t)
|
|
|
- : this.jetColorMap(t);
|
|
|
- return `rgb(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)})`;
|
|
|
- }
|
|
|
+ this.titleElement = document.createElement('div');
|
|
|
+ this.titleElement.style.fontFamily = this.options.titleFont;
|
|
|
+ this.titleElement.style.textAlign = 'center';
|
|
|
+ this.titleElement.style.fontWeight = this.options.titleBold ? 'bold' : 'normal';
|
|
|
+ this.titleElement.style.fontStyle = this.options.titleItalic ? 'italic' : 'normal';
|
|
|
+ this.titleElement.style.fontSize = `${this.options.titleFontSize}px`;
|
|
|
+ this.titleElement.style.marginBottom = this.options.position === 'horizontal' ? '0' : '5px';
|
|
|
+ this.titleElement.style.marginRight = this.options.position === 'horizontal' ? '5px' : '0';
|
|
|
+ this.titleElement.style.display = this.options.showTitle ? 'block' : 'none'; // 控制标题显示
|
|
|
+ this.titleElement.textContent = this.options.title || 'Value'; // 默认标题
|
|
|
|
|
|
- rainbowColorMap(t) {
|
|
|
- if (t < 0.25) {
|
|
|
- return [0, t * 4, 1];
|
|
|
- } else if (t < 0.5) {
|
|
|
- return [0, 1, 1 - (t - 0.25) * 4];
|
|
|
- } else if (t < 0.75) {
|
|
|
- return [(t - 0.5) * 4, 1, 0];
|
|
|
- } else {
|
|
|
- return [1, 1 - (t - 0.75) * 4, 0];
|
|
|
- }
|
|
|
- }
|
|
|
+ this.subContainer = document.createElement('div');
|
|
|
+ this.subContainer.style.display = 'flex';
|
|
|
+ this.subContainer.style.alignItems = 'flex-start';
|
|
|
+ this.subContainer.style.flexDirection = this.options.position === 'horizontal' ? 'column' : 'row';
|
|
|
+
|
|
|
+ this.barElement = document.createElement('div');
|
|
|
+ this.barElement.className = 'color-bar';
|
|
|
+ this.barElement.style.width = this.options.position === 'horizontal' ? `${this.options.height}px` : `${this.options.width}px`;
|
|
|
+ this.barElement.style.height = this.options.position === 'horizontal' ? `${this.options.width}px` : `${this.options.height}px`;
|
|
|
+ this.barElement.style.background = this.createGradient();
|
|
|
+ this.barElement.style.borderRadius = '2px';
|
|
|
+ this.barElement.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
|
|
|
+
|
|
|
+ this.labelsElement = document.createElement('div');
|
|
|
+ this.labelsElement.style.display = 'flex';
|
|
|
+ this.labelsElement.style.fontFamily = this.options.font;
|
|
|
+ this.labelsElement.style.fontWeight = this.options.bold ? 'bold' : 'normal';
|
|
|
+ this.labelsElement.style.fontStyle = this.options.italic ? 'italic' : 'normal';
|
|
|
+ this.labelsElement.style.flexDirection = this.options.position === 'horizontal' ? 'row' : 'column';
|
|
|
+ this.labelsElement.style.justifyContent = 'space-between';
|
|
|
+ this.labelsElement.style.height = this.options.position === 'horizontal' ? 'auto' : `${this.options.height}px`;
|
|
|
+ this.labelsElement.style.width = this.options.position === 'horizontal' ? `${this.options.height}px` : 'auto';
|
|
|
+ this.labelsElement.style.marginLeft = this.options.position === 'horizontal' ? '0' : '8px';
|
|
|
+ this.labelsElement.style.marginTop = this.options.position === 'horizontal' ? '8px' : '0';
|
|
|
+ this.labelsElement.style.fontSize = `${this.options.fontSize}px`;
|
|
|
+ this.updateLabels();
|
|
|
+
|
|
|
+ this.subContainer.appendChild(this.barElement);
|
|
|
+ this.subContainer.appendChild(this.labelsElement);
|
|
|
+ this.container.appendChild(this.titleElement);
|
|
|
+ this.container.appendChild(this.subContainer);
|
|
|
+ this.containerElement.appendChild(this.container);
|
|
|
+
|
|
|
+ this.updatePosition();
|
|
|
+ window.addEventListener('resize', () => this.updatePosition());
|
|
|
|
|
|
- jetColorMap(t) {
|
|
|
- if (t < 0.125) {
|
|
|
- return [0, 0, 0.5 + t * 4];
|
|
|
- } else if (t < 0.375) {
|
|
|
- return [0, (t - 0.125) * 4, 1];
|
|
|
- } else if (t < 0.625) {
|
|
|
- return [(t - 0.375) * 4, 1, 1 - (t - 0.375) * 4];
|
|
|
- } else if (t < 0.875) {
|
|
|
- return [1, 1 - (t - 0.625) * 4, 0];
|
|
|
- } else {
|
|
|
- return [1 - (t - 0.875) * 4, 0, 0];
|
|
|
- }
|
|
|
+ if (this.options.draggable) {
|
|
|
+ this.container.style.cursor = 'move';
|
|
|
+ this.container.style.userSelect = 'none';
|
|
|
+ this.container.style.pointerEvents = 'auto';
|
|
|
+ this.container.title = '拖拽移动位置';
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ createGradient() {
|
|
|
+ const stops = [];
|
|
|
+ const rangeMin = this.options.min;
|
|
|
+ const rangeMax = this.options.max;
|
|
|
+ const dataRange = rangeMax - rangeMin || 1;
|
|
|
|
|
|
- updatePosition() {
|
|
|
- if (!this.containerElement || !this.container) return;
|
|
|
-
|
|
|
- const { position, width, height } = this.options;
|
|
|
- const padding = 15;
|
|
|
- const containerRect = this.containerElement.getBoundingClientRect();
|
|
|
- const titleHeight = 20; // 估算标题高度
|
|
|
-
|
|
|
- let x, y;
|
|
|
- switch (position) {
|
|
|
- case 'left':
|
|
|
- x = padding;
|
|
|
- y = (containerRect.height - height - titleHeight) / 2; // 考虑标题高度
|
|
|
- break;
|
|
|
- case 'right':
|
|
|
- default:
|
|
|
- x = containerRect.width - width - padding - 40;
|
|
|
- y = (containerRect.height - height - titleHeight) / 2;
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- this.container.style.left = `${x}px`;
|
|
|
- this.container.style.top = `${y}px`;
|
|
|
+ for (let i = 0; i <= 100; i += 10) {
|
|
|
+ const t = i / 100;
|
|
|
+ const value = rangeMin + t * dataRange;
|
|
|
+ if (rangeMin === 0 && rangeMax === 0) {
|
|
|
+ stops.push(`rgb(0, 0, 255) ${i}%`);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ const colorPos = (value - rangeMin) / dataRange;
|
|
|
+ const color = this.getColor(Math.max(0, Math.min(1, colorPos)));
|
|
|
+ stops.push(`${color} ${i}%`);
|
|
|
}
|
|
|
|
|
|
-update(min, max, title, options = {}) {
|
|
|
- // 更新基础属性
|
|
|
- this.options.min = min;
|
|
|
- this.options.max = max;
|
|
|
- this.options.title = title || this.options.title;
|
|
|
- this.titleElement.textContent = this.options.title;
|
|
|
-
|
|
|
- // 更新颜色范围(从options.colorRange获取)
|
|
|
- if (options.colorRange) {
|
|
|
- this.options.minColor = options.colorRange.minColor || [0, 0, 1];
|
|
|
- this.options.maxColor = options.colorRange.maxColor || [1, 0, 0];
|
|
|
+ return `linear-gradient(${this.options.position === 'horizontal' ? 'to right' : 'to top'}, ${stops.join(', ')})`;
|
|
|
+ }
|
|
|
+
|
|
|
+ getColor(t) {
|
|
|
+ const [r, g, b] = this.options.colorMap === 'rainbow'
|
|
|
+ ? this.rainbowColorMap(t)
|
|
|
+ : this.jetColorMap(t);
|
|
|
+ return `rgb(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)})`;
|
|
|
+ }
|
|
|
+
|
|
|
+ rainbowColorMap(t) {
|
|
|
+ if (t < 0.25) return [0, 0, 1];
|
|
|
+ else if (t < 0.5) return [0, (t - 0.25) * 4, 1];
|
|
|
+ else if (t < 0.75) return [(t - 0.5) * 4, 1, 1 - (t - 0.5) * 4];
|
|
|
+ else return [1, 1 - (t - 0.75) * 4, 0];
|
|
|
+ }
|
|
|
+
|
|
|
+ jetColorMap(t) {
|
|
|
+ if (t < 0.125) return [0, 0, 0.5 + t * 4];
|
|
|
+ else if (t < 0.375) return [0, (t - 0.125) * 4, 1];
|
|
|
+ else if (t < 0.625) return [(t - 0.375) * 4, 1, 1 - (t - 0.375) * 4];
|
|
|
+ else if (t < 0.875) return [1, 1 - (t - 0.625) * 4, 0];
|
|
|
+ else return [1 - (t - 0.875) * 4, 0, 0];
|
|
|
}
|
|
|
|
|
|
- // 更新渐变和标签
|
|
|
- this.barElement.style.background = this.createGradient();
|
|
|
- this.updateLabels(options.intervals);
|
|
|
-}
|
|
|
-
|
|
|
- // 支持自定义间隔
|
|
|
- updateLabels(intervals) {
|
|
|
- if (intervals && intervals.length > 0) {
|
|
|
- // 使用传入的间隔
|
|
|
- this.labelsElement.innerHTML = intervals
|
|
|
- .map(val => `<span>${Number(val).toFixed(2)}</span>`)
|
|
|
- .join('')
|
|
|
- } else {
|
|
|
- // 默认三等分
|
|
|
- const mid = (this.options.min + this.options.max) / 2
|
|
|
- this.labelsElement.innerHTML = `
|
|
|
- <span>${this.options.max.toFixed(2)}</span>
|
|
|
- <span>${mid.toFixed(2)}</span>
|
|
|
- <span>${this.options.min.toFixed(2)}</span>
|
|
|
- `
|
|
|
- }
|
|
|
+ updatePosition() {
|
|
|
+ if (!this.containerElement || !this.container) return;
|
|
|
+
|
|
|
+ const { width, height, X, Y } = this.options;
|
|
|
+ const containerRect = this.containerElement.getBoundingClientRect();
|
|
|
+ const titleHeight = this.options.showTitle ? this.options.titleFontSize + 5 : 0;
|
|
|
+ const x = X * (containerRect.width - width);
|
|
|
+ const y = Y * (containerRect.height - height - titleHeight);
|
|
|
+
|
|
|
+ this.container.style.left = `${x}px`;
|
|
|
+ this.container.style.top = `${y}px`;
|
|
|
+ }
|
|
|
+
|
|
|
+ update(min, max, title, options = {}) {
|
|
|
+ this.options.min = min;
|
|
|
+ this.options.max = max;
|
|
|
+ this.options.title = title || this.options.title || 'Value'; // 确保标题不为空
|
|
|
+ this.options.font = options.font || this.options.font;
|
|
|
+ this.options.fontSize = options.fontSize || this.options.fontSize;
|
|
|
+ this.options.bold = options.bold !== undefined ? options.bold : this.options.bold;
|
|
|
+ this.options.italic = options.italic !== undefined ? options.italic : this.options.italic;
|
|
|
+ this.options.titleFont = options.titleFont || this.options.titleFont;
|
|
|
+ this.options.titleFontSize = options.titleFontSize || this.options.titleFontSize;
|
|
|
+ this.options.titleBold = options.titleBold !== undefined ? options.titleBold : this.options.titleBold;
|
|
|
+ this.options.titleItalic = options.titleItalic !== undefined ? options.titleItalic : this.options.titleItalic;
|
|
|
+ this.options.width = options.width || this.options.width;
|
|
|
+ this.options.height = options.height || this.options.height;
|
|
|
+ this.options.position = options.position || this.options.position;
|
|
|
+ this.options.X = options.X || this.options.X;
|
|
|
+ this.options.Y = options.Y || this.options.Y;
|
|
|
+ this.options.draggable = options.draggable !== undefined ? options.draggable : this.options.draggable;
|
|
|
+ this.options.check1 = options.check1 !== undefined ? options.check1 : this.options.check1;
|
|
|
+ this.options.showTitle = options.showTitle !== undefined ? options.showTitle : this.options.showTitle;
|
|
|
+ this.options.dataFormat = options.dataFormat || this.options.dataFormat;
|
|
|
+ this.options.gradientStart = options.colorRange?.minColor || this.options.gradientStart;
|
|
|
+ this.options.gradientEnd = options.colorRange?.maxColor || this.options.gradientEnd;
|
|
|
+
|
|
|
+ this.container.style.display = this.options.check1 ? 'flex' : 'none';
|
|
|
+ this.container.style.flexDirection = this.options.position === 'horizontal' ? 'row' : 'column';
|
|
|
+ this.titleElement.style.display = this.options.showTitle ? 'block' : 'none';
|
|
|
+ this.titleElement.textContent = this.options.showTitle ? this.options.title : '';
|
|
|
+ this.titleElement.style.fontFamily = this.options.titleFont;
|
|
|
+ this.titleElement.style.fontSize = `${this.options.titleFontSize}px`;
|
|
|
+ this.titleElement.style.fontWeight = this.options.titleBold ? 'bold' : 'normal';
|
|
|
+ this.titleElement.style.fontStyle = this.options.titleItalic ? 'italic' : 'normal';
|
|
|
+ this.barElement.style.width = this.options.position === 'horizontal' ? `${this.options.height}px` : `${this.options.width}px`;
|
|
|
+ this.barElement.style.height = this.options.position === 'horizontal' ? `${this.options.width}px` : `${this.options.height}px`;
|
|
|
+ this.barElement.style.background = this.createGradient();
|
|
|
+ this.labelsElement.style.fontFamily = this.options.font;
|
|
|
+ this.labelsElement.style.fontSize = `${this.options.fontSize}px`;
|
|
|
+ this.labelsElement.style.fontWeight = this.options.bold ? 'bold' : 'normal';
|
|
|
+ this.labelsElement.style.fontStyle = this.options.italic ? 'italic' : 'normal';
|
|
|
+ this.labelsElement.style.flexDirection = this.options.position === 'horizontal' ? 'row' : 'column';
|
|
|
+ this.labelsElement.style.height = this.options.position === 'horizontal' ? 'auto' : `${this.options.height}px`;
|
|
|
+ this.labelsElement.style.width = this.options.position === 'horizontal' ? `${this.options.height}px` : 'auto';
|
|
|
+ this.labelsElement.style.marginLeft = this.options.position === 'horizontal' ? '0' : '8px';
|
|
|
+ this.labelsElement.style.marginTop = this.options.position === 'horizontal' ? '8px' : '0';
|
|
|
+ if (options.intervals) {
|
|
|
+ this.updateLabels(options.intervals, options.precision);
|
|
|
}
|
|
|
+ this.updatePosition();
|
|
|
|
|
|
- enableDrag() {
|
|
|
- let isDragging = false;
|
|
|
- let offsetX, offsetY;
|
|
|
-
|
|
|
- const onMouseDown = (e) => {
|
|
|
- isDragging = true;
|
|
|
- const rect = this.container.getBoundingClientRect();
|
|
|
- offsetX = e.clientX - rect.left;
|
|
|
- offsetY = e.clientY - rect.top;
|
|
|
- e.stopPropagation();
|
|
|
- e.preventDefault();
|
|
|
- };
|
|
|
-
|
|
|
- const onMouseMove = (e) => {
|
|
|
- if (!isDragging) return;
|
|
|
- const containerRect = this.containerElement.getBoundingClientRect();
|
|
|
- const maxX = containerRect.width - this.container.offsetWidth;
|
|
|
- const maxY = containerRect.height - this.container.offsetHeight;
|
|
|
- let newX = e.clientX - offsetX - containerRect.left;
|
|
|
- let newY = e.clientY - offsetY - containerRect.top;
|
|
|
- newX = Math.max(0, Math.min(newX, maxX));
|
|
|
- newY = Math.max(0, Math.min(newY, maxY));
|
|
|
- this.container.style.left = `${newX}px`;
|
|
|
- this.container.style.top = `${newY}px`;
|
|
|
- };
|
|
|
-
|
|
|
- const onMouseUp = () => {
|
|
|
- isDragging = false;
|
|
|
- };
|
|
|
-
|
|
|
- this.container.addEventListener('mousedown', onMouseDown);
|
|
|
- document.addEventListener('mousemove', onMouseMove);
|
|
|
- document.addEventListener('mouseup', onMouseUp);
|
|
|
-
|
|
|
- return () => {
|
|
|
- this.container.removeEventListener('mousedown', onMouseDown);
|
|
|
- document.removeEventListener('mousemove', onMouseMove);
|
|
|
- document.removeEventListener('mouseup', onMouseUp);
|
|
|
- };
|
|
|
+ if (this.options.draggable && !this.dragCleanup) {
|
|
|
+ this.dragCleanup = this.enableDrag();
|
|
|
+ } else if (!this.options.draggable && this.dragCleanup) {
|
|
|
+ this.dragCleanup();
|
|
|
+ this.dragCleanup = null;
|
|
|
+ this.container.style.cursor = 'default';
|
|
|
+ this.container.style.pointerEvents = 'none';
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- dispose() {
|
|
|
- if (this.dragCleanup) this.dragCleanup();
|
|
|
- this.container.remove();
|
|
|
- window.removeEventListener('resize', () => this.updatePosition());
|
|
|
- if (this.labelObject) {
|
|
|
- this.scene.remove(this.labelObject);
|
|
|
- this.labelObject = null;
|
|
|
- }
|
|
|
+ updateLabels(intervals, precision = 2) {
|
|
|
+ if (intervals && intervals.length > 0) {
|
|
|
+ this.labelsElement.innerHTML = intervals
|
|
|
+ .map((val) => `<span>${this.options.dataFormat === 'scientific' ? Number(val).toExponential(precision) : Number(val).toFixed(precision)}</span>`)
|
|
|
+ .join('');
|
|
|
+ } else {
|
|
|
+ const mid = (this.options.min + this.options.max) / 2;
|
|
|
+ const format = this.options.dataFormat === 'scientific' ? (val) => val.toExponential(precision) : (val) => val.toFixed(precision);
|
|
|
+ this.labelsElement.innerHTML = `
|
|
|
+ <span>${format(this.options.max)}</span>
|
|
|
+ <span>${format(mid)}</span>
|
|
|
+ <span>${format(this.options.min)}</span>
|
|
|
+ `;
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ enableDrag() {
|
|
|
+ let isDragging = false;
|
|
|
+ let offsetX, offsetY;
|
|
|
+
|
|
|
+ const onMouseDown = (e) => {
|
|
|
+ isDragging = true;
|
|
|
+ const rect = this.container.getBoundingClientRect();
|
|
|
+ offsetX = e.clientX - rect.left;
|
|
|
+ offsetY = e.clientY - rect.top;
|
|
|
+ e.stopPropagation();
|
|
|
+ e.preventDefault();
|
|
|
+ };
|
|
|
+
|
|
|
+ const onMouseMove = (e) => {
|
|
|
+ if (!isDragging) return;
|
|
|
+ const containerRect = this.containerElement.getBoundingClientRect();
|
|
|
+ const maxX = containerRect.width - this.container.offsetWidth;
|
|
|
+ const maxY = containerRect.height - this.container.offsetHeight;
|
|
|
+ let newX = e.clientX - offsetX - containerRect.left;
|
|
|
+ let newY = e.clientY - offsetY - containerRect.top;
|
|
|
+ newX = Math.max(0, Math.min(newX, maxX));
|
|
|
+ newY = Math.max(0, Math.min(newY, maxY));
|
|
|
+ this.container.style.left = `${newX}px`;
|
|
|
+ this.container.style.top = `${newY}px`;
|
|
|
+ this.options.X = newX / (containerRect.width - this.options.width);
|
|
|
+ this.options.Y = newY / (containerRect.height - this.options.height);
|
|
|
+ };
|
|
|
+
|
|
|
+ const onMouseUp = () => {
|
|
|
+ isDragging = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ this.container.addEventListener('mousedown', onMouseDown);
|
|
|
+ document.addEventListener('mousemove', onMouseMove);
|
|
|
+ document.addEventListener('mouseup', onMouseUp);
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ this.container.removeEventListener('mousedown', onMouseDown);
|
|
|
+ document.removeEventListener('mousemove', onMouseMove);
|
|
|
+ document.removeEventListener('mouseup', onMouseUp);
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ dispose() {
|
|
|
+ if (this.dragCleanup) this.dragCleanup();
|
|
|
+ this.container.remove();
|
|
|
+ window.removeEventListener('resize', () => this.updatePosition());
|
|
|
+ if (this.labelObject) {
|
|
|
+ this.scene.remove(this.labelObject);
|
|
|
+ this.labelObject = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|