Inertial drag in force layout - d3.js

I'm using lastest d3js with force layout to create interactive graph like this:
Requirements are:
Nodes can be dragged (inertial drag)
Node bounces back when hitting border.
Nodes don't overlap each other (I can do this based on Collision Detection sample)
Somebody please help me with 1 & 2.
The background for this question is in this related question.
Thank you.

Background
The background for this answer is in my answer to a related question here.
That question was about why nodes jump back after release and the main point to bring from there is that, during force.drag behavior the previous node positions, (d.px, d.py) and the current positions (d.x, d.y) are actually reversed. So, when the drag is released, the initial velocity is therefor reversed, causing the jump back behavior.
This is actually due to the drag behavior updating the previous position on drag events and the internal force.tick method copying the previous values onto the current values each position calculation. (I'm sure there is a good reason for this by the way, I suspect it is related to this...)
Inertial dragging
In order to implement inertial dragging, this velocity reversal needs to be corrected, so the current and previous points need to be reversed immediately after dragend.
This is a good start, but a couple of other problems remain:
Velocity state is lost every tick when previous position is copied to the current position.
"Sticky node" behavior (on mouseover) is re-established on dragend which tends to re-capture the nodes and defeat the inertial effect.
The first one means that if a tick occurs between releasing the drag and correcting the velocity, i.e. immediately after dragend, then the velocity will be zero and the node will stop dead. This happens often enough to be annoying. One solution is to maintain a record of d3.event.dx and d3.event.dy and use these to modify (d.px, d.py) on dragend. This also avoids the problem caused by the reversal of previous and current points.
The second remaining problem can be fixed by delaying the sticky node behavior reinstatement until after mouseout. A small delay after mouseout is advisable in case the mouse immediately re-enters the node after mouseout.
Implimentation
The basic strategy to implement the above two corrections is to hook the drag events in the force layout in the former and the mouse events in the force layout in the later. For defensive reasons, the standard callbacks for the various hooks are stored on the datum object of the nodes and retrieved from there when un-hooking.
The friction parameter is set to 1 in the code which means they maintain their velocity indefinitely, to see a steady inertial effect set it to 0.9... I jest like the bouncing balls.
$(function() {
var width = 1200,
height = 800;
var circles = [{
x: width / 2 + 100,
y: height / 2,
radius: 100
}, {
x: width / 2 - 100,
y: height / 2,
radius: 100
}, ],
nodeFill = "#006E3C";
var force = d3.layout.force()
.gravity(0)
.charge(-100)
.friction(1)
.size([width, height])
.nodes(circles)
.linkDistance(250)
.linkStrength(1)
.on("tick", tick)
.start();
SliderControl("#frictionSlider", "friction", force.friction, [0, 1], ",.3f");
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.style("background-color", "white");
var nodes = svg.selectAll(".node");
nodes = nodes.data(circles);
nodes.exit().remove();
var enterNode = nodes.enter().append("g")
.attr("class", "node")
.call(force.drag);
console.log(enterNode);
//Add circle to group
enterNode.append("circle")
.attr("r", function(d) {
return d.radius;
})
.style("fill", "#006E3C")
.style("opacity", 0.6);
;
(function(d3, force) {
//Drag behaviour///////////////////////////////////////////////////////////////////
// hook drag behavior on force
//VELOCITY
// maintain velocity state in case a force tick occurs emidiately before dragend
// the tick wipes out the previous position
var dragVelocity = (function() {
var dx, dy;
function f(d) {
if (d3.event) {
dx = d3.event.dx;
dy = d3.event.dy;
}
return {
dx: dx,
dy: dy
}
};
f.correct = function(d) {
//tick occured and set px/y to x/y, re-establish velocity state
d.px = d.x - dx;
d.py = d.y - dy;
}
f.reset = function() {
dx = dy = 0
}
return f;
})()
//DRAGSTART HOOK
var stdDragStart = force.drag().on("dragstart.force");
force.drag().on("dragstart.force", myDragStart);
function myDragStart(d) {
var that = this,
node = d3.select(this);
nonStickyMouse();
dragVelocity.reset();
stdDragStart.call(this, d)
function nonStickyMouse() {
if (!d.___hooked) {
//node is not hooked
//hook mouseover/////////////////////////
//remove sticky node on mouseover behavior and save listeners
d.___mouseover_force = node.on("mouseover.force");
node.on("mouseover.force", null);
d.___mouseout_force = node.on("mouseout.force");
d.___hooked = true;
//standard mouseout will clear d.fixed
d.___mouseout_force.call(that, d);
}
//dissable mouseout/////////////////////////
node.on("mouseout.force", null);
}
}
//DRAG HOOK
var stdDrag = force.drag().on("drag.force");
force.drag().on("drag.force", myDrag);
function myDrag(d) {
var v, p;
//maintain back-up velocity state
v = dragVelocity();
p = {
x: d3.event.x,
y: d3.event.y
};
stdDrag.call(this, d)
}
//DRAGEND HOOK
var stdDragEnd = force.drag().on("dragend.force");
force.drag().on("dragend.force", myDragEnd);
function myDragEnd(d) {
var that = this,
node = d3.select(this);
//correct the final velocity vector at drag end
dragVelocity.correct(d)
//hook mouseout/////////////////////////
//re-establish standard behavior on mouseout
node.on("mouseout.force", function mouseout(d) {
myForceMouseOut.call(this, d)
});
stdDragEnd.call(that, d);
function myForceMouseOut(d) {
var timerID = window.setTimeout((function() {
var that = this,
node = d3.select(this);
return function unhookMouseover() {
//if (node.on("mouseover.force") != d.___mouseout_force) {
if (node.datum().___hooked) {
//un-hook mouseover and mouseout////////////
node.on("mouseout.force", d.___mouseout_force);
node.on("mouseover.force", d.___mouseover_force);
node.datum().___hooked = false;
}
}
}).call(this), 500);
return timerID;
}
}
})(d3, force);
function tick(e) {
//contain the nodes...
nodes.attr("transform", function(d) {
var r = 100;
if (d.x - r <= 0 && d.px > d.x) d.px -= (d.px - d.x) * 2;
if (d.x + r >= width && d.px < d.x) d.px += (d.x - d.px) * 2;
if (d.y - r <= 0 && d.py > d.y) d.py -= (d.py - d.y) * 2;
if (d.y + r >= height && d.py < d.y) d.py += (d.y - d.py) * 2;
return "translate(" + d.x + "," + d.y + ")";
});
//indicate status by color
nodes.selectAll("circle")
.style("fill", function(d, i) {
return ((d.___hooked && !d.fixed) ? "red" : nodeFill)
})
force.start();
}
function SliderControl(selector, title, value, domain, format) {
var accessor = d3.functor(value),
rangeMax = 1000,
_scale = d3.scale.linear().domain(domain).range([0, rangeMax]),
_$outputDiv = $("<div />", {
class: "slider-value"
}),
_update = function(value) {
_$outputDiv.css("left", 'calc( ' + (_$slider.position().left + _$slider.outerWidth()) + 'px + 1em )')
_$outputDiv.text(d3.format(format)(value));
$(".input").width(_$outputDiv.position().left + _$outputDiv.outerWidth() - _innerLeft)
},
_$slider = $(selector).slider({
value: _scale(accessor()),
max: rangeMax,
slide: function(e, ui) {
_update(_scale.invert(ui.value));
accessor(_scale.invert(ui.value)).start();
}
}),
_$wrapper = _$slider.wrap("<div class='input'></div>")
.before($("<div />").text(title + ":"))
.after(_$outputDiv).parent(),
_innerLeft = _$wrapper.children().first().position().left;
_update(_scale.invert($(selector).slider("value")))
};
});
body {
/*font-family: 'Open Sans', sans-serif;*/
font-family: 'Roboto', sans-serif;
}
svg {
outline: 1px solid black;
background-color: rgba(255, 127, 80, 0.6);
}
div {
display: inline-block;
}
#method,
#clear {
margin-left: 20px;
background-color: rgba(255, 127, 80, 0.6);
border: none;
}
#clear {
float: right;
}
#inputs {
font-size: 16px;
display: block;
width: 900px;
}
.input {
display: inline-block;
background-color: rgba(255, 127, 80, 0.37);
outline: 1px solid black;
position: relative;
margin: 10px 10px 0 0;
padding: 3px 10px;
}
.input div {
width: 60px;
}
.method {
display: block;
}
.ui-slider,
span.ui-slider-handle.ui-state-default {
width: 3px;
background: black;
border-radius: 0;
}
span.ui-slider-handle.ui-state-default {
top: calc(50% - 1em / 2);
height: 1em;
margin: 0;
border: none;
}
div.ui-slider-horizontal {
width: 200px;
margin: auto 10px auto 10px;
/*position: absolute;*/
/*bottom: 0.1em;*/
position: absolute;
bottom: calc(50% - 2.5px);
/*vertical-align: middle;*/
height: 5px;
border: none;
}
.slider-value {
position: absolute;
text-align: right;
}
input,
select,
button {
font-family: inherit;
font-size: inherit;
}
<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="inputs">
<div id="frictionSlider"></div>
</div>

Related

multiple numberDisplays from group

With the following data, how can I bin by the class column (high, middle, low) and total the values
and display each total with its own dc.numberDisplay in this form?
Well, this is pretty close.
Because the number display only displays one number, we need to generate a div for each value we want to display.
First, the usual crossfilter stuff, creating a group that bins by class:
var data = d3.csv.parse(d3.select('pre#data').text());
data.forEach(function(d) {
d.value = +d.value;
})
var cf = crossfilter(data);
var classDim = cf.dimension(function(d) { return d.class; });
var classGroup = classDim.group().reduceSum(function(d) { return d.value; });
Now we're going to need to pull the individual values out as if they were different groups. We can create a "fake group" that pulls an individual value by index. Note: this won't work if the number of groups changes over time. But that usually doesn't happen.
function subgroup(group, i) {
return {
value: function() {
return group.all()[i];
}
};
}
Now we need to know the index for each bin we're interested in:
var bins = ['high', 'middle', 'low'];
var indices = {};
classGroup.all().forEach(function(kv, i) {
if(bins.indexOf(kv.key) >= 0)
indices[kv.key] = i;
})
We'll build a div for each of those bins, coloring it and adding a heading based on the name of the bin, and using an array of colors for the background color:
var colors = ['rgb(219,41,0)', 'rgb(255,143,31)', 'rgb(255,198,82)'];
var display = d3.select('#numbers').selectAll('div').data(bins);
display.enter().append('div')
.attr('class', 'display')
.style('background-color', function(d, i) {
return colors[i];
})
.append('span')
.attr('class', 'heading')
.text(function(d) { return d.toUpperCase(); });
Here's the CSS to get it to look approximately as you showed above:
.display {
min-width: 100px;
min-height: 100px;
text-align: center;
vertical-align: middle;
font: 36pt sans-serif;
line-height: 100px;
position: relative;
color: white;
}
.display span.heading {
position: absolute;
top: 4px;
left: 0;
right: 0;
margin: auto;
font: 8pt sans-serif;
color: white;
}
Now, finally, we can actually generate the numberDisplay widgets for each of those divs. This is the easy part!
display.each(function(d) {
var numberDisplay = dc.numberDisplay(this)
.group(subgroup(classGroup, indices[d]))
.formatNumber(d3.format('d'));
});
dc.renderAll();
Screenshot below.
And the fiddle: http://jsfiddle.net/aw9h8d93/9/

Data representation of partition of a rectangle?

I have a rectangle. I want to split it into a number of nonoverlapping smaller rectangles. Any good data structure to represent the partition?
Ok this could be interesting, how about:
var w = $('#rect').width();
var h = $('#rect').height();
var ratio = h/w;
var splitAcross = 10;//Ten divisions squared *Changeable*
var splitW = w/splitAcross;
var splitH = ratio*splitW;//Split across and down
var divRects = document.createElement("div"); // Create with DOM
divRects.style.width = splitW+'px';
divRects.style.height = splitH+'px';
var span = 0;
var spl = 0;
while (span < splitAcross)
{
spl++;
var cln = divRects.cloneNode(true);
if (splitW*spl % w == 0) span++;
$("#rect").append(cln);
}
//Removeable*** this is just to show divisions
$("#rect div").each(function (){
$(this).animate({
left: ($(this).offset().left-(w/2))*0.1,
top: ($(this).offset().top-(h/2))*0.1
}, 1000).animate({
left: 0,
top: 0
}, 1000);
});
//Removeable
#rect div {
border: 1px solid #eee;
margin: -1px;
float: left;
position: relative;
background:#5fba7d;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="rect" style="width:450px; height:200px"></div>
<!--width:450px; height:200px *Changeable*-->

How to fix ckeditor toolbar on the screen?

I have few ckeditor-s on my page, editors work in iframe mode, they are not inline. Each of them has autogrow option turned on. So sometimes content of the editor is higher than screen and toolbar is not visible. This obviously creates usability issues for people working with editor.
To solve this I would like to keep toolbar of currently active editor on the screen. The only problem I am not sure where should I start.
Few things I've figured out already:
1) It cannot be solved with CSS-only, as long as I need toolbar be fixed only for active editor and when its toolbar is not on the screen
2) I would rather create some CKeditor plugin than creating external code that controls scroll position and moves cke_toolbox basing on that.
What would you suggest?
I think I found solution that works for me.
JS code (updated):
$(function () {
if (typeof CKEDITOR === 'undefined') {
return;
}
var floatingClass = 'floatingToolbox';
var $editors;
CKEDITOR.on('instanceReady', function (e) {
$editors = $('.cke', e.element);
e.editor.on('focus',function() {
checkToolbars($editors, floatingClass);
$(window).on('scroll.ckeditor', function () {
checkToolbars($editors, floatingClass);
});
});
e.editor.on('blur', function () {
$(window).unbind('scroll.ckeditor');
$('.cke_toolbox', $editors).removeClass(floatingClass);
});
});
});
function checkToolbars($editors, floatingClass) {
if (!$editors)
return;
var editor = $editors.filter('.cke_focus');
if (editor.length == 0)
return;
var toolbox = $('.cke_toolbox', editor);
var offset = editor.offset();
var top = offset.top;
var bottom = offset.top + editor.height() - 55;
var scrollPosition = $(window).scrollTop();
if (top < scrollPosition && bottom > scrollPosition) {
toolbox.addClass(floatingClass).css(
{
left: (offset.left + 1) + 'px',
width: editor.width() + 'px'
});
} else if (toolbox.hasClass(floatingClass)) {
toolbox.removeClass(floatingClass);
}
}
CSS:
.floatingToolbox {
background-color: #cce4fb !important;
background-image: -webkit-gradient(linear, left top, left bottom, from(#f9fcfe), to(#cce4fb)) !important;
background-image: -moz-linear-gradient(top, #f9fcfe, #cce4fb) !important;
background-image: -webkit-linear-gradient(top, #f9fcfe, #cce4fb) !important;
background-image: -o-linear-gradient(top, #f9fcfe, #cce4fb) !important;
background-image: -ms-linear-gradient(top, #f9fcfe, #cce4fb) !important;
background-image: linear-gradient(top, #f9fcfe, #cce4fb) !important;
border-bottom: 1px solid #b7cde1 !important;
border-top: 1px solid #b7cde1 !important;
box-sizing: border-box;
display: block;
padding: 5px 5px 0 5px !important;
position: fixed;
top: 29px;
z-index: 10000;
}
Made a plugin for this purpose.
Create sticky/plugin.js in CKEditor plugins folder.
Enable the plugin adding the following code inside your config.js.
plugin.js
CKEDITOR.plugins.add( 'sticky', {
init: function( editor ) {
setToolbars();
['scroll', 'click'].forEach(function(e) {
window.addEventListener(e, function(){
setToolbars();
}, false);
});
editor.on('contentDom', function () {
var editable = editor.editable();
editable.attachListener(editable, 'click', function () {
setToolbars();
});
});
function setToolbars() {
document.querySelectorAll(".cke").forEach(function(editor) {
let inner = editor.querySelector('.cke_inner'),
content = editor.querySelector('.cke_contents'),
toolbar = editor.querySelector('.cke_top');
let scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
function getCoords(elem) { // crossbrowser version
let box = elem.getBoundingClientRect(),
body = document.body,
docEl = document.documentElement,
scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop,
scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft,
clientTop = docEl.clientTop || body.clientTop || 0,
clientLeft = docEl.clientLeft || body.clientLeft || 0,
top = box.top + scrollTop - clientTop,
left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
}
inner.style.position = "relative";
toolbar.style.position = "absolute";
toolbar.style.width = "100%";
toolbar.style.left = "0";
toolbar.style.margin = "0";
toolbar.style.boxSizing = "border-box";
let editorTop = getCoords(editor).top;
let editorHeight = editor.offsetHeight;
let toolbarHeight = toolbar.offsetHeight;
content.style.paddingTop = toolbarHeight+"px";
let contentHeight = content.offsetHeight;
let editorBorderTopWidth = parseFloat(getComputedStyle(editor).borderTopWidth);
if((scrollTop > editorTop) && (scrollTop < (editorTop+contentHeight-toolbarHeight))) {
toolbar.style.top = (scrollTop-editorTop-editorBorderTopWidth) + "px";
} else if (scrollTop >= (editorTop+contentHeight-toolbarHeight)) {
toolbar.style.top = (contentHeight-toolbarHeight-editorBorderTopWidth) + "px";
} else {
toolbar.style.top = "0";
}
});
}
}
});
Also created plugin page on GitHub.
I faced the same issue,
if you have header then below css will also help
#media only screen and (max-width: 767px) {
.ck-sticky-panel__content {
top: 180px !important;
}
}
#media only screen and (min-width: 768px) {
.ck-sticky-panel__content {
top: 128px !important;
}
}
DcpCkEditor.create(document.querySelector(`#${editorId}`), {
...editorConfiguration,
initialData,
}).then((editor: any) => {
document.getElementById('main')?.addEventListener('scroll', () => {
setTimeout(() => {
// eslint-disable-next-line no-underscore-dangle
editor.ui.view.stickyPanel._checkIfShouldBeSticky()
}, 100)
})
})

Mouse hover applied to jQPLOT X AXIS TICKS

I am trying to add mouse hover function to my axis ticks.
what i want is to show long ticks full text only on hover else it would be showing only few characters . i am adding a hover event to .jqplot-xaxis-tick .But it doesnot even respond to hover.Please suggest !
.jqplot-xaxis-tick {
top: 0px;
/* initial position untill tick is drawn in proper place */
left: 15px;
/* padding-top: 10px;*/
vertical-align: top;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.jqplot-xaxis-tick :hover{
overflow:visible;
white-space: normal;
width: auto;
position: absolute;
background-color:yellow;
}
The hover is not detecting because of the z-index of the canvas which lies on top of the whole chart. I did the following and now it's shorten the tootip by CSS ellipsis and show the tooltip with full name on hover.
Based on the Gyandeep's answer, the exact JS and CSS I used are,
Javascript:
$('div.jqplot-xaxis-tick').each(function (i, obj) {
$(this).prop('title', ($(this).text()));
$(this).css('z-index', 999); // this is important otherwise mouseover won't trigger.
});
CSS:
.jqplot-xaxis .jqplot-xaxis-tick {
position: absolute;
white-space: pre;
max-width: 92px; // Change it according to your need
overflow: hidden;
text-overflow: ellipsis;
}
The JavaScript part needs to be executed after every rendering of chart. It's better to put them right after plotting the chart and may in the AJAX success handler.
I managed to add a tooltip kindof feature to axis ticks.When I hover upon them it shows a separate box with full text else only 3-4 characters are shown.
The code is something like this
$($('.jqplot-xaxis-tick')[i]).bind('mouseover', function () {
// var m= '-webkit-marquee';
$($('.jqplot-xaxis-tick')[i]).css('white-space','pre-line');
$($('.jqplot-xaxis-tick')[i]).css('overflow','visible');
$($('.jqplot-xaxis-tick')[i]).css('width','auto');
$($('.jqplot-xaxis-tick')[i]).css('position','absolute');
$($('.jqplot-xaxis-tick')[i]).css('background-color','#666666');
$($('.jqplot-xaxis-tick')[i]).css('color','white');
$($('.jqplot-xaxis-tick')[i]).css('top','-45px');
// $($('.jqplot-xaxis-tick')[i]).css('overflow-x',m);
// console.log($($('.jqplot-xaxis-tick')[i]).text());
}).bind('mouseout', function () {
//var m= '';
//$($('.jqplot-xaxis-tick')[i]).css('overflow-x',m);
$($('.jqplot-xaxis-tick')[i]).css('white-space','nowrap');
$($('.jqplot-xaxis-tick')[i]).css('overflow','hidden');
$($('.jqplot-xaxis-tick')[i]).css('width','50');
$($('.jqplot-xaxis-tick')[i]).css('background-color','');
$($('.jqplot-xaxis-tick')[i]).css('color','grey');
$($('.jqplot-xaxis-tick')[i]).css('top','0px');
});
Here's the solution I'm using to display monthly high and low temperatures. Hovering over the x-axis tick will display an alert with the active month name and temps.
// Set up hover function
$('#monthlyTemps .jqplot-xaxis-tick').hover(function () {
setActiveColumn($(this));
});
// Set active column
function setActiveColumn(sender) {
// Display values
var monthName = sender.text();
var monthIndex = monthNames.indexOf(monthName);
var monthLowTemp = getMonthLowTemp(monthIndex);
var monthHighTemp = getMonthlHighTemp(monthIndex);
alert(monthName + ': ' + monthLowTemp + ' / ' + monthHighTemp);
}
// Get month low temp
function getMonthLowTemp(monthIndex) {
var value= $("#monthlyTemps .jqplot-point-label.jqplot-series-0.jqplot-point-" + monthIndex).text();
return value;
}
// Get month high temp
function getMonthlyHighTemp(monthIndex) {
var value= $("#monthlyTemps .jqplot-point-label.jqplot-series-1.jqplot-point-" + monthIndex).text();
return value;
}
var monthNames = new Array(12);
monthAbbreviations[0] = "Jan";
monthAbbreviations[1] = "Feb";
monthAbbreviations[2] = "Mar";
monthAbbreviations[3] = "Apr";
monthAbbreviations[4] = "May";
monthAbbreviations[5] = "Jun";
monthAbbreviations[6] = "Jul";
monthAbbreviations[7] = "Aug";
monthAbbreviations[8] = "Sep";
monthAbbreviations[9] = "Oct";
monthAbbreviations[10] = "Nov";
monthAbbreviations[11] = "Dec";

Full screen scaling background image overlays

I have a page whose background image scales as the browser re-sizes. What I'm trying to figure out is how to make the blue marker image scale and maintain position in proportion to the background. For example, if the blue marker were on the tip of the harbor, as I scaled the browser down, I'd want that marker to stay on the tip of the harbor and shrink in size proportionally with the browser's new dimensions.
Anyone have any ideas that will point me in the right direction?
I used the below method to find the browser size, but I think my math's a little off. You can view the live example here:
http://mikeheavers.com/stage/full_screen_map/
The marker gets really off if you scale it in one direction more than the other.
<style type="text/css">
html {
background: url(images/antigua.jpeg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
#infoDiv {
background: white;
padding: 20px;
width: 200px;
position:absolute;
}
#map_canvas {
position:absolute ;
}
.marker {
position:absolute;
left: 800px;
top: 400px;
height: 20px;
width: 20px;
background: #ff0000;
}
</style>
<script type="text/javascript" charset="utf-8" src="js/jquery-1.4.4.min.js"></script>
<div id="infoDiv">
</div>
<div id="markers">
<div class="marker">
</div>
</div>
<script type="text/javascript">
$(document).ready(function()
{
var myWidth = window.innerWidth;
var leftVal = $("#markers").children().css('left');
var leftValNumber = parseFloat(leftVal);
var leftRatio = leftValNumber / myWidth;
var leftValPos;
var myHeight = window.innerHeight;
var topVal = $("#markers").children().css('top');
var topValNumber = parseFloat(topVal);
var topRatio = topValNumber / myHeight;
var topValPos;
var scaleRatio;
if (myWidth > myHeight){
scaleRatio = 20/myWidth;
} else {
scaleRatio = 20/myHeight;
}
window.onresize = function() {
sizeMarkers();
}
function init()
{
sizeMarkers();
}
function sizeMarkers()
{
if( typeof( window.innerWidth ) == 'number' ) {
//Non-IE
myWidth = window.innerWidth;
myHeight = window.innerHeight;
} else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
//IE 6+ in 'standards compliant mode'
myWidth = document.documentElement.clientWidth;
myHeight = document.documentElement.clientHeight;
} else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
//IE 4 compatible
myWidth = document.body.clientWidth;
myHeight = document.body.clientHeight;
}
topVal = $("#markers").children().css('top');
topValNumber = parseFloat(topVal);
topValPos = topRatio * myHeight;
leftVal = $("#markers").children().css('top');
leftValNumber = parseFloat(leftVal);
leftValPos = leftRatio * myWidth;
if (myWidth < myHeight){
$("#markers").children().width(myWidth*scaleRatio);
$("#markers").children().height(myWidth*scaleRatio);
} else {
$("#markers").children().width(myHeight*scaleRatio);
$("#markers").children().height(myHeight*scaleRatio);
}
$("#markers").children().css('top',topValPos);
$("#markers").children().css('left',leftValPos);
$("#infoDiv").html( 'Width = ' + myWidth + ' | Height = ' + myHeight + ' | Top Value: ' + topValPos + ' | Left Value: ' + leftValPos);
}
init();
});
</script>
You could adjust width of your marker using JavaScript by keeping at any onresize events the same ratio marker-width and page-width.
To know your viewport width :
http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
Hope it helps.

Resources