Connect Table Cells from Different Rows with Diagonal line - html

I'm looking to have diagonal lines connecting table cells from different rows. I can easily get a diagonal line to connect the corners of a single cell, but is there a way to cross cells?
For example, in the image below, I'd like to have diagonal lines connecting "1 and "3" to the "1 & 3" cell. Same thing with 2, 5, and 2 & 5.
Of course, this is just one variation. I would like to be able to connect any number of cells in any way. Maybe something like this...
Any ideas here?

Try this Demo : Demo
I added one more demo, which will take only the cell positions and based on that it will automatically find the positions and connect them accordingly.
It is better than the other answer, in which we need to provide the line start and end points.
HTML Code
<br>
<table border="1" cellpadding="5">
<tr>
<td>1</td>
<td>+</td>
<td>4</td>
<td>1</td>
<td>+</td>
<td>4</td>
</tr>
<tr>
<td></td>
<td>5</td>
<td></td>
<td></td>
<td>5</td>
<td></td>
</tr>
</table>
CSS Code
.line {
width : 2px;
background-color : red;
display : block;
position : absolute;
-webkit-transform-origin : 0 0;
transform-origin : 0 0;
}
.green {
background-color : green;
}
.blue {
background-color : blue;
}
JavaScript Code
$(function() {
connectCells($('table'), {x : 0, y : 0}, {x : 1, y : 1}, 'red');
connectCells($('table'), {x : 2, y : 0}, {x : 1, y : 1}, 'blue');
});
function connectCells(table, cell1, cell2, cls) {
var td1 = table.find('tr:eq(' + cell1.y + ') td:eq(' + cell1.x + ')');
var td2 = table.find('tr:eq(' + cell2.y + ') td:eq(' + cell2.x + ')');
var pos1 = $(td1).position();
var pos2 = $(td2).position();
drawLine({
x : pos1.left + $(td1).outerWidth()/2,
y : pos1.top + $(td1).outerHeight() - 5
}, {
x : pos2.left + $(td2).outerWidth()/2,
y : pos2.top + $(td2).outerHeight() - 5
}, cls);
}
function drawLine(tp1, tp2, cls) {
if(!cls) { cls = "" };
if(tp2.x < tp1.x && tp2.y < tp1.y) {
p1 = tp2;
p2 = tp1;
} else {
p1 = tp1;
p2 = tp2;
}
var ang = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI - 90;
var lin = $('<span></span>').addClass("line " + cls).appendTo('body');
$(lin).css({
'top' : p1.y,
'left' : p1.x,
'height' : Math.sqrt((p1.x - p2.x)* (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)),
'transform' : 'rotate(' + ang + 'deg)'
});
}

Try this : Demo
JavaScript Code
$(function() {
var p = { x : 200, y : 200 },
p1 = { x : 300, y : 150 },
p2 = { x : 200, y : 100 },
p3 = { x : 100, y : 150 };
drawLine(p, p1);
drawLine(p, p2, "green");
drawLine(p, p3, "blue");
});
function drawLine(tp1, tp2, cls) {
if(!cls) { cls = "" };
if(tp2.x < tp1.x && tp2.y < tp1.y) {
p1 = tp2;
p2 = tp1;
} else {
p1 = tp1;
p2 = tp2;
}
var ang = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI - 90;
var lin = $('<span></span>').addClass("line " + cls).appendTo('body');
$(lin).css({
'top' : p1.y,
'left' : p1.x,
'height' : Math.sqrt((p1.x - p2.x)* (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)),
'transform' : 'rotate(' + ang + 'deg)'
});
}
CSS Code
.line {
width : 2px;
background-color : red;
display : block;
position : absolute;
-webkit-transform-origin : 0 0;
transform-origin : 0 0;
}
.green {
background-color : green;
}
.blue {
background-color : blue;
}
It will create a new span and position it at the given co-ordinates using CSS3 transform.
You can find the start and end positions dynamically and pass them to draw lines with optional colors.
You can enhance this as per your needs.

Related

jsPDF + html2PDF - how do I append html code to footer

I know how to append my footer with text but now I need to append dynamic HTML code. I can't find anything in the documentation or in any forum.
const page_margin_left = 20;
const page_margin_right = 15;
const page_margin_bottom = 20;
const page_margin_top = 5;
html2pdf()
.set({
margin: [page_margin_top, page_margin_left, page_margin_bottom, page_margin_right],
filename: 'test.pdf',
image: { type: 'jpeg', quality: 1 },
html2canvas: { dpi: 192, scale: 2, letterRendering: true, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
//pageBreak: {mode: ['avoid-all', 'css'], avoid: ['.pi-row']},
})
.from(print_target_container[0].innerHTML)
.toPdf().get('pdf').then(function (pdf) {
var totalPages = pdf.internal.getNumberOfPages();
var pageHeight = pdf.internal.pageSize.height || pdf.internal.pageSize.getHeight();
var pageWidth = pdf.internal.pageSize.width || pdf.internal.pageSize.getWidth();
var footerText = offer_template_page_footer;
for (i = 1; i <= totalPages; i++) {
if (i > 1) {
pdf.setPage(i);
pdf.setFontSize(8);
pdf.setTextColor(150);
pdf.line(25, pageHeight - 15, pageWidth - page_margin_right, pageHeight - 15); // horizontal line x1, y1, x2, y2, style
pdf.text(i + ' (' + totalPages + ')\n' + footerText, pageWidth / 2, pageHeight - 10, {align: 'center'});
//pdf.text(i + ' (' + totalPages + ')\n' + offer_template_page_footer, pdf.internal.pageSize.getWidth() / 2, pdf.internal.pageSize.getHeight() - 8);
}
}
}).save();

How to create a diverging data legend on a choroplet map?

I'm trying to create a choroplet map with diverging data (going from -0.7 to 0.7) and can't find a way so that my legend show the correct colors. The problem is because when colors are all positive signs is always > and is sequential. Now because I have negative numbers, it doesn't work anymore. How can I correct this?
// get color depending on population differentiel value
function getColor(d) {
return d > 0.7 ? '#b2182b' :
d > 0.5 ? '#d6604d' :
d > 0.3 ? '#f4a582' :
d > 0.1 ? '#fddbc7' :
d < -0.7 ? '#2166ac' :
d < -0.5 ? '#4393c3' :
d < -0.3 ? '#92c5de' :
d < -0.1 ? '#d1e5f0' :
'#f7f7f7';
}
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [-0.7, -0.5, -0.3, -0.1, 0.1, 0.3, 0.5, 0.7],
labels = [],
from, to;
for (var i = 0; i < grades.length; i++) {
from = grades[i];
to = grades[i + 1];
labels.push(
'<i style="background:' + getColor(from + 1) + '"></i> ' +
from + (to ? ' à ' + to : '+'));
}
div.innerHTML = labels.join('<br>');
return div;
};
The problem is because when colors are all positive signs is always > and is sequential. Now because I have negative numbers, it doesn't work anymore.
No, it's because you're mixing > and < without any good reason, instead of following a regular pattern of decreasing numbers, e.g.:
function getColor(d) {
return d > 0.7 ? '#b2182b' :
d > 0.5 ? '#d6604d' :
d > 0.3 ? '#f4a582' :
d > 0.1 ? '#fddbc7' :
d > -0.1 ? '#d1e5f0' :
d > -0.3 ? '#92c5de' :
d > -0.5 ? '#4393c3' :
d > -0.7 ? '#2166ac' :
'#f7f7f7';
}
Now the stop points are aligned, so every stop point is the smallest value for the color range.
I'd go even further and store the stops and range colors in a data structure, correlating the smallest end of a range with the corresponding colour:
var stops = [
{ stop: 0.7, color: '#b2182b' },
{ stop: 0.5, color: '#d6604d' },
{ stop: 0.3, color: '#f4a582' },
{ stop: 0.1, color: '#fddbc7' },
{ stop: -0.1, color: '#d1e5f0' },
{ stop: -0.3, color: '#92c5de' },
{ stop: -0.5, color: '#4393c3' },
{ stop: -0.7, color: '#2166ac' },
{ stop: -Infinity, color: '#f7f7f7' },
];
And recreate the getColor() function by looping through that data structure:
function getColor(d) {
for (var i in stops) {
if (d > stops[i].stop) { return stops[i].color; }
}
}
Note that all numbers are greater than -Infinity, so the last entry in that data structure shall work as the default case.
And create a legend by iterating through the same data structure, carrying over the range's upper stop from the previous step (and initializing that at Infinity, as that's the implicit upper stop for the first range):
var rangeMax = 'Infinity';
for (var i in stops) {
var rangeMin = stops[i].stop.toString();
var rangeColour = stops[i].color;
labels.push(
'<i style="background:' + rangeColour + '"></i> ' +
rangeMin + ' à ' + rangeMax
);
rangeMax = stops[i].stop;
}
getColor(from + 1) is the culprit. Because of the "+ 1", you will always be off your scale.
Note that even after removing it, you will be missing the lowest legend (< 0.7). You can handle it in a similar way as your highest legend.

How to display count value of each category of Y axis in a graph using Morris.Bar function?

I am displaying data in graphical format and I am using Morris.Bar function in my cshtml page. The Y axis has categories namely: Performance, Maintainability, Others, Portability, Reliability and Security.
I am using the following function:
Morris.Bar({
element: 'category-bar-chart',
data: JSON.parse(''[{"y":"Performance","a":23},{"y":"Maintainability","a":106},{"y":"Others","a":98},{"y":"Portability","a":27},{"y":"Reliability","a":87},{"y":"Security","a":14}]'),'),
xkey: 'y',
ykeys: ['a'],
labels: ['Violation'],
xLabelAngle: 43,
});
But currently it is not displaying the value for each category at the top of each bar. May I know what property I can add to get the values at the top of each bar?
There's no built-in parameter to display the value on top of each Bar.
But you can extend Morris to add this parameter. I've extended Morris, adding a labelTop property for Bar charts. If set to true, a label with the value is added on top of each Bar (I restricted this property for non stacked Bar, as there's multiple values with stacked Bar).
Usage:
labelTop: true
Please try the snippet below to see a working example:
(function() {
var $, MyMorris;
MyMorris = window.MyMorris = {};
$ = jQuery;
MyMorris = Object.create(Morris);
MyMorris.Bar.prototype.defaults["labelTop"] = false;
MyMorris.Bar.prototype.drawLabelTop = function(xPos, yPos, text) {
var label;
return label = this.raphael.text(xPos, yPos, text).attr('font-size', this.options.gridTextSize).attr('font-family', this.options.gridTextFamily).attr('font-weight', this.options.gridTextWeight).attr('fill', this.options.gridTextColor);
};
MyMorris.Bar.prototype.drawSeries = function() {
var barWidth, bottom, groupWidth, idx, lastTop, left, leftPadding, numBars, row, sidx, size, spaceLeft, top, ypos, zeroPos;
groupWidth = this.width / this.options.data.length;
numBars = this.options.stacked ? 1 : this.options.ykeys.length;
barWidth = (groupWidth * this.options.barSizeRatio - this.options.barGap * (numBars - 1)) / numBars;
if (this.options.barSize) {
barWidth = Math.min(barWidth, this.options.barSize);
}
spaceLeft = groupWidth - barWidth * numBars - this.options.barGap * (numBars - 1);
leftPadding = spaceLeft / 2;
zeroPos = this.ymin <= 0 && this.ymax >= 0 ? this.transY(0) : null;
return this.bars = (function() {
var _i, _len, _ref, _results;
_ref = this.data;
_results = [];
for (idx = _i = 0, _len = _ref.length; _i < _len; idx = ++_i) {
row = _ref[idx];
lastTop = 0;
_results.push((function() {
var _j, _len1, _ref1, _results1;
_ref1 = row._y;
_results1 = [];
for (sidx = _j = 0, _len1 = _ref1.length; _j < _len1; sidx = ++_j) {
ypos = _ref1[sidx];
if (ypos !== null) {
if (zeroPos) {
top = Math.min(ypos, zeroPos);
bottom = Math.max(ypos, zeroPos);
} else {
top = ypos;
bottom = this.bottom;
}
left = this.left + idx * groupWidth + leftPadding;
if (!this.options.stacked) {
left += sidx * (barWidth + this.options.barGap);
}
size = bottom - top;
if (this.options.verticalGridCondition && this.options.verticalGridCondition(row.x)) {
this.drawBar(this.left + idx * groupWidth, this.top, groupWidth, Math.abs(this.top - this.bottom), this.options.verticalGridColor, this.options.verticalGridOpacity, this.options.barRadius, row.y[sidx]);
}
if (this.options.stacked) {
top -= lastTop;
}
this.drawBar(left, top, barWidth, size, this.colorFor(row, sidx, 'bar'), this.options.barOpacity, this.options.barRadius);
_results1.push(lastTop += size);
if (this.options.labelTop && !this.options.stacked) {
label = this.drawLabelTop((left + (barWidth / 2)), top - 10, row.y[sidx]);
textBox = label.getBBox();
_results.push(textBox);
}
} else {
_results1.push(null);
}
}
return _results1;
}).call(this));
}
return _results;
}).call(this);
};
}).call(this);
Morris.Bar({
element: 'category-bar-chart',
data: [
{ "y": "Performance", "a": 23 },
{ "y": "Maintainability", "a": 106 },
{ "y": "Others", "a": 98 },
{ "y": "Portability", "a": 27 },
{ "y": "Reliability", "a": 87 },
{ "y": "Security", "a": 14 }],
xkey: 'y',
ykeys: ['a'],
labels: ['Violation'],
xLabelAngle: 43,
labelTop: true
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/morris.js/0.5.1/morris.css" rel="stylesheet" />
<div id="category-bar-chart"></div>

Bootstrap color picker bigger

Is there any way to make the bootstrap color picker box bigger ?
I looked through it's css but i couldn't find where is setting the box height and weight.
I am using this color picker : Stefan Petre's Bootstrap Colorpicker
Assuming you're using the HTML5 native color picker, you can customize it with CSS :
input[type="color"].custom {
padding: 0;
border: none;
height: 50px;
width: 50px;
vertical-align: middle;
}
<div class="container">
<form role="form">
<div class="form-group">
<label for="cp1">HTML5 native Color Picker :</label><br>
<input type="color" name="cp1" value="#9b59b6">
</div>
<div class="form-group">
<label for="cp2">CSS Customized Color Picker :</label><br>
<input type="color" name="cp2" value="#9b59b6" class="custom">
</div>
</form>
</div>
EDIT (using bootstrap-colorpicker.js plugin)
As the width is hard-coded in the plugin, you'll need to modify the plugin itself. Here's the full JS code which will allow you to simply set your desired size, without changing the original CSS (just modify CPSize value in the first lines) :
/* =========================================================
* bootstrap-colorpicker.js [edited]
* http://www.eyecon.ro/bootstrap-colorpicker
* =========================================================
* Copyright 2012 Stefan Petre
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
!function( $ ) {
// Set your desired size here :
var CPSize = 300;
// Color object
var Color = function(val) {
this.value = {
h: 1,
s: 1,
b: 1,
a: 1
};
this.setColor(val);
};
Color.prototype = {
constructor: Color,
//parse a string to HSB
setColor: function(val){
val = val.toLowerCase();
var that = this;
$.each( CPGlobal.stringParsers, function( i, parser ) {
var match = parser.re.exec( val ),
values = match && parser.parse( match ),
space = parser.space||'rgba';
if ( values ) {
if (space === 'hsla') {
that.value = CPGlobal.RGBtoHSB.apply(null, CPGlobal.HSLtoRGB.apply(null, values));
} else {
that.value = CPGlobal.RGBtoHSB.apply(null, values);
}
return false;
}
});
},
setHue: function(h) {
this.value.h = 1- h;
},
setSaturation: function(s) {
this.value.s = s;
},
setLightness: function(b) {
this.value.b = 1- b;
},
setAlpha: function(a) {
this.value.a = parseInt((1 - a)*100, 10)/100;
},
// HSBtoRGB from RaphaelJS
// https://github.com/DmitryBaranovskiy/raphael/
toRGB: function(h, s, b, a) {
if (!h) {
h = this.value.h;
s = this.value.s;
b = this.value.b;
}
h *= 360;
var R, G, B, X, C;
h = (h % 360) / 60;
C = b * s;
X = C * (1 - Math.abs(h % 2 - 1));
R = G = B = b - C;
h = ~~h;
R += [C, X, 0, 0, X, C][h];
G += [X, C, C, X, 0, 0][h];
B += [0, 0, X, C, C, X][h];
return {
r: Math.round(R*255),
g: Math.round(G*255),
b: Math.round(B*255),
a: a||this.value.a
};
},
toHex: function(h, s, b, a){
var rgb = this.toRGB(h, s, b, a);
return '#'+((1 << 24) | (parseInt(rgb.r) << 16) | (parseInt(rgb.g) << 8) | parseInt(rgb.b)).toString(16).substr(1);
},
toHSL: function(h, s, b, a){
if (!h) {
h = this.value.h;
s = this.value.s;
b = this.value.b;
}
var H = h,
L = (2 - s) * b,
S = s * b;
if (L > 0 && L <= 1) {
S /= L;
} else {
S /= 2 - L;
}
L /= 2;
if (S > 1) {
S = 1;
}
return {
h: H,
s: S,
l: L,
a: a||this.value.a
};
}
};
// Picker object
var Colorpicker = function(element, options){
this.element = $(element);
var format = options.format||this.element.data('color-format')||'hex';
this.format = CPGlobal.translateFormats[format];
this.isInput = this.element.is('input');
this.component = this.element.is('.color') ? this.element.find('.add-on') : false;
this.picker = $(CPGlobal.template)
.appendTo('body')
.on('mousedown', $.proxy(this.mousedown, this));
if (this.isInput) {
this.element.on({
'focus': $.proxy(this.show, this),
'keyup': $.proxy(this.update, this)
});
} else if (this.component){
this.component.on({
'click': $.proxy(this.show, this)
});
} else {
this.element.on({
'click': $.proxy(this.show, this)
});
}
if (format === 'rgba' || format === 'hsla') {
this.picker.addClass('alpha');
this.alpha = this.picker.find('.colorpicker-alpha')[0].style;
}
if (this.component){
this.picker.find('.colorpicker-color').hide();
this.preview = this.element.find('i')[0].style;
} else {
this.preview = this.picker.find('div:last')[0].style;
}
this.base = this.picker.find('div:first')[0].style;
this.update();
};
Colorpicker.prototype = {
constructor: Colorpicker,
show: function(e) {
this.picker.show();
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
this.place();
$(window).on('resize', $.proxy(this.place, this));
if (!this.isInput) {
if (e) {
e.stopPropagation();
e.preventDefault();
}
}
$(document).on({
'mousedown': $.proxy(this.hide, this)
});
this.element.trigger({
type: 'show',
color: this.color
});
},
update: function(){
this.color = new Color(this.isInput ? this.element.prop('value') : this.element.data('color'));
this.picker.find('i')
.eq(0).css({left: this.color.value.s*100, top: CPSize - this.color.value.b*100}).end()
.eq(1).css('top', CPSize * (1 - this.color.value.h)).end()
.eq(2).css('top', CPSize * (1 - this.color.value.a));
this.previewColor();
},
setValue: function(newColor) {
this.color = new Color(newColor);
this.picker.find('i')
.eq(0).css({left: this.color.value.s*100, top: CPSize - this.color.value.b*100}).end()
.eq(1).css('top', CPSize * (1 - this.color.value.h)).end()
.eq(2).css('top', CPSize * (1 - this.color.value.a));
this.previewColor();
this.element.trigger({
type: 'changeColor',
color: this.color
});
},
hide: function(){
this.picker.hide();
$(window).off('resize', this.place);
if (!this.isInput) {
$(document).off({
'mousedown': this.hide
});
if (this.component){
this.element.find('input').prop('value', this.format.call(this));
}
this.element.data('color', this.format.call(this));
} else {
this.element.prop('value', this.format.call(this));
}
this.element.trigger({
type: 'hide',
color: this.color
});
},
place: function(){
var offset = this.component ? this.component.offset() : this.element.offset();
this.picker.css({
top: offset.top + this.height,
left: offset.left
});
},
//preview color change
previewColor: function(){
try {
this.preview.backgroundColor = this.format.call(this);
} catch(e) {
this.preview.backgroundColor = this.color.toHex();
}
//set the color for brightness/saturation slider
this.base.backgroundColor = this.color.toHex(this.color.value.h, 1, 1, 1);
//set te color for alpha slider
if (this.alpha) {
this.alpha.backgroundColor = this.color.toHex();
}
},
pointer: null,
slider: null,
mousedown: function(e){
e.stopPropagation();
e.preventDefault();
var target = $(e.target);
//detect the slider and set the limits and callbacks
var zone = target.closest('div');
if (!zone.is('.colorpicker')) {
if (zone.is('.colorpicker-saturation')) {
this.slider = $.extend({}, CPGlobal.sliders.saturation);
}
else if (zone.is('.colorpicker-hue')) {
this.slider = $.extend({}, CPGlobal.sliders.hue);
}
else if (zone.is('.colorpicker-alpha')) {
this.slider = $.extend({}, CPGlobal.sliders.alpha);
} else {
return false;
}
var offset = zone.offset();
//reference to knob's style
this.slider.knob = zone.find('i')[0].style;
this.slider.left = e.pageX - offset.left;
this.slider.top = e.pageY - offset.top;
this.pointer = {
left: e.pageX,
top: e.pageY
};
//trigger mousemove to move the knob to the current position
$(document).on({
mousemove: $.proxy(this.mousemove, this),
mouseup: $.proxy(this.mouseup, this)
}).trigger('mousemove');
}
return false;
},
mousemove: function(e){
e.stopPropagation();
e.preventDefault();
var left = Math.max(
0,
Math.min(
this.slider.maxLeft,
this.slider.left + ((e.pageX||this.pointer.left) - this.pointer.left)
)
);
var top = Math.max(
0,
Math.min(
this.slider.maxTop,
this.slider.top + ((e.pageY||this.pointer.top) - this.pointer.top)
)
);
this.slider.knob.left = left + 'px';
this.slider.knob.top = top + 'px';
if (this.slider.callLeft) {
this.color[this.slider.callLeft].call(this.color, left/CPSize);
}
if (this.slider.callTop) {
this.color[this.slider.callTop].call(this.color, top/CPSize);
}
this.previewColor();
this.element.trigger({
type: 'changeColor',
color: this.color
});
return false;
},
mouseup: function(e){
e.stopPropagation();
e.preventDefault();
$(document).off({
mousemove: this.mousemove,
mouseup: this.mouseup
});
return false;
}
}
$.fn.colorpicker = function ( option, val ) {
return this.each(function () {
var $this = $(this),
data = $this.data('colorpicker'),
options = typeof option === 'object' && option;
if (!data) {
$this.data('colorpicker', (data = new Colorpicker(this, $.extend({}, $.fn.colorpicker.defaults,options))));
}
if (typeof option === 'string') data[option](val);
});
};
$.fn.colorpicker.defaults = {
};
$.fn.colorpicker.Constructor = Colorpicker;
var CPGlobal = {
// translate a format from Color object to a string
translateFormats: {
'rgb': function(){
var rgb = this.color.toRGB();
return 'rgb('+rgb.r+','+rgb.g+','+rgb.b+')';
},
'rgba': function(){
var rgb = this.color.toRGB();
return 'rgba('+rgb.r+','+rgb.g+','+rgb.b+','+rgb.a+')';
},
'hsl': function(){
var hsl = this.color.toHSL();
return 'hsl('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%)';
},
'hsla': function(){
var hsl = this.color.toHSL();
return 'hsla('+Math.round(hsl.h*360)+','+Math.round(hsl.s*100)+'%,'+Math.round(hsl.l*100)+'%,'+hsl.a+')';
},
'hex': function(){
return this.color.toHex();
}
},
sliders: {
saturation: {
maxLeft: CPSize,
maxTop: CPSize,
callLeft: 'setSaturation',
callTop: 'setLightness'
},
hue: {
maxLeft: 0,
maxTop: CPSize,
callLeft: false,
callTop: 'setHue'
},
alpha: {
maxLeft: 0,
maxTop: CPSize,
callLeft: false,
callTop: 'setAlpha'
}
},
// HSBtoRGB from RaphaelJS
// https://github.com/DmitryBaranovskiy/raphael/
RGBtoHSB: function (r, g, b, a){
r /= 255;
g /= 255;
b /= 255;
var H, S, V, C;
V = Math.max(r, g, b);
C = V - Math.min(r, g, b);
H = (C === 0 ? null :
V == r ? (g - b) / C :
V == g ? (b - r) / C + 2 :
(r - g) / C + 4
);
H = ((H + 360) % 6) * 60 / 360;
S = C === 0 ? 0 : C / V;
return {h: H||1, s: S, b: V, a: a||1};
},
HueToRGB: function (p, q, h) {
if (h < 0)
h += 1;
else if (h > 1)
h -= 1;
if ((h * 6) < 1)
return p + (q - p) * h * 6;
else if ((h * 2) < 1)
return q;
else if ((h * 3) < 2)
return p + (q - p) * ((2 / 3) - h) * 6;
else
return p;
},
HSLtoRGB: function (h, s, l, a)
{
if (s < 0) {
s = 0;
}
var q;
if (l <= 0.5) {
q = l * (1 + s);
} else {
q = l + s - (l * s);
}
var p = 2 * l - q;
var tr = h + (1 / 3);
var tg = h;
var tb = h - (1 / 3);
var r = Math.round(CPGlobal.HueToRGB(p, q, tr) * 255);
var g = Math.round(CPGlobal.HueToRGB(p, q, tg) * 255);
var b = Math.round(CPGlobal.HueToRGB(p, q, tb) * 255);
return [r, g, b, a||1];
},
// a set of RE's that can match strings and generate color tuples.
// from John Resig color plugin
// https://github.com/jquery/jquery-color/
stringParsers: [
{
re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
parse: function( execResult ) {
return [
execResult[ 1 ],
execResult[ 2 ],
execResult[ 3 ],
execResult[ 4 ]
];
}
}, {
re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
parse: function( execResult ) {
return [
2.55 * execResult[1],
2.55 * execResult[2],
2.55 * execResult[3],
execResult[ 4 ]
];
}
}, {
re: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,
parse: function( execResult ) {
return [
parseInt( execResult[ 1 ], 16 ),
parseInt( execResult[ 2 ], 16 ),
parseInt( execResult[ 3 ], 16 )
];
}
}, {
re: /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/,
parse: function( execResult ) {
return [
parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
];
}
}, {
re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)/,
space: 'hsla',
parse: function( execResult ) {
return [
execResult[1]/360,
execResult[2] / 100,
execResult[3] / 100,
execResult[4]
];
}
}
],
template: '<div class="colorpicker dropdown-menu">'+
'<div class="colorpicker-saturation" style="width:'+CPSize+'px;height:'+CPSize+'px"><i><b></b></i></div>'+
'<div class="colorpicker-hue" style="height:'+CPSize+'px"><i></i></div>'+
'<div class="colorpicker-alpha" style="height:'+CPSize+'px"><i></i></div>'+
'<div class="colorpicker-color"><div /></div>'+
'</div>'
};
}( window.jQuery );
For Bootstrap Color Picker (based on Stefan Petre's Bootstrap Colorpicker) I needed to edit the MaxTop and MaxLeft values in defaults section of the code file. There is a section for regular or horizontal. Set the values to match your css height and width.
sliders: {
saturation: {
maxLeft: 100,
maxTop: 100,
callLeft: 'setSaturation',
callTop: 'setBrightness'
},
hue: {
maxLeft: 0,
maxTop: 100,
callLeft: false,
callTop: 'setHue'
},
alpha: {
maxLeft: 0,
maxTop: 100,
callLeft: false,
callTop: 'setAlpha'
}
},
slidersHorz: {
saturation: {
maxLeft: 100,
maxTop: 100,
callLeft: 'setSaturation',
callTop: 'setBrightness'
},
hue: {
maxLeft: 100,
maxTop: 0,
callLeft: 'setHue',
callTop: false
},
alpha: {
maxLeft: 100,
maxTop: 0,
callLeft: 'setAlpha',
callTop: false
}
}
If you want to make each instance of colorpicker bigger, change the js and css file of bootstrap pugin.
Change the below mentioned functions in bootstrap-colorpicker.js:
/** * Vertical sliders configuration * #type {Object} */
sliders: {
saturation: {
maxLeft: 200,
maxTop: 200,
callLeft: 'setSaturationRatio',
callTop: 'setBrightnessRatio'
},
hue: {
maxLeft: 0,
maxTop: 200,
callLeft: false,
callTop: 'setHueRatio'
},
alpha: {
maxLeft: 0,
maxTop: 200,
callLeft: false,
callTop: 'setAlphaRatio'
}
},
/**
* Horizontal sliders configuration
* #type {Object}
*/
slidersHorz: {
saturation: {
maxLeft: 200,
maxTop: 200,
callLeft: 'setSaturationRatio',
callTop: 'setBrightnessRatio'
},
hue: {
maxLeft: 200,
maxTop: 0,
callLeft: 'setHueRatio',
callTop: false
},
alpha: {
maxLeft: 200,
maxTop: 0,
callLeft: 'setAlphaRatio',
callTop: false
}
}
Changes in bootstrap-colorpicker.css
.colorpicker-saturation{
width: 100px;
height: 100px;
background-image: url('');
}
.colorpicker-hue, .colorpicker-alpha {
width: 15px;
height: 100px;
float: left;
cursor: row-resize;
margin-left: 4px;
margin-bottom: 4px;
}
.colorpicker.colorpicker-horizontal.colorpicker-bar {
width: 100px;
}
.colorpicker.colorpicker-horizontal.colorpicker-hue.colorpicker-guide,
.colorpicker.colorpicker-horizontal.colorpicker-alpha.colorpicker
- guide {
display: block;
height: 15px;
background: #ffffff;
position: absolute;
top: 0;
left: 0;
width: 1px;
border: none;
margin-top: 0;
}
.colorpicker-bar-horizontal {
height: 15px;
margin: 0 0 4px 0;
float: left;
width: 100px;
}
.colorpicker-bar {
height: 15px;
margin: 5px 0 0 0;
clear: both;
text-align: center;
font-size: 10px;
}
Same has been explained here.

Drawing multiple edges between two nodes with d3

I've been following Mike Bostock's code from this example to learn how to draw directed graphs in d3 and was wondering how I would structure the code so that I could add multiple edges between two nodes in the graph. For example, if the dataset in the example above were defined as
var links = [{source: "Microsoft", target: "Amazon", type: "licensing"},
{source: "Microsoft", target: "Amazon", type: "suit"},
{source: "Samsung", target: "Apple", type: "suit"},
{source: "Microsoft", target: "Amazon", type: "resolved"}];
and then run through the code, all I see is one line. All the paths are being drawn correctly in the html code, however they all have the same coordinates and orientation which causes the visual to look like 1 line. What kind of code restructuring would need to be done in this example to allow for the 3 edges to not be drawn on top of each other?
In fact, the original visualization is a prime example of one method to show multiple links between nodes, that is - using arcs rather than direct paths, so you can see both incoming and outgoing links.
This concept can be extended to show multiple of each of these types of links by changing the radius values of subsequent svg path(arc) elements representing the link. A basic example being
dr = 75/d.linknum;
Where d.linknum represents the number of the successive link. dr is later used as the rx and ry amounts for the arc being drawn.
Full implementation here: http://jsfiddle.net/7HZcR/3/
Here is the source for the answer above if anyone ever needs it :
var links = [{source: "Microsoft", target: "Amazon", type: "licensing"},
{source: "Microsoft", target: "Amazon", type: "suit"},
{source: "Samsung", target: "Apple", type: "suit"},
{source: "Microsoft", target: "Amazon", type: "resolved"}];
//sort links by source, then target
links.sort(function(a,b) {
if (a.source > b.source) {return 1;}
else if (a.source < b.source) {return -1;}
else {
if (a.target > b.target) {return 1;}
if (a.target < b.target) {return -1;}
else {return 0;}
}
});
//any links with duplicate source and target get an incremented 'linknum'
for (var i=0; i<links.length; i++) {
if (i != 0 &&
links[i].source == links[i-1].source &&
links[i].target == links[i-1].target) {
links[i].linknum = links[i-1].linknum + 1;
}
else {links[i].linknum = 1;};
};
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
var w = 600,
h = 600;
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([w, h])
.linkDistance(60)
.charge(-300)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg:svg")
.attr("width", w)
.attr("height", h);
// Per-type markers, as they don't inherit styles.
svg.append("svg:defs").selectAll("marker")
.data(["suit", "licensing", "resolved"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", 6)
.call(force.drag);
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
// A copy of the text with a thick white stroke for legibility.
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.attr("class", "shadow")
.text(function(d) { return d.name; });
text.append("svg:text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = 75/d.linknum; //linknum is defined above
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
circle.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
path.link {
fill: none;
stroke: #666;
stroke-width: 1.5px;
}
marker#licensing {
fill: green;
}
path.link.licensing {
stroke: green;
}
path.link.resolved {
stroke-dasharray: 0,2 1;
}
circle {
fill: #ccc;
stroke: #333;
stroke-width: 1.5px;
}
text {
font: 10px sans-serif;
pointer-events: none;
}
text.shadow {
stroke: #fff;
stroke-width: 3px;
stroke-opacity: .8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
And for D3v4 see here : https://bl.ocks.org/mbostock/4600693
Thanks for the answers using linknum, it really worked. however the lines started overlapping after linkum > 10.
Here is a function to generate equidistance quadratic curves
// use it like 'M' + d.source.x + ',' + d.source.y + link_arc2(d) + d.target.x + ',' + d.target.y
function link_arc2(d) {
// draw line for 1st link
if (d.linknum == 1) {
return 'L';
}
else {
let sx = d.source.x;
let sy = d.source.y;
let tx = d.target.x;
let ty = d.target.y;
// distance b/w curve paths
let cd = 30;
// find middle of source and target
let cx = (sx + tx) / 2;
let cy = (sy + ty) / 2;
// find angle of line b/w source and target
var angle = Math.atan2(ty - sy, tx - sx);
// add radian equivalent of 90 degree
var c_angle = angle + 1.5708;
// draw odd and even curves either side of line
if (d.linknum & 1) {
return 'Q ' + (cx - ((d.linknum - 1) * cd * Math.cos(c_angle))) + ',' + (cy - ((d.linknum - 1) * cd * Math.sin(c_angle))) + ' ';
}
else {
return 'Q ' + (cx + (d.linknum * cd * Math.cos(c_angle))) + ',' + (cy + (d.linknum * cd * Math.sin(c_angle))) + ' ';
}
}
}