d3 stacked to grouped bar chart date axis - date

I am building a d3 function to build a grouped bar chart that can transition into a stacked bar chart. Everything was working fine until I tried to bring in date values for the x-axis. Now the chart only graphs one bar. The X axis shows correctly but the position of the rectangles seems to be off. My jsFiddle is posted at the bottom of the code.
function createChart(inputdata,chartname,inputtop,inputbottom,inputwidth,inputheight){
var stack = d3.layout.stack(),
layers = inputdata,
m = layers[0].length, // number of samples per layer
n = layers.length, // number of layers
data = stack(d3.range(n).map(function(d) { return layers[d]; }));
var yGroupMax = d3.max(data, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
yStackMax = d3.max(data, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: inputtop, right: 10, bottom: inputbottom, left: 10},
width = inputwidth - margin.left - margin.right,
height = inputheight - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([0, width], .08);
var xTime = d3.time.scale()
.domain([new Date("2016-01-01"), d3.time.day.offset(new Date("2016-01-04"), 1)])
.rangeRound([0, width - margin.left - margin.right]);
var xAxisTime = d3.svg.axis()
.scale(xTime)
.orient('bottom')
.ticks(d3.time.days, 1)
.tickFormat(d3.time.format('%a %d'))
.tickSize(0)
.tickPadding(8);
var y = d3.scale.linear()
.domain([0, yStackMax])
.range([height, 0]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(2)
.tickPadding(6)
.outerTickSize(0);
var svg = d3.select(chartname).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(data)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
var rect = layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", height)
.attr("width", x.rangeBand())
.attr("height", 0);
rect.transition()
.delay(function(d, i) { return i * 10; })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisTime)
svg.append("g")
.attr("class", "yaxis")
.attr("transform", "translate(" + (Number(margin.left) + 14) + ",0)")
.call(yAxis);
svg.select("g.yaxis").selectAll(".tick")
.each(function (d) {
if ( d === 0 ) {
this.remove();
}
});
d3.selectAll("input").on("change", change);
var timeout = setTimeout(function() {
d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
d3.select("input[value=\"0\"]").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(timeout);
if (this.value === "grouped") transitionGrouped();
if (this.value === "stacked") transitionStacked();
//else transitionStacked();
}
function transitionGrouped() {
y.domain([0, yGroupMax]);
var allchart = d3.selectAll(".chart").selectAll(".layer").selectAll("rect"),
axistran = d3.selectAll(".chart");
allchart.transition()
.ease("linear")
.duration(300)
.delay(function(d, i) { return i * 10; })
.attr("x", function(d, i, j) { return x(d.x) + x.rangeBand() / n * j; })
.attr("width", x.rangeBand() / n)
.transition()
.duration(200)
.ease("linear")
.attr("y", function(d) { return y(d.y); })
.attr("height", function(d) { return height - y(d.y); });
axistran.select("g.yaxis").transition()
.duration(600)
.call(yAxis);
axistran.select("g.yaxis").selectAll(".tick")
.each(function (d) {
if ( d === 0 ) {
this.remove();
}
});
};
function transitionStacked() {
y.domain([0, yStackMax]);
var allchart = d3.selectAll(".chart").selectAll(".layer").selectAll("rect"),
axistran = d3.selectAll(".chart");
allchart.transition()
.ease("linear")
.duration(300)
.delay(function(d, i) { return i * 10; })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.transition()
.duration(200)
.ease("linear")
.attr("x", function(d) { return x(d.x); })
.attr("width", x.rangeBand());
axistran.select("g.yaxis").transition()
.duration(600)
.call(yAxis);
axistran.select("g.yaxis").selectAll(".tick")
.each(function (d) {
if ( d === 0 ) {
this.remove();
}
});
};
};
JSFiddle Link

You've created a time scale in variable xTime, but you aren't using it to draw the rects:
var rect = layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return xTime(new Date(d.x)); //<-- don't use x here
});
Also, If you are using a time axis, I recommend changing your input data into datetimes instead of new Date( everywhere.
Finally, your selections are wrong in your transitions.
Here it is all fixed up:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 700px;
}
text {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
form {
position: absolute;
right: 10px;
top: 10px;
}
</style>
</head>
<body>
<form>
<label>
<input type="radio" name="mode" value="grouped" /> Grouped
</label>
<label>
<input type="radio" name="mode" value="stacked" checked="" /> Stacked
</label>
</form>
<chart_1></chart_1>
<script>
var layers = [{
"x": "2016-01-01",
"y": 4,
"z": 5
}, {
"x": "2016-01-02",
"y": 5,
"z": 6
}, {
"x": "2016-01-03",
"y": 6,
"z": 3
}, {
"x": "2016-01-04",
"y": 7,
"z": 1
}];
var converted = convertjson(layers, "x", ["y", "z"]);
createChart(converted, "chart_1", 40, 20, 700, 550);
function createChart(inputdata, chartname, inputtop, inputbottom, inputwidth, inputheight) {
var stack = d3.layout.stack(),
layers = inputdata,
m = layers[0].length, // number of samples per layer
n = layers.length, // number of layers
data = stack(d3.range(n).map(function(d) {
return layers[d];
}));
var yGroupMax = d3.max(data, function(layer) {
return d3.max(layer, function(d) {
return d.y;
});
}),
yStackMax = d3.max(data, function(layer) {
return d3.max(layer, function(d) {
return d.y0 + d.y;
});
});
var margin = {
top: inputtop,
right: 10,
bottom: inputbottom,
left: 10
},
width = inputwidth - margin.left - margin.right,
height = inputheight - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([0, width], .08);
var xTime = d3.time.scale()
.domain([new Date("2016-01-01"), d3.time.day.offset(new Date("2016-01-04"), 1)])
.rangeRound([0, width - margin.left - margin.right]);
var xAxisTime = d3.svg.axis()
.scale(xTime)
.orient('bottom')
.ticks(d3.time.days, 1)
.tickFormat(d3.time.format('%a %d'))
.tickSize(0)
.tickPadding(8);
var y = d3.scale.linear()
.domain([0, yStackMax])
.range([height, 0]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(2)
.tickPadding(6)
.outerTickSize(0);
var svg = d3.select(chartname).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(data)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) {
return color(i);
});
var rect = layer.selectAll("rect")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("x", function(d) {
return xTime(d.x);
})
.attr("y", height)
.attr("width", x.rangeBand())
.attr("height", 0);
rect.transition()
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxisTime)
svg.append("g")
.attr("class", "yaxis")
.attr("transform", "translate(" + (Number(margin.left) + 14) + ",0)")
.call(yAxis);
svg.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
d3.selectAll("input").on("change", change);
/*
var timeout = setTimeout(function() {
d3.select("input[value=\"grouped\"]").property("checked", true).each(change);
d3.select("input[value=\"0\"]").property("checked", true).each(change);
}, 2000);
*/
function change() {
//clearTimeout(timeout);
if (this.value === "grouped") transitionGrouped();
if (this.value === "stacked") transitionStacked();
//else transitionStacked();
}
function transitionGrouped() {
y.domain([0, yGroupMax]);
var allchart = d3.selectAll(".layer").selectAll("rect"),
axistran = d3.selectAll(".chart");
console.log(allchart)
allchart.transition()
.ease("linear")
.duration(300)
.delay(function(d, i) {
return i * 10;
})
.attr("x", function(d, i, j) {
return xTime(d.x) + x.rangeBand() / n * j;
})
.attr("width", x.rangeBand() / n)
.transition()
.duration(200)
.ease("linear")
.attr("y", function(d) {
return y(d.y);
})
.attr("height", function(d) {
return height - y(d.y);
});
axistran.select("g.yaxis").transition()
.duration(600)
.call(yAxis);
axistran.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
};
function transitionStacked() {
y.domain([0, yStackMax]);
var allchart = d3.selectAll(".layer").selectAll("rect"),
axistran = d3.selectAll(".chart");
allchart.transition()
.ease("linear")
.duration(300)
.delay(function(d, i) {
return i * 10;
})
.attr("y", function(d) {
return y(d.y0 + d.y);
})
.attr("height", function(d) {
return y(d.y0) - y(d.y0 + d.y);
})
.transition()
.duration(200)
.ease("linear")
.attr("x", function(d) {
return xTime(d.x);
})
.attr("width", x.rangeBand());
axistran.select("g.yaxis").transition()
.duration(600)
.call(yAxis);
axistran.select("g.yaxis").selectAll(".tick")
.each(function(d) {
if (d === 0) {
this.remove();
}
});
};
};
function convertjson(data, xValue, yArray) {
var arrayconvertedjson = [];
var convertedjson = [];
for (var j = 0; j < yArray.length; j++) {
for (var i = 0; i < data.length; i++) {
convertedjson.push({
"x": new Date(data[i][xValue]),
"y": data[i][yArray[j]]
});
};
arrayconvertedjson.push(convertedjson)
convertedjson = [];
};
return arrayconvertedjson;
};
</script>
</body>
</html>

Related

How can one make an ellipse in react-leaflet?

I am trying to draw an ellipse on a map made using react-leaflet, which has built-in support for circles and rectangles.
To achieve this, I am using code to produce an ellipse in (non-react) leaflet from here, that I have adapted and pasted below:
import * as L from 'leaflet';
L.SVG.include ({
_updateEllipse: function (layer) {
var // c = layer._point,
rx = layer._radiusX,
ry = layer._radiusY,
phi = layer._tiltDeg,
endPoint = layer._endPointParams;
var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
'A' + rx + ',' + ry + ',' + phi + ',' +
endPoint.largeArc + ',' + endPoint.sweep + ',' +
endPoint.x1 + ',' + endPoint.y1 + ' z';
this._setPath(layer, d);
}
});
L.Canvas.include ({
_updateEllipse: function (layer) {
if (layer._empty()) { return; }
var p = layer._point,
ctx = this._ctx,
r = layer._radiusX,
s = (layer._radiusY || r) / r;
this._drawnLayers[layer._leaflet_id] = layer;
ctx.save();
ctx.translate(p.x, p.y);
if (layer._tilt !== 0) {
ctx.rotate( layer._tilt );
}
if (s !== 1) {
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.restore();
this._fillStroke(ctx, layer);
},
});
L.Ellipse = L.Path.extend({
options: {
fill: true,
startAngle: 0,
endAngle: 359.9
},
initialize: function (latlng, radii, tilt, options) {
L.setOptions(this, options);
this._latlng = L.latLng(latlng);
if (tilt) {
this._tiltDeg = tilt;
} else {
this._tiltDeg = 0;
}
if (radii) {
this._mRadiusX = radii[0];
this._mRadiusY = radii[1];
}
},
setRadius: function (radii) {
this._mRadiusX = radii[0];
this._mRadiusY = radii[1];
return this.redraw();
},
getRadius: function () {
return new L.point(this._mRadiusX, this._mRadiusY);
},
setTilt: function (tilt) {
this._tiltDeg = tilt;
return this.redraw();
},
getBounds: function () {
// TODO respect tilt (bounds are too big)
var lngRadius = this._getLngRadius(),
latRadius = this._getLatRadius(),
latlng = this._latlng;
return new L.LatLngBounds(
[latlng.lat - latRadius, latlng.lng - lngRadius],
[latlng.lat + latRadius, latlng.lng + lngRadius]);
},
// #method setLatLng(latLng: LatLng): this
// Sets the position of a circle marker to a new location.
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
this.redraw();
return this.fire('move', {latlng: this._latlng});
},
// #method getLatLng(): LatLng
// Returns the current geographical position of the circle marker
getLatLng: function () {
return this._latlng;
},
setStyle: L.Path.prototype.setStyle,
_project: function () {
var lngRadius = this._getLngRadius(),
latRadius = this._getLatRadius(),
latlng = this._latlng,
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);
this._point = this._map.latLngToLayerPoint(latlng);
this._radiusX = Math.max(this._point.x - pointLeft.x, 1);
this._radiusY = Math.max(pointBelow.y - this._point.y, 1);
this._tilt = Math.PI * this._tiltDeg / 180;
this._endPointParams = this._centerPointToEndPoint();
this._updateBounds();
},
_updateBounds: function () {
// http://math.stackexchange.com/questions/91132/how-to-get-the-limits-of-rotated-ellipse
var sin = Math.sin(this._tilt);
var cos = Math.cos(this._tilt);
var sinSquare = sin * sin;
var cosSquare = cos * cos;
var aSquare = this._radiusX * this._radiusX;
var bSquare = this._radiusY * this._radiusY;
var halfWidth = Math.sqrt(aSquare*cosSquare+bSquare*sinSquare);
var halfHeight = Math.sqrt(aSquare*sinSquare+bSquare*cosSquare);
var w = this._clickTolerance();
var p = [halfWidth + w, halfHeight + w];
this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
},
_update: function () {
if (this._map) {
this._updatePath();
}
},
_updatePath: function () {
this._renderer._updateEllipse(this);
},
_getLatRadius: function () {
return (this._mRadiusY / 40075017) * 360;
},
_getLngRadius: function () {
return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
},
_centerPointToEndPoint: function () {
// Convert between center point parameterization of an ellipse
// too SVG's end-point and sweep parameters. This is an
// adaptation of the perl code found here:
// http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
var c = this._point,
rx = this._radiusX,
ry = this._radiusY,
theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
theta1 = this.options.startAngle * (Math.PI / 180),
delta = this.options.endAngle,
phi = this._tiltDeg * (Math.PI / 180);
// Determine start and end-point coordinates
var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
Math.sin(-phi) * ry * Math.sin(theta1);
var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
Math.cos(phi) * ry * Math.sin(theta1);
var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
Math.sin(-phi) * ry * Math.sin(theta2);
var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
Math.cos(phi) * ry * Math.sin(theta2);
var largeArc = (delta > 180) ? 1 : 0;
var sweep = (delta > 0) ? 1 : 0;
return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
'sweep': sweep, 'x1': x1, 'y1': y1};
},
_empty: function () {
return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds);
},
_containsPoint : function (p) {
// http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
var sin = Math.sin(this._tilt);
var cos = Math.cos(this._tilt);
var dx = p.x - this._point.x;
var dy = p.y - this._point.y;
var sumA = cos * dx + sin * dy;
var sumB = sin * dx - cos * dy;
return sumA * sumA / (this._radiusX * this._radiusX) + sumB * sumB / (this._radiusY * this._radiusY) <= 1;
}
});
export const lellipse = function (latlng, radii, tilt, options) {
return new L.Ellipse(latlng, radii, tilt, options);
};
To create an ellipse to use with react-leaflet, I followed the example of Circle in react-leaflet to produce the following Ellipse component:
import PropTypes from 'prop-types'
import { lellipse as LeafletEllipse } from '../l.ellipse';
import Path from './Path'
import children from './propTypes/children'
import latlng from './propTypes/latlng'
import type { LatLng, MapLayerProps, PathOptions } from './types'
type LeafletElement = LeafletEllipse
type Props = {
center: LatLng,
mSemiMajorAxis: number,
mSemiMinorAxis: number,
degreeTiltFromWest: number,
} & MapLayerProps &
PathOptions &
Object
export default class Ellipse extends Path<LeafletElement, Props> {
static propTypes = {
center: latlng.isRequired,
mSemiMajorAxis: PropTypes.number.isRequired,
mSemiMinorAxis: PropTypes.number.isRequired,
degreeTiltFromWest: PropTypes.number.isRequired,
children: children,
}
createLeafletElement(props: Props): LeafletElement {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], this.getOptions(options))
}
updateLeafletElement(fromProps: Props, toProps: Props) {
if (toProps.center !== fromProps.center) {
this.leafletElement.setLatLng(toProps.center);
}
if (toProps.degreeTiltFromWest !== fromProps.degreeTiltFromWest) {
this.leafletElement.setTilt(toProps.degreeTiltFromWest);
}
if (toProps.mSemiMinorAxis !== fromProps.mSemiMinorAxis || toProps.mSemiMajorAxis !== fromProps.mSemiMajorAxis) {
this.leafletElement.setRadius([toProps.mSemiMajorAxis, toProps.mSemiMinorAxis]);
}
}
}
The problem with the code is that it does not render an ellipse and it does not throw any errors. Could someone suggest how to render an ellipse with react-leaflet? Thanks.
Your createLeafletElement function is missing the tilt parameter. It should be:
createLeafletElement(props) {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], degreeTiltFromWest, this.getOptions(options))
}
See below for the complete file (in ES6 rather than in typescript, as I find it clearer).
import React, { PropTypes } from 'react';
import { lellipse as LeafletEllipse } from './l.ellipse';
import { Path, withLeaflet } from 'react-leaflet';
class Ellipse extends Path {
static propTypes = {
center: PropTypes.arrayOf(PropTypes.number).isRequired,
mSemiMajorAxis: PropTypes.number.isRequired,
mSemiMinorAxis: PropTypes.number.isRequired,
degreeTiltFromWest: PropTypes.number.isRequired
}
createLeafletElement(props) {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], degreeTiltFromWest, this.getOptions(options))
}
updateLeafletElement(fromProps, toProps) {
if (toProps.center !== fromProps.center) {
this.leafletElement.setLatLng(toProps.center);
}
if (toProps.degreeTiltFromWest !== fromProps.degreeTiltFromWest) {
this.leafletElement.setTilt(toProps.degreeTiltFromWest);
}
if (toProps.mSemiMinorAxis !== fromProps.mSemiMinorAxis || toProps.mSemiMajorAxis !== fromProps.mSemiMajorAxis) {
this.leafletElement.setRadius([toProps.mSemiMajorAxis, toProps.mSemiMinorAxis]);
}
}
}
export default class withLeaflet(Ellipse);

D3 Axis transition

The visualization is a bar chart in which the range of the bar heights is great (several orders of magnitude). I have built a transition triggered by a click that increases the heights of all the bars by a multiplicative amount. I would also like the y-axis ticks and labels to transition to the new scale of the bars. I have altered the domain of the y-axis scale. The bar height transition works fine. The axis transition does nothing.I cannot seem to get the axis transition correct. Can anyone help?
The code is a simple example of what I am trying to do - data is fixed.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script type="text/javascript" src="../static/js/d3.js"></script>
<style type="text/css">
.axis, .axis text {
font: 10px sans-serif;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.tick text {
font-family: sans-serif;
fill: black;
}
</style>
</head>
<body>
<script type="text/javascript">
var xSize = 900,
ySize = 500;
var data = [10000, 3000, 100, 40, 10, 5, 1];
var margin = {top:40, right: 40, bottom:60, left: 60};
var width = xSize - margin.left - margin.right;
var height = ySize - margin.top - margin.bottom;
var barwidth = width / data.length;
var xscale = d3.scale.linear().domain([0, data.length]).range([0, width]);
var maxData = d3.max(data);
var yscale = d3.scale.linear().domain([0, maxData]).range([height, 0]);
var xAxis = d3.svg.axis().scale(xscale).orient("bottom");
var yAxis = d3.svg.axis().scale(yscale).orient("left");
var fieldView = d3.select("body")
.append("svg")
.attr("width", xSize)
.attr("height", ySize)
.on("click", update)
;
var chart = fieldView
.append("g")
.attr("id", "clip")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
;
var bar = chart.selectAll("g")
.data(data)
.enter()
.append("rect")
.attr("x", function(d, i) {return xscale(i);})
.attr("y", function(d) { return yscale(d);})
.attr("height", function(d) { return height - yscale(d);})
.attr("width", barwidth)
.attr("fill", "steelblue")
.attr("clip-path", "url(#clip)")
;
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
;
chart.append("g")
.attr("class", "y axis")
.call(yAxis)
;
var transitionScale = 1;
var transitionMultiplier = 5;
var transitionYscale = d3.scale.linear().domain([0, maxData])
.range([height, 0]);
function update() {
// new multiplier for data
transitionScale *= transitionMultiplier;
// update to y axis domain
yscale.domain([0, maxData / transitionScale]);
// transition the bars
bar
.transition()
.attr("y", function(d) {return yscale(d);})
.attr("height", function(d) {return height - yscale(d);})
.duration(2000)
.ease("linear")
;
// transition the y-axis
chart
.selectAll(".y axis")
.transition()
.call(yAxis)
;
}
</script>
</body>
</html>
Changing the axis transition selection from:
chart.selectAll(".y axis")
to:
chart.selectAll(".y.axis")
causes the axis to transition correctly. I don't know why, however.

Visualization of recursive Fibonacci calculation

Is there any program/website which can visualize a graph of a recursive Fibonacci calculation.
I want to show how many recursion steps will be needed.
I found this to be a neat little challenge, let me share my implementation:
var canvas = document.getElementById("canvas");
var width = canvas.width;
var height = canvas.height;
var ctx = canvas.getContext("2d");
var FN = 8;
var FONT_SIZE = 11; // in points
var SHOW_BOXES = false;
var SHOW_DISCS = true;
var SHOW_FIB_N = true;
function Tree(fn) {
var pdata = {};
pdata.lhs = null;
pdata.rhs = null;
pdata.fn = fn;
this.getLeft = function() { return pdata.lhs; };
this.setLeft = function(node) { pdata.lhs = node; };
this.getRight = function() { return pdata.rhs; };
this.setRight = function(node) { pdata.rhs = node; };
this.getFn = function() { return pdata.fn; };
}
function fib(n) {
if(n == 0)
return new Tree(0);
if(n == 1)
return new Tree(1);
else {
var lhs = fib(n-1);
var rhs = fib(n-2);
var root = new Tree(lhs.getFn() + rhs.getFn());
root.setLeft(lhs);
root.setRight(rhs);
return root;
}
}
var root = fib(FN);
function Box(x0, y0, x1, y1) {
if(arguments.length < 4) {
x0 = 1;
y0 = 1;
x1 = -1;
y1 = -1;
}
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
this.width = function() { return this.x1 - this.x0; };
this.height = function() { return this.y1 - this.y0; };
this.offset = function(x, y) {
this.x0 += x;
this.y0 += y;
this.x1 += x;
this.y1 += y;
};
this.extend = function(x, y) {
if(this.x1 < this.x0 || this.y1 < this.y0) {
this.x0 = x;
this.x1 = x;
this.y0 = y;
this.y1 = y;
} else {
this.x0 = this.x0 < x ? this.x0 : x;
this.y0 = this.y0 < y ? this.y0 : y;
this.x1 = this.x1 > x ? this.x1 : x;
this.y1 = this.y1 > y ? this.y1 : y;
}
}
};
(function () {
// assume spheres of radius 0.5
function setBounds(node, offX, offY) {
var bbox = new Box(offX, offY, offX + 1, offY + 1);
if(node.getLeft() != null || node.getRight() != null) {
var lhs = node.getLeft(), rhs = node.getRight();
if(lhs != null) {
setBounds(lhs, offX + 0, offY + 1.1);
bbox.extend(lhs.bbox.x0, lhs.bbox.y0);
bbox.extend(lhs.bbox.x1, lhs.bbox.y1);
}
if(rhs != null) {
setBounds(rhs, offX + (lhs != null ? lhs.bbox.width() : 0), offY + 1.1);
bbox.extend(rhs.bbox.x0, rhs.bbox.y0);
bbox.extend(rhs.bbox.x1, rhs.bbox.y1);
}
}
node.bbox = bbox;
}
setBounds(root, 0, 0);
})();
var transf = (function() {
var b = 2;
var sx = (width - 2 * b) / root.bbox.width();
var sy = (height - 2 * b) / root.bbox.height();
return {
ox: b / sx - root.bbox.x0,
oy: b / sx - root.bbox.y0,
sx: sx,
sy: sy,
};
})();
transf.smin = Math.min(transf.sx, transf.sy);
ctx.clearRect(0, 0, width, height);
(function(g) {
g.font = FONT_SIZE + "pt Arial";
g.textAlign = "center";
g.strokeStyle = "#000000";
function draw(node, pX, pY) {
if(node == null) return;
var cX = (node.bbox.x0 + node.bbox.x1) / 2;
var cY = (node.bbox.y0 + 0.5);
var radius = 0.475;
cX = transf.sx * (cX + transf.ox);
cY = transf.sy * (cY + transf.oy);
radius *= transf.smin;
draw(node.getLeft(), cX, cY);
draw(node.getRight(), cX, cY);
if(SHOW_BOXES) {
g.fillStyle = "#ff0000";
g.beginPath();
g.moveTo(transf.sx * (node.bbox.x0 + transf.ox), transf.sy * (node.bbox.y0 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x1 + transf.ox), transf.sy * (node.bbox.y0 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x1 + transf.ox), transf.sy * (node.bbox.y1 + transf.oy));
g.lineTo(transf.sx * (node.bbox.x0 + transf.ox), transf.sy * (node.bbox.y1 + transf.oy));
g.closePath();
g.stroke();
}
if(SHOW_DISCS) {
if(arguments.length >= 3) {
g.beginPath();
g.moveTo(pX, pY);
g.lineTo(cX, cY);
g.stroke();
}
g.fillStyle = "#ff0000";
g.beginPath();
g.arc(cX, cY, radius, 0, 2 * Math.PI);
g.fill();
g.stroke();
}
if(SHOW_FIB_N) {
g.fillStyle = "#0000ff";
g.fillText(node.getFn(), cX, cY + FONT_SIZE / 2);
}
}
draw(root);
})(ctx);
<canvas id="canvas" width="800" height="480" />

Cache tiles from Openstreetmap

I want to make a offline map app in html. I'm using Openlayers 3
I can save tiles from Openstreetmap, thats not the problem.
My question:
When I have a square like:
LonLat1, LonLat2
1------
-------2
How I can calculate which tiles I have to grab, on multiple zoomlevels
Caching all: '{z}/{x}/{y}.png' tiles
Later I use them offline with like this
var map = new ol.Map({
target: 'map',
layers: [new ol.layer.Tile({
source: new ol.source.XYZ({
url: '{z}/{x}/{y}.png'
})
})], ...
I solved the problem Tnx to John Barca's link!
Made a fiddle:
Fiddle
Fiddle Full Screen
includes ol3.js / ol3.css / jquery
html:
javascript:
var map;
var icons = [];
var selMarker;
var click = 0;
var tmpCoord;
map = new ol.Map({
layers: [
new ol.layer.Tile({source: new ol.source.OSM()}),
new ol.layer.Vector({
source: new ol.source.Vector({features:icons})
})],
renderer: "canvas",
target: 'map',
view: new ol.View2D({
zoom: 11
})
});
map.getView().setCenter(transform(5, 52));
map.on("click", function(evt){
var coordinate = evt.coordinate;
if(click == 0) {
tmpCoord = coordinate;
click++;
} else {
addVierkant(tmpCoord, coordinate);
click = 0;
getAllTiles(tmpCoord, coordinate);
}
addCircle(coordinate);
})
function transform(lng, lat) {
return ol.proj.transform([lng, lat], 'EPSG:4326', 'EPSG:3857');
}
function transform2(lng, lat) {
return ol.proj.transform([lng, lat], 'EPSG:3857', 'EPSG:4326');
}
function addCircle(c) {
var source = map.getLayers().getAt(1).getSource();
var iconFeature = new ol.Feature({
geometry: new ol.geom.Circle(c, 300),
});
iconFeature.setStyle(getCircleStyle());
icons[icons.length] = iconFeature;
icons[icons.length - 1][0] = "circle";
source.addFeature(iconFeature);
return iconFeature;
}
function addVierkant(c1, c2) {
var source = map.getLayers().getAt(1).getSource();
p1 = c1;
p2 = [c1[0], c2[1]];
p3 = c2;
p4 = [c2[0], c1[1]];
var coords = [p1,p2,p3,p4,p1];
var iconFeature = new ol.Feature({
geometry: new ol.geom.LineString(coords),
});
iconFeature.setStyle(getVierkantStyle());
icons[icons.length] = iconFeature;
icons[icons.length - 1][0] = "vierkant";
source.addFeature(iconFeature);
return iconFeature;
}
function getCircleStyle() {
var iconStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(0,0,0,0.3)',
width: 4
}),
fill: new ol.style.Fill({
color: 'rgba(255,255,255,0.9)'
})
});
return iconStyle;
}
function getVierkantStyle() {
var iconStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(255,255,255,0.9)',
width: 2
})
});
return iconStyle;
}
function getAllTiles(coord1, coord2) {
out1 = getTileURL(coord1, 10);
out2 = getTileURL(coord2, 10);
$("#output").html("zoom ------ " + out1[0] + "<br>from " + out1[1] + " to " + out2[1] + "<br>from " + out1[2] + " to " + out2[2]);
}
function getTileURL(coord, zoom) {
cor = transform2(coord[0], coord[1]);
lon = cor[0];
lat = cor[1];
var out = [];
var xtile = parseInt(Math.floor( (lon + 180) / 360 * (1<<zoom) ));
var ytile = parseInt(Math.floor( (1 - Math.log(Math.tan(lat.toRad()) + 1 / Math.cos(lat.toRad())) / Math.PI) / 2 * (1<<zoom) ));
log(">> " + zoom + "/" + xtile + "/" + ytile);
out[0] = zoom;
out[1] = xtile;
out[2] = ytile;
return out;
}
function log(text) {
console.log(text);
}
if (typeof(Number.prototype.toRad) === "undefined") {
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
}

d3.js Force Chart online now app with different symbol shapes

I've built a force chart application.
http://jsfiddle.net/NYEaX/130/
I want to be able to define different shapes and maybe even change the fill backgrounds for specific user images. I'm aware of the def pattern method - is this best way to go about it. How do I create the function to control the shape of the node?
var circle = svg.append("svg:g")
.attr("class", "circle")
.selectAll("circle")
.data(force.nodes())
.enter()
.append("svg:circle")
.attr("class", function(d) {
return "level"+d.level;
})
.attr("r", function(d) {
if(d.level > 0){
return getRadius(d);
}
else{
return "0";
}
})
.style("fill", function(d) {
return color(d.group);
})
Here is the latest example - I've pushed the transform attributes on the exit to have the shapes fly to the left and up - keen to enhance/clean up the code - any help from anyone to make it more efficient?
**LATEST CODE -- http://jsfiddle.net/NYEaX/617/ **
var users = that.vis.selectAll("path")
.data(that.nodes);
// Enter
users
.enter().append("path")
.attr("id", function(d){
return d.id;
})
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("d", d3.svg.symbol()
.size(function(d) { return d.size; })
.type(function(d) { return d.type; }))
.style("fill", "steelblue")
.style("stroke", "white")
.style("stroke-width", "1.5px")
.call(that.force.drag);
// Update
users
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.transition()
.duration(500);
// Exit
users.exit()
.transition()
.duration(750)
.attr("transform", function(d) { return "translate(-100,100)"; })
.remove();
I've modified the example to start to mimic an "online now" application. I want to represent users via the symbols and have the symbols gravitate towards the center - much like the example.
http://jsfiddle.net/NYEaX/231/
I've toggled the data and managed to update the force chart - but its snapping instead of fading in and out the symbols and there is no gravitation - on start up or on update.
this is the current plot code
//__morph plots
var plots = this.vis.selectAll("path")
.data(this.nodes)
// Enter
plots.enter()
.append("svg:path")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("d", d3.svg.symbol()
.size(function(d) { return d.size; })
.type(function(d) { return d.type; }))
.style("fill", "steelblue")
.style("stroke", "white")
.style("stroke-width", "1.5px")
.call(this.force.drag);
// Update
plots
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.transition()
.duration(500)
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
// Exit
plots.exit()
.transition()
.duration(250)
.remove();
//__morph plots