How can I calculate lines for a custom CartesianGrid in Rechart. How can I remap to the correct domain - recharts

CartesianGrid allows me to set the verticalPoints and horizontalPoints to be shown.
Unfortunately, these values are in pixel values on the chart, not in the coordiantes of the x and y domains.
I must be missing something. I have an x-axis and a y-axis. How can I map a value in those domains into positions on the chart?

So, given some constants defining the chart:
const chartWidth = 600; // width set on our chart object
const xAxisLeftMargin = 10; // margins set on our chart object
const xAxisRightMargin = 20;
const xPadding = 0; // padding set on both sides of xAxis
const yAxisWidth = 120; // total widths of all yAxes
we can use d3 to create an xScale, like this:
const leftX = 0 + yAxisWidth + xAxisLeftMargin + xPadding;
const rightX = chartWidth - xPadding - xAxisRightMargin;
// note that rightX assumes there are no axes on the right hand side
const xScale = d3.scaleLinear().domain(xDomain).range([leftX, rightX]);
Then assuming we have an array of verticalPoints:
const verticalPoints: number[] = [];
we just add values in the xDomain like this:
verticalPoints.push(xScale(value))
and we can render our vertical lines on the x-axis with the CartesianGrid like this:
<CartesianGrid stroke="#d5d5d5" verticalPoints={verticalPoints} />

Related

How to use d3.scaleLinear to find the radius of a map in d3

I have a chart as shown below
where the radius of the pie chart on the chart is am trying to do it as shown below
<PieChartMarkers
totalSites={totalSites}
key={i}
keymarket={name}
pieData={pieData}
x={projection(coordinates)[0]}
y={projection(coordinates)[1]}
**radius={this.getMarketRadius(totalCase)}**
mouseOverHandler={this.showTooltip}
mouseOutHandler={this.hideTooltip}
/>
getMarketRadius = (totalCase) => {
let radius = null;
// let data=d3.scaleLinear([10,130]).range([0,960]);
let callback= d3.scaleLinear()
.range([0, totalCase])
.domain([10, 130])
radius = totalCase / 650 + 10;
console.log(radius)
return radius;
};
currently i am getting the radius radius = totalCase / 650 + 10; which is working fine but suggestion is to use d3.scaleLinear to get the radius of the on chart when trying to use i am getting the value as 1313316 using the below code snippet
//totalCase is the variable value coming from API
let callback= d3.scaleLinear()
.range([0, totalCase])
.domain([10, 130]
please help me understand how to get the radius using d3.scaleLinear to draw the pie chart on the map
First of all, you should not use a linear scale to get these radii: we encode the data as the area of the circles, not as their radii. That said, you should use d3.scaleSqrt. Also, set the 0 range for the 0 domain, because there should be no circle if there are no cases.
So, your function would be something like this:
getMarketRadius = totalCase => {
return d3.scaleLinear().range([0, totalCase]).domain([0, 130])(totalCase);
};
A better option is just setting the scale outside the function, and using it directly for setting the radius.
myScale = d3.scaleLinear()
.range([0, totalCase])
.domain([0, 130]);
radius={this.myScale(totalCase)}

Add multiple axis on lineWithFocusChart

Is there any way to add multiple axis on lineWithFocusChart .
I want to add my y2axis on right side of the graph.
There is a default option in the NVD3 library to draw multiple axis chart. Just look into the example of multichart at the documentation website of the library. And a sample html file is provided on the github repo of nvd3
You can use Barchart, Areachart and line chart for multiple axis.
Just draw the graph with
var chart = nv.models.multiChart()
And change the yAxis variable to draw the graph on left yaxis or right yaxis like this.
testdata[0].type = "area";
testdata[0].yAxis = 1;
testdata[1].type = "area";
testdata[1].yAxis = 1;
testdata[2].type = "line";
testdata[2].yAxis = 1;
testdata[3].type = "line";
testdata[3].yAxis = 2;
testdata[4].type = "bar";
testdata[4].yAxis = 2;
testdata[5].type = "bar";
testdata[5].yAxis = 2;
testdata[6].type = "bar";
testdata[6].yAxis = 2;

Violin plot in d3

I need to build a violin point with discrete data points in d3.
Example:
I am not sure how to align the center for each value on X axis. The default behavior will overlay all the points with same X and Y value, however I would like the points to be offset while being center aligned e.g. 5.1 has 3 values in control group and 4.5 has 2 values, all center aligned. It is easy to do so for either right or left aligned by doing a transformation of each point by a specified amount. However, the center alignment seems to be quite hacky.
A hacky way would be to manually transform the X value by maintaining a couple of arrays to see whether this is the first, even or odd number of element and place it according my specifying the value. Is there a proper way to handle this?
The only example of violin plot in d3 I found was here - which implements a probability distribution rather than the discrete values which I require.
"A hacky way would be to manually transform the X value by maintaining a couple of arrays" - that's pretty much the way most d3 layouts work :-) . Discretise your data set by the y value (weight), keeping a total of the data points in each discrete group and a group index for each datum. Then use those to calculate offsets x-ways and the rounded y-value.
See https://jsfiddle.net/n444k759/4/
// below code assumes a svg and g group element are present (they are in the jsfiddle)
var yscale = d3.scale.linear().domain([0,10]).range([0,390]);
var xscale = d3.scale.linear().domain([0,2]).range ([0,390])
var color = d3.scale.ordinal().domain([0,1]).range(["red", "blue"]);
var data = [];
for (var n = 0; n <100; n++) {
data.push({weight: Math.random() * 10.0, category: Math.floor (Math.random() * 2.0)});
}
var groups = {};
var circleR = 5;
var discreteTo = (circleR * 2) / (yscale.range()[1] / yscale.domain()[1]);
data.forEach (function(datum) {
var g = Math.floor (datum.weight / discreteTo);
var cat = datum.category;
var ref = cat+"-"+g;
if (!groups[ref]) { groups[ref] = 0; }
datum.groupIndex = groups[ref];
datum.discy = yscale (g * discreteTo); // discrete
groups[ref]++;
});
data.forEach (function(datum) {
var cat = datum.category;
var g = Math.floor (datum.weight / discreteTo);
var ref = cat+"-"+g;
datum.offset = datum.groupIndex - ((groups[ref] - 1) / 2);
});
d3.select("svg g").selectAll("circle").data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return 50 + xscale(d.category) + (d.offset * (circleR * 2)); })
.attr("r", circleR)
.attr("cy", function(d) { return 10 + d.discy; })
.style ("fill", function(d) { return color(d.category); })
;
The above example discretes into groups according to the size of the display and the size of the circle to display. You might want to discrete by a given interval and then work out the size of circle from that.
Edit: Updated to show how to differentiate when category is different as in your screenshot above

dc.js geochoropleth map scaling

I have a geo map. Everything is running just fine but the the map that is drawn is incredibly tiny. I have checked the GEOJSON for errors and it works fine. In the JS Box there is a proper working Demo that is commented out to see a working example.
How Do I get my map to Scale up to fill my svg?
http://codepen.io/MichaelArledge/pen/VeeVmY?editors=011
$.getJSON("https://googledrive.com/host/0B9jw0MX1C_D_N1plZFhjTlZwY3c", function(json){
var max = community_per_capita_totals.top(1)[0].value;
// create a first guess for the projection
var center = d3.geo.centroid(json)
var scale = 100;
var offset = [width/2, height/2];
var projection = d3.geo.mercator().scale(scale).center(center).translate(offset);
// create the path
var path = d3.geo.path().projection(projection);
// using the path determine the bounds of the current map and use
// these to determine better values for the scale and translation
var bounds = path.bounds(json);
var hscale = scale*width / (bounds[1][0] - bounds[0][0]);
var vscale = scale*height / (bounds[1][1] - bounds[0][1]);
var scale = (hscale < vscale) ? hscale : vscale;
var offset = [width - (bounds[0][0] + bounds[1][0])/2, height - (bounds[0][1] + bounds[1][1])/2];
// new projection
projection = d3.geo.mercator().center(center).scale(scale).translate(offset);
path = path.projection(projection);
chart.dimension(community_dim)
.group(community_per_capita_totals)
.width(width)
.height(height)
.colors(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"])
.colorDomain([0, max])
.projection(d3.geo.mercator()
.center(center)
.scale(scale)
.translate(offset))
.overlayGeoJson(json["features"], "Community")
dc.renderAll();
});
The issue is with your logic for determining the projection.scale(). I am not sure how to modify your logic to give you a custom scale for your map, but here is an example of scale logic I have used in maps before. The factors I am multiplying width and height by are based on the overall aspect ratio of the albersUsa projection, so you would need to tweak those to get a good fit for the mercator projection.
var scale = Math.min(width * 1.2, height * 2.1);
var projection = albersUsaPr()
.scale(scale)
.translate([width / 2, height / 2]);

Flot - display labels on top of stacked bars

I am using the code snippet from this stackoverflow question to label my flot data points. So far this has served me well, but now I have to label the overall values of stacked bars. There are two data series and so far I've managed to calculate the sums, but I can't seem to work out a proper positioning for my labels. I'd like to place them on top of the stacks, but pointOffset only gives me the offsets based on non-stacked bars.
This is the code I am currently using, it places the labels where the second series' data points would be, if the bars weren't stacked, which puts them somewhere in the top bars.
$.each(p.getData()[1].data, function(i, el){
var series0 = p.getData()[0].data;
sum = el[1] + series0[i][2]
var o = p.pointOffset({x: el[0], y: el[1]});
$('<div class="data-point-label">' + sum + '</div>').css( {
position: 'absolute',
left: o.left - 5,
top: o.top ,
display: 'none'
}).appendTo(p.getPlaceholder()).fadeIn('slow');
});
Edit #1: So far I've tried using c2p/p2c, calculating the top value using the single data points' top values and finding more documentation on the stack plugin. I am afraid none of this has helped me much.
Edit #2: I've also tried the code given in this stackoverflow answer but it doesn't work for me. I suspect the author is using some label plugin ...
The solution to put the labels in the top of the bars in stack usinjg canvas is that you have to calculate the xPoint in base of the sum of the values in the complete stack.
Here is a example of code
var sumaArr = [];
for (var u = 0; u < p.getData().length; u++) {
$.each(p.getData()[u].data, function (i, el) {
sumaArr[i] > 0 ? sumaArr[i] = sumaArr[i] + el[1] : sumaArr[i] = el[1];
});
}
var ctx = p.getCanvas().getContext("2d");
var data = p.getData()[p.getData().length - 1].data;
var xaxis = p.getXAxes()[0];
var yaxis = p.getYAxes()[0];
var offset = p.getPlotOffset();
ctx.font = "12px 'Segoe UI'";
ctx.fillStyle = "gray";
for (var i = 0; i < data.length; i++) {
var text = sumaArr[i];
var metrics = ctx.measureText(text);
var xPos = (xaxis.p2c(data[i][0]) + offset.left) - metrics.width / 2;
var yPos = yaxis.p2c(sumaArr[i]) + offset.top - 5;
ctx.fillText(text, xPos, yPos);
}
The var yPos use the sume of the values that make the complete stack.

Resources