@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/index.esm.js +11212 -10483
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +11251 -10509
  4. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  5. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  6. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  7. package/dist/jbrowse-plugin-apollo.umd.development.js +7726 -9014
  8. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  9. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  10. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  11. package/package.json +18 -18
  12. package/src/ApolloInternetAccount/model.ts +123 -70
  13. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
  14. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
  15. package/src/BackendDrivers/CollaborationServerDriver.ts +72 -20
  16. package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
  17. package/src/ChangeManager.ts +36 -14
  18. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  19. package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
  20. package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
  21. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
  22. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +72 -234
  23. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
  24. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +23 -131
  25. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
  26. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +279 -217
  27. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
  28. package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
  29. package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
  30. package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
  31. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  32. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
  33. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
  34. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  35. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  36. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  37. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
  38. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
  39. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  40. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  41. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  42. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +157 -0
  43. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +101 -38
  44. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +334 -262
  45. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  46. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
  47. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
  48. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
  49. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
  50. package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
  51. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  52. package/src/components/AddAssembly.tsx +34 -38
  53. package/src/components/AddAssemblyAliases.tsx +1 -1
  54. package/src/components/AddChildFeature.tsx +5 -2
  55. package/src/components/AddFeature.tsx +30 -21
  56. package/src/components/AddRefSeqAliases.tsx +64 -50
  57. package/src/components/CopyFeature.tsx +4 -2
  58. package/src/components/CreateApolloAnnotation.tsx +22 -9
  59. package/src/components/DeleteAssembly.tsx +3 -10
  60. package/src/components/DownloadGFF3.tsx +2 -2
  61. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  62. package/src/components/FilterFeatures.tsx +7 -7
  63. package/src/components/FilterTranscripts.tsx +6 -6
  64. package/src/components/ImportFeatures.tsx +1 -1
  65. package/src/components/ManageChecks.tsx +3 -10
  66. package/src/components/ManageUsers.tsx +23 -22
  67. package/src/components/MergeTranscripts.tsx +12 -15
  68. package/src/components/OntologyTermAutocomplete.tsx +1 -8
  69. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  70. package/src/components/OpenLocalFile.tsx +11 -7
  71. package/src/components/ViewChangeLog.tsx +25 -50
  72. package/src/components/ViewCheckResults.tsx +2 -8
  73. package/src/components/index.ts +1 -0
  74. package/src/config.ts +6 -0
  75. package/src/index.ts +53 -115
  76. package/src/makeDisplayComponent.tsx +9 -14
  77. package/src/menus/index.ts +1 -0
  78. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
  79. package/src/menus/topLevelMenuAdmin.ts +154 -0
  80. package/src/session/ClientDataStore.ts +32 -14
  81. package/src/session/session.ts +159 -121
  82. package/src/util/annotationFeatureUtils.ts +15 -21
  83. package/src/util/displayUtils.ts +149 -0
  84. package/src/util/glyphUtils.ts +329 -0
  85. package/src/util/loadAssemblyIntoClient.ts +3 -2
  86. package/src/util/mouseEventsUtils.ts +32 -0
@@ -1,15 +1,18 @@
1
1
  import { type AnnotationFeature } from '@apollo-annotation/mst'
2
2
  import { type MenuItem } from '@jbrowse/core/ui'
3
+ import { alpha } from '@mui/material'
3
4
 
4
- import { getFeaturesUnderClick } from '../../util/annotationFeatureUtils'
5
- import { type LinearApolloDisplay } from '../stateModel'
6
5
  import {
7
- type LinearApolloDisplayMouseEvents,
8
- type MousePositionWithFeatureAndGlyph,
9
- } from '../stateModel/mouseEvents'
6
+ type MousePositionWithFeature,
7
+ containsSelectedFeature,
8
+ isMousePositionWithFeature,
9
+ } from '../../util'
10
+ import { getRelatedFeatures } from '../../util/annotationFeatureUtils'
11
+ import { type LinearApolloDisplay } from '../stateModel'
12
+ import { type LinearApolloDisplayMouseEvents } from '../stateModel/mouseEvents'
10
13
  import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
11
14
 
12
- import { boxGlyph, drawBox, isSelectedFeature } from './BoxGlyph'
15
+ import { boxGlyph, drawBox } from './BoxGlyph'
13
16
  import { type Glyph } from './Glyph'
14
17
 
15
18
  function featuresForRow(feature: AnnotationFeature): AnnotationFeature[][] {
@@ -33,9 +36,13 @@ function draw(
33
36
  stateModel: LinearApolloDisplayRendering,
34
37
  displayedRegionIndex: number,
35
38
  ) {
39
+ const { selectedFeature } = stateModel
36
40
  for (let i = 0; i < getRowCount(feature); i++) {
37
41
  drawRow(ctx, feature, row + i, row, stateModel, displayedRegionIndex)
38
42
  }
43
+ if (selectedFeature && containsSelectedFeature(feature, selectedFeature)) {
44
+ drawHighlight(stateModel, ctx, selectedFeature)
45
+ }
39
46
  }
40
47
 
41
48
  function drawRow(
@@ -59,7 +66,7 @@ function drawFeature(
59
66
  stateModel: LinearApolloDisplayRendering,
60
67
  displayedRegionIndex: number,
61
68
  ) {
62
- const { apolloRowHeight: heightPx, lgv, session } = stateModel
69
+ const { apolloRowHeight: heightPx, lgv, theme } = stateModel
63
70
  const { bpPerPx, displayedRegions, offsetPx } = lgv
64
71
  const displayedRegion = displayedRegions[displayedRegionIndex]
65
72
  const minX =
@@ -69,13 +76,11 @@ function drawFeature(
69
76
  regionNumber: displayedRegionIndex,
70
77
  })?.offsetPx ?? 0) - offsetPx
71
78
  const { reversed } = displayedRegion
72
- const { apolloSelectedFeature } = session
73
79
  const widthPx = feature.length / bpPerPx
74
80
  const startPx = reversed ? minX - widthPx : minX
75
81
  const top = row * heightPx
76
82
  const rowCount = getRowCount(feature)
77
- const isSelected = isSelectedFeature(feature, apolloSelectedFeature)
78
- const groupingColor = isSelected ? 'rgba(130,0,0,0.45)' : 'rgba(255,0,0,0.25)'
83
+ const groupingColor = alpha(theme.palette.background.paper, 0.6)
79
84
  if (rowCount > 1) {
80
85
  // draw background that encapsulates all child features
81
86
  const featureHeight = rowCount * heightPx
@@ -84,15 +89,14 @@ function drawFeature(
84
89
  boxGlyph.draw(ctx, feature, row, stateModel, displayedRegionIndex)
85
90
  }
86
91
 
87
- function drawHover(
88
- stateModel: LinearApolloDisplay,
92
+ function drawHighlight(
93
+ stateModel: LinearApolloDisplayRendering,
89
94
  ctx: CanvasRenderingContext2D,
95
+ feature: AnnotationFeature,
96
+ selected = false,
90
97
  ) {
91
- const { apolloHover, apolloRowHeight, lgv } = stateModel
92
- if (!apolloHover) {
93
- return
94
- }
95
- const { feature } = apolloHover
98
+ const { apolloRowHeight, lgv, theme } = stateModel
99
+
96
100
  const position = stateModel.getFeatureLayoutPosition(feature)
97
101
  if (!position) {
98
102
  return
@@ -110,10 +114,23 @@ function drawHover(
110
114
  })?.offsetPx ?? 0) - offsetPx
111
115
  const top = (layoutRow + featureRow) * apolloRowHeight
112
116
  const widthPx = length / bpPerPx
113
- ctx.fillStyle = 'rgba(0,0,0,0.2)'
117
+ ctx.fillStyle = selected
118
+ ? theme.palette.action.disabled
119
+ : theme.palette.action.focus
114
120
  ctx.fillRect(startPx, top, widthPx, apolloRowHeight * getRowCount(feature))
115
121
  }
116
122
 
123
+ function drawHover(
124
+ stateModel: LinearApolloDisplay,
125
+ ctx: CanvasRenderingContext2D,
126
+ ) {
127
+ const { hoveredFeature } = stateModel
128
+ if (!hoveredFeature) {
129
+ return
130
+ }
131
+ drawHighlight(stateModel, ctx, hoveredFeature.feature)
132
+ }
133
+
117
134
  function getFeatureFromLayout(
118
135
  feature: AnnotationFeature,
119
136
  bp: number,
@@ -138,14 +155,13 @@ function getRowForFeature(
138
155
 
139
156
  function getContextMenuItems(
140
157
  display: LinearApolloDisplayMouseEvents,
141
- mousePosition: MousePositionWithFeatureAndGlyph,
158
+ mousePosition: MousePositionWithFeature,
142
159
  ): MenuItem[] {
143
- const { apolloHover, session } = display
160
+ const { hoveredFeature, session } = display
144
161
  const menuItems: MenuItem[] = []
145
- if (!apolloHover) {
162
+ if (!hoveredFeature) {
146
163
  return menuItems
147
164
  }
148
- const { feature: sourceFeature } = apolloHover
149
165
  const { featureTypeOntology } = session.apolloDataStore.ontologyManager
150
166
  if (!featureTypeOntology) {
151
167
  throw new Error('featureTypeOntology is undefined')
@@ -155,21 +171,24 @@ function getContextMenuItems(
155
171
  mousePosition,
156
172
  )
157
173
  menuItems.push({
158
- label: sourceFeature.type,
174
+ label: hoveredFeature.feature.type,
159
175
  subMenu: sourceFeatureMenuItems,
160
176
  })
161
- for (const relative of getFeaturesUnderClick(mousePosition)) {
162
- if (relative._id === sourceFeature._id) {
163
- continue
177
+ if (isMousePositionWithFeature(mousePosition)) {
178
+ const { bp, feature } = mousePosition
179
+ for (const relative of getRelatedFeatures(feature, bp)) {
180
+ if (relative._id === hoveredFeature.feature._id) {
181
+ continue
182
+ }
183
+ const contextMenuItemsForFeature = boxGlyph.getContextMenuItemsForFeature(
184
+ display,
185
+ relative,
186
+ )
187
+ menuItems.push({
188
+ label: relative.type,
189
+ subMenu: contextMenuItemsForFeature,
190
+ })
164
191
  }
165
- const contextMenuItemsForFeature = boxGlyph.getContextMenuItemsForFeature(
166
- display,
167
- relative,
168
- )
169
- menuItems.push({
170
- label: relative.type,
171
- subMenu: contextMenuItemsForFeature,
172
- })
173
192
  }
174
193
  return menuItems
175
194
  }
@@ -2,10 +2,8 @@ import { type AnnotationFeature } from '@apollo-annotation/mst'
2
2
  import { type MenuItem } from '@jbrowse/core/ui'
3
3
 
4
4
  import { type OntologyRecord } from '../../OntologyManager'
5
- import {
6
- type LinearApolloDisplayMouseEvents,
7
- type MousePositionWithFeatureAndGlyph,
8
- } from '../stateModel/mouseEvents'
5
+ import { type MousePositionWithFeature } from '../../util'
6
+ import { type LinearApolloDisplayMouseEvents } from '../stateModel/mouseEvents'
9
7
  import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
10
8
  import { type CanvasMouseEvent } from '../types'
11
9
 
@@ -49,25 +47,25 @@ export interface Glyph {
49
47
 
50
48
  onMouseDown(
51
49
  display: LinearApolloDisplayMouseEvents,
52
- currentMousePosition: MousePositionWithFeatureAndGlyph,
50
+ currentMousePosition: MousePositionWithFeature,
53
51
  event: CanvasMouseEvent,
54
52
  ): void
55
53
 
56
54
  onMouseMove(
57
55
  display: LinearApolloDisplayMouseEvents,
58
- currentMousePosition: MousePositionWithFeatureAndGlyph,
56
+ currentMousePosition: MousePositionWithFeature,
59
57
  event: CanvasMouseEvent,
60
58
  ): void
61
59
 
62
60
  onMouseLeave(
63
61
  display: LinearApolloDisplayMouseEvents,
64
- currentMousePosition: MousePositionWithFeatureAndGlyph,
62
+ currentMousePosition: MousePositionWithFeature,
65
63
  event: CanvasMouseEvent,
66
64
  ): void
67
65
 
68
66
  onMouseUp(
69
67
  display: LinearApolloDisplayMouseEvents,
70
- currentMousePosition: MousePositionWithFeatureAndGlyph,
68
+ currentMousePosition: MousePositionWithFeature,
71
69
  event: CanvasMouseEvent,
72
70
  ): void
73
71
 
@@ -83,6 +81,6 @@ export interface Glyph {
83
81
 
84
82
  getContextMenuItems(
85
83
  display: LinearApolloDisplayMouseEvents,
86
- currentMousePosition: MousePositionWithFeatureAndGlyph,
84
+ currentMousePosition: MousePositionWithFeature,
87
85
  ): MenuItem[]
88
86
  }
@@ -0,0 +1,19 @@
1
+ import { type ContentBlock } from '@jbrowse/core/util/blockTypes'
2
+
3
+ import { type LinearApolloDisplay } from '../stateModel'
4
+
5
+ export function getLeftPx(
6
+ display: LinearApolloDisplay,
7
+ feature: { max: number; min: number },
8
+ block: ContentBlock,
9
+ ) {
10
+ const { lgv } = display
11
+ const { bpPerPx, offsetPx } = lgv
12
+ const blockLeftPx = block.offsetPx - offsetPx
13
+ const featureLeftBpDistanceFromBlockLeftBp = block.reversed
14
+ ? block.end - feature.max
15
+ : feature.min - block.start
16
+ const featureLeftPxDistanceFromBlockLeftPx =
17
+ featureLeftBpDistanceFromBlockLeftBp / bpPerPx
18
+ return blockLeftPx + featureLeftPxDistanceFromBlockLeftPx
19
+ }
@@ -22,8 +22,9 @@ import { addDisposer, cast, getRoot, getSnapshot, types } from 'mobx-state-tree'
22
22
 
23
23
  import { type ApolloInternetAccountModel } from '../../ApolloInternetAccount/model'
24
24
  import { FilterFeatures } from '../../components/FilterFeatures'
25
- import { type ApolloSessionModel } from '../../session'
25
+ import { type ApolloSessionModel, type HoveredFeature } from '../../session'
26
26
  import { type ApolloRootModel } from '../../types'
27
+ import { EditZoomThresholdDialog } from '../../util/displayUtils'
27
28
 
28
29
  const minDisplayHeight = 20
29
30
 
@@ -37,9 +38,8 @@ export function baseModelFactory(
37
38
  configuration: ConfigurationReference(configSchema),
38
39
  graphical: true,
39
40
  table: false,
40
- showStartCodons: false,
41
- showStopCodons: true,
42
- highContrast: false,
41
+ showCheckResults: true,
42
+ zoomThreshold: 200,
43
43
  heightPreConfig: types.maybe(
44
44
  types.refinement(
45
45
  'displayHeight',
@@ -74,16 +74,19 @@ export function baseModelFactory(
74
74
  return self.heightPreConfig
75
75
  }
76
76
  if (self.graphical && self.table) {
77
- return 500
77
+ return 400
78
78
  }
79
79
  if (self.graphical) {
80
- return 200
80
+ return 100
81
81
  }
82
- return 300
82
+ return 200
83
83
  },
84
84
  get loading() {
85
85
  return self.loadingState
86
86
  },
87
+ get zoomThresholdSetting() {
88
+ return self.zoomThreshold ?? getConf(self, 'zoomThreshold')
89
+ },
87
90
  }))
88
91
  .views((self) => ({
89
92
  get rendererTypeName() {
@@ -104,7 +107,7 @@ export function baseModelFactory(
104
107
  return regions
105
108
  },
106
109
  regionCannotBeRendered(/* region */) {
107
- if (self.lgv && self.lgv.bpPerPx >= 200) {
110
+ if (self.lgv && self.lgv.bpPerPx >= self.zoomThreshold) {
108
111
  return 'Zoom in to see annotations'
109
112
  }
110
113
  return
@@ -146,6 +149,10 @@ export function baseModelFactory(
146
149
  return (self.session as unknown as ApolloSessionModel)
147
150
  .apolloSelectedFeature
148
151
  },
152
+ get hoveredFeature(): HoveredFeature | undefined {
153
+ return (self.session as unknown as ApolloSessionModel)
154
+ .apolloHoveredFeature
155
+ },
149
156
  }))
150
157
  .actions((self) => ({
151
158
  setScrollTop(scrollTop: number) {
@@ -172,14 +179,8 @@ export function baseModelFactory(
172
179
  self.graphical = true
173
180
  self.table = true
174
181
  },
175
- toggleShowStartCodons() {
176
- self.showStartCodons = !self.showStartCodons
177
- },
178
- toggleShowStopCodons() {
179
- self.showStopCodons = !self.showStopCodons
180
- },
181
- toggleHighContrast() {
182
- self.highContrast = !self.highContrast
182
+ toggleShowCheckResults() {
183
+ self.showCheckResults = !self.showCheckResults
183
184
  },
184
185
  updateFilteredFeatureTypes(types: string[]) {
185
186
  self.filteredFeatureTypes = cast(types)
@@ -187,18 +188,15 @@ export function baseModelFactory(
187
188
  setLoading(loading: boolean) {
188
189
  self.loadingState = loading
189
190
  },
191
+ setZoomThresholdSetting({ zoomThreshold }: { zoomThreshold: number }) {
192
+ self.zoomThreshold = zoomThreshold
193
+ },
190
194
  }))
191
195
  .views((self) => {
192
196
  const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self
193
197
  return {
194
198
  trackMenuItems() {
195
- const {
196
- graphical,
197
- table,
198
- showStartCodons,
199
- showStopCodons,
200
- highContrast,
201
- } = self
199
+ const { graphical, table, showCheckResults } = self
202
200
  return [
203
201
  ...superTrackMenuItems(),
204
202
  {
@@ -230,27 +228,20 @@ export function baseModelFactory(
230
228
  },
231
229
  },
232
230
  {
233
- label: 'Show start codons',
234
- type: 'checkbox',
235
- checked: showStartCodons,
236
- onClick: () => {
237
- self.toggleShowStartCodons()
238
- },
239
- },
240
- {
241
- label: 'Show stop codons',
231
+ label: 'Check Results',
242
232
  type: 'checkbox',
243
- checked: showStopCodons,
233
+ checked: showCheckResults,
244
234
  onClick: () => {
245
- self.toggleShowStopCodons()
235
+ self.toggleShowCheckResults()
246
236
  },
247
237
  },
248
238
  {
249
- label: 'Use high contrast colors',
250
- type: 'checkbox',
251
- checked: highContrast,
239
+ label: 'Change zoom threshold',
252
240
  onClick: () => {
253
- self.toggleHighContrast()
241
+ getSession(self).queueDialog((handleClose) => [
242
+ EditZoomThresholdDialog,
243
+ { model: self, handleClose },
244
+ ])
254
245
  },
255
246
  },
256
247
  ],
@@ -286,6 +277,11 @@ export function baseModelFactory(
286
277
  self.session as unknown as ApolloSessionModel
287
278
  ).apolloSetSelectedFeature(feature)
288
279
  },
280
+ setHoveredFeature(hoveredFeature?: HoveredFeature) {
281
+ ;(
282
+ self.session as unknown as ApolloSessionModel
283
+ ).apolloSetHoveredFeature(hoveredFeature)
284
+ },
289
285
  showFeatureDetailsWidget(
290
286
  feature: AnnotationFeature,
291
287
  customWidgetNameAndId?: [string, string],
@@ -332,11 +328,6 @@ export function baseModelFactory(
332
328
  self.setLoading(false)
333
329
  }, 1000)
334
330
  })
335
- if (self.lgv.bpPerPx <= 3) {
336
- void (
337
- self.session as unknown as ApolloSessionModel
338
- ).apolloDataStore.loadRefSeq(self.regions)
339
- }
340
331
  },
341
332
  { name: 'LinearApolloDisplayLoadFeatures', delay: 1000 },
342
333
  ),
@@ -63,10 +63,11 @@ export function layoutsModelFactory(
63
63
  return self.seenFeatures.get(id)
64
64
  },
65
65
  getGlyph(feature: AnnotationFeature) {
66
- if (feature.looksLikeGene) {
66
+ const { topLevelFeature } = feature
67
+ if (topLevelFeature.looksLikeGene) {
67
68
  return geneGlyph
68
69
  }
69
- if (feature.children?.size) {
70
+ if (topLevelFeature.children?.size) {
70
71
  return genericChildGlyph
71
72
  }
72
73
  return boxGlyph