搜索文章

输入关键词开始搜索

技术拆解-叙事流程图

思考#技术拆解#数据可视化#AI#JavaScript#数据结构

设计亮点

数据驱动

抽象出了FlowchartStore这个 store 类,提供数据结构和响应的数据处理方法。

currentNarrationNode(state)这种方法就很贴合叙事可视化。

数据的存取也很不错:saveToLocalStorage()resumeFromLocalStorage()clearLocalStorageAndReload()

组件化

在 components 下面设计了一个 TheFlowchart 组件。

渲染分离

TheFlowchart 组件中通过updateAppearance()做到了数据和渲染分离。

且渲染的时候通过 CSS 的正则匹配选择器选择元素,很不错:

this.flowchartElement
  .querySelectorAll('[id^=n-], [id^=e-]')
  .forEach((element) => {
    element.classList.remove('replaced-out', 'replaced-in');
  });

状态渲染

设计了不同的状态(teased, revealed, next, current),进行不同样式的渲染:

this.flowchartStore.teasedItems.forEach((id) => {
  document.getElementById(id).setAttribute('data-state', 'teased');
});

this.flowchartStore.revealedItems.forEach((id) => {
  document.getElementById(id).setAttribute('data-state', 'revealed');
});

this.flowchartStore.currentNode.outgoing.forEach((item) => {
  item.edge.setAttribute('data-state', 'next');
  item.node.element.setAttribute('data-state', 'next');
});

this.flowchartStore.currentNode.element.setAttribute('data-state', 'current');
this.markItemAsRevealed(this.flowchartStore.currentNode.element);

// replace primary elements with alternate state variants if those exist
['teased', 'revealed', 'next', 'current'].forEach((state) => {
  this.flowchartElement
    .querySelectorAll('[data-state=' + state + ']')
    .forEach((element) => {
      const replacementElement = this.findReplacementElement(element, state);

      if (replacementElement) {
        element.classList.add('replaced-out');
        replacementElement.classList.add('replaced-in');
      }
    });
});

事件驱动的交互

给 TheFlowchart 组件设计了事件 API,用于交互功能:

export default {
  name: 'TheFlowchart',

  components: {
    InlineSvg,
  },
  // 事件API
  emits: [
    'setCurrentNodeId',
    'jumpNarrationToNode',
    'startPlayback',
    'startExplorationDuringPlayback',
    'stopExplorationDuringPlayback',
    'toggleIntroPanel',
  ],
  // ......
};

动画

动画的实现很简洁:

// smooth scroll to coordinate using custom duration and easing
smoothScroll(xEnd, yEnd, duration) {
  const time = Date.now();
  const xStart = this.flowchartContainer.scrollLeft;
  const yStart = this.flowchartContainer.scrollTop;

  const step = () => {
	const elapsed = Date.now() - time;
	const scrolling = elapsed < duration;
	const x = scrolling ? xStart + (xEnd - xStart) * easeExpOut(elapsed / duration) : xEnd;
	const y = scrolling ? yStart + (yEnd - yStart) * easeExpOut(elapsed / duration) : yEnd;

	if (scrolling) {
	  requestAnimationFrame(step);
	}

	this.flowchartContainer.scrollTo({
	  left: x,
	  top: y,
	  behavior: 'instant'
	});
  }

  step();
},

资料

代码:

GitHub - uclab-potsdam/interactive-flowchart: Navigating and narrating complexity through exploration and storytelling