Parallel.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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. this._rect = layoutUtil.getLayoutRect(parallelModel.getBoxLayoutParams(), {
  130. width: api.getWidth(),
  131. height: api.getHeight()
  132. });
  133. this._layoutAxes();
  134. };
  135. Parallel.prototype.getRect = function () {
  136. return this._rect;
  137. };
  138. Parallel.prototype._makeLayoutInfo = function () {
  139. var parallelModel = this._model;
  140. var rect = this._rect;
  141. var xy = ['x', 'y'];
  142. var wh = ['width', 'height'];
  143. var layout = parallelModel.get('layout');
  144. var pixelDimIndex = layout === 'horizontal' ? 0 : 1;
  145. var layoutLength = rect[wh[pixelDimIndex]];
  146. var layoutExtent = [0, layoutLength];
  147. var axisCount = this.dimensions.length;
  148. var axisExpandWidth = restrict(parallelModel.get('axisExpandWidth'), layoutExtent);
  149. var axisExpandCount = restrict(parallelModel.get('axisExpandCount') || 0, [0, axisCount]);
  150. var axisExpandable = parallelModel.get('axisExpandable') && axisCount > 3 && axisCount > axisExpandCount && axisExpandCount > 1 && axisExpandWidth > 0 && layoutLength > 0;
  151. // `axisExpandWindow` is According to the coordinates of [0, axisExpandLength],
  152. // for sake of consider the case that axisCollapseWidth is 0 (when screen is narrow),
  153. // where collapsed axes should be overlapped.
  154. var axisExpandWindow = parallelModel.get('axisExpandWindow');
  155. var winSize;
  156. if (!axisExpandWindow) {
  157. winSize = restrict(axisExpandWidth * (axisExpandCount - 1), layoutExtent);
  158. var axisExpandCenter = parallelModel.get('axisExpandCenter') || mathFloor(axisCount / 2);
  159. axisExpandWindow = [axisExpandWidth * axisExpandCenter - winSize / 2];
  160. axisExpandWindow[1] = axisExpandWindow[0] + winSize;
  161. } else {
  162. winSize = restrict(axisExpandWindow[1] - axisExpandWindow[0], layoutExtent);
  163. axisExpandWindow[1] = axisExpandWindow[0] + winSize;
  164. }
  165. var axisCollapseWidth = (layoutLength - winSize) / (axisCount - axisExpandCount);
  166. // Avoid axisCollapseWidth is too small.
  167. axisCollapseWidth < 3 && (axisCollapseWidth = 0);
  168. // Find the first and last indices > ewin[0] and < ewin[1].
  169. var winInnerIndices = [mathFloor(round(axisExpandWindow[0] / axisExpandWidth, 1)) + 1, mathCeil(round(axisExpandWindow[1] / axisExpandWidth, 1)) - 1];
  170. // Pos in ec coordinates.
  171. var axisExpandWindow0Pos = axisCollapseWidth / axisExpandWidth * axisExpandWindow[0];
  172. return {
  173. layout: layout,
  174. pixelDimIndex: pixelDimIndex,
  175. layoutBase: rect[xy[pixelDimIndex]],
  176. layoutLength: layoutLength,
  177. axisBase: rect[xy[1 - pixelDimIndex]],
  178. axisLength: rect[wh[1 - pixelDimIndex]],
  179. axisExpandable: axisExpandable,
  180. axisExpandWidth: axisExpandWidth,
  181. axisCollapseWidth: axisCollapseWidth,
  182. axisExpandWindow: axisExpandWindow,
  183. axisCount: axisCount,
  184. winInnerIndices: winInnerIndices,
  185. axisExpandWindow0Pos: axisExpandWindow0Pos
  186. };
  187. };
  188. Parallel.prototype._layoutAxes = function () {
  189. var rect = this._rect;
  190. var axes = this._axesMap;
  191. var dimensions = this.dimensions;
  192. var layoutInfo = this._makeLayoutInfo();
  193. var layout = layoutInfo.layout;
  194. axes.each(function (axis) {
  195. var axisExtent = [0, layoutInfo.axisLength];
  196. var idx = axis.inverse ? 1 : 0;
  197. axis.setExtent(axisExtent[idx], axisExtent[1 - idx]);
  198. });
  199. each(dimensions, function (dim, idx) {
  200. var posInfo = (layoutInfo.axisExpandable ? layoutAxisWithExpand : layoutAxisWithoutExpand)(idx, layoutInfo);
  201. var positionTable = {
  202. horizontal: {
  203. x: posInfo.position,
  204. y: layoutInfo.axisLength
  205. },
  206. vertical: {
  207. x: 0,
  208. y: posInfo.position
  209. }
  210. };
  211. var rotationTable = {
  212. horizontal: PI / 2,
  213. vertical: 0
  214. };
  215. var position = [positionTable[layout].x + rect.x, positionTable[layout].y + rect.y];
  216. var rotation = rotationTable[layout];
  217. var transform = matrix.create();
  218. matrix.rotate(transform, transform, rotation);
  219. matrix.translate(transform, transform, position);
  220. // TODO
  221. // tick layout info
  222. // TODO
  223. // update dimensions info based on axis order.
  224. this._axesLayout[dim] = {
  225. position: position,
  226. rotation: rotation,
  227. transform: transform,
  228. axisNameAvailableWidth: posInfo.axisNameAvailableWidth,
  229. axisLabelShow: posInfo.axisLabelShow,
  230. nameTruncateMaxWidth: posInfo.nameTruncateMaxWidth,
  231. tickDirection: 1,
  232. labelDirection: 1
  233. };
  234. }, this);
  235. };
  236. /**
  237. * Get axis by dim.
  238. */
  239. Parallel.prototype.getAxis = function (dim) {
  240. return this._axesMap.get(dim);
  241. };
  242. /**
  243. * Convert a dim value of a single item of series data to Point.
  244. */
  245. Parallel.prototype.dataToPoint = function (value, dim) {
  246. return this.axisCoordToPoint(this._axesMap.get(dim).dataToCoord(value), dim);
  247. };
  248. /**
  249. * Travel data for one time, get activeState of each data item.
  250. * @param start the start dataIndex that travel from.
  251. * @param end the next dataIndex of the last dataIndex will be travel.
  252. */
  253. Parallel.prototype.eachActiveState = function (data, callback, start, end) {
  254. start == null && (start = 0);
  255. end == null && (end = data.count());
  256. var axesMap = this._axesMap;
  257. var dimensions = this.dimensions;
  258. var dataDimensions = [];
  259. var axisModels = [];
  260. zrUtil.each(dimensions, function (axisDim) {
  261. dataDimensions.push(data.mapDimension(axisDim));
  262. axisModels.push(axesMap.get(axisDim).model);
  263. });
  264. var hasActiveSet = this.hasAxisBrushed();
  265. for (var dataIndex = start; dataIndex < end; dataIndex++) {
  266. var activeState = void 0;
  267. if (!hasActiveSet) {
  268. activeState = 'normal';
  269. } else {
  270. activeState = 'active';
  271. var values = data.getValues(dataDimensions, dataIndex);
  272. for (var j = 0, lenj = dimensions.length; j < lenj; j++) {
  273. var state = axisModels[j].getActiveState(values[j]);
  274. if (state === 'inactive') {
  275. activeState = 'inactive';
  276. break;
  277. }
  278. }
  279. }
  280. callback(activeState, dataIndex);
  281. }
  282. };
  283. /**
  284. * Whether has any activeSet.
  285. */
  286. Parallel.prototype.hasAxisBrushed = function () {
  287. var dimensions = this.dimensions;
  288. var axesMap = this._axesMap;
  289. var hasActiveSet = false;
  290. for (var j = 0, lenj = dimensions.length; j < lenj; j++) {
  291. if (axesMap.get(dimensions[j]).model.getActiveState() !== 'normal') {
  292. hasActiveSet = true;
  293. }
  294. }
  295. return hasActiveSet;
  296. };
  297. /**
  298. * Convert coords of each axis to Point.
  299. * Return point. For example: [10, 20]
  300. */
  301. Parallel.prototype.axisCoordToPoint = function (coord, dim) {
  302. var axisLayout = this._axesLayout[dim];
  303. return graphic.applyTransform([coord, 0], axisLayout.transform);
  304. };
  305. /**
  306. * Get axis layout.
  307. */
  308. Parallel.prototype.getAxisLayout = function (dim) {
  309. return zrUtil.clone(this._axesLayout[dim]);
  310. };
  311. /**
  312. * @return {Object} {axisExpandWindow, delta, behavior: 'jump' | 'slide' | 'none'}.
  313. */
  314. Parallel.prototype.getSlidedAxisExpandWindow = function (point) {
  315. var layoutInfo = this._makeLayoutInfo();
  316. var pixelDimIndex = layoutInfo.pixelDimIndex;
  317. var axisExpandWindow = layoutInfo.axisExpandWindow.slice();
  318. var winSize = axisExpandWindow[1] - axisExpandWindow[0];
  319. var extent = [0, layoutInfo.axisExpandWidth * (layoutInfo.axisCount - 1)];
  320. // Out of the area of coordinate system.
  321. if (!this.containPoint(point)) {
  322. return {
  323. behavior: 'none',
  324. axisExpandWindow: axisExpandWindow
  325. };
  326. }
  327. // Convert the point from global to expand coordinates.
  328. var pointCoord = point[pixelDimIndex] - layoutInfo.layoutBase - layoutInfo.axisExpandWindow0Pos;
  329. // For dragging operation convenience, the window should not be
  330. // slided when mouse is the center area of the window.
  331. var delta;
  332. var behavior = 'slide';
  333. var axisCollapseWidth = layoutInfo.axisCollapseWidth;
  334. var triggerArea = this._model.get('axisExpandSlideTriggerArea');
  335. // But consider touch device, jump is necessary.
  336. var useJump = triggerArea[0] != null;
  337. if (axisCollapseWidth) {
  338. if (useJump && axisCollapseWidth && pointCoord < winSize * triggerArea[0]) {
  339. behavior = 'jump';
  340. delta = pointCoord - winSize * triggerArea[2];
  341. } else if (useJump && axisCollapseWidth && pointCoord > winSize * (1 - triggerArea[0])) {
  342. behavior = 'jump';
  343. delta = pointCoord - winSize * (1 - triggerArea[2]);
  344. } else {
  345. (delta = pointCoord - winSize * triggerArea[1]) >= 0 && (delta = pointCoord - winSize * (1 - triggerArea[1])) <= 0 && (delta = 0);
  346. }
  347. delta *= layoutInfo.axisExpandWidth / axisCollapseWidth;
  348. delta ? sliderMove(delta, axisExpandWindow, extent, 'all')
  349. // Avoid nonsense triger on mousemove.
  350. : behavior = 'none';
  351. }
  352. // When screen is too narrow, make it visible and slidable, although it is hard to interact.
  353. else {
  354. var winSize2 = axisExpandWindow[1] - axisExpandWindow[0];
  355. var pos = extent[1] * pointCoord / winSize2;
  356. axisExpandWindow = [mathMax(0, pos - winSize2 / 2)];
  357. axisExpandWindow[1] = mathMin(extent[1], axisExpandWindow[0] + winSize2);
  358. axisExpandWindow[0] = axisExpandWindow[1] - winSize2;
  359. }
  360. return {
  361. axisExpandWindow: axisExpandWindow,
  362. behavior: behavior
  363. };
  364. };
  365. return Parallel;
  366. }();
  367. function restrict(len, extent) {
  368. return mathMin(mathMax(len, extent[0]), extent[1]);
  369. }
  370. function layoutAxisWithoutExpand(axisIndex, layoutInfo) {
  371. var step = layoutInfo.layoutLength / (layoutInfo.axisCount - 1);
  372. return {
  373. position: step * axisIndex,
  374. axisNameAvailableWidth: step,
  375. axisLabelShow: true
  376. };
  377. }
  378. function layoutAxisWithExpand(axisIndex, layoutInfo) {
  379. var layoutLength = layoutInfo.layoutLength;
  380. var axisExpandWidth = layoutInfo.axisExpandWidth;
  381. var axisCount = layoutInfo.axisCount;
  382. var axisCollapseWidth = layoutInfo.axisCollapseWidth;
  383. var winInnerIndices = layoutInfo.winInnerIndices;
  384. var position;
  385. var axisNameAvailableWidth = axisCollapseWidth;
  386. var axisLabelShow = false;
  387. var nameTruncateMaxWidth;
  388. if (axisIndex < winInnerIndices[0]) {
  389. position = axisIndex * axisCollapseWidth;
  390. nameTruncateMaxWidth = axisCollapseWidth;
  391. } else if (axisIndex <= winInnerIndices[1]) {
  392. position = layoutInfo.axisExpandWindow0Pos + axisIndex * axisExpandWidth - layoutInfo.axisExpandWindow[0];
  393. axisNameAvailableWidth = axisExpandWidth;
  394. axisLabelShow = true;
  395. } else {
  396. position = layoutLength - (axisCount - 1 - axisIndex) * axisCollapseWidth;
  397. nameTruncateMaxWidth = axisCollapseWidth;
  398. }
  399. return {
  400. position: position,
  401. axisNameAvailableWidth: axisNameAvailableWidth,
  402. axisLabelShow: axisLabelShow,
  403. nameTruncateMaxWidth: nameTruncateMaxWidth
  404. };
  405. }
  406. export default Parallel;