Recharts square zoom implementation - recharts

The sample I am talkin' about is here
https://recharts.org/en-US/examples/HighlightAndZoomLineChart
and here
https://codesandbox.io/s/highlight-zomm-line-chart-v77bt
Please press left button of the mouse and drag it to the right - this is how zoom is currently done. Please take a look at the activeLabel variable.
Currently recharts could make a zoom into the graph by passing the x coordinate (which is stored in activeLabel variable) and zooom looks like a pillar all over the y coordinate.
I want to select an area - square or rectangle to make more customized zoom. The trouble is that I can't get the y value of the graph (not pixel in window). Recharts gives only x coordinate of the graph, but not y.
I've searched all over the issues on gitHub, mailed the creator with no luck.
I've read the
Recharts value at pointer to show in tooltip?
but I could not get how could I count the initial values of chartX and chartY in my responsive container, so it is very depends on the window user has.
Please help me to find a solution to match chartY in pixels to my real values in my chart depending on Responsive Container I am using.

Zoom can be done by using the mouse events of the chart.
You can store the values in a state then call the zoom function on onMouseUp.
And in zoom function, you can set the domains of the axes.
Don't forget to set the axes to allowDataOverflow.
And you can also draw the rectangle by using ReferenceArea
function zoom () {
setXAxisBoundaries([rectangle[0], rectangle[1]])
setYAxisBoundaries([rectangle[2], rectangle[3]])
}
<ScatterChart
onMouseDown={(event) => setRectangle([event.xValue, event.yValue])}
onMouseMove={(event) => setRectangle(rectangle => [...rectangle, event.xValue, event.yValue]))}
onMouseUp={zoom}>
<XAxis
dataKey="x"
allowDataOverflow
domain={xAxisBoundaries}
type="number" />
<YAxis
dataKey="y"
allowDataOverflow
domain={yAxisBoundaries}
type="number" />

Related

Recharts - How to preserve edge value of Y-Axis?

I have a chart with proper display of data, but my Y-Axis is linear (should be) and it crops data (see screenshot).
I expect Y-Axis to show linear data AND respect the edge (max) value of the chart.
I was trying to work with d3-scale but I failed - don't know how to "append" max value to the scale. The only way I figured is to manually calculate all the ticks which I'd like to avoid.
My Y-Axis code is
<YAxis
domain={[data.yAxis.min, data.yAxis.max]}
scale="linear"
tickFormatter={formatEnergyAxis}
/>
Where min is 0 and max is 3200 (unit conversion happens in formatters).
Is there a way to adjust the scale?
If possible, call the function .nice() inside YAxis. Otherwise, call it before passing the values to the binding:
d3.scaleLinear().domain([0, 3200]).nice().domain()
// [0, 3500]
That would be
<YAxis
domain={d3.domain([data.yAxis.min, data.yAxis.max]).nice().domain()}
scale="linear"
tickFormatter={formatEnergyAxis}
/>
On your Y-Axis, you can specify your last tick to be visible with the props interval set to preserveEnd, like this:
<YAxis
domain={[data.yAxis.min, data.yAxis.max]}
scale="linear"
tickFormatter={formatEnergyAxis}
interval="preserveEnd"
/>
You can also use the value preserveStartEnd to be have the root & edge values visible.

d3 set default zoom configuration different from ZoomIdentity

like explained here: enter link description here I've added the zoom behavior to my chart.
D3 start the ZoomTransform to K=1, X=0, Y=0.
Is it possible to override those K, X, Y? For example says that the current state of chart (with its axis and scale) is not K=1,x=0,y=0, but it's something like k=0.012, x= 12, y= -18 ?
You can set the transform attribute of the SVG element in question as follows:
svg.attr('transform', 'translate(12, -18) scale(0.012)');
This could be either on initialisation of the visualisation, or in the handler for an event such as the click of a reset button.
It's important to note that this is how the zoom effect is being applied in your example. The result of d3.event.transform.toString() would be something like:
translate(100, 100) scale(2)
and the result of d3.zoomIdentity.toString() is:
translate(0, 0) scale(1)
In other words, you get a correctly formatted argument for an SVG element's transform attribute, which when applied using:
mySvgElement.attr('transform', d3.event.transform);
zooms, or pans the element.

D3 expanding rectangle's text is not adjusted

I have a rectangle and a text created through D3 which are grouped together like below.
<g>
<rect></rect>
<text></text>
</g>
In a javascript function I'm expanding this rectangle's width to allow the the value of text to be displayed in one go. (If the value is too long as the text value is set dynamically). This is working fine. However even though the rectangle width is expanding depending on the text value size , D3 text element is not showing the complete text value that is added, eventually the text is moved to left and the first bit of it can't be seen.
Do I need to increase the text element's width too or what would be the solution for this?
[UPDATE]
Following is the current code.
var elms = d3.selectAll("[id=s]"); // has the rectangle and text elements
var s = d3.selectAll("[id=s]").selectAll("text"); // just the text elements
var minimumValue = 100;
// updating rectangle width
elms.attr('width', function() { return dynamic < minimumValue ? minimumValue : dynamic;});
Now I need a way to update the position of the text element when the rectangle is expanded. Hope this is more clear. I just tried manually updating 'x' property of text element that works. But can I derive this value dynamically to set it within this function?
In case someone is looking for this, was able to get the text new 'x' value by x of rectangle + half of rectangle's width (and similar for y but with the height).
Got the answer from this link (How to place and center text in an SVG rectangle)

Snap SVG animating existing matrix

I use Snap.svg to create a simple card game. I loaded drawed cards from file and moved them to specific location using matrix translate.
It's svg element now looks kinda like this:
<g id="card11" inkscape:label="#g3908" transform="matrix(1.5621,0,0,1.5621,625.1085,529.3716)" cardposition="4" style="visibility: visible;" class="card inhand hand-4 ofplayer1">...</g>
However, now I'm trying to animate them to a specific position (same for all cards) using this:
function animateTo(object, x, y, scaleX, scaleY, time) {
var matrix = object.transform().localMatrix;
var added = new Snap.Matrix();
added.translate(x, y);
added.scale(scaleX, scaleY);
added.add(matrix);
object.animate({transform: added}, time);
}
or something like this:
function animateTo(object, x, y, scaleX, scaleY, time) {
object.animate({transform: "t100,100"}, time);//this one I tried to use to understand how snap animations works
}
And here is my problem - when it animates, it allways first deletes the animation matrix of object and start animate from it's original location with blank matrix (where the object would be without transform attribute).
For example, when I tried:
var matrix = object.transform().localMatrix;
object.animate({transform: matrix}, time);
I expected it will do nothing, but my object blinks to the top left corner (blank matrix) and animates to position where it should stay.
What am I doing wrong? I need to animate that object from some matrix state to another (ideally the same one for every object). Is it somehow possible? Like I can specify start transform attribute somehow?
Thanks.
According to Ian's advice, I've used toTransformString:
object.animate({transform: matrix.toTransformString()}, time);
but of course, I had to use it in previous transformations too using
object.attr({transform: added.toTransformString()});//this
//object.transform(added);//instead of this
However, getting local matrix still works as expected. Animation now works and I can use matrix.translate() - to relative move the object or object.animate({transform: "t100,100"}, time).
I also can modify a,b,c,d,e,f attributes of the matrix directly. (or use transform: "T100,100")
It works!
Thanks!

d3 autospace overlapping tick labels

Is there a way in d3 to not draw overlapping tick labels? For example, if I have a bar chart, but the bars are only 5 pixels wide and the labels are 10 pixels wide, I end up with a cluttered mess. I'm currently working on an implementation to only draw the labels when they do not overlap. I can't find any existing way to do that, but wasn't sure if anyone else had dealt with this problem.
There is no way of doing this automatically in D3. You can set the number of ticks or the tick values explicitly (see the documentation), but you'll have to figure out the respective numbers/values yourself. Another option would be to rotate the labels such that there is less chance of them overlapping.
Alternatively, like suggested in the other answer, you could try using a force layout to place the labels. To clarify, you would use the force layout on the labels only -- this is completely independent of the type of chart. I have done this in this example, which is slightly more relevant than the one linked in the other answer.
Note that if you go with the force layout solution, you don't have to animate the position of the labels. You could simply compute the force layout until it converges and then plot the labels.
I've had a similar problem with multiple (sub-)axis, where the last tick overlaps my vertical axis in some situations (depending on the screen width), so I've just wrote a little function that compares the position of the end of the text label with the position of the next axis. This code is very specific to my use case, but could adapted easily to your needs:
var $svg = $('#svg');
// get the last tick of each of my sub-axis
$('.tick-axis').find('.tick:last-of-type').each(function() {
// get position of the end of this text field
var endOfTextField = $(this).offset().left + $(this).find('text').width();
// get the next vertical axis
var $nextAxis = $('line[data-axis="' + $(this).closest('.tick-axis').attr('data-axis') + '"]');
// there is no axis on the very right, so just use the svg width
var positionOfAxis = ($nextAxis.length > 0) ? $nextAxis.offset().left : $svg.offset().left + $svg.width();
// hide the ugly ones!
if (endOfTextField > positionOfAxis) {
$(this).attr('class', 'tick hide');
}
});
The ticks with color: aqua are the hidden ones:

Resources