Skip to content

View hierarchy and transforms

Ivan Schütz edited this page Apr 29, 2017 · 9 revisions

Pre-0.6 there would have been no need for this chapter - there was no zooming and panning and the chart contained only 1 view, where all the layers would render their content / add subviews.

With zooming and panning came the requirement to add new subviews (particularly in order to continue providing the same ways to add content to the chart as before), which would manage the transforms and required clipping.

Now a chart always contains the following 3 main views:

  1. Chart view: This is the view where the complete chart is displayed, it includes the content, the axes and external spacing (ChartSettings.leading, ChartSettings.top, ChartSettings.trailing and , ChartSettings.bottom). This is the same view you pass to the chart when using the initializer that accepts one, or the one which is created automatically when passing a frame instead of a view. The default axis layers and guidelines draw their content directly on this view.

  2. Container view: The boundaries of this view are the inner frame of the chart (i.e. space between axes, where we add data points). It's a direct subview of chart view. The purpose of this view is to clip the content view (3.) and be the parent of chart points that have to be translated but not scaled during zooming, like lines, markers or labels.

  3. Content view: This view also exists for inner frame content. Initially it has the same frame as container view (2.) but can be scaled and translated. Content that is added as subview of this view will be scaled during zooming. Which makes sense e.g. for bars.

Here an image to make this clearer (the image doesn't show content view clipped but it will of course normally be).

viewsimg

It's important to be aware of these implementation details when working with transforms, as it may not be clear to which view the data views have to be added. In most cases each layer makes these decisions internally and you don't have to worry about it, except when using ChartPointsViewsLayer, which is handled in the following section.

ChartPointsViewsLayer transform modes.

Being a very generic way to add content to the chart, this layer doesn't know what kind of views it creates (i.e. if they have to be scaled during transform, or only translated, or something else) and thus doesn't know where to add them. You have to help the layer a little by providing these informations. This can be done by passing a mode to its initializer, which is defined as follows:

public enum ChartPointsViewsLayerMode {
    case scaleAndTranslate, translate, custom
}
  • scaleAndTranslate will apply directly the chart transform to the view making is scale as well as translate during zomming and panning. This is recommended for views that scale well, e.g. rectangular bars. Internally this mode means that ChartPointsViewsLayer will add the views to the chart's content view.

  • translate will only translate the views during zooming, and not change their size. This is recommended for text, markers, lines, etc. Internally this mode means that ChartPointsViewsLayer will add the views to the chart's container view.

  • custom is similar to translate, in that the view will not be scaled (yes, the naming could be improved), i.e. also added to the container view. It expects you to also set a customTransformer function in ChartPointsViewsLayer, which will be called on each transform delta with the current state (e.g. the layer, from which you can use the axes to calculate the updated positions) and view, letting you decide how to more the view in reaction to the transform. This mode makes sense, for example, when showing overlays or markers that have a specific offset of the point they originally represent. In the custom transform you would ensure that the view preserves this offset. Otherwise it's center will reset to the screen location of the chart point it represents. Another use case for this is when you want to translate the views only along one axis and ignore the other, like in the 3 markers at the right side of this chart:

markerschart

The custom transform function for this chart would look like this:

chartPointsLayer.customTransformer = {(model, view, layer) -> Void in
    let updatedScreenLoc = layer.modelLocToScreenLoc(x: model.chartPoint.x.scalar, y: model.chartPoint.y.scalar)
    view.frame.origin = CGPoint(x: layer.chart?.containerView.frame.maxX ?? 0, y: updatedScreenLoc.y)
}

As you can see, only the y position is updated according to the current transform, while x stays fixed at the right border of the inner frame.