Related
I'm a newbie to the web development world, so please pardon me if I miss something here and there or if I describe the issue incorrectly.
GIF illustrating the issue
Basically, I copied different code from TradingView's two or more chart examples into one JSFiddle and tried to make a chart that could show candlesticks of different time frames with the symbol name, OHLCV, MA10 values. Everything works fine when the chart loads for the first time but when click the button to change the time frame, all the candles and MA10 line loads fine but the OHLCV and MA10 values seem like being put on the old (initially loaded) data.
When I tried it for the first time all the candles and MA10 lines were also overlapping then I figured that the whole chart has to be removed so that MA10's values will be re-calculated. So now the candlesticks and MA10 are loading fine but OHLCV values are still overlapping on the previous values. Since I'm new to JS I can't figure out the right keywords for my problem so I'm not getting results that address my problem.
Could somebody help me to figure out what's going wrong?
Thank you.
I had to remove most of historic data to comply with Stackoverflow's limit of 30000 chars per post, so snippet below will not work propely. You can take a look at the code here https://jsfiddle.net/akshay7892/dhutrgfn/7/
function createSimpleSwitcher(items, activeItem, activeItemChangedCallback) {
var switcherElement = document.createElement('div');
switcherElement.classList.add('switcher');
var intervalElements = items.map(function(item) {
var itemEl = document.createElement('button');
itemEl.innerText = item;
itemEl.classList.add('switcher-item');
itemEl.classList.toggle('switcher-active-item', item === activeItem);
itemEl.addEventListener('click', function() {
onItemClicked(item);
});
switcherElement.appendChild(itemEl);
return itemEl;
});
function onItemClicked(item) {
if (item === activeItem) {
return;
}
intervalElements.forEach(function(element, index) {
element.classList.toggle('switcher-active-item', items[index] === item);
});
activeItem = item;
activeItemChangedCallback(item);
}
return switcherElement;
}
function calculateSMA(data, count){
var avg = function(data) {
var sum = 0;
for (var i = 0; i < data.length; i++) {
sum += data[i].close;
}
return sum / data.length;
};
var result = [];
for (var i=count - 1, len=data.length; i < len; i++){
var val = avg(data.slice(i - count + 1, i));
result.push({ time: data[i].time, value: val});
}
//console.log(result)
return result;
}
function calopen(data){
var open = [];
for (var i = 0; i < data.length; i++) {
open.push({ time: data[i].time, value: data[i].open});
}
return open;
}
//console.log(calopen(data))
function calhigh(data){
var high = [];
for (var i = 0; i < data.length; i++) {
high.push({ time: data[i].time, value: data[i].high});
}
return high;
}
function callow(data){
var low = [];
for (var i = 0; i < data.length; i++) {
low.push({ time: data[i].time, value: data[i].low});
}
return low;
}
function calclose(data){
var close = [];
for (var i = 0; i < data.length; i++) {
close.push({ time: data[i].time, value: data[i].close});
}
return close;
}
function calv(data){
var vol = [];
for (var i = 0; i < data.length; i++) {
vol.push({ time: data[i].time, value: data[i].volume});
}
return vol;
}
var one_minData = [{"time":1650350460,"open":329.35,"high":329.45,"low":329.1,"close":329.45,"volume":9795},{"time":1650350520,"open":329.45,"high":329.5,"low":329.15,"close":329.15,"volume":20626},{"time":1650350580,"open":329.15,"high":329.45,"low":329.15,"close":329.45,"volume":8762},{"time":1650350640,"open":329.5,"high":330.2,"low":329.25,"close":330.05,"volume":56546},{"time":1650350700,"open":330.15,"high":330.15,"low":329.55,"close":329.8,"volume":23489},{"time":1650350760,"open":329.8,"high":329.9,"low":329.6,"close":329.75,"volume":20630},]
var fifteen_minData = [{"time":1576728900,"open":509.8,"high":512.6,"low":508.9,"close":511.05,"volume":210630},{"time":1576729800,"open":511.0,"high":511.5,"low":509.35,"close":510.25,"volume":109974},{"time":1576730700,"open":510.25,"high":510.25,"low":506.2,"close":507.65,"volume":177816},]
var dayData = [{"time":1564963200,"open":460.0,"high":475.9,"low":455.85,"close":471.95,"volume":1465110},{"time":1565049600,"open":472.0,"high":487.5,"low":468.25,"close":482.15,"volume":795823},{"time":1565136000,"open":484.0,"high":489.95,"low":474.0,"close":477.25,"volume":312625},]
var weekData =
[{"time":1529884800,"open":538.35,"high":542.0,"low":508.0,"close":526.55,"volume":590970},{"time":1530489600,"open":530.0,"high":567.8,"low":523.0,"close":544.3,"volume":550127},{"time":1531094400,"open":548.8,"high":588.8,"low":544.5,"close":568.95,"volume":1021330},{"time":1531699200,"open":558.25,"high":706.8,"low":544.55,"close":687.4,"volume":3131754}{"time":1566777600,"open":484.0,"high":497.65,"low":455.15,"close":469.6,"volume":775278},];
var intervals = ['1','15','1D', '1W'];
var seriesesData = new Map([
['1', one_minData ],
['15', fifteen_minData ],
['1D', dayData ],
['1W', weekData ],
]);
var switcherElement = createSimpleSwitcher(intervals, intervals[0], syncToInterval);
document.body.style.position = 'relative';
var container = document.createElement('div');
document.body.appendChild(container);
document.body.appendChild(switcherElement);
var width = 600;
var height = 300;
var candleSeries = null;
function syncToInterval(interval) {
if (candleSeries) {
chart.remove();
chart.remove(legend);
//req_data = null;
}
chart = LightweightCharts.createChart(container, {
width: width,
height: height,
crosshair: {
mode: LightweightCharts.CrosshairMode.Normal,
},
timeScale: {
borderVisible: true,
timeVisible: true,
secondsVisible: false,},
});
candleSeries = chart.addCandlestickSeries();
req_data = seriesesData.get(interval)
//console.log(data);
candleSeries.setData(req_data);
////charts time, volume, color data////////
var smaLine = chart.addLineSeries({
color: 'rgba(4, 111, 232, 1)',
lineWidth: 2,
});
var openLine = chart.addLineSeries({
color: 'rgba(4, 111, 232, 1)',
visible: false,
lineWidth:0,
});
var highLine = chart.addLineSeries({
color: 'rgba(4, 111, 232, 1)',
visible: false,
lineWidth:0,
});
var lowLine = chart.addLineSeries({
color: 'rgba(4, 111, 232, 1)',
visible: false,
lineWidth:0,
});
var closeLine = chart.addLineSeries({
color: 'rgba(4, 111, 232, 1)',
visible: false,
lineWidth:0,
});
var volumeSeries = chart.addHistogramSeries({
color: '#26a69a',
priceFormat: {
type: 'volume',
},
priceScaleId: '',
scaleMargins: {
top: 0.8,
bottom: 0,
},
});
var volLine = chart.addLineSeries({
color: 'rgba(4, 111, 232, 1)',
visible: false,
lineWidth:5,
});
function setLegend6Text(fysymbol) {
legend6.innerHTML = '<span style=style="font-size: 24px; margin: 4px 0px; color: #20262E">' + fysymbol + '</span>';
//console.log("name",fysymbol);
}
function setLegendText(priceValue) {
let val = 'n/a';
if (priceValue !== undefined) {
val = (Math.round(priceValue * 100) / 100).toFixed(2);
}
legend.innerHTML = '<br/><br/>MA10: <span style="color:rgba(4, 111, 232, 1); margin: 4px 0px;">' + val + '</span>';
}
function setLegendText1(openValue) {
let openVal = 'n/a';
if (openValue !== undefined) {
openVal = (Math.round(openValue * 100) / 100).toFixed(2);
}
legend1.innerHTML = '<br/>O: <span style="color:rgba(4, 111, 232, 1); margin: 4px 0px;">' + openVal + '</span>';
}
function setLegendText2(highValue) {
let highVal = 'n/a';
if (highValue !== undefined) {
highVal = (Math.round(highValue * 100) / 100).toFixed(2);
}
legend2.innerHTML = '<br/>H: <span style="color:rgba(4, 111, 232, 1); margin: 4px 0px;">' + highVal + '</span>';
}
function setLegendText3(lowValue) {
let lowVal = 'n/a';
if (lowValue !== undefined) {
lowVal = (Math.round(lowValue * 100) / 100).toFixed(2);
}
legend3.innerHTML = '<br/>L: <span style="color:rgba(4, 111, 232, 1); margin: 4px 0px;">' + lowVal + '</span>';
}
function setLegendText4(closeValue) {
let closeVal = 'n/a';
if (closeValue !== undefined) {
closeVal = (Math.round(closeValue * 100) / 100).toFixed(2);
}
legend4.innerHTML = '<br/>C: <span style="color:rgba(4, 111, 232, 1); margin: 4px 0px;">' + closeVal + '</span>';
}
function setLegendText5(volValue) {
let volVal = 'n/a';
if (volValue !== undefined) {
volVal = (Math.round(volValue * 100) / 100).toFixed(2);
}
legend5.innerHTML = '<br/>V: <span style="color:rgba(4, 111, 232, 1); margin: 4px 0px;">' + (volVal/100000) + 'L' + '</span>';
}
//console.log(lowdata)
var legend = document.createElement('div');
legend.className = 'sma-legend';
container.appendChild(legend);
legend.style.display = 'block';
legend.style.left = 3 + 'px';
legend.style.top = 20 + 'px';
var legend1 = document.createElement('div');
legend1.className = 'OHLC-legend';
container.appendChild(legend1);
legend1.style.display = 'block';
legend1.style.left = 3 + 'px';
legend1.style.top = 10 + 'px';
var legend2 = document.createElement('div');
legend2.className = 'OHLC-legend';
container.appendChild(legend2);
legend2.style.display = 'block';
legend2.style.left = 78 + 'px';
legend2.style.top = 10 + 'px';
var legend3 = document.createElement('div');
legend3.className = 'OHLC-legend';
container.appendChild(legend3);
legend3.style.display = 'block';
legend3.style.left = 153 + 'px';
legend3.style.top = 10 + 'px';
var legend4 = document.createElement('div');
legend4.className = 'OHLC-legend';
container.appendChild(legend4);
legend4.style.display = 'block';
legend4.style.left = 228 + 'px';
legend4.style.top = 10 + 'px';
var legend5 = document.createElement('div');
legend5.className = 'V-legend';
container.appendChild(legend5);
legend5.style.display = 'block';
legend5.style.left = 303 + 'px';
legend5.style.top = 10 + 'px';
var legend6 = document.createElement('div');
legend6.className = 'fysymbol-legend';
container.appendChild(legend6);
legend6.style.display = 'block';
legend6.style.left = 3 + 'px';
legend6.style.top = 0 + 'px';
var fysymbol = "NSE:SBIN-EQ";
voldata = calv(req_data)
volumeSeries.setData(voldata)
volLine.setData(voldata);
smaData = calculateSMA(req_data, 10);
smaLine.setData(smaData);
opendata = calopen(req_data)
openLine.setData(opendata);
highdata = calhigh(req_data)
highLine.setData(highdata);
lowdata = callow(req_data)
lowLine.setData(lowdata);
closedata = calclose(req_data)
closeLine.setData(closedata);
var datesForMarkers = [req_data[req_data.length - 39], req_data[req_data.length - 19]];
var indexOfMinPrice = 0;
for (var i = 1; i < datesForMarkers.length; i++) {
if (datesForMarkers[i].high < datesForMarkers[indexOfMinPrice].high) {
indexOfMinPrice = i;
}
}
var markers = [{ time: req_data[req_data.length - 48].time, position: 'aboveBar', color: '#f68410', shape: 'circle', text: 'D' }];
for (var i = 0; i < datesForMarkers.length; i++) {
if (i !== indexOfMinPrice) {
markers.push({ time: datesForMarkers[i].time, position: 'aboveBar', color: '#e91e63', shape: 'arrowDown', text: 'Sell # ' + Math.floor(datesForMarkers[i].high - 1) });
} else {
markers.push({ time: datesForMarkers[i].time, position: 'belowBar', color: '#2196F3', shape: 'arrowUp', text: 'Buy # ' + Math.floor(datesForMarkers[i].low + 1) });
}
}
candleSeries.setMarkers(markers);
setLegendText(smaData[smaData.length - 1].value);
setLegendText1(opendata[opendata.length -1 ].value);
setLegendText2(highdata[highdata.length -1 ].value);
setLegendText3(lowdata[lowdata.length -1 ].value);
setLegendText4(closedata[closedata.length -1 ].value);
setLegendText5(voldata[voldata.length -1 ].value);
setLegend6Text(fysymbol);
chart.subscribeCrosshairMove((param) => {
setLegendText(param.seriesPrices.get(smaLine)),
setLegendText1(param.seriesPrices.get(openLine)),
setLegendText2(param.seriesPrices.get(highLine)),
setLegendText3(param.seriesPrices.get(lowLine)),
setLegendText4(param.seriesPrices.get(closeLine)),
setLegendText5(param.seriesPrices.get(volLine)),
setLegend6Text(fysymbol);
});
}
syncToInterval(intervals[0]);
html,
body {
font-family: 'Trebuchet MS', Roboto, Ubuntu, sans-serif;
background: #f9fafb;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.switcher {
display: flex;
align-items: center;
height: 30px;
margin-top:8px;
color: #2196F3;
}
.switcher-item {
cursor: pointer;
text-decoration: none;
display: inline-block;
padding: 6px 8px;
font-size: 14px;
color: #262b3e;
background-color: transparent;
margin-right: 8px;
border: none;
border-radius: 4px;
outline: none;
}
.switcher-item:hover {
background-color: #f2f3f5;
}
.switcher-active-item {
text-decoration: none;
cursor: default;
color: #262b3e;
}
.switcher-active-item,
.switcher-active-item:hover {
background-color: #e1eff9;
}
.sma-legend {
width: 96px;
height: 10px;
position: absolute;
padding: 8px;
font-size: 14px;
background-color: rgba(255, 255, 255, 0.23);
text-align: left;
z-index: 1000;
pointer-events: none;
white-space: normal;
}
.OHLC-legend {
width: 96px;
height: 10px;
position: absolute;
padding: 8px;
font-size: 16px;
background-color: rgba(255, 255, 255, 0.23);
text-align: left;
z-index: 1000;
pointer-events: none;
white-space: normal;
}
.V-legend {
width: 120px;
height: 70px;
position: absolute;
padding: 8px;
font-size: 16px;
background-color: rgba(255, 255, 255, 0.23);
text-align: left;
z-index: 1000;
pointer-events: none;
white-space: normal;
}
.fysymbol-legend {
width: 250px;
height: 70px;
position: absolute;
padding: 8px;
/*font-size: 20px;*/
background-color: rgba(255, 255, 255, 0.23);
text-align: left;
z-index: 1000;
pointer-events: none;
white-space: normal
}
<script src="https://unpkg.com/lightweight-charts#3.4.0/dist/lightweight-charts.standalone.production.js"></script>
First thing to look at when something's not working as expected (whatever your level!) is the dev console!
Here you can clearly see that each time you click on your button you're not only replacing an "old" value but instead adding one on top of the previous one(s).
I would recommend having an init function and/or components and an update function.
I've copied over code from an exciting website (https://romonysetra.no/tomtevelger/) to new website build (WP Divi). But I have no luck getting the code to work on the new site.https://romonysetra.no/divi/tomtevelger/
Any tips on what is wrong or what it not working on the new website build is greatly appreciated. Thanks!
<div id="mapid"></div>
<script type="text/javascript" src="https://romonysetra.no/tomteData.js"></script>
<script type="text/javascript">
var mymap = L.map('mapid', { zoomSnap:0,zoomDelta: 0.5}).setView([61.55794889870435, 12.220130837034901], 16);
L.tileLayer('https://opencache.statkart.no/gatekeeper/gk/gk.open_gmaps?layers=topo4&zoom={z}&x={x}&y={y}', {
attribution: 'Kartverket'}).addTo(mymap);
var info = L.control();
info.onAdd = function (mymap) {
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
this.update();
return this._div;
};
// method that we will use to update the control based on feature properties passed
info.update = function (props) {
this._div.innerHTML = '<h4>Informasjon</h4>' + (props ?
'<b>' + props.name + '</b><br/><br/>' + props.pris + '<br/>' + props.status + '<br/>' + props.areal
: 'Vis tomtedata');
};
info.addTo(mymap);
function getColor(d) {
return d > 1000 ? '#800026' :
d > 75 ? '#636363' :
d > 65 ? '#3182bd' :
d > 35 ? '#276897' :
d > 25 ? '#de2d26' :
d > 15 ? '#feb24c' :
d > 5 ? '#31a354' :
'#efedf5';
}
function style(feature) {
return {
weight: 2,
opacity: 1,
color: 'black',
dashArray: '3',
fillOpacity: 0.5,
fillColor: getColor(feature.properties.farge)
};
}
var geojsonTomt;
var geojsonVei;
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 2,
color: '#f0f0f0',
dashArray: '',
fillOpacity: 0.7
});
layer.bringToFront();
info.update(layer.feature.properties);
}
function resetHighlight(e) {
geojsonTomt.resetStyle(e.target);
info.update();
}
function zoomToFeature(e) {
mymap.fitBounds(e.target.getBounds());
}
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
geojsonTomt = L.geoJson(tomteData, {
style: style,
onEachFeature: onEachFeature
}).addTo(mymap);
geojsonVei = L.geoJson(veiData, {
style: style
}).addTo(mymap);
</script>
Found it. This was missing form the head of the page.
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.5.1/dist/leaflet.css"/>
<script src="https://unpkg.com/leaflet#1.5.1/dist/leaflet.js"></script>
<style>
#mapid { width: 1200px; height: 800px; }.info {
padding: 6px 8px;
font: 16px/18px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h4 {
margin: 0 0 5px;
color: #777;
}
.text-labels {
position: absolute;
font-size: 2em;
font-weight: 700;
/* Use color, background, set margins for offset, etc */
}
</style>
I'm trying to add markers and a search bar to my leaflet choropleth map. However, I keep running into an error that tells me: Uncaught TypeError: this.callInitHooks is not a function. It comes from my leaflet.js file, and am unsure how to fix it. Most of my error seems to come from the leaflet links I've copied into my body before my script, or because of me trying to add a search bar or markets onto my map. I pasted my code below:
<html>
<head>
<title>How to make a choropleth map with Leaflet.js</title>
<meta charset="utf-8">
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.0/dist/leaflet.css" />
<link rel="stylesheet" href="leaflet-search.css" />
<script src="censustracts.js"></script>
<style type="text/css">
html, body, #map{
height: 100%;
padding: 0;
margin: 0;
}
.info {
padding: 6px 8px;
font: 14px/16px Arial, Helvetica, sans-serif;
background: white;
background: rgba(255,255,255,0.8);
box-shadow: 0 0 15px rgba(0,0,0,0.2);
border-radius: 5px;
}
.info h4 {
margin: 0 0 5px;
color: #777;
}
.legend {
line-height: 18px;
color: #555;
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}
</style>
</head>
<body>
<div id="map"></map>
<script src="https://unpkg.com/leaflet#1.3.0/dist/leaflet.js"></script>
<script src="leaflet-search.js"></script>
<script type="text/javascript">
var map = L.map('map').setView([37.8, -96], 4);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: 'OSM'})
.addTo(map);
L.geoJson(statesData).addTo(map);
L.geoJson(statesData).addTo(map);
function getColor(d) {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
d > 10 ? '#FED976' :
'#FFEDA0';
}
function style(feature) {
return {
fillColor: getColor(feature.properties.density),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
}
L.geoJson(statesData, {style: style}).addTo(map);
///Functionality
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 5,
color: '#666',
dashArray: '',
fillOpacity: 0.7
});
info.update(layer.feature.properties);
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
}
function resetHighlight(e) {
geojson.resetStyle(e.target);
info.update();
}
function zoomToFeature(e) {
map.fitBounds(e.target.getBounds());
}
function onEachFeature(feature, layer) {
layer.on({
mouseover: highlightFeature,
mouseout: resetHighlight,
click: zoomToFeature
});
}
geojson = L.geoJson(statesData, {
style: style,
onEachFeature: onEachFeature
}).addTo(map);
//Add info
var info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
this.update();
return this._div;
};
// method that we will use to update the control based on feature properties passed
info.update = function (props) {
this._div.innerHTML = '<h4>US Population Density</h4>' + (props ?
'<b>' + props.name + '</b><br />' + props.density + ' people / mi<sup>2</sup>'
: 'Hover over a state');
};
info.addTo(map);
//Add Legend
var legend = L.control({position: 'bottomright'});
legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 10, 20, 50, 100, 200, 500, 1000],
labels = [];
// loop through our density intervals and generate a label with a colored square for each interval
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style="background:' + getColor(grades[i] + 1) + '"></i> ' +
grades[i] + (grades[i + 1] ? '–' + grades[i + 1] + '<br>' : '+');
}
return div;
};
legend.addTo(map);
///Bind Popups
var data = [
{"loc":[32,-86], "title":"aquamarine"},
{"loc":[41.575730,13.002411], "title":"black"},
{"loc":[41.807149,13.162994], "title":"blue"},
{"loc":[41.507149,13.172994], "title":"chocolate"},
{"loc":[41.847149,14.132994], "title":"coral"},
{"loc":[41.219190,13.062145], "title":"cyan"},
{"loc":[41.344190,13.242145], "title":"darkblue"},
{"loc":[41.679190,13.122145], "title":"Darkred"},
{"loc":[41.329190,13.192145], "title":"Darkgray"},
{"loc":[41.379290,13.122545], "title":"dodgerblue"},
{"loc":[41.409190,13.362145], "title":"gray"},
{"loc":[41.794008,12.583884], "title":"green"},
{"loc":[41.805008,12.982884], "title":"greenyellow"},
{"loc":[41.536175,13.273590], "title":"red"},
{"loc":[41.516175,13.373590], "title":"rosybrown"},
{"loc":[41.506175,13.273590], "title":"royalblue"},
{"loc":[41.836175,13.673590], "title":"salmon"},
{"loc":[41.796175,13.570590], "title":"seagreen"},
{"loc":[41.436175,13.573590], "title":"seashell"},
{"loc":[41.336175,13.973590], "title":"silver"},
{"loc":[41.236175,13.273590], "title":"skyblue"},
{"loc":[41.546175,13.473590], "title":"yellow"},
{"loc":[41.239190,13.032145], "title":"white"}
];
var markersLayer = new L.LayerGroup(); //layer contain searched elements
map.addLayer(markersLayer);
var controlSearch = L.Control.Search({
position:'topright',
layer: markersLayer,
initial: false,
zoom: 12,
marker: false
});
map.addControl( controlSearch );
for(i in data) {
var title = data[i].title, //value searched
loc = data[i].loc, //position found
marker = new L.Marker(new L.latLng(loc), {title: title} );//se property searched
marker.bindPopup('title: '+ title );
markersLayer.addLayer(marker);
}
</script>
</body>
</html>
Don't use new with lowercase function:
marker = new L.Marker(new L.latLng(loc), {title: title} );
Use new L.LatLng(loc) or L.latLng(loc) without new
I have a bunch of CSS and SVG elements that get placed into a div and I would like the user to be able to pan and zoom around to all of them. I am using the library from Jquery Panzoom. Is there a way to contain it so that if the user has an element outside of the viewport they will be able to pan to the edge of the container with all of the elements inside of it?
Below I will provide an example of what it's doing now. When it is all zoomed in and there is still an element not seen, I would like to be able to pan to the other element.
When Everything is seen, I would like it to not be able to pan but still zoom.
When it is zoomed all of the ways out, I would like the container to be the width of the viewport. The problem I am having is that if I set the container to be the width of the viewport at its most zoomed out level. It decides to hide below the viewport.
Example
Example using Panzoom
Code from codepen
Html
<div id="chart_container">
<div class="flowchart-example-container" id="example"></div>
</div>
<div class="draggable_operators">
<div class="draggable_operators_label">
Operators (drag and drop them in the flowchart):
</div>
<div class="draggable_operators_divs">
<div class="draggable_operator" data-nb-inputs="1" data-nb-outputs="0">1 input</div>
<div class="draggable_operator" data-nb-inputs="0" data-nb-outputs="1">1 output</div>
<div class="draggable_operator" data-nb-inputs="1" data-nb-outputs="1">1 input & 1 output</div>
<div class="draggable_operator" data-nb-inputs="1" data-nb-outputs="2">1 in & 2 out</div>
<div class="draggable_operator" data-nb-inputs="2" data-nb-outputs="1">2 in & 1 out</div>
<div class="draggable_operator" data-nb-inputs="2" data-nb-outputs="2">2 in & 2 out</div>
</div>
</div>
<button class="delete_selected_button">Delete selected operator / link</button>
CSS
body {
font-family: 'Helvetica Neue', Helvetica, Arial, serif;
font-size: 15px;
font-weight: 400;
line-height: 1.5;
color: #666;
}
#chart_container {
width: 100%;
height: 500px;
overflow: hidden;
background: repeating-linear-gradient( 45deg, #eee, #eee 10px, #e5e5e5 10px, #e5e5e5 20px);
border: 1px solid black;
}
.flowchart-example-container {
height: 200px;
border: 1px solid #BBB;
margin-bottom: 10px;
}
#example {
width: 2000px;
height: 2000px;
background: white;
}
.draggable_operators_label {
margin-bottom: 5px;
}
.draggable_operators_divs {
margin-bottom: 20px;
}
.draggable_operator {
display: inline-block;
padding: 2px 5px 2px 5px;
border: 1px solid #ccc;
cursor: grab;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
Javascript
$(document).ready(function() {
var $flowchart = $('#example');
var $container = $flowchart.parent();
var cx = $flowchart.width() / 2;
var cy = $flowchart.height() / 2;
// Panzoom initialization...
$flowchart.panzoom();
// Centering panzoom
$flowchart.panzoom('pan', -cx + $container.width() / 2, -cy + $container.height() / 2);
// Panzoom zoom handling...
var possibleZooms = [0.5, 0.75, 1, 2, 3];
var currentZoom = 2;
$container.on('mousewheel.focal', function(e) {
e.preventDefault();
var delta = (e.delta || e.originalEvent.wheelDelta) || e.originalEvent.detail;
var zoomOut = delta ? delta < 0 : e.originalEvent.deltaY > 0;
currentZoom = Math.max(0, Math.min(possibleZooms.length - 1, (currentZoom + (zoomOut * 2 - 1))));
$flowchart.flowchart('setPositionRatio', possibleZooms[currentZoom]);
$flowchart.panzoom('zoom', possibleZooms[currentZoom], {
animate: false,
focal: e
});
});
var data = {
operators: {
operator1: {
top: cy - 100,
left: cx - 200,
properties: {
title: 'Operator 1',
inputs: {},
outputs: {
output_1: {
label: 'Output',
}
}
}
},
operator2: {
top: cy,
left: cx + 140,
properties: {
title: 'Operator 2',
inputs: {
input_1: {
label: 'Input 1',
},
input_2: {
label: 'Input 2',
},
},
outputs: {}
}
},
},
links: {
link_1: {
fromOperator: 'operator1',
fromConnector: 'output_1',
toOperator: 'operator2',
toConnector: 'input_2',
},
}
};
// Apply the plugin on a standard, empty div...
$flowchart.flowchart({
data: data,
linkWidth: 5
});
$flowchart.parent().siblings('.delete_selected_button').click(function() {
$flowchart.flowchart('deleteSelected');
});
var $draggableOperators = $('.draggable_operator');
function getOperatorData($element) {
var nbInputs = parseInt($element.data('nb-inputs'));
var nbOutputs = parseInt($element.data('nb-outputs'));
var data = {
properties: {
title: $element.text(),
inputs: {},
outputs: {}
}
};
var i = 0;
for (i = 0; i < nbInputs; i++) {
data.properties.inputs['input_' + i] = {
label: 'Input ' + (i + 1)
};
}
for (i = 0; i < nbOutputs; i++) {
data.properties.outputs['output_' + i] = {
label: 'Output ' + (i + 1)
};
}
return data;
}
var operatorId = 0;
$draggableOperators.draggable({
cursor: "move",
opacity: 0.7,
helper: 'clone',
appendTo: 'body',
zIndex: 1000,
helper: function(e) {
var $this = $(this);
var data = getOperatorData($this);
return $flowchart.flowchart('getOperatorElement', data);
},
stop: function(e, ui) {
var $this = $(this);
var elOffset = ui.offset;
var containerOffset = $container.offset();
if (elOffset.left > containerOffset.left &&
elOffset.top > containerOffset.top &&
elOffset.left < containerOffset.left + $container.width() &&
elOffset.top < containerOffset.top + $container.height()) {
var flowchartOffset = $flowchart.offset();
var relativeLeft = elOffset.left - flowchartOffset.left;
var relativeTop = elOffset.top - flowchartOffset.top;
var positionRatio = $flowchart.flowchart('getPositionRatio');
relativeLeft /= positionRatio;
relativeTop /= positionRatio;
var data = getOperatorData($this);
data.left = relativeLeft;
data.top = relativeTop;
$flowchart.flowchart('addOperator', data);
}
}
});
});
I have a script that was written a long time ago and not by me that I have updated from V2 to V3 and I am trying to draw range rings from a centered LatLng point. This worked in V2 but it is not working in V3 and I can't figure out why as I know some of the code is depreciated but not sure what it needs to be replaced with.
//Function to draw circles
function doDrawCircle(circleUnits, center, circleRadius, liColor, liWidth, liOpa, fillColor, fillOpa, opts, radials){
var bounds = new google.maps.LatLngBounds();
var circlePoints = Array();
with (Math) {
if (circleUnits == 'KM') {
var d = circleRadius/6378.8; // radians
}
else { //miles
var d = circleRadius/3963.189; // radians
}
var lat1 = (PI/180)* center.lat(); // radians
var lng1 = (PI/180)* center.lng(); // radians
for (var a = 0 ; a < 361 ; a++ ) {
var tc = (PI/180)*a;
var y = asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc));
var dlng = atan2(sin(tc)*sin(d)*cos(lat1),cos(d)-sin(lat1)*sin(y));
var x = ((lng1-dlng+PI) % (2*PI)) - PI ; // MOD function
var point = new google.maps.LatLng(parseFloat(y*(180/PI)),parseFloat(x*(180/PI)));
circlePoints.push(point);
bounds.extend(point);
if(a==0){
var offset = new google.maps.Size(-5,0); // Added the offset - mile markers look a bit better
var label = new ELabel(point, circleRadius, "style1", offset, 40);
map.addOverlay(label);
}
if (((a==0) || (a==45) || (a==90) || (a==135) || (a==180) || (a==225) || (a==270) || (a==315)) && radials) {
//if (((a==0) || (a==45) || (a==90) || (a==135) || (a==180)) && radials) {
var pline = new google.maps.Polyline([center,point] , liColor, liWidth, liOpa);
map.addOverlay(pline);
}
}
var poly = new google.maps.Polygon(circlePoints, liColor, liWidth, liOpa, fillColor, fillOpa, opts);
map.addOverlay(poly); // Add a target circle to the map
map.setZoom(map.getBoundsZoomLevel(bounds)); // This sets the map bounds to be as big as the target circles, comment out if you don't want it
}
}
Then I have this within the initialize() function for the map.
// You can add circles, or change other parameters
// radials should be set to true for the maximum distance if you want radials
// doDrawCircle(circleUnits, center, circleRadius, lineColor, lineWidth, lineOpacity, fillColor, fillOpacity, opts, radials)
doDrawCircle('MI',llCenter, 62, lcolor, 1, .7, "#FFFF00", 0, { clickable: false }, false);
doDrawCircle('MI',llCenter, 124, lcolor, 1, .7, "#FFFF00", 0, { clickable: false }, false);
doDrawCircle('MI',llCenter, 187, lcolor, 1, .7, "#FFFF00", 0, { clickable: false }, false);
doDrawCircle('MI',llCenter, 249, lcolor, 1, .7, "#FFFF00", 0, { clickable: false }, false);
doDrawCircle('MI',llCenter, 312, lcolor, 1, .7, "#FFFF00", 0, { clickable: false }, false);
doDrawCircle('MI',llCenter, 374, lcolor, 1, .7, "#FFFF00", 0, { clickable: false }, false);
// doDrawCircle('MI',llCenter, 374, lcolor, 1, .7, '#00FF00', 0, { clickable: false }, true); // This would add the radials
This is what it is suppose to look like. This is from the working V2 map.
V2 EXAMPLE
Link to full code
FULL MAP CODE
First thing you'll need to do is get the newest version of elabel.js for google maps V3 here:
https://github.com/erunyon/ELabel/blob/master/elabel.js
Then, the good news is you don't need all of that complicated math and stuff which you have going on in your doDrawCircle function. You can now make use of google.maps.Circle as well as the geometry library which must be included via the google maps script tag's url parameters using a 'libraries=geometry' parameter so we can get the text label placement position starting point via google.maps.geometry.spherical.computeOffset. And then I've included a little tweeking of the text placement below to look a bit more tidy.
Test case:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Circles</title>
<style type="text/css">
.style1 {
/* used for range numbers on rings */
color: #FFF;
font-size: 10px;
text-shadow: 2px 2px 2px #000;
font-weight: bold;
font-family: verdana, helvetica, arial, sans-serif;
background-color:black;
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&libraries=geometry"></script>
<!-- elabel.js for google maps V3 from here: https://github.com/erunyon/ELabel/blob/master/elabel.js -->
<script src="elabel.js"></script>
<script>
function initialize() {
var i, meters, options, labelLocation, textLength, textXcenter, label,
//note I declared the actual font pixel size in .style1 css rule
//just to help with visualizing the way I'm positioning the label texts
textPixelSize = 10,
//will need to invert textYcenter as well as textXcenter to negative numbers later
textYcenter = (textPixelSize / 2) + 2, //2px tweak for 'y' position, approximation
mapOptions = {
zoom: 6,
center: new google.maps.LatLng(32.8297,-96.6486),
mapTypeId: google.maps.MapTypeId.HYBRID
},
ranges = [62, 124, 187, 249, 312, 374], //circle radii in miles
circles = [],
labels = [],
map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
for (i = 0; i < ranges.length; i++) {
//convert miles to meters:
meters = ranges[i] / 0.00062137;
options = {
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 1,
fillOpacity: 0,
map: map,
center: mapOptions.center,
radius: meters
};
circles.push(new google.maps.Circle(options));//ta-da! easy circles in V3
//labelLocation will be a google.maps.LatLng object
labelLocation = google.maps.geometry.spherical.computeOffset(mapOptions.center, meters, 0);
textLength = (''+ranges[i]).length;
textXcenter = (textLength * textPixelSize) / 2; //approximation
label = new ELabel({
latlng: labelLocation,
label: ranges[i],
classname: 'style1',
offset: new google.maps.Size(-textXcenter, -textYcenter),//negative will move left and up
opacity: 100,
overlap: true,
clicktarget: false
});
label.setMap(map);
labels.push(label);
}
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map_canvas" style="width:780px; height:600px; margin:10px auto;"></div>
</body>
</html>
EDIT:
Here is revised version which allows to toggle visibility of the rings via the 'Range' button. Also removed all the text label positioning adjustment math and replaced with using style classes for different length texts instead (makes it easier to just use em units in the styles if desired). Added LabelCircle constructor for easier encapsulation and simultaneous control of circles & labels.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Circles</title>
<style type="text/css">
.style1 {
/*used for range numbers on rings*/
color: #FFF;
font-size: .6em;
text-shadow: 2px 2px 2px #000;
font-weight: bold;
font-family: verdana, helvetica, arial, sans-serif;
background-color:black;
margin-top: -0.5em;
}
.d2 { /*two-digit numbers on rings*/
margin-left: -1em;
}
.d3 { /*three-digit numbers on rings*/
margin-left: -1.5em;
}
/*direct copy of your existing .button style rule*/
.button{
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
text-align: center;
position: relative;
font-family: Arial, sans-serif;
font-size: 13px;
font-weight:bold;
box-shadow: rgba(0, 0, 0, 0.4) 0 2px 4px;
-moz-box-shadow: rgba(0, 0, 0, 0.4) 0 2px 4px;
-webkit-box-shadow: rgba(0, 0, 0, 0.4) 0 2px 4px;
color: #000;
border: 1px solid #717B87;
background-color: #fff;
margin: 5px;
padding: 1px 6px;
overflow: hidden;
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&libraries=geometry"></script>
<!-- elabel.js for google maps V3 from here: https://github.com/erunyon/ELabel/blob/master/elabel.js -->
<script src="elabel.js"></script>
<script>
function LabelCircle(options) {
this.circle = new google.maps.Circle(options.circleOptions);
this.label = new ELabel(options.labelOptions);
this.label.setMap(options.circleOptions.map);
this.isVisible = true;
}
LabelCircle.prototype.setVisible = function (bool) {
var method = (bool) ? 'show' : 'hide';
this.circle.setVisible(bool);
this.label[method]();
this.isVisible = bool;
};
//a direct copy of your existing function
function buttonControl(options) {
var control = document.createElement('DIV');
control.innerHTML = options.name;
control.className = 'button';
control.index = 1;
// Add the control to the map
options.gmap.controls[options.position].push(control);
google.maps.event.addDomListener(control, 'click', options.action);
return control;
}
function initialize() {
var i, meters, options, labelLocation, textLength, label,
mapOptions = {
zoom: 6,
center: new google.maps.LatLng(32.8297,-96.6486),
mapTypeId: google.maps.MapTypeId.HYBRID
},
ranges = [62, 124, 187, 249, 312, 374], //circle radii in miles
labelCircles = [],
map = new google.maps.Map(document.getElementById('map_canvas'), mapOptions);
for (i = 0; i < ranges.length; i++) {
//convert miles to meters:
meters = ranges[i] / 0.00062137;
//labelLocation will be a google.maps.LatLng object
labelLocation = google.maps.geometry.spherical.computeOffset(mapOptions.center, meters, 0);
//we'll use textLength below to add a class to the label
textLength = (''+ranges[i]).length;
options = {
circleOptions: {
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 1,
fillColor: 'transparent',
fillOpacity: 0,
map: map,
center: mapOptions.center,
radius: meters
},
labelOptions: {
latlng: labelLocation,
label: ranges[i],
classname: 'style1 d' + textLength,
//offset: //no longer needed, using style classes
opacity: 100,
overlap: true,
clicktarget: false
}
};
labelCircles.push(new LabelCircle(options));
}
var rangeOptions = {
gmap: map,
name: 'Range',
position: google.maps.ControlPosition.TOP_RIGHT,
action: function(){
for (var tmp, i = 0; i < labelCircles.length; i++) {
tmp = labelCircles[i];
tmp.setVisible(!tmp.isVisible);
}
}
};
var rangeButton = buttonControl(rangeOptions);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="map_canvas" style="width:780px; height:600px; margin:10px auto;"></div>
</body>
</html>