custom-element-property.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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 } 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. import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
  19. export { CustomElementProperty };
  20. interface CustomElementProperty<T> {
  21. propertyProvider: CustomModelProperty.Provider<{}, CustomElementProperty.Value<T>>
  22. colorThemeProvider?: ColorTheme.Provider<{}>
  23. labelProvider?: LociLabelProvider
  24. }
  25. namespace CustomElementProperty {
  26. export type Value<T> = Map<ElementIndex, T>
  27. export type Data<T> = CustomProperty.Data<Value<T>>
  28. export interface Builder<T> {
  29. label: string
  30. name: string
  31. getData(model: Model, ctx?: CustomProperty.Context): Data<T> | Promise<Data<T>>
  32. coloring?: {
  33. getColor: (p: T) => Color
  34. defaultColor: Color
  35. }
  36. getLabel?: (p: T) => string | undefined
  37. isApplicable?: (data: Model) => boolean,
  38. type?: 'dynamic' | 'static',
  39. }
  40. export function create<T>(builder: Builder<T>): CustomElementProperty<T> {
  41. const modelProperty = createModelProperty(builder);
  42. return {
  43. propertyProvider: modelProperty,
  44. colorThemeProvider: builder.coloring?.getColor && createColorThemeProvider(modelProperty, builder.coloring.getColor, builder.coloring.defaultColor),
  45. labelProvider: builder.getLabel && createLabelProvider(modelProperty, builder.getLabel)
  46. };
  47. }
  48. function createModelProperty<T>(builder: Builder<T>) {
  49. return CustomModelProperty.createProvider({
  50. label: builder.label,
  51. descriptor: CustomPropertyDescriptor({
  52. name: builder.name,
  53. }),
  54. type: builder.type || 'dynamic',
  55. defaultParams: {},
  56. getParams: (data: Model) => ({}),
  57. isApplicable: (data: Model) => !builder.isApplicable || !!builder.isApplicable(data),
  58. obtain: async (ctx: CustomProperty.Context, data: Model) => {
  59. return await builder.getData(data, ctx);
  60. }
  61. });
  62. }
  63. function createColorThemeProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getColor: (p: T) => Color, defaultColor: Color): ColorTheme.Provider<{}> {
  64. function Coloring(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
  65. let color: LocationColor;
  66. const property = ctx.structure && modelProperty.get(ctx.structure.models[0]);
  67. const contextHash = property?.version;
  68. if (property?.value && ctx.structure) {
  69. const data = property.value;
  70. color = (location: Location) => {
  71. if (StructureElement.Location.is(location)) {
  72. const e = data.get(location.element);
  73. if (typeof e !== 'undefined') return getColor(e);
  74. }
  75. return defaultColor;
  76. };
  77. } else {
  78. color = () => defaultColor;
  79. }
  80. return {
  81. factory: Coloring,
  82. granularity: 'group',
  83. color: color,
  84. props: props,
  85. contextHash,
  86. description: `Assign element colors based on '${modelProperty.label}' data.`
  87. };
  88. }
  89. return {
  90. name: modelProperty.descriptor.name,
  91. label: modelProperty.label,
  92. category: 'Custom',
  93. factory: Coloring,
  94. getParams: () => ({}),
  95. defaultValues: {},
  96. isApplicable: (ctx: ThemeDataContext) => !!ctx.structure,
  97. ensureCustomProperties: {
  98. attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
  99. detach: (data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false)
  100. }
  101. };
  102. }
  103. function createLabelProvider<T>(modelProperty: CustomModelProperty.Provider<{}, Value<T>>, getLabel: (p: T) => string | undefined): LociLabelProvider {
  104. return {
  105. label: (loci: Loci) => {
  106. if (loci.kind === 'element-loci') {
  107. const e = loci.elements[0];
  108. if (!e || !e.unit.model.customProperties.hasReference(modelProperty.descriptor)) return;
  109. const data = modelProperty.get(e.unit.model).value;
  110. const element = e.unit.elements[OrderedSet.start(e.indices)];
  111. const value = data?.get(element);
  112. if (value === undefined) return;
  113. return getLabel(value);
  114. }
  115. return;
  116. }
  117. };
  118. }
  119. }