用D3绘制图表

spritejs拥有与DOM一致性很高的api,可以很方便地与d3协同工作。

与d3一同使用 - 图1

要显示坐标轴,我们可以使用d3扩展库

  1. const scene = new Scene('#d3-linear', {viewport: ['auto', 'auto'], resolution: [1540, 600]});
  2. const dataset = [125, 121, 127, 193, 309];
  3. const linear = d3.scaleLinear()
  4. .domain([100, d3.max(dataset)])
  5. .range([0, 500]);
  6. const colors = ['#f00', '#0a0', '#00a', '#3ca', '#7ac'];
  7. const s = d3.select(scene).append('fglayer');
  8. const chart = s.selectAll('sprite')
  9. .data(dataset)
  10. .enter()
  11. .append('sprite')
  12. .attr('x', 300)
  13. .attr('y', (d, i) => {
  14. return 200 + i * 55;
  15. })
  16. .attr('width', 0)
  17. .attr('height', 50)
  18. .attr('bgcolor', 'black');
  19. chart
  20. .transition()
  21. .duration(2000)
  22. .attr('width', (d, i) => {
  23. return linear(d);
  24. })
  25. .attr('bgcolor', (d, i) => {
  26. return colors[i];
  27. });
  28. s.append('axis')
  29. .attr('ticks', [100, 200, 300, 400])
  30. .attr('axisScales', [linear])
  31. .attr('direction', 'bottom')
  32. .attr('pos', [300, 500])
  33. .attr('color', 'blue');
  34. chart.on('click', (data) => {
  35. /* eslint-disable no-console */
  36. console.log(data, d3.event);
  37. /* eslint-enable no-console */
  38. });

还可以绘制层次图:

与d3一同使用 - 图2

  1. const scene = new Scene('#d3-hierarchy', {viewport: ['auto', 'auto'], resolution: [1540, 1200]});
  2. const layer = d3.select(scene).append('fglayer');
  3. d3.json('/res/data/city.json', (err, data) => {
  4. const root = d3.hierarchy(data)
  5. .sum(d => 1)
  6. .sort((a, b) => b.value - a.value);
  7. const pack = d3.pack()
  8. .size([1180, 1180])
  9. .padding(3);
  10. const nodes = pack(root);
  11. layer.append('label')
  12. .attr('pos', [1170, 50])
  13. .attr('name', 'region')
  14. .attr('font', '42px Arial')
  15. .attr('text', '中国');
  16. layer.selectAll('label[name!="region"]')
  17. .data(nodes.descendants())
  18. .enter()
  19. .append('label')
  20. .attr('anchor', 0.5)
  21. .attr('pos', (d) => {
  22. const x = Math.round(d.x),
  23. y = Math.round(d.y);
  24. return [170 + x, 10 + y];
  25. })
  26. .attr('size', (d) => {
  27. const r = Math.round(d.r);
  28. return [2 * r, 2 * r];
  29. })
  30. .attr('fillColor', 'black')
  31. .attr('bgcolor', 'rgba(31, 119, 180, 0.4)')
  32. .attr('borderRadius', (d) => {
  33. return d.r;
  34. })
  35. .attr('font', (d) => {
  36. return `${16 + Math.round(10 * Math.log2(d.value))}px Arial`;
  37. })
  38. .attr('lineHeight', (d) => {
  39. return Math.round(2 * d.r);
  40. })
  41. .attr('textAlign', 'center')
  42. .attr('text', d => d.data.name)
  43. .on('mousemove', (d) => {
  44. d3.event.stopDispatch();
  45. const {offsetX, offsetY} = d3.event,
  46. r = Math.round(d.r);
  47. if(offsetX ** 2 + offsetY ** 2 < r ** 2) {
  48. layer.selectAll('label[name="region"]')
  49. .attr('text', d.data.name);
  50. layer.selectAll('label[name!="region"]')
  51. .attr('border', null);
  52. d3.event.target.attr('border', [3, 'red']);
  53. }
  54. })
  55. .on('mouseleave', (d) => {
  56. d3.event.target.attr('border', null);
  57. });
  58. });

地图应用:

与d3一同使用 - 图3

  1. const scene = new Scene('#d3-map', {viewport: ['auto', 'auto'], resolution: [1540, 600]});
  2. const layer = d3.select(scene).append('fglayer');
  3. const width = 1540;
  4. const height = 600;
  5. const projection = d3.geoMercator()
  6. .center([107, 38])
  7. .scale(width / 2 - 40)
  8. .translate([width / 4 + 80, height / 2]);
  9. const path = d3.geoPath().projection(projection);
  10. d3.json('/res/data/china.json', (err, data) => {
  11. if(err) throw new Error(err);
  12. let selectedTarget = null;
  13. layer.selectAll('path')
  14. .data(data.features)
  15. .enter()
  16. .append('path')
  17. .attr('color', 'black')
  18. .attr('lineWidth', 1)
  19. .attr('d', path)
  20. .attr('renderMode', 'fill')
  21. .attr('fillColor', '#d1eeee')
  22. .on('click', (d) => {
  23. const paths = d3.event.target.findPath(d3.event.offsetX, d3.event.offsetY);
  24. if(paths.length) {
  25. /* eslint-disable no-console */
  26. console.log(d.properties.name);
  27. /* eslint-enable no-console */
  28. }
  29. })
  30. .on('mousemove', (d) => {
  31. const event = d3.event;
  32. if(event.target !== selectedTarget) {
  33. const paths = event.target.findPath(event.offsetX, event.offsetY);
  34. if(paths.length) {
  35. if(selectedTarget) {
  36. selectedTarget.attr('fillColor', '#d1eeee');
  37. }
  38. selectedTarget = event.target;
  39. event.target.attr('fillColor', '#c73');
  40. }
  41. }
  42. });
  43. });

连线图应用:

与d3一同使用 - 图4

  1. const scene = new Scene('#d3-link', {viewport: ['auto', 'auto'], resolution: [1540, 600]});
  2. const layer = scene.layer('fglayer', {
  3. handleEvent: false,
  4. });
  5. const simulation = d3.forceSimulation()
  6. .force('link', d3.forceLink().id(d => d.id))
  7. .force('charge', d3.forceManyBody())
  8. .force('center', d3.forceCenter(400, 300));
  9. d3.json('/res/data/miserables.json', (error, graph) => {
  10. if(error) throw error;
  11. simulation
  12. .nodes(graph.nodes)
  13. .on('tick', ticked);
  14. simulation.force('link')
  15. .links(graph.links);
  16. const path = new spritejs.Path();
  17. path.attr({
  18. size: [1200, 800],
  19. pos: [0, 100],
  20. });
  21. layer.appendChild(path);
  22. d3.select(layer.canvas)
  23. .call(d3.drag()
  24. .container(layer.canvas)
  25. .subject(dragsubject)
  26. .on('start', dragstarted)
  27. .on('drag', dragged)
  28. .on('end', dragended));
  29. path.on('afterdraw', ({context}) => {
  30. context.beginPath();
  31. graph.links.forEach((d) => {
  32. const [sx, sy] = [d.source.x, d.source.y],
  33. [tx, ty] = [d.target.x, d.target.y];
  34. context.moveTo(sx, sy);
  35. context.lineTo(tx, ty);
  36. });
  37. context.strokeStyle = '#aaa';
  38. context.stroke();
  39. context.beginPath();
  40. graph.nodes.forEach((d) => {
  41. const [x, y] = [d.x, d.y];
  42. context.moveTo(x + 3, y);
  43. context.arc(x, y, 3, 0, 2 * Math.PI);
  44. });
  45. context.fill();
  46. context.strokeStyle = '#fff';
  47. context.stroke();
  48. });
  49. function ticked() {
  50. path.forceUpdate(true);
  51. }
  52. function dragsubject() {
  53. const [x, y] = layer.toLocalPos(d3.event.x, d3.event.y);
  54. return simulation.find(x, y - 100);
  55. }
  56. });
  57. function dragstarted() {
  58. if(!d3.event.active) simulation.alphaTarget(0.3).restart();
  59. const [x, y] = [d3.event.subject.x, d3.event.subject.y];
  60. d3.event.subject.fx = x;
  61. d3.event.subject.fy = y;
  62. d3.event.subject.x0 = x;
  63. d3.event.subject.y0 = y;
  64. }
  65. function dragged() {
  66. const [x, y] = [d3.event.x, d3.event.y],
  67. {x0, y0} = d3.event.subject;
  68. const [dx, dy] = layer.toLocalPos((x - x0), (y - y0));
  69. d3.event.subject.fx = x0 + dx;
  70. d3.event.subject.fy = y0 + dy;
  71. }
  72. function dragended() {
  73. if(!d3.event.active) simulation.alphaTarget(0);
  74. d3.event.subject.fx = null;
  75. d3.event.subject.fy = null;
  76. }