How to add buffer at top of chart using d3.js - javascript

I'm drawing a CDF chart using d3.js.
In the chart I have a symbol at 100th tick at the top of the chart.
The problem is that the symbol is getting cut-off.
I might not be able to post the entire code here posting only the part where the axis are drawn.
Attached is a screenshot of the top most part of the chart.
Below is the code to draw the axis.
const margin = {
top: 5,
right: 20,
bottom: 50,
left: 60
};
this.x = this.addXScale(this.width - margin.left - margin.right);
this.y = this.addYScale(this.height - margin.top - margin.bottom);
this.yAxis = this.addLeftAxis(this.y , this.width - margin.left - margin.right, margin);
this.xAxis = this.addBottomAxis(this.x , this.height - (margin.top+0) - margin.bottom, margin);
// Define svg component and attributes
const svg = this.chartService.drawChartArea(chartElement, this.height + this.xDelta,this.width, margin);
How do I add a buffer space at the top of the chart so that the symbol doesn't get cut off.
I tried adding a buffer space to x and y (margin.top+7) but on adding that the graph plot also goes outside the 100 line.
this.y = this.addYScale(this.height - (margin.top+7) - margin.bottom);

You can adjust the range of the y-axis scale by increasing the maximum value by the amount of buffer space you want to add.
Example:
const margin = {
top: 20, // increase top margin to create buffer space
right: 20,
bottom: 50,
left: 60
};
// add buffer space to y-axis range
const yMax = d3.max(data, d => d.y) + bufferSpace; // replace `data` with your actual data array and `bufferSpace` with the amount of buffer space you want to add
this.y = this.addYScale(this.height - margin.top - margin.bottom, yMax);
// draw y-axis with updated scale
this.yAxis = this.addLeftAxis(this.y, this.width - margin.left - margin.right, margin);
To prevent the symbol from being cut off in the chart, you can make the chart taller by increasing the maximum value of the y-axis. You can control how much extra space is added at the top by adjusting the bufferSpace variable.

Related

How to display the values from this array in the console of javascript?

How can I display the values of the given array in javascript? In other words, how can use console.log over "pie" to display (42.9, 37.9 and 19.2)?
It tried console.log(Object.values(pie)) but it didn't work. Thanks a lot.
This is how I created the array:
var width = 350
height = 350
margin = 40
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = Math.min(width, height) / 2 - margin
// append the svg object to the div called 'my_dataviz'
var svg = d3.select("#my_dataviz_b")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var color =["#98abc5", "#8a89a6", "#7b6888"]
var annotations = ["Home win", "Draw game", "Away win"]
var data = d3.selectAll('.values_half_before').nodes();
var pie = d3.pie() //we create this variable, for the values to be readeable in the console
.value(function(d) {return d.innerHTML; })(data);
You can do it this way:
pie.forEach((item) => {
console.log(item.value)
});
If you are looking to log individual values of your array you could loop over them with a for loop.
for (let i = 0; i < pie.length; i++) {
console.log(pie[i].value);
}
You could also use console.table. This will display the values in a nice table overview.
console.table(pie);

Focusing on geojson in D3.js map

I am trying to view a objects in a topojson file (of buildings in a city) but get the following error:
Error: <path> attribute d: Expected number, "MNaN,NaNLNaN,NaNL…".
Here is my code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.land {
fill: #e5e5e5;
stroke: #000;
stroke-width: 0.2;
stroke-opacity: 0.8;
}
.states {
fill: none;
stroke: #fff;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js"></script>
<script>
var width = 800;
var height = 600;
var projection = d3.geo.mercator()
.center([30, 30])
.scale(500)
.translate([width / 2, height / 2]);
var path = d3.geo.path().projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
queue()
.defer(d3.json, "cairomonuments.json")
.await(ready);
function ready(error, cairo) {
if (error) throw error;
// Refine projection
var b, s, t;
projection.scale(1).translate([0, 0]);
var b = path.bounds(cairo);
var s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
var t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection.scale(s).translate(t);
svg.selectAll("path")
.data(topojson.feature(cairo, cairo.objects.monuments).features)
.enter()
.append('path')
.attr('class', 'land')
.attr('d', path);
}
</script>
</body>
I just want to center the map on my geojson file and flip it sideways. What am I missing?
topojson file here
The problem
The primary issue as far as I can see is this line:
var b = path.bounds(cairo);
path.bounds won't produce expected results with a collection of features (such as your layer). Instead it:
Computes the projected bounding box (in pixels) for the specified
feature. The bounding box is represented by a two-dimensional array:
[[left, top], [right, bottom]] , different from GIS geo.bounds'
convention.
Also, you aren't passing it geojson, you're passing it topojson. If you wanted to use a bounds of a specific feature, your code would look more like:
var b = path.bounds(topojson.feature(cairo, cairo.objects.monuments).features[0]);
Even if you pass it a singular feature in the right format, it still won't project correctly as your scale was defined as 500 earlier when you defined the projection - this will warp the calculations when dynamically re-calculating the scale.
Possible Solution (Keeping d3.js v3)
Topojson generally has a bbox property. You could use this to get your centering coordinate:
var x = (cairo.bbox[0] + cairo.bbox[2]) / 2; // get middle x coordinate
var y = (cairo.bbox[1] + cairo.bbox[3]) / 2; // get middle y coordinate
Note that the order of a geojson or topojson bounding box is : left, bottom, right, top.
So we can easily center the map on the layer center now:
projection.center([x,y]) or projection.rotate([-x,0]).center([0,y]) or projection.rotate([-x,-y]).
Now all that is left is to calculate the scale (set it at one to start).
If path.bounds returns a two coordinate array of the top left and bottom right coordinates ([min x, min y],[max x, max y], in SVG coordinate space), then we can produce an equivalent array using the topojson.bbox:
var b = [ projection([cairo.bbox[0],cairo.bbox[3]]),projection([cairo.bbox[2],cairo.bbox[1]]) ];
Here it's a little tricky as the SVG coordinate space has y coordinates starting from zero at the top (reversed from the geographic features), and the order of coordinates in the bounds is: left top right bottom (again, different than geographic features).
That leaves us with the calculation you already had:
var s = 0.95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
Which altogether gives us:
Initial declaration of scale:
var projection = d3.geo.mercator()
.scale(1)
.translate([width / 2, height / 2]);
Refinement of scale and center based on data layer:
var x = (cairo.bbox[0] + cairo.bbox[2]) / 2;
var y = (cairo.bbox[1] + cairo.bbox[3]) / 2;
projection.rotate([-x,-y]);
var b = [ projection([cairo.bbox[0],cairo.bbox[3]]),projection([cairo.bbox[2],cairo.bbox[1]]) ];
var s = 0.95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height);
projection.scale(s);
Here's a bl.ock demonstrating it all in action.
Flipping the map
There is a seldom used parameter in the projection rotation that allows you to achieve this. In my bl.ock above and in the code block above I used rotate to center the map projection. By adding a third parameter I can rotate the map relative to the viewport:
projection.rotate([-x,-y,90]);

Remove d3 axis transition on initialization

I am using this example to create my own real-time graph using d3. In my version the graph is initialized with existing data. Problem is, the x-axis initialization causes a very small portion of the graph to show while it is transitioning or collapsing on the right before finally showing the normal scale and resultantly the normal graph. I am pretty sure the axis is causing it because the moment the axis returns to normal so does the graph. Is there a way to remove this transition at the begging or otherwise have it not skew the graph or not show until it is ready? Here is the problem in action, better than me trying to explain it: http://codepen.io/Dordan/pen/NbBjPB/
Here is the code snippet for creating the x-axis:
var limit = 60 * 1;
var duration = 750;
var now = new Date(Date.now() - duration);
var x = d3.time.scale().domain([now - (limit - 2), now - duration]).range([0, width]);
var axis = svg.append('g')
.attr('class', 'x-axis')
.attr('transform', 'translate(0,' + height + ')')
.call(x.axis = d3.svg.axis().scale(x).orient('bottom'));
The instantiation of your x scale is missing the '* duration' when you're calculating the domain. Use this instead and it works well:
var x = d3.time.scale().domain([now - (limit - 2) * duration, now - duration]).range([0, width]);

Margin calculation with D3 JavaScript

I'm drawing couple of graphics on a webpage. Let's say I have 4 graphics (A-B-C-D). I would like to lay them out as follows:
A B
C D
But currently they appear like this:
A
B
C
D
I set the margin, width, and height for A as follows:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 270 - margin.top - margin.bottom;
Any ideas on how to set these (or some other) properties for B, C and D so that they appear as intended?
Thanks!
Set them .style('display', 'inline-block') and set the width to less than 50% or put a </br> tag between them.

Adjusting scale of a group to ensure shapes inside are as big as possible in d3 js

I'm using d3 tree layout similar to this example: http://bl.ocks.org/mbostock/4339083
I implemented a search box that when typing, centers your screen on a virtual "average" position of all the appropriate nodes.
I want to adjust the scale, so that selected nodes will be
All Visible
As zoomed in as possible.
If the search match is exactly 1, simulate the clicking on the node, else center to this virtual position.
if (matches[0].length === 1) {
click(matches.datum(), 0, 0, false);
}
else {
var position = GetAveragePosition(matches);
centerToPosition(position.x, position.y, 1);
}
This is what the centerToPosition function looks like:
function centerToPosition(x0, y0, newScale) {
if (typeof newScale == "undefined") {
scale = zoomListener.scale();
}
else {
scale = newScale;
}
var x = y0 * -1; //not sure why this is.. but it is
var y = x0 * -1;
x = x * scale + viewerWidth / 2;
y = y * scale + viewerHeight / 2;
d3.select('g').transition()
.duration(duration)
.attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
zoomListener.scale(scale);
zoomListener.translate([x, y]);
}
So how can I calculate the new scale? I tried different variations by taking the extents of the data points
var xExtent = d3.extent(matches.data(), function (d) {
return d.x0;
});
var yExtent = d3.extent(matches.data(), function (d) {
return d.y0;
});
Also tried looking at the transform properties of the group before centering the screen.
var components = d3.transform(svgGroup.attr("transform"));
I'll try to add a js fiddle soon!
EDIT: Here it is: http://jsfiddle.net/7SJqC/
Interesting project.
The method of determining the appropriate scale to fit a collection of points is fairly straightforward, although it took me quite a while to figure out why it wasn't working for me -- I hadn't clued in to the fact that (since you were drawing the tree horizontally) "x" from the tree layout represented vertical position, and "y" represented horizontal position, so I was getting apparently arbitrary results.
With that cleared up, to figure out the zoom you simply need to find the height and width (in data-coordinates) of the area you want to display, and compare that with the height and width of the viewport (or whatever your original max and min dimensions are).
ScaleFactor = oldDomain / newDomain
Generally, you don't want to distort the image with different horizontal and vertical scales, so you figure out the scale factor separately for width and height and take the minimum (so the entire area will fit in the viewport).
You can use the d3 array functions to figure out the extent of positions in each direction, and then find the middle of the extent adding max and min and dividing by two.
var matches = d3.selectAll(".selected");
/*...*/
if ( matches.empty() ) {
centerToPosition(0, 0, 1); //reset
}
else if (matches.size() === 1) {
click(matches.datum(), 0, 0, false);
}
else {
var xExtent = d3.extent(matches.data(), function (d) {
return d.x0;
});
var yExtent = d3.extent(matches.data(), function (d) {
return d.y0;
});
//note: the "x" values are used to set VERTICAL position,
//while the "y" values are setting the HORIZONTAL position
var potentialXZoom = viewerHeight/(xExtent[1] - xExtent[0] + 20);
var potentialYZoom = viewerWidth/(yExtent[1] - yExtent[0] + 150);
//The "20" and "150" are for height and width of the labels
//You could (should) replace with calculated values
//or values stored in variables
centerToPosition( (xExtent[0] + xExtent[1])/2,
(yExtent[0] + yExtent[1])/2,
Math.min(potentialXZoom, potentialYZoom)
);
}
http://jsfiddle.net/7SJqC/2/

Categories

Resources