Browse Source

finalized a very fancy option configurator with a fancy color picker for color fields!

flowchartTest
Alex de Mulder 9 years ago
parent
commit
a30c994e92
11 changed files with 1499 additions and 279 deletions
  1. +101
    -16
      dist/vis.css
  2. +699
    -132
      dist/vis.js
  3. +1
    -1
      dist/vis.min.css
  4. +1
    -0
      examples/network/01_basic_usage.html
  5. +100
    -16
      lib/network/css/network-colorpicker.css
  6. +1
    -0
      lib/network/css/network-configuration.css
  7. +1
    -1
      lib/network/modules/CanvasRenderer.js
  8. +181
    -32
      lib/network/modules/ConfigurationSystem.js
  9. +7
    -1
      lib/network/modules/NodesHandler.js
  10. +396
    -74
      lib/network/modules/components/colorPicker.js
  11. +11
    -6
      lib/util.js

+ 101
- 16
dist/vis.css View File

@ -858,6 +858,7 @@ div.vis-network-configuration.label.s4{
}
div.vis-network-configuration.colorBlock{
top:1px;
width:30px;
height:19px;
border:1px solid #444444;
@ -1009,13 +1010,16 @@ input.vis-network-configuration.range:focus::-ms-fill-upper {
}
div.vis-network-colorPicker-frame {
position:relative;
margin-top:40px;
width:310px;
height:310px;
position:absolute;
margin-top:-140px;
margin-left:30px;
width:293px;
height:425px;
padding: 10px;
border-radius:20px;
border-radius:15px;
background-color:#ffffff;
display:none;
box-shadow: rgba(0,0,0,0.5) 0px 0px 10px 0px;
}
div.vis-network-colorPicker-color {
@ -1025,20 +1029,16 @@ div.vis-network-colorPicker-color {
cursor: pointer;
}
div.vis-network-colorPicker-brightness {
position:absolute;
top:300px;
/*background-color:#00ff00;*/
position: absolute;
top:313px;
}
div.vis-network-colorPicker-saturation {
div.vis-network-colorPicker-opacity {
position:absolute;
top:145px;
left:160px;
/*background-color:#ff0000;*/
-ms-transform: rotate(270deg); /* IE 9 */
-webkit-transform: rotate(270deg); /* Chrome, Safari, Opera */
transform: rotate(270deg);
top:350px;
}
div.vis-network-colorPicker-selector{
@ -1048,6 +1048,7 @@ div.vis-network-colorPicker-selector{
width:15px;
height:15px;
border-radius:15px;
border:1px solid #ffffff;
background: #4c4c4c; /* Old browsers */
background: -moz-linear-gradient(top, #4c4c4c 0%, #595959 12%, #666666 25%, #474747 39%, #2c2c2c 50%, #000000 51%, #111111 60%, #2b2b2b 76%, #1c1c1c 91%, #131313 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(12%,#595959), color-stop(25%,#666666), color-stop(39%,#474747), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(60%,#111111), color-stop(76%,#2b2b2b), color-stop(91%,#1c1c1c), color-stop(100%,#131313)); /* Chrome,Safari4+ */
@ -1058,6 +1059,91 @@ div.vis-network-colorPicker-selector{
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
}
div.vis-network-colorPicker-newColor {
position:absolute;
width:140px;
height:20px;
border:1px solid rgba(0,0,0,0.1);
border-radius:5px;
top:380px;
left:159px;
text-align:right;
padding-right:2px;
font-size:10px;
color:rgba(0,0,0,0.4);
vertical-align:middle;
line-height:20px;
}
div.vis-network-colorPicker-initialColor {
position:absolute;
width:140px;
height:20px;
border:1px solid rgba(0,0,0,0.1);
border-radius:5px;
top:380px;
left:10px;
text-align:left;
padding-left:2px;
font-size:10px;
color:rgba(0,0,0,0.4);
vertical-align:middle;
line-height:20px;
}
div.vis-network-colorPicker-label {
position:absolute;
width:300px;
left:10px;
}
div.vis-network-colorPicker-label.brightness {
top:300px;
}
div.vis-network-colorPicker-label.opacity {
top:338px;
}
div.vis-network-colorPicker-button {
position:absolute;
width:68px;
height:25px;
border-radius:10px;
vertical-align: middle;
text-align:center;
line-height: 25px;
top:410px;
border:2px solid #d9d9d9;
background-color: #f7f7f7;
cursor:pointer;
}
div.vis-network-colorPicker-button.cancel {
border:2px solid #ff4e33;
background-color: #ff7761;
left:5px;
}
div.vis-network-colorPicker-button.load {
border:2px solid #a153e6;
background-color: #cb8dff;
left:82px;
}
div.vis-network-colorPicker-button.apply {
border:2px solid #4588e6;
background-color: #82b6ff;
left:159px;
}
div.vis-network-colorPicker-button.save {
border:2px solid #45e655;
background-color: #6dff7c;
left:236px;
}
input.vis-network-configuration.range.colorPicker{
width: 290px;
height:20px;
@ -1070,5 +1156,4 @@ input.vis-network-brightnessRange {
input.vis-network-saturationRange {
width: 289px !important;
}

+ 699
- 132
dist/vis.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


+ 1
- 0
examples/network/01_basic_usage.html View File

@ -49,6 +49,7 @@
physics:{solver:'BarnesHut'}
}
var network = new vis.Network(container, data, options);
// network.setOptions({nodes:{color:'red'}})
</script>
</body>

+ 100
- 16
lib/network/css/network-colorpicker.css View File

@ -1,12 +1,15 @@
div.vis-network-colorPicker-frame {
position:relative;
margin-top:40px;
width:310px;
height:310px;
position:absolute;
margin-top:-140px;
margin-left:30px;
width:293px;
height:425px;
padding: 10px;
border-radius:20px;
border-radius:15px;
background-color:#ffffff;
display:none;
box-shadow: rgba(0,0,0,0.5) 0px 0px 10px 0px;
}
div.vis-network-colorPicker-color {
@ -16,20 +19,16 @@ div.vis-network-colorPicker-color {
cursor: pointer;
}
div.vis-network-colorPicker-brightness {
position:absolute;
top:300px;
/*background-color:#00ff00;*/
position: absolute;
top:313px;
}
div.vis-network-colorPicker-saturation {
div.vis-network-colorPicker-opacity {
position:absolute;
top:145px;
left:160px;
/*background-color:#ff0000;*/
-ms-transform: rotate(270deg); /* IE 9 */
-webkit-transform: rotate(270deg); /* Chrome, Safari, Opera */
transform: rotate(270deg);
top:350px;
}
div.vis-network-colorPicker-selector{
@ -39,6 +38,7 @@ div.vis-network-colorPicker-selector{
width:15px;
height:15px;
border-radius:15px;
border:1px solid #ffffff;
background: #4c4c4c; /* Old browsers */
background: -moz-linear-gradient(top, #4c4c4c 0%, #595959 12%, #666666 25%, #474747 39%, #2c2c2c 50%, #000000 51%, #111111 60%, #2b2b2b 76%, #1c1c1c 91%, #131313 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4c4c4c), color-stop(12%,#595959), color-stop(25%,#666666), color-stop(39%,#474747), color-stop(50%,#2c2c2c), color-stop(51%,#000000), color-stop(60%,#111111), color-stop(76%,#2b2b2b), color-stop(91%,#1c1c1c), color-stop(100%,#131313)); /* Chrome,Safari4+ */
@ -49,6 +49,91 @@ div.vis-network-colorPicker-selector{
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */
}
div.vis-network-colorPicker-newColor {
position:absolute;
width:140px;
height:20px;
border:1px solid rgba(0,0,0,0.1);
border-radius:5px;
top:380px;
left:159px;
text-align:right;
padding-right:2px;
font-size:10px;
color:rgba(0,0,0,0.4);
vertical-align:middle;
line-height:20px;
}
div.vis-network-colorPicker-initialColor {
position:absolute;
width:140px;
height:20px;
border:1px solid rgba(0,0,0,0.1);
border-radius:5px;
top:380px;
left:10px;
text-align:left;
padding-left:2px;
font-size:10px;
color:rgba(0,0,0,0.4);
vertical-align:middle;
line-height:20px;
}
div.vis-network-colorPicker-label {
position:absolute;
width:300px;
left:10px;
}
div.vis-network-colorPicker-label.brightness {
top:300px;
}
div.vis-network-colorPicker-label.opacity {
top:338px;
}
div.vis-network-colorPicker-button {
position:absolute;
width:68px;
height:25px;
border-radius:10px;
vertical-align: middle;
text-align:center;
line-height: 25px;
top:410px;
border:2px solid #d9d9d9;
background-color: #f7f7f7;
cursor:pointer;
}
div.vis-network-colorPicker-button.cancel {
border:2px solid #ff4e33;
background-color: #ff7761;
left:5px;
}
div.vis-network-colorPicker-button.load {
border:2px solid #a153e6;
background-color: #cb8dff;
left:82px;
}
div.vis-network-colorPicker-button.apply {
border:2px solid #4588e6;
background-color: #82b6ff;
left:159px;
}
div.vis-network-colorPicker-button.save {
border:2px solid #45e655;
background-color: #6dff7c;
left:236px;
}
input.vis-network-configuration.range.colorPicker{
width: 290px;
height:20px;
@ -61,5 +146,4 @@ input.vis-network-brightnessRange {
input.vis-network-saturationRange {
width: 289px !important;
}

+ 1
- 0
lib/network/css/network-configuration.css View File

@ -52,6 +52,7 @@ div.vis-network-configuration.label.s4{
}
div.vis-network-configuration.colorBlock{
top:1px;
width:30px;
height:19px;
border:1px solid #444444;

+ 1
- 1
lib/network/modules/CanvasRenderer.js View File

@ -89,7 +89,7 @@ class CanvasRenderer {
* chart will be resized too.
*/
redraw() {
this.setSize(this.constants.width, this.constants.height);
this._setSize(this.constants.width, this.constants.height);
this._redraw();
}

+ 181
- 32
lib/network/modules/ConfigurationSystem.js View File

@ -202,16 +202,47 @@ class ConfigurationSystem {
}
};
this.actualOptions = {};
this.actualOptions = {
nodes:{},
edges:{},
layout:{},
interaction:{},
manipulation:{},
physics:{},
selection:{},
renderer:{},
configure: false,
configureContainer: undefined
};
this.domElements = [];
this.colorPicker = new ColorPicker();
this.colorPicker = new ColorPicker(this.network.canvas.pixelRatio);
}
/**
* refresh all options.
* Because all modules parse their options by themselves, we just use their options. We copy them here.
*
* @param options
*/
setOptions(options) {
if (options !== undefined) {
this._clean();
util.deepExtend(this.actualOptions, options);
util.extend(this.actualOptions, options);
}
this._clean();
if (this.actualOptions.configure !== undefined && this.actualOptions.configure !== false) {
util.deepExtend(this.actualOptions.nodes, this.network.nodesHandler.options, true);
util.deepExtend(this.actualOptions.edges, this.network.edgesHandler.options, true);
util.deepExtend(this.actualOptions.layout, this.network.layoutEngine.options, true);
util.deepExtend(this.actualOptions.interaction, this.network.interactionHandler.options, true);
util.deepExtend(this.actualOptions.manipulation, this.network.manipulation.options, true);
util.deepExtend(this.actualOptions.physics, this.network.nodesHandler.physics, true);
util.deepExtend(this.actualOptions.selection, this.network.selectionHandler.selection, true);
util.deepExtend(this.actualOptions.renderer, this.network.renderer.selection, true);
if (this.actualOptions.configurationContainer !== undefined) {
this.container = this.actualOptions.configurationContainer;
}
@ -219,31 +250,28 @@ class ConfigurationSystem {
this.container = this.network.body.container;
}
if (this.actualOptions.configure !== undefined && this.actualOptions.configure !== false) {
let config;
if (this.actualOptions.configure instanceof Array) {
config = this.actualOptions.configure.join();
}
else if (typeof this.actualOptions.configure === 'string') {
config = this.actualOptions.configure;
}
else if (typeof this.actualOptions.configure === 'boolean') {
config = this.actualOptions.configure;
}
else {
this._clean();
throw new Error('the option for configure has to be either a string, boolean or an array. Supplied:' + this.options.configure);
return;
}
this._create(config);
}
let config;
if (this.actualOptions.configure instanceof Array) {
config = this.actualOptions.configure.join();
}
else if (typeof this.actualOptions.configure === 'string') {
config = this.actualOptions.configure;
}
else if (typeof this.actualOptions.configure === 'boolean') {
config = this.actualOptions.configure;
}
else {
this._clean();
throw new Error('the option for configure has to be either a string, boolean or an array. Supplied:' + this.options.configure);
return;
}
this._create(config);
}
}
/**
*
* Create all DOM elements
* @param {Boolean | String} config
* @private
*/
@ -271,15 +299,24 @@ class ConfigurationSystem {
}
this._push();
this.colorPicker.generate();
this.colorPicker.insertTo(this.container);
}
/**
* draw all DOM elements on the screen
* @private
*/
_push() {
for (var i = 0; i < this.domElements.length; i++) {
this.container.appendChild(this.domElements[i]);
}
}
/**
* delete all DOM elements
* @private
*/
_clean() {
for (var i = 0; i < this.domElements.length; i++) {
this.container.removeChild(this.domElements[i]);
@ -287,6 +324,12 @@ class ConfigurationSystem {
this.domElements = [];
}
/**
* get the value from the actualOptions if it exists
* @param {array} path | where to look for the actual option
* @returns {*}
* @private
*/
_getValue(path) {
let base = this.actualOptions;
for (let i = 0; i < path.length; i++) {
@ -301,6 +344,14 @@ class ConfigurationSystem {
return base;
}
/**
* Copy the path and add a step. It needs to copy because the path will keep stacking otherwise.
* @param path
* @param newValue
* @returns {Array}
* @private
*/
_addToPath(path, newValue) {
let newPath = [];
for (let i = 0; i < path.length; i++) {
@ -310,7 +361,12 @@ class ConfigurationSystem {
return newPath;
}
/**
* all option elements are wrapped in an entree
* @param path
* @param domElements
* @private
*/
_makeEntree(path,...domElements) {
let entree = document.createElement('div');
entree.className = 'vis-network-configuration entree s' + path.length;
@ -320,6 +376,11 @@ class ConfigurationSystem {
this.domElements.push(entree);
}
/**
* header for major subjects
* @param name
* @private
*/
_makeHeader(name) {
let div = document.createElement('div');
div.className = 'vis-network-configuration header';
@ -327,6 +388,14 @@ class ConfigurationSystem {
this._makeEntree([],div);
}
/**
* make a label, if it is an object label, it gets different styling.
* @param name
* @param path
* @param objectLabel
* @returns {HTMLElement}
* @private
*/
_makeLabel(name, path, objectLabel = false) {
let div = document.createElement('div');
div.className = 'vis-network-configuration label s' + path.length;
@ -339,6 +408,14 @@ class ConfigurationSystem {
return div;
}
/**
* make a dropdown list for multiple possible string optoins
* @param arr
* @param value
* @param path
* @private
*/
_makeDropdown(arr, value, path) {
let select = document.createElement('select');
select.className = 'vis-network-configuration select';
@ -366,6 +443,14 @@ class ConfigurationSystem {
this._makeEntree(path, label, select);
}
/**
* make a range object for numeric options
* @param arr
* @param value
* @param path
* @private
*/
_makeRange(arr, value, path) {
let defaultValue = arr[0];
let min = arr[1];
@ -402,6 +487,14 @@ class ConfigurationSystem {
this._makeEntree(path, label, range, input);
}
/**
* make a checkbox for boolean options.
* @param defaultValue
* @param value
* @param path
* @private
*/
_makeCheckbox(defaultValue, value, path) {
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
@ -418,31 +511,60 @@ class ConfigurationSystem {
this._makeEntree(path, label, checkbox);
}
/**
* make a color field with a color picker for color fields
* @param arr
* @param value
* @param path
* @private
*/
_makeColorField(arr, value, path) {
let defaultColor = arr[1];
let div = document.createElement('div');
value = value === undefined ? defaultColor : value;
if (defaultColor !== 'none') {
if (value !== 'none') {
div.className = 'vis-network-configuration colorBlock';
div.style.backgroundColor = defaultColor;
div.style.backgroundColor = value;
}
else {
div.className = 'vis-network-configuration colorBlock none';
}
value = value === undefined ? defaultColor : value;
div.onclick = () => {this._showColorPicker(value, div);}
div.onclick = (event) => {this._showColorPicker(event,value,div,path);}
let label = this._makeLabel(path[path.length-1], path);
this._makeEntree(path,label, div);
}
_showColorPicker(value, div) {
this.colorPicker.show(div);
}
/**
* used by the color buttons to call the color picker.
* @param event
* @param value
* @param div
* @param path
* @private
*/
_showColorPicker(event, value, div, path) {
this.colorPicker.show(event.pageX, event.pageY);
this.colorPicker.setColor(value);
this.colorPicker.setCallback((color) => {
let colorString = 'rgba(' + color.r + ',' + color.g + ',' + color.b + ',' + color.a + ')';
div.style.backgroundColor = colorString;
this._update(colorString,path);
})
}
/**
* parse an object and draw the correct entrees
* @param obj
* @param path
* @private
*/
_handleObject(obj, path = []) {
for (let subObj in obj) {
if (obj.hasOwnProperty(subObj)) {
@ -471,6 +593,15 @@ class ConfigurationSystem {
}
}
/**
* handle the array type of option
* @param optionName
* @param arr
* @param value
* @param path
* @private
*/
_handleArray(optionName, arr, value, path) {
if (typeof arr[0] === 'string' && arr[0] === 'color') {
this._makeColorField(arr, value, path);
@ -483,6 +614,16 @@ class ConfigurationSystem {
}
}
/**
* handle the string type of option.
* TODO: Not sure what to do with this
* @param optionName
* @param string
* @param value
* @param path
* @private
*/
_handleString(optionName, string, value, path) {
if (string === 'string') {
@ -493,6 +634,13 @@ class ConfigurationSystem {
}
}
/**
* called to update the network with the new settings.
* @param value
* @param path
* @private
*/
_update(value, path) {
let options = {};
let pointer = options;
@ -505,6 +653,7 @@ class ConfigurationSystem {
pointer[path[i]] = value;
}
}
console.log('options',options)
this.network.setOptions(options);
}
}

+ 7
- 1
lib/network/modules/NodesHandler.js View File

@ -101,7 +101,13 @@ class NodesHandler {
if (options) {
util.selectiveNotDeepExtend(['color'], this.options, options);
if (options.color) {
this.options.color = util.parseColor(options.color);
let parsedColor = util.parseColor(options.color);
if (parsedColor.border !== undefined) {this.options.color.border = parsedColor.border;}
if (parsedColor.background !== undefined) {this.options.color.background = parsedColor.background;}
if (parsedColor.highlight.border !== undefined) {this.options.color.highlight.border = parsedColor.highlight.border;}
if (parsedColor.highlight.background !== undefined) {this.options.color.highlight.background = parsedColor.highlight.background;}
if (parsedColor.hover.border !== undefined) {this.options.color.hover.border = parsedColor.hover.border;}
if (parsedColor.hover.background !== undefined) {this.options.color.hover.background = parsedColor.hover.background;}
}
}
}

+ 396
- 74
lib/network/modules/components/colorPicker.js View File

@ -7,16 +7,287 @@ let hammerUtil = require('../../../hammerUtil');
let util = require('../../../util');
class ColorPicker {
constructor() {
this.touchTime = 0;
this.pixelRatio = 1;
constructor(pixelRatio = 1) {
this.pixelRatio = pixelRatio;
this.generated = false;
this.color = undefined;
this.centerCoordinates = {x:289/2, y:289/2};
this.r = 289 * 0.49;
this.color = {r:255,g:255,b:255,a:1.0};
this.hueCircle = undefined;
this.initialColor = {r:255,g:255,b:255,a:1.0};
this.previousColor= undefined;
this.applied = false;
// bound by
this.updateCallback = () => {};
// create all DOM elements
this._create();
}
/**
* this inserts the colorPicker into a div from the DOM
* @param container
*/
insertTo(container) {
if (this.hammer !== undefined) {
this.hammer.destroy();
this.hammer = undefined;
}
this.container = container;
this.container.appendChild(this.frame);
this._bindHammer();
this._setSize();
}
/**
* the callback is executed on apply and save. Bind it to the application
* @param callback
*/
setCallback(callback) {
if (typeof callback === 'function') {
this.updateCallback = callback;
}
else {
throw new Error("Function attempted to set as colorPicker callback is not a function.");
}
}
/**
* Set the color of the colorPicker
* Supported formats:
* '#ffffff' --> hex string
* 'rbg(255,255,255)' --> rgb string
* 'rgba(255,255,255,1.0)' --> rgba string
* {r:255,g:255,b:255} --> rgb object
* {r:255,g:255,b:255,a:1.0} --> rgba object
* @param color
* @param setInitial
*/
setColor(color, setInitial = true) {
if (color === 'none') {
return;
}
let rgba;
// check format
if (util.isString(color) === true) {
if (util.isValidRGB(color) === true) {
let rgbaArray = color.substr(4).substr(0, color.length - 5).split(',');
rgba = {r:rgbaArray[0], g:rgbaArray[1], b:rgbaArray[2], a:1.0};
}
else if (util.isValidRGBA(color) === true) {
let rgbaArray = color.substr(5).substr(0, color.length - 6).split(',');
rgba = {r:rgbaArray[0], g:rgbaArray[1], b:rgbaArray[2], a:rgbaArray[3]};
}
else if (util.isValidHex(color) === true) {
let rgbObj = util.hexToRGB(color);
rgba = {r:rgbObj.r, g:rgbObj.g, b:rgbObj.b, a:1.0};
}
}
else {
if (color instanceof Object) {
if (color.r !== undefined && color.g !== undefined && color.b !== undefined) {
let alpha = color.a !== undefined ? color.a : '1.0';
rgba = {r:color.r, g:color.g, b:color.b, a:alpha};
}
}
}
// set color
if (rgba === undefined) {
throw new Error("Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: " + JSON.stringify(color));
}
else {
this._setColor(rgba, setInitial);
}
}
this.create();
/**
* this shows the color picker at a location. The hue circle is constructed once and stored.
* @param x
* @param y
*/
show(x,y) {
this.applied = false;
this.frame.style.display = 'block';
this.frame.style.top = y + 'px';
this.frame.style.left = x + 'px';
this._generateHueCircle();
}
// ------------------------------------------ PRIVATE ----------------------------- //
/**
* Hide the picker. Is called by the cancel button.
* Optional boolean to store the previous color for easy access later on.
* @param storePrevious
* @private
*/
_hide(storePrevious = true) {
// store the previous color for next time;
if (storePrevious === true) {
this.previousColor = util.extend({}, this.color);
}
if (this.applied === true) {
this.updateCallback(this.initialColor);
}
this.frame.style.display = 'none';
}
/**
* bound to the save button. Saves and hides.
* @private
*/
_save() {
this.updateCallback(this.color);
this.applied = false;
this.hide();
}
create() {
/**
* Bound to apply button. Saves but does not close. Is undone by the cancel button.
* @private
*/
_apply() {
this.applied = true;
this.updateCallback(this.color);
this._updatePicker(this.color);
}
/**
* load the color from the previous session.
* @private
*/
_loadLast() {
if (this.previousColor !== undefined) {
this.setColor(this.previousColor, false);
}
else {
alert("There is no last color to load...");
}
}
/**
* set the color, place the picker
* @param rgba
* @param setInitial
* @private
*/
_setColor(rgba, setInitial = true) {
// store the initial color
if (setInitial === true) {
console.log("here")
this.initialColor = util.extend({}, rgba);
}
this.color = rgba;
let hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b);
let angleConvert = 2 * Math.PI;
let radius = this.r * hsv.s;
let x = this.centerCoordinates.x + radius * Math.sin(angleConvert * hsv.h);
let y = this.centerCoordinates.y + radius * Math.cos(angleConvert * hsv.h);
this.colorPickerSelector.style.left = x - 0.5 * this.colorPickerSelector.clientWidth + 'px';
this.colorPickerSelector.style.top = y - 0.5 * this.colorPickerSelector.clientHeight + 'px';
this._updatePicker(rgba);
}
/**
* bound to opacity control
* @param value
* @private
*/
_setOpacity(value) {
this.color.a = value / 100;
this._updatePicker(this.color);
}
/**
* bound to brightness control
* @param value
* @private
*/
_setBrightness(value) {
let hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b);
hsv.v = value / 100;
let rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v);
rgba['a'] = this.color.a;
this.color = rgba;
this._updatePicker();
}
/**
* update the colorpicker. A black circle overlays the hue circle to mimic the brightness decreasing.
* @param rgba
* @private
*/
_updatePicker(rgba = this.color) {
let hsv = util.RGBToHSV(rgba.r, rgba.g, rgba.b);
let ctx = this.colorPickerCanvas.getContext('2d');
if (this.pixelRation === undefined) {
this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1);
}
ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);
// clear the canvas
let w = this.colorPickerCanvas.clientWidth;
let h = this.colorPickerCanvas.clientHeight;
ctx.clearRect(0, 0, w, h);
ctx.putImageData(this.hueCircle, 0,0);
ctx.fillStyle = 'rgba(0,0,0,' + (1- hsv.v) + ')';
ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r);
ctx.fill();
this.brightnessRange.value = 100 * hsv.v;
this.opacityRange.value = 100 * rgba.a;
this.initialColorDiv.style.backgroundColor = 'rgba(' + this.initialColor.r + ',' + this.initialColor.g + ',' + this.initialColor.b + ',' + this.initialColor.a + ')';
this.newColorDiv.style.backgroundColor = 'rgba(' + this.color.r + ',' + this.color.g + ',' + this.color.b + ',' + this.color.a + ')';
}
/**
* used by create to set the size of the canvas.
* @private
*/
_setSize() {
this.colorPickerCanvas.style.width = '100%';
this.colorPickerCanvas.style.height = '100%';
this.colorPickerCanvas.width = 289 * this.pixelRatio;
this.colorPickerCanvas.height = 289 * this.pixelRatio;
}
/**
* create all dom elements
* TODO: cleanup, lots of similar dom elements
* @private
*/
_create() {
let visPrefix = 'vis-network-'
this.frame = document.createElement('div');
@ -51,55 +322,109 @@ class ColorPicker {
this.colorPickerDiv.className = visPrefix + 'colorPicker-color';
this.opacityDiv = document.createElement('div');
this.opacityDiv.className = visPrefix + 'colorPicker-opacity';
this.brightnessDiv = document.createElement('div');
this.brightnessDiv.className = visPrefix + 'colorPicker-brightness';
this.saturationDiv = document.createElement('div');
this.saturationDiv.className = visPrefix + 'colorPicker-saturation';
this.opacityRange = document.createElement('input');
this.opacityRange.type = 'range';
this.opacityRange.min = '0';
this.opacityRange.max = '100';
this.opacityRange.value = '100';
this.opacityRange.className = visPrefix + 'configuration range colorPicker';
this.brightnessRange = document.createElement('input');
this.brightnessRange.type = 'range';
this.brightnessRange.min = '0';
this.brightnessRange.max = '100';
this.brightnessRange.value = '100';
this.brightnessRange.className = 'vis-network-configuration range colorPicker';
this.saturationRange = document.createElement('input');
this.saturationRange.type = 'range';
this.saturationRange.min = '0';
this.saturationRange.max = '100';
this.saturationRange.value = '100';
this.saturationRange.className = 'vis-network-configuration range colorPicker';
this.brightnessRange.className = visPrefix + 'configuration range colorPicker';
this.opacityDiv.appendChild(this.opacityRange);
this.brightnessDiv.appendChild(this.brightnessRange);
this.saturationDiv.appendChild(this.saturationRange);
var me = this;
this.opacityRange.onchange = function () {me._setOpacity(this.value);}
this.opacityRange.oninput = function () {me._setOpacity(this.value);}
this.brightnessRange.onchange = function () {me._setBrightness(this.value);}
this.brightnessRange.oninput = function () {me._setBrightness(this.value);}
this.brightnessLabel = document.createElement("div");
this.brightnessLabel.className = visPrefix + "colorPicker-label brightness";
this.brightnessLabel.innerHTML = 'brightness:';
this.opacityLabel = document.createElement("div");
this.opacityLabel.className = visPrefix + "colorPicker-label opacity";
this.opacityLabel.innerHTML = 'opacity:';
this.newColorDiv = document.createElement("div");
this.newColorDiv.className = visPrefix + "colorPicker-newColor";
this.newColorDiv.innerHTML = 'new';
this.initialColorDiv = document.createElement("div");
this.initialColorDiv.className = visPrefix + "colorPicker-initialColor";
this.initialColorDiv.innerHTML = 'initial';
this.cancelButton = document.createElement("div");
this.cancelButton.className = visPrefix + "colorPicker-button cancel";
this.cancelButton.innerHTML = 'cancel';
this.cancelButton.onclick = this._hide.bind(this, false);
this.applyButton = document.createElement("div");
this.applyButton.className = visPrefix + "colorPicker-button apply";
this.applyButton.innerHTML = 'apply';
this.applyButton.onclick = this._apply.bind(this);
this.saveButton = document.createElement("div");
this.saveButton.className = visPrefix + "colorPicker-button save";
this.saveButton.innerHTML = 'save';
this.saveButton.onclick = this._save.bind(this);
this.loadButton = document.createElement("div");
this.loadButton.className = visPrefix + "colorPicker-button load";
this.loadButton.innerHTML = 'load last';
this.loadButton.onclick = this._loadLast.bind(this);
this.frame.appendChild(this.colorPickerDiv);
this.frame.appendChild(this.saturationDiv);
this.frame.appendChild(this.brightnessLabel);
this.frame.appendChild(this.brightnessDiv);
this.frame.appendChild(this.opacityLabel);
this.frame.appendChild(this.opacityDiv);
this.frame.appendChild(this.newColorDiv);
this.frame.appendChild(this.initialColorDiv);
this.frame.appendChild(this.cancelButton);
this.frame.appendChild(this.applyButton);
this.frame.appendChild(this.saveButton);
this.frame.appendChild(this.loadButton);
}
show(container) {
this.container = container;
this.container.appendChild(this.frame);
this.bindHammer();
this.setSize();
}
/**
* bind hammer to the color picker
* @private
*/
_bindHammer() {
this.drag = {};
this.pinch = {};
this.hammer = new Hammer(this.colorPickerCanvas);
this.hammer.get('pinch').set({enable: true});
setColor(color) {
//todo make
hammerUtil.onTouch(this.hammer, (event) => {this._moveSelector(event)});
this.hammer.on('tap', (event) => {this._moveSelector(event)});
this.hammer.on('panstart', (event) => {this._moveSelector(event)});
this.hammer.on('panmove', (event) => {this._moveSelector(event)});
this.hammer.on('panend', (event) => {this._moveSelector(event)});
}
setSize() {
this.colorPickerCanvas.style.width = '100%';
this.colorPickerCanvas.style.height = '100%';
this.colorPickerCanvas.width = this.colorPickerDiv.clientWidth * this.pixelRatio;
this.colorPickerCanvas.height = this.colorPickerDiv.clientHeight * this.pixelRatio;
}
generate() {
/**
* generate the hue circle. This is relatively heavy (200ms) and is done only once on the first time it is shown.
* @private
*/
_generateHueCircle() {
if (this.generated === false) {
let ctx = this.colorPickerCanvas.getContext('2d');
if (this.pixelRation === undefined) {
@ -116,44 +441,41 @@ class ColorPicker {
let h = this.colorPickerCanvas.clientHeight;
ctx.clearRect(0, 0, w, h);
// draw hue circle
let x, y, hue, sat;
let center = {x: w * 0.5, y: h * 0.5};
let r = 0.49 * w;
this.centerCoordinates = {x: w * 0.5, y: h * 0.5};
this.r = 0.49 * w;
let angleConvert = (2 * Math.PI) / 360;
let hfac = 1 / 360;
let sfac = 1 / r;
let sfac = 1 / this.r;
let rgb;
for (hue = 0; hue < 360; hue++) {
for (sat = 0; sat < r; sat++) {
x = center.x + sat * Math.sin(angleConvert * hue);
y = center.y + sat * Math.cos(angleConvert * hue);
for (sat = 0; sat < this.r; sat++) {
x = this.centerCoordinates.x + sat * Math.sin(angleConvert * hue);
y = this.centerCoordinates.y + sat * Math.cos(angleConvert * hue);
rgb = util.HSVToRGB(hue * hfac, sat * sfac, 1);
ctx.fillStyle = 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
ctx.fillRect(x - 0.5, y - 0.5, 2, 2);
}
}
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.circle(this.centerCoordinates.x, this.centerCoordinates.y, this.r);
ctx.stroke();
this.hueCircle = ctx.getImageData(0,0,w,h);
}
this.generated = true;
}
bindHammer() {
this.drag = {};
this.pinch = {};
this.hammer = new Hammer(this.colorPickerCanvas);
this.hammer.get('pinch').set({enable: true});
hammerUtil.onTouch(this.hammer, (event) => {this.moveSelector(event)});
this.hammer.on('tap', (event) => {this.moveSelector(event)});
//this.hammer.on('doubletap', (event) => {this.moveSelector(event)});
//this.hammer.on('press', (event) => {this.moveSelector(event)});
this.hammer.on('panstart', (event) => {this.moveSelector(event)});
this.hammer.on('panmove', (event) => {this.moveSelector(event)});
this.hammer.on('panend', (event) => {this.moveSelector(event)});
//this.hammer.on('pinch', (event) => {this.moveSelector(event)});
}
moveSelector(event) {
/**
* move the selector. This is called by hammer functions.
*
* @param event
* @private
*/
_moveSelector(event) {
let rect = this.colorPickerDiv.getBoundingClientRect();
let left = event.center.x - rect.left;
let top = event.center.y - rect.top;
@ -164,29 +486,29 @@ class ColorPicker {
let x = left - centerX;
let y = top - centerY;
let angle = Math.atan(y / x);
if (x < 0) {
angle += Math.PI;
}
let angle = Math.atan2(x,y);
let radius = 0.98 * Math.min(Math.sqrt(x * x + y * y), centerX);
let newTop = Math.sin(angle) * radius + centerY;
let newLeft = Math.cos(angle) * radius + centerX;
let newTop = Math.cos(angle) * radius + centerY;
let newLeft = Math.sin(angle) * radius + centerX;
this.colorPickerSelector.style.top = newTop - 0.5 * this.colorPickerSelector.clientHeight + 'px';
this.colorPickerSelector.style.left = newLeft - 0.5 * this.colorPickerSelector.clientWidth + 'px';
}
redraw(roomController) {
if (this.frame === undefined) {
this._create();
}
let pos = roomController.canvasToDOM({x: 0, y: 0});
this.frame.style.top = '50px';
this.frame.style.left = pos.x - 350 + 'px';
// set color
let h = angle / (2 * Math.PI);
h = h < 0 ? h + 1 : h;
let s = radius / this.r;
let hsv = util.RGBToHSV(this.color.r, this.color.g, this.color.b);
hsv.h = h;
hsv.s = s;
let rgba = util.HSVToRGB(hsv.h, hsv.s, hsv.v);
rgba['a'] = this.color.a;
this.color = rgba;
// update previews
this.initialColorDiv.style.backgroundColor = 'rgba(' + this.initialColor.r + ',' + this.initialColor.g + ',' + this.initialColor.b + ',' + this.initialColor.a + ')';
this.newColorDiv.style.backgroundColor = 'rgba(' + this.color.r + ',' + this.color.g + ',' + this.color.b + ',' + this.color.a + ')';
}
}

+ 11
- 6
lib/util.js View File

@ -903,8 +903,8 @@ exports.parseColor = function(color) {
}
else {
c = {};
c.background = color.background || 'white';
c.border = color.border || c.background;
c.background = color.background || undefined;
c.border = color.border || undefined;
if (exports.isString(color.highlight)) {
c.highlight = {
@ -914,8 +914,8 @@ exports.parseColor = function(color) {
}
else {
c.highlight = {};
c.highlight.background = color.highlight && color.highlight.background || c.background;
c.highlight.border = color.highlight && color.highlight.border || c.border;
c.highlight.background = color.highlight && color.highlight.background || undefined;
c.highlight.border = color.highlight && color.highlight.border || undefined;
}
if (exports.isString(color.hover)) {
@ -926,8 +926,8 @@ exports.parseColor = function(color) {
}
else {
c.hover = {};
c.hover.background = color.hover && color.hover.background || c.background;
c.hover.border = color.hover && color.hover.border || c.border;
c.hover.background = color.hover && color.hover.background || undefined;
c.hover.border = color.hover && color.hover.border || undefined;
}
}
@ -1069,6 +1069,11 @@ exports.isValidRGB = function(rgb) {
var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
return isOk;
}
exports.isValidRGBA = function(rgba) {
rgba = rgba.replace(" ","");
var isOk = /rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(rgba);
return isOk;
}
/**
* This recursively redirects the prototype of JSON objects to the referenceObject

Loading…
Cancel
Save