VictoryLegend: how to relatively position (e.g. bottom center) - victory-charts

I'm using VictoryCharts, specifically the VictoryLegend component to render the legend for my chart. According to the docs it sounds like the only options for positioning the legend are absolute x and y coordinates. I'm trying to position the legend relatively, for example "at the bottom in the middle". This is the desired appearance:
Because the series labels in my legend are internationalized, the number of characters and thus the legend's width changes based on locale, so it isn't an option to hard-code an x coordinate to center the legend. I would also prefer not to have to calculate the y coordinate based on the height of my chart. Is there any way to relatively position the VictoryLegend below the chart and horizontally centered?

According to the maintainer in a Gitter conversation, it appears this is not possible:

This is possible, but there's nothing entirely built-in with Victory charts to do it. The library has an amazing amount of flexibility but a terrible dearth of sensible defaults.
To begin, you first have to backfill basic chart responsiveness as outlined here:
https://github.com/FormidableLabs/victory/issues/396#issuecomment-773791721
Then you can use the same boundingRect width/height to position the legend as well:
https://codesandbox.io/s/loving-fog-f3ed9d?file=/index.js
Or the relevant code pieces here:
const Chart = () => {
//NOTE victory charts don't automatically resize width 🤦‍♂️ yet - https://github.com/FormidableLabs/victory/issues/396
const [boundingRect, setBoundingRect] = useState({ width: 0, height: 0 });
const containerRef = useCallback((node) => {
if (node !== null) {
setBoundingRect(node.getBoundingClientRect());
}
}, []);
//...
return (
<div ref={containerRef} style={{ width: "90vw", height: "90vh" }}>
<VictoryChart
domainPadding={30}
theme={VictoryTheme.material}
height={boundingRect.width * 0.9}
width={boundingRect.width}
>
<VictoryBar
data={chartData}
cornerRadius={2}
style={{ data: { fill: ({ datum }) => datum.fill } }}
/>
<VictoryLegend
data={legendData}
centerTitle
gutter={20}
itemsPerRow={2}
orientation="horizontal"
rowGutter={{ top: 0, bottom: -10 }}
style={{ title: { fontSize: 20 } }}
titleOrientation="bottom"
x={50}
y={boundingRect.width * 0.81}
/>
</VictoryChart>
</div>
);
};

Related

How to provide styles on X axis gantt high chart

I want some customization in the Gantt chart as follows:
display some text/html on the top left section (interaction of x and y-axis)
show x Axis (header) with black color in the background and some other customization in styles but got nothing in documentation.
Please refer attached image.
https://i.stack.imgur.com/Ir1gN.png
You can render text via SVG renderer, set position from the plot area. What interaction section do you mean, could you show screenshot?
https://api.highcharts.com/class-reference/Highcharts.SVGRenderer
Gantt chart is draw by line, and draw automatically, you can't find background options for axis in our documentation.
Possibility to make this is draw rectangle by SVG renderer and set them under draw line.
chart: {
events: {
render: function() {
var chart = this,
series = chart.series,
plotLeft = chart.plotLeft,
plotTop = chart.plotTop,
plotWidth = chart.plotWidth;
if (chart.myBackground) {
chart.myBackground.destroy();
}
chart.myBackground = chart.renderer.rect(plotLeft, plotTop, plotWidth, 50, 0)
.attr({
'stroke-width': 2,
stroke: 'red',
fill: 'yellow',
opacity: 0.5,
zIndex: -1
})
.add();
}
}
},
Live demo:
https://jsfiddle.net/BlackLabel/xmg31aez/

Higcharts - marginBottom does not let rendering xAxis series ellipsis

The space for xAxis generally is rendered dynamically by the Highcharts library and add the ellipsis in the correct place, making it display from the start and cutting it when it stop displaying it on the graph with some ellipsis.
When I try to change that space to not render dynamically, the property marginBottom does it,but it stops picking up when the text should start displaying and the start of the text is cutted from the down of the graph. Is there a way to render correctly the text at the bottom from the highcharts? I Do need it to be rotate 270 degrees, and not let the auto rotate work, and don't want to collapse more part of the graph just for displaying the text.
Here is a sample when that happes:
Highcharts.chart('container', {
chart: {
height: 350,
spacingBottom: 0,
marginBottom: 50,
type: "column"
},
series: [{
name: 'Total',
data: [1,2,3,4,5]
}],
xAxis: {
categories: ["very long name to test if the characters are rigth and cropped",
"Not so long but long enough", "I still am long and out of the screen",
"lets see how is cropped", "cropped", "crop"],
labels: {
rotation: 270
}
}
});
Sample fiddle with marginBottom : https://jsfiddle.net/ragmar/6ru4hze3/
If you remove the marginBottom, even the legend move down a bit.
It is possible to do this behavior? even including some css?
Thanks in advance
To make it work the way you want, you have to make some modifications in Highcharts core. For example you can do not use marginBottom option, but set fixed value as a commonWidth variable in renderUnsquish method:
if (attr.rotation) {
commonWidth = (
maxLabelLength > chart.chartHeight * 0.5 ?
40 : // changed from 'chart.chartHeight * 0.33'
maxLabelLength
);
if (!textOverflowOption) {
commonTextOverflow = 'ellipsis';
}
}
Live demo: https://jsfiddle.net/BlackLabel/moL1tw6c/

How to get horizontal padding in Photoswipe?

I'm using photoswipe with disabled zoom, like suggested here, but also want to avoid very landscapish images to reach beneath the prev/next arrows. Is there an easy way to achieve this?
Just found a solution, by
1) giving "padding" to the .pswp__item, like so:
.pswp__item {
left: 50px;
right: 50px;
}
2) updating the horizontal viewport size before resize:
pswp.listen('beforeResize', function(){
pswp.viewportSize.x = pswp.viewportSize.x - 100;
);
I can't comment on #johjoh's answer, so I'm going to copy it here and add the 3rd step required to get this working if you are using PhotoSwipe's getThumbBoundsFn.
1) giving "padding" to the .pswp__item, like so:
.pswp__item {
left: 50px;
right: 50px;
}
2) updating the horizontal viewport size before resize:
pswp.listen('beforeResize', function(){
pswp.viewportSize.x = pswp.viewportSize.x - 100;
);
3) Ensuring the getThumbBoundsFn properly calculates the x value by subtracting one side (50px in this example) from your calculated value:
bounds = {
x: {x} - 50,
y: ...,
w: ...,
}

Highcharts yAxis labels inside plot area and left padding

I have a column chart that has yAxis labels inside the plot area. DEMO: http://jsfiddle.net/o4abatfo/
This is how I have set up the labels:
yAxis: {
labels: {
align: 'left',
x: 5,
y: -3
}
}
The problem is that the leftmost column is so near the plot area edge that labels are overlapping it. Is there a way to adjust the plot area padding so that the columns would start a bit further on the right?
You can set min value as -0.49.
http://jsfiddle.net/o4abatfo/2/
One possible solution:
Keep the labels on the outside, and apply the plotBackgroundColor as the chart backgroundColor.
This means that the legend will be encased in the background color too, but, again, it's on option.
Example:
http://jsfiddle.net/jlbriggs/o4abatfo/11/
I asked the same thing in the Highcharts forum and got this solution as a reply from #paweł-fus:
var chartExtraMargin = 30;
Highcharts.wrap(Highcharts.Axis.prototype, 'setAxisSize', function (p) {
p.call(this);
if (this.isXAxis) {
this.left += chartExtraMargin;
this.width -= chartExtraMargin;
this.len = Math.max(this.horiz ? this.width : this.height, 0);
this.pos = this.horiz ? this.left : this.top;
}
});
However, adding this made the tooltips appear in the wrong position. This was fixed by overriding the Tooltip.prototype.getAnchor() method and adding the extra margin in the x coordinate:
Highcharts.wrap(Highcharts.Tooltip.prototype, 'getAnchor', function(p, points, mouseEvent) {
var anchor = p.call(this, points, mouseEvent);
anchor[0] += chartExtraMargin;
return anchor;
});

How to draw a background behind a plotLine label in a highstock chart

I want to draw a background behind a plotLine label in a highstock chart.
Using an example from the Highstock API, I came up with this code (crt is the chart object):
var textbox = crt.yAxis[ 0 ].plotLinesAndBands[ 0 ].label;
var box = textbox.getBBox();
crt.renderer.rect(box.x - 3, box.y + 1, box.width + 6, box.height, 3).attr({
fill: '#0c0',
id: 'labelBack',
opacity: .7,
'stroke-width': 0,
zIndex: 4
}).add();
This draws a semi-transparent box behind the label as intended (the label has zIndex 5). However when the chart is resized, the box maintains the same position relative to the top-left of the chart, causing misalignment with the label text (the position of the label changes because of the chart resizing).
I tried using the chart redraw event for this, but even though I can see that the event is fired, and the function is executed again, no other boxes are drawn (I was trying to get more boxes to appear on each redraw, planning to solve removing obsolete boxes in the next iteration).
How can I solve this?
It feels more like a hack than a genuine solution, but I have come up with a workaround that solves my issue for now, I have the function below:
var labelBackground = null;
function labelDrawBack(crt) {
if ( isIntraDay ) {
var textbox = crt.yAxis[ 0 ].plotLinesAndBands[ 0 ].label;
if ( !!labelBackground ) {
labelBackground.attr({ y: textbox.y - 10 });
} else {
var box = textbox.getBBox();
labelBackground = crt.renderer.rect( box.x - 3, box.y + 1, box.width + 6, box.height, 3 ).attr( {
fill: '#fff',
id: 'labelBack',
opacity: .65,
'stroke-width': 0,
zIndex: 4
}).add();
}
}
}
I make sure this function is executed immediately after the chart is initialized and additionally I attach the function to the chart object that is returned from the StockChart call:
var chartObj = new Highcharts.StockChart( chartConfig, function ( crt ) {
labelDrawBack( crt );
} );
chartObj.labelDraw = labelDrawBack;
And in the chart options I have added this to the chart.redraw event:
events: {
redraw: function() {
this.labelDraw(this);
}
}
This works as intended, moving the transparent background with the label (which is moved vertically when the chart is resized).
The reason I have redirected the call in the chart redraw event is that the labelDrawBack function is defined in another function than the one where my chart options are defined, thus the labelDrawBack function is out of scope there.

Resources