diff --git a/src/mol-geo/representation/structure/cartoon.ts b/src/mol-geo/representation/structure/cartoon.ts
index 6296fce4978ef5d1795770ebb30baae35f74a9af..37c540cd6551fd95e93f2e4b5ffec426c4a0ba61 100644
--- a/src/mol-geo/representation/structure/cartoon.ts
+++ b/src/mol-geo/representation/structure/cartoon.ts
@@ -15,6 +15,7 @@ import { PolymerGapVisual, DefaultPolymerGapProps } from './visual/polymer-gap-c
 import { NucleotideBlockVisual, DefaultNucleotideBlockProps } from './visual/nucleotide-block-mesh';
 import { PolymerDirectionVisual, DefaultPolymerDirectionProps } from './visual/polymer-direction-wedge';
 import { CarbohydrateSymbolVisual } from './visual/carbohydrate-symbol-mesh';
+import { CarbohydrateLinkVisual } from './visual/carbohydrate-link-cylinder';
 
 export const DefaultCartoonProps = {
     ...DefaultPolymerTraceProps,
@@ -29,14 +30,20 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
     const gapRepr = StructureUnitsRepresentation(PolymerGapVisual)
     const blockRepr = StructureUnitsRepresentation(NucleotideBlockVisual)
     const directionRepr = StructureUnitsRepresentation(PolymerDirectionVisual)
-    const carbohydrateRepr = StructureRepresentation(CarbohydrateSymbolVisual)
+
+    // TODO move to own repr
+    const carbohydrateSymbolRepr = StructureRepresentation(CarbohydrateSymbolVisual)
+    const carbohydrateLinkRepr = StructureRepresentation(CarbohydrateLinkVisual)
 
     return {
         get renderObjects() {
-            return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects, ...blockRepr.renderObjects, ...directionRepr.renderObjects, ...carbohydrateRepr.renderObjects ]
+            return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects,
+                ...blockRepr.renderObjects, ...directionRepr.renderObjects,
+                ...carbohydrateSymbolRepr.renderObjects, ...carbohydrateLinkRepr.renderObjects ]
         },
         get props() {
-            return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props, ...carbohydrateRepr.props }
+            return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props,
+                ...carbohydrateSymbolRepr.props, ...carbohydrateLinkRepr.props }
         },
         create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => {
             const p = Object.assign({}, DefaultCartoonProps, props)
@@ -45,7 +52,8 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
                 await gapRepr.create(structure, p).runInContext(ctx)
                 await blockRepr.create(structure, p).runInContext(ctx)
                 await directionRepr.create(structure, p).runInContext(ctx)
-                await carbohydrateRepr.create(structure, p).runInContext(ctx)
+                await carbohydrateSymbolRepr.create(structure, p).runInContext(ctx)
+                await carbohydrateLinkRepr.create(structure, p).runInContext(ctx)
             })
         },
         update: (props: CartoonProps) => {
@@ -55,7 +63,8 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
                 await gapRepr.update(p).runInContext(ctx)
                 await blockRepr.update(p).runInContext(ctx)
                 await directionRepr.update(p).runInContext(ctx)
-                await carbohydrateRepr.update(p).runInContext(ctx)
+                await carbohydrateSymbolRepr.update(p).runInContext(ctx)
+                await carbohydrateLinkRepr.update(p).runInContext(ctx)
             })
         },
         getLoci: (pickingId: PickingId) => {
@@ -63,26 +72,30 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> {
             const gapLoci = gapRepr.getLoci(pickingId)
             const blockLoci = blockRepr.getLoci(pickingId)
             const directionLoci = directionRepr.getLoci(pickingId)
-            const carbohydrateRepr = directionRepr.getLoci(pickingId)
+            const carbohydrateSymbolLoci = carbohydrateSymbolRepr.getLoci(pickingId)
+            const carbohydrateLinkLoci = carbohydrateLinkRepr.getLoci(pickingId)
             return !isEmptyLoci(traceLoci) ? traceLoci
                 : !isEmptyLoci(gapLoci) ? gapLoci
                 : !isEmptyLoci(blockLoci) ? blockLoci
                 : !isEmptyLoci(directionLoci) ? directionLoci
-                : carbohydrateRepr
+                : !isEmptyLoci(carbohydrateSymbolLoci) ? carbohydrateSymbolLoci
+                : carbohydrateLinkLoci
         },
         mark: (loci: Loci, action: MarkerAction) => {
             traceRepr.mark(loci, action)
             gapRepr.mark(loci, action)
             blockRepr.mark(loci, action)
             directionRepr.mark(loci, action)
-            carbohydrateRepr.mark(loci, action)
+            carbohydrateSymbolRepr.mark(loci, action)
+            carbohydrateLinkRepr.mark(loci, action)
         },
         destroy() {
             traceRepr.destroy()
             gapRepr.destroy()
             blockRepr.destroy()
             directionRepr.destroy()
-            carbohydrateRepr.destroy()
+            carbohydrateSymbolRepr.destroy()
+            carbohydrateLinkRepr.destroy()
         }
     }
 }
\ No newline at end of file
diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts
new file mode 100644
index 0000000000000000000000000000000000000000..83be8d6a34304e4b244bfc558222fc7e9a1ca4c1
--- /dev/null
+++ b/src/mol-geo/representation/structure/visual/carbohydrate-link-cylinder.ts
@@ -0,0 +1,189 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ValueCell } from 'mol-util/value-cell'
+
+import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
+import { Unit, Structure, Link, StructureElement } from 'mol-model/structure';
+import { DefaultStructureProps, StructureVisual } from '..';
+import { RuntimeContext } from 'mol-task'
+import { createIdentityTransform, createColors } from './util/common';
+import { MeshValues } from 'mol-gl/renderable';
+import { getMeshData } from '../../../util/mesh-data';
+import { Mesh } from '../../../shape/mesh';
+import { PickingId } from '../../../util/picking';
+import { createMarkers, MarkerAction, MarkerData } from '../../../util/marker-data';
+import { Loci, EmptyLoci } from 'mol-model/loci';
+import { SizeTheme } from '../../../theme';
+import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
+import { Vec3 } from 'mol-math/linear-algebra';
+import { deepEqual } from 'mol-util';
+import { LocationIterator } from './util/location-iterator';
+import { createValueColor } from '../../../util/color-data';
+import { createLinkCylinderMesh, DefaultLinkCylinderProps, LinkCylinderProps } from './util/link';
+import { OrderedSet } from 'mol-data/int';
+
+// TODO create seperate visual
+// for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) {
+//     const tl = carbohydrates.terminalLinks[i]
+//     const center = carbohydrates.elements[tl.carbohydrateIndex].geometry.center
+//     tl.elementUnit.conformation.position(tl.elementUnit.elements[tl.elementIndex], p)
+//     if (tl.fromCarbohydrate) {
+//         builder.addCylinder(center, p, 0.5, linkParams)
+//     } else {
+//         builder.addCylinder(p, center, 0.5, linkParams)
+//     }
+// }
+
+async function createCarbohydrateLinkCylinderMesh(ctx: RuntimeContext, structure: Structure, props: LinkCylinderProps, mesh?: Mesh) {
+    const { links, elements } = structure.carbohydrates
+
+    const builderProps = {
+        linkCount: links.length,
+        referencePosition: (edgeIndex: number) => null,
+        position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
+            const l = links[edgeIndex]
+            Vec3.copy(posA, elements[l.carbohydrateIndexA].geometry.center)
+            Vec3.copy(posB, elements[l.carbohydrateIndexB].geometry.center)
+        },
+        order: (edgeIndex: number) => 1,
+        flags: (edgeIndex: number) => 0
+    }
+
+    return createLinkCylinderMesh(ctx, builderProps, props, mesh)
+}
+
+export const DefaultCarbohydrateLinkProps = {
+    ...DefaultMeshProps,
+    ...DefaultStructureProps,
+    ...DefaultLinkCylinderProps,
+    sizeTheme: { name: 'physical', factor: 1 } as SizeTheme,
+    detail: 0,
+    unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[]
+}
+export type CarbohydrateLinkProps = Partial<typeof DefaultCarbohydrateLinkProps>
+
+export function CarbohydrateLinkVisual(): StructureVisual<CarbohydrateLinkProps> {
+    let renderObject: MeshRenderObject
+    let currentProps: typeof DefaultCarbohydrateLinkProps
+    let mesh: Mesh
+    let currentStructure: Structure
+
+    return {
+        get renderObject () { return renderObject },
+        async create(ctx: RuntimeContext, structure: Structure, props: CarbohydrateLinkProps = {}) {
+            currentProps = Object.assign({}, DefaultCarbohydrateLinkProps, props)
+            currentStructure = structure
+
+            const { colorTheme } = { ...DefaultCarbohydrateLinkProps, ...props }
+            const instanceCount = 1
+            const elementCount = currentStructure.elementCount
+
+            mesh = await createCarbohydrateLinkCylinderMesh(ctx, currentStructure, currentProps, mesh)
+            // console.log(mesh)
+
+            const transforms = createIdentityTransform()
+            const color = createValueColor(0x119911)//createColors(colorTheme)
+            const marker = createMarkers(instanceCount * elementCount)
+
+            const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
+
+            const values: MeshValues = {
+                ...getMeshData(mesh),
+                ...color,
+                ...marker,
+                aTransform: transforms,
+                elements: mesh.indexBuffer,
+                ...createMeshValues(currentProps, counts),
+                aColor: ValueCell.create(new Float32Array(mesh.vertexCount * 3))
+            }
+            const state = createRenderableState(currentProps)
+
+            renderObject = createMeshRenderObject(values, state)
+        },
+        async update(ctx: RuntimeContext, props: CarbohydrateLinkProps) {
+            const newProps = Object.assign({}, currentProps, props)
+
+            if (!renderObject) return false
+
+            let updateColor = false
+
+            // if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+            //     updateColor = true
+            // }
+
+            // if (updateColor) {
+            //     createColors(LinkIterator.fromGroup(currentGroup), newProps.colorTheme, renderObject.values)
+            // }
+
+            updateMeshValues(renderObject.values, newProps)
+            updateRenderableState(renderObject.state, newProps)
+
+            currentProps = newProps
+            return false
+        },
+        getLoci(pickingId: PickingId) {
+            return getLinkLoci(pickingId, currentStructure, renderObject.id)
+        },
+        mark(loci: Loci, action: MarkerAction) {
+            // TODO
+            // markLink(loci, action, currentStructure, renderObject.values)
+        },
+        destroy() {
+            // TODO
+        }
+    }
+}
+
+function getLinkLoci(pickingId: PickingId, structure: Structure, id: number) {
+    const { objectId, elementId } = pickingId
+    if (id === objectId) {
+        const { links, elements } = structure.carbohydrates
+        const l = links[elementId]
+        const carbA = elements[l.carbohydrateIndexA]
+        const carbB = elements[l.carbohydrateIndexB]
+        const indexA = OrderedSet.findPredecessorIndex(carbA.unit.elements, carbA.anomericCarbon)
+        const indexB = OrderedSet.findPredecessorIndex(carbB.unit.elements, carbB.anomericCarbon)
+        return Link.Loci([
+            Link.Location(
+                carbA.unit, indexA as StructureElement.UnitIndex,
+                carbB.unit, indexB as StructureElement.UnitIndex
+            )
+        ])
+    }
+    return EmptyLoci
+}
+
+// TODO
+// function markLink(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData) {
+//     const tMarker = values.tMarker
+
+//     const links = structure.links
+//     const elementCount = links.bondCount
+//     const instanceCount = 1
+
+//     let changed = false
+//     const array = tMarker.ref.value.array
+//     if (isEveryLoci(loci)) {
+//         applyMarkerAction(array, 0, elementCount * instanceCount, action)
+//         changed = true
+//     } else if (Link.isLoci(loci)) {
+//         for (const b of loci.links) {
+//             const _idx = structure.links.getBondIndex(b.aIndex, b.aUnit, b.bIndex, b.bUnit)
+//             if (_idx !== -1) {
+//                 const idx = _idx
+//                 if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+//                     changed = true
+//                 }
+//             }
+//         }
+//     } else {
+//         return
+//     }
+//     if (changed) {
+//         ValueCell.update(tMarker, tMarker.ref.value)
+//     }
+// }
\ No newline at end of file
diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts
index eb65b9e8ddad298fb886976ea0d811693b3c5009..b0352d0b4986b46413924663351f65f1b5c38807 100644
--- a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts
+++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts
@@ -7,26 +7,27 @@
 import { ValueCell } from 'mol-util/value-cell'
 
 import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
-import { Unit, Structure } from 'mol-model/structure';
+import { Unit, Structure, StructureElement } from 'mol-model/structure';
 import { DefaultStructureProps, StructureVisual } from '..';
 import { RuntimeContext } from 'mol-task'
-import { createIdentityTransform } from './util/common';
+import { createIdentityTransform, createColors } from './util/common';
 import { MeshValues } from 'mol-gl/renderable';
 import { getMeshData } from '../../../util/mesh-data';
 import { Mesh } from '../../../shape/mesh';
 import { PickingId } from '../../../util/picking';
-import { createMarkers, MarkerAction } from '../../../util/marker-data';
-import { Loci, EmptyLoci } from 'mol-model/loci';
+import { createMarkers, MarkerAction, MarkerData, applyMarkerAction } from '../../../util/marker-data';
+import { Loci, EmptyLoci, isEveryLoci } from 'mol-model/loci';
 import { SizeTheme } from '../../../theme';
 import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util';
 import { MeshBuilder } from '../../../shape/mesh-builder';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra';
-import { createValueColor } from '../../../util/color-data';
 import { getSaccharideShape, SaccharideShapes } from 'mol-model/structure/structure/carbohydrates/constants';
+import { deepEqual } from 'mol-util';
+import { LocationIterator } from './util/location-iterator';
+import { OrderedSet } from 'mol-data/int';
 
 const t = Mat4.identity()
 const sVec = Vec3.zero()
-const p = Vec3.zero()
 const pd = Vec3.zero()
 
 async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, mesh?: Mesh) {
@@ -34,89 +35,74 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
 
     const carbohydrates = structure.carbohydrates
 
-    function centerAlign(center: Vec3, normal: Vec3, direction: Vec3) {
-        Vec3.add(pd, center, direction)
-        Mat4.targetTo(t, center, pd, normal)
-        Mat4.setTranslation(t, center)
-    }
-
     const side = 1.75 * 2 * 0.806; // 0.806 == Math.cos(Math.PI / 4)
     const radius = 1.75
 
-    const linkParams = { radiusTop: 0.4, radiusBottom: 0.4 }
-
     for (let i = 0, il = carbohydrates.elements.length; i < il; ++i) {
         const c = carbohydrates.elements[i];
-        if (!c.hasRing) continue;
-
-        const cGeo = c.geometry!
         const shapeType = getSaccharideShape(c.component.type)
 
+        const { center, normal, direction } = c.geometry
+        Vec3.add(pd, center, direction)
+        Mat4.targetTo(t, center, pd, normal)
+        Mat4.setTranslation(t, center)
+
+        builder.setId(i)
+
         switch (shapeType) {
             case SaccharideShapes.FilledSphere:
-                builder.addSphere(cGeo.center, radius, 2)
+                builder.addSphere(center, radius, 2)
                 break;
             case SaccharideShapes.FilledCube:
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.scaleUniformly(t, t, side)
                 builder.addBox(t)
                 break;
             case SaccharideShapes.CrossedCube:
                 // TODO split
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.scaleUniformly(t, t, side)
                 builder.addBox(t)
                 break;
             case SaccharideShapes.FilledCone:
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.scaleUniformly(t, t, side * 1.2)
                 builder.addOctagonalPyramid(t)
                 break
             case SaccharideShapes.DevidedCone:
                 // TODO split
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.scaleUniformly(t, t, side * 1.2)
                 builder.addOctagonalPyramid(t)
                 break
             case SaccharideShapes.FlatBox:
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
                 builder.addBox(t)
                 break
             case SaccharideShapes.FilledStar:
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.mul(t, t, Mat4.rotZY90)
                 builder.addStar(t, { outerRadius: side, innerRadius: side / 2, thickness: side / 2, pointCount: 5 })
                 break
             case SaccharideShapes.FilledDiamond:
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
                 builder.addOctahedron(t)
                 break
             case SaccharideShapes.DividedDiamond:
                 // TODO split
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side * 1.4, side * 1.4, side * 1.4))
                 builder.addOctahedron(t)
                 break
             case SaccharideShapes.FlatDiamond:
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side, side / 2, side / 2))
                 builder.addDiamondPrism(t)
                 break
             case SaccharideShapes.Pentagon:
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.mul(t, t, Mat4.rotZY90)
                 Mat4.scale(t, t, Vec3.set(sVec, side, side, side / 2))
                 builder.addPentagonalPrism(t)
                 break
             case SaccharideShapes.FlatHexagon:
             default:
-                centerAlign(cGeo.center, cGeo.normal, cGeo.direction)
                 Mat4.mul(t, t, Mat4.rotZYZ90)
                 Mat4.scale(t, t, Vec3.set(sVec, side / 1.5, side , side / 2))
                 builder.addHexagonalPrism(t)
@@ -124,24 +110,6 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru
         }
     }
 
-    for (let i = 0, il = carbohydrates.links.length; i < il; ++i) {
-        const l = carbohydrates.links[i]
-        const centerA = carbohydrates.elements[l.carbohydrateIndexA].geometry!.center
-        const centerB = carbohydrates.elements[l.carbohydrateIndexB].geometry!.center
-        builder.addCylinder(centerA, centerB, 0.5, linkParams)
-    }
-
-    for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) {
-        const tl = carbohydrates.terminalLinks[i]
-        const center = carbohydrates.elements[tl.carbohydrateIndex].geometry!.center
-        tl.elementUnit.conformation.position(tl.elementUnit.elements[tl.elementIndex], p)
-        if (tl.fromCarbohydrate) {
-            builder.addCylinder(center, p, 0.5, linkParams)
-        } else {
-            builder.addCylinder(p, center, 0.5, linkParams)
-        }
-    }
-
     return builder.getMesh()
 }
 
@@ -166,14 +134,14 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr
             currentProps = Object.assign({}, DefaultCarbohydrateSymbolProps, props)
             currentStructure = structure
 
+            const { colorTheme } = { ...DefaultCarbohydrateSymbolProps, ...props }
             const instanceCount = 1
             const elementCount = currentStructure.elementCount
 
             mesh = await createCarbohydrateSymbolMesh(ctx, currentStructure, mesh)
-            // console.log(mesh)
 
             const transforms = createIdentityTransform()
-            const color = createValueColor(0x999911) // TODO
+            const color = createColors(createCarbohydrateIterator(structure), colorTheme)
             const marker = createMarkers(instanceCount * elementCount)
 
             const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount }
@@ -196,19 +164,87 @@ export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolPr
 
             if (!renderObject) return false
 
+            let updateColor = false
+
+            if (!deepEqual(newProps.colorTheme, currentProps.colorTheme)) {
+                updateColor = true
+            }
+
+            if (updateColor) {
+                createColors(createCarbohydrateIterator(currentStructure), newProps.colorTheme, renderObject.values)
+            }
+
             updateMeshValues(renderObject.values, newProps)
             updateRenderableState(renderObject.state, newProps)
 
+            currentProps = newProps
             return false
         },
         getLoci(pickingId: PickingId) {
-            return EmptyLoci
+            return getCarbohydrateLoci(pickingId, currentStructure, renderObject.id)
         },
         mark(loci: Loci, action: MarkerAction) {
-            // TODO
+            markCarbohydrate(loci, action, currentStructure, renderObject.values)
         },
         destroy() {
             // TODO
         }
     }
 }
+
+function createCarbohydrateIterator(structure: Structure): LocationIterator {
+    const carbs = structure.carbohydrates.elements
+    const elementCount = carbs.length
+    const instanceCount = 1
+    const location = StructureElement.create()
+    const getLocation = (elementIndex: number, instanceIndex: number) => {
+        const carb = carbs[elementIndex]
+        location.unit = carb.unit
+        location.element = carb.anomericCarbon
+        return location
+    }
+    return LocationIterator(elementCount, instanceCount, getLocation)
+}
+
+function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: number) {
+    const { objectId, elementId } = pickingId
+    if (id === objectId) {
+        const carb = structure.carbohydrates.elements[elementId]
+        const { unit } = carb
+        const index = OrderedSet.findPredecessorIndex(unit.elements, carb.anomericCarbon)
+        const indices = OrderedSet.ofSingleton(index as StructureElement.UnitIndex)
+        return StructureElement.Loci([{ unit, indices }])
+    }
+    return EmptyLoci
+}
+
+function markCarbohydrate(loci: Loci, action: MarkerAction, structure: Structure, values: MarkerData) {
+    const tMarker = values.tMarker
+
+    const { byUnitAndElement } = structure.carbohydrates
+    const elementCount = structure.carbohydrates.elements.length
+
+    let changed = false
+    const array = tMarker.ref.value.array
+    if (isEveryLoci(loci)) {
+        if (applyMarkerAction(array, 0, elementCount, action)) {
+            changed = true
+        }
+    } else if (StructureElement.isLoci(loci)) {
+        for (const e of loci.elements) {
+            OrderedSet.forEach(e.indices, index => {
+                const idx = byUnitAndElement(e.unit, e.unit.elements[index])
+                if (idx !== undefined) {
+                    if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
+                        changed = true
+                    }
+                }
+            })
+        }
+    } else {
+        return
+    }
+    if (changed) {
+        ValueCell.update(tMarker, tMarker.ref.value)
+    }
+}
\ No newline at end of file
diff --git a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts
index bf55d079d25cd346fc2436638986917589fb5e01..9a4ec8d3b7973667448955b35b04bf3808564a3f 100644
--- a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts
+++ b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts
@@ -105,7 +105,7 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?:
             }
 
             if (i % 10000 === 0 && ctx.shouldUpdate) {
-                await ctx.update({ message: 'Gap mesh', current: i });
+                await ctx.update({ message: 'Nucleotide block mesh', current: i });
             }
             ++i
         }
diff --git a/src/mol-geo/representation/structure/visual/util/common.ts b/src/mol-geo/representation/structure/visual/util/common.ts
index e2e43030fd29a438e04924175c920d5541c63d9e..07bc94993cb11eafcf00208fd395fe0aed5175dd 100644
--- a/src/mol-geo/representation/structure/visual/util/common.ts
+++ b/src/mol-geo/representation/structure/visual/util/common.ts
@@ -16,6 +16,7 @@ import { ColorTheme, SizeTheme } from '../../../../theme';
 import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../../../theme/structure/color';
 import { ValueCell, defaults } from 'mol-util';
 import { LocationIterator } from './location-iterator';
+import { carbohydrateSymbolColorData } from '../../../../theme/structure/color/carbohydrate-symbol';
 
 export function createTransforms({ units }: Unit.SymmetryGroup, transforms?: ValueCell<Float32Array>) {
     const unitCount = units.length
@@ -37,6 +38,8 @@ export function createColors(locationIt: LocationIterator, props: ColorTheme, co
     switch (props.name) {
         case 'atom-index':
             return elementIndexColorData(locationIt, colorData)
+        case 'carbohydrate-symbol':
+            return carbohydrateSymbolColorData(locationIt, colorData)
         case 'chain-id':
             return chainIdColorData(locationIt, colorData)
         case 'element-symbol':
diff --git a/src/mol-geo/theme/index.ts b/src/mol-geo/theme/index.ts
index a306838fc92e1d7afc1d037d078c14c0b633b4a6..d34cb26a6d101a9ff03d58d9d03b136337ce69d1 100644
--- a/src/mol-geo/theme/index.ts
+++ b/src/mol-geo/theme/index.ts
@@ -5,8 +5,6 @@
  */
 
 import { Color } from 'mol-util/color';
-// import { Loci } from 'mol-model/loci';
-// import { Structure } from 'mol-model/structure';
 
 export interface UniformColorTheme {
     name: 'uniform'
@@ -14,27 +12,15 @@ export interface UniformColorTheme {
 }
 
 export interface ScaleColorTheme {
-    name:  'atom-index' | 'chain-id' | 'element-symbol' | 'instance-index'
+    name:  'atom-index' | 'chain-id'| 'instance-index'
     domain?: [number, number]
 }
 
-// interface StructureColorProvider {
-//     uniform(): Color
-//     instance(instanceIdx: number): Color
-//     element(elementIdx: number): Color
-//     elementInstance(elementIdx: number, instanceIdx: number): Color
-
-//     lociColor(loci: Loci): Color
-// }
-
-// export namespace ColorProvider {
-//     export function fromLociColor(lociColor: (loci: Loci) => Color) {
-
-//         return
-//     }
-// }
+export interface TableColorTheme {
+    name:  'carbohydrate-symbol' | 'element-symbol'
+}
 
-export type ColorTheme = UniformColorTheme | ScaleColorTheme
+export type ColorTheme = UniformColorTheme | ScaleColorTheme | TableColorTheme
 
 export interface UniformSizeTheme {
     name: 'uniform',
diff --git a/src/mol-geo/theme/structure/color/carbohydrate-symbol.ts b/src/mol-geo/theme/structure/color/carbohydrate-symbol.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e6f8065b436ea3260b77f9aea806b1f8dead892a
--- /dev/null
+++ b/src/mol-geo/theme/structure/color/carbohydrate-symbol.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Unit, StructureProperties, StructureElement, Link } from 'mol-model/structure';
+
+import { ColorData, createElementColor } from '../../../util/color-data';
+import { ColorScale, Color } from 'mol-util/color';
+import { LocationIterator, LocationValue } from '../../../representation/structure/visual/util/location-iterator';
+
+function getAsymId(unit: Unit): StructureElement.Property<string> {
+    switch (unit.kind) {
+        case Unit.Kind.Atomic:
+            return StructureProperties.chain.label_asym_id
+        case Unit.Kind.Spheres:
+        case Unit.Kind.Gaussians:
+            return StructureProperties.coarse.asym_id
+    }
+}
+
+export function carbohydrateSymbolColorData(locationIt: LocationIterator, colorData?: ColorData) {
+    const l = StructureElement.create()
+
+    function colorFn(locationValue: LocationValue): Color {
+        const { location } = locationValue
+        if (StructureElement.isLocation(location)) {
+            const map = location.unit.model.properties.asymIdSerialMap
+            const scale = ColorScale.create({ domain: [ 0, map.size - 1 ] })
+            const asym_id = getAsymId(location.unit)
+            return scale.color(map.get(asym_id(location)) || 0)
+        } else if (Link.isLocation(location)) {
+            const map = location.aUnit.model.properties.asymIdSerialMap
+            const scale = ColorScale.create({ domain: [ 0, map.size - 1 ] })
+            const asym_id = getAsymId(location.aUnit)
+            l.unit = location.aUnit
+            l.element = location.aUnit.elements[location.aIndex]
+            return scale.color(map.get(asym_id(l)) || 0)
+        }
+        return 0
+    }
+
+    return createElementColor(locationIt, colorFn, colorData)
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts
index 97034a5c5e856fc6b9ba91326ac0c903a6c949d9..cb1f9a33aa945d78624476e51a55b601038a90ac 100644
--- a/src/mol-model/structure/structure/carbohydrates/compute.ts
+++ b/src/mol-model/structure/structure/carbohydrates/compute.ts
@@ -18,7 +18,7 @@ import StructureElement from '../element';
 import Structure from '../structure';
 import Unit from '../unit';
 import { SaccharideNameMap, UnknownSaccharideComponent } from './constants';
-import { CarbohydrateElement, CarbohydrateLink, Carbohydrates, CarbohydrateTerminalLink } from './data';
+import { CarbohydrateElement, CarbohydrateLink, Carbohydrates, CarbohydrateTerminalLink, PartialCarbohydrateElement } from './data';
 import { UnitRings, UnitRing } from '../unit/rings';
 import { ElementIndex } from '../../model/indexing';
 
@@ -49,7 +49,7 @@ function getAnomericCarbon(unit: Unit.Atomic, ringAtoms: ArrayLike<StructureElem
             // possibly an anomeric carbon if this is a mono-saccharide without a glycosidic bond
             indexHasOxygenAndCarbon = ei
         } else if (label_atom_id.value(ei).startsWith('C1')) {
-            // likely the anomeric carbon as it is name C1 by convention
+            // likely the anomeric carbon as it is named C1 by convention
             indexHasC1Name = ei
         } else {
             // use any carbon as a fallback
@@ -122,6 +122,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
     const links: CarbohydrateLink[] = []
     const terminalLinks: CarbohydrateTerminalLink[] = []
     const elements: CarbohydrateElement[] = []
+    const partialElements: PartialCarbohydrateElement[] = []
 
     const elementsWithRingMap = new Map<string, number>()
 
@@ -173,10 +174,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
                 const sugarRings = filterFusedRings(unit.rings, sugarResidueMap.get(residueIndex));
 
                 if (!sugarRings || !sugarRings.length) {
-                    elements.push({
-                        hasRing: false,
-                        unit, residueIndex, component: saccharideComp
-                    })
+                    partialElements.push({ unit, residueIndex, component: saccharideComp })
                     continue;
                 }
 
@@ -199,8 +197,8 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
                     elementsWithRingMap.set(elementKey(residueIndex, unit.id, altId), elementIndex)
                     elements.push({
                         geometry: { center, normal, direction },
-                        hasRing: true,
-                        unit, residueIndex, component: saccharideComp
+                        component: saccharideComp,
+                        unit, residueIndex, anomericCarbon
                     })
                 }
 
@@ -279,5 +277,20 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
         })
     }
 
-    return { links, terminalLinks, elements }
+    // build lookup map
+    const map = new Map<string, number>()
+    for (let i = 0, il = elements.length; i < il; ++i) {
+        const { unit, anomericCarbon } = elements[i]
+        map.set(key(unit, anomericCarbon), i)
+    }
+
+    function key(unit: Unit, anomericCarbon: ElementIndex) {
+        return `${unit.id}|${anomericCarbon}`
+    }
+
+    function byUnitAndElement(unit: Unit, anomericCarbon: ElementIndex) {
+        return map.get(key(unit, anomericCarbon))
+    }
+
+    return { links, terminalLinks, elements, partialElements, byUnitAndElement }
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/carbohydrates/data.ts b/src/mol-model/structure/structure/carbohydrates/data.ts
index 0c9361042df84c5d713888b20be4424cb96f66b3..046748f921acd5d5efc31d5e6d42166d7797d1a4 100644
--- a/src/mol-model/structure/structure/carbohydrates/data.ts
+++ b/src/mol-model/structure/structure/carbohydrates/data.ts
@@ -6,7 +6,7 @@
 
 import Unit from '../unit';
 import { Vec3 } from 'mol-math/linear-algebra';
-import { ResidueIndex } from '../../model';
+import { ResidueIndex, ElementIndex } from '../../model';
 import { SaccharideComponent } from './constants';
 
 export interface CarbohydrateLink {
@@ -23,16 +23,24 @@ export interface CarbohydrateTerminalLink {
 }
 
 export interface CarbohydrateElement {
-    // geometry is only defined if at least one ring is present.
-    readonly geometry?: { readonly center: Vec3, readonly normal: Vec3, readonly direction: Vec3 },
-    readonly hasRing: boolean,
+    readonly geometry: { readonly center: Vec3, readonly normal: Vec3, readonly direction: Vec3 },
+    readonly anomericCarbon: ElementIndex,
     readonly unit: Unit.Atomic,
     readonly residueIndex: ResidueIndex,
-    readonly component: SaccharideComponent
+    readonly component: SaccharideComponent,
+}
+
+// partial carbohydrate with no ring present
+export interface PartialCarbohydrateElement {
+    readonly unit: Unit.Atomic,
+    readonly residueIndex: ResidueIndex,
+    readonly component: SaccharideComponent,
 }
 
 export interface Carbohydrates {
     links: ReadonlyArray<CarbohydrateLink>
     terminalLinks: ReadonlyArray<CarbohydrateTerminalLink>
     elements: ReadonlyArray<CarbohydrateElement>
+    partialElements: ReadonlyArray<PartialCarbohydrateElement>
+    byUnitAndElement: (unit: Unit, element: ElementIndex) => number | undefined
 }
\ No newline at end of file
diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts
index c88e165ecbd419f523053e38b8743c9fa9bbfdfc..ed5ce1cc1e9c53bb933dab8bc12afa39c5c25e3c 100644
--- a/src/mol-view/stage.ts
+++ b/src/mol-view/stage.ts
@@ -110,7 +110,8 @@ export class Stage {
         // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
         // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`)
         // this.loadMmcifUrl(`../../examples/1crn.cif`)
-        // this.loadPdbid('1zag') // temp
+        // this.loadPdbid('5u0q') // mixed dna/rna in same polymer
+        // this.loadPdbid('5u0q') // temp
 
         // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000001.cif`) // ok
         // this.loadMmcifUrl(`../../../test/pdb-dev/PDBDEV_00000002.cif`) // ok
@@ -154,8 +155,8 @@ export class Stage {
     }
 
     loadPdbid (pdbid: string) {
-        return this.loadMmcifUrl(`http://www.ebi.ac.uk/pdbe/static/entry/${pdbid}_updated.cif`)
-        // return this.loadMmcifUrl(`https://files.rcsb.org/download/${pdbid}.cif`)
+        // return this.loadMmcifUrl(`http://www.ebi.ac.uk/pdbe/static/entry/${pdbid}_updated.cif`)
+        return this.loadMmcifUrl(`https://files.rcsb.org/download/${pdbid}.cif`)
     }
 
     dispose () {