Plotly.js in relative barmode labels are overlapped when both bars have value 0 - d3.js

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.

Related

Recharts value at pointer to show in tooltip?

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}
...

Enable/disable clipping per material (r87)

I'm trying to work with THREE's clipping planes, I didn't read the description of Material.clipIntersection and just blindly took it to mean "is clipping enabled".
After reading the description, playing with the example and digging through code I've concluded that there is no parameter to control wether the clipping is enabled or not. The only two interfaces are:
.clippingPlanes[]
.clipIntersection
And perhaps Renderer.localClippingEnabled but i don't want to globally enable/disable the... local clipping. Ie. if i have two materials, i'd like to be able to control it on one.
The problem seems to be that clippingPlanes defines NUM_CLIPPING_PLANES:
'#define NUM_CLIPPING_PLANES ' + parameters.numClippingPlanes,
And I can see that there is more stuff going on with WebGLClipping. Still i'm confused by the define and am wondering if i need to update the material every time i add/remove the clipping planes.
tl:dr;
Is there a built in way to easily add a toggle to enable/disable the clipping to this example:
https://threejs.org/examples/#webgl_clipping_intersection, without recompiling the shader?
Probably not in the spirit of your question, but the easy solution is to replace the clippingPlanes property with an empty array.
I changed/added the following code in the example:
var params = {
clipPlanesOn: true,
clipIntersection: true,
planeConstant: 0,
showHelpers: false
};
...
var clipPlanes = [
new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 ),
new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0 ),
new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0 )
];
var clipPlanesOff = [];
...
gui.add( params, 'clipPlanesOn' ).name( 'clip planes on' ).onChange( function ( value ) {
var children = group.children;
for ( var i = 0; i < children.length; i ++ ) {
children[ i ].material.clippingPlanes = (value) ? clipPlanes : clipPlanesOff;
}
render();
} );
Checking the box turns the clipping on/off.
Update with more info:
AFAICT, it's all "automagic". Each render during setProgram, WebGLRenderer sets a variable called _clippingEnabled. This is set based on the return value from WebGLClipping.init, where one of the cases checks if the material has any values in its clippingPlanes property.
WebGLClipping has the following line:
uniform = { value: null, needsUpdate: false };
With _clippingEnabled set to true, and some values in the clippingPlanes property, the process makes its way into projectPlanes of WebGLClipping, which includes the line:
uniform.needsUpdate = true;
Boom. The uniform is flagged for automagic update.

DC JS: remove outer padding for line charts with an ordinal scale x-axis?

I built a line chart using DC JS with an ordinal x-axis. It works, except that there is some outer padding on the x-axis that I can't seem to get rid of. I'd like the left-most and right-most data points to be flush with the edges of the chart, with no padding. Hopefully I'm not missing something obvious.
I think I'm setting up the x-axis scale incorrectly. Given a data set like this:
var data = [
{ key: 'Monday', value: 3000000 },
{ key: 'Tuesday', value: 3100000 },
{ key: 'Wednesday', value: 3500000 },
{ key: 'Thursday', value: 3070000 },
{ key: 'Friday', value: 4500000 },
{ key: 'Saturday', value: 3003030 },
{ key: 'Sunday', value: 5010000 }
];
Here's what I'm doing with the x-axis scale:
var ordinalScale = d3.scale.ordinal().domain(['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']).rangeRoundBands([0,chartWidth]);
chart.x(ordinalScale).xUnits(dc.units.ordinal);
I've also looked at the D3 documentation on ordinal scales, but that hasn't helped yet.
Here's a screen shot (extraneous padding marked in red):
http://imgur.com/a/zmfF4
Here's a working JS fiddle that demonstrates the problem:
https://jsfiddle.net/qvp4fpzy/4/
It's a little confusing dealing with both dc.js and d3.js features around range bands.
dc.js has its own built-in calculations for bar/line x positions, but if the x scale is ordinal, it automatically chooses .rangeBands:
if (_chart.isOrdinal()) {
_x.rangeBands([0, _chart.xAxisLength()], _rangeBandPadding,
_chart._useOuterPadding() ? _outerRangeBandPadding : 0);
} else // ...
source link
So I don't think your call to .rangeRoundBands has any effect. (Should dc.js use rangeRoundBands instead of rangeBands? Maybe, but it doesn't weight on this question.)
It's the third parameter to rangeBands that you want to influence, and that's controlled by chart._outerRangeBandPadding(). (Ignore chart._useOuterPadding() - that's a backward compatibility thing.)
So all you need here is
chart._outerRangeBandPadding(0);
Working fork of your fiddle.

Draw/update circles in rect - nested selections confusion

I am trying to get a layout that would, for each object in the data array:
append a rect or a g element that will serve as container
inside or on top of this, append a circle for each of the coordinates.
Below is a mock-up of how the data is massaged before I'm trying to append to the DOM (at the top of the update() function in the block below):
[{
label: 'foo',
circles: [
{ x: 0, y: 10 },
{ x: 10, y: 10 }
]
},{
...
}]
The drawing and updating of the rect elements seems to be working fine, but I am getting the selection and joins confused for the circles.
Here's the block: http://blockbuilder.org/basilesimon/91f75ab5209a62981f11d30a81f618b5
With
var dots = rects.selectAll('.dots')
I can select the right data below but can't draw it.
Could you help me getting the selections right so I can draw and update both the rect and the circle, please?
Thank you Gerard for your help. This is my current state, but I've pitted myself into a hole by running a for loop instead of d3 selections.
I wonder if I couldn't nest the circles in g elements after building a new data object like so:
var data = dataset.map(function(d) {
var circles = d3.range(d.amount).map(function(i) {
return {
x: (i % 5)*20,
y: (i / 5 >> 0)*20
}
});
return {
label: d.label,
dots: circles
};
});
From each object in data, we'll append a g, and inside each g we'll append the circles. Any help appreciated, since this will affect the dots + i used by the update pattern...
New question here
Here is the problem:
var dots = svg.selectAll('dots')
You're selecting something that doesn't exist. Because of that, your "enter" selection will always contain all the data, and your "exit" selection will always be empty.
The solution is changing it for something like this:
var dots = svg.selectAll(".dots" + i)
And, in the enter selection, setting the classes:
.attr("class", "dots" + i)
Here is your updated bl.ocks (with some other minor changes): https://bl.ocks.org/anonymous/4c2e1d66f1ab890da983465a4f84ca9b

Plot.ly: Simple prefix or suffix for hover text

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>

Resources