Git Product home page Git Product logo

ac-nh-turnip-prices's People

Contributors

akccakcctw avatar arctco avatar blaaaakuu avatar captaincurls avatar catstarwind avatar daniel-giralt-len avatar devsplash avatar frederikgoovaerts avatar gnehs avatar hank076 avatar harrypeach avatar keruwolf avatar lluk avatar lysoun avatar manu-alonso avatar mcpower avatar mikebryant avatar molorius avatar nullying avatar peter50216 avatar popiolken avatar promarcel avatar rctgamer3 avatar smileyhead avatar sujeom avatar thertc204 avatar tingozhu avatar trevor-welch avatar tybantarnusa avatar valdotdev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ac-nh-turnip-prices's Issues

API?

Disclosure: Webdev is a bit out of my field of expertise.

If there's interest for it and an API I can consume though, I can write a handy mobile app - just for the heck of it!

EDIT: I could always just write the prediction calculations myself, probably. But if somebody else has done that bit and got it working fine, I'd rather just collab with them on something than rewrite unnecessarily.

Spreadsheet not loading for some reason?

Opening the app in Chrome on Mac OS. For some reason when I enter the values, the spreadsheet isn't showing up? It works on Incognito for some reason though. Here's the error that's logged in the console when I run the page.

Screen Shot 2020-04-13 at 10 25 51 PM

Guaranteed Minimum (All Patterns) -- math error

The guaranteed minimum for "All Patterns" (the last row) appears to be taking the maximum of the minimums for the "All Patterns" row. However, it seems to me that the guaranteed minimum should actually be the minimum of the guaranteed minimums for all rows above it.

See below. The guaranteed minimum should be 128 rather than 78.

image

[Feature Proposal] Sort output rows by probability, and keep the "All patterns" row at the top.

Reading the output table could be more intuitive if the rows were sorted by their probability, so that the most likely outcome is the first one that the user sees.

It would also be nice keep the "All patterns" row at the top of the table as a summary.

If either of these changes sound good, I'd be happy to make a pull request referencing this issue that implements them.

Here's a side-by-side of the current and proposed behavior:
current_behavior

proposed_change

Can we add statistic feature?

some statistic analysis will be nice, mean, median..etc, and it''s better to show total probability of pattern 0/1/2/3
here are some expected peak value of each pattern I derived from the code
mean_p0 = 1.3375
mean_p1 = 4
mean_p2 = 0.595 (or using current day price)
mean_p3 = 1.7

You could please add support to acept get parameters to auto set the form values?

Hi. The idea would be to be able to put a url like this:

https://turnipprophet.io/index.html?buy=90&sell_2=80

And then it was automatically put into the buy input and added to the table.

Only these functions would be necessary:

function getSearchParameters() {
    var prmstr = window.location.search.substr(1);
    return prmstr != null && prmstr != "" ? transformToAssocArray(prmstr) : {};
}

function transformToAssocArray(prmstr) {
    var params = {};
    var prmarr = prmstr.split("&");
    for (var i = 0; i < prmarr.length; i++) {
        var tmparr = prmarr[i].split("=");
        params[tmparr[0]] = tmparr[1];
    }
    return params;
}

With these functions it would only remain to do something similar to this when the page starts:

const params = getSearchParameters();
document.getElementById('buy').value = params.buy;
document.getElementById('sell_2').value = params.sell_2;

If you could please add it it would be great, if you have any questions don't hesitate to ask me and I will help you.

Thank you very much in advance, greetings!

Can we break out the part that does the calculations?

In another project, we’re using the chunk of code in scripts.js above the jQuery that does the actual predictions (generate_possibilities and everything it depends on). We’re generating a google sheet instead of HTML. It’d be nice if the prediction code was separate from the code that generates the HTML.

I was thinking the best way would be to move the code into another git repository and include it in this one as a sub module. That way your website is independent of the code that generates the predictions (would be nice for our project). Also we could make an npm package potentially.

Let me know what you think 😊

Chance of each pattern

Hi! I love this tool and use it every day with my friends :) It paid off last week when someone with a predicted high hit it (519 bells)!

I've been looking at the reverse engineered code, too, and have an idea I'd like to implement, and I'd be happy to take a stab at a pull request if you're interested. What we know is that each of the four price patterns have a chance of happening depending on the previous week's pattern. (So if you had a decreasing pattern last week, your chance of a large spike this week is 50%). Right now, each of the patterns are shown, but we don't have a sense of what are the odds of each pattern occurring.

I'm thinking of adding a dropdown where you can select which of the four patterns you had the previous week (or "unknown", the default choice). It won't be required, only if you happen to know what it is. The results will then calculate what are the odds of each pattern happening. And when we know some patterns won't happen, the chances will be recalculated based on available patterns. It should give someone much more clarity on what their odds are of hitting a large spike, given their prices and previous week's behavior!

Let me know what you think and if you would like me to help!

All patterns result

I put in my totals for sun, Mon, Tues, and then after wed am entered, it showed the result as "all patterns" with 0-999 under every prediction. It is a first week input.

Issue with calculate week

I tried to set my week and can't calculate it. If you put 130+ bells in any of the label gives you an error.

I bought turnips in Sunday at 91 bells.
Monday AM were at 120 bells
Monday PM were at 147 bells
Tuesday AM were at 197 bells
Tuesday PM were at 199 bells
Wednesday AM were at 99 bells
Wednesday PM were at 95 bells

Impossible pattern?

I purchased my turnips yesterday for 104 apiece. This morning (Monday AM) they were 97, and now (Monday PM) they are 92.

I am 100% positive these are the correct prices, but the app has no patterns that allow for 92 right now if I purchased at 104 yesterday. Is this a floating point case or have I screwed up using it?

(Understood if it may be hard to solve from this minimal case, but it may help as a data point for another issue. This is also my third week in the stalk market, so I don't think anything special applies)

Reset executed on "Enter"

When entering the current price, I accidentally hit enter in an attempt to "submit" the value.

This sadly triggered the reset functionality, as it seems to try and submit the values with the next button (Reset Turnip Prophet)

It would be good to somehow prevent the enter event or to add some confirmation dialog

Tooltip descriptions for pattern names

I am currently thinking that having shorter pattern names would be desirable.

I also think having a more detailed description of how they would work be nice.

My thoughts as to how to solve this are to put them in the table with short names, and on hover or tap on mobile they should explain what happens in longer words.

Add probabilities when user does not know previous pattern

We can calculate the steady state probability of any given pattern using a Markov model. Thus, we can get initial probabilities for the 4 states and display it to the users when they select "I don't know" for the "Previous Pattern". This builds on issue #13.

If my calculations are correct (details below; someone please verify), given no information about the previous week and no information on Mon - Sat for the current week, the chances for each pattern are:

pattern0: 0.346
pattern1: 0.247
pattern2: 0.148
pattern3: 0.259

I'm using a Markov chain with the pattern numbers & probabilities from Ninji's TurnipPrices::calculate(). And, since I've not done these calculations in over a decade, I'm using an online calculator with the following data:

0.20 0.30 0.15 0.35
0.50 0.05 0.20 0.25
0.25 0.45 0.05 0.25
0.45 0.25 0.15 0.15

I'm not certain how this is affected by issue #48, which seeks to update probabilities based on Bayes. I suspect the problems are independent; that is, the solution for issue #48 will just work when given these probabilities. If not, maybe a HMM would work?

Dark Mode

The amount of white space is near blinding when playing the Stalk Market late at night. I would love to have a feature to enable dark mode!

Option to hide/collapse past columns

Similar to #36 but I think it would be useful to have the option to collapse or hide the columns from the data you've already entered (except perhaps the current day's prices). It's additional clutter and would be useful for clarity on remaining days.

UX: Add a note clarifying how many data points are needed "at minimum", "ideally", etc

Thanks for all your amazing work on this tool! I had a UX suggestion, and I hope this is the right place.

As a new user, I'm unable to determine what's the bare minimum number of data points needed to obtain an accurate estimate. I can enter just one data point and see results, but is this enough? The tool allows for data entry all the way until Saturday evening, which may cause users to question if they need to wait that long before getting accurate data and selling their turnips. I'm also not sure if missing a day (let's say I forget to check Tuesday morning) invalidates my projections for the week. Do I need to enter the purchase price from Daisy Mae? Or can the tool create an accurate estimate without it? Etc etc.

Some short descriptions added to the main page may help alleviate this!

I'd also recommend blocking projection generation if the user hasn't entered enough data, or hasn't entered required data. For example, if the purchase price from Daisy Mae hasn't been entered and is indeed essential for the projection, display a small note in place of the projection informing the player they need to enter this data first.

pattern description question

Hello! Me again :D

I was wondering what is meant with high, decreasing, high, decreasing, high

Actually i'm in this prediction (very reliable)

Screen Shot 2020-04-08 at 13 56 15

and I'm wondering if "high" means that the price will always grow or if it will stay in the "high price" area...

Let me be more clear with an example:

  • It is possible that the price will grow from friday to saturday until a max <=152
    or that
  • the price will remain between 93 and 152 fluctuating on friday and saturday?

An help box with the explaination of the pattern terms could be useful.

Thank you and great domain name :D

Guaranteed Min 999. Potential Max 0.

Hi.

When entering the following values, I am getting my results as guaranteed Min 999 and potential Max as 0.

First time: No
Pattern: Decreasing
Buy Price: 101
Monday: 89AM - 77PM

When clearing the Buy Price it readjusts to Guaranteed min 89 and Potential max 139.

Any ideas why it’s not calculating correctly when I enter my buy price?

Small spike 3rd and 5th price ranges incorrect

    sellPrices[work++] = intceil(randfloat(0.9, 1.4) * (float)basePrice);
    sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice);
    rate = randfloat(1.4, 2.0);
    sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1;
    sellPrices[work++] = intceil(rate * basePrice);
    sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1;
  • The min and max prices for the 3rd and 5th periods of a small spike should be 1 lower than the min and max prices for the 4th period.

  • Once the 4th period's price is known, the max price for the 5th period should be 1 less than the 4th period's price.

Thanks for the handy tool!

Edit: I just noticed this is already mentioned in a TODO comment :)

Unpredicted high spike

Hi,
I have tested your tool with the following pattern, which I just got :
Sell Price : 95
Buy Prices : 79, 76, 74, 69, 95, xx, 529
For the 6th value, it was around 145 but I can not tell more precisely (I sold them on an other island before that).
Even entering all the values, your tool can not predict the 529, nor the large spike. It will only predict a small spike if you do not enter the 6th or 7th value, and won't be able to tell the pattern if you do.
No time travel, and no FirstKabuBuy involved.

Thanks a lot for all your work :)
Regards,

decreasing, spike, decreasing pattern: ceiling function should be applied to minimum spike value

Based on my own turnip prices, and Treeki's original gist, I think there is a small inaccuracy with the spike in the "decreasing, spike, decreasing" pattern. Here's output from the website for my turnip prices (note the 86 value in both predictions):

issue

According to the site, the min for when the spike starts is 86, but this is inconsistent with the Treeki's original gist. For this pattern, the spike values are calculated like so:

sellPrices[work++] = intceil(randfloat(0.9, 1.4) * (float)basePrice);
sellPrices[work++] = intceil(randfloat(0.9, 1.4) * basePrice);
rate = randfloat(1.4, 2.0);
sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1;
sellPrices[work++] = intceil(rate * basePrice);
sellPrices[work++] = intceil(randfloat(1.4, rate) * basePrice) - 1;

So for basePrice=96, the min for the start of the spike should be ceil(0.9*96) = 87. Forgive me, I'm not 100% certain of the relevant part in the code, but it appears to be here where instead of Math.floor it should be Math.ceil

Probability may not be accurate

current algorithm calculates probability via redistribution probability based on user input price falling into some price intervals and rule out those impossible ones, right? But I think this way may not be accurate. (still a good approximation)

say 100 for buy price and 85 on MonAM and last week is pattern3, it rule out pattern0 and redistribute. Total probabilities are:

pattern1 = 0.25*1   /(0.25*1+0.15*7/8+0.15*1)=0.47/7=0.0672each
pattern2 = 0.15*7/8 /(0.25*1+0.15*7/8+0.15*1)=0.28
pattern3 = 0.15*1   /(0.25*1+0.15*7/8+0.15*1)=0.247/7=0.0353 each

But for pattern 1 and 2 (they follow same rule on Mon so treat as one), it needs : rate = 0.85 for rate = randfloat(0.85,0.9)
and for pattern3, it needs: rate = 0.85 for rate = randfloat(0.4,0.9)
clearly the probability for pattern3 to get rate = 0.85 is much smaller(10 times) than pattern 1 and 2

using bayes equation it should be:

pattern1 = 0.25*0.01/(0.9-0.85) /( 0.25*0.01/(0.9-0.85)+0.15*0.01/(0.9-0.4)+0.15*0.01/(0.9-0.85))=0.602/7=0.0860 each
pattern2 = 0.15*0.01/(0.9-0.85) /( 0.25*0.01/(0.9-0.85)+0.15*0.01/(0.9-0.4)+0.15*0.01/(0.9-0.85))=0.361
pattern3 = 0.15*0.01/(0.9-0.4)  /( 0.25*0.01/(0.9-0.85)+0.15*0.01/(0.9-0.4)+0.15*0.01/(0.9-0.85))=0.0361/7=0.0051 each

or generally
P(pattern is k | input price is X) = P( X | k) *P(k) / sum(P( X | k) *P(k)) k from 0 to 3

key here is P( X | k), under pattern_k find probability of input price X, i think assuming X is uniformly distributed in calculated maxmin interval is a good start, since most of turnip price is generated by sellPrices[work++] = intceil(randfloat(A, B) * basePrice);, only for decreasing pattern like

rate = randfloat(0.9, 0.85);
    for (work = 2; work < peakStart; work++)
    {
      sellPrices[work] = intceil(rate * basePrice);
      rate -= 0.03;
      rate -= randfloat(0, 0.02);
    }

the price X = A-sum(B_k), A and B are uniform distributed but X is bell shape distributed

Spanish translation!

I'm in charge of an ACNH discord in Spanish and would love to help edit a Spanish version of the calculator, don't know much about HTML5 or coding in general, but could pass you a doc with full translations! Im awaiting any confirmation if my help is needed!

Insert Turnip Quantity Field

May be a useful visual representation of how much you stand to make. Obviously the math can be done manually very quickly but people appreciate the visual value done for them.

My pattern is invalid?

image

My turnip price was 108, barely above the range of valid patterns I get for my shop prices this week. I have not bought from Daisy Mae on my own island yet, but my friend did come to buy from her, so maybe that has something to do with it?

Layout/Design: Scroll bars are too small

If anyone is working specifically on the design/styling, suggest making the scroll bars for the table at least twice as wide; as it is they are difficult to target.

Page incorrectly logging prices

The newly designed webpage is currently recognising the AM/PM inputs for each day as being for separate days in the prediction outputs.

So Monday PM is logged as Tuesdays price, Tuesday AM is Wednesdays price, etc.
Screenshot attached. Error occurs in chrome for Android.

Screenshot_20200407-123639_Chrome

Guaranteed Minimum?

Just noticed that the final two columns are Guaranteed Minimum and Potential Maximum. I am assuming both are meant for the highest price of the week, correct?

If that is the case, perhaps we should rename the guaranteed min column to something like "Lowest Potential Maximum" and the current potential max to "Highest Potential Maximum". Maybe it's just me, but "guaranteed minimum" seems to imply that turnips will not go below that price for the entire week.

[Feature Proposal] Overall Percent Chance

Hello,

I was wondering if this could be an additional feature to continue with the statistic trend.

image

I made a working sample for the current version as of 4/12/2020. It loops through the data found in the table and adds all of the percent chances per category.

I use the Custom JavaScript for Websites 2 extension for Chrome to run outside scripts into a page. If anyone wants to sample this in their own browser, you can use the following JavaScript code:

var trend = {};

function getChances() {
  trend = {
    random: {
      chance: 0,
      count: 0,
      min: 0,
      max: 0
    },
    down: {
      chance: 0,
      count: 0,
      min: 0,
      max: 0
    },
    small: {
      chance: 0,
      count: 0,
      min: 0,
      max: 0
    },
    large: {
      chance: 0,
      count: 0,
      min: 0,
      max: 0
    }
  }
  $('#output tr').each(function(){
    var type = $(this).children('td:nth-child(1)').text();
    var percent = parseFloat($(this).children('td:nth-child(2)').text());
    var low = parseInt($(this).children('td:nth-child(16)').text());
    var high = parseInt($(this).children('td:nth-child(17)').text());
    
    switch (type) {
      case 'Fluctuating':
        type = trend.random;
        break;
      case 'Decreasing':
        type = trend.down;
        break;
      case 'Small spike':
        type = trend.small;
        break;
      case 'Large spike':
        type = trend.large;
        break;
    }
    
    type.chance = percent;
    type.count++;
    type.min = low;
    type.max = high;
  });
  
  var totalCount = trend.large.count + trend.small.count + trend.random.count + trend.down.count;
  var randomChance = isNaN(trend.random.chance) ? `${((trend.random.count / totalCount) * 100).toFixed(0)}%` : `${(trend.random.chance * trend.random.count).toFixed(0)}%`;
  var downChance = isNaN(trend.down.chance) ? `${((trend.down.count / totalCount) * 100).toFixed(0)}%` : `${(trend.down.chance * trend.down.count).toFixed(0)}%`;
  var smallChance = isNaN(trend.small.chance) ? `${((trend.small.count / totalCount) * 100).toFixed(0)}%` : `${(trend.small.chance * trend.small.count).toFixed(0)}%`;
  var largeChance = isNaN(trend.large.chance) ? `${((trend.large.count / totalCount) * 100).toFixed(0)}%` : `${(trend.large.chance * trend.large.count).toFixed(0)}%`;
  
  $('.table-wrapper').before(`<div id="extraStats">
    <style>
      #extraStats, #extraStats>div {
        width: 100%;
        display: flex;
        justify-content: space-around;
      }
      #extraStats>div>div>* {
        text-align: center;
      }
      @media (max-width: 768px) {
        #extraStats {
          flex-direction: column;
        }
      }
    </style>
    <div>
      <div>
        <h2>Fluctuating</h2>
        <h3>Chance: ${randomChance}</h3>
        <h3>Min/Max: ${trend.random.min}~${trend.random.max}</h3>
      </div>
      <div>
        <h2>Decreasing</h2>
        <h3>Chance: ${downChance}</h3>
        <h3>Min/Max: ${trend.down.min}~${trend.down.max}</h3>
      </div>
    </div>
    <div>
      <div>
        <h2>Small Spike</h2>
        <h3>Chance: ${smallChance}</h3>
        <h3>Min/Max: ${trend.small.min}~${trend.small.max}</h3>
      </div>
      <div>
        <h2>Large Spike</h2>
        <h3>Chance: ${largeChance}</h3>
        <h3>Min/Max: ${trend.large.min}~${trend.large.max}</h3>
      </div>
    </div>
  </div>`);
}

getChances();

$('body').on('DOMSubtreeModified', '#output', function(){
  $('#extraStats').remove();
  getChances();
});

Edit: I have modified the above code to include other added suggestions discussed in this issue.

image

Save data in local storage

  • save data automatically, so people can just come back to the site and add prices throughout the week
  • button to clear data (since refresh won't)

Translation

Hello!
It is possible in a near future to provide you a French translation? I would like to share your wonderful tool to some friends or people who are not good in English...

First Time Buyer Shows All Small Spike Patterns

When putting in a buy price and checking the box for first time buyer, instead of showing you only the options for small spike for your buy price, it shows all options for each buy price. I do not believe this is the intended functionality, I would assume you want to only show the options for your buy price. My guess that there is just a small issue with the JS that is not hiding the other options.

I would love to contribute to this project as well, I feel like more can be added to the already great tool you made (my thought is some sort of graph with a cone of uncertainty, since I am in Florida and love the cone of uncertainty from Hurricane season).

image

Incorrect Forecast?

Hi, I'm not sure if it's expected for the given ranges to be 100% accurate, but my turnip prices create 0 possible outcomes using your tool.

Buy 93
Monday AM: 115
Monday PM: ??? (Forgot to check)
Tuesday AM: 199
Tuesday PM: 215

If I enter just the "Buy" and "Monday AM" prices, I get a bunch of different outcomes, but the highest possible outcome is 186 for Tuesday AM.

Week Max uses Sunday's price

I'll try to be as clear as possible.

I noticed that "Week Max" column uses Sunday's buying price when it collects every price to get the highest, as we can see on this screenshot (I just removed all the other patterns with Ctrl+Shift+I). We can see that, on each prediction, I'll never reach 92 or more; however, Week Max returns 92, which is Sunday's price.

Min/max prices not reachable

I forgot my Monday AM prices, so I just entered the next 3 prices. The table shows me that my possible prices for Monday AM are 86..92, but if I update my Monday AM prices with either 86 or 92, no pattern is found, whereas with 87 to 91 it works fine.

I saw #8 but I'm not sure it's exactly the same issue. Sorry if it is.
If this is a different issue specific to these prices, here are the values to reproduce : buy price of 102, Monday PM 84, Tuesday AM 79, Tuesday PM 76.

First time buyer definition

Hi, I have to say it is an amazing tool for turnip market. However I'm a little bit confused by how this tool defines a 'first time buyer'. My case is: Daisy Mae came to my island for the first time this week but I actually bought turnips from another island. So in this case should I mark myself as a first time buyer?

All Patterns now sorted among other results

A recent change has introduced a bug where the All Patterns result is now sorted among the rest of the pattern results, rather than being at the end of the list.

image

This appears to be the result of #64. In the PR the contributor notes that sorting did not work in Chrome, however my testing seems to indicate that the only browser it did work in was Chrome.

Tooltip with additional info about "First Time Buyer"

Once the box is checked for "First time buyer", there is no way to input the buy price. However, based on the rows that emerge, it appears that the buy price does affect the forecast. Is there way to address this?

For reference, the input I'm working w/ is:

  • [Sun / Mon AM / Mon PM / Tue AM / Tue PM / Wed AM / Wed PM]:
  • [105 / 78 / 74 / 68 / 65 / 59 / 54 ]

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.