Coder Social home page Coder Social logo

jwilber / roughviz Goto Github PK

View Code? Open in Web Editor NEW
6.6K 55.0 229.0 3.49 MB

Reusable JavaScript library for creating sketchy/hand-drawn styled charts in the browser.

Home Page: https://www.jwilber.me/roughviz/

License: MIT License

JavaScript 92.98% HTML 5.90% CSS 1.12%
data-visualization d3v5 dashboard data-science visualization charting-library

roughviz's Issues

Python Implementation

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. ^^

HTML links to labels/titles

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?

Line data as Object (no csv or tsv)

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 ?

Legend for `roughViz.StackedBar`

I think it is not possible at the moment, but would it be possible to have a legend for roughViz.StackedBar?

Thanks in advance

Installation via npm contains additional roughviz.min.js

Not really an issue but thought I would mention it. When installed via npm the dist folder contains two resources for the minified code:-

image

It wasn't immediately clear which one to use so maybe you can remove one.

Thanks for a great package.

Ability to clear and re-draw

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.

Usage with React TypeScript

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.

Abstract Class

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!

Crash with a zero value in Pie/Donut chart with Firefox 70.0.1

Description

When a value is 0 in a pie/donut chart, it makes the browser freezing (consuming tons of memory).

How to reproduce

Open the example for Pie/Donut and set a data value to 0

Workarounds

  • Zero values could be filtered
  • You can put a very small value

x axis labels in line plot as strings

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!

Make charts responsive

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)

Save roughViz resulting chart to SVG

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...

Legend overlaps title

Hi, when title is a bit longer, the legend overlaps the title text in Pie and Donut charts.
image

Axis-caption and hovering message-box displaced

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.

rv1
rv2

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.

Line chart with local data

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.

Not working with Svelte = Cannot read property 'ownerDocument' of null

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:-

content of index.js as installed via npm

// 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 trace

var 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 to index.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

When drawing a pie chart, somehow there are areas which are drawn two times, as visible in the screenshot. also, the message popup box on hover is displaced underneath the graph.

b1

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.

Different color bars in stacked bar

I messed with the example and changed the data on the 'March' row. I replaced B with D. (Perhaps March doesn't have a B)
Nothing happened.
I was expecting the March column (right-most) to have a different color for the D 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:-

roughViz

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:-

chartJs

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

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;

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, the src 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 πŸ™‚

Label names overlap each other

Hello, I'm using your package to help me do some survey data analysis. And although I love the way the bar chart looks, my labels by the x-axis overlap. I tried to see if there were any attributes in the Bar function that help rotate the labels, but I don't see anything for that.
Bar graph

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 photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.