@grunwaldlab/heat-tree 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -15
- package/dist/heat-tree.es.min.js +1906 -286
- package/dist/heat-tree.es.min.js.map +1 -1
- package/dist/heat-tree.iife.min.js +8 -1
- package/dist/heat-tree.iife.min.js.map +1 -1
- package/dist/heat-tree.umd.min.js +8 -1
- package/dist/heat-tree.umd.min.js.map +1 -1
- package/package.json +3 -2
package/dist/heat-tree.es.min.js
CHANGED
|
@@ -824,7 +824,7 @@ function extend(parent, definition) {
|
|
|
824
824
|
for (var key in definition) prototype[key] = definition[key];
|
|
825
825
|
return prototype;
|
|
826
826
|
}
|
|
827
|
-
function Color() {
|
|
827
|
+
function Color$1() {
|
|
828
828
|
}
|
|
829
829
|
var darker = 0.7;
|
|
830
830
|
var brighter = 1 / darker;
|
|
@@ -979,7 +979,7 @@ var named = {
|
|
|
979
979
|
yellow: 16776960,
|
|
980
980
|
yellowgreen: 10145074
|
|
981
981
|
};
|
|
982
|
-
define(Color, color, {
|
|
982
|
+
define(Color$1, color, {
|
|
983
983
|
copy(channels) {
|
|
984
984
|
return Object.assign(new this.constructor(), this, channels);
|
|
985
985
|
},
|
|
@@ -1019,7 +1019,7 @@ function rgba(r, g, b, a) {
|
|
|
1019
1019
|
return new Rgb(r, g, b, a);
|
|
1020
1020
|
}
|
|
1021
1021
|
function rgbConvert(o) {
|
|
1022
|
-
if (!(o instanceof Color)) o = color(o);
|
|
1022
|
+
if (!(o instanceof Color$1)) o = color(o);
|
|
1023
1023
|
if (!o) return new Rgb();
|
|
1024
1024
|
o = o.rgb();
|
|
1025
1025
|
return new Rgb(o.r, o.g, o.b, o.opacity);
|
|
@@ -1033,7 +1033,7 @@ function Rgb(r, g, b, opacity) {
|
|
|
1033
1033
|
this.b = +b;
|
|
1034
1034
|
this.opacity = +opacity;
|
|
1035
1035
|
}
|
|
1036
|
-
define(Rgb, rgb, extend(Color, {
|
|
1036
|
+
define(Rgb, rgb, extend(Color$1, {
|
|
1037
1037
|
brighter(k) {
|
|
1038
1038
|
k = k == null ? brighter : Math.pow(brighter, k);
|
|
1039
1039
|
return new Rgb(this.r * k, this.g * k, this.b * k, this.opacity);
|
|
@@ -1086,7 +1086,7 @@ function hsla(h, s, l, a) {
|
|
|
1086
1086
|
}
|
|
1087
1087
|
function hslConvert(o) {
|
|
1088
1088
|
if (o instanceof Hsl) return new Hsl(o.h, o.s, o.l, o.opacity);
|
|
1089
|
-
if (!(o instanceof Color)) o = color(o);
|
|
1089
|
+
if (!(o instanceof Color$1)) o = color(o);
|
|
1090
1090
|
if (!o) return new Hsl();
|
|
1091
1091
|
if (o instanceof Hsl) return o;
|
|
1092
1092
|
o = o.rgb();
|
|
@@ -1111,7 +1111,7 @@ function Hsl(h, s, l, opacity) {
|
|
|
1111
1111
|
this.l = +l;
|
|
1112
1112
|
this.opacity = +opacity;
|
|
1113
1113
|
}
|
|
1114
|
-
define(Hsl, hsl, extend(Color, {
|
|
1114
|
+
define(Hsl, hsl, extend(Color$1, {
|
|
1115
1115
|
brighter(k) {
|
|
1116
1116
|
k = k == null ? brighter : Math.pow(brighter, k);
|
|
1117
1117
|
return new Hsl(this.h, this.s, this.l * k, this.opacity);
|
|
@@ -2869,7 +2869,7 @@ function zoom() {
|
|
|
2869
2869
|
};
|
|
2870
2870
|
return zoom2;
|
|
2871
2871
|
}
|
|
2872
|
-
const styles = ".ht-widget{display:flex;flex-direction:column;width:100%;height:100%}.ht-toolbar{flex:0 0 auto;margin-bottom:4px;display:flex;flex-direction:column;position:relative}.ht-toggle-container{position:absolute;top:0;right:0;
|
|
2872
|
+
const styles = ".ht-widget{display:flex;flex-direction:column;width:100%;height:100%}.ht-toolbar{flex:0 0 auto;margin-bottom:4px;display:flex;flex-direction:column;position:relative}.ht-toggle-container{position:absolute;top:0;right:0;padding:4px 8px;pointer-events:none}.ht-control-panel-toggle{background-color:transparent;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;gap:4px;transition:opacity .2s;pointer-events:auto}.ht-control-panel-toggle:hover{opacity:.7}.ht-control-panel-toggle .ht-toggle-arrow{transition:transform .3s}.ht-control-panel-toggle.collapsed .ht-toggle-arrow{transform:rotate(180deg)}.ht-control-panel-toggle .ht-hamburger-icon{color:#333}.ht-collapsible-panel{max-height:1000px;transition:max-height .3s ease-in-out}.ht-collapsible-panel.ht-panel-collapsed{max-height:0}.ht-tabs{display:flex;gap:20px;padding:4px 8px;background-color:#f5f5f5;border-bottom:2px solid #ddd;-webkit-user-select:none;user-select:none}.ht-tab{cursor:pointer;padding:2px 4px;font-family:sans-serif;font-size:14px;color:#333;border-bottom:2px solid transparent;transition:all .2s}.ht-tab:hover{color:#666}.ht-tab.active{color:#000;font-weight:700;border-bottom-color:#007bff}.ht-tab.active:hover{color:#000}.ht-tab.disabled{color:#999;cursor:not-allowed;opacity:.5}.ht-tab.disabled:hover{color:#999}.ht-controls{padding:2px 8px;background-color:#fafafa;border-bottom:1px solid #ddd;display:flex;flex-wrap:wrap;column-gap:8px;row-gap:2px;align-items:center;min-height:26px}.ht-controls.hidden{display:none}.ht-control-group{display:flex;align-items:center;gap:4px;white-space:nowrap;padding:2px 0;border-bottom:2px solid transparent;transition:border-bottom-color .2s}.ht-control-group.ht-aesthetic-editing{border-bottom-color:#007bff}.ht-control-label{font-family:sans-serif;font-size:14px;color:#333;white-space:nowrap;display:flex;align-items:center}.ht-button{font-size:14px;font-family:sans-serif;background-color:#e0e0e0;border:1px solid #ccc;border-radius:4px;cursor:pointer;transition:background-color .2s;white-space:nowrap}.ht-button:hover{background-color:#d0d0d0}.ht-button:disabled{background-color:#f0f0f0;color:#999;cursor:not-allowed}.ht-button.primary{background-color:#007bff;color:#fff;font-weight:700}.ht-button.primary:hover{background-color:#0056b3}.ht-icon-button{font-size:14px;font-family:sans-serif;background-color:#e0e0e0;border:1px solid #ccc;border-radius:4px;cursor:pointer;transition:background-color .2s;display:flex;align-items:center;justify-content:center;padding:2px}.ht-icon-button:hover{background-color:#d0d0d0}.ht-icon-button svg{display:block}.ht-select{padding:1px 2px;font-size:14px;border:1px solid #ccc;border-radius:4px}.ht-slider{cursor:pointer;width:150px}.ht-toggle{width:50px;background-color:#ccc;border-radius:12px;position:relative;cursor:pointer;transition:background-color .3s;flex-shrink:0}.ht-toggle.active{background-color:#007bff}.ht-toggle-knob{background-color:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:left .3s}.ht-toggle.active .ht-toggle-knob{left:calc(100% - 2px);transform:translate(-100%)}.ht-number-input{font-size:14px;border:1px solid #ccc;border-radius:4px;width:80px}.ht-text-input{font-size:14px;border:1px solid #ccc;border-radius:4px;padding:2px 4px;width:100px}.ht-aesthetic-settings{padding:2px 8px;background-color:#f0f0f0;border-bottom:1px solid #ddd;display:flex;flex-wrap:wrap;column-gap:8px;row-gap:2px;align-items:center;max-height:1000px;transition:max-height .3s ease-in-out,padding .3s ease-in-out}.ht-aesthetic-settings.hidden{max-height:0;padding-top:0;padding-bottom:0;border-bottom:none}.ht-color-palette-editor{display:flex;gap:8px;align-items:center}.ht-palette-buttons-container{display:flex;flex-direction:column;gap:2px}.ht-palette-button{width:24px;height:24px;font-size:14px;font-weight:700;background-color:#e0e0e0;border:1px solid #ccc;border-radius:4px;cursor:pointer;transition:background-color .2s}.ht-palette-button:hover{background-color:#d0d0d0}.ht-gradient-container{display:flex;gap:8px}.ht-gradient-column{display:flex;flex-direction:column;gap:4px}.ht-color-squares-container{display:flex;position:relative;height:16px;width:200px}.ht-color-square-wrapper{position:absolute;display:flex;flex-direction:column;align-items:center;transform:translate(-50%)}.ht-color-square{width:12px;height:12px;border:2px solid #333;border-radius:4px;cursor:pointer}.ht-color-square-tick{width:2px;height:8px;background-color:#333}.ht-gradient-box{width:200px;height:24px;border:1px solid #ccc;border-radius:4px;position:relative}.ht-range-slider-container{width:200px;height:12px;position:relative;border:1px solid #ccc;border-radius:4px;background:#f0f0f0;display:flex;align-items:center}.ht-range-handle{position:absolute;width:12px;height:12px;border:2px solid #333;border-radius:4px;cursor:ew-resize;transform:translate(-50%)}.ht-range-handle-indicator{position:absolute;bottom:90%;left:50%;transform:translate(-50%) rotate(180deg);width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #333;border-radius:2px;margin-bottom:2px}.ht-null-color-column{display:flex;flex-direction:column;gap:4px}.ht-null-color-square-container{display:flex;position:relative;height:16px;justify-content:center}.ht-null-color-square{width:12px;height:12px;border:2px solid #333;border-radius:4px;cursor:pointer}.ht-null-color-square-tick{width:2px;height:6px;background-color:#333;position:absolute;bottom:-6px;left:50%;transform:translate(-50%)}.ht-null-color-box{width:16px;height:24px;border:1px solid #ccc;border-radius:4px;position:relative}.ht-null-color-reset-container{position:relative;display:flex;align-items:center;justify-content:center}.ht-null-color-reset-indicator{position:absolute;bottom:85%;left:50%;transform:translate(-50%) rotate(180deg);width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #333;border-radius:2px;margin-bottom:2px}.ht-null-color-reset{font-size:14px;color:#d00;font-weight:700;line-height:1;-webkit-user-select:none;user-select:none;cursor:pointer}.ht-tree{flex:1 1 auto;min-height:0;position:relative}.ht-tree svg{display:block}.picker_wrapper,.picker_wrapper.popup{z-index:999999!important;position:fixed!important}.picker_wrapper *,.picker_selector,.picker_slider,.picker_editor,.picker_sample,.picker_done,.picker_cancel{pointer-events:auto!important}";
|
|
2873
2873
|
function injectStyles() {
|
|
2874
2874
|
const styleId = "heat-tree-styles";
|
|
2875
2875
|
if (!document.getElementById(styleId)) {
|
|
@@ -3118,67 +3118,69 @@ function parseNewick(newickStr) {
|
|
|
3118
3118
|
}
|
|
3119
3119
|
return result;
|
|
3120
3120
|
}
|
|
3121
|
-
function parseTable(tsvStr, sep = " ") {
|
|
3121
|
+
function parseTable(tsvStr, valid_ids, sep = " ") {
|
|
3122
3122
|
let metadataMap = /* @__PURE__ */ new Map();
|
|
3123
|
-
let metadataColumns = [];
|
|
3124
3123
|
let columnTypes = /* @__PURE__ */ new Map();
|
|
3124
|
+
let validIdCounts = [];
|
|
3125
|
+
let idColumns = [];
|
|
3125
3126
|
const lines = tsvStr.trim().split("\n");
|
|
3126
3127
|
if (lines.length == 0) {
|
|
3127
|
-
console.error("Empty
|
|
3128
|
+
console.error("Empty metadata table");
|
|
3128
3129
|
} else {
|
|
3129
3130
|
const headers = lines[0].split(sep);
|
|
3130
|
-
const
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3131
|
+
const columnValues = /* @__PURE__ */ new Map();
|
|
3132
|
+
headers.forEach((col) => columnValues.set(col, []));
|
|
3133
|
+
for (let i = 1; i < lines.length; i++) {
|
|
3134
|
+
const values = lines[i].split(sep);
|
|
3135
|
+
const metadata = {};
|
|
3136
|
+
for (let j = 0; j < headers.length; j++) {
|
|
3137
|
+
const colName = headers[j];
|
|
3138
|
+
const value = values[j];
|
|
3139
|
+
metadata[colName] = value;
|
|
3140
|
+
columnValues.get(colName).push(value);
|
|
3141
|
+
}
|
|
3142
|
+
metadataMap.set(i - 1, metadata);
|
|
3143
|
+
}
|
|
3144
|
+
headers.forEach((col) => {
|
|
3145
|
+
const values = columnValues.get(col);
|
|
3146
|
+
const numericValues = values.map((v) => parseFloat(v)).filter((v) => !isNaN(v));
|
|
3147
|
+
const isContinuous = numericValues.length > 0 && numericValues.length === values.filter((v) => v !== "").length;
|
|
3148
|
+
columnTypes.set(col, isContinuous ? "continuous" : "categorical");
|
|
3149
|
+
let matchCount = 0;
|
|
3150
|
+
for (const value of values) {
|
|
3151
|
+
if (value && valid_ids.has(value)) {
|
|
3152
|
+
matchCount++;
|
|
3148
3153
|
}
|
|
3149
|
-
metadataMap.set(nodeId, metadata);
|
|
3150
3154
|
}
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
}
|
|
3155
|
+
if (matchCount > 0) {
|
|
3156
|
+
validIdCounts.push({ col, matchCount });
|
|
3157
|
+
}
|
|
3158
|
+
});
|
|
3159
|
+
validIdCounts = validIdCounts.sort((a, b) => b.matchCount - a.matchCount);
|
|
3160
|
+
idColumns = validIdCounts.map((x) => x.col);
|
|
3158
3161
|
}
|
|
3159
|
-
return {
|
|
3160
|
-
metadataMap,
|
|
3161
|
-
columnTypes
|
|
3162
|
-
};
|
|
3162
|
+
return { metadataMap, columnTypes, idColumns };
|
|
3163
3163
|
}
|
|
3164
3164
|
class NullScale {
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
if (
|
|
3168
|
-
console.error("A
|
|
3169
|
-
} else {
|
|
3170
|
-
this.defaultValue = defaultValue;
|
|
3165
|
+
constructor(options = {}) {
|
|
3166
|
+
this.state = { ...options };
|
|
3167
|
+
if (this.state.default === void 0) {
|
|
3168
|
+
console.error("A default value for a NullScale is needed.");
|
|
3171
3169
|
}
|
|
3172
3170
|
}
|
|
3173
3171
|
getValue() {
|
|
3174
|
-
return this.
|
|
3172
|
+
return this.state.default;
|
|
3175
3173
|
}
|
|
3176
3174
|
}
|
|
3177
3175
|
class IdentityScale {
|
|
3178
|
-
constructor(
|
|
3179
|
-
this.
|
|
3180
|
-
|
|
3181
|
-
|
|
3176
|
+
constructor(options = {}) {
|
|
3177
|
+
this.state = {
|
|
3178
|
+
default: null,
|
|
3179
|
+
outputValues: null,
|
|
3180
|
+
transformFn: null,
|
|
3181
|
+
...options
|
|
3182
|
+
};
|
|
3183
|
+
this.validValues = this.state.outputValues ? new Set(this.state.outputValues) : null;
|
|
3182
3184
|
}
|
|
3183
3185
|
/**
|
|
3184
3186
|
* Get the value, optionally transformed and validated
|
|
@@ -3186,23 +3188,26 @@ class IdentityScale {
|
|
|
3186
3188
|
* @returns {*} The output value, or null if invalid
|
|
3187
3189
|
*/
|
|
3188
3190
|
getValue(value) {
|
|
3189
|
-
let result = this.transformFn ? this.transformFn(value) : value;
|
|
3191
|
+
let result = this.state.transformFn ? this.state.transformFn(value) : value;
|
|
3190
3192
|
if (this.validValues !== null && !this.validValues.has(result)) {
|
|
3191
3193
|
result = null;
|
|
3192
3194
|
}
|
|
3193
3195
|
if (result === null || result === void 0 || result === "") {
|
|
3194
|
-
result = this.
|
|
3196
|
+
result = this.state.default;
|
|
3195
3197
|
}
|
|
3196
3198
|
return result;
|
|
3197
3199
|
}
|
|
3198
3200
|
}
|
|
3199
3201
|
class CategoricalTextScale {
|
|
3200
|
-
constructor(values,
|
|
3202
|
+
constructor(values, options = {}) {
|
|
3201
3203
|
if (!Array.isArray(values) || values.length === 0) {
|
|
3202
3204
|
throw new Error("values must be a non-empty array");
|
|
3203
3205
|
}
|
|
3204
|
-
this.
|
|
3205
|
-
|
|
3206
|
+
this.state = {
|
|
3207
|
+
outputValues: null,
|
|
3208
|
+
default: null,
|
|
3209
|
+
...options
|
|
3210
|
+
};
|
|
3206
3211
|
const frequencyMap = /* @__PURE__ */ new Map();
|
|
3207
3212
|
for (const category of values) {
|
|
3208
3213
|
if (category === null || category === void 0 || category === "") {
|
|
@@ -3217,18 +3222,18 @@ class CategoricalTextScale {
|
|
|
3217
3222
|
const sortedCategories = new Map(
|
|
3218
3223
|
[...frequencyMap.entries()].sort((a, b) => a[0] - b[0])
|
|
3219
3224
|
);
|
|
3220
|
-
if (sortedCategories.length >
|
|
3221
|
-
this.otheredCategories = sortedCategories.keys().slice(
|
|
3225
|
+
if (sortedCategories.length > this.state.outputValues.length) {
|
|
3226
|
+
this.otheredCategories = sortedCategories.keys().slice(this.state.outputValues.length - 2, sortedCategories.length);
|
|
3222
3227
|
} else {
|
|
3223
3228
|
this.otheredCategories = [];
|
|
3224
3229
|
}
|
|
3225
3230
|
this.categoryMap = /* @__PURE__ */ new Map();
|
|
3226
3231
|
const sortedCategoriesKeys = [...sortedCategories.keys()];
|
|
3227
3232
|
for (let i = 0; i < sortedCategoriesKeys.length; i++) {
|
|
3228
|
-
if (i <
|
|
3229
|
-
this.categoryMap.set(sortedCategoriesKeys[i],
|
|
3233
|
+
if (i < this.state.outputValues.length) {
|
|
3234
|
+
this.categoryMap.set(sortedCategoriesKeys[i], this.state.outputValues[i]);
|
|
3230
3235
|
} else {
|
|
3231
|
-
this.categoryMap.set(sortedCategoriesKeys[i],
|
|
3236
|
+
this.categoryMap.set(sortedCategoriesKeys[i], this.state.outputValues[this.state.outputValues.length - 1]);
|
|
3232
3237
|
}
|
|
3233
3238
|
}
|
|
3234
3239
|
}
|
|
@@ -3239,17 +3244,20 @@ class CategoricalTextScale {
|
|
|
3239
3244
|
*/
|
|
3240
3245
|
getValue(category) {
|
|
3241
3246
|
if (category === null || category === void 0 || category === "") {
|
|
3242
|
-
return this.
|
|
3247
|
+
return this.state.default;
|
|
3243
3248
|
}
|
|
3244
3249
|
return this.categoryMap.get(category);
|
|
3245
3250
|
}
|
|
3246
3251
|
}
|
|
3247
3252
|
class ContinuousSizeScale {
|
|
3248
|
-
constructor(dataMin, dataMax,
|
|
3253
|
+
constructor(dataMin, dataMax, options = {}) {
|
|
3254
|
+
this.state = {
|
|
3255
|
+
outputRange: [0.5, 2],
|
|
3256
|
+
nullValue: 1,
|
|
3257
|
+
...options
|
|
3258
|
+
};
|
|
3249
3259
|
this.dataMin = dataMin;
|
|
3250
3260
|
this.dataMax = dataMax;
|
|
3251
|
-
this.sizeMin = sizeMin;
|
|
3252
|
-
this.sizeMax = sizeMax;
|
|
3253
3261
|
}
|
|
3254
3262
|
/**
|
|
3255
3263
|
* Get the size corresponding to the given value
|
|
@@ -3257,46 +3265,53 @@ class ContinuousSizeScale {
|
|
|
3257
3265
|
* @returns {number} The corresponding size, clamped to min/max
|
|
3258
3266
|
*/
|
|
3259
3267
|
getValue(value) {
|
|
3268
|
+
if (value === null || value === void 0 || value === "") {
|
|
3269
|
+
return this.state.nullValue;
|
|
3270
|
+
}
|
|
3260
3271
|
const clampedValue = Math.max(this.dataMin, Math.min(this.dataMax, value));
|
|
3261
3272
|
if (this.dataMin === this.dataMax) {
|
|
3262
3273
|
if (value === this.dataMax) {
|
|
3263
|
-
return (this.
|
|
3274
|
+
return (this.state.outputRange[0] + this.state.outputRange[1]) / 2;
|
|
3264
3275
|
} else if (value < this.dataMin) {
|
|
3265
|
-
return this.
|
|
3276
|
+
return this.state.outputRange[0];
|
|
3266
3277
|
} else {
|
|
3267
|
-
return this.
|
|
3278
|
+
return this.state.outputRange[1];
|
|
3268
3279
|
}
|
|
3269
3280
|
}
|
|
3270
3281
|
const t = (clampedValue - this.dataMin) / (this.dataMax - this.dataMin);
|
|
3271
|
-
return this.
|
|
3282
|
+
return this.state.outputRange[0] + t * (this.state.outputRange[1] - this.state.outputRange[0]);
|
|
3272
3283
|
}
|
|
3273
3284
|
}
|
|
3274
3285
|
class ContinuousColorScale {
|
|
3275
|
-
constructor(dataMin, dataMax,
|
|
3286
|
+
constructor(dataMin, dataMax, options = {}) {
|
|
3287
|
+
this.state = {
|
|
3288
|
+
transformMin: 0,
|
|
3289
|
+
transformMax: 1,
|
|
3290
|
+
colorPalette: null,
|
|
3291
|
+
colorPositions: null,
|
|
3292
|
+
nullValue: "#808080",
|
|
3293
|
+
...options
|
|
3294
|
+
};
|
|
3276
3295
|
this.dataMin = dataMin;
|
|
3277
3296
|
this.dataMax = dataMax;
|
|
3278
|
-
this.
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
if (
|
|
3282
|
-
|
|
3283
|
-
} else {
|
|
3284
|
-
if (colors.length < 1) {
|
|
3285
|
-
throw new Error("At least 1 color is required");
|
|
3286
|
-
}
|
|
3287
|
-
this.colors = colors.map((c) => this._hexToRgb(c));
|
|
3297
|
+
if (this.state.colorPalette === null) {
|
|
3298
|
+
this.state.colorPalette = ["#440154", "#31688e", "#35b779", "#fde724"];
|
|
3299
|
+
}
|
|
3300
|
+
if (this.state.colorPalette.length < 1) {
|
|
3301
|
+
throw new Error("At least 1 color is required");
|
|
3288
3302
|
}
|
|
3303
|
+
this.colors = this.state.colorPalette.map((c) => this._hexToRgb(c));
|
|
3289
3304
|
if (this.colors.length === 1) {
|
|
3290
3305
|
this.colorPositions = [0];
|
|
3291
3306
|
return;
|
|
3292
3307
|
}
|
|
3293
|
-
if (colorPositions === null) {
|
|
3308
|
+
if (this.state.colorPositions === null) {
|
|
3294
3309
|
this.colorPositions = this.colors.map((_, i) => i / (this.colors.length - 1));
|
|
3295
3310
|
} else {
|
|
3296
|
-
if (colorPositions.length !== this.colors.length) {
|
|
3311
|
+
if (this.state.colorPositions.length !== this.colors.length) {
|
|
3297
3312
|
throw new Error("colorPositions must have the same length as colors");
|
|
3298
3313
|
}
|
|
3299
|
-
const paired = colorPositions.map((pos, i) => ({ pos, color: this.colors[i] }));
|
|
3314
|
+
const paired = this.state.colorPositions.map((pos, i) => ({ pos, color: this.colors[i] }));
|
|
3300
3315
|
paired.sort((a, b) => a.pos - b.pos);
|
|
3301
3316
|
this.colorPositions = paired.map((p) => p.pos);
|
|
3302
3317
|
this.colors = paired.map((p) => p.color);
|
|
@@ -3316,7 +3331,7 @@ class ContinuousColorScale {
|
|
|
3316
3331
|
*/
|
|
3317
3332
|
getValue(value) {
|
|
3318
3333
|
if (value === null || value === void 0 || value === "") {
|
|
3319
|
-
return this.
|
|
3334
|
+
return this.state.nullValue;
|
|
3320
3335
|
}
|
|
3321
3336
|
if (this.colors.length === 1) {
|
|
3322
3337
|
return this._rgbToHex(this.colors[0].r, this.colors[0].g, this.colors[0].b);
|
|
@@ -3335,7 +3350,7 @@ class ContinuousColorScale {
|
|
|
3335
3350
|
}
|
|
3336
3351
|
const clampedValue = Math.max(this.dataMin, Math.min(this.dataMax, value));
|
|
3337
3352
|
const dataT = (clampedValue - this.dataMin) / (this.dataMax - this.dataMin);
|
|
3338
|
-
const transformedT = this.transformMin + dataT * (this.transformMax - this.transformMin);
|
|
3353
|
+
const transformedT = this.state.transformMin + dataT * (this.state.transformMax - this.state.transformMin);
|
|
3339
3354
|
const clampedT = Math.max(0, Math.min(1, transformedT));
|
|
3340
3355
|
if (clampedT <= this.colorPositions[0]) {
|
|
3341
3356
|
return this._rgbToHex(this.colors[0].r, this.colors[0].g, this.colors[0].b);
|
|
@@ -3397,14 +3412,19 @@ class ContinuousColorScale {
|
|
|
3397
3412
|
}
|
|
3398
3413
|
class CategoricalColorScale {
|
|
3399
3414
|
defaultPalette = ["#440154", "#31688e", "#35b779", "#fde724"];
|
|
3400
|
-
constructor(categoryData,
|
|
3415
|
+
constructor(categoryData, options = {}) {
|
|
3401
3416
|
if (!Array.isArray(categoryData) || categoryData.length === 0) {
|
|
3402
3417
|
throw new Error("categoryData must be a non-empty array");
|
|
3403
3418
|
}
|
|
3404
|
-
this.
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3419
|
+
this.state = {
|
|
3420
|
+
transformMin: 0,
|
|
3421
|
+
transformMax: 1,
|
|
3422
|
+
colorPalette: null,
|
|
3423
|
+
colorPositions: null,
|
|
3424
|
+
maxCategories: 10,
|
|
3425
|
+
nullValue: "#808080",
|
|
3426
|
+
...options
|
|
3427
|
+
};
|
|
3408
3428
|
this.frequencyMap = /* @__PURE__ */ new Map();
|
|
3409
3429
|
for (const category of categoryData) {
|
|
3410
3430
|
if (this.frequencyMap.has(category)) {
|
|
@@ -3414,26 +3434,25 @@ class CategoricalColorScale {
|
|
|
3414
3434
|
}
|
|
3415
3435
|
}
|
|
3416
3436
|
this.categories = [...this.frequencyMap.entries()].sort((a, b) => b[1] - a[1]).map((entry) => entry[0]);
|
|
3417
|
-
if (
|
|
3418
|
-
this.
|
|
3419
|
-
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
}
|
|
3423
|
-
this.colors = colors.map((c) => this._hexToRgb(c));
|
|
3437
|
+
if (this.state.colorPalette === null) {
|
|
3438
|
+
this.state.colorPalette = this.defaultPalette;
|
|
3439
|
+
}
|
|
3440
|
+
if (this.state.colorPalette.length < 1) {
|
|
3441
|
+
throw new Error("At least 1 color is required");
|
|
3424
3442
|
}
|
|
3443
|
+
this.colors = this.state.colorPalette.map((c) => this._hexToRgb(c));
|
|
3425
3444
|
if (this.colors.length === 1) {
|
|
3426
3445
|
this.colorPositions = [0];
|
|
3427
3446
|
this._assignColors();
|
|
3428
3447
|
return;
|
|
3429
3448
|
}
|
|
3430
|
-
if (colorPositions === null) {
|
|
3449
|
+
if (this.state.colorPositions === null) {
|
|
3431
3450
|
this.colorPositions = this.colors.map((_, i) => i / (this.colors.length - 1));
|
|
3432
3451
|
} else {
|
|
3433
|
-
if (colorPositions.length !== this.colors.length) {
|
|
3452
|
+
if (this.state.colorPositions.length !== this.colors.length) {
|
|
3434
3453
|
throw new Error("colorPositions must have the same length as colors");
|
|
3435
3454
|
}
|
|
3436
|
-
const paired = colorPositions.map((pos, i) => ({ pos, color: this.colors[i] }));
|
|
3455
|
+
const paired = this.state.colorPositions.map((pos, i) => ({ pos, color: this.colors[i] }));
|
|
3437
3456
|
paired.sort((a, b) => a.pos - b.pos);
|
|
3438
3457
|
this.colorPositions = paired.map((p) => p.pos);
|
|
3439
3458
|
this.colors = paired.map((p) => p.color);
|
|
@@ -3461,13 +3480,13 @@ class CategoricalColorScale {
|
|
|
3461
3480
|
return;
|
|
3462
3481
|
}
|
|
3463
3482
|
if (this.categories.length === 1) {
|
|
3464
|
-
const color2 = this._getColorAtPosition(this.transformMin);
|
|
3483
|
+
const color2 = this._getColorAtPosition(this.state.transformMin);
|
|
3465
3484
|
this.categoryColorMap.set(this.categories[0], color2);
|
|
3466
3485
|
} else {
|
|
3467
|
-
const nCategories = this.categories.length > this.
|
|
3486
|
+
const nCategories = this.categories.length > this.state.maxCategories ? this.state.maxCategories : this.categories.length;
|
|
3468
3487
|
for (let i = 0; i < this.categories.length; i++) {
|
|
3469
3488
|
const t = i >= nCategories ? 1 : i / (nCategories - 1);
|
|
3470
|
-
const transformedT = this.transformMin + t * (this.transformMax - this.transformMin);
|
|
3489
|
+
const transformedT = this.state.transformMin + t * (this.state.transformMax - this.state.transformMin);
|
|
3471
3490
|
const color2 = this._getColorAtPosition(transformedT);
|
|
3472
3491
|
this.categoryColorMap.set(this.categories[i], color2);
|
|
3473
3492
|
}
|
|
@@ -3514,7 +3533,7 @@ class CategoricalColorScale {
|
|
|
3514
3533
|
*/
|
|
3515
3534
|
getValue(category) {
|
|
3516
3535
|
if (category === null || category === void 0 || category === "") {
|
|
3517
|
-
return this.
|
|
3536
|
+
return this.state.nullValue;
|
|
3518
3537
|
}
|
|
3519
3538
|
if (this.categoryColorMap.has(category)) {
|
|
3520
3539
|
return this.categoryColorMap.get(category);
|
|
@@ -3554,25 +3573,820 @@ class CategoricalColorScale {
|
|
|
3554
3573
|
}).join("");
|
|
3555
3574
|
}
|
|
3556
3575
|
}
|
|
3557
|
-
|
|
3576
|
+
/*!
|
|
3577
|
+
* vanilla-picker v2.12.3
|
|
3578
|
+
* https://vanilla-picker.js.org
|
|
3579
|
+
*
|
|
3580
|
+
* Copyright 2017-2024 Andreas Borgen (https://github.com/Sphinxxxx), Adam Brooks (https://github.com/dissimulate)
|
|
3581
|
+
* Released under the ISC license.
|
|
3582
|
+
*/
|
|
3583
|
+
var classCallCheck = function(instance, Constructor) {
|
|
3584
|
+
if (!(instance instanceof Constructor)) {
|
|
3585
|
+
throw new TypeError("Cannot call a class as a function");
|
|
3586
|
+
}
|
|
3587
|
+
};
|
|
3588
|
+
var createClass = /* @__PURE__ */ (function() {
|
|
3589
|
+
function defineProperties(target, props) {
|
|
3590
|
+
for (var i = 0; i < props.length; i++) {
|
|
3591
|
+
var descriptor = props[i];
|
|
3592
|
+
descriptor.enumerable = descriptor.enumerable || false;
|
|
3593
|
+
descriptor.configurable = true;
|
|
3594
|
+
if ("value" in descriptor) descriptor.writable = true;
|
|
3595
|
+
Object.defineProperty(target, descriptor.key, descriptor);
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
return function(Constructor, protoProps, staticProps) {
|
|
3599
|
+
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
3600
|
+
if (staticProps) defineProperties(Constructor, staticProps);
|
|
3601
|
+
return Constructor;
|
|
3602
|
+
};
|
|
3603
|
+
})();
|
|
3604
|
+
var slicedToArray = /* @__PURE__ */ (function() {
|
|
3605
|
+
function sliceIterator(arr, i) {
|
|
3606
|
+
var _arr = [];
|
|
3607
|
+
var _n = true;
|
|
3608
|
+
var _d = false;
|
|
3609
|
+
var _e = void 0;
|
|
3610
|
+
try {
|
|
3611
|
+
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
|
|
3612
|
+
_arr.push(_s.value);
|
|
3613
|
+
if (i && _arr.length === i) break;
|
|
3614
|
+
}
|
|
3615
|
+
} catch (err) {
|
|
3616
|
+
_d = true;
|
|
3617
|
+
_e = err;
|
|
3618
|
+
} finally {
|
|
3619
|
+
try {
|
|
3620
|
+
if (!_n && _i["return"]) _i["return"]();
|
|
3621
|
+
} finally {
|
|
3622
|
+
if (_d) throw _e;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
return _arr;
|
|
3626
|
+
}
|
|
3627
|
+
return function(arr, i) {
|
|
3628
|
+
if (Array.isArray(arr)) {
|
|
3629
|
+
return arr;
|
|
3630
|
+
} else if (Symbol.iterator in Object(arr)) {
|
|
3631
|
+
return sliceIterator(arr, i);
|
|
3632
|
+
} else {
|
|
3633
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance");
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
})();
|
|
3637
|
+
String.prototype.startsWith = String.prototype.startsWith || function(needle) {
|
|
3638
|
+
return this.indexOf(needle) === 0;
|
|
3639
|
+
};
|
|
3640
|
+
String.prototype.padStart = String.prototype.padStart || function(len, pad) {
|
|
3641
|
+
var str = this;
|
|
3642
|
+
while (str.length < len) {
|
|
3643
|
+
str = pad + str;
|
|
3644
|
+
}
|
|
3645
|
+
return str;
|
|
3646
|
+
};
|
|
3647
|
+
var colorNames = { cb: "0f8ff", tqw: "aebd7", q: "-ffff", qmrn: "7fffd4", zr: "0ffff", bg: "5f5dc", bsq: "e4c4", bck: "---", nch: "ebcd", b: "--ff", bvt: "8a2be2", brwn: "a52a2a", brw: "deb887", ctb: "5f9ea0", hrt: "7fff-", chcT: "d2691e", cr: "7f50", rnw: "6495ed", crns: "8dc", crms: "dc143c", cn: "-ffff", Db: "--8b", Dcn: "-8b8b", Dgnr: "b8860b", Dgr: "a9a9a9", Dgrn: "-64-", Dkhk: "bdb76b", Dmgn: "8b-8b", Dvgr: "556b2f", Drng: "8c-", Drch: "9932cc", Dr: "8b--", Dsmn: "e9967a", Dsgr: "8fbc8f", DsTb: "483d8b", DsTg: "2f4f4f", Dtrq: "-ced1", Dvt: "94-d3", ppnk: "1493", pskb: "-bfff", mgr: "696969", grb: "1e90ff", rbrc: "b22222", rwht: "af0", stg: "228b22", chs: "-ff", gnsb: "dcdcdc", st: "8f8ff", g: "d7-", gnr: "daa520", gr: "808080", grn: "-8-0", grnw: "adff2f", hnw: "0fff0", htpn: "69b4", nnr: "cd5c5c", ng: "4b-82", vr: "0", khk: "0e68c", vnr: "e6e6fa", nrb: "0f5", wngr: "7cfc-", mnch: "acd", Lb: "add8e6", Lcr: "08080", Lcn: "e0ffff", Lgnr: "afad2", Lgr: "d3d3d3", Lgrn: "90ee90", Lpnk: "b6c1", Lsmn: "a07a", Lsgr: "20b2aa", Lskb: "87cefa", LsTg: "778899", Lstb: "b0c4de", Lw: "e0", m: "-ff-", mgrn: "32cd32", nn: "af0e6", mgnt: "-ff", mrn: "8--0", mqm: "66cdaa", mmb: "--cd", mmrc: "ba55d3", mmpr: "9370db", msg: "3cb371", mmsT: "7b68ee", "": "-fa9a", mtr: "48d1cc", mmvt: "c71585", mnLb: "191970", ntc: "5fffa", mstr: "e4e1", mccs: "e4b5", vjw: "dead", nv: "--80", c: "df5e6", v: "808-0", vrb: "6b8e23", rng: "a5-", rngr: "45-", rch: "da70d6", pgnr: "eee8aa", pgrn: "98fb98", ptrq: "afeeee", pvtr: "db7093", ppwh: "efd5", pchp: "dab9", pr: "cd853f", pnk: "c0cb", pm: "dda0dd", pwrb: "b0e0e6", prp: "8-080", cc: "663399", r: "--", sbr: "bc8f8f", rb: "4169e1", sbrw: "8b4513", smn: "a8072", nbr: "4a460", sgrn: "2e8b57", ssh: "5ee", snn: "a0522d", svr: "c0c0c0", skb: "87ceeb", sTb: "6a5acd", sTgr: "708090", snw: "afa", n: "-ff7f", stb: "4682b4", tn: "d2b48c", t: "-8080", thst: "d8bfd8", tmT: "6347", trqs: "40e0d0", vt: "ee82ee", whT: "5deb3", wht: "", hts: "5f5f5", w: "-", wgrn: "9acd32" };
|
|
3648
|
+
function printNum(num) {
|
|
3649
|
+
var decs = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : 1;
|
|
3650
|
+
var str = decs > 0 ? num.toFixed(decs).replace(/0+$/, "").replace(/\.$/, "") : num.toString();
|
|
3651
|
+
return str || "0";
|
|
3652
|
+
}
|
|
3653
|
+
var Color = (function() {
|
|
3654
|
+
function Color2(r, g, b, a) {
|
|
3655
|
+
classCallCheck(this, Color2);
|
|
3656
|
+
var that = this;
|
|
3657
|
+
function parseString(input) {
|
|
3658
|
+
if (input.startsWith("hsl")) {
|
|
3659
|
+
var _input$match$map = input.match(/([\-\d\.e]+)/g).map(Number), _input$match$map2 = slicedToArray(_input$match$map, 4), h = _input$match$map2[0], s = _input$match$map2[1], l = _input$match$map2[2], _a = _input$match$map2[3];
|
|
3660
|
+
if (_a === void 0) {
|
|
3661
|
+
_a = 1;
|
|
3662
|
+
}
|
|
3663
|
+
h /= 360;
|
|
3664
|
+
s /= 100;
|
|
3665
|
+
l /= 100;
|
|
3666
|
+
that.hsla = [h, s, l, _a];
|
|
3667
|
+
} else if (input.startsWith("rgb")) {
|
|
3668
|
+
var _input$match$map3 = input.match(/([\-\d\.e]+)/g).map(Number), _input$match$map4 = slicedToArray(_input$match$map3, 4), _r = _input$match$map4[0], _g = _input$match$map4[1], _b = _input$match$map4[2], _a2 = _input$match$map4[3];
|
|
3669
|
+
if (_a2 === void 0) {
|
|
3670
|
+
_a2 = 1;
|
|
3671
|
+
}
|
|
3672
|
+
that.rgba = [_r, _g, _b, _a2];
|
|
3673
|
+
} else {
|
|
3674
|
+
if (input.startsWith("#")) {
|
|
3675
|
+
that.rgba = Color2.hexToRgb(input);
|
|
3676
|
+
} else {
|
|
3677
|
+
that.rgba = Color2.nameToRgb(input) || Color2.hexToRgb(input);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
if (r === void 0) ;
|
|
3682
|
+
else if (Array.isArray(r)) {
|
|
3683
|
+
this.rgba = r;
|
|
3684
|
+
} else if (b === void 0) {
|
|
3685
|
+
var color2 = r && "" + r;
|
|
3686
|
+
if (color2) {
|
|
3687
|
+
parseString(color2.toLowerCase());
|
|
3688
|
+
}
|
|
3689
|
+
} else {
|
|
3690
|
+
this.rgba = [r, g, b, a === void 0 ? 1 : a];
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
createClass(Color2, [{
|
|
3694
|
+
key: "printRGB",
|
|
3695
|
+
value: function printRGB(alpha) {
|
|
3696
|
+
var rgb2 = alpha ? this.rgba : this.rgba.slice(0, 3), vals = rgb2.map(function(x, i) {
|
|
3697
|
+
return printNum(x, i === 3 ? 3 : 0);
|
|
3698
|
+
});
|
|
3699
|
+
return alpha ? "rgba(" + vals + ")" : "rgb(" + vals + ")";
|
|
3700
|
+
}
|
|
3701
|
+
}, {
|
|
3702
|
+
key: "printHSL",
|
|
3703
|
+
value: function printHSL(alpha) {
|
|
3704
|
+
var mults = [360, 100, 100, 1], suff = ["", "%", "%", ""];
|
|
3705
|
+
var hsl2 = alpha ? this.hsla : this.hsla.slice(0, 3), vals = hsl2.map(function(x, i) {
|
|
3706
|
+
return printNum(x * mults[i], i === 3 ? 3 : 1) + suff[i];
|
|
3707
|
+
});
|
|
3708
|
+
return alpha ? "hsla(" + vals + ")" : "hsl(" + vals + ")";
|
|
3709
|
+
}
|
|
3710
|
+
}, {
|
|
3711
|
+
key: "printHex",
|
|
3712
|
+
value: function printHex(alpha) {
|
|
3713
|
+
var hex2 = this.hex;
|
|
3714
|
+
return alpha ? hex2 : hex2.substring(0, 7);
|
|
3715
|
+
}
|
|
3716
|
+
}, {
|
|
3717
|
+
key: "rgba",
|
|
3718
|
+
get: function get2() {
|
|
3719
|
+
if (this._rgba) {
|
|
3720
|
+
return this._rgba;
|
|
3721
|
+
}
|
|
3722
|
+
if (!this._hsla) {
|
|
3723
|
+
throw new Error("No color is set");
|
|
3724
|
+
}
|
|
3725
|
+
return this._rgba = Color2.hslToRgb(this._hsla);
|
|
3726
|
+
},
|
|
3727
|
+
set: function set2(rgb2) {
|
|
3728
|
+
if (rgb2.length === 3) {
|
|
3729
|
+
rgb2[3] = 1;
|
|
3730
|
+
}
|
|
3731
|
+
this._rgba = rgb2;
|
|
3732
|
+
this._hsla = null;
|
|
3733
|
+
}
|
|
3734
|
+
}, {
|
|
3735
|
+
key: "rgbString",
|
|
3736
|
+
get: function get2() {
|
|
3737
|
+
return this.printRGB();
|
|
3738
|
+
}
|
|
3739
|
+
}, {
|
|
3740
|
+
key: "rgbaString",
|
|
3741
|
+
get: function get2() {
|
|
3742
|
+
return this.printRGB(true);
|
|
3743
|
+
}
|
|
3744
|
+
}, {
|
|
3745
|
+
key: "hsla",
|
|
3746
|
+
get: function get2() {
|
|
3747
|
+
if (this._hsla) {
|
|
3748
|
+
return this._hsla;
|
|
3749
|
+
}
|
|
3750
|
+
if (!this._rgba) {
|
|
3751
|
+
throw new Error("No color is set");
|
|
3752
|
+
}
|
|
3753
|
+
return this._hsla = Color2.rgbToHsl(this._rgba);
|
|
3754
|
+
},
|
|
3755
|
+
set: function set2(hsl2) {
|
|
3756
|
+
if (hsl2.length === 3) {
|
|
3757
|
+
hsl2[3] = 1;
|
|
3758
|
+
}
|
|
3759
|
+
this._hsla = hsl2;
|
|
3760
|
+
this._rgba = null;
|
|
3761
|
+
}
|
|
3762
|
+
}, {
|
|
3763
|
+
key: "hslString",
|
|
3764
|
+
get: function get2() {
|
|
3765
|
+
return this.printHSL();
|
|
3766
|
+
}
|
|
3767
|
+
}, {
|
|
3768
|
+
key: "hslaString",
|
|
3769
|
+
get: function get2() {
|
|
3770
|
+
return this.printHSL(true);
|
|
3771
|
+
}
|
|
3772
|
+
}, {
|
|
3773
|
+
key: "hex",
|
|
3774
|
+
get: function get2() {
|
|
3775
|
+
var rgb2 = this.rgba, hex2 = rgb2.map(function(x, i) {
|
|
3776
|
+
return i < 3 ? x.toString(16) : Math.round(x * 255).toString(16);
|
|
3777
|
+
});
|
|
3778
|
+
return "#" + hex2.map(function(x) {
|
|
3779
|
+
return x.padStart(2, "0");
|
|
3780
|
+
}).join("");
|
|
3781
|
+
},
|
|
3782
|
+
set: function set2(hex2) {
|
|
3783
|
+
this.rgba = Color2.hexToRgb(hex2);
|
|
3784
|
+
}
|
|
3785
|
+
}], [{
|
|
3786
|
+
key: "hexToRgb",
|
|
3787
|
+
value: function hexToRgb2(input) {
|
|
3788
|
+
var hex2 = (input.startsWith("#") ? input.slice(1) : input).replace(/^(\w{3})$/, "$1F").replace(/^(\w)(\w)(\w)(\w)$/, "$1$1$2$2$3$3$4$4").replace(/^(\w{6})$/, "$1FF");
|
|
3789
|
+
if (!hex2.match(/^([0-9a-fA-F]{8})$/)) {
|
|
3790
|
+
throw new Error("Unknown hex color; " + input);
|
|
3791
|
+
}
|
|
3792
|
+
var rgba2 = hex2.match(/^(\w\w)(\w\w)(\w\w)(\w\w)$/).slice(1).map(function(x) {
|
|
3793
|
+
return parseInt(x, 16);
|
|
3794
|
+
});
|
|
3795
|
+
rgba2[3] = rgba2[3] / 255;
|
|
3796
|
+
return rgba2;
|
|
3797
|
+
}
|
|
3798
|
+
}, {
|
|
3799
|
+
key: "nameToRgb",
|
|
3800
|
+
value: function nameToRgb(input) {
|
|
3801
|
+
var hash = input.toLowerCase().replace("at", "T").replace(/[aeiouyldf]/g, "").replace("ght", "L").replace("rk", "D").slice(-5, 4), hex2 = colorNames[hash];
|
|
3802
|
+
return hex2 === void 0 ? hex2 : Color2.hexToRgb(hex2.replace(/\-/g, "00").padStart(6, "f"));
|
|
3803
|
+
}
|
|
3804
|
+
}, {
|
|
3805
|
+
key: "rgbToHsl",
|
|
3806
|
+
value: function rgbToHsl(_ref) {
|
|
3807
|
+
var _ref2 = slicedToArray(_ref, 4), r = _ref2[0], g = _ref2[1], b = _ref2[2], a = _ref2[3];
|
|
3808
|
+
r /= 255;
|
|
3809
|
+
g /= 255;
|
|
3810
|
+
b /= 255;
|
|
3811
|
+
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
3812
|
+
var h = void 0, s = void 0, l = (max + min) / 2;
|
|
3813
|
+
if (max === min) {
|
|
3814
|
+
h = s = 0;
|
|
3815
|
+
} else {
|
|
3816
|
+
var d = max - min;
|
|
3817
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
3818
|
+
switch (max) {
|
|
3819
|
+
case r:
|
|
3820
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
3821
|
+
break;
|
|
3822
|
+
case g:
|
|
3823
|
+
h = (b - r) / d + 2;
|
|
3824
|
+
break;
|
|
3825
|
+
case b:
|
|
3826
|
+
h = (r - g) / d + 4;
|
|
3827
|
+
break;
|
|
3828
|
+
}
|
|
3829
|
+
h /= 6;
|
|
3830
|
+
}
|
|
3831
|
+
return [h, s, l, a];
|
|
3832
|
+
}
|
|
3833
|
+
}, {
|
|
3834
|
+
key: "hslToRgb",
|
|
3835
|
+
value: function hslToRgb(_ref3) {
|
|
3836
|
+
var _ref4 = slicedToArray(_ref3, 4), h = _ref4[0], s = _ref4[1], l = _ref4[2], a = _ref4[3];
|
|
3837
|
+
var r = void 0, g = void 0, b = void 0;
|
|
3838
|
+
if (s === 0) {
|
|
3839
|
+
r = g = b = l;
|
|
3840
|
+
} else {
|
|
3841
|
+
var hue2rgb = function hue2rgb2(p2, q2, t) {
|
|
3842
|
+
if (t < 0) t += 1;
|
|
3843
|
+
if (t > 1) t -= 1;
|
|
3844
|
+
if (t < 1 / 6) return p2 + (q2 - p2) * 6 * t;
|
|
3845
|
+
if (t < 1 / 2) return q2;
|
|
3846
|
+
if (t < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - t) * 6;
|
|
3847
|
+
return p2;
|
|
3848
|
+
};
|
|
3849
|
+
var q = l < 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
|
|
3850
|
+
r = hue2rgb(p, q, h + 1 / 3);
|
|
3851
|
+
g = hue2rgb(p, q, h);
|
|
3852
|
+
b = hue2rgb(p, q, h - 1 / 3);
|
|
3853
|
+
}
|
|
3854
|
+
var rgba2 = [r * 255, g * 255, b * 255].map(Math.round);
|
|
3855
|
+
rgba2[3] = a;
|
|
3856
|
+
return rgba2;
|
|
3857
|
+
}
|
|
3858
|
+
}]);
|
|
3859
|
+
return Color2;
|
|
3860
|
+
})();
|
|
3861
|
+
var EventBucket = (function() {
|
|
3862
|
+
function EventBucket2() {
|
|
3863
|
+
classCallCheck(this, EventBucket2);
|
|
3864
|
+
this._events = [];
|
|
3865
|
+
}
|
|
3866
|
+
createClass(EventBucket2, [{
|
|
3867
|
+
key: "add",
|
|
3868
|
+
value: function add(target, type, handler) {
|
|
3869
|
+
target.addEventListener(type, handler, false);
|
|
3870
|
+
this._events.push({
|
|
3871
|
+
target,
|
|
3872
|
+
type,
|
|
3873
|
+
handler
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
}, {
|
|
3877
|
+
key: "remove",
|
|
3878
|
+
value: function remove2(target, type, handler) {
|
|
3879
|
+
this._events = this._events.filter(function(e) {
|
|
3880
|
+
var isMatch = true;
|
|
3881
|
+
if (target && target !== e.target) {
|
|
3882
|
+
isMatch = false;
|
|
3883
|
+
}
|
|
3884
|
+
if (type && type !== e.type) {
|
|
3885
|
+
isMatch = false;
|
|
3886
|
+
}
|
|
3887
|
+
if (handler && handler !== e.handler) {
|
|
3888
|
+
isMatch = false;
|
|
3889
|
+
}
|
|
3890
|
+
if (isMatch) {
|
|
3891
|
+
EventBucket2._doRemove(e.target, e.type, e.handler);
|
|
3892
|
+
}
|
|
3893
|
+
return !isMatch;
|
|
3894
|
+
});
|
|
3895
|
+
}
|
|
3896
|
+
}, {
|
|
3897
|
+
key: "destroy",
|
|
3898
|
+
value: function destroy() {
|
|
3899
|
+
this._events.forEach(function(e) {
|
|
3900
|
+
return EventBucket2._doRemove(e.target, e.type, e.handler);
|
|
3901
|
+
});
|
|
3902
|
+
this._events = [];
|
|
3903
|
+
}
|
|
3904
|
+
}], [{
|
|
3905
|
+
key: "_doRemove",
|
|
3906
|
+
value: function _doRemove(target, type, handler) {
|
|
3907
|
+
target.removeEventListener(type, handler, false);
|
|
3908
|
+
}
|
|
3909
|
+
}]);
|
|
3910
|
+
return EventBucket2;
|
|
3911
|
+
})();
|
|
3912
|
+
function parseHTML(htmlString) {
|
|
3913
|
+
var div = document.createElement("div");
|
|
3914
|
+
div.innerHTML = htmlString;
|
|
3915
|
+
return div.firstElementChild;
|
|
3916
|
+
}
|
|
3917
|
+
function dragTrack(eventBucket, area, callback) {
|
|
3918
|
+
var dragging = false;
|
|
3919
|
+
function clamp(val, min, max) {
|
|
3920
|
+
return Math.max(min, Math.min(val, max));
|
|
3921
|
+
}
|
|
3922
|
+
function onMove(e, info, starting) {
|
|
3923
|
+
if (starting) {
|
|
3924
|
+
dragging = true;
|
|
3925
|
+
}
|
|
3926
|
+
if (!dragging) {
|
|
3927
|
+
return;
|
|
3928
|
+
}
|
|
3929
|
+
e.preventDefault();
|
|
3930
|
+
var bounds = area.getBoundingClientRect(), w = bounds.width, h = bounds.height, x = info.clientX, y = info.clientY;
|
|
3931
|
+
var relX = clamp(x - bounds.left, 0, w), relY = clamp(y - bounds.top, 0, h);
|
|
3932
|
+
callback(relX / w, relY / h);
|
|
3933
|
+
}
|
|
3934
|
+
function onMouse(e, starting) {
|
|
3935
|
+
var button = e.buttons === void 0 ? e.which : e.buttons;
|
|
3936
|
+
if (button === 1) {
|
|
3937
|
+
onMove(e, e, starting);
|
|
3938
|
+
} else {
|
|
3939
|
+
dragging = false;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
function onTouch(e, starting) {
|
|
3943
|
+
if (e.touches.length === 1) {
|
|
3944
|
+
onMove(e, e.touches[0], starting);
|
|
3945
|
+
} else {
|
|
3946
|
+
dragging = false;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
eventBucket.add(area, "mousedown", function(e) {
|
|
3950
|
+
onMouse(e, true);
|
|
3951
|
+
});
|
|
3952
|
+
eventBucket.add(area, "touchstart", function(e) {
|
|
3953
|
+
onTouch(e, true);
|
|
3954
|
+
});
|
|
3955
|
+
eventBucket.add(window, "mousemove", onMouse);
|
|
3956
|
+
eventBucket.add(area, "touchmove", onTouch);
|
|
3957
|
+
eventBucket.add(window, "mouseup", function(e) {
|
|
3958
|
+
dragging = false;
|
|
3959
|
+
});
|
|
3960
|
+
eventBucket.add(area, "touchend", function(e) {
|
|
3961
|
+
dragging = false;
|
|
3962
|
+
});
|
|
3963
|
+
eventBucket.add(area, "touchcancel", function(e) {
|
|
3964
|
+
dragging = false;
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
3967
|
+
var BG_TRANSP = "linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%) 0 0 / 2em 2em,\n linear-gradient(45deg, lightgrey 25%, white 25%, white 75%, lightgrey 75%) 1em 1em / 2em 2em";
|
|
3968
|
+
var HUES = 360;
|
|
3969
|
+
var EVENT_KEY = "keydown", EVENT_CLICK_OUTSIDE = "mousedown", EVENT_TAB_MOVE = "focusin";
|
|
3970
|
+
function $(selector2, context) {
|
|
3971
|
+
return (context || document).querySelector(selector2);
|
|
3972
|
+
}
|
|
3973
|
+
function stopEvent(e) {
|
|
3974
|
+
e.preventDefault();
|
|
3975
|
+
e.stopPropagation();
|
|
3976
|
+
}
|
|
3977
|
+
function onKey(bucket, target, keys, handler, stop) {
|
|
3978
|
+
bucket.add(target, EVENT_KEY, function(e) {
|
|
3979
|
+
if (keys.indexOf(e.key) >= 0) {
|
|
3980
|
+
handler(e);
|
|
3981
|
+
}
|
|
3982
|
+
});
|
|
3983
|
+
}
|
|
3984
|
+
var Picker = (function() {
|
|
3985
|
+
function Picker2(options) {
|
|
3986
|
+
classCallCheck(this, Picker2);
|
|
3987
|
+
this.settings = {
|
|
3988
|
+
popup: "right",
|
|
3989
|
+
layout: "default",
|
|
3990
|
+
alpha: true,
|
|
3991
|
+
editor: true,
|
|
3992
|
+
editorFormat: "hex",
|
|
3993
|
+
cancelButton: false,
|
|
3994
|
+
defaultColor: "#0cf"
|
|
3995
|
+
};
|
|
3996
|
+
this._events = new EventBucket();
|
|
3997
|
+
this.onChange = null;
|
|
3998
|
+
this.onDone = null;
|
|
3999
|
+
this.onOpen = null;
|
|
4000
|
+
this.onClose = null;
|
|
4001
|
+
this.setOptions(options);
|
|
4002
|
+
}
|
|
4003
|
+
createClass(Picker2, [{
|
|
4004
|
+
key: "setOptions",
|
|
4005
|
+
value: function setOptions(options) {
|
|
4006
|
+
var _this = this;
|
|
4007
|
+
if (!options) {
|
|
4008
|
+
return;
|
|
4009
|
+
}
|
|
4010
|
+
var settings = this.settings;
|
|
4011
|
+
function transfer(source, target, skipKeys) {
|
|
4012
|
+
for (var key in source) {
|
|
4013
|
+
target[key] = source[key];
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
if (options instanceof HTMLElement) {
|
|
4017
|
+
settings.parent = options;
|
|
4018
|
+
} else {
|
|
4019
|
+
if (settings.parent && options.parent && settings.parent !== options.parent) {
|
|
4020
|
+
this._events.remove(settings.parent);
|
|
4021
|
+
this._popupInited = false;
|
|
4022
|
+
}
|
|
4023
|
+
transfer(options, settings);
|
|
4024
|
+
if (options.onChange) {
|
|
4025
|
+
this.onChange = options.onChange;
|
|
4026
|
+
}
|
|
4027
|
+
if (options.onDone) {
|
|
4028
|
+
this.onDone = options.onDone;
|
|
4029
|
+
}
|
|
4030
|
+
if (options.onOpen) {
|
|
4031
|
+
this.onOpen = options.onOpen;
|
|
4032
|
+
}
|
|
4033
|
+
if (options.onClose) {
|
|
4034
|
+
this.onClose = options.onClose;
|
|
4035
|
+
}
|
|
4036
|
+
var col = options.color || options.colour;
|
|
4037
|
+
if (col) {
|
|
4038
|
+
this._setColor(col);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
var parent = settings.parent;
|
|
4042
|
+
if (parent && settings.popup && !this._popupInited) {
|
|
4043
|
+
var openProxy = function openProxy2(e) {
|
|
4044
|
+
return _this.openHandler(e);
|
|
4045
|
+
};
|
|
4046
|
+
this._events.add(parent, "click", openProxy);
|
|
4047
|
+
onKey(this._events, parent, [" ", "Spacebar", "Enter"], openProxy);
|
|
4048
|
+
this._popupInited = true;
|
|
4049
|
+
} else if (options.parent && !settings.popup) {
|
|
4050
|
+
this.show();
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
}, {
|
|
4054
|
+
key: "openHandler",
|
|
4055
|
+
value: function openHandler(e) {
|
|
4056
|
+
if (this.show()) {
|
|
4057
|
+
e && e.preventDefault();
|
|
4058
|
+
this.settings.parent.style.pointerEvents = "none";
|
|
4059
|
+
var toFocus = e && e.type === EVENT_KEY ? this._domEdit : this.domElement;
|
|
4060
|
+
setTimeout(function() {
|
|
4061
|
+
return toFocus.focus();
|
|
4062
|
+
}, 100);
|
|
4063
|
+
if (this.onOpen) {
|
|
4064
|
+
this.onOpen(this.colour);
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
}, {
|
|
4069
|
+
key: "closeHandler",
|
|
4070
|
+
value: function closeHandler(e) {
|
|
4071
|
+
var event = e && e.type;
|
|
4072
|
+
var doHide = false;
|
|
4073
|
+
if (!e) {
|
|
4074
|
+
doHide = true;
|
|
4075
|
+
} else if (event === EVENT_CLICK_OUTSIDE || event === EVENT_TAB_MOVE) {
|
|
4076
|
+
var knownTime = (this.__containedEvent || 0) + 100;
|
|
4077
|
+
if (e.timeStamp > knownTime) {
|
|
4078
|
+
doHide = true;
|
|
4079
|
+
}
|
|
4080
|
+
} else {
|
|
4081
|
+
stopEvent(e);
|
|
4082
|
+
doHide = true;
|
|
4083
|
+
}
|
|
4084
|
+
if (doHide && this.hide()) {
|
|
4085
|
+
this.settings.parent.style.pointerEvents = "";
|
|
4086
|
+
if (event !== EVENT_CLICK_OUTSIDE) {
|
|
4087
|
+
this.settings.parent.focus();
|
|
4088
|
+
}
|
|
4089
|
+
if (this.onClose) {
|
|
4090
|
+
this.onClose(this.colour);
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
}, {
|
|
4095
|
+
key: "movePopup",
|
|
4096
|
+
value: function movePopup(options, open) {
|
|
4097
|
+
this.closeHandler();
|
|
4098
|
+
this.setOptions(options);
|
|
4099
|
+
if (open) {
|
|
4100
|
+
this.openHandler();
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
}, {
|
|
4104
|
+
key: "setColor",
|
|
4105
|
+
value: function setColor(color2, silent) {
|
|
4106
|
+
this._setColor(color2, { silent });
|
|
4107
|
+
}
|
|
4108
|
+
}, {
|
|
4109
|
+
key: "_setColor",
|
|
4110
|
+
value: function _setColor(color2, flags) {
|
|
4111
|
+
if (typeof color2 === "string") {
|
|
4112
|
+
color2 = color2.trim();
|
|
4113
|
+
}
|
|
4114
|
+
if (!color2) {
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
flags = flags || {};
|
|
4118
|
+
var c = void 0;
|
|
4119
|
+
try {
|
|
4120
|
+
c = new Color(color2);
|
|
4121
|
+
} catch (ex) {
|
|
4122
|
+
if (flags.failSilently) {
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
throw ex;
|
|
4126
|
+
}
|
|
4127
|
+
if (!this.settings.alpha) {
|
|
4128
|
+
var hsla2 = c.hsla;
|
|
4129
|
+
hsla2[3] = 1;
|
|
4130
|
+
c.hsla = hsla2;
|
|
4131
|
+
}
|
|
4132
|
+
this.colour = this.color = c;
|
|
4133
|
+
this._setHSLA(null, null, null, null, flags);
|
|
4134
|
+
}
|
|
4135
|
+
}, {
|
|
4136
|
+
key: "setColour",
|
|
4137
|
+
value: function setColour(colour, silent) {
|
|
4138
|
+
this.setColor(colour, silent);
|
|
4139
|
+
}
|
|
4140
|
+
}, {
|
|
4141
|
+
key: "show",
|
|
4142
|
+
value: function show() {
|
|
4143
|
+
var parent = this.settings.parent;
|
|
4144
|
+
if (!parent) {
|
|
4145
|
+
return false;
|
|
4146
|
+
}
|
|
4147
|
+
if (this.domElement) {
|
|
4148
|
+
var toggled = this._toggleDOM(true);
|
|
4149
|
+
this._setPosition();
|
|
4150
|
+
return toggled;
|
|
4151
|
+
}
|
|
4152
|
+
var html = this.settings.template || '<div class="picker_wrapper" tabindex="-1"><div class="picker_arrow"></div><div class="picker_hue picker_slider"><div class="picker_selector"></div></div><div class="picker_sl"><div class="picker_selector"></div></div><div class="picker_alpha picker_slider"><div class="picker_selector"></div></div><div class="picker_editor"><input aria-label="Type a color name or hex value"/></div><div class="picker_sample"></div><div class="picker_done"><button>Ok</button></div><div class="picker_cancel"><button>Cancel</button></div></div>';
|
|
4153
|
+
var wrapper = parseHTML(html);
|
|
4154
|
+
this.domElement = wrapper;
|
|
4155
|
+
this._domH = $(".picker_hue", wrapper);
|
|
4156
|
+
this._domSL = $(".picker_sl", wrapper);
|
|
4157
|
+
this._domA = $(".picker_alpha", wrapper);
|
|
4158
|
+
this._domEdit = $(".picker_editor input", wrapper);
|
|
4159
|
+
this._domSample = $(".picker_sample", wrapper);
|
|
4160
|
+
this._domOkay = $(".picker_done button", wrapper);
|
|
4161
|
+
this._domCancel = $(".picker_cancel button", wrapper);
|
|
4162
|
+
wrapper.classList.add("layout_" + this.settings.layout);
|
|
4163
|
+
if (!this.settings.alpha) {
|
|
4164
|
+
wrapper.classList.add("no_alpha");
|
|
4165
|
+
}
|
|
4166
|
+
if (!this.settings.editor) {
|
|
4167
|
+
wrapper.classList.add("no_editor");
|
|
4168
|
+
}
|
|
4169
|
+
if (!this.settings.cancelButton) {
|
|
4170
|
+
wrapper.classList.add("no_cancel");
|
|
4171
|
+
}
|
|
4172
|
+
this._ifPopup(function() {
|
|
4173
|
+
return wrapper.classList.add("popup");
|
|
4174
|
+
});
|
|
4175
|
+
this._setPosition();
|
|
4176
|
+
if (this.colour) {
|
|
4177
|
+
this._updateUI();
|
|
4178
|
+
} else {
|
|
4179
|
+
this._setColor(this.settings.defaultColor);
|
|
4180
|
+
}
|
|
4181
|
+
this._bindEvents();
|
|
4182
|
+
return true;
|
|
4183
|
+
}
|
|
4184
|
+
}, {
|
|
4185
|
+
key: "hide",
|
|
4186
|
+
value: function hide() {
|
|
4187
|
+
return this._toggleDOM(false);
|
|
4188
|
+
}
|
|
4189
|
+
}, {
|
|
4190
|
+
key: "destroy",
|
|
4191
|
+
value: function destroy() {
|
|
4192
|
+
this._events.destroy();
|
|
4193
|
+
if (this.domElement) {
|
|
4194
|
+
this.settings.parent.removeChild(this.domElement);
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
}, {
|
|
4198
|
+
key: "_bindEvents",
|
|
4199
|
+
value: function _bindEvents() {
|
|
4200
|
+
var _this2 = this;
|
|
4201
|
+
var that = this, dom = this.domElement, events = this._events;
|
|
4202
|
+
function addEvent(target, type, handler) {
|
|
4203
|
+
events.add(target, type, handler);
|
|
4204
|
+
}
|
|
4205
|
+
addEvent(dom, "click", function(e) {
|
|
4206
|
+
return e.preventDefault();
|
|
4207
|
+
});
|
|
4208
|
+
dragTrack(events, this._domH, function(x, y) {
|
|
4209
|
+
return that._setHSLA(x);
|
|
4210
|
+
});
|
|
4211
|
+
dragTrack(events, this._domSL, function(x, y) {
|
|
4212
|
+
return that._setHSLA(null, x, 1 - y);
|
|
4213
|
+
});
|
|
4214
|
+
if (this.settings.alpha) {
|
|
4215
|
+
dragTrack(events, this._domA, function(x, y) {
|
|
4216
|
+
return that._setHSLA(null, null, null, 1 - y);
|
|
4217
|
+
});
|
|
4218
|
+
}
|
|
4219
|
+
var editInput = this._domEdit;
|
|
4220
|
+
{
|
|
4221
|
+
addEvent(editInput, "input", function(e) {
|
|
4222
|
+
that._setColor(this.value, { fromEditor: true, failSilently: true });
|
|
4223
|
+
});
|
|
4224
|
+
addEvent(editInput, "focus", function(e) {
|
|
4225
|
+
var input = this;
|
|
4226
|
+
if (input.selectionStart === input.selectionEnd) {
|
|
4227
|
+
input.select();
|
|
4228
|
+
}
|
|
4229
|
+
});
|
|
4230
|
+
}
|
|
4231
|
+
this._ifPopup(function() {
|
|
4232
|
+
var popupCloseProxy = function popupCloseProxy2(e) {
|
|
4233
|
+
return _this2.closeHandler(e);
|
|
4234
|
+
};
|
|
4235
|
+
addEvent(window, EVENT_CLICK_OUTSIDE, popupCloseProxy);
|
|
4236
|
+
addEvent(window, EVENT_TAB_MOVE, popupCloseProxy);
|
|
4237
|
+
onKey(events, dom, ["Esc", "Escape"], popupCloseProxy);
|
|
4238
|
+
var timeKeeper = function timeKeeper2(e) {
|
|
4239
|
+
_this2.__containedEvent = e.timeStamp;
|
|
4240
|
+
};
|
|
4241
|
+
addEvent(dom, EVENT_CLICK_OUTSIDE, timeKeeper);
|
|
4242
|
+
addEvent(dom, EVENT_TAB_MOVE, timeKeeper);
|
|
4243
|
+
addEvent(_this2._domCancel, "click", popupCloseProxy);
|
|
4244
|
+
});
|
|
4245
|
+
var onDoneProxy = function onDoneProxy2(e) {
|
|
4246
|
+
_this2._ifPopup(function() {
|
|
4247
|
+
return _this2.closeHandler(e);
|
|
4248
|
+
});
|
|
4249
|
+
if (_this2.onDone) {
|
|
4250
|
+
_this2.onDone(_this2.colour);
|
|
4251
|
+
}
|
|
4252
|
+
};
|
|
4253
|
+
addEvent(this._domOkay, "click", onDoneProxy);
|
|
4254
|
+
onKey(events, dom, ["Enter"], onDoneProxy);
|
|
4255
|
+
}
|
|
4256
|
+
}, {
|
|
4257
|
+
key: "_setPosition",
|
|
4258
|
+
value: function _setPosition() {
|
|
4259
|
+
var parent = this.settings.parent, elm = this.domElement;
|
|
4260
|
+
if (parent !== elm.parentNode) {
|
|
4261
|
+
parent.appendChild(elm);
|
|
4262
|
+
}
|
|
4263
|
+
this._ifPopup(function(popup) {
|
|
4264
|
+
if (getComputedStyle(parent).position === "static") {
|
|
4265
|
+
parent.style.position = "relative";
|
|
4266
|
+
}
|
|
4267
|
+
var cssClass = popup === true ? "popup_right" : "popup_" + popup;
|
|
4268
|
+
["popup_top", "popup_bottom", "popup_left", "popup_right"].forEach(function(c) {
|
|
4269
|
+
if (c === cssClass) {
|
|
4270
|
+
elm.classList.add(c);
|
|
4271
|
+
} else {
|
|
4272
|
+
elm.classList.remove(c);
|
|
4273
|
+
}
|
|
4274
|
+
});
|
|
4275
|
+
elm.classList.add(cssClass);
|
|
4276
|
+
});
|
|
4277
|
+
}
|
|
4278
|
+
}, {
|
|
4279
|
+
key: "_setHSLA",
|
|
4280
|
+
value: function _setHSLA(h, s, l, a, flags) {
|
|
4281
|
+
flags = flags || {};
|
|
4282
|
+
var col = this.colour, hsla2 = col.hsla;
|
|
4283
|
+
[h, s, l, a].forEach(function(x, i) {
|
|
4284
|
+
if (x || x === 0) {
|
|
4285
|
+
hsla2[i] = x;
|
|
4286
|
+
}
|
|
4287
|
+
});
|
|
4288
|
+
col.hsla = hsla2;
|
|
4289
|
+
this._updateUI(flags);
|
|
4290
|
+
if (this.onChange && !flags.silent) {
|
|
4291
|
+
this.onChange(col);
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
}, {
|
|
4295
|
+
key: "_updateUI",
|
|
4296
|
+
value: function _updateUI(flags) {
|
|
4297
|
+
if (!this.domElement) {
|
|
4298
|
+
return;
|
|
4299
|
+
}
|
|
4300
|
+
flags = flags || {};
|
|
4301
|
+
var col = this.colour, hsl2 = col.hsla, cssHue = "hsl(" + hsl2[0] * HUES + ", 100%, 50%)", cssHSL = col.hslString, cssHSLA = col.hslaString;
|
|
4302
|
+
var uiH = this._domH, uiSL = this._domSL, uiA = this._domA, thumbH = $(".picker_selector", uiH), thumbSL = $(".picker_selector", uiSL), thumbA = $(".picker_selector", uiA);
|
|
4303
|
+
function posX(parent, child, relX) {
|
|
4304
|
+
child.style.left = relX * 100 + "%";
|
|
4305
|
+
}
|
|
4306
|
+
function posY(parent, child, relY) {
|
|
4307
|
+
child.style.top = relY * 100 + "%";
|
|
4308
|
+
}
|
|
4309
|
+
posX(uiH, thumbH, hsl2[0]);
|
|
4310
|
+
this._domSL.style.backgroundColor = this._domH.style.color = cssHue;
|
|
4311
|
+
posX(uiSL, thumbSL, hsl2[1]);
|
|
4312
|
+
posY(uiSL, thumbSL, 1 - hsl2[2]);
|
|
4313
|
+
uiSL.style.color = cssHSL;
|
|
4314
|
+
posY(uiA, thumbA, 1 - hsl2[3]);
|
|
4315
|
+
var opaque = cssHSL, transp = opaque.replace("hsl", "hsla").replace(")", ", 0)"), bg = "linear-gradient(" + [opaque, transp] + ")";
|
|
4316
|
+
this._domA.style.background = bg + ", " + BG_TRANSP;
|
|
4317
|
+
if (!flags.fromEditor) {
|
|
4318
|
+
var format = this.settings.editorFormat, alpha = this.settings.alpha;
|
|
4319
|
+
var value = void 0;
|
|
4320
|
+
switch (format) {
|
|
4321
|
+
case "rgb":
|
|
4322
|
+
value = col.printRGB(alpha);
|
|
4323
|
+
break;
|
|
4324
|
+
case "hsl":
|
|
4325
|
+
value = col.printHSL(alpha);
|
|
4326
|
+
break;
|
|
4327
|
+
default:
|
|
4328
|
+
value = col.printHex(alpha);
|
|
4329
|
+
}
|
|
4330
|
+
this._domEdit.value = value;
|
|
4331
|
+
}
|
|
4332
|
+
this._domSample.style.color = cssHSLA;
|
|
4333
|
+
}
|
|
4334
|
+
}, {
|
|
4335
|
+
key: "_ifPopup",
|
|
4336
|
+
value: function _ifPopup(actionIf, actionElse) {
|
|
4337
|
+
if (this.settings.parent && this.settings.popup) {
|
|
4338
|
+
actionIf && actionIf(this.settings.popup);
|
|
4339
|
+
} else {
|
|
4340
|
+
actionElse && actionElse();
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4343
|
+
}, {
|
|
4344
|
+
key: "_toggleDOM",
|
|
4345
|
+
value: function _toggleDOM(toVisible) {
|
|
4346
|
+
var dom = this.domElement;
|
|
4347
|
+
if (!dom) {
|
|
4348
|
+
return false;
|
|
4349
|
+
}
|
|
4350
|
+
var displayStyle = toVisible ? "" : "none", toggle = dom.style.display !== displayStyle;
|
|
4351
|
+
if (toggle) {
|
|
4352
|
+
dom.style.display = displayStyle;
|
|
4353
|
+
}
|
|
4354
|
+
return toggle;
|
|
4355
|
+
}
|
|
4356
|
+
}]);
|
|
4357
|
+
return Picker2;
|
|
4358
|
+
})();
|
|
4359
|
+
{
|
|
4360
|
+
var style = document.createElement("style");
|
|
4361
|
+
style.textContent = '.picker_wrapper.no_alpha .picker_alpha{display:none}.picker_wrapper.no_editor .picker_editor{position:absolute;z-index:-1;opacity:0}.picker_wrapper.no_cancel .picker_cancel{display:none}.layout_default.picker_wrapper{display:flex;flex-flow:row wrap;justify-content:space-between;align-items:stretch;font-size:10px;width:25em;padding:.5em}.layout_default.picker_wrapper input,.layout_default.picker_wrapper button{font-size:1rem}.layout_default.picker_wrapper>*{margin:.5em}.layout_default.picker_wrapper::before{content:"";display:block;width:100%;height:0;order:1}.layout_default .picker_slider,.layout_default .picker_selector{padding:1em}.layout_default .picker_hue{width:100%}.layout_default .picker_sl{flex:1 1 auto}.layout_default .picker_sl::before{content:"";display:block;padding-bottom:100%}.layout_default .picker_editor{order:1;width:6.5rem}.layout_default .picker_editor input{width:100%;height:100%}.layout_default .picker_sample{order:1;flex:1 1 auto}.layout_default .picker_done,.layout_default .picker_cancel{order:1}.picker_wrapper{box-sizing:border-box;background:#f2f2f2;box-shadow:0 0 0 1px silver;cursor:default;font-family:sans-serif;color:#444;pointer-events:auto}.picker_wrapper:focus{outline:none}.picker_wrapper button,.picker_wrapper input{box-sizing:border-box;border:none;box-shadow:0 0 0 1px silver;outline:none}.picker_wrapper button:focus,.picker_wrapper button:active,.picker_wrapper input:focus,.picker_wrapper input:active{box-shadow:0 0 2px 1px #1e90ff}.picker_wrapper button{padding:.4em .6em;cursor:pointer;background-color:#f5f5f5;background-image:linear-gradient(0deg, gainsboro, transparent)}.picker_wrapper button:active{background-image:linear-gradient(0deg, transparent, gainsboro)}.picker_wrapper button:hover{background-color:#fff}.picker_selector{position:absolute;z-index:1;display:block;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);border:2px solid #fff;border-radius:100%;box-shadow:0 0 3px 1px #67b9ff;background:currentColor;cursor:pointer}.picker_slider .picker_selector{border-radius:2px}.picker_hue{position:relative;background-image:linear-gradient(90deg, red, yellow, lime, cyan, blue, magenta, red);box-shadow:0 0 0 1px silver}.picker_sl{position:relative;box-shadow:0 0 0 1px silver;background-image:linear-gradient(180deg, white, rgba(255, 255, 255, 0) 50%),linear-gradient(0deg, black, rgba(0, 0, 0, 0) 50%),linear-gradient(90deg, #808080, rgba(128, 128, 128, 0))}.picker_alpha,.picker_sample{position:relative;background:linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%) 0 0/2em 2em,linear-gradient(45deg, lightgrey 25%, white 25%, white 75%, lightgrey 75%) 1em 1em/2em 2em;box-shadow:0 0 0 1px silver}.picker_alpha .picker_selector,.picker_sample .picker_selector{background:none}.picker_editor input{font-family:monospace;padding:.2em .4em}.picker_sample::before{content:"";position:absolute;display:block;width:100%;height:100%;background:currentColor}.picker_arrow{position:absolute;z-index:-1}.picker_wrapper.popup{position:absolute;z-index:2;margin:1.5em}.picker_wrapper.popup,.picker_wrapper.popup .picker_arrow::before,.picker_wrapper.popup .picker_arrow::after{background:#f2f2f2;box-shadow:0 0 10px 1px rgba(0,0,0,.4)}.picker_wrapper.popup .picker_arrow{width:3em;height:3em;margin:0}.picker_wrapper.popup .picker_arrow::before,.picker_wrapper.popup .picker_arrow::after{content:"";display:block;position:absolute;top:0;left:0;z-index:-99}.picker_wrapper.popup .picker_arrow::before{width:100%;height:100%;-webkit-transform:skew(45deg);transform:skew(45deg);-webkit-transform-origin:0 100%;transform-origin:0 100%}.picker_wrapper.popup .picker_arrow::after{width:150%;height:150%;box-shadow:none}.popup.popup_top{bottom:100%;left:0}.popup.popup_top .picker_arrow{bottom:0;left:0;-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}.popup.popup_bottom{top:100%;left:0}.popup.popup_bottom .picker_arrow{top:0;left:0;-webkit-transform:rotate(90deg) scale(1, -1);transform:rotate(90deg) scale(1, -1)}.popup.popup_left{top:0;right:100%}.popup.popup_left .picker_arrow{top:0;right:0;-webkit-transform:scale(-1, 1);transform:scale(-1, 1)}.popup.popup_right{top:0;left:100%}.popup.popup_right .picker_arrow{top:0;left:0}';
|
|
4362
|
+
document.documentElement.firstElementChild.appendChild(style);
|
|
4363
|
+
Picker.StyleElement = style;
|
|
4364
|
+
}
|
|
4365
|
+
class Aesthetic extends Subscribable {
|
|
3558
4366
|
state;
|
|
3559
4367
|
// Object containing all configuration used to infer the scale
|
|
3560
4368
|
scale;
|
|
3561
4369
|
// the actual scale instance
|
|
4370
|
+
values;
|
|
4371
|
+
// Store the original values for scale recreation
|
|
4372
|
+
defaultPalette = ["#440154", "#31688e", "#35b779", "#fde724"];
|
|
3562
4373
|
constructor(values, options = {}) {
|
|
4374
|
+
super();
|
|
3563
4375
|
if (!options.scaleType) {
|
|
3564
4376
|
throw new Error("scaleType is required");
|
|
3565
4377
|
}
|
|
3566
4378
|
if (options.default === void 0) {
|
|
3567
4379
|
throw new Error("default is required");
|
|
3568
4380
|
}
|
|
4381
|
+
this.values = values;
|
|
3569
4382
|
this.state = {
|
|
3570
4383
|
scaleType: void 0,
|
|
3571
4384
|
default: void 0,
|
|
3572
4385
|
isCategorical: void 0,
|
|
3573
4386
|
outputValues: null,
|
|
3574
4387
|
outputRegex: null,
|
|
3575
|
-
colorPalette:
|
|
4388
|
+
colorPalette: this.defaultPalette,
|
|
4389
|
+
colorPositions: this.defaultPalette.map((_, i) => i / (this.defaultPalette.length - 1)),
|
|
3576
4390
|
outputRange: null,
|
|
3577
4391
|
inputUnits: null,
|
|
3578
4392
|
title: null,
|
|
@@ -3582,17 +4396,10 @@ class Aesthetic {
|
|
|
3582
4396
|
transformMin: 0,
|
|
3583
4397
|
transformMax: 1,
|
|
3584
4398
|
transformFn: null,
|
|
3585
|
-
|
|
4399
|
+
nullValue: null,
|
|
3586
4400
|
...options
|
|
3587
4401
|
};
|
|
3588
|
-
this.updateScale(
|
|
3589
|
-
}
|
|
3590
|
-
/**
|
|
3591
|
-
* Set the scale instance for this aesthetic
|
|
3592
|
-
* @param {object} scale - The scale instance
|
|
3593
|
-
*/
|
|
3594
|
-
setScale(scale) {
|
|
3595
|
-
this.scale = scale;
|
|
4402
|
+
this.updateScale(false);
|
|
3596
4403
|
}
|
|
3597
4404
|
/**
|
|
3598
4405
|
* Get the output value for a given input value
|
|
@@ -3607,14 +4414,10 @@ class Aesthetic {
|
|
|
3607
4414
|
* Update the scale based on current state
|
|
3608
4415
|
* Uses the stored state to create an appropriate scale
|
|
3609
4416
|
*/
|
|
3610
|
-
updateScale(
|
|
4417
|
+
updateScale(notify = true) {
|
|
4418
|
+
const values = this.values;
|
|
3611
4419
|
const { scaleType, isCategorical } = this.state;
|
|
3612
4420
|
let scale;
|
|
3613
|
-
if (scaleType === "null") {
|
|
3614
|
-
scale = new NullScale(this.state.default);
|
|
3615
|
-
this.setScale(scale);
|
|
3616
|
-
return;
|
|
3617
|
-
}
|
|
3618
4421
|
let isAlreadyOutputFormat = true;
|
|
3619
4422
|
if (isCategorical && (this.state.outputValues || this.state.outputRegex)) {
|
|
3620
4423
|
if (this.state.outputValues) {
|
|
@@ -3636,77 +4439,461 @@ class Aesthetic {
|
|
|
3636
4439
|
} else {
|
|
3637
4440
|
isAlreadyOutputFormat = false;
|
|
3638
4441
|
}
|
|
3639
|
-
if (scaleType === "
|
|
3640
|
-
scale = new
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
return;
|
|
3647
|
-
}
|
|
3648
|
-
if (scaleType === "text") {
|
|
4442
|
+
if (scaleType === "null") {
|
|
4443
|
+
scale = new NullScale(this.state);
|
|
4444
|
+
this.scale = scale;
|
|
4445
|
+
} else if (scaleType === "identity" || isAlreadyOutputFormat) {
|
|
4446
|
+
scale = new IdentityScale(this.state);
|
|
4447
|
+
this.scale = scale;
|
|
4448
|
+
} else if (scaleType === "text") {
|
|
3649
4449
|
if (!isCategorical) {
|
|
3650
4450
|
throw new Error("Text scales can only be used with categorical data");
|
|
3651
4451
|
}
|
|
3652
|
-
scale = new CategoricalTextScale(values, this.state
|
|
3653
|
-
this.
|
|
3654
|
-
|
|
3655
|
-
}
|
|
3656
|
-
if (scaleType === "size") {
|
|
4452
|
+
scale = new CategoricalTextScale(values, this.state);
|
|
4453
|
+
this.scale = scale;
|
|
4454
|
+
} else if (scaleType === "size") {
|
|
3657
4455
|
if (isCategorical) {
|
|
3658
4456
|
throw new Error("Size scales can only be used with continuous data");
|
|
3659
4457
|
}
|
|
3660
4458
|
const numericValues = values.map((v) => Number(v)).filter((v) => !isNaN(v));
|
|
3661
4459
|
if (numericValues.length === 0) {
|
|
3662
4460
|
console.warn("No numeric values found for size scale, using NullScale");
|
|
3663
|
-
scale = new NullScale(this.state
|
|
4461
|
+
scale = new NullScale(this.state);
|
|
3664
4462
|
} else {
|
|
3665
4463
|
const min = Math.min(...numericValues);
|
|
3666
4464
|
const max = Math.max(...numericValues);
|
|
3667
|
-
|
|
3668
|
-
scale = new ContinuousSizeScale(min, max, range[0], range[1]);
|
|
4465
|
+
scale = new ContinuousSizeScale(min, max, this.state);
|
|
3669
4466
|
}
|
|
3670
|
-
this.
|
|
3671
|
-
|
|
3672
|
-
}
|
|
3673
|
-
if (scaleType === "color") {
|
|
4467
|
+
this.scale = scale;
|
|
4468
|
+
} else if (scaleType === "color") {
|
|
3674
4469
|
if (isCategorical) {
|
|
3675
|
-
scale = new CategoricalColorScale(
|
|
3676
|
-
values,
|
|
3677
|
-
this.state.transformMin,
|
|
3678
|
-
this.state.transformMax,
|
|
3679
|
-
this.state.colorPalette,
|
|
3680
|
-
this.state.colorPositions,
|
|
3681
|
-
this.state.maxCategories
|
|
3682
|
-
);
|
|
4470
|
+
scale = new CategoricalColorScale(values, this.state);
|
|
3683
4471
|
} else {
|
|
3684
4472
|
const numericValues = values.map((v) => Number(v)).filter((v) => !isNaN(v));
|
|
3685
4473
|
if (numericValues.length === 0) {
|
|
3686
4474
|
console.warn("No numeric values found for color scale, using NullScale");
|
|
3687
|
-
scale = new NullScale(this.state
|
|
4475
|
+
scale = new NullScale(this.state);
|
|
3688
4476
|
} else {
|
|
3689
4477
|
const min = Math.min(...numericValues);
|
|
3690
4478
|
const max = Math.max(...numericValues);
|
|
3691
|
-
scale = new ContinuousColorScale(
|
|
3692
|
-
min,
|
|
3693
|
-
max,
|
|
3694
|
-
this.state.transformMin,
|
|
3695
|
-
this.state.transformMax,
|
|
3696
|
-
this.state.colorPalette,
|
|
3697
|
-
this.state.colorPositions
|
|
3698
|
-
);
|
|
4479
|
+
scale = new ContinuousColorScale(min, max, this.state);
|
|
3699
4480
|
}
|
|
3700
4481
|
}
|
|
3701
|
-
this.
|
|
3702
|
-
|
|
4482
|
+
this.scale = scale;
|
|
4483
|
+
} else {
|
|
4484
|
+
throw new Error(`Unknown scale type: ${scaleType}`);
|
|
4485
|
+
}
|
|
4486
|
+
if (notify) {
|
|
4487
|
+
this.notify("aestheticChange", this);
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
/**
|
|
4491
|
+
* Update aesthetic state properties
|
|
4492
|
+
* @param {Object} updates - Object with properties to update
|
|
4493
|
+
*/
|
|
4494
|
+
updateState(updates) {
|
|
4495
|
+
Object.assign(this.state, updates);
|
|
4496
|
+
this.updateScale(this.values);
|
|
4497
|
+
}
|
|
4498
|
+
/**
|
|
4499
|
+
* Create settings widget(s) for this aesthetic
|
|
4500
|
+
* @param {Object} options - Configuration options
|
|
4501
|
+
* @param {number} options.controlHeight - Height of controls
|
|
4502
|
+
* @returns {HTMLElement|null} The settings widget container, or null if no settings available
|
|
4503
|
+
*/
|
|
4504
|
+
createSettingsWidget(options = {}) {
|
|
4505
|
+
const {
|
|
4506
|
+
controlHeight = 24
|
|
4507
|
+
} = options;
|
|
4508
|
+
if (this.state.scaleType === "color") {
|
|
4509
|
+
return this.createColorPaletteEditor(controlHeight);
|
|
3703
4510
|
}
|
|
3704
|
-
|
|
4511
|
+
return null;
|
|
3705
4512
|
}
|
|
4513
|
+
/**
|
|
4514
|
+
* Create a color palette editor widget
|
|
4515
|
+
* @param {number} controlHeight - Height of controls
|
|
4516
|
+
* @returns {HTMLElement} The palette editor container
|
|
4517
|
+
*/
|
|
4518
|
+
createColorPaletteEditor(controlHeight) {
|
|
4519
|
+
if (!this.scale) {
|
|
4520
|
+
return null;
|
|
4521
|
+
}
|
|
4522
|
+
const container = document.createElement("div");
|
|
4523
|
+
container.className = "ht-color-palette-editor";
|
|
4524
|
+
const gradientContainer = document.createElement("div");
|
|
4525
|
+
gradientContainer.className = "ht-gradient-container";
|
|
4526
|
+
const gradientColumn = document.createElement("div");
|
|
4527
|
+
gradientColumn.className = "ht-gradient-column";
|
|
4528
|
+
const colorSquaresContainer = document.createElement("div");
|
|
4529
|
+
colorSquaresContainer.className = "ht-color-squares-container";
|
|
4530
|
+
const gradientBox = document.createElement("div");
|
|
4531
|
+
gradientBox.className = "ht-gradient-box";
|
|
4532
|
+
const updateGradientDisplay = () => {
|
|
4533
|
+
const gradientStops = this.state.colorPalette.map((color2, i) => {
|
|
4534
|
+
const pos = this.state.colorPositions[i] * 100;
|
|
4535
|
+
return `${color2} ${pos}%`;
|
|
4536
|
+
}).join(", ");
|
|
4537
|
+
gradientBox.style.background = `linear-gradient(to right, ${gradientStops})`;
|
|
4538
|
+
};
|
|
4539
|
+
updateGradientDisplay();
|
|
4540
|
+
let currentPickerParent = null;
|
|
4541
|
+
const pickerContainer = document.createElement("div");
|
|
4542
|
+
pickerContainer.style.position = "fixed";
|
|
4543
|
+
pickerContainer.style.zIndex = "999999";
|
|
4544
|
+
pickerContainer.style.pointerEvents = "auto";
|
|
4545
|
+
document.body.appendChild(pickerContainer);
|
|
4546
|
+
const nullPickerContainer = document.createElement("div");
|
|
4547
|
+
nullPickerContainer.style.position = "fixed";
|
|
4548
|
+
nullPickerContainer.style.zIndex = "999999";
|
|
4549
|
+
nullPickerContainer.style.pointerEvents = "auto";
|
|
4550
|
+
document.body.appendChild(nullPickerContainer);
|
|
4551
|
+
const closeGradientPicker = () => {
|
|
4552
|
+
pickerContainer.style.display = "none";
|
|
4553
|
+
currentPickerParent = null;
|
|
4554
|
+
};
|
|
4555
|
+
const closeNullPicker = () => {
|
|
4556
|
+
nullPickerContainer.style.display = "none";
|
|
4557
|
+
};
|
|
4558
|
+
const nullColorColumn = document.createElement("div");
|
|
4559
|
+
nullColorColumn.className = "ht-null-color-column";
|
|
4560
|
+
const nullColorSquareContainer = document.createElement("div");
|
|
4561
|
+
nullColorSquareContainer.className = "ht-null-color-square-container";
|
|
4562
|
+
const nullSquareWrapper = document.createElement("div");
|
|
4563
|
+
nullSquareWrapper.style.display = "flex";
|
|
4564
|
+
nullSquareWrapper.style.flexDirection = "column";
|
|
4565
|
+
nullSquareWrapper.style.alignItems = "center";
|
|
4566
|
+
const nullSquare = document.createElement("div");
|
|
4567
|
+
nullSquare.className = "ht-null-color-square";
|
|
4568
|
+
nullSquare.style.backgroundColor = this.state.nullValue;
|
|
4569
|
+
nullSquare.title = "Click to edit missing data color";
|
|
4570
|
+
const nullTick = document.createElement("div");
|
|
4571
|
+
nullTick.className = "ht-null-color-square-tick";
|
|
4572
|
+
nullSquareWrapper.appendChild(nullSquare);
|
|
4573
|
+
nullSquareWrapper.appendChild(nullTick);
|
|
4574
|
+
nullColorSquareContainer.appendChild(nullSquareWrapper);
|
|
4575
|
+
const nullColorBox = document.createElement("div");
|
|
4576
|
+
nullColorBox.className = "ht-null-color-box";
|
|
4577
|
+
nullColorBox.style.backgroundColor = this.state.nullValue;
|
|
4578
|
+
const sharedPicker = new Picker({
|
|
4579
|
+
parent: pickerContainer,
|
|
4580
|
+
popup: false,
|
|
4581
|
+
alpha: false,
|
|
4582
|
+
editor: true,
|
|
4583
|
+
color: this.state.colorPalette[0],
|
|
4584
|
+
onChange: (color2) => {
|
|
4585
|
+
if (!currentPickerParent) return;
|
|
4586
|
+
const colorIndex = parseInt(currentPickerParent.getAttribute("data-color-index"));
|
|
4587
|
+
const hexColor = color2.hex.substring(0, 7);
|
|
4588
|
+
this.state.colorPalette[colorIndex] = hexColor;
|
|
4589
|
+
currentPickerParent.style.backgroundColor = hexColor;
|
|
4590
|
+
updateGradientDisplay();
|
|
4591
|
+
const minColor2 = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMin);
|
|
4592
|
+
minHandle.style.backgroundColor = minColor2;
|
|
4593
|
+
const maxColor2 = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMax);
|
|
4594
|
+
maxHandle.style.backgroundColor = maxColor2;
|
|
4595
|
+
this.updateScale();
|
|
4596
|
+
},
|
|
4597
|
+
onDone: () => {
|
|
4598
|
+
closeGradientPicker();
|
|
4599
|
+
}
|
|
4600
|
+
});
|
|
4601
|
+
pickerContainer.style.display = "none";
|
|
4602
|
+
const closePickerOnClickOutside = (e) => {
|
|
4603
|
+
if (!pickerContainer.contains(e.target) && !e.target.closest(".ht-color-square")) {
|
|
4604
|
+
closeGradientPicker();
|
|
4605
|
+
}
|
|
4606
|
+
};
|
|
4607
|
+
document.addEventListener("click", closePickerOnClickOutside);
|
|
4608
|
+
const nullColorPicker = new Picker({
|
|
4609
|
+
parent: nullPickerContainer,
|
|
4610
|
+
popup: false,
|
|
4611
|
+
alpha: false,
|
|
4612
|
+
editor: true,
|
|
4613
|
+
color: this.state.nullValue,
|
|
4614
|
+
onChange: (color2) => {
|
|
4615
|
+
const hexColor = color2.hex.substring(0, 7);
|
|
4616
|
+
this.state.nullValue = hexColor;
|
|
4617
|
+
nullSquare.style.backgroundColor = hexColor;
|
|
4618
|
+
nullColorBox.style.backgroundColor = hexColor;
|
|
4619
|
+
this.updateScale();
|
|
4620
|
+
},
|
|
4621
|
+
onDone: () => {
|
|
4622
|
+
closeNullPicker();
|
|
4623
|
+
}
|
|
4624
|
+
});
|
|
4625
|
+
nullPickerContainer.style.display = "none";
|
|
4626
|
+
const closeNullPickerOnClickOutside = (e) => {
|
|
4627
|
+
if (!nullPickerContainer.contains(e.target) && e.target !== nullSquare) {
|
|
4628
|
+
closeNullPicker();
|
|
4629
|
+
}
|
|
4630
|
+
};
|
|
4631
|
+
document.addEventListener("click", closeNullPickerOnClickOutside);
|
|
4632
|
+
container.dataset.pickerCleanup = "cleanup";
|
|
4633
|
+
container.cleanupFunction = () => {
|
|
4634
|
+
document.removeEventListener("click", closePickerOnClickOutside);
|
|
4635
|
+
document.removeEventListener("click", closeNullPickerOnClickOutside);
|
|
4636
|
+
if (pickerContainer.parentElement) {
|
|
4637
|
+
pickerContainer.parentElement.removeChild(pickerContainer);
|
|
4638
|
+
}
|
|
4639
|
+
if (nullPickerContainer.parentElement) {
|
|
4640
|
+
nullPickerContainer.parentElement.removeChild(nullPickerContainer);
|
|
4641
|
+
}
|
|
4642
|
+
};
|
|
4643
|
+
const recreateColorSquares = () => {
|
|
4644
|
+
colorSquaresContainer.innerHTML = "";
|
|
4645
|
+
this.state.colorPalette.forEach((color2, i) => {
|
|
4646
|
+
const squareContainer = createColorSquareWithTick(colorSquaresContainer, color2, i, (e) => {
|
|
4647
|
+
e.preventDefault();
|
|
4648
|
+
e.stopPropagation();
|
|
4649
|
+
const square = e.currentTarget;
|
|
4650
|
+
const colorIndex = parseInt(square.getAttribute("data-color-index"));
|
|
4651
|
+
closeNullPicker();
|
|
4652
|
+
currentPickerParent = square;
|
|
4653
|
+
const rect = square.getBoundingClientRect();
|
|
4654
|
+
sharedPicker.setColor(this.state.colorPalette[colorIndex], true);
|
|
4655
|
+
pickerContainer.style.left = `${rect.left}px`;
|
|
4656
|
+
pickerContainer.style.top = `${rect.bottom + 5}px`;
|
|
4657
|
+
pickerContainer.style.display = "block";
|
|
4658
|
+
});
|
|
4659
|
+
squareContainer.style.left = `${this.state.colorPositions[i] * 100}%`;
|
|
4660
|
+
});
|
|
4661
|
+
};
|
|
4662
|
+
recreateColorSquares();
|
|
4663
|
+
const rangeSliderContainer = document.createElement("div");
|
|
4664
|
+
rangeSliderContainer.className = "ht-range-slider-container";
|
|
4665
|
+
const minHandle = document.createElement("div");
|
|
4666
|
+
minHandle.className = "ht-range-handle";
|
|
4667
|
+
minHandle.style.left = `${this.state.transformMin * 100}%`;
|
|
4668
|
+
minHandle.title = "Drag to adjust minimum";
|
|
4669
|
+
let minColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMin);
|
|
4670
|
+
minHandle.style.backgroundColor = minColor;
|
|
4671
|
+
const minIndicator = document.createElement("div");
|
|
4672
|
+
minIndicator.className = "ht-range-handle-indicator";
|
|
4673
|
+
minHandle.appendChild(minIndicator);
|
|
4674
|
+
const maxHandle = document.createElement("div");
|
|
4675
|
+
maxHandle.className = "ht-range-handle";
|
|
4676
|
+
maxHandle.style.left = `${this.state.transformMax * 100}%`;
|
|
4677
|
+
maxHandle.title = "Drag to adjust maximum";
|
|
4678
|
+
let maxColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMax);
|
|
4679
|
+
maxHandle.style.backgroundColor = maxColor;
|
|
4680
|
+
const maxIndicator = document.createElement("div");
|
|
4681
|
+
maxIndicator.className = "ht-range-handle-indicator";
|
|
4682
|
+
maxHandle.appendChild(maxIndicator);
|
|
4683
|
+
let isDraggingMin = false;
|
|
4684
|
+
minHandle.addEventListener("mousedown", (e) => {
|
|
4685
|
+
isDraggingMin = true;
|
|
4686
|
+
e.preventDefault();
|
|
4687
|
+
});
|
|
4688
|
+
let isDraggingMax = false;
|
|
4689
|
+
maxHandle.addEventListener("mousedown", (e) => {
|
|
4690
|
+
isDraggingMax = true;
|
|
4691
|
+
e.preventDefault();
|
|
4692
|
+
});
|
|
4693
|
+
const handleMouseMove = (e) => {
|
|
4694
|
+
if (!isDraggingMin && !isDraggingMax) return;
|
|
4695
|
+
const rect = rangeSliderContainer.getBoundingClientRect();
|
|
4696
|
+
const x = e.clientX - rect.left;
|
|
4697
|
+
const width = rect.width;
|
|
4698
|
+
let newValue = Math.max(0, Math.min(1, x / width));
|
|
4699
|
+
if (isDraggingMin) {
|
|
4700
|
+
newValue = Math.min(newValue, this.state.transformMax - 0.01);
|
|
4701
|
+
this.state.transformMin = newValue;
|
|
4702
|
+
minHandle.style.left = `${newValue * 100}%`;
|
|
4703
|
+
minColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, newValue);
|
|
4704
|
+
minHandle.style.backgroundColor = minColor;
|
|
4705
|
+
this.updateScale();
|
|
4706
|
+
} else if (isDraggingMax) {
|
|
4707
|
+
newValue = Math.max(newValue, this.state.transformMin + 0.01);
|
|
4708
|
+
this.state.transformMax = newValue;
|
|
4709
|
+
maxHandle.style.left = `${newValue * 100}%`;
|
|
4710
|
+
maxColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, newValue);
|
|
4711
|
+
maxHandle.style.backgroundColor = maxColor;
|
|
4712
|
+
this.updateScale();
|
|
4713
|
+
}
|
|
4714
|
+
};
|
|
4715
|
+
const handleMouseUp = () => {
|
|
4716
|
+
isDraggingMin = false;
|
|
4717
|
+
isDraggingMax = false;
|
|
4718
|
+
};
|
|
4719
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
4720
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
4721
|
+
rangeSliderContainer.appendChild(minHandle);
|
|
4722
|
+
rangeSliderContainer.appendChild(maxHandle);
|
|
4723
|
+
gradientColumn.appendChild(colorSquaresContainer);
|
|
4724
|
+
gradientColumn.appendChild(gradientBox);
|
|
4725
|
+
gradientColumn.appendChild(rangeSliderContainer);
|
|
4726
|
+
nullSquare.addEventListener("click", (e) => {
|
|
4727
|
+
e.preventDefault();
|
|
4728
|
+
e.stopPropagation();
|
|
4729
|
+
closeGradientPicker();
|
|
4730
|
+
const rect = nullSquare.getBoundingClientRect();
|
|
4731
|
+
nullColorPicker.setColor(this.state.nullValue, true);
|
|
4732
|
+
nullPickerContainer.style.left = `${rect.left}px`;
|
|
4733
|
+
nullPickerContainer.style.top = `${rect.bottom + 5}px`;
|
|
4734
|
+
nullPickerContainer.style.display = "block";
|
|
4735
|
+
});
|
|
4736
|
+
const resetContainer = document.createElement("div");
|
|
4737
|
+
resetContainer.className = "ht-null-color-reset-container";
|
|
4738
|
+
resetContainer.title = "Reset to default missing data color";
|
|
4739
|
+
const resetIndicator = document.createElement("div");
|
|
4740
|
+
resetIndicator.className = "ht-null-color-reset-indicator";
|
|
4741
|
+
const resetX = document.createElement("div");
|
|
4742
|
+
resetX.className = "ht-null-color-reset";
|
|
4743
|
+
resetX.textContent = "✕";
|
|
4744
|
+
resetX.addEventListener("click", () => {
|
|
4745
|
+
const defaultNullColor = "#808080";
|
|
4746
|
+
this.state.nullValue = defaultNullColor;
|
|
4747
|
+
nullSquare.style.backgroundColor = defaultNullColor;
|
|
4748
|
+
nullColorBox.style.backgroundColor = defaultNullColor;
|
|
4749
|
+
nullColorPicker.setColor(defaultNullColor, true);
|
|
4750
|
+
this.updateScale();
|
|
4751
|
+
});
|
|
4752
|
+
resetContainer.appendChild(resetIndicator);
|
|
4753
|
+
resetContainer.appendChild(resetX);
|
|
4754
|
+
nullColorColumn.appendChild(nullColorSquareContainer);
|
|
4755
|
+
nullColorColumn.appendChild(nullColorBox);
|
|
4756
|
+
nullColorColumn.appendChild(resetContainer);
|
|
4757
|
+
gradientContainer.appendChild(gradientColumn);
|
|
4758
|
+
gradientContainer.appendChild(nullColorColumn);
|
|
4759
|
+
const leftButtonsContainer = document.createElement("div");
|
|
4760
|
+
leftButtonsContainer.className = "ht-palette-buttons-container";
|
|
4761
|
+
const leftPlusBtn = createPaletteButton("+", "Add color to left");
|
|
4762
|
+
leftPlusBtn.addEventListener("click", () => {
|
|
4763
|
+
const newColor = this.state.colorPalette[0];
|
|
4764
|
+
this.state.colorPalette.unshift(newColor);
|
|
4765
|
+
this.state.colorPositions = this.state.colorPalette.map((_, i) => i / (this.state.colorPalette.length - 1));
|
|
4766
|
+
updateGradientDisplay();
|
|
4767
|
+
recreateColorSquares();
|
|
4768
|
+
minColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMin);
|
|
4769
|
+
minHandle.style.backgroundColor = minColor;
|
|
4770
|
+
maxColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMax);
|
|
4771
|
+
maxHandle.style.backgroundColor = maxColor;
|
|
4772
|
+
this.updateScale();
|
|
4773
|
+
});
|
|
4774
|
+
const leftMinusBtn = createPaletteButton("-", "Remove color from left");
|
|
4775
|
+
leftMinusBtn.addEventListener("click", () => {
|
|
4776
|
+
if (this.state.colorPalette.length <= 2) {
|
|
4777
|
+
console.warn("Cannot remove color: minimum 2 colors required");
|
|
4778
|
+
return;
|
|
4779
|
+
}
|
|
4780
|
+
this.state.colorPalette.shift();
|
|
4781
|
+
this.state.colorPositions = this.state.colorPalette.map((_, i) => i / (this.state.colorPalette.length - 1));
|
|
4782
|
+
updateGradientDisplay();
|
|
4783
|
+
recreateColorSquares();
|
|
4784
|
+
minColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMin);
|
|
4785
|
+
minHandle.style.backgroundColor = minColor;
|
|
4786
|
+
maxColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMax);
|
|
4787
|
+
maxHandle.style.backgroundColor = maxColor;
|
|
4788
|
+
this.updateScale();
|
|
4789
|
+
});
|
|
4790
|
+
leftButtonsContainer.appendChild(leftPlusBtn);
|
|
4791
|
+
leftButtonsContainer.appendChild(leftMinusBtn);
|
|
4792
|
+
const rightButtonsContainer = document.createElement("div");
|
|
4793
|
+
rightButtonsContainer.className = "ht-palette-buttons-container";
|
|
4794
|
+
const rightPlusBtn = createPaletteButton("+", "Add color to right");
|
|
4795
|
+
rightPlusBtn.addEventListener("click", () => {
|
|
4796
|
+
const newColor = this.state.colorPalette[this.state.colorPalette.length - 1];
|
|
4797
|
+
this.state.colorPalette.push(newColor);
|
|
4798
|
+
this.state.colorPositions = this.state.colorPalette.map((_, i) => i / (this.state.colorPalette.length - 1));
|
|
4799
|
+
updateGradientDisplay();
|
|
4800
|
+
recreateColorSquares();
|
|
4801
|
+
minColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMin);
|
|
4802
|
+
minHandle.style.backgroundColor = minColor;
|
|
4803
|
+
maxColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMax);
|
|
4804
|
+
maxHandle.style.backgroundColor = maxColor;
|
|
4805
|
+
this.updateScale();
|
|
4806
|
+
});
|
|
4807
|
+
const rightMinusBtn = createPaletteButton("-", "Remove color from right");
|
|
4808
|
+
rightMinusBtn.addEventListener("click", () => {
|
|
4809
|
+
if (this.state.colorPalette.length <= 2) {
|
|
4810
|
+
console.warn("Cannot remove color: minimum 2 colors required");
|
|
4811
|
+
return;
|
|
4812
|
+
}
|
|
4813
|
+
this.state.colorPalette.pop();
|
|
4814
|
+
this.state.colorPositions = this.state.colorPalette.map((_, i) => i / (this.state.colorPalette.length - 1));
|
|
4815
|
+
updateGradientDisplay();
|
|
4816
|
+
recreateColorSquares();
|
|
4817
|
+
minColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMin);
|
|
4818
|
+
minHandle.style.backgroundColor = minColor;
|
|
4819
|
+
maxColor = interpolateGradient(this.state.colorPalette, this.state.colorPositions, this.state.transformMax);
|
|
4820
|
+
maxHandle.style.backgroundColor = maxColor;
|
|
4821
|
+
this.updateScale();
|
|
4822
|
+
});
|
|
4823
|
+
rightButtonsContainer.appendChild(rightPlusBtn);
|
|
4824
|
+
rightButtonsContainer.appendChild(rightMinusBtn);
|
|
4825
|
+
container.appendChild(leftButtonsContainer);
|
|
4826
|
+
container.appendChild(gradientContainer);
|
|
4827
|
+
container.appendChild(rightButtonsContainer);
|
|
4828
|
+
return container;
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
function createPaletteButton(text, title) {
|
|
4832
|
+
const button = document.createElement("button");
|
|
4833
|
+
button.className = "ht-palette-button";
|
|
4834
|
+
button.textContent = text;
|
|
4835
|
+
button.title = title;
|
|
4836
|
+
return button;
|
|
4837
|
+
}
|
|
4838
|
+
function interpolateGradient(colors, positions, t) {
|
|
4839
|
+
t = Math.max(0, Math.min(1, t));
|
|
4840
|
+
if (colors.length === 1) {
|
|
4841
|
+
return colors[0];
|
|
4842
|
+
}
|
|
4843
|
+
let i = 0;
|
|
4844
|
+
while (i < positions.length - 1 && t > positions[i + 1]) {
|
|
4845
|
+
i++;
|
|
4846
|
+
}
|
|
4847
|
+
if (t === positions[i]) {
|
|
4848
|
+
return colors[i];
|
|
4849
|
+
}
|
|
4850
|
+
if (i === positions.length - 1) {
|
|
4851
|
+
return colors[i];
|
|
4852
|
+
}
|
|
4853
|
+
const t1 = positions[i];
|
|
4854
|
+
const t2 = positions[i + 1];
|
|
4855
|
+
const localT = (t - t1) / (t2 - t1);
|
|
4856
|
+
const color1 = hexToRgb(colors[i]);
|
|
4857
|
+
const color2 = hexToRgb(colors[i + 1]);
|
|
4858
|
+
const r = Math.round(color1.r + (color2.r - color1.r) * localT);
|
|
4859
|
+
const g = Math.round(color1.g + (color2.g - color1.g) * localT);
|
|
4860
|
+
const b = Math.round(color1.b + (color2.b - color1.b) * localT);
|
|
4861
|
+
return rgbToHex(r, g, b);
|
|
4862
|
+
}
|
|
4863
|
+
function hexToRgb(hex2) {
|
|
4864
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex2);
|
|
4865
|
+
return result ? {
|
|
4866
|
+
r: parseInt(result[1], 16),
|
|
4867
|
+
g: parseInt(result[2], 16),
|
|
4868
|
+
b: parseInt(result[3], 16)
|
|
4869
|
+
} : { r: 0, g: 0, b: 0 };
|
|
4870
|
+
}
|
|
4871
|
+
function rgbToHex(r, g, b) {
|
|
4872
|
+
return "#" + [r, g, b].map((x) => {
|
|
4873
|
+
const hex2 = x.toString(16);
|
|
4874
|
+
return hex2.length === 1 ? "0" + hex2 : hex2;
|
|
4875
|
+
}).join("");
|
|
4876
|
+
}
|
|
4877
|
+
function createColorSquareWithTick(parent, color2, colorIndex, clickHandler) {
|
|
4878
|
+
const squareContainer = document.createElement("div");
|
|
4879
|
+
squareContainer.className = "ht-color-square-wrapper";
|
|
4880
|
+
const square = document.createElement("div");
|
|
4881
|
+
square.className = "ht-color-square";
|
|
4882
|
+
square.style.backgroundColor = color2;
|
|
4883
|
+
square.title = "Click to edit color";
|
|
4884
|
+
square.setAttribute("data-color-index", colorIndex);
|
|
4885
|
+
square.addEventListener("click", clickHandler);
|
|
4886
|
+
const tick = document.createElement("div");
|
|
4887
|
+
tick.className = "ht-color-square-tick";
|
|
4888
|
+
squareContainer.appendChild(square);
|
|
4889
|
+
squareContainer.appendChild(tick);
|
|
4890
|
+
parent.appendChild(squareContainer);
|
|
4891
|
+
return squareContainer;
|
|
3706
4892
|
}
|
|
3707
4893
|
class TreeData extends Subscribable {
|
|
3708
4894
|
tree;
|
|
3709
4895
|
metadata = /* @__PURE__ */ new Map();
|
|
4896
|
+
// Map of table ID to array of row objects
|
|
3710
4897
|
metadataTableNames = /* @__PURE__ */ new Map();
|
|
3711
4898
|
// Map of table ID to display name
|
|
3712
4899
|
columnType = /* @__PURE__ */ new Map();
|
|
@@ -3717,14 +4904,17 @@ class TreeData extends Subscribable {
|
|
|
3717
4904
|
// Display-friendly column name, keyed by unique column ID
|
|
3718
4905
|
columnAesthetic = /* @__PURE__ */ new Map();
|
|
3719
4906
|
// Map of columnId -> Map of aestheticId -> Aesthetic
|
|
4907
|
+
nodeIdColumn = /* @__PURE__ */ new Map();
|
|
4908
|
+
// Map of table ID to the column name used for node IDs
|
|
4909
|
+
validIdColumns = /* @__PURE__ */ new Map();
|
|
4910
|
+
// Map of table ID to array of column names that contain valid node IDs
|
|
3720
4911
|
#nextTableId = 0;
|
|
3721
4912
|
constructor(newickStr, metadataTables = [], metadataTableNames = []) {
|
|
3722
4913
|
super();
|
|
3723
4914
|
this.tree = this.parseTree(newickStr);
|
|
3724
4915
|
if (Array.isArray(metadataTables)) {
|
|
3725
4916
|
metadataTables.forEach((tableStr, index) => {
|
|
3726
|
-
|
|
3727
|
-
this.addTable(tableStr, tableName);
|
|
4917
|
+
this.addTable(tableStr, metadataTableNames[index]);
|
|
3728
4918
|
});
|
|
3729
4919
|
}
|
|
3730
4920
|
}
|
|
@@ -3752,7 +4942,7 @@ class TreeData extends Subscribable {
|
|
|
3752
4942
|
*/
|
|
3753
4943
|
setTree(newickStr) {
|
|
3754
4944
|
this.tree = this.parseTree(newickStr);
|
|
3755
|
-
this.metadata.keys().forEach(this.#attachTable);
|
|
4945
|
+
this.metadata.keys().forEach((tableId) => this.#attachTable(tableId));
|
|
3756
4946
|
this.notify("treeUpdated", this);
|
|
3757
4947
|
}
|
|
3758
4948
|
/**
|
|
@@ -3762,6 +4952,39 @@ class TreeData extends Subscribable {
|
|
|
3762
4952
|
getMetadataTableNames() {
|
|
3763
4953
|
return Array.from(this.metadataTableNames.values());
|
|
3764
4954
|
}
|
|
4955
|
+
/**
|
|
4956
|
+
* Get all node names from the tree
|
|
4957
|
+
* @returns {Set<string>} Set of all node names in the tree
|
|
4958
|
+
*/
|
|
4959
|
+
getTreeNodeNames() {
|
|
4960
|
+
const nodeNames = /* @__PURE__ */ new Set();
|
|
4961
|
+
this.tree.each((d) => {
|
|
4962
|
+
if (d.data.name) {
|
|
4963
|
+
nodeNames.add(d.data.name);
|
|
4964
|
+
}
|
|
4965
|
+
});
|
|
4966
|
+
return nodeNames;
|
|
4967
|
+
}
|
|
4968
|
+
/**
|
|
4969
|
+
* Generate a Map from node ID to row data for a given table
|
|
4970
|
+
* @param {string} tableId - ID of the table
|
|
4971
|
+
* @returns {Map} Map from node ID to row data
|
|
4972
|
+
*/
|
|
4973
|
+
#generateMetadataMap(tableId) {
|
|
4974
|
+
const rows = this.metadata.get(tableId);
|
|
4975
|
+
const idColumn = this.nodeIdColumn.get(tableId);
|
|
4976
|
+
if (!rows || !idColumn) {
|
|
4977
|
+
return /* @__PURE__ */ new Map();
|
|
4978
|
+
}
|
|
4979
|
+
const metadataMap = /* @__PURE__ */ new Map();
|
|
4980
|
+
for (const row of rows) {
|
|
4981
|
+
const nodeId = row[idColumn];
|
|
4982
|
+
if (nodeId) {
|
|
4983
|
+
metadataMap.set(nodeId, row);
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
return metadataMap;
|
|
4987
|
+
}
|
|
3765
4988
|
/**
|
|
3766
4989
|
* Add a metadata table
|
|
3767
4990
|
* @param {string} tableStr - TSV formatted string or path
|
|
@@ -3770,22 +4993,29 @@ class TreeData extends Subscribable {
|
|
|
3770
4993
|
* @returns {string} The table ID
|
|
3771
4994
|
*/
|
|
3772
4995
|
addTable(tableStr, tableName = null, sep = " ") {
|
|
3773
|
-
|
|
3774
|
-
const
|
|
4996
|
+
let { metadataMap, columnTypes, idColumns } = parseTable(tableStr, this.getTreeNodeNames(), sep);
|
|
4997
|
+
const tableId = `table_${this.#nextTableId++}`;
|
|
3775
4998
|
if (!tableName) {
|
|
3776
4999
|
tableName = `Metadata ${this.#nextTableId}`;
|
|
3777
5000
|
}
|
|
3778
|
-
this.metadataTableNames.set(
|
|
5001
|
+
this.metadataTableNames.set(tableId, tableName);
|
|
3779
5002
|
const columnIdMap = /* @__PURE__ */ new Map();
|
|
3780
5003
|
for (const [originalName, columnType] of columnTypes) {
|
|
3781
|
-
const uniqueId = `${
|
|
5004
|
+
const uniqueId = `${tableId}_${originalName}`;
|
|
3782
5005
|
columnIdMap.set(originalName, uniqueId);
|
|
3783
5006
|
this.columnType.set(uniqueId, columnType);
|
|
3784
5007
|
this.columnName.set(uniqueId, originalName);
|
|
3785
5008
|
this.columnDisplayName.set(uniqueId, columnToHeader(originalName));
|
|
3786
5009
|
}
|
|
3787
|
-
|
|
3788
|
-
|
|
5010
|
+
idColumns = idColumns.map((x) => columnIdMap.get(x));
|
|
5011
|
+
let selectedIdColumn = null;
|
|
5012
|
+
if (idColumns.length > 0) {
|
|
5013
|
+
selectedIdColumn = idColumns[0];
|
|
5014
|
+
} else {
|
|
5015
|
+
console.warn(`No valid node ID column found in table ${tableName}`);
|
|
5016
|
+
}
|
|
5017
|
+
const metadataArray = [];
|
|
5018
|
+
for (const nodeData of metadataMap.values()) {
|
|
3789
5019
|
const transformedNodeData = {};
|
|
3790
5020
|
for (const [originalColumnName, value] of Object.entries(nodeData)) {
|
|
3791
5021
|
const uniqueId = columnIdMap.get(originalColumnName);
|
|
@@ -3793,15 +5023,79 @@ class TreeData extends Subscribable {
|
|
|
3793
5023
|
transformedNodeData[uniqueId] = value;
|
|
3794
5024
|
}
|
|
3795
5025
|
}
|
|
3796
|
-
|
|
5026
|
+
metadataArray.push(transformedNodeData);
|
|
3797
5027
|
}
|
|
3798
|
-
this.
|
|
3799
|
-
this
|
|
5028
|
+
this.validIdColumns.set(tableId, idColumns);
|
|
5029
|
+
this.nodeIdColumn.set(tableId, selectedIdColumn);
|
|
5030
|
+
this.metadata.set(tableId, metadataArray);
|
|
5031
|
+
this.#attachTable(tableId);
|
|
3800
5032
|
this.notify("metadataAdded", {
|
|
3801
|
-
tableId
|
|
3802
|
-
columnIds: columnIdMap.values()
|
|
5033
|
+
tableId,
|
|
5034
|
+
columnIds: Array.from(columnIdMap.values())
|
|
5035
|
+
});
|
|
5036
|
+
return tableId;
|
|
5037
|
+
}
|
|
5038
|
+
/**
|
|
5039
|
+
* Get valid ID columns for a table
|
|
5040
|
+
* @param {string} tableId - ID of the table
|
|
5041
|
+
* @returns {Array<string>} Array of column names that contain valid node IDs
|
|
5042
|
+
*/
|
|
5043
|
+
getValidIdColumns(tableId) {
|
|
5044
|
+
return this.validIdColumns.get(tableId) || [];
|
|
5045
|
+
}
|
|
5046
|
+
/**
|
|
5047
|
+
* Get the current node ID column for a table
|
|
5048
|
+
* @param {string} tableId - ID of the table
|
|
5049
|
+
* @returns {string|null} Column name used as node ID, or null if none
|
|
5050
|
+
*/
|
|
5051
|
+
getNodeIdColumn(tableId) {
|
|
5052
|
+
return this.nodeIdColumn.get(tableId);
|
|
5053
|
+
}
|
|
5054
|
+
/**
|
|
5055
|
+
* Get all column IDs for a table
|
|
5056
|
+
* @param {string} tableId - ID of the table
|
|
5057
|
+
* @returns {Array<string>} Array of column IDs in the table
|
|
5058
|
+
*/
|
|
5059
|
+
getTableColumnIds(tableId) {
|
|
5060
|
+
const table = this.metadata.get(tableId);
|
|
5061
|
+
if (!table || table.length === 0) {
|
|
5062
|
+
return [];
|
|
5063
|
+
}
|
|
5064
|
+
return Object.keys(table[0]);
|
|
5065
|
+
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Change the node ID column for a table
|
|
5068
|
+
* @param {string} tableId - ID of the table
|
|
5069
|
+
* @param {string} newIdColumnName - Name of the new ID column to use
|
|
5070
|
+
*/
|
|
5071
|
+
setNodeIdColumn(tableId, newIdColumnName) {
|
|
5072
|
+
const table = this.metadata.get(tableId);
|
|
5073
|
+
if (!table) {
|
|
5074
|
+
console.warn(`Table ${tableId} does not exist`);
|
|
5075
|
+
return;
|
|
5076
|
+
}
|
|
5077
|
+
if (!this.validIdColumns.get(tableId).includes(newIdColumnName)) {
|
|
5078
|
+
console.warn(`Column ${newIdColumnName} is not a valid ID column for table ${tableId}`);
|
|
5079
|
+
return;
|
|
5080
|
+
}
|
|
5081
|
+
const oldIdColumn = this.nodeIdColumn.get(tableId);
|
|
5082
|
+
if (oldIdColumn === newIdColumnName) {
|
|
5083
|
+
return;
|
|
5084
|
+
}
|
|
5085
|
+
const columnIds = this.getTableColumnIds(tableId);
|
|
5086
|
+
for (const columnId of columnIds) {
|
|
5087
|
+
this.columnAesthetic.delete(columnId);
|
|
5088
|
+
}
|
|
5089
|
+
this.#detachTable(tableId);
|
|
5090
|
+
this.nodeIdColumn.set(tableId, newIdColumnName);
|
|
5091
|
+
this.#attachTable(tableId);
|
|
5092
|
+
this.notify("metadataChanged", {
|
|
5093
|
+
tableId,
|
|
5094
|
+
oldIdColumn,
|
|
5095
|
+
newIdColumn: newIdColumnName,
|
|
5096
|
+
columnIds,
|
|
5097
|
+
requiresAestheticRefresh: true
|
|
3803
5098
|
});
|
|
3804
|
-
return id2;
|
|
3805
5099
|
}
|
|
3806
5100
|
/**
|
|
3807
5101
|
* Remove a metadata table
|
|
@@ -3813,7 +5107,7 @@ class TreeData extends Subscribable {
|
|
|
3813
5107
|
console.warn(`Table ${tableId} does not exist`);
|
|
3814
5108
|
return;
|
|
3815
5109
|
}
|
|
3816
|
-
const keys = Object.keys(table
|
|
5110
|
+
const keys = table.length > 0 ? Object.keys(table[0]) : [];
|
|
3817
5111
|
for (const uniqueId of keys) {
|
|
3818
5112
|
this.columnType.delete(uniqueId);
|
|
3819
5113
|
this.columnName.delete(uniqueId);
|
|
@@ -3823,7 +5117,9 @@ class TreeData extends Subscribable {
|
|
|
3823
5117
|
this.#detachTable(tableId);
|
|
3824
5118
|
this.metadata.delete(tableId);
|
|
3825
5119
|
this.metadataTableNames.delete(tableId);
|
|
3826
|
-
this.
|
|
5120
|
+
this.nodeIdColumn.delete(tableId);
|
|
5121
|
+
this.validIdColumns.delete(tableId);
|
|
5122
|
+
this.notify("metadataChanged", {
|
|
3827
5123
|
tableId,
|
|
3828
5124
|
columnIds: keys
|
|
3829
5125
|
});
|
|
@@ -3893,11 +5189,11 @@ class TreeData extends Subscribable {
|
|
|
3893
5189
|
* Add metadata to tree nodes
|
|
3894
5190
|
*/
|
|
3895
5191
|
#attachTable(tableId) {
|
|
3896
|
-
const
|
|
5192
|
+
const metadataMap = this.#generateMetadataMap(tableId);
|
|
3897
5193
|
this.tree.each((d) => {
|
|
3898
5194
|
const nodeName = d.data.name;
|
|
3899
|
-
if (nodeName &&
|
|
3900
|
-
const tableMetadata =
|
|
5195
|
+
if (nodeName && metadataMap.has(nodeName)) {
|
|
5196
|
+
const tableMetadata = metadataMap.get(nodeName);
|
|
3901
5197
|
d.metadata = { ...d.metadata, ...tableMetadata };
|
|
3902
5198
|
}
|
|
3903
5199
|
});
|
|
@@ -3907,11 +5203,16 @@ class TreeData extends Subscribable {
|
|
|
3907
5203
|
*/
|
|
3908
5204
|
#detachTable(tableId) {
|
|
3909
5205
|
const table = this.metadata.get(tableId);
|
|
3910
|
-
|
|
5206
|
+
if (!table || table.length === 0) {
|
|
5207
|
+
return;
|
|
5208
|
+
}
|
|
5209
|
+
const keys = Object.keys(table[0]);
|
|
3911
5210
|
this.tree.each((d) => {
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
5211
|
+
if (d.metadata) {
|
|
5212
|
+
keys.forEach((key) => {
|
|
5213
|
+
delete d.metadata[key];
|
|
5214
|
+
});
|
|
5215
|
+
}
|
|
3915
5216
|
});
|
|
3916
5217
|
}
|
|
3917
5218
|
}
|
|
@@ -4208,6 +5509,7 @@ class TreeState extends Subscribable {
|
|
|
4208
5509
|
title: "Tip label text",
|
|
4209
5510
|
scaleType: "identity",
|
|
4210
5511
|
default: "",
|
|
5512
|
+
nullValue: "",
|
|
4211
5513
|
downstream: ["updateTipLabelText", "updateCoordinates"],
|
|
4212
5514
|
hasLegend: false
|
|
4213
5515
|
},
|
|
@@ -4215,6 +5517,7 @@ class TreeState extends Subscribable {
|
|
|
4215
5517
|
title: "Tip label color",
|
|
4216
5518
|
scaleType: "color",
|
|
4217
5519
|
default: "#000000",
|
|
5520
|
+
nullValue: "#808080",
|
|
4218
5521
|
otherCategory: "#555555",
|
|
4219
5522
|
downstream: [],
|
|
4220
5523
|
hasLegend: true
|
|
@@ -4223,6 +5526,7 @@ class TreeState extends Subscribable {
|
|
|
4223
5526
|
title: "Tip label size",
|
|
4224
5527
|
scaleType: "size",
|
|
4225
5528
|
default: 1,
|
|
5529
|
+
nullValue: 1,
|
|
4226
5530
|
isCategorical: false,
|
|
4227
5531
|
outputRange: [0.5, 2],
|
|
4228
5532
|
downstream: ["updateCoordinates"],
|
|
@@ -4232,6 +5536,7 @@ class TreeState extends Subscribable {
|
|
|
4232
5536
|
title: "Tip label font",
|
|
4233
5537
|
scaleType: "identity",
|
|
4234
5538
|
default: "sans-serif",
|
|
5539
|
+
nullValue: "sans-serif",
|
|
4235
5540
|
downstream: ["updateCoordinates"],
|
|
4236
5541
|
hasLegend: false
|
|
4237
5542
|
},
|
|
@@ -4240,6 +5545,7 @@ class TreeState extends Subscribable {
|
|
|
4240
5545
|
scaleType: "text",
|
|
4241
5546
|
outputValues: ["normal", "bold", "italic", "bold italic"],
|
|
4242
5547
|
default: "normal",
|
|
5548
|
+
nullValue: "normal",
|
|
4243
5549
|
otherCategory: "italic",
|
|
4244
5550
|
downstream: ["updateCoordinates"],
|
|
4245
5551
|
hasLegend: false
|
|
@@ -4248,6 +5554,7 @@ class TreeState extends Subscribable {
|
|
|
4248
5554
|
title: "Node label text",
|
|
4249
5555
|
scaleType: "identity",
|
|
4250
5556
|
default: "",
|
|
5557
|
+
nullValue: "",
|
|
4251
5558
|
downstream: ["updateNodeLabelText"],
|
|
4252
5559
|
hasLegend: false
|
|
4253
5560
|
},
|
|
@@ -4255,6 +5562,7 @@ class TreeState extends Subscribable {
|
|
|
4255
5562
|
title: "Node label size",
|
|
4256
5563
|
scaleType: "size",
|
|
4257
5564
|
default: 1,
|
|
5565
|
+
nullValue: 1,
|
|
4258
5566
|
isCategorical: false,
|
|
4259
5567
|
outputRange: [0.5, 2],
|
|
4260
5568
|
downstream: ["updateCoordinates"],
|
|
@@ -4306,8 +5614,22 @@ class TreeState extends Subscribable {
|
|
|
4306
5614
|
this.state.treeData.subscribe("treeUpdate", () => {
|
|
4307
5615
|
this.#initalize();
|
|
4308
5616
|
});
|
|
4309
|
-
this.state.treeData.subscribe("
|
|
4310
|
-
|
|
5617
|
+
this.state.treeData.subscribe("metadataChanged", (info) => {
|
|
5618
|
+
if (info.columnIds && Array.isArray(info.columnIds)) {
|
|
5619
|
+
if (info.requiresAestheticRefresh) {
|
|
5620
|
+
const aestheticsToRefresh = {};
|
|
5621
|
+
for (const [aestheticId, columnId] of Object.entries(this.state.aesthetics)) {
|
|
5622
|
+
if (columnId && info.columnIds.includes(columnId)) {
|
|
5623
|
+
aestheticsToRefresh[aestheticId] = columnId;
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
if (Object.keys(aestheticsToRefresh).length > 0) {
|
|
5627
|
+
this.setAesthetics(aestheticsToRefresh, true);
|
|
5628
|
+
}
|
|
5629
|
+
} else {
|
|
5630
|
+
this.setAesthetics(Object.fromEntries(info.columnIds.map((key) => [key, void 0])));
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
4311
5633
|
});
|
|
4312
5634
|
}
|
|
4313
5635
|
#initalize() {
|
|
@@ -4357,21 +5679,16 @@ class TreeState extends Subscribable {
|
|
|
4357
5679
|
if (force || columnId !== this.state.aesthetics[aestheticId]) {
|
|
4358
5680
|
this.state.aesthetics[aestheticId] = columnId;
|
|
4359
5681
|
if (!columnId) {
|
|
4360
|
-
this.aestheticsScales[aestheticId] = new NullScale(aesData.default);
|
|
5682
|
+
this.aestheticsScales[aestheticId] = new NullScale({ default: aesData.default });
|
|
4361
5683
|
} else {
|
|
4362
5684
|
this.aestheticsScales[aestheticId] = this.state.treeData.getAesthetic(columnId, aestheticId, aesData);
|
|
5685
|
+
this.aestheticsScales[aestheticId].subscribe("aestheticChange", () => {
|
|
5686
|
+
this.#updateTreeDataForAesthetic(aestheticId, columnId);
|
|
5687
|
+
this.#updateLegends();
|
|
5688
|
+
this.notify(`${aestheticId}Change`);
|
|
5689
|
+
});
|
|
4363
5690
|
}
|
|
4364
|
-
this
|
|
4365
|
-
if (columnId && columnId !== null && columnId !== void 0) {
|
|
4366
|
-
if (d.metadata && d.metadata[columnId] !== void 0) {
|
|
4367
|
-
d[aestheticId] = this.aestheticsScales[aestheticId].getValue(d.metadata[columnId]);
|
|
4368
|
-
} else {
|
|
4369
|
-
d[aestheticId] = aesData.default;
|
|
4370
|
-
}
|
|
4371
|
-
} else {
|
|
4372
|
-
d[aestheticId] = this.aestheticsScales[aestheticId].getValue();
|
|
4373
|
-
}
|
|
4374
|
-
});
|
|
5691
|
+
this.#updateTreeDataForAesthetic(aestheticId, columnId);
|
|
4375
5692
|
for (const methodName of aesData.downstream) {
|
|
4376
5693
|
downstreams.add(methodName);
|
|
4377
5694
|
}
|
|
@@ -4388,6 +5705,24 @@ class TreeState extends Subscribable {
|
|
|
4388
5705
|
this[methodName]();
|
|
4389
5706
|
}
|
|
4390
5707
|
}
|
|
5708
|
+
/**
|
|
5709
|
+
* Update tree data for a specific aesthetic
|
|
5710
|
+
* @private
|
|
5711
|
+
*/
|
|
5712
|
+
#updateTreeDataForAesthetic(aestheticId, columnId) {
|
|
5713
|
+
const aesData = this.#AESTHETICS[aestheticId];
|
|
5714
|
+
this.state.treeData.tree.each((d) => {
|
|
5715
|
+
if (columnId && columnId !== null && columnId !== void 0) {
|
|
5716
|
+
if (d.metadata) {
|
|
5717
|
+
d[aestheticId] = this.aestheticsScales[aestheticId].getValue(d.metadata[columnId]);
|
|
5718
|
+
} else {
|
|
5719
|
+
d[aestheticId] = aesData.default;
|
|
5720
|
+
}
|
|
5721
|
+
} else {
|
|
5722
|
+
d[aestheticId] = this.aestheticsScales[aestheticId].getValue();
|
|
5723
|
+
}
|
|
5724
|
+
});
|
|
5725
|
+
}
|
|
4391
5726
|
#updateLegends() {
|
|
4392
5727
|
this.legends = [];
|
|
4393
5728
|
for (const [aestheticId, columnId] of Object.entries(this.state.aesthetics)) {
|
|
@@ -4727,6 +6062,10 @@ const ICONS = {
|
|
|
4727
6062
|
"m 17,4 c 0,0 9,8 0,16",
|
|
4728
6063
|
"M 17,8 V 4 h 4",
|
|
4729
6064
|
"M 21,20 H 17 V 16"
|
|
6065
|
+
],
|
|
6066
|
+
edit: [
|
|
6067
|
+
"m 12,8 -8,8 -1,5 5,-1 8,-8 z",
|
|
6068
|
+
"M 21,7 18,10 14,6 17,3 Z"
|
|
4730
6069
|
]
|
|
4731
6070
|
};
|
|
4732
6071
|
function appendIcon(svgSel, name, size, padding = 2) {
|
|
@@ -4899,8 +6238,8 @@ class TextSizeLegend extends LegendBase {
|
|
|
4899
6238
|
updateCoordinates() {
|
|
4900
6239
|
const minValue = this.state.aesthetic.scale.dataMin;
|
|
4901
6240
|
const maxValue = this.state.aesthetic.scale.dataMax;
|
|
4902
|
-
const minSize = this.state.aesthetic.
|
|
4903
|
-
const maxSize = this.state.aesthetic.
|
|
6241
|
+
const minSize = this.state.aesthetic.state.outputRange[0];
|
|
6242
|
+
const maxSize = this.state.aesthetic.state.outputRange[1];
|
|
4904
6243
|
const ticks = generateNiceTicks(minValue, maxValue, 5);
|
|
4905
6244
|
const maxLetterFont = maxSize * this.state.treeState.labelSizeToPxFactor * 0.7;
|
|
4906
6245
|
const minLetterFont = minSize * this.state.treeState.labelSizeToPxFactor * 0.7;
|
|
@@ -4939,8 +6278,8 @@ class TextSizeLegend extends LegendBase {
|
|
|
4939
6278
|
text: this.state.aesthetic.state.inputUnits || ""
|
|
4940
6279
|
}
|
|
4941
6280
|
};
|
|
4942
|
-
|
|
4943
|
-
const x = leftOverhang +
|
|
6281
|
+
if (minValue === maxValue) {
|
|
6282
|
+
const x = leftOverhang + baseWidth / 2;
|
|
4944
6283
|
this.coordinates.ticks.push({
|
|
4945
6284
|
x1: x,
|
|
4946
6285
|
y1: rampBaseY,
|
|
@@ -4950,15 +6289,37 @@ class TextSizeLegend extends LegendBase {
|
|
|
4950
6289
|
this.coordinates.labels.push({
|
|
4951
6290
|
x,
|
|
4952
6291
|
y: rampBaseY + this.tickHeight,
|
|
4953
|
-
text: formatTickLabel(
|
|
6292
|
+
text: formatTickLabel(minValue, ticks)
|
|
4954
6293
|
});
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
6294
|
+
const avgLetterFont = (minLetterFont + maxLetterFont) / 2;
|
|
6295
|
+
this.coordinates.polygon = [
|
|
6296
|
+
{ x: leftOverhang, y: rampBaseY },
|
|
6297
|
+
{ x: leftOverhang, y: rampBaseY - avgLetterFont },
|
|
6298
|
+
{ x: leftOverhang + baseWidth, y: rampBaseY - avgLetterFont },
|
|
6299
|
+
{ x: leftOverhang + baseWidth, y: rampBaseY }
|
|
6300
|
+
];
|
|
6301
|
+
} else {
|
|
6302
|
+
ticks.forEach((tickValue, i) => {
|
|
6303
|
+
const x = leftOverhang + i / (ticks.length - 1) * baseWidth;
|
|
6304
|
+
this.coordinates.ticks.push({
|
|
6305
|
+
x1: x,
|
|
6306
|
+
y1: rampBaseY,
|
|
6307
|
+
x2: x,
|
|
6308
|
+
y2: rampBaseY + this.tickHeight
|
|
6309
|
+
});
|
|
6310
|
+
this.coordinates.labels.push({
|
|
6311
|
+
x,
|
|
6312
|
+
y: rampBaseY + this.tickHeight,
|
|
6313
|
+
text: formatTickLabel(tickValue, ticks)
|
|
6314
|
+
});
|
|
6315
|
+
});
|
|
6316
|
+
this.coordinates.polygon = [
|
|
6317
|
+
{ x: leftOverhang, y: rampBaseY },
|
|
6318
|
+
{ x: leftOverhang, y: rampBaseY - minLetterFont },
|
|
6319
|
+
{ x: leftOverhang + baseWidth, y: rampBaseY - maxLetterFont },
|
|
6320
|
+
{ x: leftOverhang + baseWidth, y: rampBaseY }
|
|
6321
|
+
];
|
|
6322
|
+
}
|
|
4962
6323
|
}
|
|
4963
6324
|
/**
|
|
4964
6325
|
* Render the legend in the specified SVG element
|
|
@@ -5035,7 +6396,7 @@ class TextColorLegend extends LegendBase {
|
|
|
5035
6396
|
let currentX = 0;
|
|
5036
6397
|
let currentY = titleHeightOffset + this.verticalSpacing + this.squareSize / 2;
|
|
5037
6398
|
let rowHeight = this.squareSize;
|
|
5038
|
-
categories.slice(0, aesthetic.
|
|
6399
|
+
categories.slice(0, aesthetic.state.maxCategories).forEach((category, i) => {
|
|
5039
6400
|
const color2 = aesthetic.scale.getValue(category);
|
|
5040
6401
|
const labelSize = this.textSizeEstimator.getTextSize(category, this.state.labelFontSize);
|
|
5041
6402
|
const itemWidth = this.squareSize + this.itemLabelGap + labelSize.widthPx;
|
|
@@ -5048,7 +6409,7 @@ class TextColorLegend extends LegendBase {
|
|
|
5048
6409
|
x: currentX,
|
|
5049
6410
|
y: currentY,
|
|
5050
6411
|
color: color2,
|
|
5051
|
-
label: i < aesthetic.
|
|
6412
|
+
label: i < aesthetic.state.maxCategories - 1 ? category : aesthetic.state.otherLabel,
|
|
5052
6413
|
squareX: currentX,
|
|
5053
6414
|
squareY: currentY - this.squareSize / 2,
|
|
5054
6415
|
labelX: currentX + this.squareSize + this.itemLabelGap,
|
|
@@ -5105,8 +6466,8 @@ class TextColorLegend extends LegendBase {
|
|
|
5105
6466
|
text: aesthetic.state.inputUnits || ""
|
|
5106
6467
|
}
|
|
5107
6468
|
};
|
|
5108
|
-
|
|
5109
|
-
const x = leftOverhang +
|
|
6469
|
+
if (minValue === maxValue) {
|
|
6470
|
+
const x = leftOverhang + baseWidth / 2;
|
|
5110
6471
|
this.coordinates.ticks.push({
|
|
5111
6472
|
x1: x,
|
|
5112
6473
|
y1: ticksY,
|
|
@@ -5116,9 +6477,24 @@ class TextColorLegend extends LegendBase {
|
|
|
5116
6477
|
this.coordinates.labels.push({
|
|
5117
6478
|
x,
|
|
5118
6479
|
y: labelsY,
|
|
5119
|
-
text: formatTickLabel(
|
|
6480
|
+
text: formatTickLabel(minValue, ticks)
|
|
5120
6481
|
});
|
|
5121
|
-
}
|
|
6482
|
+
} else {
|
|
6483
|
+
ticks.forEach((tickValue, i) => {
|
|
6484
|
+
const x = leftOverhang + i / (ticks.length - 1) * baseWidth;
|
|
6485
|
+
this.coordinates.ticks.push({
|
|
6486
|
+
x1: x,
|
|
6487
|
+
y1: ticksY,
|
|
6488
|
+
x2: x,
|
|
6489
|
+
y2: ticksY + this.tickHeight
|
|
6490
|
+
});
|
|
6491
|
+
this.coordinates.labels.push({
|
|
6492
|
+
x,
|
|
6493
|
+
y: labelsY,
|
|
6494
|
+
text: formatTickLabel(tickValue, ticks)
|
|
6495
|
+
});
|
|
6496
|
+
});
|
|
6497
|
+
}
|
|
5122
6498
|
}
|
|
5123
6499
|
/**
|
|
5124
6500
|
* Render the legend in the specified SVG element
|
|
@@ -5154,11 +6530,17 @@ class TextColorLegend extends LegendBase {
|
|
|
5154
6530
|
const gradientId = `color-gradient-${Math.random().toString(36).substr(2, 9)}`;
|
|
5155
6531
|
const defs = this.group.append("defs");
|
|
5156
6532
|
const gradient = defs.append("linearGradient").attr("id", gradientId).attr("x1", "0%").attr("x2", "100%").attr("y1", "0%").attr("y2", "0%");
|
|
5157
|
-
|
|
5158
|
-
const
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
6533
|
+
if (minValue === maxValue) {
|
|
6534
|
+
const color2 = aesthetic.scale.getValue(minValue);
|
|
6535
|
+
gradient.append("stop").attr("offset", "0%").attr("stop-color", color2);
|
|
6536
|
+
gradient.append("stop").attr("offset", "100%").attr("stop-color", color2);
|
|
6537
|
+
} else {
|
|
6538
|
+
for (let i = 0; i <= this.numGradientStops; i++) {
|
|
6539
|
+
const t = i / this.numGradientStops;
|
|
6540
|
+
const value = minValue + t * (maxValue - minValue);
|
|
6541
|
+
const color2 = aesthetic.scale.getValue(value);
|
|
6542
|
+
gradient.append("stop").attr("offset", `${t * 100}%`).attr("stop-color", color2);
|
|
6543
|
+
}
|
|
5162
6544
|
}
|
|
5163
6545
|
this.group.append("rect").attr("x", this.coordinates.gradient.x).attr("y", this.coordinates.gradient.y).attr("width", this.coordinates.gradient.width).attr("height", this.coordinates.gradient.height).style("fill", `url(#${gradientId})`).style("stroke", "#000").style("stroke-width", 1);
|
|
5164
6546
|
this.coordinates.ticks.forEach((tick) => {
|
|
@@ -6264,10 +7646,75 @@ function convertSvgToPng(svgString, width, height, filename) {
|
|
|
6264
7646
|
};
|
|
6265
7647
|
img.src = url;
|
|
6266
7648
|
}
|
|
7649
|
+
function createControlGroup() {
|
|
7650
|
+
const group = document.createElement("div");
|
|
7651
|
+
group.className = "ht-control-group";
|
|
7652
|
+
return group;
|
|
7653
|
+
}
|
|
7654
|
+
function createLabel(text, height) {
|
|
7655
|
+
const label = document.createElement("label");
|
|
7656
|
+
label.className = "ht-control-label";
|
|
7657
|
+
label.textContent = text;
|
|
7658
|
+
label.style.height = `${height}px`;
|
|
7659
|
+
return label;
|
|
7660
|
+
}
|
|
7661
|
+
function createButton(text, title = "", height) {
|
|
7662
|
+
const button = document.createElement("button");
|
|
7663
|
+
button.className = "ht-button";
|
|
7664
|
+
button.textContent = text;
|
|
7665
|
+
button.title = title;
|
|
7666
|
+
button.style.height = `${height}px`;
|
|
7667
|
+
return button;
|
|
7668
|
+
}
|
|
7669
|
+
function createIconButton(iconSvg, title = "", height) {
|
|
7670
|
+
const button = document.createElement("button");
|
|
7671
|
+
button.className = "ht-icon-button";
|
|
7672
|
+
button.innerHTML = iconSvg;
|
|
7673
|
+
button.title = title;
|
|
7674
|
+
button.style.height = `${height}px`;
|
|
7675
|
+
button.style.width = `${height}px`;
|
|
7676
|
+
return button;
|
|
7677
|
+
}
|
|
7678
|
+
function createSlider(min, max, value, step, height) {
|
|
7679
|
+
const slider = document.createElement("input");
|
|
7680
|
+
slider.type = "range";
|
|
7681
|
+
slider.className = "ht-slider";
|
|
7682
|
+
slider.min = min;
|
|
7683
|
+
slider.max = max;
|
|
7684
|
+
slider.value = value;
|
|
7685
|
+
slider.step = step;
|
|
7686
|
+
slider.style.height = `${height}px`;
|
|
7687
|
+
return slider;
|
|
7688
|
+
}
|
|
7689
|
+
function createToggle(initialState, height) {
|
|
7690
|
+
const toggleHeight = Math.min(24, height - 4);
|
|
7691
|
+
const knobSize = toggleHeight - 4;
|
|
7692
|
+
const toggle = document.createElement("div");
|
|
7693
|
+
toggle.className = initialState ? "ht-toggle active" : "ht-toggle";
|
|
7694
|
+
toggle.style.height = `${toggleHeight}px`;
|
|
7695
|
+
const knob = document.createElement("div");
|
|
7696
|
+
knob.className = "ht-toggle-knob";
|
|
7697
|
+
knob.style.width = `${knobSize}px`;
|
|
7698
|
+
knob.style.height = `${knobSize}px`;
|
|
7699
|
+
toggle.appendChild(knob);
|
|
7700
|
+
return toggle;
|
|
7701
|
+
}
|
|
7702
|
+
function createNumberInput(value, min, max, step, height) {
|
|
7703
|
+
const input = document.createElement("input");
|
|
7704
|
+
input.type = "number";
|
|
7705
|
+
input.className = "ht-number-input";
|
|
7706
|
+
input.value = value;
|
|
7707
|
+
input.min = min;
|
|
7708
|
+
input.max = max;
|
|
7709
|
+
input.step = step;
|
|
7710
|
+
input.style.height = `${height}px`;
|
|
7711
|
+
return input;
|
|
7712
|
+
}
|
|
6267
7713
|
function createToolbar(toolbarDiv, treeDataInstances, getCurrentTreeState, getCurrentTreeView, switchToTree, addNewTree, options) {
|
|
6268
7714
|
const CONTROL_HEIGHT = 24;
|
|
6269
7715
|
let currentTab = null;
|
|
6270
7716
|
let selectedMetadata = null;
|
|
7717
|
+
let currentAestheticSettings = null;
|
|
6271
7718
|
let expandSubtreesBtn = null;
|
|
6272
7719
|
let expandRootBtn = null;
|
|
6273
7720
|
let showHiddenBtn = null;
|
|
@@ -6295,6 +7742,8 @@ function createToolbar(toolbarDiv, treeDataInstances, getCurrentTreeState, getCu
|
|
|
6295
7742
|
tabsContainer.className = "ht-tabs";
|
|
6296
7743
|
const controlsContainer = document.createElement("div");
|
|
6297
7744
|
controlsContainer.className = "ht-controls hidden";
|
|
7745
|
+
const aestheticSettingsContainer = document.createElement("div");
|
|
7746
|
+
aestheticSettingsContainer.className = "ht-aesthetic-settings hidden";
|
|
6298
7747
|
const tabs = [
|
|
6299
7748
|
{ id: "data", label: "Data", requiresTree: false },
|
|
6300
7749
|
{ id: "controls", label: "Controls", requiresTree: true },
|
|
@@ -6311,6 +7760,7 @@ function createToolbar(toolbarDiv, treeDataInstances, getCurrentTreeState, getCu
|
|
|
6311
7760
|
if (tabDiv.classList.contains("disabled")) {
|
|
6312
7761
|
return;
|
|
6313
7762
|
}
|
|
7763
|
+
closeAestheticSettings();
|
|
6314
7764
|
if (currentTab === tab.id) {
|
|
6315
7765
|
closeTab();
|
|
6316
7766
|
} else {
|
|
@@ -6320,6 +7770,14 @@ function createToolbar(toolbarDiv, treeDataInstances, getCurrentTreeState, getCu
|
|
|
6320
7770
|
tabElements[tab.id] = tabDiv;
|
|
6321
7771
|
tabsContainer.appendChild(tabDiv);
|
|
6322
7772
|
});
|
|
7773
|
+
controlsContainer.addEventListener("click", (e) => {
|
|
7774
|
+
const editButton = e.target.closest(".ht-icon-button");
|
|
7775
|
+
const aestheticGroup = e.target.closest(".ht-control-group.ht-aesthetic-editing");
|
|
7776
|
+
if (editButton || aestheticGroup) {
|
|
7777
|
+
return;
|
|
7778
|
+
}
|
|
7779
|
+
closeAestheticSettings();
|
|
7780
|
+
});
|
|
6323
7781
|
function updateTabStates() {
|
|
6324
7782
|
const hasTree = getCurrentTreeState() !== null;
|
|
6325
7783
|
tabs.forEach((tab) => {
|
|
@@ -6402,6 +7860,44 @@ function createToolbar(toolbarDiv, treeDataInstances, getCurrentTreeState, getCu
|
|
|
6402
7860
|
}
|
|
6403
7861
|
return "tree";
|
|
6404
7862
|
}
|
|
7863
|
+
function openAestheticSettings(aestheticId, aestheticGroup) {
|
|
7864
|
+
if (currentAestheticSettings === aestheticId) {
|
|
7865
|
+
closeAestheticSettings();
|
|
7866
|
+
return;
|
|
7867
|
+
}
|
|
7868
|
+
closeAestheticSettings();
|
|
7869
|
+
currentAestheticSettings = aestheticId;
|
|
7870
|
+
aestheticGroup.classList.add("ht-aesthetic-editing");
|
|
7871
|
+
aestheticSettingsContainer.classList.remove("hidden");
|
|
7872
|
+
populateAestheticSettings(aestheticId);
|
|
7873
|
+
}
|
|
7874
|
+
function closeAestheticSettings() {
|
|
7875
|
+
if (!currentAestheticSettings) return;
|
|
7876
|
+
const existingEditors = aestheticSettingsContainer.querySelectorAll(".ht-color-palette-editor");
|
|
7877
|
+
existingEditors.forEach((editor) => {
|
|
7878
|
+
if (editor.cleanupFunction && typeof editor.cleanupFunction === "function") {
|
|
7879
|
+
editor.cleanupFunction();
|
|
7880
|
+
}
|
|
7881
|
+
});
|
|
7882
|
+
const allGroups = controlsContainer.querySelectorAll(".ht-control-group");
|
|
7883
|
+
allGroups.forEach((group) => group.classList.remove("ht-aesthetic-editing"));
|
|
7884
|
+
aestheticSettingsContainer.classList.add("hidden");
|
|
7885
|
+
aestheticSettingsContainer.innerHTML = "";
|
|
7886
|
+
currentAestheticSettings = null;
|
|
7887
|
+
}
|
|
7888
|
+
function populateAestheticSettings(aestheticId) {
|
|
7889
|
+
aestheticSettingsContainer.innerHTML = "";
|
|
7890
|
+
const treeState = getCurrentTreeState();
|
|
7891
|
+
if (!treeState) return;
|
|
7892
|
+
if (aestheticId === "tipLabelColor") {
|
|
7893
|
+
populateTipLabelColorSettings(aestheticSettingsContainer, treeState, CONTROL_HEIGHT);
|
|
7894
|
+
return;
|
|
7895
|
+
}
|
|
7896
|
+
const placeholder = document.createElement("div");
|
|
7897
|
+
placeholder.textContent = `Settings for ${aestheticId} (coming soon)`;
|
|
7898
|
+
placeholder.style.padding = "10px";
|
|
7899
|
+
aestheticSettingsContainer.appendChild(placeholder);
|
|
7900
|
+
}
|
|
6405
7901
|
function openTab(tabId) {
|
|
6406
7902
|
const tabDef = tabs.find((t) => t.id === tabId);
|
|
6407
7903
|
if (tabDef && tabDef.requiresTree && !getCurrentTreeState()) {
|
|
@@ -6425,9 +7921,11 @@ function createToolbar(toolbarDiv, treeDataInstances, getCurrentTreeState, getCu
|
|
|
6425
7921
|
});
|
|
6426
7922
|
controlsContainer.classList.add("hidden");
|
|
6427
7923
|
controlsContainer.innerHTML = "";
|
|
7924
|
+
closeAestheticSettings();
|
|
6428
7925
|
}
|
|
6429
7926
|
function populateControls(tabId) {
|
|
6430
7927
|
controlsContainer.innerHTML = "";
|
|
7928
|
+
closeAestheticSettings();
|
|
6431
7929
|
switch (tabId) {
|
|
6432
7930
|
case "data":
|
|
6433
7931
|
populateDataControls(
|
|
@@ -6474,7 +7972,16 @@ function createToolbar(toolbarDiv, treeDataInstances, getCurrentTreeState, getCu
|
|
|
6474
7972
|
);
|
|
6475
7973
|
break;
|
|
6476
7974
|
case "tip-label-settings":
|
|
6477
|
-
populateTipLabelSettingsControls(
|
|
7975
|
+
populateTipLabelSettingsControls(
|
|
7976
|
+
controlsContainer,
|
|
7977
|
+
getCurrentTreeState,
|
|
7978
|
+
options,
|
|
7979
|
+
CONTROL_HEIGHT,
|
|
7980
|
+
openAestheticSettings,
|
|
7981
|
+
closeAestheticSettings,
|
|
7982
|
+
populateAestheticSettings,
|
|
7983
|
+
() => currentAestheticSettings
|
|
7984
|
+
);
|
|
6478
7985
|
break;
|
|
6479
7986
|
case "export":
|
|
6480
7987
|
populateExportControls(controlsContainer, getCurrentTreeState, getCurrentTreeView, getCurrentTreeName, options, CONTROL_HEIGHT);
|
|
@@ -6501,6 +8008,7 @@ function createToolbar(toolbarDiv, treeDataInstances, getCurrentTreeState, getCu
|
|
|
6501
8008
|
}
|
|
6502
8009
|
collapsiblePanel.appendChild(tabsContainer);
|
|
6503
8010
|
collapsiblePanel.appendChild(controlsContainer);
|
|
8011
|
+
collapsiblePanel.appendChild(aestheticSettingsContainer);
|
|
6504
8012
|
toolbarDiv.appendChild(toggleContainer);
|
|
6505
8013
|
toolbarDiv.appendChild(collapsiblePanel);
|
|
6506
8014
|
updateTabStates();
|
|
@@ -6601,6 +8109,7 @@ function populateDataControls(container, treeDataInstances, getCurrentTreeState,
|
|
|
6601
8109
|
});
|
|
6602
8110
|
metadataSelect.addEventListener("change", (e) => {
|
|
6603
8111
|
setSelectedMetadata(e.target.value);
|
|
8112
|
+
refreshCurrentTab();
|
|
6604
8113
|
});
|
|
6605
8114
|
}
|
|
6606
8115
|
metadataGroup.appendChild(metadataSelect);
|
|
@@ -6647,6 +8156,51 @@ function populateDataControls(container, treeDataInstances, getCurrentTreeState,
|
|
|
6647
8156
|
metadataFileInput.click();
|
|
6648
8157
|
});
|
|
6649
8158
|
container.appendChild(addMetadataBtn);
|
|
8159
|
+
if (selectedMetadata) {
|
|
8160
|
+
const treeData = currentTreeState.state.treeData;
|
|
8161
|
+
let selectedTableId = null;
|
|
8162
|
+
for (const [tableId, tableName] of treeData.metadataTableNames.entries()) {
|
|
8163
|
+
if (tableName === selectedMetadata) {
|
|
8164
|
+
selectedTableId = tableId;
|
|
8165
|
+
break;
|
|
8166
|
+
}
|
|
8167
|
+
}
|
|
8168
|
+
if (selectedTableId) {
|
|
8169
|
+
const validIdColumns = treeData.getValidIdColumns(selectedTableId);
|
|
8170
|
+
const currentIdColumn = treeData.getNodeIdColumn(selectedTableId);
|
|
8171
|
+
const nodeIdGroup = createControlGroup();
|
|
8172
|
+
const nodeIdLabel = createLabel("Node/Tip ID Column:", controlHeight);
|
|
8173
|
+
nodeIdGroup.appendChild(nodeIdLabel);
|
|
8174
|
+
const nodeIdSelect = document.createElement("select");
|
|
8175
|
+
nodeIdSelect.className = "ht-select";
|
|
8176
|
+
nodeIdSelect.style.height = `${controlHeight}px`;
|
|
8177
|
+
if (validIdColumns.length === 0) {
|
|
8178
|
+
const option = document.createElement("option");
|
|
8179
|
+
option.textContent = "No ID column found";
|
|
8180
|
+
option.value = "";
|
|
8181
|
+
nodeIdSelect.appendChild(option);
|
|
8182
|
+
nodeIdSelect.disabled = true;
|
|
8183
|
+
} else {
|
|
8184
|
+
validIdColumns.forEach((columnName) => {
|
|
8185
|
+
const option = document.createElement("option");
|
|
8186
|
+
option.value = columnName;
|
|
8187
|
+
option.textContent = treeData.columnDisplayName.get(columnName);
|
|
8188
|
+
if (columnName === currentIdColumn) {
|
|
8189
|
+
option.selected = true;
|
|
8190
|
+
}
|
|
8191
|
+
nodeIdSelect.appendChild(option);
|
|
8192
|
+
});
|
|
8193
|
+
nodeIdSelect.addEventListener("change", (e) => {
|
|
8194
|
+
const newIdColumn = e.target.value;
|
|
8195
|
+
treeData.setNodeIdColumn(selectedTableId, newIdColumn);
|
|
8196
|
+
currentTreeState.updateCoordinates();
|
|
8197
|
+
refreshCurrentTab();
|
|
8198
|
+
});
|
|
8199
|
+
}
|
|
8200
|
+
nodeIdGroup.appendChild(nodeIdSelect);
|
|
8201
|
+
container.appendChild(nodeIdGroup);
|
|
8202
|
+
}
|
|
8203
|
+
}
|
|
6650
8204
|
}
|
|
6651
8205
|
function populateControlsTab(container, getCurrentTreeState, getCurrentTreeView, options, controlHeight) {
|
|
6652
8206
|
container.innerHTML = "";
|
|
@@ -6845,13 +8399,19 @@ function populateTreeManipulationControls(container, getCurrentTreeState, refres
|
|
|
6845
8399
|
radialLayoutGroup.appendChild(radialLayoutToggle);
|
|
6846
8400
|
container.appendChild(radialLayoutGroup);
|
|
6847
8401
|
}
|
|
6848
|
-
function populateTipLabelSettingsControls(container, getCurrentTreeState, options, controlHeight) {
|
|
8402
|
+
function populateTipLabelSettingsControls(container, getCurrentTreeState, options, controlHeight, openAestheticSettings, closeAestheticSettings, populateAestheticSettings, getCurrentAestheticSettings) {
|
|
6849
8403
|
container.innerHTML = "";
|
|
6850
8404
|
const treeState = getCurrentTreeState();
|
|
6851
8405
|
if (!treeState) {
|
|
6852
8406
|
container.textContent = "No tree selected";
|
|
6853
8407
|
return;
|
|
6854
8408
|
}
|
|
8409
|
+
const editIconSvg = `
|
|
8410
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
8411
|
+
<path d="M11.5 1.5L14.5 4.5L5 14H2V11L11.5 1.5Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8412
|
+
<path d="M10 3L13 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8413
|
+
</svg>
|
|
8414
|
+
`;
|
|
6855
8415
|
const tipLabelTextGroup = createControlGroup();
|
|
6856
8416
|
const tipLabelTextLabel = createLabel("Text:", controlHeight);
|
|
6857
8417
|
tipLabelTextGroup.appendChild(tipLabelTextLabel);
|
|
@@ -6861,20 +8421,30 @@ function populateTipLabelSettingsControls(container, getCurrentTreeState, option
|
|
|
6861
8421
|
"Default",
|
|
6862
8422
|
controlHeight,
|
|
6863
8423
|
true,
|
|
6864
|
-
null
|
|
8424
|
+
null,
|
|
8425
|
+
getCurrentAestheticSettings,
|
|
8426
|
+
populateAestheticSettings
|
|
6865
8427
|
);
|
|
6866
8428
|
tipLabelTextGroup.appendChild(tipLabelTextSelect);
|
|
6867
8429
|
container.appendChild(tipLabelTextGroup);
|
|
6868
8430
|
const tipLabelColorGroup = createControlGroup();
|
|
6869
8431
|
const tipLabelColorLabel = createLabel("Color:", controlHeight);
|
|
6870
8432
|
tipLabelColorGroup.appendChild(tipLabelColorLabel);
|
|
8433
|
+
const tipLabelColorEditBtn = createIconButton(editIconSvg, "Edit color settings", controlHeight);
|
|
8434
|
+
tipLabelColorEditBtn.addEventListener("click", (e) => {
|
|
8435
|
+
e.stopPropagation();
|
|
8436
|
+
openAestheticSettings("tipLabelColor", tipLabelColorGroup);
|
|
8437
|
+
});
|
|
8438
|
+
tipLabelColorGroup.appendChild(tipLabelColorEditBtn);
|
|
6871
8439
|
const tipLabelColorSelect = createMetadataColumnSelect(
|
|
6872
8440
|
treeState,
|
|
6873
8441
|
"tipLabelColor",
|
|
6874
8442
|
"Default",
|
|
6875
8443
|
controlHeight,
|
|
6876
8444
|
false,
|
|
6877
|
-
null
|
|
8445
|
+
null,
|
|
8446
|
+
getCurrentAestheticSettings,
|
|
8447
|
+
populateAestheticSettings
|
|
6878
8448
|
);
|
|
6879
8449
|
tipLabelColorGroup.appendChild(tipLabelColorSelect);
|
|
6880
8450
|
container.appendChild(tipLabelColorGroup);
|
|
@@ -6887,7 +8457,9 @@ function populateTipLabelSettingsControls(container, getCurrentTreeState, option
|
|
|
6887
8457
|
"Default",
|
|
6888
8458
|
controlHeight,
|
|
6889
8459
|
false,
|
|
6890
|
-
true
|
|
8460
|
+
true,
|
|
8461
|
+
getCurrentAestheticSettings,
|
|
8462
|
+
populateAestheticSettings
|
|
6891
8463
|
);
|
|
6892
8464
|
tipLabelSizeGroup.appendChild(tipLabelSizeSelect);
|
|
6893
8465
|
container.appendChild(tipLabelSizeGroup);
|
|
@@ -6900,7 +8472,9 @@ function populateTipLabelSettingsControls(container, getCurrentTreeState, option
|
|
|
6900
8472
|
"Default",
|
|
6901
8473
|
controlHeight,
|
|
6902
8474
|
false,
|
|
6903
|
-
false
|
|
8475
|
+
false,
|
|
8476
|
+
getCurrentAestheticSettings,
|
|
8477
|
+
populateAestheticSettings
|
|
6904
8478
|
);
|
|
6905
8479
|
tipLabelStyleGroup.appendChild(tipLabelStyleSelect);
|
|
6906
8480
|
container.appendChild(tipLabelStyleGroup);
|
|
@@ -6932,7 +8506,102 @@ function populateTipLabelSettingsControls(container, getCurrentTreeState, option
|
|
|
6932
8506
|
tipLabelFontGroup.appendChild(tipLabelFontSelect);
|
|
6933
8507
|
container.appendChild(tipLabelFontGroup);
|
|
6934
8508
|
}
|
|
6935
|
-
function
|
|
8509
|
+
function populateTipLabelColorSettings(container, treeState, controlHeight) {
|
|
8510
|
+
const columnId = treeState.state.aesthetics.tipLabelColor;
|
|
8511
|
+
if (!columnId) {
|
|
8512
|
+
const message = document.createElement("div");
|
|
8513
|
+
message.textContent = "Select a metadata column for tip label color to edit its settings";
|
|
8514
|
+
message.style.padding = "10px";
|
|
8515
|
+
message.style.color = "#666";
|
|
8516
|
+
container.appendChild(message);
|
|
8517
|
+
return;
|
|
8518
|
+
}
|
|
8519
|
+
const aesthetic = treeState.aestheticsScales.tipLabelColor;
|
|
8520
|
+
if (!aesthetic) {
|
|
8521
|
+
const message = document.createElement("div");
|
|
8522
|
+
message.textContent = "Error: Could not find color aesthetic";
|
|
8523
|
+
message.style.padding = "10px";
|
|
8524
|
+
message.style.color = "#d00";
|
|
8525
|
+
container.appendChild(message);
|
|
8526
|
+
return;
|
|
8527
|
+
}
|
|
8528
|
+
const settingsWidget = aesthetic.createSettingsWidget({
|
|
8529
|
+
controlHeight
|
|
8530
|
+
});
|
|
8531
|
+
if (settingsWidget) {
|
|
8532
|
+
container.appendChild(settingsWidget);
|
|
8533
|
+
} else {
|
|
8534
|
+
const message = document.createElement("div");
|
|
8535
|
+
message.textContent = "No settings available for this aesthetic";
|
|
8536
|
+
message.style.padding = "10px";
|
|
8537
|
+
message.style.color = "#666";
|
|
8538
|
+
container.appendChild(message);
|
|
8539
|
+
return;
|
|
8540
|
+
}
|
|
8541
|
+
if (aesthetic.state.isCategorical) {
|
|
8542
|
+
const maxCategoriesGroup = createControlGroup();
|
|
8543
|
+
const maxCategoriesLabel = createLabel("Max colors:", controlHeight);
|
|
8544
|
+
maxCategoriesGroup.appendChild(maxCategoriesLabel);
|
|
8545
|
+
const maxCategoriesInput = createNumberInput(
|
|
8546
|
+
aesthetic.state.maxCategories || 7,
|
|
8547
|
+
1,
|
|
8548
|
+
100,
|
|
8549
|
+
1,
|
|
8550
|
+
controlHeight
|
|
8551
|
+
);
|
|
8552
|
+
maxCategoriesInput.addEventListener("input", (e) => {
|
|
8553
|
+
const value = parseInt(e.target.value);
|
|
8554
|
+
if (isNaN(value) || value < 1) return;
|
|
8555
|
+
aesthetic.updateState({ maxCategories: value });
|
|
8556
|
+
aesthetic.updateScale(aesthetic.values);
|
|
8557
|
+
treeState.state.treeData.tree.each((node) => {
|
|
8558
|
+
const columnValue = node[columnId];
|
|
8559
|
+
if (columnValue !== void 0 && columnValue !== null) {
|
|
8560
|
+
node.tipLabelColor = aesthetic.getValue(columnValue);
|
|
8561
|
+
}
|
|
8562
|
+
});
|
|
8563
|
+
treeState.updateCoordinates();
|
|
8564
|
+
treeState.notify("legendsChange");
|
|
8565
|
+
});
|
|
8566
|
+
maxCategoriesGroup.appendChild(maxCategoriesInput);
|
|
8567
|
+
container.appendChild(maxCategoriesGroup);
|
|
8568
|
+
}
|
|
8569
|
+
const titleGroup = createControlGroup();
|
|
8570
|
+
const titleLabel = createLabel("Legend title:", controlHeight);
|
|
8571
|
+
titleGroup.appendChild(titleLabel);
|
|
8572
|
+
const titleInput = document.createElement("input");
|
|
8573
|
+
titleInput.type = "text";
|
|
8574
|
+
titleInput.className = "ht-text-input";
|
|
8575
|
+
titleInput.style.height = `${controlHeight}px`;
|
|
8576
|
+
titleInput.style.flex = "1";
|
|
8577
|
+
titleInput.value = aesthetic.state.title || "";
|
|
8578
|
+
titleInput.placeholder = "Enter legend title";
|
|
8579
|
+
titleInput.addEventListener("input", (e) => {
|
|
8580
|
+
aesthetic.updateState({ title: e.target.value });
|
|
8581
|
+
treeState.notify("legendsChange");
|
|
8582
|
+
});
|
|
8583
|
+
titleGroup.appendChild(titleInput);
|
|
8584
|
+
container.appendChild(titleGroup);
|
|
8585
|
+
if (!aesthetic.state.isCategorical) {
|
|
8586
|
+
const unitsGroup = createControlGroup();
|
|
8587
|
+
const unitsLabel = createLabel("Units label:", controlHeight);
|
|
8588
|
+
unitsGroup.appendChild(unitsLabel);
|
|
8589
|
+
const unitsInput = document.createElement("input");
|
|
8590
|
+
unitsInput.type = "text";
|
|
8591
|
+
unitsInput.className = "ht-text-input";
|
|
8592
|
+
unitsInput.style.height = `${controlHeight}px`;
|
|
8593
|
+
unitsInput.style.flex = "1";
|
|
8594
|
+
unitsInput.value = aesthetic.state.inputUnits || "";
|
|
8595
|
+
unitsInput.placeholder = "Enter units (e.g., °C, km)";
|
|
8596
|
+
unitsInput.addEventListener("input", (e) => {
|
|
8597
|
+
aesthetic.updateState({ inputUnits: e.target.value });
|
|
8598
|
+
treeState.notify("legendsChange");
|
|
8599
|
+
});
|
|
8600
|
+
unitsGroup.appendChild(unitsInput);
|
|
8601
|
+
container.appendChild(unitsGroup);
|
|
8602
|
+
}
|
|
8603
|
+
}
|
|
8604
|
+
function createMetadataColumnSelect(treeState, aesthetic, defaultLabel, controlHeight, includeNone = false, continuous = null, getCurrentAestheticSettings = null, populateAestheticSettings = null) {
|
|
6936
8605
|
const select2 = document.createElement("select");
|
|
6937
8606
|
select2.className = "ht-select";
|
|
6938
8607
|
select2.style.height = `${controlHeight}px`;
|
|
@@ -6986,6 +8655,12 @@ function createMetadataColumnSelect(treeState, aesthetic, defaultLabel, controlH
|
|
|
6986
8655
|
const aestheticUpdate = {};
|
|
6987
8656
|
aestheticUpdate[aesthetic] = columnId;
|
|
6988
8657
|
treeState.setAesthetics(aestheticUpdate);
|
|
8658
|
+
if (getCurrentAestheticSettings && populateAestheticSettings) {
|
|
8659
|
+
const currentAestheticSettings = getCurrentAestheticSettings();
|
|
8660
|
+
if (currentAestheticSettings === aesthetic) {
|
|
8661
|
+
populateAestheticSettings(aesthetic);
|
|
8662
|
+
}
|
|
8663
|
+
}
|
|
6989
8664
|
});
|
|
6990
8665
|
return select2;
|
|
6991
8666
|
}
|
|
@@ -7137,61 +8812,6 @@ function populateExportControls(container, getCurrentTreeState, getCurrentTreeVi
|
|
|
7137
8812
|
marginGroup.appendChild(marginInput);
|
|
7138
8813
|
container.appendChild(marginGroup);
|
|
7139
8814
|
}
|
|
7140
|
-
function createControlGroup() {
|
|
7141
|
-
const group = document.createElement("div");
|
|
7142
|
-
group.className = "ht-control-group";
|
|
7143
|
-
return group;
|
|
7144
|
-
}
|
|
7145
|
-
function createLabel(text, height) {
|
|
7146
|
-
const label = document.createElement("label");
|
|
7147
|
-
label.className = "ht-control-label";
|
|
7148
|
-
label.textContent = text;
|
|
7149
|
-
label.style.height = `${height}px`;
|
|
7150
|
-
return label;
|
|
7151
|
-
}
|
|
7152
|
-
function createButton(text, title = "", height) {
|
|
7153
|
-
const button = document.createElement("button");
|
|
7154
|
-
button.className = "ht-button";
|
|
7155
|
-
button.textContent = text;
|
|
7156
|
-
button.title = title;
|
|
7157
|
-
button.style.height = `${height}px`;
|
|
7158
|
-
return button;
|
|
7159
|
-
}
|
|
7160
|
-
function createSlider(min, max, value, step, height) {
|
|
7161
|
-
const slider = document.createElement("input");
|
|
7162
|
-
slider.type = "range";
|
|
7163
|
-
slider.className = "ht-slider";
|
|
7164
|
-
slider.min = min;
|
|
7165
|
-
slider.max = max;
|
|
7166
|
-
slider.value = value;
|
|
7167
|
-
slider.step = step;
|
|
7168
|
-
slider.style.height = `${height}px`;
|
|
7169
|
-
return slider;
|
|
7170
|
-
}
|
|
7171
|
-
function createToggle(initialState, height) {
|
|
7172
|
-
const toggleHeight = Math.min(24, height - 4);
|
|
7173
|
-
const knobSize = toggleHeight - 4;
|
|
7174
|
-
const toggle = document.createElement("div");
|
|
7175
|
-
toggle.className = initialState ? "ht-toggle active" : "ht-toggle";
|
|
7176
|
-
toggle.style.height = `${toggleHeight}px`;
|
|
7177
|
-
const knob = document.createElement("div");
|
|
7178
|
-
knob.className = "ht-toggle-knob";
|
|
7179
|
-
knob.style.width = `${knobSize}px`;
|
|
7180
|
-
knob.style.height = `${knobSize}px`;
|
|
7181
|
-
toggle.appendChild(knob);
|
|
7182
|
-
return toggle;
|
|
7183
|
-
}
|
|
7184
|
-
function createNumberInput(value, min, max, step, height) {
|
|
7185
|
-
const input = document.createElement("input");
|
|
7186
|
-
input.type = "number";
|
|
7187
|
-
input.className = "ht-number-input";
|
|
7188
|
-
input.value = value;
|
|
7189
|
-
input.min = min;
|
|
7190
|
-
input.max = max;
|
|
7191
|
-
input.step = step;
|
|
7192
|
-
input.style.height = `${height}px`;
|
|
7193
|
-
return input;
|
|
7194
|
-
}
|
|
7195
8815
|
function heatTree(containerSelector, treesInput = [], options = {}) {
|
|
7196
8816
|
if (treesInput && !Array.isArray(treesInput)) {
|
|
7197
8817
|
treesInput = [treesInput];
|