jwilber / roughviz Goto Github PK
View Code? Open in Web Editor NEWReusable JavaScript library for creating sketchy/hand-drawn styled charts in the browser.
Home Page: https://www.jwilber.me/roughviz/
License: MIT License
Reusable JavaScript library for creating sketchy/hand-drawn styled charts in the browser.
Home Page: https://www.jwilber.me/roughviz/
License: MIT License
Hello, thanks for making such awesome visualization style. I made a Python wrapper based on your RoughViz
You could check it out here:
https://github.com/charlesdong1991/py-roughviz
And would be appreciated if you'd like to add to your README, so that more people could see and try python version based on your library. ^^
Very cool project! I was just playing with this yesterday and I was wondering if there's a way to add HTML links to either the label and/or titles?
Hey there !
Thanks for the fantastic work :-)
I'd love to use the RoughViz Line without having to create .csv or .tsv files (getting data directly from a private db).
Is this something that can be easily enabled ?
I think it is not possible at the moment, but would it be possible to have a legend for roughViz.StackedBar
?
Thanks in advance
I'm currently using ref.current.querySelectorAll("*").forEach(n => n.remove());
where ref.current
refers to the DOM <div>
element.
This kinda works but it appears the y-axis is changing which might be a bug but I'd rather figure out a way to re-draw or clear.
I have been having difficulties in integrating this with my TypeScript project. It is an amazing library but if there is an article or example I can refer to. Well, that would be amazing.
And I have tried to use react-roughviz as well but found the documentation majorly lacking. Any help would be highly appreciated.
Originally I wanted to create a chart Abstract Base Class with the common methods across the charts (e.g. resolveFont()
, initChartValues()
, setSVG()
, resolveData()
, etc.) but didn't find a straightforward manner of doing so in ES6. I'm sure it can be done easily enough with prototypal inheritance, but I'd prefer to keep everything as ES6 as possible.
I don't write too much JavaScript, so it's pretty likely that I missed something. Very open to any ideas!
When a value is 0 in a pie/donut chart, it makes the browser freezing (consuming tons of memory).
Open the example for Pie/Donut and set a data value to 0
Hi,
I am trying to plot a line graph where my xaxis are times (as strings i.e. '7am'), I have the following code:
new roughViz.Line( { element: '#streaming', data: './static/availability.csv', title: 'Line Chart', x: 'time', y1: '# games', width: window.innerWidth / 2, } );
It will render if I remove 'x', but otherwise I can't get it to use strings as the values- possibly because it needs continuous values? If so, is there a workaround to replace the visible tick labels?
Thanks!
I was thinking about making the charts responsive.
As far as I can tell, this may be done by changing the width
and height
attributes for this.svg
in the setSvg()
method to preserveAspectRatio
and viewBox
as follows:
Old:
setSvg() {
this.svg = select(this.el)
.append('svg')
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom)
.append('g')
.attr('id', this.roughId)
.attr('transform',
'translate(' + this.margin.left + ',' + this.margin.top + ')');
}
New
setSvg() {
this.svg = select(this.el)
.append('svg')
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", `0 0 ${(this.width + this.margin.left + this.margin.right)}
${(this.height + this.margin.top + this.margin.bottom)}`)
.append('g')
.attr('id', this.roughId)
.attr('transform',
'translate(' + this.margin.left + ',' + this.margin.top + ')');
}
I can wait to make this change after you've finished the ABC, but since you're working on it (and I believe setSVG()
will be a method of the ABC), I figured you could add it, assuming it works.
Something to think about!
Originally posted by @jwilber in #32 (comment)
First of all: wonderful work!
Now... Would it be possible to save the resulting SVG chart to a SVG file?
Maybe using FileSave.js (eligrey/FileSaver.js#176)
(But in that case the chart would have to be converted to blob first... Is it possible?)
I think this would be a good workaround to make the result responsive...
Hello,
I have created a python wrapper for your library with most of the features.
You can check it out over here:
https://github.com/hannansatopay/roughviz
https://pypi.org/project/roughviz/
Bar chart doesn't work.
I tested this with the current npm package as well as your unpkg-link versions 1.0.5, 1.0.4 and 1.0.3. If I create a simple chart with own object-data, the labels are one chart-size beneath the bars. With the example from your docs there are two axis-labelings. When I hover the charts, the info box gets displayed in the area of the displaced label svg box.
If I position the parent div relative and the two svgs inside absolute, they are displayed correctly, but the message box on hover is completely displaced.
Awesome package, thank you!
I took the liberty to wrap it for React: https://www.npmjs.com/package/react-roughviz
Maybe you want to link to it?
Hi, would it be possible to use a line chart with local data, eg. objects instead of .csv files?
Edit: found out that when you pass an object an error occurs: this.data.map
is not a function. This is a bug in Line.js:464
.
Would like to open a PR for this.
The provided demo throws Cannot read property 'ownerDocument' of null
when used as a Svelte component. See the following Repl showing the issue.
The reason seems to be (as covered in this video by Svelte Master) that the index.js
is incorrect and contains the contents of what looks like a build / config issue:-
// modules are defined as an array // [ module function, map of requires ] // // map of requires is short require name -> numeric require // // anything defined in a previous bundle is accessed via the // orig method which is the require for previous bundles parcelRequire = (function (modules, cache, entry, globalName) { // Save the require from previous bundle to this closure if any var previousRequire = typeof parcelRequire === 'function' && parcelRequire; var nodeRequire = typeof require === 'function' && require;function newRequire(name, jumped) {
if (!cache[name]) {
if (!modules[name]) {
// if we cannot find the module within our internal map or
// cache jump to the current global require ie. the last bundle
// that was added to the page.
var currentRequire = typeof parcelRequire === 'function' && parcelRequire;
if (!jumped && currentRequire) {
return currentRequire(name, true);
}// If there are other bundles on this page the require from the // previous one is saved to 'previousRequire'. Repeat this as // many times as there are bundles until the module is found or // we exhaust the require chain. if (previousRequire) { return previousRequire(name, true); } // Try the node require function if it exists. if (nodeRequire && typeof name === 'string') { return nodeRequire(name); } var err = new Error('Cannot find module \'' + name + '\''); err.code = 'MODULE_NOT_FOUND'; throw err; } localRequire.resolve = resolve; localRequire.cache = {}; var module = cache[name] = new newRequire.Module(name); modules[name][0].call(module.exports, localRequire, module, module.exports, this); } return cache[name].exports; function localRequire(x){ return newRequire(localRequire.resolve(x)); } function resolve(x){ return modules[name][1][x] || x; }
}
function Module(moduleName) {
this.id = moduleName;
this.bundle = newRequire;
this.exports = {};
}newRequire.isParcelRequire = true;
newRequire.Module = Module;
newRequire.modules = modules;
newRequire.cache = cache;
newRequire.parent = previousRequire;
newRequire.register = function (id, exports) {
modules[id] = [function (require, module) {
module.exports = exports;
}, {}];
};var error;
for (var i = 0; i < entry.length; i++) {
try {
newRequire(entry[i]);
} catch (e) {
// Save first error but execute all entries
if (!error) {
error = e;
}
}
}if (entry.length) {
// Expose entry point to Node, AMD or browser globals
// Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
var mainExports = newRequire(entry[entry.length - 1]);// CommonJS if (typeof exports === "object" && typeof module !== "undefined") { module.exports = mainExports; // RequireJS } else if (typeof define === "function" && define.amd) { define(function () { return mainExports; }); // <script> } else if (globalName) { this[globalName] = mainExports; }
}
// Override the current require with this new one
parcelRequire = newRequire;if (error) {
// throw error from earlier, after updating parcelRequire
throw error;
}return newRequire;
})({"../node_modules/parcel-bundler/src/builtins/bundle-url.js":[function(require,module,exports) {
var bundleURL = null;function getBundleURLCached() {
if (!bundleURL) {
bundleURL = getBundleURL();
}return bundleURL;
}function getBundleURL() {
// Attempt to find the URL of the current script and use that as the base URL
try {
throw new Error();
} catch (err) {
var matches = ('' + err.stack).match(/(https?|file|ftp|chrome-extension|moz-extension)://[^)\n]+/g);if (matches) { return getBaseURL(matches[0]); }
}
return '/';
}function getBaseURL(url) {
return ('' + url).replace(/^((?:https?|file|ftp|chrome-extension|moz-extension)://.+)/[^/]+$/, '$1') + '/';
}exports.getBundleURL = getBundleURLCached;
exports.getBaseURL = getBaseURL;
},{}],"../node_modules/parcel-bundler/src/builtins/css-loader.js":[function(require,module,exports) {
var bundle = require('./bundle-url');function updateLink(link) {
var newLink = link.cloneNode();newLink.onload = function () {
link.remove();
};newLink.href = link.href.split('?')[0] + '?' + Date.now();
link.parentNode.insertBefore(newLink, link.nextSibling);
}var cssTimeout = null;
function reloadCSS() {
if (cssTimeout) {
return;
}cssTimeout = setTimeout(function () {
var links = document.querySelectorAll('link[rel="stylesheet"]');for (var i = 0; i < links.length; i++) { if (bundle.getBaseURL(links[i].href) === bundle.getBundleURL()) { updateLink(links[i]); } } cssTimeout = null;
}, 50);
}module.exports = reloadCSS;
},{"./bundle-url":"../node_modules/parcel-bundler/src/builtins/bundle-url.js"}],"../node_modules/parcel-bundler/src/builtins/hmr-runtime.js":[function(require,module,exports) {
var global = arguments[3];
var OVERLAY_ID = 'parcel__error__overlay';
var OldModule = module.bundle.Module;function Module(moduleName) {
OldModule.call(this, moduleName);
this.hot = {
data: module.bundle.hotData,
_acceptCallbacks: [],
_disposeCallbacks: [],
accept: function (fn) {
this._acceptCallbacks.push(fn || function () {});
},
dispose: function (fn) {
this._disposeCallbacks.push(fn);
}
};
module.bundle.hotData = null;
}module.bundle.Module = Module;
var checkedAssets, assetsToAccept;
var parent = module.bundle.parent;if ((!parent || !parent.isParcelRequire) && typeof WebSocket !== 'undefined') {
var hostname = "" || location.hostname;
var protocol = location.protocol === 'https:' ? 'wss' : 'ws';
var ws = new WebSocket(protocol + '://' + hostname + ':' + "57962" + '/');ws.onmessage = function (event) {
checkedAssets = {};
assetsToAccept = [];
var data = JSON.parse(event.data);if (data.type === 'update') { var handled = false; data.assets.forEach(function (asset) { if (!asset.isNew) { var didAccept = hmrAcceptCheck(global.parcelRequire, asset.id); if (didAccept) { handled = true; } } }); // Enable HMR for CSS by default. handled = handled || data.assets.every(function (asset) { return asset.type === 'css' && asset.generated.js; }); if (handled) { console.clear(); data.assets.forEach(function (asset) { hmrApply(global.parcelRequire, asset); }); assetsToAccept.forEach(function (v) { hmrAcceptRun(v[0], v[1]); }); } else { window.location.reload(); } } if (data.type === 'reload') { ws.close(); ws.onclose = function () { location.reload(); }; } if (data.type === 'error-resolved') { console.log('[parcel] β¨ Error resolved'); removeErrorOverlay(); } if (data.type === 'error') { console.error('[parcel] π¨ ' + data.error.message + '\n' + data.error.stack); removeErrorOverlay(); var overlay = createErrorOverlay(data); document.body.appendChild(overlay); }
};
}function removeErrorOverlay() {
var overlay = document.getElementById(OVERLAY_ID);if (overlay) {
overlay.remove();
}
}function createErrorOverlay(data) {
var overlay = document.createElement('div');
overlay.id = OVERLAY_ID; // html encode message and stack tracevar message = document.createElement('div');
var stackTrace = document.createElement('pre');
message.innerText = data.error.message;
stackTrace.innerText = data.error.stack;
overlay.innerHTML = '' + 'ERROR' + 'π¨' + '';' + message.innerHTML + '' + '' + stackTrace.innerHTML + '' + '
return overlay;
}function getParents(bundle, id) {
var modules = bundle.modules;if (!modules) {
return [];
}var parents = [];
var k, d, dep;for (k in modules) {
for (d in modules[k][1]) {
dep = modules[k][1][d];if (dep === id || Array.isArray(dep) && dep[dep.length - 1] === id) { parents.push(k); } }
}
if (bundle.parent) {
parents = parents.concat(getParents(bundle.parent, id));
}return parents;
}function hmrApply(bundle, asset) {
var modules = bundle.modules;if (!modules) {
return;
}if (modules[asset.id] || !bundle.parent) {
var fn = new Function('require', 'module', 'exports', asset.generated.js);
asset.isNew = !modules[asset.id];
modules[asset.id] = [fn, asset.deps];
} else if (bundle.parent) {
hmrApply(bundle.parent, asset);
}
}function hmrAcceptCheck(bundle, id) {
var modules = bundle.modules;if (!modules) {
return;
}if (!modules[id] && bundle.parent) {
return hmrAcceptCheck(bundle.parent, id);
}if (checkedAssets[id]) {
return;
}checkedAssets[id] = true;
var cached = bundle.cache[id];
assetsToAccept.push([bundle, id]);if (cached && cached.hot && cached.hot._acceptCallbacks.length) {
return true;
}return getParents(global.parcelRequire, id).some(function (id) {
return hmrAcceptCheck(global.parcelRequire, id);
});
}function hmrAcceptRun(bundle, id) {
var cached = bundle.cache[id];
bundle.hotData = {};if (cached) {
cached.hot.data = bundle.hotData;
}if (cached && cached.hot && cached.hot._disposeCallbacks.length) {
cached.hot._disposeCallbacks.forEach(function (cb) {
cb(bundle.hotData);
});
}delete bundle.cache[id];
bundle(id);
cached = bundle.cache[id];if (cached && cached.hot && cached.hot._acceptCallbacks.length) {
cached.hot._acceptCallbacks.forEach(function (cb) {
cb();
});return true;
}
}
},{}]},{},["../node_modules/parcel-bundler/src/builtins/hmr-runtime.js"], null)
//# sourceMappingURL=/index.js.map
Work arounds:-
import roughViz from 'node_modules/rough-viz/dist/rough-viz.min.js';
- rename the
roughviz.min.js
toindex.js
(which is probably related to issue #7)Thanks for the great package. They are a great alternative to the standard charts and makes the charts fun :)
piechart not drawn correctly
nice! didn't you run into rough.js problems with size?
I've used rough.js in another context, and found that the roughness factor had to be set for each general range of size--for small objects, the roughness was off the charts, and for large minor, all given the same roughness factor.
Example links in README are 404
All βExampleβ links under the Features section of the readme go to 404 pages.
Different color bars in stacked bar
I messed with the example and changed the data on the 'March' row. I replaced
B
withD
. (Perhaps March doesn't have a B)
Nothing happened.
I was expecting the March column (right-most) to have a different color for theD
value.data: [ {month:'Jan', A:20, B: 5, C: 10}, {month:'Feb', A:25, B: 10, C: 20}, {month:'March', A:30, D:50, C:10} ],
Unifiy titleFontSize
Some charts have
titleFontSize
with a default value set to'1rem'
and others set to'0.95rem'
.
For consistency, I think that all charts should have the same default value.Feature Request: Animated redraw / Individually colored bars
Would it be possible to add a method that will allow the chart values to be updated and for any changes in the bar value to be shown by animating/transitioning to the new value.
I was able to get to the chart to redraw by force removal of the SVG nodes:-
function refreshChart(values, labels) { if (chart !== undefined) { chart.data.labels = labels; chart.data.values = values; chart.svg.selectAll("*").remove(); chart.drawChart(); } }
This works but looks a little janky and has downsides such as no longer supporting tooltips, interactive highlight, etc. as the nodes are being recreated:-
It would be awesome if there was a way to update all the options and call refresh()/redraw() and have these changes reflected without having to destroy the SVG nodes:-
I would also like to be able to supply an array of colours for the bar charts similar to what is supported for the pie charts. Supporting only a single colour seems very limiting for such a cool chart.
Thanks
fillStyle in react-roughviz.Line doesn't work
Hi,
I found when 'fillStyle' is added in React component , it doesn't work.
It will be considered as a column in data and log an error during rendering.some options with a value of `0` are overridden with the defaults
I expected to be able to provide a value of
0
for some options, but when I do they are turned into the defaults:this.roughness = roughCeiling(opts.roughness) || 1; this.axisRoughness = opts.axisRoughness || 0.5; this.simplification = opts.simplification || 0.2;A spell error in readme.md
Deploy a 1.0.7
First off, I love this library, such a clever idea!
I'm building a website that is still a bit rough around the edges, and you can see that the graphs are broken (at the time of writing this), however this is a bug that has already been fixed.
It turns out that the contents of the
dist
folder in the npm package are not up to date, thesrc
folder is all good, but there are 2 dist files and neither are more recent than a year.Would you please consider rebuilding and publishing a
1.0.7
?Thanks again π
Vue-Wrapper
I created a Vue-Wrapper.
Have a look: https://github.com/jolo-dev/vue-roughvizLabel names overlap each other
Stacked bar chart?
Can this be done or is this a feature request?
Clickable bar charts
I'm using a stacked bar chart and I'd like things to happen when the user clicks bars. At the moment, a legend floats under the mouse cursor but I'd like to do more. I'd click to display information underneath the chart about the bar you clicked on.
Recommend Projects
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
TensorFlow
An Open Source Machine Learning Framework for Everyone
Django
The Web framework for perfectionists with deadlines.
Laravel
A PHP framework for web artisans
D3
Bring data to life with SVG, Canvas and HTML. πππ
Recommend Topics
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
web
Some thing interesting about web. New door for the world.
server
A server is a program made to process requests and deliver data to clients.
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Visualization
Some thing interesting about visualization, use data art
Game
Some thing interesting about game, make everyone happy.
Recommend Org
We are working to build community through open source technology. NB: members must have two-factor auth.
Microsoft
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba
Alibaba Open Source for everyone
D3
Data-Driven Documents codes.
Tencent
China tencent open source team.