Single Axis Zoom in D3 & React - reactjs

I am currently trying to make a simple graph using D3 and React, where it is possible to zoom in/out on both axes, or on one of the two axes separately. An example of such a graph made only with D3 is available here (http://bl.ocks.org/jgbos/9752277).
I managed to get the same behaviour using D3 and React, as you can see in this sandbox (https://codesandbox.io/s/objective-cohen-b92pr). This example works almost perfectly, but I sometimes see a "jump" when, for example, I zoom in on Y, then X and then both (see gif below).
My guess is that d3 stores a zoomTransform object {k, x, y} for each container (x-axis-listener, y-axis-listener & svg) on which the zoom event is called. I suppose we should be able to share the same instance of the object between all the containers, but so far I have not been successful.
Any guesses?
Thanks a lot !

There is a good reason why you can either have X and Y zoom separately, or have a single zoom factor in your graph (which applies to both X and Y), but you cannot have a situation where you can smoothly change them all. That's why:
A D3 zoom identity object has 3 attributes: x, y, k, where x and y are offsets from the initial pos (0,0) and k is the current zoom factor (1 by default).
Now, if you want to manage 3 separate zoom identities, you have actually 3 different objects {xV, yV, kV} for vertical zoom, {xH, yH, kH} for horizontal and {xG, yG, kG} for the global zoom. When you rescale, you can apply xV to xH, yH to yG and so on, but you cannot apply two different values (kV and kH) to one (kG), you will need to choose between them (otherwise you have a logical contradiction here).
UPD: You can, however, replace zoomBoth callback with mouseWheel event handler, take X and Y zoom identities, change both of them proportionally (divide or multiply to the same number) and then apply changed zoom identities to both X and Y scales. It's possible :)

I finally came up with a working solution, which can be found here: https://codesandbox.io/s/quirky-yalow-3y6q4. This is mainly a refacto in React of this code observable: https://observablehq.com/#d3/x-y-zoom.
I'm using only one global transform object in the dom, and using the useState hook to store the global transform previous value, and one transform object for each axis.
The actual and the previous global transforms object allows me to retrieve the parameters to pass to the transform.translate and transform.scale methods in order to update the axis transforms object accordingly to where my mouse is.
This feels really "hacky" to me and I there should be a more elegant way of doing this.

Related

React + Leaflet: how to rotate an array of markers and Rectangle vectors around (i) its center and (ii) one of its corners

I have created a very simple leaflet map displaying an array of markers as a square which I want to rotate through any angle around its center or relative to its bottom left hand vertex (which is also its origin & position[0][0]).
https://codesandbox.io/s/react-leaflet-grid-with-markers-ds9yl?file=/src/index.js
I don't wish to rotate individual markers (there's plugins for that) but the entire grid which should maintain its shape with all relative marker spacings remaining the same but with the entire map rotated through any angle. As an added complexity the each marker is rendered within a grid cell which is just a leaflet rectangle but which also needs to maintain its position relative to adjacent cells.
It would be a big bonus to be to be able to apply the transform when generating the grid, but applying the transform to the grid shown is also a great start.
Leaflet already provides path transforms but I need to transform an entire array of markers and their path representations. It looks like a geometry/maths problem but I'm hoping that leaflet already has it covered.
Any help or advice much appreciated.
Ok my friend. This was not trivial. But it was fun.
Working codesandbox
There is nothing built in to leaflet to make this happen. I am a big fan of leaflet-geometryutil to do GIS calculations and transformations in leaflet. While leaflet-path-transoform exists, I found it to be too narrow in scope for what you need. Rather, let's invent our own wheel.
The absolute simplest way to approach this is to take every coordinate that's involved, and rotate it around a given latlng. That can be done with leaflet-geometryutil's rotatePoint function. Note the documentation there is not 100% correct - it takes 4 arguments, the first being the map instance. So I had to get a ref to the map by tagging the <Map> component with a ref.
I threw a bit of UI on there so you can manually change the angle, and toggle the point of rotation from the center of the grid to the bottom left corner. That part is a bit trivial, and you can check out the code in the sandbox. The most crucial part is being able to rotate every point involved in the grid (squares and markers alike) around a rotation point. This function does that:
const rotatePoint = React.useCallback(
(point) => {
const { lat, lng } = L.GeometryUtil.rotatePoint(
mapRef.current.leafletElement,
L.latLng(point[0], point[1]),
transformation.degrees,
L.latLng(axis[0], axis[1])
);
return [lat, lng];
},
[mapRef, transformation, axis]
);
The axis parameter is the rotation origin we want to use. transoformation.degrees is simply the number from a user input to get the nuber of degrees you want to rotate. I'm not sure how you plan on implementing this (UI vs programatically), but this function enables you to rotate however you want.
When the map is created, we take your gridBottomLeft and gridSize, and calculate an initial grid, called baseGrid, and save that to state. Really this doesn't need to be a state variable, because we don't expect it to change unless gridBottomLeft or gridSize changes, in which case the component will rerender anyway. However I kept it as a state var just to keep the same logic you had. It might also make sense to keep it as a state var, because as you see, when you toggle between different rotation origins, things may not behave as you expect, and you may want to reset the baseGrid when you toggle rotation origin points.
We keep a separate state var for the current state of the grid, grid, which is a variation of the baseGrid. When the user changes the degree input, a useEffect is fired, which creates a transofmration of baseGrid based on the degree number, and sets it to the grid state var:
useEffect(() => {
const rotatedGrid = baseGrid.map((square) => {
return {
id: square.id,
positions: square.positions.map((coord) => {
return rotatePoint(coord);
}),
center: rotatePoint(square.position)
};
});
setGrid(rotatedGrid);
}, [transformation.degrees, bottomLeft, rotatePoint, baseGrid]);
As you can see, what I'm doing is applying our rotation transformation function to all points in the square (renamed to positions), as well as the marker's position (renamed center).
And voila, the entire grid rotates, along with its markers, around the axis point that you define.
**Note that you were using the Rectangle component, with 2 points as bounds to define the rectangle. This no longer works as you rotate the rectangle, so I converted it to a Polygon, with the 4 corners of the square as its positions prop.
Also note this will not work in react-leaflet version 3. v3 has an absolute ton of breaking changes (which IMO have greatly improved the performance and UX of the library), but the management of refs and changing props is completely different, and as such, will need to be accounted for in v3. If you have questions about that, comment and I can write a bit more, but this answer is already long.

Recharts: how is it possible to show only the value of ONE data point inside a Tooltip?

I am trying to implement the following example inside a Recharts LineChart: the Tooltip value is relative to the blue point, because my mouse happens to be near it, and further away from the grey point with the same x-value. If I move the mouse closer to the grey point, the tooltip content changes.
However, all available examples show that a Recharts Tooltip receives data about all the data series being drawn and that it seems not possible to discriminate the point nearest to the mouse, so that the Tooltip may provide its value only.
Is there a way to specify for which dot I want to send data to the Tooltip?
At the end of a long fruitless search, I decided to solve this problem myself.
The minimal code is published in this Github gist.
The basic problem to solve is that any standard Recharts tooltip receives information about:
the x-value where the mouse pointer is at the moment, expressed in pixels on the chart canvas
the y-values for all the data series in closest position to the mouse x-value, expressed in the y-axis real-world unit (euros, kilograms, etc.)
It is necessary therefore to feed the custom tooltip also with y-axis mouse position information expressed in pixels on the chart canvas.
The tooltip can then calculate which data series is closest to the vertical mouse position and display only the value belonging to that data series.
Extracting the y-position in pixels is tricky, because Recharts changes the mapping between pixel and ordinate values each time it redraws the chart. But there is a chart component that must know very well this mapping, in order to place itself at the right vertical position and display the corresponding real-world ordinate value: that's every tick on the y-axis.
Problem is: how do we plug into the Recharts drawing workflow in order to get to know the mapping?
Here's how: the tick property of the Recharts YAxis component allows to provide a custom React component, albeit not documented with examples.
This custom component is instantiated one time for each tick that Recharts decides to place on the y-axis.
By trial and error I found out that my custom Tick component receives the following properties:
{ x, y, payload, ...anyCustomPropertyAddedByMe }
Where x and y are the cartesian coordinates of the tick (canvas pixels) and payload is such an object:
{ coordinates, isShow, offset, tickCoord, value }
Where value is expressed in real-world y-axis units.
The idea is to find out the couple (y, value) for the lowest and highest tick in each drawing and calculate the conversion factor between pixels and real values.
This will allow the custom tooltip to perform the computations mentioned above.
(Strictly speaking it would be enough to collect two couples from the first two ticks that are instantiated at each chart repaint, but choosing the two most far apart gives more precision)
The whole algorithm is divided among three components:
a tooltipCollector: this is a JavaScript module that presents two methods:
collect(value, y), invoked by the customized tick, that stores all couples (y, value) in a private array _collection
maxAndMin(), invoked by the custom tooltip, that reads the _collection array and returns the couple of items in the collection that represent the lowest and highest ticks (watch out that vertical pixel values in a canvas are measured upside down!)
a CustomizedTick React component that:
receives the tooltip collector among its custom properties
sends its y and payload.value to the collector by invoking its collect(y, value) method
returns a very simple JSX tick markup that makes usage of y (to place itself at the right vertical position) and payload.value (to display the user the real-world value the tick indicates)
a CustomTooltip React component that:
receives the tooltip collector among its custom properties and invokes its maxAndMin() method
verifies (by considering its prop coordinate.y) whether it's close enough to one of the chart data series, using a threshold value; this ensures that the tooltip is drawn only when the mouse cursor is very close to a point on the graph
modifies its returned JSX markup to contain only the value relative to the data series the mouse is closest to; in case more points in the chart are closer than the threshold, the tooltip will present more than one value
The code in my gist has been simplified to remove all unnecessary JSX markup. It presents a chart component that puts at work all the above mentioned components.
Please note that the standard Recharts behaviour of highlighting all the data series' points to which the tooltip abscissa is pointing has not been changed. It is therefore good practice to put a color code in the tooltip content to illustrate clearly to which data series the displayed value belongs.

OxyPlot WPF Muliple LinearBarSeries with same X-Axis Values

I currently have the need to be able to display multiple bars that may have the same X-Axis value. I used the WPF LinearBarSeries example in the source code.
The issue I am having is if the series with the smaller Y value is selected first, the series with the bigger Y value hides the smaller one. If I select the series with the bigger Y value first and then the smaller Y value, they both are displayed.
I am putting a small border around my bars to make them more readable.
Bigger First
Smaller First
Admittedly, I'm not an expert with OxyPlot and I haven't done a whole lot of charting in the past. Is there a better approach I should be taking? Maybe a different Series to use?
I did not find the way to display LinearBarSeries side by side either. I finally used the RectangleBarSeries where the bar is defined between two points x,y, and so you can manually define an offset between them.
var serie = new RectangleBarSeries { Title = "Example"};
serie.Items.Add(new RectangleBarItem(x1, 0, x2, y2));

JFreeChart: Logarithmic Axis Tick Labels missing

I am using JFreeChart to make XYLineCharts with a Logarithmic y-axis but am facing an issue that I am unable to resolve.
My x, y values are very low in some cases (in one such case, the y-axis values for the dataset range between 4.5e-8 to 1.7). I plot these values on a XYLineChart using a Logarithmic Axis for the y-axis (and using LogAxis.createLogTickUnits(Locale.ENGLISH) and .setExpTickLabelsFlag(true) on the y-axis to create the exponential tick units). I set my range's bounds from 4.5e-8 to 1.7 and can see the points plotted clearly but there are no tick labels visible for the y-axis !
I was earlier having this issue while zooming into the charts but I have fixed the zoom & AutoZoom by over-riding those methods.
My current LogarithmicAxis works well for most of my x, y datasets but in a few cases, the y-axis is plotted but does not show any Tick Labels on it, despite my creating them & setting their visibility to true.
If anyone has any suggestions on how to fix this & ensure that the Tick Labels are visible no matter what the y-axis values may be, please let me know soon as I need to get this done ASAP.
Thanks.

Is it possible to have independent multiple axes in WPF graphs?

Having independent multiple axes for each series in scientific graphs is very common condition.
The requirement is,
The graph can have more than one series. and each series is associated with separate Y axes and one X axis (Time).
E.g. Y1 axis shows temperature and Y2 axis shows pressure against Time on X axis.
Both axes may show different scales (range min - max) like temperature from 0 to 100 Degree kelvin & pressure from 300 to 900 kPa and accordingly the series will be plotted.
I am looking for open source control or freeware which can provide this feature.
I am trying to use WPF for plotting graphs and studying WPF Tookit & Dynamic Data Display for that.
Please look at FLOT example of multiple axes. I want this feature in WPF.
But I am not able to find feature of independent multiple axes in these controls.
Please help otherwise I have to drop the idea of using WPF.
Thanks in advance.
As far as Dynamic Data Display goes, I know that it is very possible to implement what you are looking for. You can always just make a new axis object and add it to the plotter's children. You can then interact and map each axis freely. The key to manipulation in D3 is the mapping, so if you have two different data sources, you can plot them on the same graph dependent on the axis that you map them to, and how you map them. Dynamic Data Display should fit your requirement.

Resources