How to handle zoom and pan in d3 scaleOrdinal - d3.js

I have to create a custom stock chart with variable width x axis for each date.
I am using d3.scaleOrdinal for creating the x-axis
const xScale = d3.scaleOrdinal()
.domain(["16-09-2022", "17-09-2022", "18-09-2022", "19-09-2022", "20-09-2022"])
.range([0, 260, 500, 750, 900,1010]);
While zooming using geometric approach, the axis got scaled as a whole(i.e ticks, ticklabel, axis line) which I want to work like scaleLinear.
svg.select("#xaxis")
.attr("transform", `translate(${transform.x},${margin.top})scale(${transform.k}) `)
Can anybody suggest how to achieve the desired result with d3 v7
Thanks in advance

You need to use a continuous scale, like scaleLinear, otherwise it won't work because the ordinal scale cannot interpolate between values.
Think of it like having an array with indexes 0, 1, 2. There is no index 0.5 or 1.5, because ordinals are discrete whole values.

Related

set yAxis ticks D3js

I am having time series chart in D3js in that I am setting up yScale like.
setYScale() {
this.yScale = d3
.scaleBand()
.range([this.gHeight, 0])
.domain(this.yData) //this is array [0,1,2,3,4,5....23]
.padding(0.06);
},
This sets tick for me in left side, from 0 to 23 (24hrs), what I would like to achieve is that instead of having it sequential order from 0,1,2,3,4...tick I would like to have 0,2,4,6,8 (even numbers), and rest should work be as it is just ticks I would like to have it as even number. Can anyone help me on this.
Your code is just the scale, assuming you have an axis called axis, try:
axis.tickFormat(function(d) { return (+d)%2 ? '' : d })

d3 floating grouped bar with ranged values in a timeline

im trying to understand what tools i need to use as im new to d3 and didnt find any thing related...
i need a area chart that is like bars but can float and be on multiple values both on the x and y axis.
in this example the values are days but it might be hours/months etc...
need to know the direction i need to go.. / the right term to search...
There's no significant difference between drawing this chart and a normal bar chart.
And you need to define some scales that will map the values in your data to co-ordinates on your chart.
You need to draw some rect shapes.
So, in the above example you would define a time scale that, given an input date, will map that to a certain x co-ordinate on your chart. You can then use that to determine both the x co-ordinate for where the left-hand-side of a rectangle will be, and to work out how wide the rectangle needs to be.
const xScale = d3.scaleTime()
.domain([d3.min(dateValuesInMyDataset, d => d.date), d3.max(dateValuesInMyDataset, d => d.date)])
.range([0, widthOfMyChart]);
The above xScale if given the earliest date in your dataset would return the value 0, because this is the x co-ordinate representing that date.
Similarly, you would want to construct a linear scale which defines how to map the numerical range of values in your dataset, to the y co-ordinates in your chart. Then you can use the scale to determine the y value and height of all of the rectangles in your chart.
There are lots of good examples of this on ObservableHQ.com that you can browse and see the code for.

D3's scale not working properly

Well, I'm starting with D3 and I'm trying to create a vertical bar chart.
I really don't know what's happening but some things are not working as expected for me (maybe because I'm just a noob on the matter).
I'm using line scales, works pretty well with axes, but it's miscalculating the height of the bars, for instance the higher values are not displayed (because of the low value of the result).
I've used the d3.max to determine the range. I really don't get what's happening.
var yScaleLeft = d3.scale.linear()
.domain([0, d3.max(stats)])
.range([realHeight, 0]);
.attr("height", function(d) {
return yScaleLeft(d);
});
Here is the code: http://jsfiddle.net/aKhhb/ Look at * Scales * and // Stats bars
(Just forget about the x-alignement of the bars, I will see that later, I want to set its height properly)
Thanks a lot! Y saludos desde Chile :)
The issue is that your input and output ranges are mirrored -- that is, the largest input value maps to the smallest output value. That is fine, but you need to take it into account when calculating the y and height attributes. Essentially, you had the calculations for both reversed.
Fixed fiddle here. I've also fixed the x axis by adding your margin and half of the bar width to the computed x positions. Oh and you don't need parseInt() when doing calculations, only when you actually want to parse an integer from a string.

What does this.__chart__ refer to in a d3 selection?

I've been poking around in d3.box and d3.bullet. In both cases, the current scale is retrieved using something like the following...
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
...where x1 is the current scale object. I realise that this idiom is used to update the box plot or bullet chart, but could anyone explain how? What does this.__chart__ refer to? A scale object? Why the conditional? (||) Why does x0's domain cover the range 0 to infinity when the current scale is unlikely to have such a large range?
Apologies if my questions are poorly specified. Any help would be greatly appreciated.
The this context is the DOM element that contains the chart: i.e. a g element. Binding some variable to the DOM element, e.g. this.myvar = state, provides a way to deal with chart specific state. Multiple update calls on one specific chart g element, will then all have access to the same variable.
Mike and Jason have used the property name __chart__ in various charts and also in the d3 axis component, for keeping track of chart specific state.
You're right that in this case it's the scale that is being stored in the g element's __chart__ property. See excerpt from bullet.js:
// Compute the new x-scale.
var x1 = d3.scale.linear()
.domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
.range(reverse ? [width, 0] : [0, width]);
// Retrieve the old x-scale, if this is an update.
var x0 = this.__chart__ || d3.scale.linear()
.domain([0, Infinity])
.range(x1.range());
// Stash the new scale.
this.__chart__ = x1;
So, a scale x1 is determined based on the current data. It will be stored in __chart__, to be used some time in the future when this chart is updated with new data.
The previous scale is taken from this.__chart__ and kept in x0. The this.__chart__ will return undefined when the chart is just being constructed (i.e. the enter phase). In that case x0 will instead become d3.scale.linear().domain([0, Infinity]).range(x1.range()). See short circuit eval.
The old scale is needed for smooth transition. When new data points are entered, we first want to plot them on the chart using the old scale. After that we will transition all points (new and updated) according to the new scale.
Regarding the [0, Infinity] domain. So, a scale with this domain will only be used when the chart is just constructed. That means it provides a way to setup the initial transition when introducing the chart. A infinite domain with a finite range means that all points are scaled to 0. So, when the chart is set up, all points will be plotted at 0 and transition to the proper values according the x1 scale.

How to add a rectangle to specified axis in D3

I have a zoomable area plot done in D3, which works well. Now I am trying to add a rectangle to the specified location along x-axis in the middle of the plot. However, I can't seem to figure out how to do that. "rect" element is specified using absolute (x,y) of the plot and so when using zooms it stays in the same position.
So I was wondering if there is a way to tie "rect" to the axis when plotting, so that it benefits from all the zoom and translate behaviour or do I need to manually edit the x,y,width and length of the rectangle according to translation as well as figuring out where the corresponding x and y coordinates are on the graph? I am trying to use "rect" because it seems the most flexible element to use.
Thanks
Alex
I'm not sure how you are doing the zooming, but I am guessing you are changing the parameters of the scales you use with your axis? You should be able to use the same scales to place your rectangle.
If you are starting with plot coordinates then maybe using the invert function on the scale will help (available at least for quantitive scales), e.g. https://github.com/mbostock/d3/wiki/Quantitative-Scales#wiki-linear_invert
You should be able to take initial plot coordinates and invert them to determine data coordinates that can then move with changes in the scale.
If the scale is linear you can probably invert the length and width too, but you will have to compute offsets if your domain does not include 0. Easiest is to compute the rectangle's end points, something like:
var dataX0 = xScale.invert(rect.x);
var dataX1 = xScale.invert(rect.x + rect.width);
var dataWidth = dataX1 - dataX0;
If you have the data in axes coordinates already you should be able to do something like:
var rectData = [{x: 'April 1, 1999', y: 10000, width: 100, height:100}];
svg.selectAll('rect.boxy')
.data(rectData)
.enter().append('rect').classed('boxy', true)
.style('fill','black');
svg.selectAll('rect.boxy')
.attr('x', function(d) { return x(new Date(d.x));} )
.attr('y', function(d) { return y(d.y);})
.attr('width', function(d) { return d.width;} )
.attr('height', function(d) { return d.height;} );
Based on the example you shared where x and y (as functions) are the scales the axes are based on.

Resources