├── public ├── favicon.ico ├── manifest.json └── index.html ├── README.md ├── .gitignore ├── src ├── index.css └── index.js └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ezyang/stride-visualizer/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Stride Visualizer 2 | 3 | Live at [Stride Visualizer](https://ezyang.github.io/stride-visualizer/index.html). 4 | 5 | Made with the help of our fine friends at [React](https://reactjs.org/) 6 | and [D3.js](https://d3js.org/). 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Stride Visualizer", 3 | "name": "Stride Visualizer", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | h1 { margin-bottom: 0 } 2 | 3 | .author { margin-left: 2em; } 4 | 5 | p { 6 | max-width: 80em; 7 | } 8 | 9 | body { 10 | font: 14px "Century Gothic", Futura, sans-serif; 11 | margin: 20px; 12 | margin-right:40px; 13 | } 14 | 15 | .form { 16 | margin-bottom: 1em; 17 | float: left; 18 | } 19 | 20 | .viewport { 21 | margin-left: 30em; 22 | } 23 | 24 | .grid-container { 25 | margin-bottom: 1em; 26 | } 27 | 28 | table { 29 | border-collapse: collapse; 30 | } 31 | 32 | td { 33 | background: #fff; 34 | border: 1px solid #999; 35 | height: 34px; 36 | width: 34px; 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stride-visualizer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "d3-scale-chromatic": "^1.2.0", 7 | "d3v4": "^4.2.2", 8 | "gh-pages": "^1.1.0", 9 | "react": "^16.2.0", 10 | "react-dom": "^16.2.0", 11 | "react-scripts": "1.1.1" 12 | }, 13 | "homepage": "https://ezyang.github.io/stride-visualizer", 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject", 19 | "predeploy": "yarn build", 20 | "deploy": "gh-pages -d build" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 11 | 12 | 21 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import * as d3 from 'd3v4';
4 | import './index.css';
5 |
6 | /**
7 | * An HTML5 range slider and associated raw text input.
8 | *
9 | * Properties:
10 | * - min: The minimum allowed value for the slider range
11 | * - max: The maximum allowed value for the slider range
12 | * - value: The current value of the slider
13 | * - disabled: Whether or not to disable the slider. A slider
14 | * is automatically disabled when min == max.
15 | * - onChange: Callback when the value of this slider changes.
16 | */
17 | function Slider(props) {
18 | const max = parseInt(props.max, 10);
19 | const min = parseInt(props.min, 10);
20 | const maxLength = max ? Math.ceil(Math.log10(Math.abs(max))) : 1;
21 | const disabled = props.disabled || min >= max;
22 | return (
23 |
24 |
28 |
34 |
35 | );
36 | }
37 |
38 | /**
39 | * Create a 1-dimensional array of size 'length', where the 'i'th entry
40 | * is initialized to 'f(i)', or 'undefined' if 'f' is not passed.
41 | */
42 | function array1d(length, f) {
43 | return Array.from({length: length}, f ? ((v, i) => f(i)) : undefined);
44 | }
45 |
46 | /**
47 | * Create a 2-dimensional array of size 'height' x 'width', where the 'i','j' entry
48 | * is initialized to 'f(i, j)', or 'undefined' if 'f' is not passed.
49 | */
50 | function array2d(height, width, f) {
51 | return Array.from({length: height}, (v, i) => Array.from({length: width}, f ? ((w, j) => f(i, j)) : undefined));
52 | }
53 |
54 | function array3d(depth, height, width, f) {
55 | return Array.from({length: depth}, (v, i) =>
56 | Array.from({length: height}, (v, j) =>
57 | Array.from({length: width},
58 | f ? ((w, k) => f(i, j)) : undefined)));
59 | }
60 |
61 | // We use the next two functions (maxWhile and minWhile) to
62 | // inefficiently compute the bounds for various parameters
63 | // given fixed values for other parameters.
64 |
65 | /**
66 | * Given a predicate 'pred' and a starting integer 'start',
67 | * find the largest integer i >= start such that 'pred(i)'
68 | * is true OR end, whichever is smaller.
69 | */
70 | function maxWhile(start, end, pred) {
71 | for (let i = start; i <= end; i++) {
72 | if (pred(i)) continue;
73 | return i - 1;
74 | }
75 | return end;
76 | }
77 |
78 | /**
79 | * Given a predicate 'pred' and a starting integer 'start',
80 | * find the smallest integer i <= start such that 'pred(i)'
81 | * is true OR end, whichever is larger.
82 | */
83 | function minWhile(start, end, pred) {
84 | for (let i = start; i >= end; i--) {
85 | if (pred(i)) continue;
86 | return i + 1;
87 | }
88 | return end;
89 | }
90 |
91 | function watermarks(view_height, view_width, stride_height, stride_width) {
92 | // NB: both of these watermarks are INCLUSIVE
93 | // For example, if all strides are 0, we get [0, 0], which is true, we
94 | // will access the memory at 0.
95 | // NB: this does the RIGHT THING when height/width is zero. Then high
96 | // watermark is negative while low watermark is zero, meaning the
97 | // empty range, which is precisely correct.
98 |
99 | let high_watermark = 0;
100 | if (stride_height > 0) high_watermark += (view_height - 1) * stride_height;
101 | if (stride_width > 0) high_watermark += (view_width - 1) * stride_width;
102 |
103 | let low_watermark = 0;
104 | if (stride_height < 0) low_watermark += (view_height - 1) * stride_height;
105 | if (stride_width < 0) low_watermark += (view_width - 1) * stride_width;
106 |
107 | return [low_watermark, high_watermark];
108 | }
109 |
110 | function paramsOK(storage_height, storage_width, storage_offset, view_height, view_width, stride_height, stride_width) {
111 | const wms = watermarks(view_height, view_width, stride_height, stride_width);
112 |
113 | if (wms[1] < wms[0]) return true;
114 |
115 | const storage_size = storage_height * storage_width;
116 | return wms[0] + storage_offset >= 0 && wms[1] + storage_offset < storage_size;
117 | }
118 |
119 | /**
120 | * Top-level component for the entire visualization. This component
121 | * controls top level parameters like input sizes, but not the mouse
122 | * interaction with the actual visualized grids.
123 | */
124 | class App extends React.Component {
125 | constructor(props) {
126 | super(props);
127 | this.state = {
128 | storage_height: 4,
129 | storage_width: 4,
130 | storage_offset: 0,
131 | view_height: 4,
132 | view_width: 4,
133 | stride_height: 4,
134 | stride_width: 1,
135 | };
136 | }
137 |
138 | // React controlled components clobber saved browser state, so
139 | // instead we manually save/load our state from localStorage.
140 |
141 | componentDidMount() {
142 | const state = localStorage.getItem("stride-visualizer");
143 | if (state) {
144 | this.setState(JSON.parse(state));
145 | }
146 | }
147 |
148 | componentDidUpdate() {
149 | localStorage.setItem("stride-visualizer", JSON.stringify(this.state));
150 | }
151 |
152 | render() {
153 | const storage_height = this.state.storage_height;
154 | const storage_width = this.state.storage_width;
155 | const storage_offset = this.state.storage_offset;
156 | const view_height = this.state.view_height;
157 | const view_width = this.state.view_width;
158 | const stride_height = this.state.stride_height;
159 | const stride_width = this.state.stride_width;
160 |
161 | const onChange = (state_key) => {
162 | return (e) => {
163 | const r = parseInt(e.target.value, 10);
164 | // Text inputs can sometimes temporarily be in invalid states.
165 | // If it's not a valid number, refuse to set it.
166 | if (typeof r !== "undefined") {
167 | this.setState({[state_key]: r});
168 | }
169 | };
170 | };
171 |
172 | const max_storage = 64;
173 | const max_size = 8;
174 | const max_stride = 8;
175 |
176 | return (
177 | 181 | Strides specify a factor by which an index is multiplied when computing its 182 | index into an array. Strides are surprisingly versatile and can be used 183 | to program a large number of access patterns: 184 |
185 |