Is it possible to use iOS Charts (Daniel Gindi) to generate graphs in pdf document on iOS - pdf

I am using iOS Charts (Daniel Gindi) to generate graphs in an iOS app and I want to be able to generate a PDF report with those graphs included in the body of the report. Can anyone explain how to go about doing this. Ideally I don't want to generate an image from the UIView that shows in the app because the size/resolution would not be suitable for the PDF document.
As I understand it there are a few options:
use the graphics context for the pdf document to draw the graph on - it's not clear whether this would be possible when using the Charts library
use a UIView somehow to generate the graph and generate a PDF image data from that, embed this image into the pdf report
It seems like option 1 is probably the preferred way to get best resolution/control - somewhat speculative - doing it this way means you should be able to specify the exact position and size and get the correct font sizes, line thicknesses, etc..
Using option 2 means you have to figure out the scaling between a UIView and the PDF page view and I am not sure how these would map to each other.
Can anyone provide any suggestions on the following:
Is it possible to use Charts to generate graphs in a PDF document, and if so how?
If not what other options are there, short of writing custom drawing code.

OK so here is what I have done
Option 1: Using a UIView.layer to render on the PDF CGContect
func drawLineGraph(x: CGFloat, y: CGFloat)->CGRect{
let width = (pageSize.width - 2*kBorderInset - 2*kMarginInset)/2.0 - 50.0
let renderingRect = CGRect(x: x, y: y + 50.0, width: width, height: 150.0)
// Create a view for the Graph
let graphController = LineChartController(rect: renderingRect, building: self.building)
if let currentContext = UIGraphicsGetCurrentContext() {
let frame = graphController.chartView.frame
currentContext.saveGState()
currentContext.translateBy(x:frame.origin.x, y:frame.origin.y);
graphController.chartView.layer.render(in: currentContext)
currentContext.restoreGState()
}
return renderingRect
}
The graphController is just an object that has essentially the same function as the usual parent ViewController that would contain the graph. Sets the graph parameters and data.
Once that has been done the function below is called to render on the PDF page context.
A bit of translation required to put the graphs in the correct position.
Option 2: Drawing on the PDF Page CGContect
And the solution is...ta da...
func drawBarGraph(x: CGFloat, y: CGFloat)->CGRect{
let width = (pageSize.width - 2*kBorderInset - 2*kMarginInset)/2.0 - 50.0
let renderingRect = CGRect(x: x + width + 50, y: y + 50.0, width: width, height: 150.0)
// Create a view for the Graph
let graphController = BarChartController(rect: renderingRect, building: self.building)
if let currentContext = UIGraphicsGetCurrentContext() {
let frame = graphController.chartView.frame
currentContext.saveGState()
currentContext.translateBy(x:frame.origin.x, y:frame.origin.y)
//graphController.chartView.layer.render(in: currentContext)
graphController.chartView.draw(frame)
currentContext.restoreGState()
}
return renderingRect
}
Since the current context is set to the PDF page's context just call the charts draw() function directly passing the frame rectangle.
What have I missed here, can it be this easy ?
You can find a copy of the generated PDF here as well as sample code.

Related

Using metal to snapshot SCNRenderer produces darker output image

I'm using a Metal render pass to snapshot my SceneKit scene attached to a SCNRenderer. The method is faster than using the UIImage-producing SCNRenderer.snapshot(), but the output of the two methods is different; my method produces a darker image. I thought this could be to do with either a color-space difference, or alpha issue.
The image on the right shows my custom method, in which the color doesn't look right.
The color space seems to be the same in the UIImage produced by both the standard method's result, and my own (kCGColorSpaceModelRGB; sRGB IEC61966-2.1), so I don't think this is the issue.
I'll share elements of the custom render code that I believe are relevant.
I configure the MTLRenderPassDescriptor as follows:
renderPassDescriptor.colorAttachments[0].loadAction = MTLLoadAction.clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 0)
renderPassDescriptor.colorAttachments[0].storeAction = MTLStoreAction.store
I then create a texture to render into. I create a CGContext with:
bitsPerComponent: 8
bitsPerPixel: 32
colorSpace: CGColorSpaceCreateDeviceRGB()
bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.noneSkipFirst.rawValue
fillColor: UIColor.clear.cgColor
This is an area I'm concerned about. I've tried other color spaces, CGBitmapInfo and CGImageAlphaInfo flags, and other fill colors. The fill color does have an effect on the output, but I do need transparency, so clear does feel correct.
I create a MTLTextureDescriptor.texture2DDescriptor with .rgba8Unorm as the pixel format, with usage MTLTextureUsage(rawValue: MTLTextureUsage.renderTarget.rawValue | MTLTextureUsage.shaderRead.rawValue).
I then go on to hand my texture to the render pass descriptor and run a render command.
renderPassDescriptor.colorAttachments[0].texture = texture
let commandBuffer = commandQueue.makeCommandBuffer()!
renderer.render(atTime: time, viewport: viewport, commandBuffer: commandBuffer,
passDescriptor: renderPassDescriptor)
commandBuffer.commit()
In my normal pipeline, I go on here to create a CVPixelBuffer, but I introduced the creation of a CGImage to be able to more easily preview the image in the Xcode debugger. I do this using the following:
var data = Array<UInt8>(repeatElement(0, count: 4*mtlTexture.width*mtlTexture.height))
mtlTexture.getBytes(&data, bytesPerRow: 4*mtlTexture.width, from: MTLRegionMake2D(0, 0, mtlTexture.width, mtlTexture.height), mipmapLevel: 0)
let bitmapInfo = CGBitmapInfo(rawValue: (CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue))
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: &data,
width: mtlTexture.width,
height: mtlTexture.height,
bitsPerComponent: 8,
bytesPerRow: 4*mtlTexture.width,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue)
return context?.makeImage()
And this CGImage (or the CVPixelBuffer) is where I first observe the darkened image. So I believe that either the initial Metal render pass is creating the color disparity, or, I'm always performing wrong conversions to each other format I use.
An issue that is perhaps related can be found here:
https://github.com/MetalPetal/MetalPetal/issues/76
That issue seems to be taking place in a render view, and I don't use a SceneView or anything called a renderView. I have a SCNRenderer and I turn snapshots into images to write to video buffers, but the color issue presents itself earlier than those steps. The post does mention that the render view should use the format bgra8Unorm_srgb, so I wonder if that should be introduced in my pipeline, but I just can't work out where it belongs. Changing the pixelFormat from rgba8Unorm to bgra8Unorm_srgb in my MTLTextureDescriptor doesn't seem to make any difference.
Does this effect look familiar to anyone, or can anyone shed light on this?
It should work if you chose CGColorSpaceCreateWithName(kCGColorSpaceSRGB) for the bitmap context and MTLPixelFormatRGBA8Unorm_sRGB for the texture format.

How to crop rect area of PDF page using pdf.js

I want to crop specific rect (x,y,width,height) of a PDF page. Is that possible with pdf.js?
Get the canvas reference of the page. Let us say your current scale is 133% so you need to multiply your coordinates by 1.33
var context = document.getElementById("page1").getContext('2d');
var imageData = context.getImageData(
160.89*(1.33),
193.97*(1.33),
517.29*(1.33),
148.87*(1.33))
Then you can put it to some other canvas or do something with it
context.putImageData(imageData,0,0);

Determine the dimensions of a Dojo Widget

My objective is to build a Dojo widget that embeds some graphic information, for example an Analogue Gauge, along with some other stuff.
I have managed to build a widget and initialise the Analogue widget with code such as:
gauge = new dojox.widget.AnalogGauge({
id: "defaultGauge",
width: 300,
height: 200,
cx: 150,
cy: 175,
radius: 125,
Now I've generalised this so that the width, height, cx, cy and radius can be calculated if I know the dimensions in which the widget will be rendered. Say for example, it's going to be in the "top" region of a Border Layout of height 150px, then I can compute suitable values.
The question: how do I determine the available space for my widget to work in? Is there some API by which I can obtain this information from the Layout or Content Pane?
It seems that the widget can provide a
resize(dimensions)
method, which is called both when the widget is first displayed and also when it is resized. The argument being an object holding the width and height of the display area.
Hence if the
gauge = new dojox.widget.AnalogGauge({
code is moved into the resize method we can provide suitable size information to the gauge constructor. I have yet to understand how to deal with subsequent resize() requests.

dojox.drawing.Drawing - custom tool to create rectangle with rounded corners

I'm working with dojox.drawing.Drawing to create a simple diagramming tool. I have created a custom tool to draw rounded rectangle by extending dojox.drawing.tools.Rect as shown below -
dojo.provide("dojox.drawing.tools.custom.RoundedRect");
dojo.require("dojox.drawing.tools.Rect");
dojox.drawing.tools.custom.RoundedRect = dojox.drawing.util.oo.declare(
dojox.drawing.tools.Rect,
function(options){
},
{
customType:"roundedrect"
}
);
dojox.drawing.tools.custom.RoundedRect.setup = {
name:"dojox.drawing.tools.custom.RoundedRect",
tooltip:"Rounded Rect",
iconClass:"iconRounded"
};
dojox.drawing.register(dojox.drawing.tools.custom.RoundedRect.setup, "tool");
I was able to add my tool to the toolbar and use it to draw a rectagle on canvas. Now, I would like to customize the rectangle created by my custom tool to have rounded corners, but I'm not able to figure out how.
I have checked the source of dojox.drawing.tools.Rect class as well as it's parent dojox.drawing.stencil.Rect class and I can see the actual rectangle being created in dojox.drawing.stencil.Rect as follows -
_create: function(/*String*/shp, /*StencilData*/d, /*Object*/sty){
// summary:
// Creates a dojox.gfx.shape based on passed arguments.
// Can be called many times by implementation to create
// multiple shapes in one stencil.
//
//console.log("render rect", d)
//console.log("rect sty:", sty)
this.remove(this[shp]);
this[shp] = this.container.createRect(d)
.setStroke(sty)
.setFill(sty.fill);
this._setNodeAtts(this[shp]);
}
In dojox.gfx, rounded corners can be added to a a rectangle by setting r property.
With this context, could anybody please provide answers to my following questions?
What's the mechanism in dojox.drawing to customize the appearance of rectangle to have
rounded corners?
In the code snippet above, StencilData is passed to createRect call. What's the mechanism to customize this data? Can the r property of a rectangle that governs rounded corners be set in this data?
Adding rounded rectangles programmatically is easy. In the tests folder you'll find test_shadows.html which has a line that adds a rectangle with rounded corners:
myDrawing.addStencil("rect", {data:{x:50, y:175, width:100, height:50, r:10}});
You create a data object with x,y,width,height, and a value for r (otherwise it defaults to 0).
If you wanted to do it by extending rect, the easiest way to do it would just be to set the value in the constructor function (data.r=10, for example), or you could create a pointsToData function to override Rect's version. Either you would have set the value for this.data.r, or the default:
pointsToData: function(/*Array*/p){
// summary:
// Converts points to data
p = p || this.points;
var s = p[0];
var e = p[2];
this.data = {
x: s.x,
y: s.y,
width: e.x-s.x,
height: e.y-s.y,
r:this.data.r || 10
};
return this.data;
},
In that example I give r the value 10 as the default, instead of 0 as it was before. This works because every time stencil goes to draw your rect, it converts canvas x,y points (all stencils remember their points) to data (which gfx uses to draw). In other words this function will always be called before rect renders.

Zooming in an NSView

I have an NSView in which the user can draw circles. These circles are stored as an array of NSBezierPaths, and in drawRect:, I loop through the array and call -stroke on each of the paths. How do I add a button to zoom in and out the NSView? Just change the bounds of the view?
Thanks.
Send your view a scaleUnitSquareToSize: message.
You might also find this informative:
https://developer.apple.com/library/content/qa/qa1346/_index.html
The code in that document lets you add a "scale" property to a view.
The above answers didn't work for my scenario but led me to a solution.
The updated link to #Peter's answer was helpful: scaleUnitSquareToSize
I have found two soultions for zooming:
Cropping the bounds manually
Scalling the bounds with scaleUnitSquareToSize
I have created a small test project. Both solutions can be found on my GitHub repo : BoundsAndFramesCroppingAndScalling
To understand bounds vs frames read this SO article: difference-between-the-frame-and-the-bounds.
Swift scalling code:
let scaleSize = NSSize(width: 0.5, height: 0.5)
// 0.5 - Half the size
// 1.0 - No calling
// 2.0 - double the size , ... etc
myView?.scaleUnitSquare(to: scaleSize)
myView?.needsDisplay = true