i have geojson like this :
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "Name": "Saluran I", "descriptio": null, "timestamp": null, "begin": null, "end": null, "altitudeMo": null, "tessellate": 1, "extrude": -1, "visibility": -1, "drawOrder": null, "icon": null }, "geometry": { "type": "LineString", "coordinates": [ [ 115.16149264388849, -8.692345779607091 ], [ 115.1619799975747, -8.693099022961922 ], [ 115.1621209969948, -8.693450580043523 ], [ 115.1621893956145, -8.69367970180001 ], [ 115.1622369286341, -8.693885376054253 ], [ 115.16248624315379, -8.694125330077087 ], [ 115.1625293838875, -8.694297689361708 ], [ 115.1625662929434, -8.694480291402353 ], [ 115.1638244075253, -8.694489484612449 ] ] } },
{ "type": "Feature", "properties": { "Name": "Saluran II", "descriptio": null, "timestamp": null, "begin": null, "end": null, "altitudeMo": null, "tessellate": 1, "extrude": -1, "visibility": -1, "drawOrder": null, "icon": null }, "geometry": { "type": "LineString", "coordinates": [ [ 115.1647420393289, -8.691263798416188 ], [ 115.16480885306601, -8.691910749059817 ], [ 115.1648021389716, -8.692020041570267 ], [ 115.16476460026691, -8.692197857370241 ], [ 115.16467300953239, -8.692311184386924 ], [ 115.1645832229062, -8.692570845169653 ], [ 115.164590403574, -8.69280000074686 ], [ 115.1642184661912, -8.692878192437396 ], [ 115.16418831658579, -8.693471791565786 ], [ 115.16411080877791, -8.69417746825723 ], [ 115.1639500244154, -8.694469415766308 ], [ 115.1638498474275, -8.69450385891758 ], [ 115.1637726241196, -8.6949183073729 ], [ 115.1637482310352, -8.695106949243888 ], [ 115.16369576938609, -8.695516868583109 ], [ 115.1633930487843, -8.695552277605064 ], [ 115.1628619559151, -8.695616245099258 ], [ 115.1628453449146, -8.695861398016726 ], [ 115.1625531407406, -8.695981675836846 ], [ 115.1619167160671, -8.696110249672243 ], [ 115.1621001879372, -8.697348692504496 ], [ 115.1619454016469, -8.697429501488445 ] ] } },
{ "type": "Feature", "properties": { "Name": "Saluran III", "descriptio": null, "timestamp": null, "begin": null, "end": null, "altitudeMo": null, "tessellate": 1, "extrude": -1, "visibility": -1, "drawOrder": null, "icon": null }, "geometry": { "type": "LineString", "coordinates": [ [ 115.1649918428157, -8.698726927918452 ], [ 115.1647717936939, -8.698771893626452 ], [ 115.16464767674699, -8.698766178177538 ], [ 115.164531974608, -8.698766747854149 ], [ 115.1643076171094, -8.698759607942607 ], [ 115.1639182931609, -8.698808262913275 ], [ 115.1637251462856, -8.69887278452787 ], [ 115.1633890383243, -8.698973728970067 ], [ 115.1626822904817, -8.699201749498535 ], [ 115.1626359629325, -8.699082282748639 ], [ 115.1621957225977, -8.698410881596306 ], [ 115.16193358395429, -8.69743434218265 ] ] } }
]
}
i can showing that geojson on my map,
line.loadGeoJson(urlBase + 'peta/aliran.geojson');
// styling features aliran
line.setStyle(function(feature){
return {
strokeColor : '#0000FF',
strokeWeight : 5,
zIndex : 1
}
});
// set map;
line.setMap(map);
but i have problem when i want to animating symbols on the line like this : https://developers.google.com/maps/documentation/javascript/examples/overlay-symbol-animate. could anybody help me ?
Leveraging the code in this related example (which animates a marker along a polyline from the directions service) to animate a symbol along one of the polylines in your data:
proof of concept fiddle
code snippet:
var map;
var position;
var marker = null;
var polyline = null;
var poly2 = null;
var speed = 0.000005,
wait = 1;
var infowindow = null;
var myPano;
var panoClient;
var nextPanoId;
var timerHandle = null;
var polyline;
function initialize() {
map = new google.maps.Map(
document.getElementById("map_canvas"), {
center: new google.maps.LatLng(37.4419, -122.1419),
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var bounds = new google.maps.LatLngBounds();
map.data.addListener('addfeature', function(e) {
processPoints(e.feature.getGeometry(), bounds.extend, bounds);
if (e.feature.getGeometry().getType() == "LineString") {
polyline = new google.maps.Polyline({
map: map,
path: e.feature.getGeometry().getArray(),
strokeWeight: 5,
strokeOpacity: 0.4,
strokeColor: 'red'
});
marker = createMarker(polyline.getPath().getAt(0), "here", "marker text");
setTimeout(startAnimation, 2000);
}
map.fitBounds(bounds);
});
// var line = google.maps.Data;
map.data.addGeoJson(geoJson);
// styling features aliran
map.data.setStyle(function(feature) {
return {
strokeColor: '#0000FF',
strokeWeight: 1,
zIndex: 1
}
});
// set map;
map.data.setMap(map);
}
var step = 5; // metres
var tick = 100; // milliseconds
var eol;
var k = 0;
var stepnum = 0;
var speed = "";
var lastVertex = 1;
//=============== animation functions ======================
function updatePoly(d) {
// Spawn a new polyline every 20 vertices, because updating a 100-vertex poly is too slow
if (poly2.getPath().getLength() > 20) {
poly2 = new google.maps.Polyline([polyline.getPath().getAt(lastVertex - 1)]);
}
if (polyline.GetIndexAtDistance(d) < lastVertex + 2) {
if (poly2.getPath().getLength() > 1) {
poly2.getPath().removeAt(poly2.getPath().getLength() - 1)
}
poly2.getPath().insertAt(poly2.getPath().getLength(), polyline.GetPointAtDistance(d));
} else {
poly2.getPath().insertAt(poly2.getPath().getLength(), polyline.getPath().getAt(polyline.getPath().getLength() - 1));
}
}
function animate(d) {
if (d > eol) {
map.panTo(endLocation.latlng);
marker.setPosition(endLocation.latlng);
return;
}
var p = polyline.GetPointAtDistance(d);
map.panTo(p);
marker.setPosition(p);
updatePoly(d);
timerHandle = setTimeout("animate(" + (d + step) + ")", tick);
}
function startAnimation() {
if (timerHandle) clearTimeout(timerHandle);
eol = google.maps.geometry.spherical.computeLength(polyline.getPath());
map.setCenter(polyline.getPath().getAt(0));
poly2 = new google.maps.Polyline({
path: [polyline.getPath().getAt(0)],
strokeColor: "#0000FF",
strokeWeight: 10
});
setTimeout("animate(0)", tick); // Allow time for the initial map display
}
function processPoints(geometry, callback, thisArg) {
if (geometry instanceof google.maps.LatLng) {
callback.call(thisArg, geometry);
} else if (geometry instanceof google.maps.Data.Point) {
callback.call(thisArg, geometry.get());
} else {
geometry.getArray().forEach(function(g) {
processPoints(g, callback, thisArg);
});
}
}
// ==================================================
function createMarker(latlng, label, html) {
var contentString = '<b>' + label + '</b><br>' + html;
var marker = new google.maps.Marker({
position: latlng,
map: map,
title: label,
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 8,
strokeColor: '#393'
},
zIndex: Math.round(latlng.lat() * -100000) << 5
});
marker.myname = label;
// gmarkers.push(marker);
google.maps.event.addListener(marker, 'click', function() {
infowindow.setContent(contentString);
infowindow.open(map, marker);
});
return marker;
}
google.maps.event.addDomListener(window, "load", initialize);
var geoJson = {
"type": "FeatureCollection",
"crs": {
"type": "name",
"properties": {
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features": [{
"type": "Feature",
"properties": {
"Name": "Saluran II",
"descriptio": null,
"timestamp": null,
"begin": null,
"end": null,
"altitudeMo": null,
"tessellate": 1,
"extrude": -1,
"visibility": -1,
"drawOrder": null,
"icon": null
},
"geometry": {
"type": "LineString",
"coordinates": [
[115.1647420393289, -8.691263798416188],
[115.16480885306601, -8.691910749059817],
[115.1648021389716, -8.692020041570267],
[115.16476460026691, -8.692197857370241],
[115.16467300953239, -8.692311184386924],
[115.1645832229062, -8.692570845169653],
[115.164590403574, -8.69280000074686],
[115.1642184661912, -8.692878192437396],
[115.16418831658579, -8.693471791565786],
[115.16411080877791, -8.69417746825723],
[115.1639500244154, -8.694469415766308],
[115.1638498474275, -8.69450385891758],
[115.1637726241196, -8.6949183073729],
[115.1637482310352, -8.695106949243888],
[115.16369576938609, -8.695516868583109],
[115.1633930487843, -8.695552277605064],
[115.1628619559151, -8.695616245099258],
[115.1628453449146, -8.695861398016726],
[115.1625531407406, -8.695981675836846],
[115.1619167160671, -8.696110249672243],
[115.1621001879372, -8.697348692504496],
[115.1619454016469, -8.697429501488445]
]
}
}]
};
/*********************************************************************\
* *
* epolys.js by Mike Williams *
* updated to API v3 by Larry Ross *
* *
* A Google Maps API Extension *
* *
* Adds various Methods to google.maps.Polygon and google.maps.Polyline *
* *
* .Contains(latlng) returns true is the poly contains the specified *
* GLatLng *
* *
* .Area() returns the approximate area of a poly that is *
* not self-intersecting *
* *
* .Distance() returns the length of the poly path *
* *
* .Bounds() returns a GLatLngBounds that bounds the poly *
* *
* .GetPointAtDistance() returns a GLatLng at the specified distance *
* along the path. *
* The distance is specified in metres *
* Reurns null if the path is shorter than that *
* *
* .GetPointsAtDistance() returns an array of GLatLngs at the *
* specified interval along the path. *
* The distance is specified in metres *
* *
* .GetIndexAtDistance() returns the vertex number at the specified *
* distance along the path. *
* The distance is specified in metres *
* Returns null if the path is shorter than that *
* *
* .Bearing(v1?,v2?) returns the bearing between two vertices *
* if v1 is null, returns bearing from first to last *
* if v2 is null, returns bearing from v1 to next *
* *
* *
***********************************************************************
* *
* This Javascript is provided by Mike Williams *
* Blackpool Community Church Javascript Team *
* http://www.blackpoolchurch.org/ *
* http://econym.org.uk/gmap/ *
* *
* This work is licenced under a Creative Commons Licence *
* http://creativecommons.org/licenses/by/2.0/uk/ *
* *
***********************************************************************
* *
* Version 1.1 6-Jun-2007 *
* Version 1.2 1-Jul-2007 - fix: Bounds was omitting vertex zero *
* add: Bearing *
* Version 1.3 28-Nov-2008 add: GetPointsAtDistance() *
* Version 1.4 12-Jan-2009 fix: GetPointsAtDistance() *
* Version 3.0 11-Aug-2010 update to v3 *
* *
\*********************************************************************/
// === A method which returns a GLatLng of a point a given distance along the path ===
// === Returns null if the path is shorter than the specified distance ===
google.maps.Polyline.prototype.GetPointAtDistance = function(metres) {
// some awkward special cases
if (metres == 0) return this.getPath().getAt(0);
if (metres < 0) return null;
if (this.getPath().getLength() < 2) return null;
var dist = 0;
var olddist = 0;
for (var i = 1;
(i < this.getPath().getLength() && dist < metres); i++) {
olddist = dist;
dist += google.maps.geometry.spherical.computeDistanceBetween(this.getPath().getAt(i), this.getPath().getAt(i - 1));
}
if (dist < metres) {
return null;
}
var p1 = this.getPath().getAt(i - 2);
var p2 = this.getPath().getAt(i - 1);
var m = (metres - olddist) / (dist - olddist);
return new google.maps.LatLng(p1.lat() + (p2.lat() - p1.lat()) * m, p1.lng() + (p2.lng() - p1.lng()) * m);
}
// === A method which returns the Vertex number at a given distance along the path ===
// === Returns null if the path is shorter than the specified distance ===
google.maps.Polyline.prototype.GetIndexAtDistance = function(metres) {
// some awkward special cases
if (metres == 0) return this.getPath().getAt(0);
if (metres < 0) return null;
var dist = 0;
var olddist = 0;
for (var i = 1;
(i < this.getPath().getLength() && dist < metres); i++) {
olddist = dist;
dist += google.maps.geometry.spherical.computeDistanceBetween(this.getPath().getAt(i), this.getPath().getAt(i - 1));
}
if (dist < metres) {
return null;
}
return i;
}
html,
body,
#map_canvas {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<input type="button" value="restart animation" onclick="startAnimation()" />
<div id="map_canvas"></div>
Related
I need to replicate this chart bellow in my shiny app. But I am struggling to deal with the javascript part Any help would be amazing:
Clock Chart Highchart
This is the javascript code: how do I 'translate' this to R?
Any help/indication to deal with javascript in R would be amazing.
Many many tahnks guys
`/**
* Get the current time
*/
function getNow() {
var now = new Date();
return {
hours: now.getHours() + now.getMinutes() / 60,
minutes: now.getMinutes() * 12 / 60 + now.getSeconds() * 12 / 3600,
seconds: now.getSeconds() * 12 / 60
};
}
/**
* Pad numbers
*/
function pad(number, length) {
// Create an array of the remaining length + 1 and join it with 0's
return new Array((length || 2) + 1 - String(number).length).join(0) + number;
}
var now = getNow();
// Create the chart
Highcharts.chart('container', {
chart: {
type: 'gauge',
plotBackgroundColor: null,
plotBackgroundImage: null,
plotBorderWidth: 0,
plotShadow: false,
height: '80%'
},
credits: {
enabled: false
},
title: {
text: 'The Highcharts clock'
},
pane: {
background: [{
// default background
}, {
// reflex for supported browsers
backgroundColor: Highcharts.svg ? {
radialGradient: {
cx: 0.5,
cy: -0.4,
r: 1.9
},
stops: [
[0.5, 'rgba(255, 255, 255, 0.2)'],
[0.5, 'rgba(200, 200, 200, 0.2)']
]
} : null
}]
},
yAxis: {
labels: {
distance: -20
},
min: 0,
max: 12,
lineWidth: 0,
showFirstLabel: false,
minorTickInterval: 'auto',
minorTickWidth: 1,
minorTickLength: 5,
minorTickPosition: 'inside',
minorGridLineWidth: 0,
minorTickColor: '#666',
tickInterval: 1,
tickWidth: 2,
tickPosition: 'inside',
tickLength: 10,
tickColor: '#666',
title: {
text: 'Powered by<br/>Highcharts',
style: {
color: '#BBB',
fontWeight: 'normal',
fontSize: '8px',
lineHeight: '10px'
},
y: 10
}
},
tooltip: {
formatter: function () {
return this.series.chart.tooltipText;
}
},
series: [{
data: [{
id: 'hour',
y: now.hours,
dial: {
radius: '60%',
baseWidth: 4,
baseLength: '95%',
rearLength: 0
}
}, {
id: 'minute',
y: now.minutes,
dial: {
baseLength: '95%',
rearLength: 0
}
}, {
id: 'second',
y: now.seconds,
dial: {
radius: '100%',
baseWidth: 1,
rearLength: '20%'
}
}],
animation: false,
dataLabels: {
enabled: false
}
}]
},
// Move
function (chart) {
setInterval(function () {
now = getNow();
if (chart.axes) { // not destroyed
var hour = chart.get('hour'),
minute = chart.get('minute'),
second = chart.get('second'),
// run animation unless we're wrapping around from 59 to 0
animation = now.seconds === 0 ?
false : {
easing: 'easeOutBounce'
};
// Cache the tooltip text
chart.tooltipText =
pad(Math.floor(now.hours), 2) + ':' +
pad(Math.floor(now.minutes * 5), 2) + ':' +
pad(now.seconds * 5, 2);
hour.update(now.hours, true, animation);
minute.update(now.minutes, true, animation);
second.update(now.seconds, true, animation);
}
}, 1000);
});
/**
* Easing function from https://github.com/danro/easing-js/blob/master/easing.js
*/
Math.easeOutBounce = function (pos) {
if ((pos) < (1 / 2.75)) {
return (7.5625 * pos * pos);
}
if (pos < (2 / 2.75)) {
return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
}
if (pos < (2.5 / 2.75)) {
return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
}
return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
};`
This converts that JS into R/JS (you need to collect time in Javascript). I noticed odd vertical lines in the Viewer pane of RStudio when this runs, but these lines don't appear in my browser.
For most calls in JS for highcharter, the function or argument is identical in R. I used lubridate for the time functions in the R code. (Although, you could set the time to static values because the time isn't controlled by R code.)
After creating the graph, I used htmlwidgets::onRender to give add the animation so that it follows actual time.
If you run this without htmlwidgets, this is what you'll see. (Well, you'll see the time on the clock for your local time at the moment you render it.)
library(highcharter)
library(lubridate)
highchart() %>%
hc_chart(type = "gauge", plotBackgroundColor = NULL,
plotBackgroundImage = NULL, plotBorderWidth = 0,
plotShadow = F) %>%
hc_pane(
background = list(
backgroundColor = list(
radialGradient = list(cx = .5, cy = -.4, r = 1.9),
stops = list(
list(.5, "rgba(255, 255, 255, .2)"),
list(.5, "rgba(200, 200, 200, .2)"))
))) %>%
hc_tooltip(enabled = FALSE) %>%
hc_yAxis(
labels = list(distance = -20),
min = 0, max = 12, lineWidth = 0, showFirstLabel = F,
minorTickInterval = "auto", minorTickWidth = 1,
minorTickColor = "#666", tickColor = "#666",
minorTickPosition = "inside", minorGridLineWidth = 0,
tickInterval = 1, tickWidth = 2, tickPosition = "inside",
tickLength = 10) %>%
hc_add_series(
data = list(
list(id = "hour", y = hour(now()), dial = list(
radius = "60%", baseWidth = 4, baseLength = "95%", rearLength = 0)),
list(id = "minute", y = minute(now()), dial = list(
baseLength = "95%", rearLength = 0)),
list(id = "second", y = second(now()), dial = list(
radius = "100%", baseWidth = 1, rearLength = "20%"))),
dataLabels = list(enabled = F)) %>%
htmlwidgets::onRender("
function(el, x) {
chart = $('#' + el.id).highcharts()
$.extend($.easing, {
easeOutElastic: function (x, t, b, c, d) {
var s = 1.70158; var p = 0; var a = c;
if (t == 0) return b; if ((t /= d) == 1) return b+c;
if (!p) p = d*.3;
if (a < Math.abs(c)) { a = c; var s = p/4; }
else var s = p/(2 * Math.PI) * Math.asin (c/a);
return a * Math.pow(2, -10 * t) * Math.sin( (t * d - s) * (2 * Math.PI)/p) + c + b;
}
});
function getNow () {
var now = new Date();
return {
hours: now.getHours() + now.getMinutes() / 60,
minutes: now.getMinutes() * 12 / 60 + now.getSeconds() * 12 / 3600,
seconds: now.getSeconds() * 12 / 60
};
};
setInterval(function () {
var hour = chart.get('hour'),
minute = chart.get('minute'),
second = chart.get('second'),
now = getNow(),
/* run animation unless we're wrapping around from 59 to 0 */
animation = now.seconds == 0 ?
false : {easing: 'easeOutElastic'};
hour.update(now.hours, true, animation);
minute.update(now.minutes, true, animation);
second.update(now.seconds, true, animation);
}, 1000);
}")
In this JS, you'll see some deviation from the original code. I needed to define 'chart'. I did that using the same mechanism that is used to change any highcharter R object into it's HTML rendering: chart = $('#' + el.id).highcharts(). Since the function that sets the interval was originally part of creating the graph, it was an unnamed function. Since we're calling after we render the graph, I dropped that outer function(chart).
Need suggestions on how to add Curved Polyline using Here Maps Javascript API ?
There is no any straight way, but you can use following formula:
var D2R = Math.PI / 180;
var R2D = 180 / Math.PI;
var Coord = function(lon, lat) {
this.lon = lon;
this.lat = lat;
this.x = D2R * lon;
this.y = D2R * lat;
};
Coord.prototype.view = function() {
return String(this.lon).slice(0, 4) + ',' + String(this.lat).slice(0, 4);
};
Coord.prototype.antipode = function() {
var anti_lat = -1 * this.lat;
var anti_lon = (this.lon < 0) ? 180 + this.lon : (180 - this.lon) * -1;
return new Coord(anti_lon, anti_lat);
};
var LineString = function() {
this.coords = [];
this.length = 0;
};
LineString.prototype.move_to = function(coord) {
this.length++;
this.coords.push(coord);
};
var Arc = function(properties) {
this.properties = properties || {};
this.geometries = [];
};
Arc.prototype.json = function() {
if (this.geometries.length <= 0) {
return { 'geometry': { 'type': 'LineString', 'coordinates': null }, 'type': 'Feature', 'properties': this.properties };
} else if (this.geometries.length == 1) {
return { 'geometry': { 'type': 'LineString', 'coordinates': this.geometries[0].coords }, 'type': 'Feature', 'properties': this.properties };
} else {
var multiline = [];
for (var i = 0; i < this.geometries.length; i++) {
multiline.push(this.geometries[i].coords);
}
return { 'geometry': { 'type': 'MultiLineString', 'coordinates': multiline }, 'type': 'Feature', 'properties': this.properties };
}
};
Arc.prototype.strip = function() {
var s = H.geo.Strip ? new H.geo.Strip() : new H.geo.LineString();
for (var i = 0; i < this.geometries.length; i++) {
if (this.geometries[i].coords.lenght !== 0) {
var coords = this.geometries[i].coords;
for (var j = 0; j < coords.length; j++) {
var p = new H.geo.Point(coords[j][1], coords[j][0]);
s.pushPoint(p);
}
}
}
return s;
}
For detailed example, I have created a sample, please check - https://demo.support.here.com/examples/v3.1/geodesic_polyline
I'm trying to make nodes collapsed on click of node itself. The graph at present is force directed graph with draggable nodes.
Problem I'm facing is, after attaching the node click event I cannot get the click to fire instead node is displaced from it's position (like dragged on mouse click).
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var graph = { "links" : [ { "relation" : "Relation 0",
"source" : 0,
"target" : 1
},
{ "relation" : "Relation 1",
"source" : 1,
"target" : 3
},
{ "relation" : "Relation 3",
"source" : 1,
"target" : 4
},
{ "relation" : "Relation 4",
"source" : 1,
"target" : 5
},
{ "relation" : "Relation 5",
"source" : 0,
"target" : 2
},
{ "relation" : "Relation 6",
"source" : 2,
"target" : 6
}
],
"nodes" : [ { "collapsed" : false,
"collapsing" : 0,
"id" : 0,
"name" : "Node 0"
},
{ "collapsed" : false,
"collapsing" : 0,
"id" : 1,
"name" : "Node 1"
},
{ "collapsed" : false,
"collapsing" : 0,
"id" : 2,
"name" : "Node 2"
},
{ "collapsed" : false,
"collapsing" : 0,
"id" : 3,
"name" : "Node 3"
},
{ "collapsed" : false,
"collapsing" : 0,
"id" : 4,
"name" : "Node 4"
},
{ "collapsed" : false,
"collapsing" : 0,
"id" : 5,
"name" : "Node 5"
},
{ "collapsed" : false,
"collapsing" : 0,
"id" : 6,
"name" : "Node 6"
}
]
};
var edges = [];
graph.links.forEach(function(e) {
var sourceNode = graph.nodes.filter(function(n) {
return n.id === e.source;
})[0],
targetNode = graph.nodes.filter(function(n) {
return n.id === e.target;
})[0];
edges.push({
source: sourceNode,
target: targetNode,
relation: e.relation
});
});
update();
function update() {
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 8)
.attr("fill", function(d) { return color(d.group); })
.on("click", togglenode)
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.append("title")
.text(function(d) { return d.id; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
}
function dragstarted(d) {
console.log('drag started');
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
console.log('dragged');
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
console.log('drag ended');
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
function togglenode(d) {
console.log('clicked node : '+d.id);
if (!d3.event.defaultPrevented) {
var inc = d.collapsed ? -1 : 1;
recurse(d);
function recurse(sourceNode){
//check if link is from this node, and if so, collapse
edges.forEach(function(l) {
if (l.source.id === sourceNode.id){
l.target.collapsing += inc;
recurse(l.target);
}
});
}
d.collapsed = !d.collapsed;
update();
}
}
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 4.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.js"></script>
<svg width="800" height="500"></svg>
is it possible to stack 2 or more graphs in one chart? with the same Scrollbar / Chartcursor?
I tried it with the live editor, but havn't found any option to make this possible.
Here som pictures for explaination:
At the moment i have this graph: http://imgur.com/XpU42xK
But i want a graph like this: http://imgur.com/xdwJ7lu
Thanks for your help :)
Just for the sake of sport I tried to implement it. Good news - it's possible :)
First of all you will need to prepare both charts
First chart
Scrollbar enabled
Chart cursor enabled
Category axis hidden
Legend disabled
Bottom margin: 0
Second chart
Scrollbar disabled
Chart cursor enabled
Category axis enabled
Legend enabled
Top margin: 0
This creates two charts, "glued" together like in your mockup:
Syncing the charts
They still act on their own, like two separate charts. We have to sync them up:
Cursor
Legend
Zoom/pan
That's where this code comes in:
/**
* Use events to sync up cursors and zooms
*/
for ( var x = 0; x < AmCharts.charts.length; x++ ) {
// use "changed" event to track cursor movement on all charts
// place the cursor on other charts on the same index
AmCharts.charts[ x ].addListener( "changed", function( event ) {
// find which category is currently being rolled over
var category = event.chart.dataProvider[ event.index ][ event.chart.categoryField ];
// cycle through all available charts and place the cursor at
// the same category
for ( var i = 0; i < AmCharts.charts.length; i++ ) {
if ( event.chart !== AmCharts.charts[ i ] ) {
AmCharts.charts[ i ].chartCursor.showCursorAt( category );
}
}
} );
// use "zoomed" event to track zooms/pans so we can apply
// the same zoom across other charts
AmCharts.charts[ x ].addListener( "zoomed", function( zoomEvent ) {
// cycle through all available charts and apply same zoom
for ( var i = 0; i < AmCharts.charts.length; i++ ) {
if ( zoomEvent.chart !== AmCharts.charts[ i ] && ( AmCharts.charts[ i ].startIndex != zoomEvent.startIndex || AmCharts.charts[ i ].endIndex != zoomEvent.endIndex ) ) {
AmCharts.charts[ i ].zoomToIndexes( zoomEvent.startIndex, zoomEvent.endIndex );
}
}
} );
}
/**
* Use the last chart to display legend
* We will collect graphs from other charts and mirror them on
* the last chart.
*/
var lastChart = AmCharts.charts[ AmCharts.charts.length - 1 ];
for ( var x = 0; x < ( AmCharts.charts.length - 1 ); x++ ) {
// add graphs
for ( var g = 0; g < AmCharts.charts[ x ].graphs.length; g++ ) {
// create the related graph
var graph = new AmCharts.AmGraph();
graph.lineAlpha = 0;
graph.lineColor = AmCharts.charts[ x ].graphs[ g ].lineColor;
graph.relatedGraph = AmCharts.charts[ x ].graphs[ g ];
graph.valueField = "value_" + x + "_" + g;
graph.showBalloon = false;
graph.includeInMinMax = false;
graph.title = AmCharts.charts[ x ].graphs[ g ].title;
lastChart.addGraph( graph );
// mirror data for rollovers
for ( var i = 0; i < lastChart.dataProvider.length; i++ ) {
lastChart.dataProvider[ i ][ graph.valueField ] = AmCharts.charts[ x ].dataProvider[ i ][ AmCharts.charts[ x ].graphs[ g ].valueField ];
}
}
}
/**
* Add events to the legend of last chart so we can toggle
* graphs of the other charts
*/
lastChart.addListener( "init", function() {
// hide graph
lastChart.legend.addListener( "hideItem", function( event ) {
if ( event.dataItem.relatedGraph !== undefined ) {
event.dataItem.relatedGraph.chart.hideGraph( event.dataItem.relatedGraph );
}
} );
// show graph
lastChart.legend.addListener( "showItem", function( event ) {
if ( event.dataItem.relatedGraph !== undefined ) {
event.dataItem.relatedGraph.chart.showGraph( event.dataItem.relatedGraph );
}
} );
} );
And voila:
Here's a working demo:
/**
* First chart
* With scrollbar
* Without category axis
*/
AmCharts.makeChart( "chartdiv1", {
"type": "serial",
"theme": "light",
"path": "http://www.amcharts.com/lib/3/",
"dataProvider": [ {
"category": "Category #1",
"value": 2025
}, {
"category": "Category #2",
"value": 1882
}, {
"category": "Category #3",
"value": 1809
}, {
"category": "Category #4",
"value": 1322
}, {
"category": "Category #5",
"value": 1122
}, {
"category": "Category #6",
"value": 1114
}, {
"category": "Category #7",
"value": 984
}, {
"category": "Category #8",
"value": 711
}, {
"category": "Category #9",
"value": 665
}, {
"category": "Category #10",
"value": 580
} ],
"valueAxes": [ {
"gridAlpha": 0.2,
"dashLength": 0,
"showFirstLabel": false,
"ignoreAxisWidth": true,
"title": "First graph"
} ],
"startDuration": 1,
"graphs": [ {
"id": "g1",
"lineThickness": 2,
"lineColor": "#f56400",
"type": "step",
"valueField": "value",
"title": "Graph 1"
} ],
"chartCursor": {
"categoryBalloonEnabled": false,
"cursorColor": "#c30000",
"animationDuration": 0
},
"chartScrollbar": {},
"categoryField": "category",
"categoryAxis": {
"ignoreAxisWidth": true,
"labelsEnabled": false,
"axisAlpha": 0
},
"marginBottom": 0,
"marginLeft": 80
} );
/**
* Second chart
* Without scrollbar
* With category axis
*/
AmCharts.makeChart( "chartdiv2", {
"type": "serial",
"theme": "light",
"zoomOutText": "",
"path": "http://www.amcharts.com/lib/3/",
"dataProvider": [ {
"category": "Category #1",
"value": 521
}, {
"category": "Category #2",
"value": 215
}, {
"category": "Category #3",
"value": 655
}, {
"category": "Category #4",
"value": 601
}, {
"category": "Category #5",
"value": 122
}, {
"category": "Category #6",
"value": 114
}, {
"category": "Category #7",
"value": 521
}, {
"category": "Category #8",
"value": 351
}, {
"category": "Category #9",
"value": 222
}, {
"category": "Category #10",
"value": 156
} ],
"valueAxes": [ {
"gridAlpha": 0.2,
"dashLength": 0,
"showLastLabel": false,
"ignoreAxisWidth": true,
"title": "Second graph"
} ],
"startDuration": 1,
"graphs": [ {
"id": "g1",
"lineThickness": 2,
"lineColor": "#fae879",
"type": "step",
"valueField": "value",
"title": "Graph 2"
} ],
"chartCursor": {
"cursorColor": "#c30000",
"animationDuration": 0
},
"categoryField": "category",
"categoryAxis": {
"tickPosition": "middle"
},
"legend": {},
"marginTop": 0,
"marginLeft": 80
} );
/**
* Use events to sync up cursors and zooms
*/
for ( var x = 0; x < AmCharts.charts.length; x++ ) {
// use "changed" event to track cursor movement on all charts
// place the cursor on other charts on the same index
AmCharts.charts[ x ].addListener( "changed", function( event ) {
// find which category is currently being rolled over
var category = event.chart.dataProvider[ event.index ][ event.chart.categoryField ];
// cycle through all available charts and place the cursor at
// the same category
for ( var i = 0; i < AmCharts.charts.length; i++ ) {
if ( event.chart !== AmCharts.charts[ i ] ) {
AmCharts.charts[ i ].chartCursor.showCursorAt( category );
}
}
} );
// use "zoomed" event to track zooms/pans so we can apply
// the same zoom across other charts
AmCharts.charts[ x ].addListener( "zoomed", function( zoomEvent ) {
// cycle through all available charts and apply same zoom
for ( var i = 0; i < AmCharts.charts.length; i++ ) {
if ( zoomEvent.chart !== AmCharts.charts[ i ] && ( AmCharts.charts[ i ].startIndex != zoomEvent.startIndex || AmCharts.charts[ i ].endIndex != zoomEvent.endIndex ) ) {
AmCharts.charts[ i ].zoomToIndexes( zoomEvent.startIndex, zoomEvent.endIndex );
}
}
} );
}
/**
* Use the last chart to display legend
* We will collect graphs from other charts and mirror them on
* the last chart.
*/
var lastChart = AmCharts.charts[ AmCharts.charts.length - 1 ];
for ( var x = 0; x < ( AmCharts.charts.length - 1 ); x++ ) {
// add graphs
for ( var g = 0; g < AmCharts.charts[ x ].graphs.length; g++ ) {
// create the related graph
var graph = new AmCharts.AmGraph();
graph.lineAlpha = 0;
graph.lineColor = AmCharts.charts[ x ].graphs[ g ].lineColor;
graph.relatedGraph = AmCharts.charts[ x ].graphs[ g ];
graph.valueField = "value_" + x + "_" + g;
graph.showBalloon = false;
graph.includeInMinMax = false;
graph.title = AmCharts.charts[ x ].graphs[ g ].title;
lastChart.addGraph( graph );
// mirror data for rollovers
for ( var i = 0; i < lastChart.dataProvider.length; i++ ) {
lastChart.dataProvider[ i ][ graph.valueField ] = AmCharts.charts[ x ].dataProvider[ i ][ AmCharts.charts[ x ].graphs[ g ].valueField ];
}
}
}
/**
* Add events to the legend of last chart so we can toggle
* graphs of the other charts
*/
lastChart.addListener( "init", function() {
// hide graph
lastChart.legend.addListener( "hideItem", function( event ) {
if ( event.dataItem.relatedGraph !== undefined ) {
event.dataItem.relatedGraph.chart.hideGraph( event.dataItem.relatedGraph );
}
} );
// show graph
lastChart.legend.addListener( "showItem", function( event ) {
if ( event.dataItem.relatedGraph !== undefined ) {
event.dataItem.relatedGraph.chart.showGraph( event.dataItem.relatedGraph );
}
} );
} );
.chartdiv {
width: 100%;
height: 300px;
font-size: 11px;
}
<script src="http://www.amcharts.com/lib/3/amcharts.js"></script>
<script src="http://www.amcharts.com/lib/3/serial.js"></script>
<script src="http://www.amcharts.com/lib/3/themes/light.js"></script>
<div id="chartdiv1" class="chartdiv"></div>
<div id="chartdiv2" class="chartdiv"></div>
i am facing quite a problem which is to create the nice graph from http://www.amcharts.com/ but i need to retrieve data from my sql database. But i don't know how to place inside. Please guide me. Below is the way how the graph displayed, but i wanted to work with data from database. Thank you.
<script type="text/javascript">
var chartData = generateChartData();
function generateChartData() {
var chartData = [];
var firstDate = new Date(2012, 0, 1);
firstDate.setDate(firstDate.getDate() - 500);
firstDate.setHours(0, 0, 0, 0);
for (var i = 0; i < 500; i++) {
var newDate = new Date(firstDate);
newDate.setDate(newDate.getDate() + i);
var value = Math.round(Math.random() * (40 + i)) + 100 + i;
chartData.push({
date: newDate,
value: value
});
}
return chartData;
}
AmCharts.makeChart("chartdiv", {
type: "stock",
pathToImages: "../amcharts/images/",
dataSets: [{
color: "#b0de09",
fieldMappings: [{
fromField: "value",
toField: "value"
}],
dataProvider: chartData,
categoryField: "date"
}],
panels: [{
showCategoryAxis: true,
title: "Value",
eraseAll: false,
labels: [{
x: 0,
y: 100,
text: "Click on the pencil icon on top-right to start drawing",
align: "center",
size: 16
}],
stockGraphs: [{
id: "g1",
valueField: "value",
bullet: "round",
bulletColor: "#FFFFFF",
bulletBorderColor: "#00BBCC",
bulletBorderAlpha: 1,
bulletBorderThickness: 2,
bulletSize: 7,
lineThickness: 2,
lineColor: "#00BBCC",
useDataSetColors: false
}],
stockLegend: {
valueTextRegular: " ",
markerType: "none"
},
drawingIconsEnabled: true
}],
chartScrollbarSettings: {
graph: "g1"
},
chartCursorSettings: {
valueBalloonsEnabled: true
},
periodSelector: {
position: "bottom",
periods: [{
period: "DD",
count: 10,
label: "10 days"
}, {
period: "MM",
count: 1,
label: "1 month"
}, {
period: "YYYY",
count: 1,
label: "1 year"
}, {
period: "YTD",
label: "YTD"
}, {
period: "MAX",
label: "MAX"
}]
}
});
</script>
Can you generate this script in your code behind ( using a string builder for example ) then use this
ScriptManager.RegisterStartupScript(this, this.GetType(), "", "'" + YourStringBuild.toString() + "'", true);