custom-element-property.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { ElementIndex, Model, CustomPropertyDescriptor } from '../../mol-model/structure';
  8. import { StructureElement } from '../../mol-model/structure/structure';
  9. import { Location } from '../../mol-model/location';
  10. import { ThemeDataContext } from '../../mol-theme/theme';
  11. import { ColorTheme, LocationColor } from '../../mol-theme/color';
  12. import { Color } from '../../mol-util/color';
  13. import { Loci } from '../../mol-model/loci';
  14. import { OrderedSet } from '../../mol-data/int';
  15. import { CustomModelProperty } from './custom-model-property';
  16. import { CustomProperty } from './custom-property';
  17. import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
  18. export { CustomElementProperty };
  19. interface CustomElementProperty<T> {
  20. propertyProvider: CustomModelProperty.Provider<{}, CustomElementProperty.Value<T>>
  21. colorThemeProvider?: ColorTheme.Provider<{}>
  22. labelProvider?: LociLabelProvider
  23. }
  24. namespace CustomElementProperty {
  25. export type Value<T> = Map<ElementIndex, T>
  26. export interface Builder<T> {
  27. label: string
  28. name: string
  29. getData(model: Model, ctx?: CustomProperty.Context): Value<T> | Promise<Value<T>>
  30. coloring?: {
  31. getColor: (p: T) => Color
  32. defaultColor: Color
  33. }
  34. getLabel?: (p: T) => string | undefined
  35. isApplicable?: (data: Model) => boolean,
  36. type?: 'dynamic' | 'static',
  37. }
  38. export function create<T>(builder: Builder<T>): CustomElementProperty<T> {
  39. const modelProperty = createModelProperty(builder)
  40. return {
  41. propertyProvider: modelProperty,
  42. colorThemeProvider: builder.coloring?.getColor && createColorThemeProvider(modelProperty, builder.coloring.getColor, builder.coloring.defaultColor),
  43. labelProvider: builder.getLabel && createLabelProvider(modelProperty, builder.getLabel)
  44. }
  45. }
  46. function createModelProperty<T>(builder: Builder<T>) {
  47. return CustomModelProperty.createProvider({
  48. label: builder.label,
  49. descriptor: CustomPropertyDescriptor({
  50. name: builder.name,
  51. }),
  52. type: builder.type || 'dynamic',
  53. defaultParams: {},
  54. getParams: (data: Model) => ({}),
  55. isApplicable: (data: Model) => !!builder.isApplicable?.(data),
  56. obtain: async (ctx: CustomProperty.Context, data: Model) => {
  57. return await builder.getData(data, ctx)
  58. }
  59. })
  60. }
  61. function createColorThemeProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getColor: (p: T) => Color, defaultColor: Color): ColorTheme.Provider<{}> {
  62. function Coloring(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
  63. let color: LocationColor;
  64. const property = ctx.structure && modelProperty.get(ctx.structure.models[0])
  65. const contextHash = property?.version
  66. if (property?.value && ctx.structure) {
  67. const data = property.value
  68. color = (location: Location) => {
  69. if (StructureElement.Location.is(location)) {
  70. const e = data.get(location.element);
  71. if (typeof e !== 'undefined') return getColor(e);
  72. }
  73. return defaultColor;
  74. }
  75. } else {
  76. color = () => defaultColor;
  77. }
  78. return {
  79. factory: Coloring,
  80. granularity: 'group',
  81. color: color,
  82. props: props,
  83. contextHash,
  84. description: `Assign element colors based on '${modelProperty.label}' data.`
  85. };
  86. }
  87. return {
  88. name: modelProperty.descriptor.name,
  89. label: modelProperty.label,
  90. category: 'Custom',
  91. factory: Coloring,
  92. getParams: () => ({}),
  93. defaultValues: {},
  94. isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value,
  95. ensureCustomProperties: {
  96. attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
  97. detach: (_, data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
  98. }
  99. }
  100. }
  101. function createLabelProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getLabel: (p: T) => string | undefined) {
  102. return function(loci: Loci): string | undefined {
  103. if (loci.kind === 'element-loci') {
  104. const e = loci.elements[0];
  105. if (!e) return
  106. const data = modelProperty.get(e.unit.model).value
  107. const element = e.unit.elements[OrderedSet.start(e.indices)]
  108. const value = data?.get(element)
  109. if (value === undefined) return
  110. return getLabel(value);
  111. }
  112. return
  113. }
  114. }
  115. }