拓展和丰富
pivot 是一个可运行的丰富:“显示源代码对此还有什么内容”。您声明它,库运行它,将结果分阶段进行分类,并在分析师这么说后将它们提交到图表中,并提供每个元素的来源。
它解决的问题是感兴趣的节点通常有太多的连接。一个指标可以与数千个其他指标相关。因此,拓展是两次调用而不是一次:一个便宜的调用说明了现有的情况,另一个昂贵的调用仅在分析师同意的数字上运行。
词汇,下面各处都一致使用
- pivot:可运行的丰富
- provider:您提供的两个函数
- candidates:已获取结果,尚未在图中
- ingest:将选定的候选者提交到图中
- reject:显式解雇,会话记住
- source:写在拓展着陆的所有内容上的出处标签
- save:将摄取的结果写回out,写入它来自的系统
- 未保存:在画布上,尚未写回 — 精确计数,而非估计
summarize → fetch → triage → ingest → save
"2,143" the 210 the 12 on canvas in the source system只需要fetch。该行上的其他所有内容都是您选择执行的步骤。
查看它的工作情况:拓展和充实用于缩小流程,拒绝和重试用于分类和失败状态。
使用一个是分析师按顺序所做的事情,以及每个步骤对后端的成本。从 注册一个 开始的所有内容都是连接者的参考,而您的源必须提供 与从服务端读取的合同相同。
使用一个
1 · 进入拓展模式。 按P,或单击左导轨上的拓展 插槽。该槽仅在至少注册了一个拓展时才存在。目前还没有向任何后端提出任何要求,并且在模式关闭时选择节点也没有提出任何要求。框选五十个节点来拖动它们不应触发五十个计数查询。
2 · 选择一个原点。 原点是问题所涉及的节点集。 拾取原点是静止工具,因此只需单击即可设置它。 套索起源围绕几个绘制。进入模式时已选择的内容将成为原点,清除将其清空。空原点仍然有用途:声明为 origin: 'none' 的拓展是在没有选取任何内容时应用的拓展。
3 · 阅读适用内容。 输入模式是运行 summarize 的意图,每个适用的拓展一次,批量为多节点源的单个调用。每个条目都显示了其提供商声称的内容,写作 ~2,143。波形符标记了提供者声称的数字,而不是图书馆计数的数字。没有 summarize 的拓展显示裸露的 Run。如果没有任何适用的情况,面板会这样说明,而不是显示空列表。
4 · 缩小,直到门打开。 声明 maxCandidates 的拓展在计数超过时保持 Fetch 无法触及,并说明数量、限制和前进的方向。勾选缩小控件会重新询问 summarize 的选择,因此 2,143 变为 210,并且 Fetch 自行打开。 明确收窄重新开始。除此之外,获取和摄取将每个结果显示在图表上,无需审核。仅当拓展左 autoIngest 未设置时才提供。
5 · 对返回的内容进行分类。 结果不会影响图表。他们打开底部停靠栏中的 Review 选项卡,每个您运行的提供商都有一个窗格,并在其一侧列出。单击行上的任意位置对其进行标记,按住 Shift 键单击可跨范围进行标记,选择所有 n 个匹配 遵循当前过滤器; 搜索行…过滤器(正则表达式可选)和列排序。 拒绝选定的和拒绝所有剩余的显式处理行并在会话中记住,而×仅关闭一个窗格并且拒绝任何内容。 重新运行以相同的缩小范围再次询问。在地带的底部,关闭失败会丢弃所有没有返回任何内容的获取,而关闭所有会清空队列;也不拒绝任何事情。 候选人不是图剩下的。
6 · 摄取,如果错误则撤消。 摄取选择 (12) 恰好提交这 12 个,放置在您从中枢转的节点周围,并以拓展标记为源。 摄取旁边的所有 213 会跳过标记并提交提供者仍在提供的所有内容,无论表被过滤到什么。吐司上写着Ingested 12 nodes, 14 edges。将其收回的是顶部栏中的撤消控件,该控件永久存在,而不是只要吐司持续就存在,并反转整个运行 - 包括其边缘和它合并的任何子项。
7 · 如果拓展可以的话,将其保存回去。 摄取将十二放在画布上;它不会将它们写在任何地方。拓展面板的底部显示此会话已拉入但未写回的元素数量,并通过 Save 写入它们。声明没有 save 的拓展表永远不会贡献该数字 - 请参阅保存拓展图结果。
没有留下任何规则的摄取会关闭该窗格,并且条带会移至下一个等待的提供者。剩菜让它保持打开状态,等待重新运行也是如此。一旦最后一个窗格消失,评论打开的停靠栏就会折叠回原来的位置,因此拓展会保留它找到的布局。
一键拓展
第 2 步到第 4 步适用于值得首先阅读的问题。大多数都不是。右键单击一个节点,拓展 ▸ 列出适用于该节点的每个拓展以及每个拓展所宣传的内容。选择一行运行它,然后:没有面板,没有任何可填充的内容。原点是当单击的节点属于它时的选择,否则该节点单独存在,因此无需离开菜单即可访问批量拓展。
打开子菜单也是如此。每个带有 summarize 的数据点都放置了面板自己的问题,这个原点没有任何缩小,并且它的行保留一个占位符,直到计数到达:~2,143,就像提供商广告的每个数字一样的咨询。不做任何广告的拓展表什么也不显示,并且运行得一样。只有在子菜单被保留一会儿后,这些问题才会消失,因此,在其他地方穿过 Pivot ▸ 的指针不会询问任何内容,如果它先关闭,它们就会被取消。答案以两种方式缓存:再次查看立即绘制,面板重新使用菜单学到的内容。比菜单长的注册表可以容纳卷轴,切到中间行,这样它看起来就可以继续。
结果的去向取决于其中有多少是新的:
- 最多
pivotQuickIngestLimit新候选人(默认为50)直接登陆,像任何其他摄取一样被标记和撤消。 - 更重要的是打开审查选项卡,就像面板运行一样。
- 上限拒绝发生在获取任何内容之前,因此无需检查。拓展面板在该拓展上打开,并显示数字和将其抬起的狭窄部分。
一排没有任何狭窄的地方。当一个主元实际上是多个运行时,分析师会在其中进行选择,例如通过其属性之一丰富对象,则声明 menuChoices:然后该行打开一个子菜单,每个选择一行,每个主元都运行自己的缩小范围,最后一行打开该主元上的面板。它是同步的,每次打开菜单时都会运行,如 appliesTo。如果选择的上限被拒绝,则会打开选择范围缩小的面板。对于普通行,返回 []。
menuChoices: ([object]) => attributesOf(object).map((a) => ({
label: `${a.type}: ${a.value}`,
narrowing: { attribute: [a.id] },
})),画布上已经存在的候选者并不是新的,也永远不会满足限制,因此在您已覆盖的地面上重新运行拓展仍然是一键式手势。屏幕上已有的两个节点之间的边确实很重要,因为否则您会被问到它是一行。声明 autoIngest 的拓展已经回答了这个问题,它的答案成立:true 总是落地,false 总是评论。该限制仅决定未设置的拓展,并且仅决定此手势;面板 Fetch 始终会出现。
在知道答案之前,运行没有提供任何信息,因此不需要任何屏幕即可表明它正在运行。当取出时,Review选项卡会加入扩展坞的条形阅读Review…,而无需展开扩展坞或向前移动窗格;单击它可以观看运行或取消运行。只有需要决定的结果才会出现,而落地的跑动会带走它的标签。通知者的两个无需决定的答案:失败的获取通过重试表示,超过绝对值的结果candidateCeiling表示数量和限制。
那么,有三种方式:轨道模式、节点的上下文菜单 Pivot ▸ 子菜单(不存在,从不禁用,当没有任何应用时),以及 pivotRimBadgeVisible: 'always',一个边缘徽章,它打开范围为其自己的拓展的模式,或者,边缘为所有拓展携带一个徽章,到节点。节点的徽章只有一个拓展,无需缩小范围,可以直接运行它,就像菜单行一样。
注册一个
三扇门,一处登记处,按您伸手取物的顺序排列:
// 1. Options, at construction. Lands before the UI is built, so the rail button is
// already right on the first paint.
const graph = new Pivotick(el, data, { pivots: [correlations] })
// 2. Imperatively, at any point. Returns a disposer.
const remove = graph.pivots.register(correlations)
// 3. From a plugin's install, beside addPanel / addDockTab / addRailMode.
ctx.addPivot(correlations)graph.pivots既是注册表又是运行时。 for()、run()、undo()、invalidate()和候选模型都挂在上面。
完整的提供商
一个工作支点需要的一切,没有遗漏。本页的其余部分是每件作品的参考;这是先复制然后砍掉的东西。
下面的post是你自己的JSON助手——它抛出一个非2xx并传递signal,因此拓展表读取为合约而不是HTTP。
const correlations = {
id: 'correlations', // the provenance tag written on everything it lands
label: 'Correlations', // shown verbatim, so translate it yourself
maxCandidates: 2000, // refuse to fetch while the count is above this
// Which nodes this offers itself for — return the ones you accept, and a mixed
// selection runs it against just those. Omit it and it applies to everything.
appliesTo: nodes => nodes.filter(node => node.getData()?.type !== 'case'),
// The cheap call: what is out there, and what you could narrow by.
summarize: async (nodes, narrowing, { signal }) => {
const { total, byType } = await post('/api/correlations/count', {
ids: nodes.map(node => node.id),
types: narrowing.type ?? [], // pass the narrowing on so the count tracks it
}, signal)
return {
total, // judged against maxCandidates
facets: [{
key: 'type', // the key you read back out of `narrowing`
label: 'Type',
type: 'multiselect',
options: byType.map(row => ({ label: row.label, value: row.value, count: row.count })),
}],
}
},
// The expensive call: a flat fragment, run only on a number the analyst agreed to.
fetch: async (nodes, narrowing, { signal }) => {
const { items } = await post('/api/correlations', {
ids: nodes.map(node => node.id),
types: narrowing.type ?? [],
}, signal)
return {
nodes: items.map(item => ({
id: item.uuid, // an id already on canvas is skipped, never overwritten
data: { label: item.value, type: item.type, seen: item.first_seen },
})),
edges: items.map(item => ({
from: item.origin_uuid, // both endpoints must land for the edge to land
to: item.uuid,
data: { type: 'correlation' },
})),
}
},
// The write half, and the only optional one here. Omit it and this pivot's results
// are *not savable* — never counted unsaved, and no Save offered. See below.
async save({ origin, nodes, edges }, ctx) {
const written = await post('/api/correlations/save', {
event: origin[0]?.id,
nodes: nodes.map((node) => node.getData()),
}, ctx.signal)
return {
savedNodeIds: written.ok,
savedEdgeIds: edges.map((edge) => edge.id),
message: written.refused ? `${written.refused} refused by the server` : undefined,
}
},
}
new Pivotick(document.querySelector('#graph'), data, { pivots: [correlations] })有四件事很容易出错,所有这些都在上面可见:
narrowing是你的荣誉。 它以{ [facetKey]: value }的形式到达,使用你自己声明的键。忽略它是合法的,但是计数永远不会移动,并且上限上的拓展永远不会被解锁。signal不是可选的礼貌。 离开拓展模式或更改原点会中止飞行中的问题。将其传递给每个请求,或者您为没有人在等待的答案付费。ctx.isStale()与不接收信号的传输进行相同的检查。- 失败则抛出,空则返回空。 抛出的错误在拓展表条目上显示为重试。
{ total: 0 }是一个诚实的“什么都没有”,对于分析师来说,这两者的解读完全不同。 - 当你不知道的时候就说出来。 现场询问的消息来源在回答之前可能没有计数。返回
{ total: null, facets }:不进行计数,maxCandidates不拒绝它,candidateCeiling仍然限制fetch返回的内容。0会声称什么都没有。 - Ids 是重复数据删除密钥。 返回源系统的稳定 ID,而不是每个响应的 ID,或者每次重新运行都会再次将相同的节点作为新的候选者。
供应商合同
interface PivotDefinition {
id: string // provenance tag, menu key, triage key
label: string // used verbatim, so it can be translated
icon?: string // trusted SVG, injected as-is
origin?: 'selection' | 'none'
appliesTo?: (nodes: Node[]) => boolean | Node[]
summarize?: (nodes: Node[], narrowing: PivotNarrowing, ctx: PivotContext) => PivotSummary | Promise<…>
fetch: (nodes: Node[], narrowing: PivotNarrowing, ctx: PivotContext) => PivotResult | Promise<…>
autoIngest?: boolean
maxCandidates?: number
save?: (payload: PivotSavePayload, ctx: PivotSaveContext) => PivotSaveOutcome | Promise<…>
autoSave?: boolean
}这两个调用都采用 array 个节点,因此超过 50 个节点的拓展是一个后端请求,而不是 50 个。两者都采用当前的缩小。两者都有一个 PivotContext 携带 signal 和 isStale(),库中每个异步钩子都使用相同的取消模式。
appliesTo:全部或部分起源
对于关于整个原点的规则,返回 boolean;对于一次读取一个节点的规则,返回您接受的 节点。空数组的含义与 false 相同。
appliesTo: nodes => nodes.length >= 2 // whole origin
appliesTo: nodes => nodes.filter(n => n.getData()?.type === 'domain') // per node一旦混合选择,每个节点的形式就很重要:一起选择的域和 IP 各自保留自己的提供商,而不是只留下接受两者的提供商,并且面板在条目上如此说明 - 适用于所选择的 5 个中的 3 个。
无论它保留什么,都是调用提供者的源:summarize和fetch永远不会看到它拒绝的节点,并且缓存的摘要由该缩小的源作为键控。每次原点更改时都会重新读取它,因此保持同步且便宜。
graph.pivots.for(nodes) // everything that applies, partially included
graph.pivots.originFor('whois', nodes) // the share this one would run on使用拓展拒绝的原点调用 run 会完全解析 'failed',而不是在空原点上获取 - 看起来不错并且没有回答任何问题的运行会更糟。
summarize:最便宜的
完整显示在完整的提供商下。它的total是判断上限的数字,它的facets成为缩小控件:你声明形状,库绘制小部件。将缩小范围传递到后端,以便计数跟踪分析师的选择,否则大门永远无法打开。
构面可以声明 default:选项值 a multiselect 以勾选开头,或者值 a select 或 boolean 开头。面板绘制它并清除缩小返回到它,但键在保留时保持在缩小范围之外,因此您的提供程序必须将丢失的键读取为默认值。默认情况下的空multiselect将作为[]发送,这意味着none。
{ key: 'element', label: 'Element', type: 'multiselect', default: ['attribute', 'object'],
options: [/* attribute, object, tag, … */] }完全省略 summarize,拓展不提供计数,也不缩小范围,仅提供 Run 按钮。这是合法的,然后 fetch 一定可以安全地跟注盲注,因为没有任何东西可以阻止它。
计数是建议性的。总是。
summarize 返回声明,而不是合同,并且它与摄取土地的内容完全不同:首先进行重复数据删除,然后是权限、过时性和故意近似的提供者。 UI 用波浪号标记差异 - ~2,143 correlations 是提供者的编号,而 210 fetched 和 12 selected 是图书馆自己的编号。收缩永远不会是错误,计数永远不会对布局造成负担。
fetch:真实的
返回一个平面片段:{ nodes: RawNode[], edges: RawEdge[] }。没有返回的节点命名现有的画布上父节点。嵌套在 RawNode.children 中传播,因此容器作为携带其自己的子树的新节点到达。 edges-only结果是合法的:其端点都已经在画布上的边成为它们自己的分类行。
刻面类型
缩小使用查询引擎知道的每个构面类型,除了 regex 之外,不能要求后端评估:text、select、multiselect、numberRange、boolean。正则表达式位于分类窗格自己的客户端过滤器中,其中的行已在其中。
面板如何绘制它们,以及故障线从何而来
multiselect 被绘制为包含每个选项的复选框列表以及屏幕上的 count,因此您缩小范围的数字在缩小范围时保持可见。单选 select 保留下拉列表并在选项标签中包含其计数。
在总数下,面板打印它的组成部分,取自第一个 multiselect 面:这些选项对结果进行划分。一旦该方面进行了选择(发送或默认),则仅列出所选择的选项。单选方面的计数是替代项而不是部分,因此不会在那里求和。
门控:帽盖和天花板
maxCandidates: 2000 // per pivot: refuse to fetch while the count exceeds it
new Pivotick(el, data, { pivotCandidateCeiling: 10_000 }) // absolute, all pivots两者都拒绝,既不截断也不采样。拒绝总是按顺序包含三件事:数量、限制和前进的方向。没有数字的“太多结果”不是分析师可以采取的行动。
上限是根据当前收窄的最新计数来判断的,这就是让它上升的原因。从 2,143 缩小到 210,Fetch 自行打开。上限与候选对象的内存有关,而不是与虚拟化的表有关。
候选人不是图表
在被摄取之前,拓展表返回的任何内容都不存在于图表、数据表或任何构面计数中。分类结果会打开扩展坞的 Review 选项卡 — 每个拓展一个窗格,在其一侧以垂直选项卡形式列出,每个选项卡都有自己的计数和出路 — 包含提供者自己的列、数据表的过滤器、排序、分页和摄取操作。
- 拒绝是明确的,并且会在会话中记住,按拓展键入。下一次运行不会再次提供该候选者,标题行会显示它抑制了多少个候选者。拒绝的行会在适当的位置被删除而不是移动,并且保持可逆状态。
- 拓展表阻止的内容列在拓展表面板中的条目上,以表命名,每行上都有恢复,并在其下恢复所有。一旦窗格关闭,它就会保留在那里,因此可以重新考虑拒绝,而无需再次运行拓展来查找它。
- 节点和边是单独的块,当运行返回两者时,每个块都会被命名和计数。仅边缘结果是一个自己的表格,而不是空窗格。
- 打开一个容器行。它携带一个Children直接保存的内容的计数,并且插入符列出了内容,其中包含每个子级的共享属性以及一个注释,其中子级本身就是一个容器。打开一行并不是对其进行标记,并且内部的任何内容都无法自行选取 - 摄取该行会占用整个容器。
- 关闭提供程序不会拒绝任何内容。未分类的剩余内容会在下次运行时返回。 拒绝所有剩余的是处理它们的唯一手势。
- 重新运行会替换该提供商的窗格。如果您有标记的行,它会这样说并提供显示新的/保持分类,而不是在没有询问的情况下丢弃您的分类。
- 按组摄取会将选定的行折叠起来,每种类型一组,远离原点。请参阅拓展着陆。除非
UI.simplify关闭,否则将提供此功能。使用pivotIngestGrouped: true时,每个着陆都会分组,按钮上显示为 摄取松散。
该窗格所做的一切也可以通过编程方式访问:
graph.pivots.candidates('correlations') // the staged set
graph.pivots.mark('correlations', id) // …markAll / reject / rejectRemaining
await graph.pivots.ingest('correlations') // commit the marked ones
await graph.pivots.ingest('correlations', 'triage', { group: true }) // …folded, one group per type
graph.pivots.discard('correlations') // close it; rejects nothing
graph.pivots.rejectedRows('correlations') // what the session is holding back
graph.pivots.unreject('correlations', id) // …unrejectAll for every one of themautoIngest
对于没有人愿意挑选的小而可信的结果,例如将容器扩展到其十几个子容器,请声明 autoIngest: true。结果直接登陆,顶部栏的撤消将它们像任何其他摄取一样收回。提供商决定这一点,因为只有它知道自己的结果是否很小并且足够可信以跳过分类。
不设置它与 false 不同。它说拓展没有视图,这使得一键拓展可以自由地单独决定大小; false 表示,结果总是需要人们的关注,无论跑步是如何开始的,他都感到很荣幸。
autoIngest: true // always lands
autoIngest: false // always reviewed
// unset: a one-click run decides on size
new Pivotick(el, data, { pivotQuickIngestLimit: 50 }) // that size, all pivots无论从这里还是从评论中着陆,都可以折叠成着陆组而不是松散的:
new Pivotick(el, data, { pivotIngestGrouped: true }) // every landing, one group per type摄取以及允许执行的操作
一个手势,一批,一个dataBatchChanged,按以下顺序:
- Dedup — 已经在画布上的 id 会被跳过,永远不会被覆盖,无论它是在候选人上场时存在的,还是在他们等待时另一次运行得到的。
- 儿童联盟 ID。
- 放置靠近原点节点,抖动。提供商自己的
x/y获胜,并且在视口中心无源运行种子。 - 出处,则批次落地。
摄取纯粹是累加的。唯一删除的是 removeBySource 并运行撤消。
一个门,对整个集合调用一次:
callbacks: {
onBeforeIngest: async ({ pivotId, origin, candidates, trigger, confirm }) => {
if (candidates.nodes.length < 50) return true
return await confirm({ title: `Add ${candidates.nodes.length} nodes?` })
},
}返回 false 否决,或 { accept: true, nodes, edges } 获得子集。省略的键表示“按要求”,正如 onBeforeDelete 所读。
出处
每个节点和边都带有为其担保的源的set。一个集合而不是一个标量,因为两个拓展在一个节点上重叠是正常情况。对于标量,第二个到达的节点会删除第一个节点的声明,并且删除任何一个节点都会带走该节点。
node.getSources() // ['seed', 'correlations'], where 'seed' is the loaded data
node.hasSource('correlations')
edge.getSources() // edges carry it too, which is the load-bearing half
graph.removeBySource('correlations') // drop the tag; delete only when the set empties跑回来
摄取是graph.history中的一个条目,与其他改变画布内容的事物一起:删除、持久隐藏和手工绘制的元素。
graph.history.entries() // newest first; a pivot entry's `id` is its runId
graph.history.undo() // the newest entry
graph.history.undo(runId) // that ingest, and contiguously anything done since
graph.history.redo() // re-lands the recorded delta; no refetch, no re-gating撤消是连续:针对较旧的条目,也会将其上方的每个条目作为一批进行反转。单独反转一个旧的运行是graph.removeBySource(pivotId),它会删除该源的担保并仅删除其他没有担保的内容 - 向前操作,记录为一个。
一个分阶段集合的部分摄取会获得自己的 runId,因此每个批次都是一个单独的条目。撤消仍然是最新条目的摄取 ** 会将其候选项放回“审阅”窗格**,而不是再次向提供者询问它们。请参阅撤消和历史记录了解全部内容。
保存结果
摄取将候选人放在画布上;它不会将它们写在任何地方。声明 save 的拓展表可以将其自己的结果推回到它们来自的系统中,并且库保留分类帐:运行创建了哪些元素,它们是否已被写入,哪些失败了,以及重试应该携带什么。
省略save,并且拓展的结果是不可保存。它们永远不会进入分类帐,永远不会被视为未保存,并且不会为它们显示保存。这是导出数据的正确声明——关联引擎的输出不是你可以写回的——并且它可以阻止永久的“210 未保存”且无法补救。
整个过程,包括部分写入、重试、源系统生成的 ID 以及撤消将达到和不会达到的内容:保存拓展结果。
拓展轨道模式
使用一个在表面行走。当您配置而不是驾驶它时,重要的是:进入模式是调用提供程序的意图 - 就像在上下文菜单中打开 Pivot ▸ 一样 - 让它停止飞行中的每个问题,并且仅选择不需要任何成本。选择也是拖动、样式设置和批量编辑的手势,因此该库从不推测性地调用提供程序。
该模式在注册表上是门控:
UI: { pivotMode: 'auto' } // default: the button exists only while a pivot is registered
UI: { pivotMode: true } // force it, for pivots that arrive asynchronously
UI: { pivotMode: false } // never因此,未注册拓展的消费者看不到该功能的痕迹。它永远不会以 viewer 或 static 模式安装。
无原点拓展
某些丰富功能没有可运行的节点,例如搜索、导入或粘贴指标列表。声明 origin: 'none' ,拓展接收 [],当原点为空时应用,并在选择一个后折叠成没有原点组。结果通过相同的分类窗格、相同的门和相同的出处模型,然后落在视口中心。
appliesTo 对于其中之一来说毫无意义,因此不会被咨询。
宣称的潜力
节点边缘有一条提示,表示“这里有 2,100 个相关性”,由加载数据的人放置在那里。
node.setPotential('correlations', 2100) // 0 clears it
node.getPotential('correlations')
node.setPotential(2199) // the total across every pivot
node.getPotential()它是声明的,从未被查询过。图书馆不会要求提供者绘制徽章,因为在被询问时在一个节点上实现的计数会让画布讲述两个不同的故事。单击徽章将打开仅限于该拓展的拓展模式。
与 Node 上的所有其他 setter 一样,它将节点标记为脏:如果没有其他内容要渲染,则调用 graph.renderer.update()。
边缘画出什么
默认情况下,轮辋徽章仅在拓展模式打开时显示,它们有助于选择原点;单击其中的一个会将面板范围限制到该节点和拓展。当它们显示时pivotRimBadgeVisible会发生变化:
new Pivotick(el, data, { pivots, pivotRimBadgeVisible: 'always' })
graph.pivots.rimBadgeVisible = 'pivot-mode' // or later; it dirties every node'pivot-mode' (默认) | 仅当拓展导轨模式打开时。 |
'always' | 无论哪种模式,徽章也是进入拓展模式的一种方式。 |
'never' | 一点也不。 |
一旦显示出来,它们的形状就是下一个选择。
一个节点有四个边缘角,一旦有子节点,就只有两个 - 扩展可供性保留了东侧。因此,每个提供商的一个徽章在少数情况下可读,但在一百个情况下则不可能可读。 pivotRimBadge 选择形状:
new Pivotick(el, data, { pivots, pivotRimBadge: 'summary' })
graph.pivots.rimBadge = 'per-pivot' // or later; it dirties every node'per-pivot' (默认) | 每个拓展都有一枚徽章,表明了潜力,每个拓展都打开了。其中一些在有子节点的节点早期崩溃为 +n。 |
'summary' | One 徽章,无论提供商数量如何。它显示了在没有声明拓展表 ID 的情况下声明的总数,或者在没有声明任何数据的情况下应用了多少个拓展表。 |
'off' | 什么也没有。 NodeStyle.badges仍然在画。 |
单击 summary 徽章会执行什么操作,以及其计数的来源
在 summary 中,徽章是一种手势:它打开该节点上的拓展面板,因为在任何真实的提供商计数中,该面板都是计数和选择所在的位置。例外的是一个节点,其中恰好有一个主元应用和,它声明没有summarize——没有什么要先读的,也没有什么要缩小的——点击运行它。结果落地的是提供者自己的调用:autoIngest 在画布和分类窗格之间做出决定,就像其他地方一样。
应用拓展的计数是派生的,而不是声明的,因此不需要提供者调用。它在图形更改之间保留,并在注册表或图形节点移动时刷新; appliesTo 阅读其他内容可能会滞后,而面板始终是准确的答案。
缓存
该库缓存一件事:summarize结果,以(拓展,原点,缩小)为键,因为多选摘要是任何东西都无法分解的聚合。其他一切都是你的:
graph.pivots.invalidate() // all of it
graph.pivots.invalidate('correlations') // one pivot's
graph.pivots.invalidate('correlations', nodes) // only what was asked about these身份验证、重试、速率限制和您自己的任何缓存都属于您的提供程序函数,这些函数是您编写的普通函数。
您的消息来源必须提供什么
上面的页面适用于编写提供程序的人。对于拥有它所调用的服务的人来说,这与从另一方读取的合同相同。
- 获取调用,返回对象及其关系。唯一需要的一个。关于它的两件事很重要:它在一个请求中需要许多 id,因此超过 50 个节点的拓展需要一次调用,并且它返回的 id 在运行中稳定,因为这是 dedup 所依赖的。每个响应 ID 再次将昨天的结果作为新候选者。
- 计数调用,采用相同的 ID 和相同的过滤器,返回总计和每个值的计数(无论可以缩小范围)。这就是使大门和狭窄部分发挥作用的原因,并且每次对其进行更改时都会重新询问,因此它必须便宜。近似值即可:计数为建议值。没有一个拓展仍然运行,只有一个光秃秃的Run,没有任何东西可以控制它。
- 两者都遵循相同的过滤器。忽略它们的计数永远不会移动,因此超过其上限的拓展永远不会被解锁。
- 一种取消方式。当分析师继续前进时,问题就会被放弃,并且提供者所收到的
signal只有在传输起作用时才值得传递。 - 写入调用,如果结果由您写回。它应该返回它分配的 id,因此
canonicalIds可以阻止明天的运行重复保存的内容,并能够报告部分成功。
除了失败之外,两个读取调用都应该给出一个空答案:没有结果是关于数据的事实,错误是关于服务的事实,并且它们对分析师的读取完全不同。
这里不需要分页、光标或流传输。缩小范围是取代它们的。
这不是什么
- 没有流媒体,也没有服务器游标。缩小范围取代了它们:不是获取两千行,而是询问那里有什么并让分析师选择。该合约从不需要流媒体后端,因为许多来源没有流媒体后端。
- 无重新父子关系。返回的节点永远不会命名现有的父节点。按 id 进行子节点联合是摄取接触现有节点子节点的唯一方式。
- 无属性编辑撤消。
graph.history涵盖画布所保存和显示的内容 - 进来的内容、出去的内容、隐藏的内容。节点的数据是库未创作的后端状态,因此在本地恢复字段是onBeforeNodeEditCommit的工作,而不是我们的工作。 - 无传输策略。
save回写,但凭证、重试、速率限制和冲突检测都是消费者的。库的唯一重试是分析师按 Retry。 - 重新加载时没有拒绝记忆。 拒绝是一个会话上下文中的判决,同一候选者可能在另一张图中值得不同的答案。拒绝将在会议期间保留,不再保留。
- 不刷新此处已有的内容。 id 匹配的候选节点不会影响现有节点;将节点与较新的副本进行协调需要库没有的冲突策略。