Plotly,js - Y autoscale - plotly.js

I have a line chart. When I run it the chart is autoscalled on Y axis. Meaning it shows max and min data automatically. Let's say the chart shows one hour of data. So, xaxis.range = ['01/01/2020 12:00', '01/01/2020 01:00']. If I change xaxis.range to ['01/01/2020 12:30', '01/01/2020 01:00'] and then relayout the chart will it autoscale the Y axis to new min and max of data that falls between new X axis range?
Thanks
Here is an example:
Pen
and code:
var data = [
{
x: ['2013-10-04 22:23:00', '2013-11-04 22:23:00', '2013-12-04 22:23:00'],
y: [1, 3, 6],
type: 'scatter'
}
];
var myDiv = document.getElementById('myDiv');
Plotly.newPlot(myDiv, data);
myDiv.layout.xaxis.range = ['2013-10-04 22:23:00', '2013-11-04 22:23:00'];
Plotly.relayout(myDiv, myDiv.layout);
Where I am trying changing the X range but even that is not happening.

No, as of today it is not yet possible to autoscale the y axis based on the the data that is currently visible on the x axis. This has been discussed in a github issue here: https://github.com/plotly/plotly.js/issues/1876#issuecomment-314913229
However, if you calculate the new min and max of the y axis yourself, you should be able to set it via the relayout method.
const data = [
{
x: ['2013-10-04 22:23:00', '2013-11-04 22:23:00', '2013-12-04 22:23:00'],
y: [1, 3, 6],
type: 'scatter'
}
];
const myDiv = document.getElementById('myDiv');
Plotly.newPlot(myDiv, data).then(() => {
const xRange = ['2013-10-04 22:23:00', '2013-11-04 22:23:00'];
const yRange = [1, 3];
Plotly.relayout(myDiv, {
'xaxis.range': xRange,
'yaxis.range': yRange
});
});
<head>
<!-- Load plotly.js into the DOM -->
<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
</head>
<body>
<div id='myDiv'><!-- Plotly chart will be drawn inside this DIV --></div>
</body>

Related

why doesn't d3-geo-zoom pan correctly for more then 1 canvas elements on the page?

When panning in the first canvas, things work like expected. When panning in the second canvas, it doesn't work like expected. I expected both to work the same. The second globe spins rapidly after a little bit of panning, the first globe keeps the cursor on the same coordinates.
https://codepen.io/tonytrupe/pen/jOqjGvE
class UI {
constructor(canvas) {
var width = canvas.width,
height = canvas.height;
//set projection type here, geoOrthographic, geoWinkel3
var projection = d3
.geoWinkel3()
//.scale((Math.min(width, height)) / 2)
.translate([width / 2, height / 2])
//.rotate([0,0,0])
.fitExtent(
[
[6, 6],
[width - 6, height - 6]
],
{
type: "Sphere"
}
);
draw();
//this.addZoomPan = function () {
d3
.geoZoom()
.northUp(true)
.projection(projection)
.onMove(draw)(canvas);
//};
function draw() {
var ctx = canvas.getContext("2d");
var path = d3.geoPath().context(ctx).projection(projection);
// Store the current transformation matrix
ctx.save();
// Use the identity matrix while clearing the canvas
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Restore the transform
ctx.restore();
var border = {
type: "Sphere"
};
ctx.beginPath();
path(border);
ctx.strokeStyle = "#000";
ctx.stroke();
var lat = 45;
var lon = 45;
var graticule = d3.geoGraticule().step([lat, lon]);
ctx.beginPath();
path(graticule());
ctx.strokeStyle = "#000";
ctx.stroke();
}
}
}
//var one = new UI(document.getElementById("one"));
//var two = new UI(document.getElementById("two"));
var three = new UI(document.getElementById("three"));
html
<html>
<script src="//d3js.org/d3.v6.js"></script>
<script src="//d3js.org/d3-geo.v2.min.js"></script>
<script src="//d3js.org/d3-geo-projection.v3.min.js"></script>
<script src="//unpkg.com/d3-geo-zoom"></script>
<!--removing all but the last canvas element makes things work as expected-->
<canvas id="one" class="canvas" width="320" height="200"></canvas>
<canvas id="two" class="canvas" width="320" height="200"></canvas>
<canvas id="three" class="canvas" width="320" height="200"></canvas>
</html>
https://github.com/vasturiano/d3-geo-zoom/issues/12
It previously wasn't getting pointer location relative to the node element. Now it is.
const pointers = d3Pointers(zoomEv, nodeEl);
https://github.com/vasturiano/d3-geo-zoom/blob/86da0d98f267a838a4715abec60e4a278ace2121/src/geoZoom.js#L59

Area chart - start filling from non zero y value

I am trying to create an area chart that looks like this where I am using bars:
var trace1 = {
x: ['2013-10-04 9:00:00', '2013-10-04 9:30:00', '2013-10-04 10:00:00', '2013-10-04 11:00:00', '2013-10-04 11:30:00', '2013-10-04 12:30:00'],
y: [20, 20, 10, 10, 20, 20],
type: 'bar',
base: [5,5,5,5,5,5],
mode: 'none'
};
var data = [trace1];
myDiv = document.getElementById('myDiv');
Plotly.newPlot(myDiv, data);
Pen
As you can see y axis starts from non zero value. Is it even possible?
Thanks
You can add this code to start y-axis 0
var layout = {
yaxis: {
rangemode: 'tozero' //or below
//range: [0, 25]
}
};
Plotly.newPlot(myDiv, data, layout);

NVD3 - Show all tick values

My chart needs to have tick values in multiples of 3. For example, if I have 5 data points, my x-axis should show (3, 6, 9, 12, 15). How do I get nvd3 to display all tick numbers? I pasted my code below for reference
var chart = nv.models.lineChart()
.options({
margin: {left: 100, bottom: 100},
x: function(d,i) { return i*3},
showXAxis: true,
showYAxis: true,
showLegend: true,
reduceXTicks: false,
showMaxMin: false,
//values: $scope.data.values.map( function(d,i) { return i*3; }),
useInteractiveGuideline: true,
transitionDuration: 2500,
showControls : true
})
;
chart.xAxis //Chart x-axis settings
.axisLabel('Ager')
.orient('bottom')
//.tickValues( function(d,i) { return i*3;})
.tickFormat(d3.format(',r'));
chart.yAxis //Chart y-axis settings
.axisLabel('Voltage (v)')
.tickFormat(d3.format('.02f')); nv.utils.windowResize(chart.update);
I've tried everything I can think of and read online to get nvd3 show all ticks in multiples of 3. Please help me out of this tricky situation.
Thanks in advance!
You could have read the d3 docs and see that .tickValues does not accept a function.
Construct the array of tick values based on the domain of the x-axis. Because the domain is not yet set you have to construct it yourself based on the data.
Using the SinCos example from the nvd3 site
var myData = sinAndCos();
var xExtent = d3.extent(myData[0].values, d=>d.x);
xExtent = [Math.floor(xExtent[0]), Math.ceil(xExtent[1])+1];
var xTicks = d3.range(xExtent[0], xExtent[1]).filter(n => n%3===0);
chart.xAxis
.axisLabel('Time (ms)')
.tickValues(xTicks)
.tickFormat(d3.format(',r'));
Complete example. It will not run directly from the browser (will not load nvd3 inside an iframe(??)). Copy it to a local file and run it from there.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link href="https://nvd3.org/assets/css/nv.d3.css" rel="stylesheet"/>
<script src="https://nvd3.org/assets/lib/d3.v3.js"></script>
<script src="https://nvd3.org/assets/js/nv.d3.js"></script>
</head>
<body>
<div id="chart">
<svg style="width:800px;height:500px;"></svg>
</div>
<script>
/*These lines are all chart setup. Pick and choose which chart features you want to utilize. */
nv.addGraph(function() {
var chart = nv.models.lineChart()
.margin({left: 100}) //Adjust chart margins to give the x-axis some breathing room.
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
.transitionDuration(350) //how fast do you want the lines to transition?
.showLegend(true) //Show the legend, allowing users to turn on/off line series.
.showYAxis(true) //Show the y-axis
.showXAxis(true) //Show the x-axis
;
var myData = sinAndCos();
var xExtent = d3.extent(myData[0].values, d=>d.x);
xExtent = [Math.floor(xExtent[0]), Math.ceil(xExtent[1])+1];
var xTicks = d3.range(xExtent[0], xExtent[1]).filter(n => n%3===0);
chart.xAxis //Chart x-axis settings
.axisLabel('Time (ms)')
.tickValues(xTicks)
.tickFormat(d3.format(',r'));
chart.yAxis //Chart y-axis settings
.axisLabel('Voltage (v)')
.tickFormat(d3.format('.02f'));
d3.select('#chart svg') //Select the <svg> element you want to render the chart in.
.datum(myData) //Populate the <svg> element with chart data...
.call(chart); //Finally, render the chart!
//Update the chart when window resizes.
nv.utils.windowResize(function() { chart.update() });
return chart;
});
/**************************************
* Simple test data generator
*/
function sinAndCos() {
var sin = [],sin2 = [],
cos = [];
//Data is represented as an array of {x,y} pairs.
for (var i = 0; i < 100; i++) {
sin.push({x: i, y: Math.sin(i/10)});
sin2.push({x: i, y: Math.sin(i/10) *0.25 + 0.5});
cos.push({x: i, y: .5 * Math.cos(i/10)});
}
//Line chart data should be sent as an array of series objects.
return [
{
values: sin, //values - represents the array of {x,y} data points
key: 'Sine Wave', //key - the name of the series.
color: '#ff7f0e' //color - optional: choose your own line color.
},
{
values: cos,
key: 'Cosine Wave',
color: '#2ca02c'
},
{
values: sin2,
key: 'Another sine wave',
color: '#7777ff',
area: true //area - set to true if you want this line to turn into a filled area chart.
}
];
}
</script>
</body>
</html>

dimple.js dash line chart

How can draw series 2 below as a dashed line? If not something that can be done in dimple.js itself, how would i do this using d3.js?
<div id="chart1">
<script>
var svg1 = dimple.newSvg("#chart1", 600, 500);
var data1 = [[{x: '01/31/1998', y: 100.0}, {x: '02/28/1998', y: 110.0}, {x: '03/31/1998', y: 120.0}, {x: '04/30/1998', y: 130.0}],
[{x: '01/31/1998', y: 120.0}, {x: '02/28/1998', y: 130.0}, {x: '03/31/1998', y: 140.0}, {x: '04/30/1998', y: 150.0}]]
var chart1 = new dimple.chart(svg1);
chart1.setBounds(70, 30, 400, 300)
var xAxis = chart1.addTimeAxis("x", "x", "%m/%d/%Y", "%b %y");
xAxis.title="Date"
var yAxis = chart1.addMeasureAxis("y", "y");
yAxis.title = "Price"
s1 = chart1.addSeries("Series1", dimple.plot.line, [xAxis, yAxis]);
s1.data = data1[0]
s2 = chart1.addSeries("Series2", dimple.plot.line, [xAxis, yAxis]);
s2.data = data1[1]
myLegend1 = chart1.addLegend(510, 100,60, 200, "Right");
chart1.draw();
</script>
</div>
You can access the shapes after calling the draw method. They will be classed according to their value, then you can do what you like with a bit of d3. Here's how to make the second series in your case dashed:
svg1.selectAll("path.dimple-series2").style("stroke-dasharray", "2");
NB. The class is dimple-series2 because it's named Series2 not because of it's position. If you have a more complicated series name you might need to inspect the line to determine which class to use. for example My Awesome Series would be classed as "dimple-my-awesome-series". If you have a single series with multiple lines then series names and values are added as classes so you can grab whichever you need.

how to set the domain and scale on an axis on a nvd3.js multiBarChart

In d3.js you can set an x axis to use d3.time.scale() then set x.domain([start_date, end_date]) and it will 'fill in' any missing dates that aren't in the data with 0 values. I want to do the same with a nvd3.js mulitBarChart.
This code (can be pasted directly into http://nvd3.org/livecode/#codemirrorNav) shows a bar chart of totals by year, there are missing values for 2002 & 2003. I want to set the scale to be d3.time.scale() and then the domain to the first and last years of the dataset so the missing years are automatically added with 0 values. How do I do that?
nv.addGraph(function() {
var chart = nv.models.multiBarChart();
chart.xAxis
.tickFormat(function(d){ return d3.time.format('%y')(new Date(d)); });
chart.yAxis
.tickFormat(d3.format(',f'));
chart.reduceXTicks(false);
chart.showControls(false);
var data = [{
'key': 'GB by year',
'values': [
{x: new Date().setFullYear('2001'), y: 0.12},
{x: new Date().setFullYear('2004'), y: 0.03},
{x: new Date().setFullYear('2005'), y: 0.53},
{x: new Date().setFullYear('2006'), y: 0.43},
{x: new Date().setFullYear('2007'), y: 5.5},
{x: new Date().setFullYear('2008'), y: 9.9},
{x: new Date().setFullYear('2009'), y: 26.85},
{x: new Date().setFullYear('2010'), y: 0.03},
{x: new Date().setFullYear('2011'), y: 0.12}
]
}];
d3.select('#chart svg')
.datum(data)
.transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
Based on the above answer, you can do this with numeric x values (not Date objects) as well as a forced X range and specified tickValues.... for certain types of charts.
Bar charts do not seem to have the capability, however nvd3.lineCharts do what you'd like. The multiBarChart model does not allow the use of the forceX function to be applied (right now, ever?).
A solution to your problem would be to fill in the 0's or to use a sequential chart type (e.g. lineChart)
nv.addGraph(function() {
var chart = nv.models.lineChart()
.forceX(2001,2011);
var tickMarks = [2001,2002,2003,2004,2005,2006,2007,2008,2009,2010,2011]
chart.xAxis
.tickValues(tickMarks)
.tickFormat(function(d){ return d });
chart.yAxis
.tickFormat(d3.format(',f'));
var data = [{
'key': 'GB by year',
'values': [
{x: 2001, y: 0.12},
{x: 2004, y: 0.03},
{x: 2005, y: 0.53},
{x: 2006, y: 0.43},
{x: 2007, y: 5.5},
{x: 2008, y: 9.9},
{x: 2009, y: 26.85},
{x: 2010, y: 0.03},
{x: 2011, y: 0.12}
]
}];
d3.select('#chart svg')
.datum(data)
.transition().duration(500).call(chart);
nv.utils.windowResize(chart1.update);
return chart;
});
There is really no need to interpolate your values. You actually can modify the scale of most nvd3 charts, including multiBarCharts, although there is some extra work that needs to be done to make it work.
The basic thing you need to do is this:
var xScale = d3.time.scale();
chart.multibar.xScale(xScale);
Then that should just work! Except it doesn't, because the multiBarChart assumes that the xScale is d3.scale.ordinal(). So you will need to fake being that type by setting xScale.rangeBands and xScale.rangeBand:
xScale.rangeBands = xScale.range;
xScale.rangeBand = function() { return (1 - chart.groupSpacing()) * SOME_VALUE };
The problem now is getting SOME_VALUE. This needs to equal the width of an individual bar, which depends on two things: the width of the whole chart and the number of ticks there would be, including the zero values that are missing in the data.
Here's how nvd3 gets the available width internally:
var container = d3.select('#chart svg'),
availableWidth = (chart.width() || parseInt(container.style('width')) || 960) - chart.margin().left - chart.margin().right;
However, if the window resizes, you will need to refresh this value:
nv.utils.windowResize(function() {
availableWidth = (chart.width() || parseInt(container.style('width')) || 960) - chart.margin().left - chart.margin().right;
});
As for getting the number of ticks, this depends solely on your data. In your case, there will be 11 ticks: every year between 2001 and 2011. So we'll go with that. Therefore, the entire scale definition looks like this:
var container = d3.select('#chart svg'),
availableWidth,
numTicks = 11,
xScale = d3.time.scale();
function updateAvailableWidth() {
availableWidth = (chart.width() || parseInt(container.style('width')) || 960) - chart.margin().left - chart.margin().right;
}
updateAvailableWidth();
nv.utils.windowResize(updateAvailableWidth);
xScale.rangeBands = xScale.range;
xScale.rangeBand = function() { return (1 - chart.groupSpacing()) * availableWidth / numTicks; };
chart.multibar.xScale(xScale);
Finally, you need to set your xDomain manually. If you did this with the ordinal scale it had before, it would fail, but with a linear time scale it will work excellently:
chart.xDomain([new Date().setFullYear('2001'), new Date().setFullYear('2011')]);
Putting it all together, here is your example code (pasteable into http://nvd3.org/livecode/#codemirrorNav):
nv.addGraph(function() {
var chart = nv.models.multiBarChart(),
container = d3.select('#chart svg'),
availableWidth,
numTicks = 11,
xScale = d3.time.scale();
function updateAvailableWidth() {
availableWidth = (chart.width() || parseInt(container.style('width')) || 960) - chart.margin().left - chart.margin().right;
}
updateAvailableWidth();
nv.utils.windowResize(updateAvailableWidth);
xScale.rangeBands = xScale.range;
xScale.rangeBand = function() { return (1 - chart.groupSpacing()) * availableWidth / numTicks; };
chart.multibar.xScale(xScale);
chart.xDomain([new Date().setFullYear('2001'), new Date().setFullYear('2011')]);
chart.xAxis
.tickFormat(function(d){ return d3.time.format('%y')(new Date(d)); });
chart.yAxis
.tickFormat(d3.format(',f'));
chart.reduceXTicks(false);
chart.showControls(false);
var data = [{
'key': 'GB by year',
'values': [
{x: new Date().setFullYear('2001'), y: 0.12},
{x: new Date().setFullYear('2004'), y: 0.03},
{x: new Date().setFullYear('2005'), y: 0.53},
{x: new Date().setFullYear('2006'), y: 0.43},
{x: new Date().setFullYear('2007'), y: 5.5},
{x: new Date().setFullYear('2008'), y: 9.9},
{x: new Date().setFullYear('2009'), y: 26.85},
{x: new Date().setFullYear('2010'), y: 0.03},
{x: new Date().setFullYear('2011'), y: 0.12}
]
}];
container.datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
You can do this in 2 ways:
A) You either rewrite the axis component of nvd3 to use d3.time.scale() / make another axis component for this use case...
Or the easiest way:
B) You use the custom values for the axis. First of all you use the + operator ( +(date) ) to have the values in ms. There is a tickValues function in d3 that allows you to pass custom values for the ticks.. To force the X scale you have the forceX() method from the scatter (I assume you already know about this) and you write a simple function that takes custom values for ticks.... So if you force your scale to have values between Jan 1 2002 and Dec 31 2012 and then decide to have 4 ticks you can use either ticks directly or tickValues...
So it goes like this (add something similar to the multiBarChart.js file):
lines.forceX(minValue, maxValue) //where minValue and maxValue are the values
//converted to ms already after you did +(date)
//then you just rewrite the ticks - if you want a custom number of ticks you can do it like this
//numberOfTicks is a method I added to the axis component (axis.js) to give the number of ticks the user would like to have
//x.domain() now contains the forced values instead of the values you initially used..
var maxTicks = xAxis.numberOfTicks()-1, xMin = x.domain()[0], xMax = x.domain()[1],
xDiff = (xMax - xMin)/maxTicks, tickInterval = [];
tickInterval[0] = xMin;
for(i=1; i<maxTicks; i++){
var current = xMin + i*xDiff;
tickInterval[i] = current;
}
tickInterval[maxTicks] = xMax;
//tickInterval already contains the values you want to pass to the tickValues function
xAxis.tickValues(tickInterval);
Hope this helps... I know it's hack but it worked in my case :) And of course if you already formatted the date to be displayed as year you will get the values for the years when displaying the ticks :)
This is how I did it for lines. For multiBarChart you will need to add an extra step: you need to deal with the reduceTicks functionality (set it to false, delete that part of the code, do whatever you like with it...)

Resources