I am trying to make a column chart using js HighCharts that has 2 unique plotlines for each column of the chart, rather than a single plotline that spans the width of the entire chart.
This way I will be able to show a max and min value for EACH column.
You can't make an actual plot line that works that way.
You can use a scatter series, and define a custom line marker type, like here:
http://jsfiddle.net/highcharts/e96yX/
example code for both a horizontal line and a vertical line:
Highcharts.Renderer.prototype.symbols.vline = function(x, y, width, height) {
return ['M',x ,y + width / 2,'L',x+height,y + width / 2];
};
Highcharts.Renderer.prototype.symbols.hline = function(x, y, width, height) {
return ['M',x ,y + height / 2,'L',x+width,y + width / 2];
};
alternatively, based on your question, a column range chart may be helpful as well:
http://highcharts.com/demo/columnrange
Related
Using highcharts, what would be the best way to implement target lines over a column or bar chart? (Also known as target vs goal)
Found this picture from a similar d3.js thread as an example:
I'm thinking another series, but don't see any options in the documentation about a target line. Here's a basic column chart: jsfiddle.net/eksh8a8p/
I've thought about using a columnrange series, but you are limited to having a start/end value which can be problematic due to scaling of number values.
Are there other ideas/options that could create a similar result to the picture above?
You can use a columnrange series but there is a simpler option - a scatter series with a rectangle marker
//custom marker
Highcharts.SVGRenderer.prototype.symbols['c-rect'] = function (x, y, w, h) {
return ['M', x, y + h / 2, 'L', x + w, y + h / 2];
};
//series options
{
marker: {
symbol: 'c-rect',
lineWidth:3,
lineColor: Highcharts.getOptions().colors[1],
radius: 10
},
type: 'scatter'
example: http://jsfiddle.net/eksh8a8p/1/
I followed a tutorial to create a canvas graph using js. The code plotting is this:
function plotData(context, dataSet, sections, xScale) {
context.lineWidth = 1;
context.outlineWidth = 0;
context.strokeWidth = 0;
context.beginPath();
context.moveTo(0, dataSet[0]);
for (i=0; i<sections; i++) {
context.lineTo(i * xScale, dataSet[i]);
}
context.stroke();
}
I am calling this function with an array holding the points where to go.. The x value is calculated per column (xScale) and the resulting graph if i use more than 1 source of data shows up fine. Screenshot when working fine:
http://s21.postimg.org/vlc1qg9iv/Screen_Shot_2016_04_08_at_15_48_42.png
But when i remove the 2 last data lines and leave only 1 line (so when the graph has a smaller difference between graph max and min values it shows up like this:
http://s16.postimg.org/ex0fakef9/Screen_Shot_2016_04_08_at_15_44_21.png
It is in this screenshot that you can clearly see, that while it should draw a line, the line is not really a 1px line but a shape, much like a (badly) distorted line?
I am not sure if i am doing something wrong or i am plainly ignoring something? The height of the canvas is fixed and it is always calculated using:
canvas = $('#canvas-container canvas')[0];
canvas.width = $('#canvas-container').width() * 0.9;
canvas.height = $('#canvas-container').width() / 1.45;
Thanks!
Codepen of the exact effect (from the exact tutorial) can be found here:
https://codepen.io/anon/pen/JXMwBy?editors=1111
(notice there are 2 more lines of graph data i commented out and in doing so i made the Val_max and Val_min vars different to "stretch" the data in the Y line)
You are stretching the Y axis on every operation after this line:
context.scale(1,-1 * yScale);
Instead, remove the line above and multiply the y values when you draw the line in plotData().
// multiply all Y values by -yScale to flip and scale
context.moveTo(0, dataSet[0] * -yScale);
for (i=1;i<sections;i++) {
context.lineTo(i * xScale, dataSet[i] * -yScale);
}
We are using Chartjs to plot the charts ,However there is no to give the x-axis titles,There is a solution for y-Axis title in here , But for x-Axis I could add the x axis name but could not create the space below the chart to properly place it , using y as this.chart.height overlapped the text with x axis title.
I have looked the chartjs v2.0 but it also does not have support for x-Axis title.
We set the height of the chart depending on how much height we want for the x axis label and then write in that space.
We don't have to do anything in the draw override because the canvas clearing too happens based on the height (that we adjusted)
Preview
Script
Chart.types.Line.extend({
name: "LineAlt",
initialize: function (data) {
this.chart.height -= 30;
Chart.types.Line.prototype.initialize.apply(this, arguments);
var ctx = this.chart.ctx;
ctx.save();
// text alignment and color
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillStyle = this.options.scaleFontColor;
// position
var x = this.chart.width / 2;
var y = this.chart.height + 15 + 5;
// change origin
ctx.translate(x, y)
ctx.fillText("My x axis label", 0, 0);
ctx.restore();
}
});
and then
...
new Chart(ctx).LineAlt(data);
Fiddle - http://jsfiddle.net/ct2h2wde/
I have made a simple graph in a canvas but am having difficulty with two issues.
The first issue is setting the vertical axis with an appropriate scale automatically with enough room for each data value in an array. Ideally i'd like the numbers to be more rounded to the nearest million or thousand etc depending on it's actual value ranges rather than a value like 33145 as the first scale line.
Currently one value is too high for the scale and is not being drawn on the canvas because it is out of bounds.
The second issue, is the points don't seem to be plotting in their correct location, which I am unsure where my mistake was.
I made a JSFiddle as for the most part it might be a bit confusing without seeing it in action:
http://jsfiddle.net/ezttywzr/
This is how i plot my data and draw my vertical axis:
Vertical Axis:
var x = 0,
y,
range = data.max() - data.min(),
valueStep = range / 10,
// get width of largest number
margin = 3 + ctx.measureText(data.min() + (valueStep*10)).width,
pixelStep = (graph.height-40) / 10,
verticalP = pixelStep,
output;
// draw left hand values
for(var i = 0; i < 11; i++){
output = data.min() + (valueStep*i);
y = graph.height-20 - (verticalP + i*pixelStep);
ctx.fillText(output,x,y+6);
ctx.beginPath();
ctx.moveTo(margin, y);
ctx.lineTo(x2,y);
ctx.stroke();
}
Data Plotting:
var y = graph.height,
x = margin,
pos,
valueStep = (graph.width-(margin*2)) / data.length,
pixelRange = graph.height-20,
pp = range / pixelRange;
for(var i = 0; i < data.length; i++){
x += valueStep;
pos = x - (valueStep/2);
ctx.beginPath();
ctx.moveTo(x, graph.height-20);
ctx.lineTo(x, graph.height);
ctx.stroke();
ctx.fillText('Week '+(i+1),pos-(ctx.measureText('Week '+(i+1)).width/2),y);
ctx.beginPath();
ctx.arc(pos,(graph.height-20)-(verticalP+(data[i]/pp)),2,0,2*Math.PI);
ctx.stroke();
ctx.fill();
}
Nice job so far.
I made a few changes: http://jsfiddle.net/ezttywzr/2/
To get the scale I used
STEP = data.max() / NUM_HORIZONTAL_LINES
Where NUM_HORIZONTAL_LINES is the number of horizontal lines you want above the x-axis. In this case I used 10.
This means the first line will be 1 * STEP, the second will be 2 * STEP, the third will be 3 * STEP and so on..
This scale is convenient because it guarantees that the max value fits on the graph. In fact, the max value is on the top line because of the way we defined the scale.
Once we have our scale it's easy to calculate the position of the points relative to the x-axis. It's simply:
(PIXELS_PER_STEP / STEP) * VALUE
To go a step further you can do some math to round the top point of the graph up and pick a scale with that has nice round numbers.
I will put a text element in every arc of my Pie Chart (center) - as shown in this example:
http://bl.ocks.org/mbostock/3887235
But I will only put the text element if the room is sufficient for the whole text, so im must compare the size of my text element with the "available" space in every arc.
I think I can do this with getBBox() to get the text dimensions... but how can I get (and compare) the dimension of the available space in every arc.
thx...!
This question has been asked several times before.
The solutions I have suggested there is to rotate the label but it has never quite satisfied me. Part of it was the horrible font rendering done by some browsers and loss in legibility that brings and the weird flip when one label crosses over the 180° line. In some cases, the results were acceptable and unavoidable, e.g. when the labels were too long.
One of the other solution, the one suggested by Lars, is to put the labels outside the pie chart. However, that just pushes the labels outside, granting them a larger radius, but does not solve the overlap problem completely.
The other solution is actually using the technique you suggest: just remove the labels which do not fit.
Hide overflowing labels
Compare Original, which has >= 65 label overflowing to Solution where the overflowing label is gone.
Reducing the problem
The key insight is to see that this problem is of finding whether one convex polygon (a rectangle, the bounding box) is contained inside another convex polygon(-ish) (a wedge).
The problem can be reduced to finding whether all the points of the rectangle lie inside the wedge or not. If they do, then the rectangle lies inside the arc.
Does a point lie inside a wedge
Now that part is easy. All one needs to do is to check:
The distance of the point from the center is less than the radius
The angle subtended by the point on the center is between the startAngle and endAngle of the arc.
function pointIsInArc(pt, ptData, d3Arc) {
// Center of the arc is assumed to be 0,0
// (pt.x, pt.y) are assumed to be relative to the center
var r1 = d3Arc.innerRadius()(ptData), // Note: Using the innerRadius
r2 = d3Arc.outerRadius()(ptData),
theta1 = d3Arc.startAngle()(ptData),
theta2 = d3Arc.endAngle()(ptData);
var dist = pt.x * pt.x + pt.y * pt.y,
angle = Math.atan2(pt.x, -pt.y); // Note: different coordinate system.
angle = (angle < 0) ? (angle + Math.PI * 2) : angle;
return (r1 * r1 <= dist) && (dist <= r2 * r2) &&
(theta1 <= angle) && (angle <= theta2);
}
Find the bounding box of the labels
Now that we have that out of the way, the second part is figuring out what are the four corners of the rectangle. That, also, is easy:
g.append("text")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; })
.each(function (d) {
var bb = this.getBBox(),
center = arc.centroid(d);
var topLeft = {
x : center[0] + bb.x,
y : center[1] + bb.y
};
var topRight = {
x : topLeft.x + bb.width,
y : topLeft.y
};
var bottomLeft = {
x : topLeft.x,
y : topLeft.y + bb.height
};
var bottomRight = {
x : topLeft.x + bb.width,
y : topLeft.y + bb.height
};
d.visible = pointIsInArc(topLeft, d, arc) &&
pointIsInArc(topRight, d, arc) &&
pointIsInArc(bottomLeft, d, arc) &&
pointIsInArc(bottomRight, d, arc);
})
.style('display', function (d) { return d.visible ? null : "none"; });
The pith of the solution is in the each function. We first place the text at the right place so that the DOM renders it. Then we use the getBBox() method to get the bounding box of the text in the user space. A new user space is created by any element which has a transform attribute set on it. That element, in our case, is the text box itself. So the bounding box returned is relative to the center of the text, as we have set the text-anchor to be middle.
The position of the text relative to the arc can be calculated since we have applied the transformation 'translate(' + arc.centroid(d) + ')' to it. Once we have the center, we just calculate the topLeft, topRight, bottomLeft and bottomRight points from it and see whether they all lie inside the wedge.
Finally, we determine if all the points lie inside the wedge and if they do not fit, set the display CSS property to none.
Working demo
Original
Solution
Note
I am using the innerRadius which, if non zero, makes the wedge non-convex which will make the calculations much more complex! However, I think the danger here is not significant since the only case it might fail is this, and, frankly, I don't think it'll happen often (I had trouble finding this counter example):
x and y are flipped and y has a negative sign while calculating Math.atan2. This is because of the difference between how Math.atan2 and d3.svg.arc view the coordinate system and the direction of positive y with svg.
Coordinate system for Math.atan2
θ = Math.atan2(y, x) = Math.atan2(-svg.y, x)
Coordinate system for d3.svg.arc
θ = Math.atan2(x, y) = Math.atan2(x, -svg.y)
You can't really do this with the bounding box because the bounding box is much larger than a wedge for the pie chart wedges. That is, even though the wedge at the outer edge would be wide enough to accommodate the text, that doesn't mean that it's wide enough at the actual position of the text.
Unfortunately, there's no easy way of doing what you're trying to do (pixel-level overlap testing). See e.g. this question for some more information. I would suggest simply putting the text labels outside of the pie chart so you don't run into this problem.