Parallel.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. /**
  41. * Parallel Coordinates
  42. * <https://en.wikipedia.org/wiki/Parallel_coordinates>
  43. */
  44. import * as zrUtil from 'zrender/lib/core/util.js';
  45. import * as matrix from 'zrender/lib/core/matrix.js';
  46. import * as layoutUtil from '../../util/layout.js';
  47. import * as axisHelper from '../../coord/axisHelper.js';
  48. import ParallelAxis from './ParallelAxis.js';
  49. import * as graphic from '../../util/graphic.js';
  50. import * as numberUtil from '../../util/number.js';
  51. import sliderMove from '../../component/helper/sliderMove.js';
  52. var each = zrUtil.each;
  53. var mathMin = Math.min;
  54. var mathMax = Math.max;
  55. var mathFloor = Math.floor;
  56. var mathCeil = Math.ceil;
  57. var round = numberUtil.round;
  58. var PI = Math.PI;
  59. var Parallel = /** @class */function () {
  60. function Parallel(parallelModel, ecModel, api) {
  61. this.type = 'parallel';
  62. /**
  63. * key: dimension
  64. */
  65. this._axesMap = zrUtil.createHashMap();
  66. /**
  67. * key: dimension
  68. * value: {position: [], rotation, }
  69. */
  70. this._axesLayout = {};
  71. this.dimensions = parallelModel.dimensions;
  72. this._model = parallelModel;
  73. this._init(parallelModel, ecModel, api);
  74. }
  75. Parallel.prototype._init = function (parallelModel, ecModel, api) {
  76. var dimensions = parallelModel.dimensions;
  77. var parallelAxisIndex = parallelModel.parallelAxisIndex;
  78. each(dimensions, function (dim, idx) {
  79. var axisIndex = parallelAxisIndex[idx];
  80. var axisModel = ecModel.getComponent('parallelAxis', axisIndex);
  81. var axis = this._axesMap.set(dim, new ParallelAxis(dim, axisHelper.createScaleByModel(axisModel), [0, 0], axisModel.get('type'), axisIndex));
  82. var isCategory = axis.type === 'category';
  83. axis.onBand = isCategory && axisModel.get('boundaryGap');
  84. axis.inverse = axisModel.get('inverse');
  85. // Injection
  86. axisModel.axis = axis;
  87. axis.model = axisModel;
  88. axis.coordinateSystem = axisModel.coordinateSystem = this;
  89. }, this);
  90. };
  91. /**
  92. * Update axis scale after data processed
  93. */
  94. Parallel.prototype.update = function (ecModel, api) {
  95. this._updateAxesFromSeries(this._model, ecModel);
  96. };
  97. Parallel.prototype.containPoint = function (point) {
  98. var layoutInfo = this._makeLayoutInfo();
  99. var axisBase = layoutInfo.axisBase;
  100. var layoutBase = layoutInfo.layoutBase;
  101. var pixelDimIndex = layoutInfo.pixelDimIndex;
  102. var pAxis = point[1 - pixelDimIndex];
  103. var pLayout = point[pixelDimIndex];
  104. return pAxis >= axisBase && pAxis <= axisBase + layoutInfo.axisLength && pLayout >= layoutBase && pLayout <= layoutBase + layoutInfo.layoutLength;
  105. };
  106. Parallel.prototype.getModel = function () {
  107. return this._model;
  108. };
  109. /**
  110. * Update properties from series
  111. */
  112. Parallel.prototype._updateAxesFromSeries = function (parallelModel, ecModel) {
  113. ecModel.eachSeries(function (seriesModel) {
  114. if (!parallelModel.contains(seriesModel, ecModel)) {
  115. return;
  116. }
  117. var data = seriesModel.getData();
  118. each(this.dimensions, function (dim) {
  119. var axis = this._axesMap.get(dim);
  120. axis.scale.unionExtentFromData(data, data.mapDimension(dim));
  121. axisHelper.niceScaleExtent(axis.scale, axis.model);
  122. }, this);
  123. }, this);
  124. };
  125. /**
  126. * Resize the parallel coordinate system.
  127. */
  128. Parallel.prototype.resize = function (parallelModel, api) {
  129. var refContainer = layoutUtil.createBoxLayoutReference(parallelModel, api).refContainer;
  130. this._rect = layoutUtil.getLayoutRect(parallelModel.getBoxLayoutParams(), refContainer);
  131. this._layoutAxes();
  132. };
  133. Parallel.prototype.getRect = function () {
  134. return this._rect;
  135. };
  136. Parallel.prototype._makeLayoutInfo = function () {
  137. var parallelModel = this._model;
  138. var rect = this._rect;
  139. var xy = ['x', 'y'];
  140. var wh = ['width', 'height'];
  141. var layout = parallelModel.get('layout');
  142. var pixelDimIndex = layout === 'horizontal' ? 0 : 1;
  143. var layoutLength = rect[wh[pixelDimIndex]];
  144. var layoutExtent = [0, layoutLength];
  145. var axisCount = this.dimensions.length;
  146. var axisExpandWidth = restrict(parallelModel.get('axisExpandWidth'), layoutExtent);
  147. var axisExpandCount = restrict(parallelModel.get('axisExpandCount') || 0, [0, axisCount]);
  148. var axisExpandable = parallelModel.get('axisExpandable') && axisCount > 3 && axisCount > axisExpandCount && axisExpandCount > 1 && axisExpandWidth > 0 && layoutLength > 0;
  149. // `axisExpandWindow` is According to the coordinates of [0, axisExpandLength],
  150. // for sake of consider the case that axisCollapseWidth is 0 (when screen is narrow),
  151. // where collapsed axes should be overlapped.
  152. var axisExpandWindow = parallelModel.get('axisExpandWindow');
  153. var winSize;
  154. if (!axisExpandWindow) {
  155. winSize = restrict(axisExpandWidth * (axisExpandCount - 1), layoutExtent);
  156. var axisExpandCenter = parallelModel.get('axisExpandCenter') || mathFloor(axisCount / 2);
  157. axisExpandWindow = [axisExpandWidth * axisExpandCenter - winSize / 2];
  158. axisExpandWindow[1] = axisExpandWindow[0] + winSize;
  159. } else {
  160. winSize = restrict(axisExpandWindow[1] - axisExpandWindow[0], layoutExtent);
  161. axisExpandWindow[1] = axisExpandWindow[0] + winSize;
  162. }
  163. var axisCollapseWidth = (layoutLength - winSize) / (axisCount - axisExpandCount);
  164. // Avoid axisCollapseWidth is too small.
  165. axisCollapseWidth < 3 && (axisCollapseWidth = 0);
  166. // Find the first and last indices > ewin[0] and < ewin[1].
  167. var winInnerIndices = [mathFloor(round(axisExpandWindow[0] / axisExpandWidth, 1)) + 1, mathCeil(round(axisExpandWindow[1] / axisExpandWidth, 1)) - 1];
  168. // Pos in ec coordinates.
  169. var axisExpandWindow0Pos = axisCollapseWidth / axisExpandWidth * axisExpandWindow[0];
  170. return {
  171. layout: layout,
  172. pixelDimIndex: pixelDimIndex,
  173. layoutBase: rect[xy[pixelDimIndex]],
  174. layoutLength: layoutLength,
  175. axisBase: rect[xy[1 - pixelDimIndex]],
  176. axisLength: rect[wh[1 - pixelDimIndex]],
  177. axisExpandable: axisExpandable,
  178. axisExpandWidth: axisExpandWidth,
  179. axisCollapseWidth: axisCollapseWidth,
  180. axisExpandWindow: axisExpandWindow,
  181. axisCount: axisCount,
  182. winInnerIndices: winInnerIndices,
  183. axisExpandWindow0Pos: axisExpandWindow0Pos
  184. };
  185. };
  186. Parallel.prototype._layoutAxes = function () {
  187. var rect = this._rect;
  188. var axes = this._axesMap;
  189. var dimensions = this.dimensions;
  190. var layoutInfo = this._makeLayoutInfo();
  191. var layout = layoutInfo.layout;
  192. axes.each(function (axis) {
  193. var axisExtent = [0, layoutInfo.axisLength];
  194. var idx = axis.inverse ? 1 : 0;
  195. axis.setExtent(axisExtent[idx], axisExtent[1 - idx]);
  196. });
  197. each(dimensions, function (dim, idx) {
  198. var posInfo = (layoutInfo.axisExpandable ? layoutAxisWithExpand : layoutAxisWithoutExpand)(idx, layoutInfo);
  199. var positionTable = {
  200. horizontal: {
  201. x: posInfo.position,
  202. y: layoutInfo.axisLength
  203. },
  204. vertical: {
  205. x: 0,
  206. y: posInfo.position
  207. }
  208. };
  209. var rotationTable = {
  210. horizontal: PI / 2,
  211. vertical: 0
  212. };
  213. var position = [positionTable[layout].x + rect.x, positionTable[layout].y + rect.y];
  214. var rotation = rotationTable[layout];
  215. var transform = matrix.create();
  216. matrix.rotate(transform, transform, rotation);
  217. matrix.translate(transform, transform, position);
  218. // TODO
  219. // tick layout info
  220. // TODO
  221. // update dimensions info based on axis order.
  222. this._axesLayout[dim] = {
  223. position: position,
  224. rotation: rotation,
  225. transform: transform,
  226. axisNameAvailableWidth: posInfo.axisNameAvailableWidth,
  227. axisLabelShow: posInfo.axisLabelShow,
  228. nameTruncateMaxWidth: posInfo.nameTruncateMaxWidth,
  229. tickDirection: 1,
  230. labelDirection: 1
  231. };
  232. }, this);
  233. };
  234. /**
  235. * Get axis by dim.
  236. */
  237. Parallel.prototype.getAxis = function (dim) {
  238. return this._axesMap.get(dim);
  239. };
  240. /**
  241. * Convert a dim value of a single item of series data to Point.
  242. */
  243. Parallel.prototype.dataToPoint = function (value, dim) {
  244. return this.axisCoordToPoint(this._axesMap.get(dim).dataToCoord(value), dim);
  245. };
  246. /**
  247. * Travel data for one time, get activeState of each data item.
  248. * @param start the start dataIndex that travel from.
  249. * @param end the next dataIndex of the last dataIndex will be travel.
  250. */
  251. Parallel.prototype.eachActiveState = function (data, callback, start, end) {
  252. start == null && (start = 0);
  253. end == null && (end = data.count());
  254. var axesMap = this._axesMap;
  255. var dimensions = this.dimensions;
  256. var dataDimensions = [];
  257. var axisModels = [];
  258. zrUtil.each(dimensions, function (axisDim) {
  259. dataDimensions.push(data.mapDimension(axisDim));
  260. axisModels.push(axesMap.get(axisDim).model);
  261. });
  262. var hasActiveSet = this.hasAxisBrushed();
  263. for (var dataIndex = start; dataIndex < end; dataIndex++) {
  264. var activeState = void 0;
  265. if (!hasActiveSet) {
  266. activeState = 'normal';
  267. } else {
  268. activeState = 'active';
  269. var values = data.getValues(dataDimensions, dataIndex);
  270. for (var j = 0, lenj = dimensions.length; j < lenj; j++) {
  271. var state = axisModels[j].getActiveState(values[j]);
  272. if (state === 'inactive') {
  273. activeState = 'inactive';
  274. break;
  275. }
  276. }
  277. }
  278. callback(activeState, dataIndex);
  279. }
  280. };
  281. /**
  282. * Whether has any activeSet.
  283. */
  284. Parallel.prototype.hasAxisBrushed = function () {
  285. var dimensions = this.dimensions;
  286. var axesMap = this._axesMap;
  287. var hasActiveSet = false;
  288. for (var j = 0, lenj = dimensions.length; j < lenj; j++) {
  289. if (axesMap.get(dimensions[j]).model.getActiveState() !== 'normal') {
  290. hasActiveSet = true;
  291. }
  292. }
  293. return hasActiveSet;
  294. };
  295. /**
  296. * Convert coords of each axis to Point.
  297. * Return point. For example: [10, 20]
  298. */
  299. Parallel.prototype.axisCoordToPoint = function (coord, dim) {
  300. var axisLayout = this._axesLayout[dim];
  301. return graphic.applyTransform([coord, 0], axisLayout.transform);
  302. };
  303. /**
  304. * Get axis layout.
  305. */
  306. Parallel.prototype.getAxisLayout = function (dim) {
  307. return zrUtil.clone(this._axesLayout[dim]);
  308. };
  309. /**
  310. * @return {Object} {axisExpandWindow, delta, behavior: 'jump' | 'slide' | 'none'}.
  311. */
  312. Parallel.prototype.getSlidedAxisExpandWindow = function (point) {
  313. var layoutInfo = this._makeLayoutInfo();
  314. var pixelDimIndex = layoutInfo.pixelDimIndex;
  315. var axisExpandWindow = layoutInfo.axisExpandWindow.slice();
  316. var winSize = axisExpandWindow[1] - axisExpandWindow[0];
  317. var extent = [0, layoutInfo.axisExpandWidth * (layoutInfo.axisCount - 1)];
  318. // Out of the area of coordinate system.
  319. if (!this.containPoint(point)) {
  320. return {
  321. behavior: 'none',
  322. axisExpandWindow: axisExpandWindow
  323. };
  324. }
  325. // Convert the point from global to expand coordinates.
  326. var pointCoord = point[pixelDimIndex] - layoutInfo.layoutBase - layoutInfo.axisExpandWindow0Pos;
  327. // For dragging operation convenience, the window should not be
  328. // slided when mouse is the center area of the window.
  329. var delta;
  330. var behavior = 'slide';
  331. var axisCollapseWidth = layoutInfo.axisCollapseWidth;
  332. var triggerArea = this._model.get('axisExpandSlideTriggerArea');
  333. // But consider touch device, jump is necessary.
  334. var useJump = triggerArea[0] != null;
  335. if (axisCollapseWidth) {
  336. if (useJump && axisCollapseWidth && pointCoord < winSize * triggerArea[0]) {
  337. behavior = 'jump';
  338. delta = pointCoord - winSize * triggerArea[2];
  339. } else if (useJump && axisCollapseWidth && pointCoord > winSize * (1 - triggerArea[0])) {
  340. behavior = 'jump';
  341. delta = pointCoord - winSize * (1 - triggerArea[2]);
  342. } else {
  343. (delta = pointCoord - winSize * triggerArea[1]) >= 0 && (delta = pointCoord - winSize * (1 - triggerArea[1])) <= 0 && (delta = 0);
  344. }
  345. delta *= layoutInfo.axisExpandWidth / axisCollapseWidth;
  346. delta ? sliderMove(delta, axisExpandWindow, extent, 'all')
  347. // Avoid nonsense triger on mousemove.
  348. : behavior = 'none';
  349. }
  350. // When screen is too narrow, make it visible and slidable, although it is hard to interact.
  351. else {
  352. var winSize2 = axisExpandWindow[1] - axisExpandWindow[0];
  353. var pos = extent[1] * pointCoord / winSize2;
  354. axisExpandWindow = [mathMax(0, pos - winSize2 / 2)];
  355. axisExpandWindow[1] = mathMin(extent[1], axisExpandWindow[0] + winSize2);
  356. axisExpandWindow[0] = axisExpandWindow[1] - winSize2;
  357. }
  358. return {
  359. axisExpandWindow: axisExpandWindow,
  360. behavior: behavior
  361. };
  362. };
  363. return Parallel;
  364. }();
  365. function restrict(len, extent) {
  366. return mathMin(mathMax(len, extent[0]), extent[1]);
  367. }
  368. function layoutAxisWithoutExpand(axisIndex, layoutInfo) {
  369. var step = layoutInfo.layoutLength / (layoutInfo.axisCount - 1);
  370. return {
  371. position: step * axisIndex,
  372. axisNameAvailableWidth: step,
  373. axisLabelShow: true
  374. };
  375. }
  376. function layoutAxisWithExpand(axisIndex, layoutInfo) {
  377. var layoutLength = layoutInfo.layoutLength;
  378. var axisExpandWidth = layoutInfo.axisExpandWidth;
  379. var axisCount = layoutInfo.axisCount;
  380. var axisCollapseWidth = layoutInfo.axisCollapseWidth;
  381. var winInnerIndices = layoutInfo.winInnerIndices;
  382. var position;
  383. var axisNameAvailableWidth = axisCollapseWidth;
  384. var axisLabelShow = false;
  385. var nameTruncateMaxWidth;
  386. if (axisIndex < winInnerIndices[0]) {
  387. position = axisIndex * axisCollapseWidth;
  388. nameTruncateMaxWidth = axisCollapseWidth;
  389. } else if (axisIndex <= winInnerIndices[1]) {
  390. position = layoutInfo.axisExpandWindow0Pos + axisIndex * axisExpandWidth - layoutInfo.axisExpandWindow[0];
  391. axisNameAvailableWidth = axisExpandWidth;
  392. axisLabelShow = true;
  393. } else {
  394. position = layoutLength - (axisCount - 1 - axisIndex) * axisCollapseWidth;
  395. nameTruncateMaxWidth = axisCollapseWidth;
  396. }
  397. return {
  398. position: position,
  399. axisNameAvailableWidth: axisNameAvailableWidth,
  400. axisLabelShow: axisLabelShow,
  401. nameTruncateMaxWidth: nameTruncateMaxWidth
  402. };
  403. }
  404. export default Parallel;