How do i specify the axis range in React-Vis
For Ex:
0 to 100 for Y Axis and the data is as below
data={[ {x: 1, y: 45}, {x: 2, y: 50}, {x: 3, y: 85} ]}/>
The YAxis must range 0 to 100 even though the max Y value here is 85
Use the xDomain or yDomain props on the XYPlot component <XYPlot xDomain={[0, 50]}
From their docs about scales at:
https://uber.github.io/react-vis/documentation/general-principles/scales-and-data
"To redefine a scale, you must pass a prop to the series that uses that scale. The prop names are based on the name of the attribute: name + Domain, name + Range, name + Type, name + Padding (for instance: yDomain, colorType, xRange)."
So for your purpose, you would set <XYPlot yDomain=[0,100]></XYPlot>
Related
I've got this kind of non uniforme datas :
[{'time':0,'sum':0},{'time':600,'sum':2},{'time':700,'sum':4},{'time':1200,'sum':1},{'time':1300,'sum':3},{'time':1600,'sum':1},{'time':2000,'sum':0}];
"time" is on x axis and "sum" on y axis. If I make an area, I've got these shapes (curved in red, not curved in white) :
https://codepen.io/kilden/pen/podadRW
But the meaning of this is wrong. I have to interpret the "missing" datas. A bit like the "kernel density estimation" charts (example here :https://bl.ocks.org/mbostock/4341954) where values are at zero when there is no data, but there is a "fall off" around the point with data. (a gaussian curve)
It's hard to explain with words (and English is not my mother tongue). So I did this second codepen to show the idea of the shape. The area in red is the shape I want (White one is the reference of the first codepen) :
https://codepen.io/kilden/pen/VwrQrbo
I wonder if there is a way to make this kind of cumulative gaussian curves with a (hidden?) d3 function or a trick function ?
A. Your cheating yourself when you use the Epanechnikov kernel, evaluate these on a rather coarse grid and make a smooth line interpolation so that it looks gaussian. Just take a gaussian kernel to start with.
B. You're comparing apples and oranges. A kernel density estimate is an estimate of a probability density that cannot be compared to the count of observations. The integral of the kernel density estimate is always equal to 1. You can scale the estimate by the total count of observations, but even then your curve would not "stick" to the point, since the kernel spreads the observation away from the point.
C. What comes close to what you want to achieve is implemented below. Use a gaussian curve which is 1 at 0, i. e. without the normalizing factor and the rescaling by the bandwidth. The bandwidth now scales only the width of the curve but not its height. Then use your original data array and add up all these curves with the weight sum from your data array.
This will match your data points when there are no clustered observations. Naturally, when two observations are close to each other, their individual gaussian curves can add up to something bigger than each observation.
DISCLAIMER: As I already pointed out in the comments, this just produces a pretty chart and is mathematical nonsense. I strongly recommend working out the mathematics behind what it is you really want to achieve. Only then you should make a chart of your data.
const WIDTH = 600;
const HEIGHT = 150;
const BANDWIDTH = 25;
let data = [
{time: 0, sum: 0},
{time: 200, sum: 4},
{time: 250, sum: 2},
{time: 500, sum: 1},
{time: 600, sum: 2},
{time: 1500, sum: 5},
{time: 1600, sum: 4},
{time: 1800, sum: 3},
{time: 2000, sum: 0},
];
// svg
const svg = d3.select("body")
.append("svg")
.attr("width", WIDTH)
.attr("height", HEIGHT)
.style("background-color", "grey");
// scales
const x_scale = d3.scaleLinear()
.domain([0, 2000])
.range([0, WIDTH]);
const y_scale = d3.scaleLinear()
.range([HEIGHT, 0]);
// curve interpolator
const line = d3.line()
.x(d => x_scale(d.time))
.y(d => y_scale(d.sum))
.curve(d3.curveMonotoneX);
const grid = [...Array(2001).keys()];
svg.append("path")
.style("fill", "rgba(255,255,255,0.4");
// gaussian "kernel"
const gaussian = k => x => Math.exp(-0.5 * x / k * x / k);
// similar to kernel density estimate
function estimate(kernel, grid) {
return obs => grid.map(x => ({time: x, sum: d3.sum(obs, d => d.sum * kernel(x - d.time))}));
}
function render(data) {
data = data.sort((a, b) => a.time - b.time);
// make curve estimate with these kernels
const curve_estimate = estimate(gaussian(BANDWIDTH), grid)(data);
// set endpoints to zero for area plot
curve_estimate[0].sum = 0;
curve_estimate[curve_estimate.length-1].sum = 0;
y_scale.domain([0, 1.5 * Math.max(d3.max(data, d => d.sum), d3.max(curve_estimate, d => d.sum))]);
svg.select("path")
.attr("d", line(curve_estimate))
const circles = svg.selectAll("circle")
.data(data, d => d.time)
.join(
enter => enter.append("circle")
.attr("fill", "red"),
update => update.attr("fill", "white")
)
.attr("r", 2)
.attr("cx", d => x_scale(d.time))
.attr("cy", d => y_scale(d.sum));
}
render(data);
function randomData() {
data = [];
for (let i = 0; i < 10; i++) {
data.push({
time: Math.round(2000 * Math.random()),
sum: Math.round(10 * Math.random()) + 1,
});
}
render(data);
}
function addData() {
data.push({
time: Math.round(2000 * Math.random()),
sum: Math.round(10 * Math.random()) + 1,
});
render(data);
}
d3.select("#random_data").on("click", randomData);
d3.select("#add_data").on("click", addData);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.min.js"></script>
<button id="random_data">
Random Data
</button>
<button id="add_data">
Add data point
</button>
I trying to produce ticks for scaleLog().base(2).
Seems to be, it does not work correctly.
For instance, for the call:
d3.scaleLog().base(2).domain([50,500]).ticks(10)
I got:
[ 50, 100, 150, 200, 250, 300, 350, 400, 450, 500 ]
Which just linear placed ticks. For base(10) it works properly.
d3.scaleLog().base(10).domain([50,500]).ticks(10)
[ 50, 60, 70, 80, 90, 100, 200, 300, 400, 500 ]
I using d3.js version 6.1.1.
I am missing something?
You're not missing anything, but there is this line, inside the source code:
if (z.length * 2 < n) z = ticks(u, v, n);
Here, z is the generated array (in this case [64, 128, 256]), n is the required number of ticks (10), and u and v are the domain (50 and 500).
Because the number of generated ticks is too low, d3 defaults to a linear scale. Try one of the following instead:
console.log(d3.scaleLog().base(2).domain([50, 500]).ticks(6));
console.log(d3.scaleLog().base(2).domain([32, 512]).ticks(10));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.1.1/d3.min.js"></script>
If all parameters are variable, you could calculate the maximum possible number of ticks and use that as an upper bound:
const domain = [50, 500];
const ticks = 100;
console.log(d3.scaleLog().base(2).domain(domain).ticks(ticks));
function getNTicks(domain, ticks) {
const maxPossibleTicks = Math.floor(Math.log2(domain[1]) - Math.log2(domain[0]));
return Math.min(ticks, maxPossibleTicks);
}
console.log(d3.scaleLog().base(2).domain(domain).ticks(getNTicks(domain, ticks)));
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.1.1/d3.min.js"></script>
I need to create autosized text setting some box size:
text(textContent, textX, textY, textWidth, textHeight);
The problem is that the text is multilined because it can't place on one line. Ok, then I've tried to check textBounds - but it works only with one line of string. How to find bounds for text drawing in some box/rect (textWidth, textHeight)?
I am having a little difficulty understanding your question. But if you wanted to create a box that encompassed all the text you could implement something like the following:
Lets assume we have our text stored to objects like this to make the logic easier to read:
var text1 = {
"str" : "This Is Line One",
X1: 0,
Y1: 0,
X2: 300,
Y2: 100,
size:30
}
var text2 = {
"str" : "This Is Line 2",
X1: 0,
Y1: 30,
X2: 300,
Y2: 100,
size:20
}
X1,Y1 is the upper left coordinate, and X2,Y2 is the lower right.
Now we have a data model to work with. We simply have to combine the bounds into a single rectangle.
Remember:
Rectangles are Declared like this:
Rect(X,Y,Width,Height)
Which is exactly the information textBounds() gives us!
Here is a quick and dirty example of the math:
function draw() {
textSize(this.text1.size);
text(this.text1.str,this.text1.X1,this.text1.Y1,this.text1.X2,this.text1.Y2);
textSize(this.text2.size);
text(this.text2.str,this.text2.X1,this.text2.Y1,this.text2.X2,this.text2.Y2);
//Get Text Bounds object for each text
let text1box = this.font.textBounds(this.text1.str,this.text1.X1,this.text1.Y1,this.text1.size)
let text2box = this.font.textBounds(this.text2.str,this.text2.X1,this.text2.Y1,this.text2.size)
//Get the upper-left and lower-right coordinates of the bounding rectangle of all the text
let bounds = {
x1: min(text1box.X,text2box.X), //upper left x
y1: min(text1box.Y,text2box.Y), //uper left y
x2: max(text1box.x + text1box.w,text2box.x + text2box.w), //lower right x
y2: max(text1box.y + text1box.y,text2box.y + text2box.h) //lower right y
}
//if you want padding:
let padding = 2;
rect(
bounds.x1 - padding,
bounds.y1 - padding,
abs(bounds.x2, - bounds.x1) + padding, //Width of the bounding rectangle
abs(bounds.y2 - bounds.y1) + padding //Height of the bounding rectangle
)
}
To fully show the logic here is a quick diagram I made
The cool thing about this logic is that even if the text isn't lined up perfectly, it is still able to put a box around all the text.
I have an array:
taskTypes = [slot1, slot2, slot3,slot4,slot1,slot2,slot6];
my code for y-axis is:
var y = d3.scale.ordinal()
.domain(taskTypes)
.rangeRoundBands([ 0, height - margin.top - margin.bottom ], .1);
It is showing only unique values on y-axis, I want to show the values as they are. Can you help me out with the correct code?
This answer gives the basic solution: Using the ordinal scale, domain values uniquely identify corresponding range values. In your case both the '"slot1"' input values will map to the same output value, so will not appear unique.
The solution is to use array indices (0, 1, 2...) instead of array values ("slot1", "slot2"...) as your ordinal scale input domain:
var taskTypes = ["slot1", "slot2", "slot3","slot4","slot1","slot2","slot6"];
var y = d3.scale.ordinal()
.domain(d3.range(0, taskTypes.length))
.rangeRoundBands([ 0, height - margin.top - margin.bottom ], .1);
Fiddle here: http://jsfiddle.net/henbox/fhf3095r/3/
I'm trying to specify the x axis labels as strings. I can get the strings to show up, but I can't get them to spread out/align properly. All of the numbers are displaying correctly, I just can't seem to get the labels to spread out correctly or the domain to show up. I'm really looking to get the labels working, but the domain seemed like it could be an alternate way possibly.
nv.addGraph(function() {
var chart = nv.models.lineChart();
var fitScreen = false;
var width = 600;
var height = 300;
var zoom = 1;
chart.useInteractiveGuideline(true);
chart.xAxis
.domain(["Sunday","Monday","Tuesday","Wednesday","Thursday"])
.tickValues(['Label 1','Label 2','Label 3','Label 4','Label 5'])
.ticks(5);
//.tickFormat(d3.format('d'));
chart.yAxis
.tickFormat(d3.format(',.2f'));
d3.select('#chart1 svg')
.attr('perserveAspectRatio', 'xMinYMid')
.attr('width', width)
.attr('height', height)
.datum(veggies);
setChartViewBox();
resizeChart();
return chart;
});
Data
veggies = [
{
key: "Peas",
values: [
{x: 0, y: 2},
{x: 1, y: 5},
{x: 2, y: 4}
]
},
{
key: "Carrots",
values: [
{x: 0, y: 2},
{x: 1, y: 5},
{x: 2, y: 4}
]
}
];
The "domain" of a d3 scale or axis is the input data. Which in your data set look like numbers, not names of the week. See http://alignedleft.com/tutorials/d3/scales
You want to something like xAxis.domain([0,1,2,3,4,5]);.
So how do you get labels to go with your numbers? Without thinking about it to closely, I suggested using the "range" (output) part of the scale. But range for the axis defines the numerical spacing for the categories on your graph, not the labels.
What you want is a custom formatting function that converts the numbers from the data into labels. You set that with the "tickFormat" option:
xAxis.tickFormat( function(index) {
var labels = ["Label0", "Label1", "Label2", "Label3", "Label4", "Label5"];
return labels[index];
});
Normally, tick formatting functions are used to format numbers into decimals or percents, but the syntax allows you to use any function that takes the data value as a parameter (I've named it index here) and returns the string that you want to be displayed. The line return labels[index]; finds the index-numbered element in the labels array, where the first label is index 0.
If your data values aren't consecutive integers starting with zero, you can use the object/associative array format, with named elements, instead of just an array. For example, if your data had the values "M", "T", "W", "R", etc., and you wanted it to display full day names, you would use:
xAxis.tickFormat(function(name) {
var labels = {"M":"Monday", "T":"Tuesday", "W":"Wednesday",
"R": "Thursday", "F":"Friday"};
return labels[name];
});
The values you pass to tickValues() have to be values in the input domain,* i.e, in the same format as the values in the JSON data. Also, once you set tickValues explicitly, don't over-ride that setting by setting a number of ticks. But you should only need tickValues if you only want some of the days to have labels (for example, if there is not enough space on the chart for all the names).
*Note correction.