Get left and top position of highcharts scrollbar - angularjs

I wanted to put custom label on highcharts which should be placed on left
side of scrollbar. How can I get top and left position of scrollbar.?
I have put label with following code
chart.renderer.text('<span style="font-weight:600;"> 1-21 </span>', 20, 120)
.css({
color: 'green',
fontSize: '12px'
})
.add();

You can get position of the scrollbar using chart.xAxis[0].scrollbar.group.translateX and chart.xAxis[0].scrollbar.group.translateY, for example: https://codepen.io/anon/pen/KeBxNj?editors=1010
Snippet:
var chart = Highcharts.chart('container', {
chart: {
type: 'bar',
marginLeft: 150,
events: {
load: function () {
var scrollbar = this.xAxis[0].scrollbar,
bbox;
// Render:
this.customLabel = this.renderer.text('<span style="font-weight:600;"> 1-21 </span>', 0, 0).attr({
zIndex: 5
}).add();
// Get bbox
bbox = this.customLabel.getBBox();
// Position label
this.customLabel.attr({
x: scrollbar.group.translateX - bbox.width,
y: scrollbar.group.translateY + bbox.height
});
}
}
},
...
});

You can determine the scrollbar's left position using the chart.plotWidth + chart.plotLeft - 25. Also, the top position can be got using chart.plotTop + 10.
The numeric values are just top and left paddings.
Please have a look at this codepen.
https://codepen.io/samuellawrentz/pen/eKjdpN?editors=1010
Hope this helps :)

Related

Give different colors to area under and over a specific point on a D3 line chart

I've recently started working with D3 and I am moving all my existing charts over from Chartjs and so far my attempts have been successful. There is this one chart however that I am unable to produce exactly the same way in D3.
So with Chartjs, there's properties built in to the library that we can use to set the colors for values above and below a certain point on a Line chart. Here's what I had used to get the intended chart with Chartjs:
...config,
fill: {
above: '#4E4AFF20',
below: '#FF515114',
target: 'origin'
},
...config
And this is what the chart in Chartjs ended up looking like:
But D3 doesn't seem to have such a thing as far as I can tell. There's only gradients. So here's what I was able to build in D3:
As you can see, this looks way different from what I had earlier with Chartjs. Also notice how the gradient exists in both the line and the colored area underneath. I know it's there because I added it but that's not what I want and everywhere I look, that's the only way people are doing it. I have done countless attempts to fix this to no avail hence now I'm here asking for your help. Here's the D3 code I have right now:
import * as d3 from 'd3';
import { useEffect, useRef } from 'react';
interface Data {
x: number;
y: number;
}
const width = 350;
const height = 117;
const zeroPoint = 0;
const data: Data[] = [
{ x: 0, y: -20 },
{ x: 10, y: -20 },
{ x: 20, y: -20 },
{ x: 40, y: -20 },
{ x: 50, y: -20 },
{ x: 60, y: -20 },
{ x: 70, y: -20 },
{ x: 80, y: 0 },
{ x: 90, y: 20 },
{ x: 100, y: 20 },
{ x: 110, y: 20 },
{ x: 120, y: 20 },
{ x: 130, y: 20 },
{ x: 140, y: 20 },
{ x: 150, y: 20 }
];
export const Chart: React.FC = () => {
const ref = useRef<SVGSVGElement>(null);
const generateLinePath = (
element: d3.Selection<SVGSVGElement, unknown, null, undefined>,
data: Data[],
xScale: d3.ScaleLinear<number, number>,
yScale: d3.ScaleLinear<number, number>
) => {
const lineGenerator = d3
.line<Data>()
.x(d => xScale(d.x))
.y(d => yScale(d.y));
element.append('path').attr('d', lineGenerator(data));
};
const drawZeroLine = (element: d3.Selection<SVGSVGElement, unknown, null, undefined>, yScale: d3.ScaleLinear<number, number>) => {
element
.append('line')
.attr('x1', '0')
.attr('y1', yScale(zeroPoint))
.attr('x2', width)
.attr('y2', yScale(zeroPoint))
.attr('stroke', '#c4c4c4');
};
const createChart = (data: Data[]) => {
const svg = d3.select(ref.current!).attr('viewBox', `0 0 ${width} ${height}`);
svg.selectAll('*').remove();
const [minX, maxX] = d3.extent(data, d => d.x);
const [minY, maxY] = d3.extent(data, d => d.y);
const xScale = d3.scaleLinear().domain([minX!, maxX!]).range([0, width]);
const yScale = d3
.scaleLinear()
.domain([minY!, maxY!])
.range([height, 0]);
svg
.append('linearGradient')
.attr('id', 'line-gradient')
.attr('gradientUnits', 'userSpaceOnUse')
.attr('x1', 0)
.attr('x2', width)
.selectAll('stop')
.data(data)
.join('stop')
.attr('offset', d => xScale(d.x) / width)
.attr('stop-color', d => (d.y < zeroPoint ? '#FF5151' : '#4E4AFF'));
svg
.append('linearGradient')
.attr('id', 'area-gradient')
.attr('gradientUnits', 'userSpaceOnUse')
.attr('x1', xScale(data[0].x))
.attr('x2', xScale(data[data.length - 1].x))
.selectAll('stop')
.data([
{ color: '#FF515110', offset: '0%' },
{ color: '#4E4AFF20', offset: '100%' }
])
.enter()
.append('stop')
.attr('offset', function (d) {
return d.offset;
})
.attr('stop-color', function (d) {
return d.color;
});
svg.attr('stroke', 'url(#line-gradient)').attr('fill', 'url(#area-gradient)');
generateLinePath(svg, data, xScale, yScale);
drawZeroLine(svg, yScale);
};
useEffect(() => {
createChart(data);
}, []);
return <svg ref={ref} />;
};
So there's two problems I am looking to get solved with your help. The more important one is to give different colors to areas under and above the zero line in D3 the way I was able to do with Chartjs and the other one is moving away from gradients and get solid colors without any smooth transitions on both the line and the colored areas underneath.
Alright I managed to recreate the same chart in D3 using a workaround.
So it's not as straightforward as it's in Chartjs but it works pretty well. The idea is to create polygons under and over the line using the same data used to generate the line.
So my chart works like this. The grey line is a straight zero line and the values below zero go under that line with a red color and the ones above are purple. And here's what the Chart data looks like:
data = [
{ x: 0, y: 0 },
{ x: 1, y: 1 },
...
]
Anyways, here's the steps
Generate the scales
const [minX, maxX] = d3.extent(data, d => d.x);
const [minY, maxY] = d3.extent(data, d => d.y);
const xScale = d3.scaleLinear().domain([minX, maxX]).range([0, width]);
const yScale = d3.scaleLinear().domain([minY, maxY]).range([height, 0]);
Generate the line chart using D3's line() function. And don't give any stroke to the generated line.
const lineGenerator = d3.line().x(d => xScale(d.x)).y(d => yScale(d.y));
element.append('path').attr('d', lineGenerator(data));
Add a red SVG polygon that starts from the left side at zero line, then goes to the left bottom, then to where the value starts becoming negative and then finally to where the line reaches the zero line again.
svg.append('polygon').attr(
'points',
`
${xScale(minX - 1)},${yScale(0)} // top left point
${xScale(minX - 1)},${yScale(minY)} // bottom left point
${xScale(data[indexWhereRedPointStartsBecomingPositive].x)},${yScale(data[indexWhereRedPointStartsBecomingPositive].y)} // bottom right point
${xScale(data[indexWhereXReachesZeroLine].x)},${yScale(0)} // top right point
`
)
.attr('fill', 'lightRed')
.attr('stroke', 'darkRed');
Notice how we gave the red stroke to the polygon? That's the reason why we got rid of the stroke from the line and gave it here instead. This is because we need two separate colors (red for below and purple for above) for the chart. The reason why we do minX - 1 is because the stroke is applied to all four sides of the polygon and we want to hide it from the left side so we subtract 1px from the left.
Add another purple SVG polygon that starts from the left side at zero line (where the purple area starts somewhere in the middle), then goes all the way to the right end of the chart and then goes up to the top.
svg.append('polygon').attr(
'points',
`
${xScale(data[indexWhereValueStartsGoingPositive].x)},${yScale(0)}
${width + 1},${yScale(data[data.length - 1].y)}
${width + 1},${yScale(0)}
`
)
.attr('fill', 'lightPurple')
.attr('stroke', 'darkPurple');
Here we do width + 1 to hide the stroke of this purple polygon on the right side the same way we did minX - 1 with the left side of the red box.
So in conclusion, instead of giving stroke to the line generated using d3.line(), give strokes to the two polygons created using the same data that was used to generate the line chart and create the polygons 1px larger than the chart data so the strokes don't appear on the left and right side of the charts.
That's quite a lot I know but I couldn't think of any other way to get the chart to look like this. Anyways, I hope this helps anyone else experiencing a similar problem.

Filling a chart with a color above a specific Y value

I've been trying to fill a chart with color above certain Y value and I can't do it.
What I tried to do is writing a condition that if the value is >3.5 that area of the chart will be filled with a different color.
splineSeries.fill(function() {
if (this.value > 3.5)
return '#d3f335 0.4'
else
return '#cdf0a7 0.6'})
However, this doesn't work for me as it fills the whole area of the chart which values are >3.5 and not only the area that it's above the line.
This is how that chunk of code is working in my chart.1
If you know how this can be solved I would really appreciate if you help me :)
Thanks!!
It does not seem AnyChart supports this kind of filling out of the box. You can however get creative with gradients:
var cmin = chart.getStat("yScalesMin");
var cmax = splineSeries.getStat('seriesMax');
var cutoff = 3.5;
splineSeries.fill({
angle: 90,
keys: [{
color: '#cdf0a7',
opacity: 0.6,
offset: (cutoff-cmin) / (cmax-cmin)
}, {
color: '#d3f335',
opacity: 0.4,
offset: (cutoff-cmin) / (cmax-cmin)
}],
thickness: 3
});
Here's a working example:
chart = anychart.area();
var series = chart.area(generateRandomData());
var cmin = chart.getStat("yScalesMin");
var cmax = series.getStat('seriesMax');
var cutoff = 3.5;
series.fill({
angle: 90,
keys: [{
color: '#cdf0a7',
opacity: 0.6,
offset: (cutoff-cmin) / (cmax-cmin)
}, {
color: '#d3f335',
opacity: 0.4,
offset: (cutoff-cmin) / (cmax-cmin)
}],
thickness: 3
});
chart.container("chart");
chart.draw();
function generateRandomData() {
var data = [];
for (let i=0; i<16; i++) {
data.push({x:i, value:Math.random() * 7.5});
}
return data;
}
#chart {
height: 400px;
}
<script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-core.min.js"></script>
<script src="https://cdn.anychart.com/releases/8.11.0/js/anychart-cartesian.min.js"></script>
<div id="chart"></div>
This should also work with charts with yScalesMin > 0

Prevent JointJS elements from generating off paper

I'm using JointJS in a React environment to create a Directed graph from some Neo4j data. My problem is that elements are being generated off the paper As pictured here, "test6" is generated mostly off the page and "test10" isn't even shown. I would like all elements to be displayed on the paper, without overlapping each other or links if possible.
My paper is defined with only a width dimension set equal to the div width and the div is styled to be 100% width
...
width: $('#paper').width(),
...
and
render(){
return(
<React.Fragment>
<div id="paper" style={{width:'100%'}}></div>
</React.Fragment>
)
}
The code for generating an element is as follows:
function makeElement(node) {
var maxLineLength = _.max(node.name.split('\n'), function(l) { return l.length; }).length;
var letterSize = 12;
var width = 2 * (letterSize * (0.6 * maxLineLength + 1));
var height = 2 * ((node.name.split('\n').length + 1) * letterSize);
return new joint.shapes.basic.Rect({
id: node.id,
size: { width: 100, height: height },
attrs: {
type:'node',
text: {
text: node.name,
'font-size': letterSize,
'font-family': 'monospace' },
rect: {
width: width, height: height,
rx: 5, ry: 5,
stroke: '#555'
}
}
});
}
Thanks in advance :)
EDIT: I don't have the exact solution yet, but in the meantime I used this to make the paper draggable to view all nodes

How to customize tooltip for forceDirectedGraph in angular nvd3

I am using the force directed graph in angular nv3d. I would like to customize the text color on the nodes as well as modify the tool tip. Ive also been trying to figure out how to force the nodes to be more sparse. Here is my chart object:
chart: {
type: 'forceDirectedGraph',
height: 450,
color: function(d) {
return color(d.Name);
},
tooltipContent: function (key) {
return '<h3>' + key + '</h3>';
},
margin: {top: 20, right: 20, bottom: 20, left: 20},
nodeExtras: function(node) {
node && node
.append('text')
.attr('dx', 15)
.attr('dy', '.35em')
.text(function(d) {
return d.Name;
})
.style('font-size', '25px');
},
},
};
As you can see, I tried adding the tooltipContent property to the chart object to no avail. Any help would be greatly appreciated, thanks!
To customize the tool tip do the following
chart: {
type: 'forceDirectedGraph',
... /* All properties */
height : 400,
tooltip : {
contentGenerator : function (obj) { return "<div> **custom formating** </div>"}
}
}

ExtJs Chart, How to get boundary dates from selection mask on 'Time' type axis

I have ExtJs 4 Area chart with Time serie. I'd like user to be able to horizontally select part of chart and then obtain higher density data from server adequately. Problem is I can't get boundary dates from selection. I've got:
var chart = Ext.create('Ext.chart.Chart', {
store: store,
enableMask: true,
mask: 'horizontal',
listeners: {
select: {
fn: function(me, selection) {
console.log(arguments); // selection = Object { height: 218, width: 117, x: 665, y: 123 }
}
},
...
But select listener provides only pixel data. Is there some way to get boundary axis data (e.g. { from: 2013-08-01, to: 2013-08-20 } or some way to unproject pixels to values? I'm desperade I would say it's such a basic thing but can't find solution anywhere. Thanks in advance.
Well.. it probably doesn't exists a method for this. After digging into source code I've utilized lines from chart.setZoom() method to create function for manual unprojecting of mask selection to X axis data:
var unprojectXAxis = function(chart, selection) {
zoomArea = {
x : selection.x - chart.el.getX(),
width : selection.width
};
xScale = chart.chartBBox.width,
zoomer = {
x : zoomArea.x / xScale,
width : zoomArea.width / xScale
}
ends = chart.axes.items[0].calcEnds();
from = (ends.to - ends.from) * zoomer.x + ends.from;
to = (ends.to - ends.from) * zoomer.width + from;
return { from: new Date(from), to: new Date(to) };
}

Resources