Is it possible in Recharts to show a Horizontal line at the Y location where the user has their mouse over and retrieve that Y value so we can display it on the Tooltip?
https://meridian.a2z.com/components/tooltip/?platform=react-web
I've been trying to do some research into how we could get the Y value on the graph where the mouse is hovering or clicked, but I'm having trouble seeing where we could even pull that out.
Any tips on attributes or components we could use to grab this data? Is it even something we have access to from the library?
To clarify, we're trying to get the value of the Y axis at where the user has their cursor over the graph.
So if the graph looks like this and the user has their mouse at the pink dot location, I would be trying to grab out the value of ~7000 - what the y value would be at that graph location
Edit:
Note about responsiveness:
If you want to make this responsive, just adjust the chartBounds based on the padding/margin you've applied to the chart component and you should be good to go.
If you're trying something more advanced and need the height and width to pass to the chart component for more calculations, the following article should help: https://www.pluralsight.com/tech-blog/getting-size-and-position-of-an-element-in-react/
NOTE: This is a bit of a hack and may not be a perfect solution but it should be enough to get you on the right track
You should be able to use the chartX and chartY fields from onMouseMove. Unfortunately, this is just the pixel value under the cursor but you should be able to translate it into the range you are using for your graph.
Here is an example put together using the SimpleLineChart example recharts has up. This should work if you just want to get the Y value under the user's cursor and can be extended to get the X value as well.
const {LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend} = Recharts;
const data = [
{name: 'Page A', uv: 4000, pv: 2400, amt: 2400},
{name: 'Page B', uv: 3000, pv: 1398, amt: 2210},
{name: 'Page C', uv: 2000, pv: 9800, amt: 2290},
{name: 'Page D', uv: 2780, pv: 3908, amt: 2000},
{name: 'Page E', uv: 1890, pv: 4800, amt: 2181},
{name: 'Page F', uv: 2390, pv: 3800, amt: 2500},
{name: 'Page G', uv: 3490, pv: 4300, amt: 2100},
];
//The pixel bounds for the LineChart, 0,0 is the top left corner
// these were found using the inspector built into the web browser
// these are in pixels but correspond to the values used in your graph
// so 246 is 0 Y on the graph and 5 is 10000 Y on the graph (according to your data)
const chartBoundsY = {min: 246, max: 5}
// The bounds we are using for the chart
const chartMinMaxY = {min: 0, max: 10000}
// Convert the pixel value from the cursor to the scale used in the chart
const remapRange = value => {
let fromAbs = value - chartBoundsY.min
let fromMaxAbs = chartBoundsY.max - chartBoundsY.min
let normal = fromAbs / fromMaxAbs
let toMaxAbs = chartMinMaxY.max - chartMinMaxY.min
let toAbs = toMaxAbs * normal
return Math.ceil(toAbs + chartMinMaxY.min)
}
const SimpleLineChart = React.createClass({
render () {
return (
<LineChart
width={600} height={300} data={data}
margin={{top: 5, right: 30, left: 20, bottom: 5}}
onMouseMove={props => {
// We get the values passed into the onMouseMove event
if(props.isTooltipActive) {
// If the tooltip is active then we display the Y value
// under the mouse using our custom mapping
console.log(remapRange(props.chartY))
}
}}
>
<XAxis dataKey="name"/>
<YAxis/>
<CartesianGrid strokeDasharray="3 3"/>
<Tooltip/>
<Legend />
<Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{r: 8}}/>
<Line type="monotone" dataKey="uv" stroke="#82ca9d" />
</LineChart>
)
}
})
ReactDOM.render(
<SimpleLineChart />,
document.getElementById('container')
)
You can open this example in jsfiddle and paste in the code above in the JS editor to try it out for yourself. http://recharts.org/en-US/examples
Here is the documentation for the mouse event for the LineChart: http://recharts.org/en-US/api/LineChart
This can be done with the axis scale option together with d3's invert method.
The following code excerpt should give you an idea.
const domainY = d3.extent(data, d => d[keyY])
const scaleY = d3.scaleLinear().domain(domainY).range([0, 1])
<AreaChart
onMouseDown={(e) => console.log(scaleY.invert(e.chartY))}
...
<YAxis
domain={['auto', 'auto']}
dataKey={keyY}
type="number"
scale={scaleY}
...
Related
I'm trying to use Squib to generate a map for a boardgame by printing one "card" which is the size of an entire A4 sheet of paper.
There must be some default in Squib that is cropping the resulting images and text, however, so that only a portion of it is visible.
Here is some sample code demonstrating the problem:
require 'squib'
Squib::Deck.new cards: 1, layout: [] do
rect x: 0, y: 0, width: 3457, height: 2438, fill_color: 'BLUE'
circle x: 1728, y: 1218 , radius: 1218, fill_color: 'RED'
save_pdf file: 'maps_1.pdf'
end
At 300 dpi, a landscape A4 piece of paper should be 3457x2438 pixels, so this ought to display a blue box with a red circle, filling the page. Instead it displays a poker-card-sized chunk of that image in the upper left hand corner:
resulting pdf
The result is much the same if I use millimeters, with a sprue:
require 'squib'
Squib::Deck.new cards: 1, layout: [] do
rect x: 0, y: 0, width: '295mm', height: '208mm', fill_color: 'BLUE'
circle x: '147.5mm', y: '104mm' , radius: '104mm', fill_color: 'RED'
save_pdf file: 'maps_1.pdf', sprue: 'layouts/map-sprue.yml'
end
Sprue:
---
sheet_width: 297mm
sheet_height: 210mm
card_width: 295.0mm
card_height: 208.0mm
cards:
- x: 0.0mm
y: 0.0mm
crop_line:
lines:
- type: :vertical
position: 0.0mm
- type: :vertical
position: 295.0mm
- type: :horizontal
position: 0.0mm
- type: :horizontal
position: 208.0mm
Does anyone know what is forcing squib only to address a portion of the A4 page?
thanks
You want width and height as parameters to Squib::Deck.new
To make your "card" the entire size of an A4 page, use something like this:
Squib::Deck.new(width: '210mm', height: '297mm') do
end
HOWEVER If you are planning on printing to an A4 page I recommend making it a tad smaller so that your printer doesn't autoscale or cut things off. My printer does fine with 6mm margin, probably something like 200x290.
You probably don't need sprues in this situation.
Continue my journey with Plotly.js. I'm using relative barmode and need to show some customized text when both opposite bars have value 0. The issue that for value 0 both bars grow in the same direction and labels are overlapped:
The only solution which I've thought about is checking when the value is 0 and change it dynamically to something like -0.009 and manually display the right value despite it's actually wrong. But it's cumbersome solution and chart keep rendering tiny bar for those values which isn't acceptable.
This is my example on Codepen
Can label direction be controlled manually? Thank you.
Initial Thoughts
One solution is to specify the base parameter for each trace as 0 and for the negative bars use a negative base value if the value is 0.
const altbase=-1000/3;
const trace1 = {
x: xValue,
y: [FN],
name: `Model 1`,
text: `${FN} <br> FN`,
type: 'bar',
base: 0,
textposition: 'outside'
};
const trace2 = {
x: xValue,
y: [-TN],
name: 'Model 1',
text: `${TN} <br> TN`,
base: altbase,
type: 'bar',
textposition: 'outside'
};
It looks like -max_y_lim / 3 results in a good offset which is what altbase is used for.
You could also define altbase as a variable checking if TN==0:
var altbase = ((TN==0) ? -FP/3 : 0);
UPDATE1: or you can bypass the variable and put the if statement right into trace definition like base: ((TN==0) ? -FP/3 : 0),
UPDATE2: and a slightly more elegant equation for the offset would be:
var altbase = ((TN==0) ? -1.4/4*Math.max(FN,TN,TP,FP) : 0);
Final Update
After some thought it's probably best to always calculate altbase so that it's available to the other potential negative bar and then use an if statement for setting the base: where needed.
var altbase = -1.4/4*Math.max(FN,TN,TP,FP);
const trace1 = {
x: xValue,
y: [FN],
name: `Model 1`,
text: `${FN} <br> FN`,
type: 'bar',
base: 0,
textposition: 'outside'
};
const trace2 = {
x: xValue,
y: [-TN],
name: 'Model 1',
text: `${TN} <br> TN`,
base: ((TN==0) ? altbase : 0),
type: 'bar',
textposition: 'outside'
};
Here is the implementation in CodePen.
I'm using jqplot for represent several parameters in series across the time. So I have one Xaxis represent the time and several Yaxes(yaxis, y2axis, y3axis, y4axis, etc.). Each parameter is in different units(temperature, voltage, current, pressure, etc.), so all the Yaxes set to auto for min and max because I dont know what values will come from the server. I'm using ajax request to fill the data and next for real time updating the series with new points.
So now I want for the user to be able to set manual min and max for any Y-axe. So I set by this way:
var axis_name="y2axis";
console.log(plot.axes[axis_name].max);
var new_max=prompt("Please enter max value?");
console.log(new_max);
var plotOptionsEval = "plotOptions = { axes: { "+axis_name+": { max: \""+new_max+"\" } } };";
eval(plotOptionsEval);
console.log(plotOptions);
plot.replot(plotOptions);
console.log(plot.axes[axis_name].max);
So , when I set new max for the first axis(yaxis) everythins is fine.
But when I try to set the max parameter of any other axis - y4axis for example something gone wrong and that max value has a some different value from this the user is entered.
This is a debug from console.log output
50
12
axes: y4axis: {max: "12"}
350
Any ideas?
So I have found a solution if someone asking the same.
The trick is you need to set min and max parameters for all Yaxis in your plot. So I have prepared the min/max axes object with its original values:
var plotOptions = {
axes: {
yaxis: {min: 11.568, max: 11.582}
y2axis: {min: 11.688, max: 11.702}
y3axis: {min: 6.390000000000001, max: 6.53}
y4axis: {min: -300, max: 50}
}
}
Next set the desired Yaxis min/max parameter in the plotOptions object:
var new_max=prompt("Please enter max value?");
plotOptions.axes.y2axis.max=parseFloat(new_max);
and finally replot it:
plot.replot(plotOptions);
I am trying to visualize this data in a stacked bar chart with D3 v4:
var data = [
{'id': '10', 'status': 'on', 'variant': 'A', 'value': '200'},
{'id': '10', 'status': 'on', 'variant': 'B', 'value': '500'},
{'id': '11', 'status': 'off', 'variant': 'A', 'value': '100'},
{'id': '12', 'status': 'on', 'variant': 'A', 'value': '600'},
...
]
All elements with the same key id should stack on the x axis, where value defines the height and status defines the color.
A data set may contain multiple elements with the same id but different status or variant, like id=10 in the example.
Which format would be suitable to stack this data? My approach was nesting it:
var nested = d3.nest()
.key(function(d) { return d.id; })
.entries(data);
From here I am not sure how to generate the y0 and y1 values.
Can I use d3.stack?
You could loop through the values array in each top-level object and keep a running total of value, which becomes y0 for each <rect>. y1 then is y0 + value.
Then bind each top-level object to a <g> tag representing the entire stack, translate that to the appropriate x position (assuming bars are vertical), and append <rect>s to each <g> with the values prop as data. Set the height and y position of the rect according to where in the stack it belongs. That might look like:
var groups = d3.selectAll("g")
.data(nested)
.enter().append("g");
groups.attr("transform","translate...");
var rects = groups.selectAll("rect")
.data(function(d) { return d.values })
.enter().append("rect");
rects
.attr("width",...)
.attr("height",...)
.attr("y",...)
I've tried reading the documentation but I haven't found a simple way to add a suffix to a hover text.
Is there a way to add text to the hoverformat? For example y respondents where y is the y value.
The setting yaxis: {hoverformat: ''} does not seem to allow strings?
I'm used to working with FlotChart where you can simply put "%y respondents".
Any help is much appreciated.
If you set text in your trace you can get a suffix.
Sets text elements associated with each (x,y) pair. If a single
string, the same string appears over all the data points. If an array
of string, the items are mapped in order to the this trace's (x,y)
coordinates.
Together with hoverinfo (hoverinfo: 'text+y') you get close to what you what you want (except for the unneeded line break which would need to be removed manually).
For bar charts this does not seem to work (at least not in Jan 2017). It is necessary to replicate the data and write it into an array in the text attribute (which also solves the line break issue).
var N = 16,
x = Plotly.d3.range(N).map(Plotly.d3.random.normal(3, 1)),
y = Plotly.d3.range(N),
data = [{
x: x,
y: y,
type: 'scatter',
mode: 'markers',
text: 'respondents',
marker: {
size: 16
},
hoverinfo: 'text+y'
}],
layout = {
hovermode: 'closest',
};
Plotly.plot('myDiv', data, layout);
//here comes the bar chart
var data = [{
x: ['Jan', 'Feb', 'Mar'],
y: [20, 14, 25],
type: 'bar',
text: [],
hoverinfo: 'text'
}];
for (N = 0; N < data[0].y.length; N += 1) {
data[0].text.push(data[0].y[N] + ' Respondents');
}
Plotly.plot('myBarChart', data);
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="myDiv" style="width:400px;height:400px;"></div>
<div id="myBarChart" style="width:400px;height:400px;"></div>